%global appLoc serverName; %let compiled_apploc=/30.SASApps/3030.Projects/303001.DataController/build2/DataController; %let serverName=SASApp; %let appLoc=%sysfunc(coalescec(&appLoc,&compiled_apploc)); %let sasjs_clickmeservice=clickme; %let syscc=0; options ps=max nonotes nosgen nomprint nomlogic nosource2 nosource noquotelenmax; /* user supplied build vars */ /* user supplied build vars end */ /* system macro dependencies for build process */ %macro mf_getattrn( libds ,attr )/*/STORE SOURCE*/; %local dsid rc; %let dsid=%sysfunc(open(&libds,is)); %if &dsid = 0 %then %do; %put %str(WARN)ING: Cannot open %trim(&libds), system message below; %put %sysfunc(sysmsg()); -1 %end; %else %do; %sysfunc(attrn(&dsid,&attr)) %let rc=%sysfunc(close(&dsid)); %end; %mend mf_getattrn; %macro mf_nobs(libds )/*/STORE SOURCE*/; %mf_getattrn(&libds,NLOBS) %mend mf_nobs; %macro mf_abort(mac=mf_abort.sas, msg=, iftrue=%str(1=1) )/des='ungraceful abort' /*STORE SOURCE*/; %if not(%eval(%unquote(&iftrue))) %then %return; %put NOTE: /// mf_abort macro executing //; %if %length(&mac)>0 %then %put NOTE- called by &mac; %put NOTE - &msg; %abort; %mend mf_abort; /** @endcond */ %macro mf_verifymacvars( verifyVars /* list of macro variable NAMES */ ,makeUpcase=NO /* set to YES to make all the variable VALUES uppercase */ ,mAbort=SOFT )/*/STORE SOURCE*/; %local verifyIterator verifyVar abortmsg; %do verifyIterator=1 %to %sysfunc(countw(&verifyVars,%str( ))); %let verifyVar=%qscan(&verifyVars,&verifyIterator,%str( )); %if not %symexist(&verifyvar) %then %do; %let abortmsg= Variable &verifyVar is MISSING; %goto exit_err; %end; %if %length(%trim(&&&verifyVar))=0 %then %do; %let abortmsg= Variable &verifyVar is EMPTY; %goto exit_err; %end; %if &makeupcase=YES %then %do; %let &verifyVar=%upcase(&&&verifyvar); %end; %end; %goto exit_success; %exit_err: %put &abortmsg; %mf_abort(iftrue=(&mabort ne SOFT), mac=mf_verifymacvars, msg=%str(&abortmsg) ) 0 %return; %exit_success: 1 %mend mf_verifymacvars; %macro mm_getDirectories( path= ,outds=work.mm_getDirectories ,mDebug=0 )/*/STORE SOURCE*/; %local mD; %if &mDebug=1 %then %let mD=; %else %let mD=%str(*); %&mD.put Executing mm_getDirectories.sas; %&mD.put _local_; data &outds (keep=directoryuri name directoryname directorydesc ); length directoryuri name directoryname directorydesc $256; call missing(of _all_); __i+1; %if %length(&path)=0 %then %do; do while (metadata_getnobj("omsobj:Directory?@Id contains '.'",__i,directoryuri)>0); %end; %else %do; do while( metadata_getnobj("omsobj:Directory?@DirectoryName='&path'",__i,directoryuri) >0 ); %end; __rc1=metadata_getattr(directoryuri, "Name", name); __rc2=metadata_getattr(directoryuri, "DirectoryName", directoryname); __rc3=metadata_getattr(directoryuri, "Desc", directorydesc); &mD.putlog (_all_) (=); drop __:; __i+1; if sum(of __rc1-__rc3)=0 then output; end; run; %mend mm_getDirectories; %macro mf_getuniquefileref(prefix=_,maxtries=1000,lrecl=32767); %local rc fname; %if &prefix=0 %then %do; %let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl)); %if &rc %then %put %sysfunc(sysmsg()); &fname %end; %else %do; %local x len; %let len=%eval(8-%length(&prefix)); %let x=0; %do x=0 %to &maxtries; %let fname=&prefix%substr(%sysfunc(ranuni(0)),3,&len); %if %sysfunc(fileref(&fname)) > 0 %then %do; %let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl)); %if &rc %then %put %sysfunc(sysmsg()); &fname %return; %end; %end; %put unable to find available fileref after &maxtries attempts; %end; %mend mf_getuniquefileref; %macro mm_updatestpsourcecode(stp= ,stpcode= ,minify=NO ,mdebug=0 ); /* first, check if STP exists */ %local tsuri; %let tsuri=stopifempty ; data _null_; format type uri tsuri value $200.; call missing (of _all_); path="&stp.(StoredProcess)"; /* first, find the STP ID */ if metadata_pathobj("",path,"StoredProcess",type,uri)>0 then do; /* get sourcecode */ cnt=1; do while (metadata_getnasn(uri,"Notes",cnt,tsuri)>0); rc=metadata_getattr(tsuri,"Name",value); %if &mdebug=1 %then %do; put tsuri= value=; %end; if value="SourceCode" then do; /* found it! */ rc=metadata_getattr(tsuri,"Id",value); call symputx('tsuri',value,'l'); stop; end; cnt+1; end; end; else put (_all_)(=); run; %if &tsuri=stopifempty %then %do; %put %str(WARN)ING: &stp.(StoredProcess) not found!; %return; %end; %if %length(&stpcode)<2 %then %do; %put %str(WARN)ING: No SAS code supplied!!; %return; %end; %local frefin frefout; %let frefin=%mf_getuniquefileref(); %let frefout=%mf_getuniquefileref(); /* write header XML */ data _null_; file &frefin; put "$METAREPOSITORY ','>'); outstr=tranwrd(outstr,"'",'''); outstr=tranwrd(outstr,'"','"'); outstr=tranwrd(outstr,'0A'x,' '); outstr=tranwrd(outstr,'0D'x,' '); outstr=tranwrd(outstr,'$','$'); %if &minify=YES %then %do; outstr=cats(outstr); if outstr ne ''; if not (outstr=:'/*' and subpad(left(reverse(outstr)),1,2)='/*'); %end; outstr=trim(outstr); put outstr ' '; run; %end; data _null_; file &frefin mod; put "'>SAS268435456 "; run; proc metadata in= &frefin out=&frefout; run; %if &mdebug=1 %then %do; /* write the response to the log for debugging */ data _null_; infile &frefout lrecl=32767; input; put _infile_; run; %end; %else %do; filename &frefin clear; filename &frefout clear; %end; %mend mm_updatestpsourcecode; %macro mm_getrepos( outds=work.mm_getrepos )/*/STORE SOURCE*/; * use a temporary fileref to hold the response; filename response temp; /* get list of libraries */ proc metadata in= "1" out=response; run; /* write the response to the log for debugging */ /* data _null_; infile response lrecl=1048576; input; put _infile_; run; */ /* create an XML map to read the response */ filename sxlemap temp; data _null_; file sxlemap; put ''; put "/GetRepositories/Repositories/Repository"; put ""; put ''; put "/GetRepositories/Repositories/Repository/@Id"; put ""; put "characterstring200"; put ''; put ''; put "/GetRepositories/Repositories/Repository/@Name"; put ""; put "characterstring200"; put ''; put ''; put "/GetRepositories/Repositories/Repository/@Desc"; put ""; put "characterstring200"; put ''; put ''; put ""; put "/GetRepositories/Repositories/Repository/@DefaultNS"; put "characterstring200"; put ''; put ''; put ""; put "/GetRepositories/Repositories/Repository/@RepositoryType"; put "characterstring20"; put ''; put ''; put ""; put "/GetRepositories/Repositories/Repository/@RepositoryFormat"; put "characterstring10"; put ''; put ''; put ""; put "/GetRepositories/Repositories/Repository/@Access"; put "characterstring16"; put ''; put ''; put ""; put "/GetRepositories/Repositories/Repository/@CurrentAccess"; put "characterstring16"; put ''; put ''; put ""; put "/GetRepositories/Repositories/Repository/@PauseState"; put "characterstring16"; put ''; put ''; put "/GetRepositories/Repositories/Repository/@Path"; put ""; put "characterstring256"; put ''; put ''; put "/GetRepositories/Repositories/Repository/@Engine"; put ""; put "characterstring8"; put ''; put ''; put "/GetRepositories/Repositories/Repository/@Options"; put ""; put "characterstring32"; put ''; put ''; put ""; put "/GetRepositories/Repositories/Repository/@MetadataCreated"; put "characterstring24"; put ''; put ''; put ""; put "/GetRepositories/Repositories/Repository/@MetadataUpdated"; put "characterstring24"; put ''; put '
'; run; libname _XML_ xml xmlfileref=response xmlmap=sxlemap; proc sort data= _XML_.SASRepos out=&outds; by name; run; /* clear references */ filename sxlemap clear; filename response clear; libname _XML_ clear; %mend mm_getrepos; %macro mm_getservercontexts( outds=work.mm_getrepos )/*/STORE SOURCE*/; %local repo repocnt x; %let repo=%sysfunc(getoption(metarepository)); /* first get list of available repos */ %mm_getrepos(outds=work.repos) %let repocnt=0; data _null_; set repos; where repositorytype in('CUSTOM','FOUNDATION'); keep id name ; call symputx(cats('repo',_n_),name,'l'); call symputx('repocnt',_n_,'l'); run; filename __mc1 temp; filename __mc2 temp; data &outds; length serveruri servername $200; call missing (of _all_); stop; run; %do x=1 %to &repocnt; options metarepository=&&repo&x; proc metadata in= "$METAREPOSITORY ServerContextSAS 0" out=__mc1; run; /* data _null_; infile __mc1 lrecl=1048576; input; put _infile_; run; */ data _null_; file __mc2; put ''; put "/GetMetadataObjects/Objects/ServerContext"; put ""; put ''; put "/GetMetadataObjects/Objects/ServerContext/@Id"; put ""; put "characterstring200"; put ''; put ''; put "/GetMetadataObjects/Objects/ServerContext/@Name"; put ""; put "characterstring200"; put ''; put '
'; run; libname __mc3 xml xmlfileref=__mc1 xmlmap=__mc2; proc append base=&outds data=__mc3.SASContexts;run; libname __mc3 clear; %end; options metarepository=&repo; filename __mc1 clear; filename __mc2 clear; %mend mm_getservercontexts; %macro mp_abort(mac=mp_abort.sas, type=, msg=, iftrue=%str(1=1) , errds=work.mp_abort_errds , mode=REGULAR )/*/STORE SOURCE*/; %global sysprocessmode sysprocessname sasjs_stpsrv_header_loc sasjsprocessmode; %local fref fid i; %if not(%eval(%unquote(&iftrue))) %then %return; %put NOTE: /// mp_abort macro executing //; %if %length(&mac)>0 %then %put NOTE- called by &mac; %put NOTE - &msg; %if %symexist(_SYSINCLUDEFILEDEVICE) /* abort cancel FILE does not restart outside the INCLUDE on Viya 3.5 */ and %superq(SYSPROCESSNAME) ne %str(Compute Server) %then %do; %if "*&_SYSINCLUDEFILEDEVICE*" ne "**" %then %do; data &errds; iftrue='1=1'; length mac $100 msg $5000; mac=symget('mac'); msg=symget('msg'); run; data _null_; abort cancel FILE; run; %return; %end; %end; /* Web App Context */ %if %symexist(_PROGRAM) or %superq(SYSPROCESSNAME) = %str(Compute Server) or &mode=INCLUDE %then %do; options obs=max replace mprint; %if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5" %then %do; options nosyntaxcheck; %end; %if &mode=INCLUDE %then %do; %if %sysfunc(exist(&errds))=1 %then %do; data _null_; set &errds; call symputx('iftrue',iftrue,'l'); call symputx('mac',mac,'l'); call symputx('msg',msg,'l'); putlog (_all_)(=); run; %if (&iftrue)=0 %then %return; %end; %else %do; %put &sysmacroname: No include errors found; %return; %end; %end; /* extract log errs / warns, if exist */ %local logloc logline; %global logmsg; /* capture global messages */ %if %symexist(SYSPRINTTOLOG) %then %let logloc=&SYSPRINTTOLOG; %else %let logloc=%qsysfunc(getoption(LOG)); proc printto log=log;run; %let logline=0; %if %length(&logloc)>0 %then %do; data _null_; infile &logloc lrecl=5000; input; putlog _infile_; i=1; retain logonce 0; if ( _infile_=:"%str(WARN)ING" or _infile_=:"%str(ERR)OR" ) and logonce=0 then do; call symputx('logline',_n_); logonce+1; end; run; /* capture log including lines BEFORE the err */ %if &logline>0 %then %do; data _null_; infile &logloc lrecl=5000; input; i=1; stoploop=0; if _n_ ge &logline-15 and stoploop=0 then do until (i>22); call symputx('logmsg',catx('\n',symget('logmsg'),_infile_)); input; i+1; stoploop=1; end; if stoploop=1 then stop; run; %end; %end; %if %symexist(SYS_JES_JOB_URI) %then %do; /* setup webout for Viya */ options nobomfile; %if "X&SYS_JES_JOB_URI.X"="XX" %then %do; filename _webout temp lrecl=999999 mod; %end; %else %do; filename _webout filesrvc parenturi="&SYS_JES_JOB_URI" name="_webout.json" lrecl=999999 mod; %end; %end; %else %if %sysfunc(filename(fref,&sasjs_stpsrv_header_loc))=0 %then %do; options nobomfile; /* set up http header for SASjs Server */ %let fid=%sysfunc(fopen(&fref,A)); %if &fid=0 %then %do; %put %str(ERR)OR: %sysfunc(sysmsg()); %return; %end; %let rc=%sysfunc(fput(&fid,%str(Content-Type: application/json))); %let rc=%sysfunc(fwrite(&fid)); %let rc=%sysfunc(fclose(&fid)); %let rc=%sysfunc(filename(&fref)); %end; /* send response in SASjs JSON format */ data _null_; file _webout mod lrecl=32000 encoding='utf-8'; length msg syswarningtext syserrortext $32767 mode $10 ; sasdatetime=datetime(); msg=symget('msg'); %if &logline>0 %then %do; msg=cats(msg,'\n\nLog Extract:\n',symget('logmsg')); %end; /* escape the escapes */ msg=tranwrd(msg,'\','\\'); /* escape the quotes */ msg=tranwrd(msg,'"','\"'); /* ditch the CRLFs as chrome complains */ msg=compress(msg,,'kw'); /* quote without quoting the quotes (which are escaped instead) */ msg=cats('"',msg,'"'); if symexist('_debug') then debug=quote(trim(symget('_debug'))); else debug='""'; if symget('sasjsprocessmode')='Stored Program' then mode='SASJS'; if mode ne 'SASJS' then put '>>weboutBEGIN<<'; put '{"SYSDATE" : "' "&SYSDATE" '"'; put ',"SYSTIME" : "' "&SYSTIME" '"'; put ',"sasjsAbort" : [{'; put ' "MSG":' msg ; put ' ,"MAC": "' "&mac" '"}]'; put ",""SYSUSERID"" : ""&sysuserid"" "; put ',"_DEBUG":' debug ; if symexist('_metauser') then do; _METAUSER=quote(trim(symget('_METAUSER'))); put ",""_METAUSER"": " _METAUSER; _METAPERSON=quote(trim(symget('_METAPERSON'))); put ',"_METAPERSON": ' _METAPERSON; end; if symexist('SYS_JES_JOB_URI') then do; SYS_JES_JOB_URI=quote(trim(symget('SYS_JES_JOB_URI'))); put ',"SYS_JES_JOB_URI": ' SYS_JES_JOB_URI; end; _PROGRAM=quote(trim(resolve(symget('_PROGRAM')))); put ',"_PROGRAM" : ' _PROGRAM ; put ",""SYSCC"" : ""&syscc"" "; syserrortext=cats(symget('syserrortext')); if findc(syserrortext,'"\'!!'0A0D09000E0F010210111A'x) then do; syserrortext='"'!!trim( prxchange('s/"/\\"/',-1, /* double quote */ prxchange('s/\x0A/\n/',-1, /* new line */ prxchange('s/\x0D/\r/',-1, /* carriage return */ prxchange('s/\x09/\\t/',-1, /* tab */ prxchange('s/\x00/\\u0000/',-1, /* NUL */ prxchange('s/\x0E/\\u000E/',-1, /* SS */ prxchange('s/\x0F/\\u000F/',-1, /* SF */ prxchange('s/\x01/\\u0001/',-1, /* SOH */ prxchange('s/\x02/\\u0002/',-1, /* STX */ prxchange('s/\x10/\\u0010/',-1, /* DLE */ prxchange('s/\x11/\\u0011/',-1, /* DC1 */ prxchange('s/\x1A/\\u001A/',-1, /* SUB */ prxchange('s/\\/\\\\/',-1,syserrortext) )))))))))))))!!'"'; end; else syserrortext=cats('"',syserrortext,'"'); put ',"SYSERRORTEXT" : ' syserrortext; put ",""SYSHOSTNAME"" : ""&syshostname"" "; put ",""SYSJOBID"" : ""&sysjobid"" "; put ",""SYSSCPL"" : ""&sysscpl"" "; put ",""SYSSITE"" : ""&syssite"" "; sysvlong=quote(trim(symget('sysvlong'))); put ',"SYSVLONG" : ' sysvlong; syswarningtext=cats(symget('syswarningtext')); if findc(syswarningtext,'"\'!!'0A0D09000E0F010210111A'x) then do; syswarningtext='"'!!trim( prxchange('s/"/\\"/',-1, /* double quote */ prxchange('s/\x0A/\n/',-1, /* new line */ prxchange('s/\x0D/\r/',-1, /* carriage return */ prxchange('s/\x09/\\t/',-1, /* tab */ prxchange('s/\x00/\\u0000/',-1, /* NUL */ prxchange('s/\x0E/\\u000E/',-1, /* SS */ prxchange('s/\x0F/\\u000F/',-1, /* SF */ prxchange('s/\x01/\\u0001/',-1, /* SOH */ prxchange('s/\x02/\\u0002/',-1, /* STX */ prxchange('s/\x10/\\u0010/',-1, /* DLE */ prxchange('s/\x11/\\u0011/',-1, /* DC1 */ prxchange('s/\x1A/\\u001A/',-1, /* SUB */ prxchange('s/\\/\\\\/',-1,syswarningtext) )))))))))))))!!'"'; end; else syswarningtext=cats('"',syswarningtext,'"'); put ",""SYSWARNINGTEXT"" : " syswarningtext; put ',"END_DTTM" : "' "%sysfunc(datetime(),E8601DT26.6)" '" '; put "}" ; if mode ne 'SASJS' then put '>>weboutEND<<'; run; %put _all_; %if "&sysprocessmode " = "SAS Stored Process Server " %then %do; data _null_; putlog 'stpsrvset program err and syscc'; rc=stpsrvset('program error', 0); call symputx("syscc",0,"g"); run; %if &sysscp=WIN and 1=0 /* deprecating this logic until we figure out a consistent abort */ and "%substr(%str(&sysvlong ),1,8)"="9.04.01M" and "%substr(%str(&sysvlong ),9,1)">"5" %then %do; /* skip approach (below) does not work in windows m6+ envs */ endsas; %end; %else %do; /** * endsas kills 9.4m3 deployments by orphaning multibridges. * Abort variants are ungraceful (non zero return code) * This approach lets SAS run silently until the end :-) * Caution - fails when called within a %include within a macro * Use mp_include() to handle this. */ filename skip temp; data _null_; file skip; put '%macro skip();'; comment '%mend skip; -> fix lint '; put '%macro skippy();'; comment '%mend skippy; -> fix lint '; run; %inc skip; %end; %end; %else %if "&sysprocessmode " = "SAS Compute Server " %then %do; /* endsas kills the session making it harder to fetch results */ data _null_; syswarningtext=symget('syswarningtext'); syserrortext=symget('syserrortext'); abort_msg=symget('msg'); syscc=symget('syscc'); sysuserid=symget('sysuserid'); iftrue=symget('iftrue'); put (_all_)(/=); call symputx('syscc',0); abort cancel nolist; run; %end; %else %do; %abort cancel; %end; %end; %else %do; %put _all_; %abort cancel; %end; %mend mp_abort; /** @endcond */ %macro mf_isblank(param )/*/STORE SOURCE*/; %sysevalf(%superq(param)=,boolean) %mend mf_isblank; %macro mp_dropmembers( list /* space separated list of datasets / views */ ,libref=WORK /* can only drop from a single library at a time */ ,iftrue=%str(1=1) )/*/STORE SOURCE*/; %if not(%eval(%unquote(&iftrue))) %then %return; %if %mf_isblank(&list) %then %do; %put NOTE: nothing to drop!; %return; %end; proc datasets lib=&libref nolist; delete &list; delete &list /mtype=view; run; %mend mp_dropmembers; %macro mm_createstp( stpname=SASjs Default STP ,stpdesc=This stp was created automatically by the mm_createstp macro ,filename=mm_createstp.sas ,directory=SASEnvironment/SASCode ,tree=/User Folders/sasdemo ,package=false ,streaming=true ,outds=work.mm_createstp ,mDebug=0 ,server=SASApp ,stptype=1 ,minify=NO ,frefin=mm_in ,frefout=mm_out ,LogicalServerType=Sps )/*/STORE SOURCE*/; %local mD; %if &mDebug=1 %then %let mD=; %else %let mD=%str(*); %&mD.put Executing mm_CreateSTP.sas; %&mD.put _local_; %mp_abort( iftrue=(%mf_verifymacvars(stpname filename directory tree)=0) ,mac=&sysmacroname ,msg=%str(Empty inputs: stpname filename directory tree) ) %mp_dropmembers(%scan(&outds,2,.)) /* check LogicalServerType validity */ %mp_abort( iftrue=( &LogicalServerType ne Sps and &LogicalServerType ne Wks and &LogicalServerType ne Any ) ,mac=&sysmacroname ,msg=%str(Invalid value for LogicalServerType (&LogicalServerType)) ) /** * check tree exists */ data _null_; length type uri $256; rc=metadata_pathobj("","&tree","Folder",type,uri); call symputx('foldertype',type,'l'); call symputx('treeuri',uri,'l'); run; %if &foldertype ne Tree %then %do; %put %str(WARN)ING: Tree &tree does not exist!; %return; %end; /** * Check STP does not exist already */ %local cmtype; data _null_; length type uri $256; rc=metadata_pathobj("","&tree/&stpname",'StoredProcess',type,uri); call symputx('cmtype',type,'l'); call symputx('stpuri',uri,'l'); run; %if &cmtype = ClassifierMap %then %do; %put %str(WARN)ING: Stored Process &stpname already exists in &tree!; %return; %end; /** * Check that the physical file exists */ %if %sysfunc(fileexist(&directory/&filename)) ne 1 %then %do; %put %str(WARN)ING: FILE *&directory/&filename* NOT FOUND!; %return; %end; %if &stptype=1 %then %do; /* type 1 STP - where code is stored on filesystem */ %if %sysevalf(&sysver lt 9.2) %then %do; %put %str(WARN)ING: Version 9.2 or later required; %return; %end; /* check directory object (where 9.2 source code reference is stored) */ data _null_; length id $20 dirtype $256; rc=metadata_resolve("&directory",dirtype,id); call symputx('checkdirtype',dirtype,'l'); run; %if &checkdirtype ne Directory %then %do; %mm_getdirectories(path=&directory,outds=&outds ,mDebug=&mDebug) %if %mf_nobs(&outds)=0 or %sysfunc(exist(&outds))=0 %then %do; %put %str(WARN)ING: The directory object does not exist for &directory; %return; %end; %end; %else %do; data &outds; directoryuri="&directory"; run; %end; data &outds (keep=stpuri prompturi fileuri texturi); length stpuri prompturi fileuri texturi serveruri $256 ; if _n_=1 then call missing (of _all_); set &outds; /* final checks on uris */ length id $20 type $256; __rc=metadata_resolve("&treeuri",type,id); if type ne 'Tree' then do; putlog "%str(WARN)ING: Invalid tree URI: &treeuri"; stopme=1; end; __rc=metadata_resolve(directoryuri,type,id); if type ne 'Directory' then do; putlog "%str(WARN)ING: Invalid directory URI: " directoryuri; stopme=1; end; /* get server info */ __rc=metadata_resolve("&server",type,serveruri); if type ne 'LogicalServer' then do; __rc=metadata_getnobj("omsobj:LogicalServer?@Name='&server'",1,serveruri); if serveruri='' then do; putlog "%str(WARN)ING: Invalid server: &server"; stopme=1; end; end; if stopme=1 then do; putlog (_all_)(=); stop; end; /* create empty prompt */ rc1=METADATA_NEWOBJ('PromptGroup',prompturi,'Parameters'); rc2=METADATA_SETATTR(prompturi, 'UsageVersion', '1000000'); rc3=METADATA_SETATTR(prompturi, 'GroupType','2'); rc4=METADATA_SETATTR(prompturi, 'Name','Parameters'); rc5=METADATA_SETATTR(prompturi, 'PublicType','Embedded:PromptGroup'); GroupInfo= ""; rc6 = METADATA_SETATTR(prompturi, 'GroupInfo',groupinfo); if sum(of rc1-rc6) ne 0 then do; putlog "%str(WARN)ING: Issue creating prompt."; if prompturi ne . then do; putlog ' Removing orphan: ' prompturi; rc = METADATA_DELOBJ(prompturi); put rc=; end; stop; end; /* create a file uri */ rc7=METADATA_NEWOBJ('File',fileuri,'SP Source File'); rc8=METADATA_SETATTR(fileuri, 'FileName',"&filename"); rc9=METADATA_SETATTR(fileuri, 'IsARelativeName','1'); rc10=METADATA_SETASSN(fileuri, 'Directories','MODIFY',directoryuri); if sum(of rc7-rc10) ne 0 then do; putlog "%str(WARN)ING: Issue creating file."; if fileuri ne . then do; putlog ' Removing orphans:' prompturi fileuri; rc = METADATA_DELOBJ(prompturi); rc = METADATA_DELOBJ(fileuri); put (_all_)(=); end; stop; end; /* create a TextStore object */ rc11= METADATA_NEWOBJ('TextStore',texturi,'Stored Process'); rc12= METADATA_SETATTR(texturi, 'TextRole','StoredProcessConfiguration'); rc13= METADATA_SETATTR(texturi, 'TextType','XML'); storedtext='' !!"" !!""; rc14= METADATA_SETATTR(texturi, 'StoredText',storedtext); if sum(of rc11-rc14) ne 0 then do; putlog "%str(WARN)ING: Issue creating TextStore."; if texturi ne . then do; putlog ' Removing orphans: ' prompturi fileuri texturi; rc = METADATA_DELOBJ(prompturi); rc = METADATA_DELOBJ(fileuri); rc = METADATA_DELOBJ(texturi); put (_all_)(=); end; stop; end; /* create meta obj */ rc15= METADATA_NEWOBJ('ClassifierMap',stpuri,"&stpname"); rc16= METADATA_SETASSN(stpuri, 'Trees','MODIFY',treeuri); rc17= METADATA_SETASSN(stpuri, 'ComputeLocations','MODIFY',serveruri); rc18= METADATA_SETASSN(stpuri, 'SourceCode','MODIFY',fileuri); rc19= METADATA_SETASSN(stpuri, 'Prompts','MODIFY',prompturi); rc20= METADATA_SETASSN(stpuri, 'Notes','MODIFY',texturi); rc21= METADATA_SETATTR(stpuri, 'PublicType', 'StoredProcess'); rc22= METADATA_SETATTR(stpuri, 'TransformRole', 'StoredProcess'); rc23= METADATA_SETATTR(stpuri, 'UsageVersion', '1000000'); rc24= METADATA_SETATTR(stpuri, 'Desc', "&stpdesc"); /* tidy up if err */ if sum(of rc15-rc24) ne 0 then do; putlog "%str(WARN)ING: Issue creating STP."; if stpuri ne . then do; putlog ' Removing orphans: ' prompturi fileuri texturi stpuri; rc = METADATA_DELOBJ(prompturi); rc = METADATA_DELOBJ(fileuri); rc = METADATA_DELOBJ(texturi); rc = METADATA_DELOBJ(stpuri); put (_all_)(=); end; end; else do; fullpath=cats('_program=',treepath,"/&stpname"); putlog "NOTE: Stored Process Created!"; putlog "NOTE- "; putlog "NOTE-"; putlog "NOTE-" fullpath; putlog "NOTE- "; putlog "NOTE-"; end; output; stop; run; %end; %else %if &stptype=2 %then %do; /* type 2 stp - code is stored in metadata */ %if %sysevalf(&sysver lt 9.3) %then %do; %put %str(WARN)ING: SAS version 9.3 or later required to create type2 STPs; %return; %end; /* check we have the correct ServerContext */ %mm_getservercontexts(outds=contexts) %local serveruri; %let serveruri=NOTFOUND; data _null_; set contexts; where upcase(servername)="%upcase(&server)"; call symputx('serveruri',serveruri); run; %if &serveruri=NOTFOUND %then %do; %put %str(WARN)ING: ServerContext *&server* not found!; %return; %end; /** * First, create a Hello World type 2 stored process */ filename &frefin temp; data _null_; file &frefin; treeuri=quote(symget('treeuri')); serveruri=quote(symget('serveruri')); stpdesc=quote(symget('stpdesc')); stpname=quote(symget('stpname')); put "$METAREPOSITORY "/ ''/ " "/ " "/ " "/ " "/ ' '/ ' ' / " "/ " "/ ' '/ " "/ ""/ "SAS"/ "268435456"; run; filename &frefout temp; proc metadata in= &frefin out=&frefout ; run; %if &mdebug=1 %then %do; /* write the response to the log for debugging */ data _null_; infile &frefout lrecl=1048576; input; put _infile_; run; %end; /** * Next, add the source code */ %mm_updatestpsourcecode(stp=&tree/&stpname ,stpcode="&directory/&filename" ,mdebug=&mdebug ,minify=&minify) %end; %else %do; %put %str(WARN)ING: STPTYPE=*&stptype* not recognised!; %end; %mend mm_createstp; %macro mf_getuser( )/*/STORE SOURCE*/; %local user; %if %symexist(_sasjs_username) %then %let user=&_sasjs_username; %else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do; %let user=&SYS_COMPUTE_SESSION_OWNER; %end; %else %if %symexist(_metaperson) %then %do; %if %length(&_metaperson)=0 %then %let user=&sysuserid; /* sometimes SAS will add @domain extension - remove for consistency */ /* but be sure to quote in case of usernames with commas */ %else %let user=%unquote(%scan(%quote(&_metaperson),1,@)); %end; %else %let user=&sysuserid; %quote(&user) %mend mf_getuser; %macro mm_createfolder(path=,mDebug=0); %put &sysmacroname: execution started for &path; %local dbg errorcheck; %if &mDebug=0 %then %let dbg=*; %local parentFolderObjId child errorcheck paths; %let paths=0; %let errorcheck=1; %if &syscc ge 4 %then %do; %put SYSCC=&syscc - this macro requires a clean session; %return; %end; data _null_; length objId parentId objType parent child $200 folderPath $1000; call missing (of _all_); folderPath = "%trim(&path)"; * remove any trailing slash ; if ( substr(folderPath,length(folderPath),1) = '/' ) then folderPath=substr(folderPath,1,length(folderPath)-1); * name must not be blank; if ( folderPath = '' ) then do; put 'ERR' +(-1) "OR: &sysmacroname PATH parameter value must be non-blank"; end; * must have a starting slash ; if ( substr(folderPath,1,1) ne '/' ) then do; put 'ERR' +(-1) "OR: &sysmacroname PATH param value must have starting slash"; stop; end; * check if folder already exists ; rc=metadata_pathobj('',cats(folderPath,"(Folder)"),"",objType,objId); if rc ge 1 then do; put "NOTE: Folder " folderPath " already exists!"; stop; end; * do not create a root (one level) folder ; if countc(folderPath,'/')=1 then do; put 'ERR' +(-1) "OR: &sysmacroname will not create a new ROOT folder"; stop; end; * check that root folder exists ; root=cats('/',scan(folderpath,1,'/'),"(Folder)"); if metadata_pathobj('',root,"",objType,parentId)<1 then do; put 'ERR' +(-1) "OR: " root " does not exist!"; stop; end; * check that parent folder exists ; child=scan(folderPath,-1,'/'); parent=substr(folderpath,1,length(folderpath)-length(child)-1); rc=metadata_pathobj('',cats(parent,"(Folder)"),"",objType,parentId); if rc<1 then do; putlog 'The following folders will be created:'; /* folder does not exist - so start from top and work down */ length newpath $1000; paths=0; do x=2 to countw(folderpath,'/'); newpath=''; do i=1 to x; newpath=cats(newpath,'/',scan(folderpath,i,'/')); end; rc=metadata_pathobj('',cats(newpath,"(Folder)"),"",objType,parentId); if rc<1 then do; paths+1; call symputx(cats('path',paths),newpath); putlog newpath; end; call symputx('paths',paths); end; end; else putlog "parent " parent " exists"; call symputx('parentFolderObjId',parentId,'l'); call symputx('child',child,'l'); call symputx('errorcheck',0,'l'); &dbg put (_all_)(=); run; %if &errorcheck=1 or &syscc ge 4 %then %return; %if &paths>0 %then %do x=1 %to &paths; %put executing recursive call for &&path&x; %mm_createfolder(path=&&path&x) %end; %else %do; filename __newdir temp; options noquotelenmax; %local inmeta; %put creating: &path; %let inmeta=$METAREPOSITORY SAS268435456 ; proc metadata in="&inmeta" out=__newdir verbose; run ; /* check it was successful */ data _null_; length objId parentId objType parent child $200 ; call missing (of _all_); rc=metadata_pathobj('',cats("&path","(Folder)"),"",objType,objId); if rc ge 1 then do; putlog "SUCCCESS! &path created."; end; else do; putlog 'ERR' +(-1) "OR: unsuccessful attempt to create &path"; call symputx('syscc',8); end; run; /* write the response to the log for debugging */ %if &mDebug ne 0 %then %do; data _null_; infile __newdir lrecl=32767; input; put _infile_; run; %end; filename __newdir clear; %end; %put &sysmacroname: execution finished for &path; %mend mm_createfolder; %macro mm_deletestp( target= )/*/STORE SOURCE*/; /** * Check STP does exist */ %local cmtype; data _null_; length type uri $256; rc=metadata_pathobj("","&target",'StoredProcess',type,uri); call symputx('cmtype',type,'l'); call symputx('stpuri',uri,'l'); run; %if &cmtype ne ClassifierMap %then %do; %put NOTE: No Stored Process found at ⌖ %return; %end; filename __in temp lrecl=10000; filename __out temp lrecl=10000; data _null_ ; file __in ; put ""; put "SAS268436480"; put ""; run ; proc metadata in=__in out=__out verbose;run; /* list the result */ data _null_;infile __out; input; list; run; filename __in clear; filename __out clear; /** * Check deletion */ %local isgone; data _null_; length type uri $256; call missing (of _all_); rc=metadata_pathobj("","&target",'Note',type,uri); call symputx('isgone',type,'l'); run; %if &isgone = ClassifierMap %then %do; %put %str(ERR)OR: STP not deleted from ⌖ %let syscc=4; %return; %end; %mend mm_deletestp; /* system macro dependencies for build process end*/ /* system macros for build process */ %macro mm_createwebservice(path= ,name=initService ,precode= ,code=ft15f001 ,desc=This stp was created automagically by the mm_createwebservice macro ,mDebug=0 ,server=SASApp ,replace=YES ,adapter=sasjs )/*/STORE SOURCE*/; %if &syscc ge 4 %then %do; %put &=syscc - &sysmacroname will not execute in this state; %return; %end; %local mD; %if &mDebug=1 %then %let mD=; %else %let mD=%str(*); %&mD.put Executing mm_createwebservice.sas; %&mD.put _local_; * remove any trailing slash ; %if "%substr(&path,%length(&path),1)" = "/" %then %let path=%substr(&path,1,%length(&path)-1); /** * Add webout macro * These put statements are auto generated - to change the macro, change the * source (mm_webout) and run `build.py` */ filename sasjs temp; data _null_; file sasjs lrecl=3000 ; put "/* Created on %sysfunc(datetime(),datetime19.) by %mf_getuser() */"; /* WEBOUT BEGIN */ put '%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y '; put ' ,engine=DATASTEP '; put ' ,missing=NULL '; put ' ,showmeta=N '; put ' ,maxobs=MAX '; put ')/*/STORE SOURCE*/; '; put '%local tempds colinfo fmtds i numcols numobs stmt_obs lastobs optval '; put ' tmpds1 tmpds2 tmpds3 tmpds4; '; put '%let numcols=0; '; put '%if &maxobs ne MAX %then %let stmt_obs=%str(if _n_>&maxobs then stop;); '; put ' '; put '%if &action=OPEN %then %do; '; put ' options nobomfile; '; put ' data _null_;file &jref encoding=''utf-8'' lrecl=200; '; put ' put ''{"PROCESSED_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''"''; '; put ' run; '; put '%end; '; put '%else %if (&action=ARR or &action=OBJ) %then %do; '; put ' /* force variable names to always be uppercase in the JSON */ '; put ' options validvarname=upcase; '; put ' /* To avoid issues with _webout on EBI - such as encoding diffs and truncation '; put ' (https://support.sas.com/kb/49/325.html) we use temporary files */ '; put ' filename _sjs1 temp lrecl=200 ; '; put ' data _null_; file _sjs1 encoding=''utf-8''; '; put ' put ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":"; '; put ' run; '; put ' /* now write to _webout 1 char at a time */ '; put ' data _null_; '; put ' infile _sjs1 lrecl=1 recfm=n; '; put ' file &jref mod lrecl=1 recfm=n; '; put ' input sourcechar $char1. @@; '; put ' format sourcechar hex2.; '; put ' put sourcechar char1. @@; '; put ' run; '; put ' filename _sjs1 clear; '; put ' '; put ' /* grab col defs */ '; put ' proc contents noprint data=&ds '; put ' out=_data_(keep=name type length format formatl formatd varnum label); '; put ' run; '; put ' %let colinfo=%scan(&syslast,2,.); '; put ' proc sort data=&colinfo; '; put ' by varnum; '; put ' run; '; put ' /* move meta to mac vars */ '; put ' data &colinfo; '; put ' if _n_=1 then call symputx(''numcols'',nobs,''l''); '; put ' set &colinfo end=last nobs=nobs; '; put ' name=upcase(name); '; put ' /* fix formats */ '; put ' if type=2 or type=6 then do; '; put ' typelong=''char''; '; put ' length fmt $49.; '; put ' if format='''' then fmt=cats(''$'',length,''.''); '; put ' else if formatl=0 then fmt=cats(format,''.''); '; put ' else fmt=cats(format,formatl,''.''); '; put ' end; '; put ' else do; '; put ' typelong=''num''; '; put ' if format='''' then fmt=''best.''; '; put ' else if formatl=0 then fmt=cats(format,''.''); '; put ' else if formatd=0 then fmt=cats(format,formatl,''.''); '; put ' else fmt=cats(format,formatl,''.'',formatd); '; put ' end; '; put ' /* 32 char unique name */ '; put ' newname=''sasjs''!!substr(cats(put(md5(name),$hex32.)),1,27); '; put ' '; put ' call symputx(cats(''name'',_n_),name,''l''); '; put ' call symputx(cats(''newname'',_n_),newname,''l''); '; put ' call symputx(cats(''length'',_n_),length,''l''); '; put ' call symputx(cats(''fmt'',_n_),fmt,''l''); '; put ' call symputx(cats(''type'',_n_),type,''l''); '; put ' call symputx(cats(''typelong'',_n_),typelong,''l''); '; put ' call symputx(cats(''label'',_n_),coalescec(label,name),''l''); '; put ' /* overwritten when fmt=Y and a custom format exists in catalog */ '; put ' if typelong=''num'' then call symputx(cats(''fmtlen'',_n_),200,''l''); '; put ' else call symputx(cats(''fmtlen'',_n_),min(32767,ceil((length+10)*1.5)),''l''); '; put ' run; '; put ' '; put ' %let tempds=%substr(_%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32); '; put ' proc sql; '; put ' select count(*) into: lastobs from &ds; '; put ' %if &maxobs ne MAX %then %let lastobs=%sysfunc(min(&lastobs,&maxobs)); '; put ' '; put ' %if &engine=PROCJSON %then %do; '; put ' %if &missing=STRING %then %do; '; put ' %put &sysmacroname: Special Missings not supported in proc json.; '; put ' %put &sysmacroname: Switching to DATASTEP engine; '; put ' %goto datastep; '; put ' %end; '; put ' data &tempds; '; put ' set &ds; '; put ' &stmt_obs; '; put ' %if &fmt=N %then format _numeric_ best32.;; '; put ' /* PRETTY is necessary to avoid line truncation in large files */ '; put ' filename _sjs2 temp lrecl=131068 encoding=''utf-8''; '; put ' proc json out=_sjs2 pretty '; put ' %if &action=ARR %then nokeys ; '; put ' ;export &tempds / nosastags fmtnumeric; '; put ' run; '; put ' /* send back to webout */ '; put ' data _null_; '; put ' infile _sjs2 lrecl=1 recfm=n; '; put ' file &jref mod lrecl=1 recfm=n; '; put ' input sourcechar $char1. @@; '; put ' format sourcechar hex2.; '; put ' put sourcechar char1. @@; '; put ' run; '; put ' filename _sjs2 clear; '; put ' %end; '; put ' %else %if &engine=DATASTEP %then %do; '; put ' %datastep: '; put ' %if %sysfunc(exist(&ds)) ne 1 & %sysfunc(exist(&ds,VIEW)) ne 1 '; put ' %then %do; '; put ' %put &sysmacroname: &ds NOT FOUND!!!; '; put ' %return; '; put ' %end; '; put ' '; put ' %if &fmt=Y %then %do; '; put ' /** '; put ' * Extract format definitions '; put ' * First, by getting library locations from dictionary.formats '; put ' * Then, by exporting the width using proc format '; put ' * Cannot use maxw from sashelp.vformat as not always populated '; put ' * Cannot use fmtinfo() as not supported in all flavours '; put ' */ '; put ' %let tmpds1=%substr(fmtsum%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32); '; put ' %let tmpds2=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32); '; put ' %let tmpds3=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32); '; put ' %let tmpds4=%substr(col%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32); '; put ' proc sql noprint; '; put ' create table &tmpds1 as '; put ' select cats(libname,''.'',memname) as FMTCAT, '; put ' FMTNAME '; put ' from dictionary.formats '; put ' where fmttype=''F'' and libname is not null '; put ' and fmtname in (select format from &colinfo where format is not null) '; put ' order by 1; '; put ' create table &tmpds2( '; put ' FMTNAME char(32), '; put ' LENGTH num '; put ' ); '; put ' %local catlist cat fmtlist i; '; put ' select distinct fmtcat into: catlist separated by '' '' from &tmpds1; '; put ' %do i=1 %to %sysfunc(countw(&catlist,%str( ))); '; put ' %let cat=%scan(&catlist,&i,%str( )); '; put ' proc sql; '; put ' select distinct fmtname into: fmtlist separated by '' '' '; put ' from &tmpds1 where fmtcat="&cat"; '; put ' proc format lib=&cat cntlout=&tmpds3(keep=fmtname length); '; put ' select &fmtlist; '; put ' run; '; put ' proc sql; '; put ' insert into &tmpds2 select distinct fmtname,length from &tmpds3; '; put ' %end; '; put ' '; put ' proc sql; '; put ' create table &tmpds4 as '; put ' select a.*, b.length as MAXW '; put ' from &colinfo a '; put ' left join &tmpds2 b '; put ' on cats(a.format)=cats(upcase(b.fmtname)) '; put ' order by a.varnum; '; put ' data _null_; '; put ' set &tmpds4; '; put ' if not missing(maxw); '; put ' call symputx( '; put ' cats(''fmtlen'',_n_), '; put ' /* vars need extra padding due to JSON escaping of special chars */ '; put ' min(32767,ceil((max(length,maxw)+10)*1.5)) '; put ' ,''l'' '; put ' ); '; put ' run; '; put ' '; put ' /* configure varlenchk - as we are explicitly shortening the variables */ '; put ' %let optval=%sysfunc(getoption(varlenchk)); '; put ' options varlenchk=NOWARN; '; put ' data _data_(compress=char); '; put ' /* shorten the new vars */ '; put ' length '; put ' %do i=1 %to &numcols; '; put ' &&name&i $&&fmtlen&i '; put ' %end; '; put ' ; '; put ' /* rename on entry */ '; put ' set &ds(rename=( '; put ' %do i=1 %to &numcols; '; put ' &&name&i=&&newname&i '; put ' %end; '; put ' )); '; put ' &stmt_obs; '; put ' '; put ' drop '; put ' %do i=1 %to &numcols; '; put ' &&newname&i '; put ' %end; '; put ' ; '; put ' %do i=1 %to &numcols; '; put ' %if &&typelong&i=num %then %do; '; put ' &&name&i=cats(put(&&newname&i,&&fmt&i)); '; put ' %end; '; put ' %else %do; '; put ' &&name&i=put(&&newname&i,&&fmt&i); '; put ' %end; '; put ' %end; '; put ' if _error_ then do; '; put ' call symputx(''syscc'',1012); '; put ' stop; '; put ' end; '; put ' run; '; put ' %let fmtds=&syslast; '; put ' options varlenchk=&optval; '; put ' %end; '; put ' '; put ' proc format; /* credit yabwon for special null removal */ '; put ' value bart (default=40) '; put ' %if &missing=NULL %then %do; '; put ' ._ - .z = null '; put ' %end; '; put ' %else %do; '; put ' ._ = [quote()] '; put ' . = null '; put ' .a - .z = [quote()] '; put ' %end; '; put ' other = [best.]; '; put ' '; put ' data &tempds; '; put ' attrib _all_ label=''''; '; put ' %do i=1 %to &numcols; '; put ' %if &&typelong&i=char or &fmt=Y %then %do; '; put ' length &&name&i $&&fmtlen&i...; '; put ' format &&name&i $&&fmtlen&i...; '; put ' %end; '; put ' %end; '; put ' %if &fmt=Y %then %do; '; put ' set &fmtds; '; put ' %end; '; put ' %else %do; '; put ' set &ds; '; put ' %end; '; put ' &stmt_obs; '; put ' format _numeric_ bart.; '; put ' %do i=1 %to &numcols; '; put ' %if &&typelong&i=char or &fmt=Y %then %do; '; put ' if findc(&&name&i,''"\''!!''0A0D09000E0F010210111A''x) then do; '; put ' &&name&i=''"''!!trim( '; put ' prxchange(''s/"/\\"/'',-1, /* double quote */ '; put ' prxchange(''s/\x0A/\n/'',-1, /* new line */ '; put ' prxchange(''s/\x0D/\r/'',-1, /* carriage return */ '; put ' prxchange(''s/\x09/\\t/'',-1, /* tab */ '; put ' prxchange(''s/\x00/\\u0000/'',-1, /* NUL */ '; put ' prxchange(''s/\x0E/\\u000E/'',-1, /* SS */ '; put ' prxchange(''s/\x0F/\\u000F/'',-1, /* SF */ '; put ' prxchange(''s/\x01/\\u0001/'',-1, /* SOH */ '; put ' prxchange(''s/\x02/\\u0002/'',-1, /* STX */ '; put ' prxchange(''s/\x10/\\u0010/'',-1, /* DLE */ '; put ' prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */ '; put ' prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */ '; put ' prxchange(''s/\\/\\\\/'',-1,&&name&i) '; put ' )))))))))))))!!''"''; '; put ' end; '; put ' else &&name&i=quote(cats(&&name&i)); '; put ' %end; '; put ' %end; '; put ' run; '; put ' '; put ' filename _sjs3 temp lrecl=131068 ; '; put ' data _null_; '; put ' file _sjs3 encoding=''utf-8''; '; put ' if _n_=1 then put "["; '; put ' set &tempds; '; put ' if _n_>1 then put "," @; put '; put ' %if &action=ARR %then "[" ; %else "{" ; '; put ' %do i=1 %to &numcols; '; put ' %if &i>1 %then "," ; '; put ' %if &action=OBJ %then """&&name&i"":" ; '; put ' "&&name&i"n /* name literal for reserved variable names */ '; put ' %end; '; put ' %if &action=ARR %then "]" ; %else "}" ; ; '; put ' '; put ' /* close out the table */ '; put ' data _null_; '; put ' file _sjs3 mod encoding=''utf-8''; '; put ' put '']''; '; put ' run; '; put ' data _null_; '; put ' infile _sjs3 lrecl=1 recfm=n; '; put ' file &jref mod lrecl=1 recfm=n; '; put ' input sourcechar $char1. @@; '; put ' format sourcechar hex2.; '; put ' put sourcechar char1. @@; '; put ' run; '; put ' filename _sjs3 clear; '; put ' %end; '; put ' '; put ' proc sql; '; put ' drop table &colinfo, &tempds; '; put ' '; put ' %if %substr(&showmeta,1,1)=Y %then %do; '; put ' filename _sjs4 temp lrecl=131068 encoding=''utf-8''; '; put ' data _null_; '; put ' file _sjs4; '; put ' length label $350; '; put ' put ", ""$%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":{""vars"":{"; '; put ' do i=1 to &numcols; '; put ' name=quote(trim(symget(cats(''name'',i)))); '; put ' format=quote(trim(symget(cats(''fmt'',i)))); '; put ' label=quote(prxchange(''s/\\/\\\\/'',-1,trim(symget(cats(''label'',i))))); '; put ' length=quote(trim(symget(cats(''length'',i)))); '; put ' type=quote(trim(symget(cats(''typelong'',i)))); '; put ' if i>1 then put "," @@; '; put ' put name '':{"format":'' format '',"label":'' label '; put ' '',"length":'' length '',"type":'' type ''}''; '; put ' end; '; put ' put ''}}''; '; put ' run; '; put ' /* send back to webout */ '; put ' data _null_; '; put ' infile _sjs4 lrecl=1 recfm=n; '; put ' file &jref mod lrecl=1 recfm=n; '; put ' input sourcechar $char1. @@; '; put ' format sourcechar hex2.; '; put ' put sourcechar char1. @@; '; put ' run; '; put ' filename _sjs4 clear; '; put ' %end; '; put '%end; '; put ' '; put '%else %if &action=CLOSE %then %do; '; put ' data _null_; file &jref encoding=''utf-8'' mod ; '; put ' put "}"; '; put ' run; '; put '%end; '; put '%mend mp_jsonout; '; put ' '; put '%macro mf_getuser( '; put ')/*/STORE SOURCE*/; '; put ' %local user; '; put ' '; put ' %if %symexist(_sasjs_username) %then %let user=&_sasjs_username; '; put ' %else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do; '; put ' %let user=&SYS_COMPUTE_SESSION_OWNER; '; put ' %end; '; put ' %else %if %symexist(_metaperson) %then %do; '; put ' %if %length(&_metaperson)=0 %then %let user=&sysuserid; '; put ' /* sometimes SAS will add @domain extension - remove for consistency */ '; put ' /* but be sure to quote in case of usernames with commas */ '; put ' %else %let user=%unquote(%scan(%quote(&_metaperson),1,@)); '; put ' %end; '; put ' %else %let user=&sysuserid; '; put ' '; put ' %quote(&user) '; put ' '; put '%mend mf_getuser; '; put '%macro mm_webout(action,ds,dslabel=,fref=_webout,fmt=N,missing=NULL '; put ' ,showmeta=N,maxobs=MAX,workobs=0 '; put '); '; put '%global _webin_file_count _webin_fileref1 _webin_name1 _program _debug '; put ' sasjs_tables; '; put '%local i tempds jsonengine; '; put ' '; put '/* see https://github.com/sasjs/core/issues/41 */ '; put '%if "%upcase(&SYSENCODING)" ne "UTF-8" %then %let jsonengine=PROCJSON; '; put '%else %let jsonengine=DATASTEP; '; put ' '; put ' '; put '%if &action=FETCH %then %do; '; put ' %if %str(&_debug) ge 131 %then %do; '; put ' options mprint notes mprintnest; '; put ' %end; '; put ' %let _webin_file_count=%eval(&_webin_file_count+0); '; put ' /* now read in the data */ '; put ' %do i=1 %to &_webin_file_count; '; put ' %if &_webin_file_count=1 %then %do; '; put ' %let _webin_fileref1=&_webin_fileref; '; put ' %let _webin_name1=&_webin_name; '; put ' %end; '; put ' data _null_; '; put ' infile &&_webin_fileref&i termstr=crlf; '; put ' input; '; put ' call symputx(''input_statement'',_infile_); '; put ' putlog "&&_webin_name&i input statement: " _infile_; '; put ' stop; '; put ' data &&_webin_name&i; '; put ' infile &&_webin_fileref&i firstobs=2 dsd termstr=crlf encoding=''utf-8''; '; put ' input &input_statement; '; put ' %if %str(&_debug) ge 131 %then %do; '; put ' if _n_<20 then putlog _infile_; '; put ' %end; '; put ' run; '; put ' %let sasjs_tables=&sasjs_tables &&_webin_name&i; '; put ' %end; '; put '%end; '; put ' '; put '%else %if &action=OPEN %then %do; '; put ' /* fix encoding */ '; put ' OPTIONS NOBOMFILE; '; put ' '; put ' /** '; put ' * check xengine type to avoid the below err message: '; put ' * > Function is only valid for filerefs using the CACHE access method. '; put ' */ '; put ' data _null_; '; put ' set sashelp.vextfl(where=(fileref="_WEBOUT")); '; put ' if xengine=''STREAM'' then do; '; put ' rc=stpsrv_header(''Content-type'',"text/html; encoding=utf-8"); '; put ' end; '; put ' run; '; put ' '; put ' /* setup json */ '; put ' data _null_;file &fref encoding=''utf-8''; '; put ' %if %str(&_debug) ge 131 %then %do; '; put ' put ''>>weboutBEGIN<<''; '; put ' %end; '; put ' put ''{"SYSDATE" : "'' "&SYSDATE" ''"''; '; put ' put '',"SYSTIME" : "'' "&SYSTIME" ''"''; '; put ' run; '; put ' '; put '%end; '; put ' '; put '%else %if &action=ARR or &action=OBJ %then %do; '; put ' %mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt,jref=&fref '; put ' ,engine=&jsonengine,missing=&missing,showmeta=&showmeta,maxobs=&maxobs '; put ' ) '; put '%end; '; put '%else %if &action=CLOSE %then %do; '; put ' /* To avoid issues with _webout on EBI we use a temporary file */ '; put ' filename _sjsref temp lrecl=131068; '; put ' %if %str(&workobs) > 0 %then %do; '; put ' /* if debug mode, send back first XX records of each work table also */ '; put ' data;run;%let tempds=%scan(&syslast,2,.); '; put ' ods output Members=&tempds; '; put ' proc datasets library=WORK memtype=data; '; put ' %local wtcnt;%let wtcnt=0; '; put ' data _null_; '; put ' set &tempds; '; put ' if not (upcase(name) =:"DATA"); /* ignore temp datasets */ '; put ' i+1; '; put ' call symputx(cats(''wt'',i),name,''l''); '; put ' call symputx(''wtcnt'',i,''l''); '; put ' data _null_; file _sjsref mod encoding=''utf-8''; '; put ' put ",""WORK"":{"; '; put ' %do i=1 %to &wtcnt; '; put ' %let wt=&&wt&i; '; put ' data _null_; file _sjsref mod encoding=''utf-8''; '; put ' dsid=open("WORK.&wt",''is''); '; put ' nlobs=attrn(dsid,''NLOBS''); '; put ' nvars=attrn(dsid,''NVARS''); '; put ' rc=close(dsid); '; put ' if &i>1 then put '',''@; '; put ' put " ""&wt"" : {"; '; put ' put ''"nlobs":'' nlobs; '; put ' put '',"nvars":'' nvars; '; put ' %mp_jsonout(OBJ,&wt,jref=_sjsref,dslabel=first10rows,showmeta=Y '; put ' ,maxobs=&workobs '; put ' ) '; put ' data _null_; file _sjsref mod encoding=''utf-8''; '; put ' put "}"; '; put ' %end; '; put ' data _null_; file _sjsref mod encoding=''utf-8''; '; put ' put "}"; '; put ' run; '; put ' %end; '; put ' /* close off json */ '; put ' data _null_;file _sjsref mod encoding=''utf-8''; '; put ' length SYSPROCESSNAME syserrortext syswarningtext autoexec $512; '; put ' put ",""_DEBUG"" : ""&_debug"" "; '; put ' _METAUSER=quote(trim(symget(''_METAUSER''))); '; put ' put ",""_METAUSER"": " _METAUSER; '; put ' _METAPERSON=quote(trim(symget(''_METAPERSON''))); '; put ' put '',"_METAPERSON": '' _METAPERSON; '; put ' _PROGRAM=quote(trim(resolve(symget(''_PROGRAM'')))); '; put ' put '',"_PROGRAM" : '' _PROGRAM ; '; put ' autoexec=quote(urlencode(trim(getoption(''autoexec'')))); '; put ' put '',"AUTOEXEC" : '' autoexec; '; put ' put ",""MF_GETUSER"" : ""%mf_getuser()"" "; '; put ' put ",""SYSCC"" : ""&syscc"" "; '; put ' put ",""SYSENCODING"" : ""&sysencoding"" "; '; put ' syserrortext=cats(symget(''syserrortext'')); '; put ' if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do; '; put ' syserrortext=''"''!!trim( '; put ' prxchange(''s/"/\\"/'',-1, /* double quote */ '; put ' prxchange(''s/\x0A/\n/'',-1, /* new line */ '; put ' prxchange(''s/\x0D/\r/'',-1, /* carriage return */ '; put ' prxchange(''s/\x09/\\t/'',-1, /* tab */ '; put ' prxchange(''s/\x00/\\u0000/'',-1, /* NUL */ '; put ' prxchange(''s/\x0E/\\u000E/'',-1, /* SS */ '; put ' prxchange(''s/\x0F/\\u000F/'',-1, /* SF */ '; put ' prxchange(''s/\x01/\\u0001/'',-1, /* SOH */ '; put ' prxchange(''s/\x02/\\u0002/'',-1, /* STX */ '; put ' prxchange(''s/\x10/\\u0010/'',-1, /* DLE */ '; put ' prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */ '; put ' prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */ '; put ' prxchange(''s/\\/\\\\/'',-1,syserrortext) '; put ' )))))))))))))!!''"''; '; put ' end; '; put ' else syserrortext=cats(''"'',syserrortext,''"''); '; put ' put '',"SYSERRORTEXT" : '' syserrortext; '; put ' put ",""SYSHOSTNAME"" : ""&syshostname"" "; '; put ' put ",""SYSPROCESSID"" : ""&SYSPROCESSID"" "; '; put ' put ",""SYSPROCESSMODE"" : ""&SYSPROCESSMODE"" "; '; put ' SYSPROCESSNAME=quote(urlencode(cats(SYSPROCESSNAME))); '; put ' put ",""SYSPROCESSNAME"" : " SYSPROCESSNAME; '; put ' put ",""SYSJOBID"" : ""&sysjobid"" "; '; put ' put ",""SYSSCPL"" : ""&sysscpl"" "; '; put ' put ",""SYSSITE"" : ""&syssite"" "; '; put ' put ",""SYSUSERID"" : ""&sysuserid"" "; '; put ' sysvlong=quote(trim(symget(''sysvlong''))); '; put ' put '',"SYSVLONG" : '' sysvlong; '; put ' syswarningtext=cats(symget(''syswarningtext'')); '; put ' if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do; '; put ' syswarningtext=''"''!!trim( '; put ' prxchange(''s/"/\\"/'',-1, /* double quote */ '; put ' prxchange(''s/\x0A/\n/'',-1, /* new line */ '; put ' prxchange(''s/\x0D/\r/'',-1, /* carriage return */ '; put ' prxchange(''s/\x09/\\t/'',-1, /* tab */ '; put ' prxchange(''s/\x00/\\u0000/'',-1, /* NUL */ '; put ' prxchange(''s/\x0E/\\u000E/'',-1, /* SS */ '; put ' prxchange(''s/\x0F/\\u000F/'',-1, /* SF */ '; put ' prxchange(''s/\x01/\\u0001/'',-1, /* SOH */ '; put ' prxchange(''s/\x02/\\u0002/'',-1, /* STX */ '; put ' prxchange(''s/\x10/\\u0010/'',-1, /* DLE */ '; put ' prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */ '; put ' prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */ '; put ' prxchange(''s/\\/\\\\/'',-1,syswarningtext) '; put ' )))))))))))))!!''"''; '; put ' end; '; put ' else syswarningtext=cats(''"'',syswarningtext,''"''); '; put ' put '',"SYSWARNINGTEXT" : '' syswarningtext; '; put ' put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" ''; '; put ' length memsize $32; '; put ' memsize="%sysfunc(INPUTN(%sysfunc(getoption(memsize)), best.),sizekmg.)"; '; put ' memsize=quote(cats(memsize)); '; put ' put '',"MEMSIZE" : '' memsize; '; put ' put "}" @; '; put ' %if %str(&_debug) ge 131 %then %do; '; put ' put ''>>weboutEND<<''; '; put ' %end; '; put ' run; '; put ' /* now write to _webout 1 char at a time */ '; put ' data _null_; '; put ' infile _sjsref lrecl=1 recfm=n; '; put ' file &fref mod lrecl=1 recfm=n; '; put ' input sourcechar $char1. @@; '; put ' format sourcechar hex2.; '; put ' put sourcechar char1. @@; '; put ' run; '; put ' filename _sjsref clear; '; put ' '; put '%end; '; put ' '; put '%mend mm_webout; '; /* WEBOUT END */ put '%macro webout(action,ds,dslabel=,fmt=,missing=NULL,showmeta=NO'; put ' ,maxobs=MAX'; put ');'; put ' %mm_webout(&action,ds=&ds,dslabel=&dslabel,fmt=&fmt,missing=&missing'; put ' ,showmeta=&showmeta,maxobs=&maxobs'; put ' )'; put '%mend;'; run; /* add precode and code */ %local work tmpfile; %let work=%sysfunc(pathname(work)); %let tmpfile=__mm_createwebservice.temp; %local x fref freflist mod; %let freflist= &adapter &precode &code ; %do x=1 %to %sysfunc(countw(&freflist)); %if &x>1 %then %let mod=mod; %let fref=%scan(&freflist,&x); %&mD.put &sysmacroname: adding &fref; data _null_; file "&work/&tmpfile" lrecl=3000 &mod; infile &fref; input; put _infile_; run; %end; /* create the metadata folder if not already there */ %mm_createfolder(path=&path) %if &syscc ge 4 %then %return; %if %upcase(&replace)=YES %then %do; %mm_deletestp(target=&path/&name) %end; /* create the web service */ %mm_createstp(stpname=&name ,filename=&tmpfile ,directory=&work ,tree=&path ,stpdesc=&desc ,mDebug=&mdebug ,server=&server ,stptype=2) /* find the web app url */ %local url; %let url=localhost/SASStoredProcess; data _null_; length url $128; rc=METADATA_GETURI("Stored Process Web App",url); if rc=0 then call symputx('url',url,'l'); run; %put &sysmacroname: STP &name successfully created in &path; %put Check it out here:; %put ;%put ;%put ; %put &url?_PROGRAM=&path/&name; %put ;%put ;%put ; %mend mm_createwebservice; /* system macros for build process end */ * BuildInit start; /** @file @brief Initialise build program for SAS 9 DC

SAS Macros

**/ options nomprint; %global _metaperson _url dcpath; /* set webout if not running in STP mode */ data _null_; if "&sysprocessmode" ne "SAS Stored Process Server" then do; call execute('filename _webout temp;'); end; run; * BuildInit end; %let path=services; %let path=services/admin; %let service=configurator; filename sascode temp lrecl=32767; data _null_; file sascode; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '/**'; put '@file mp_jsonout.sas'; put '@brief Writes JSON in SASjs format to a fileref'; put '@details This macro can be used to OPEN a JSON stream and send one or more'; put 'tables as arrays of rows, where each row can be an object or a nested array.'; put 'There are two engines available - DATASTEP or PROCJSON.'; put 'PROC JSON is fast but will produce errs like the ones below if'; put 'special chars are encountered.'; put '> (ERR)OR: Some code points did not transcode.'; put '> An object or array close is not valid at this point in the JSON text.'; put '> Date value out of range'; put 'If this happens, try running with ENGINE=DATASTEP.'; put 'The DATASTEP engine is used to handle special SAS missing numerics, and'; put 'can also convert entire datasets to formatted values. Output JSON is always'; put 'in UTF-8.'; put 'Usage:'; put 'filename tmp temp;'; put 'data class; set sashelp.class;run;'; put '%mp_jsonout(OPEN,jref=tmp)'; put '%mp_jsonout(OBJ,class,jref=tmp)'; put '%mp_jsonout(OBJ,class,dslabel=class2,jref=tmp,showmeta=Y)'; put '%mp_jsonout(CLOSE,jref=tmp)'; put 'data _null_;'; put 'infile tmp;'; put 'input;putlog _infile_;'; put 'run;'; put 'If you are building web apps with SAS then you are strongly encouraged to use'; put 'the mX_createwebservice macros in combination with the'; put '[sasjs adapter](https://github.com/sasjs/adapter).'; put 'For more information see https://sasjs.io'; put '@param [in] action Valid values:'; put '@li OPEN - opens the JSON'; put '@li OBJ - sends a table with each row as an object'; put '@li ARR - sends a table with each row in an array'; put '@li CLOSE - closes the JSON'; put '@param [in] ds The dataset to send. Must be a work table.'; put '@param [out] jref= (_webout) The fileref to which to send the JSON'; put '@param [out] dslabel= The name to give the table in the exported JSON'; put '@param [in] fmt= (Y) Whether to keep (Y) or strip (N) formats from the table'; put '@param [in] engine= (DATASTEP) Which engine to use to send the JSON. Options:'; put '@li PROCJSON (default)'; put '@li DATASTEP (more reliable when data has non standard characters)'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to JSON'; put '

Related Files

'; put '@li mp_ds2fmtds.sas'; put '@version 9.2'; put '@author Allan Bowe'; put '@source https://github.com/sasjs/core'; put '**/'; put '%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y'; put ',engine=DATASTEP'; put ',missing=NULL'; put ',showmeta=N'; put ',maxobs=MAX'; put ')/*/STORE SOURCE*/;'; put '%local tempds colinfo fmtds i numcols numobs stmt_obs lastobs optval'; put 'tmpds1 tmpds2 tmpds3 tmpds4;'; put '%let numcols=0;'; put '%if &maxobs ne MAX %then %let stmt_obs=%str(if _n_>&maxobs then stop;);'; put '%if &action=OPEN %then %do;'; put 'options nobomfile;'; put 'data _null_;file &jref encoding=''utf-8'' lrecl=200;'; put 'put ''{"PROCESSED_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''"'';'; put 'run;'; put '%end;'; put '%else %if (&action=ARR or &action=OBJ) %then %do;'; put '/* force variable names to always be uppercase in the JSON */'; put 'options validvarname=upcase;'; put '/* To avoid issues with _webout on EBI - such as encoding diffs and truncation'; put '(https://support.sas.com/kb/49/325.html) we use temporary files */'; put 'filename _sjs1 temp lrecl=200 ;'; put 'data _null_; file _sjs1 encoding=''utf-8'';'; put 'put ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":";'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjs1 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs1 clear;'; put '/* grab col defs */'; put 'proc contents noprint data=&ds'; put 'out=_data_(keep=name type length format formatl formatd varnum label);'; put 'run;'; put '%let colinfo=%scan(&syslast,2,.);'; put 'proc sort data=&colinfo;'; put 'by varnum;'; put 'run;'; put '/* move meta to mac vars */'; put 'data &colinfo;'; put 'if _n_=1 then call symputx(''numcols'',nobs,''l'');'; put 'set &colinfo end=last nobs=nobs;'; put 'name=upcase(name);'; put '/* fix formats */'; put 'if type=2 or type=6 then do;'; put 'typelong=''char'';'; put 'length fmt $49.;'; put 'if format='''' then fmt=cats(''$'',length,''.'');'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else fmt=cats(format,formatl,''.'');'; put 'end;'; put 'else do;'; put 'typelong=''num'';'; put 'if format='''' then fmt=''best.'';'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else if formatd=0 then fmt=cats(format,formatl,''.'');'; put 'else fmt=cats(format,formatl,''.'',formatd);'; put 'end;'; put '/* 32 char unique name */'; put 'newname=''sasjs''!!substr(cats(put(md5(name),$hex32.)),1,27);'; put 'call symputx(cats(''name'',_n_),name,''l'');'; put 'call symputx(cats(''newname'',_n_),newname,''l'');'; put 'call symputx(cats(''length'',_n_),length,''l'');'; put 'call symputx(cats(''fmt'',_n_),fmt,''l'');'; put 'call symputx(cats(''type'',_n_),type,''l'');'; put 'call symputx(cats(''typelong'',_n_),typelong,''l'');'; put 'call symputx(cats(''label'',_n_),coalescec(label,name),''l'');'; put '/* overwritten when fmt=Y and a custom format exists in catalog */'; put 'if typelong=''num'' then call symputx(cats(''fmtlen'',_n_),200,''l'');'; put 'else call symputx(cats(''fmtlen'',_n_),min(32767,ceil((length+10)*1.5)),''l'');'; put 'run;'; put '%let tempds=%substr(_%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql;'; put 'select count(*) into: lastobs from &ds;'; put '%if &maxobs ne MAX %then %let lastobs=%sysfunc(min(&lastobs,&maxobs));'; put '%if &engine=PROCJSON %then %do;'; put '%if &missing=STRING %then %do;'; put '%put &sysmacroname: Special Missings not supported in proc json.;'; put '%put &sysmacroname: Switching to DATASTEP engine;'; put '%goto datastep;'; put '%end;'; put 'data &tempds;'; put 'set &ds;'; put '&stmt_obs;'; put '%if &fmt=N %then format _numeric_ best32.;;'; put '/* PRETTY is necessary to avoid line truncation in large files */'; put 'filename _sjs2 temp lrecl=131068 encoding=''utf-8'';'; put 'proc json out=_sjs2 pretty'; put '%if &action=ARR %then nokeys ;'; put ';export &tempds / nosastags fmtnumeric;'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs2 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs2 clear;'; put '%end;'; put '%else %if &engine=DATASTEP %then %do;'; put '%datastep:'; put '%if %sysfunc(exist(&ds)) ne 1 & %sysfunc(exist(&ds,VIEW)) ne 1'; put '%then %do;'; put '%put &sysmacroname: &ds NOT FOUND!!!;'; put '%return;'; put '%end;'; put '%if &fmt=Y %then %do;'; put '/**'; put '* Extract format definitions'; put '* First, by getting library locations from dictionary.formats'; put '* Then, by exporting the width using proc format'; put '* Cannot use maxw from sashelp.vformat as not always populated'; put '* Cannot use fmtinfo() as not supported in all flavours'; put '*/'; put '%let tmpds1=%substr(fmtsum%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds2=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds3=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds4=%substr(col%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql noprint;'; put 'create table &tmpds1 as'; put 'select cats(libname,''.'',memname) as FMTCAT,'; put 'FMTNAME'; put 'from dictionary.formats'; put 'where fmttype=''F'' and libname is not null'; put 'and fmtname in (select format from &colinfo where format is not null)'; put 'order by 1;'; put 'create table &tmpds2('; put 'FMTNAME char(32),'; put 'LENGTH num'; put ');'; put '%local catlist cat fmtlist i;'; put 'select distinct fmtcat into: catlist separated by '' '' from &tmpds1;'; put '%do i=1 %to %sysfunc(countw(&catlist,%str( )));'; put '%let cat=%scan(&catlist,&i,%str( ));'; put 'proc sql;'; put 'select distinct fmtname into: fmtlist separated by '' '''; put 'from &tmpds1 where fmtcat="&cat";'; put 'proc format lib=&cat cntlout=&tmpds3(keep=fmtname length);'; put 'select &fmtlist;'; put 'run;'; put 'proc sql;'; put 'insert into &tmpds2 select distinct fmtname,length from &tmpds3;'; put '%end;'; put 'proc sql;'; put 'create table &tmpds4 as'; put 'select a.*, b.length as MAXW'; put 'from &colinfo a'; put 'left join &tmpds2 b'; put 'on cats(a.format)=cats(upcase(b.fmtname))'; put 'order by a.varnum;'; put 'data _null_;'; put 'set &tmpds4;'; put 'if not missing(maxw);'; put 'call symputx('; put 'cats(''fmtlen'',_n_),'; put '/* vars need extra padding due to JSON escaping of special chars */'; put 'min(32767,ceil((max(length,maxw)+10)*1.5))'; put ',''l'''; put ');'; put 'run;'; put '/* configure varlenchk - as we are explicitly shortening the variables */'; put '%let optval=%sysfunc(getoption(varlenchk));'; put 'options varlenchk=NOWARN;'; put 'data _data_(compress=char);'; put '/* shorten the new vars */'; put 'length'; put '%do i=1 %to &numcols;'; put '&&name&i $&&fmtlen&i'; put '%end;'; put ';'; put '/* rename on entry */'; put 'set &ds(rename=('; put '%do i=1 %to &numcols;'; put '&&name&i=&&newname&i'; put '%end;'; put '));'; put '&stmt_obs;'; put 'drop'; put '%do i=1 %to &numcols;'; put '&&newname&i'; put '%end;'; put ';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=num %then %do;'; put '&&name&i=cats(put(&&newname&i,&&fmt&i));'; put '%end;'; put '%else %do;'; put '&&name&i=put(&&newname&i,&&fmt&i);'; put '%end;'; put '%end;'; put 'if _error_ then do;'; put 'call symputx(''syscc'',1012);'; put 'stop;'; put 'end;'; put 'run;'; put '%let fmtds=&syslast;'; put 'options varlenchk=&optval;'; put '%end;'; put 'proc format; /* credit yabwon for special null removal */'; put 'value bart (default=40)'; put '%if &missing=NULL %then %do;'; put '._ - .z = null'; put '%end;'; put '%else %do;'; put '._ = [quote()]'; put '. = null'; put '.a - .z = [quote()]'; put '%end;'; put 'other = [best.];'; put 'data &tempds;'; put 'attrib _all_ label='''';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'length &&name&i $&&fmtlen&i...;'; put 'format &&name&i $&&fmtlen&i...;'; put '%end;'; put '%end;'; put '%if &fmt=Y %then %do;'; put 'set &fmtds;'; put '%end;'; put '%else %do;'; put 'set &ds;'; put '%end;'; put '&stmt_obs;'; put 'format _numeric_ bart.;'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'if findc(&&name&i,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put '&&name&i=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,&&name&i)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else &&name&i=quote(cats(&&name&i));'; put '%end;'; put '%end;'; put 'run;'; put 'filename _sjs3 temp lrecl=131068 ;'; put 'data _null_;'; put 'file _sjs3 encoding=''utf-8'';'; put 'if _n_=1 then put "[";'; put 'set &tempds;'; put 'if _n_>1 then put "," @; put'; put '%if &action=ARR %then "[" ; %else "{" ;'; put '%do i=1 %to &numcols;'; put '%if &i>1 %then "," ;'; put '%if &action=OBJ %then """&&name&i"":" ;'; put '"&&name&i"n /* name literal for reserved variable names */'; put '%end;'; put '%if &action=ARR %then "]" ; %else "}" ; ;'; put '/* close out the table */'; put 'data _null_;'; put 'file _sjs3 mod encoding=''utf-8'';'; put 'put '']'';'; put 'run;'; put 'data _null_;'; put 'infile _sjs3 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs3 clear;'; put '%end;'; put 'proc sql;'; put 'drop table &colinfo, &tempds;'; put '%if %substr(&showmeta,1,1)=Y %then %do;'; put 'filename _sjs4 temp lrecl=131068 encoding=''utf-8'';'; put 'data _null_;'; put 'file _sjs4;'; put 'length label $350;'; put 'put ", ""$%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":{""vars"":{";'; put 'do i=1 to &numcols;'; put 'name=quote(trim(symget(cats(''name'',i))));'; put 'format=quote(trim(symget(cats(''fmt'',i))));'; put 'label=quote(prxchange(''s/\\/\\\\/'',-1,trim(symget(cats(''label'',i)))));'; put 'length=quote(trim(symget(cats(''length'',i))));'; put 'type=quote(trim(symget(cats(''typelong'',i))));'; put 'if i>1 then put "," @@;'; put 'put name '':{"format":'' format '',"label":'' label'; put ''',"length":'' length '',"type":'' type ''}'';'; put 'end;'; put 'put ''}}'';'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs4 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs4 clear;'; put '%end;'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put 'data _null_; file &jref encoding=''utf-8'' mod ;'; put 'put "}";'; put 'run;'; put '%end;'; put '%mend mp_jsonout;'; put '/**'; put '@file mm_webout.sas'; put '@brief Send data to/from SAS Stored Processes'; put '@details This macro should be added to the start of each Stored Process,'; put '**immediately** followed by a call to:'; put '%mm_webout(FETCH)'; put 'This will read all the input data and create same-named SAS datasets in the'; put 'WORK library. You can then insert your code, and send data back using the'; put 'following syntax:'; put 'data some datasets; * make some data ;'; put 'retain some columns;'; put 'run;'; put '%mm_webout(OPEN)'; put '%mm_webout(ARR,some) * Array format, fast, suitable for large tables ;'; put '%mm_webout(OBJ,datasets) * Object format, easier to work with ;'; put 'Finally, wrap everything up send some helpful system variables too'; put '%mm_webout(CLOSE)'; put '@param [in] action Either FETCH, OPEN, ARR, OBJ or CLOSE'; put '@param [in] ds The dataset to send back to the frontend'; put '@param [out] dslabel= Value to use instead of table name for sending to JSON'; put '@param [in] fmt= (N) Setting Y converts all vars to their formatted values'; put '@param [out] fref= (_webout) The fileref to which to write the JSON'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] workobs= (0) When set to a positive integer, will create a new'; put 'output object (WORK) which contains this number of observations from all'; put 'tables in the WORK library.'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to output JSON'; put '

SAS Macros

'; put '@li mp_jsonout.sas'; put '

Related Macros

'; put '@li ms_webout.sas'; put '@li mv_webout.sas'; put '@version 9.3'; put '@author Allan Bowe'; put '**/'; put '%macro mm_webout(action,ds,dslabel=,fref=_webout,fmt=N,missing=NULL'; put ',showmeta=N,maxobs=MAX,workobs=0'; put ');'; put '%global _webin_file_count _webin_fileref1 _webin_name1 _program _debug'; put 'sasjs_tables;'; put '%local i tempds jsonengine;'; put '/* see https://github.com/sasjs/core/issues/41 */'; put '%if "%upcase(&SYSENCODING)" ne "UTF-8" %then %let jsonengine=PROCJSON;'; put '%else %let jsonengine=DATASTEP;'; put '%if &action=FETCH %then %do;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'options mprint notes mprintnest;'; put '%end;'; put '%let _webin_file_count=%eval(&_webin_file_count+0);'; put '/* now read in the data */'; put '%do i=1 %to &_webin_file_count;'; put '%if &_webin_file_count=1 %then %do;'; put '%let _webin_fileref1=&_webin_fileref;'; put '%let _webin_name1=&_webin_name;'; put '%end;'; put 'data _null_;'; put 'infile &&_webin_fileref&i termstr=crlf;'; put 'input;'; put 'call symputx(''input_statement'',_infile_);'; put 'putlog "&&_webin_name&i input statement: " _infile_;'; put 'stop;'; put 'data &&_webin_name&i;'; put 'infile &&_webin_fileref&i firstobs=2 dsd termstr=crlf encoding=''utf-8'';'; put 'input &input_statement;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'if _n_<20 then putlog _infile_;'; put '%end;'; put 'run;'; put '%let sasjs_tables=&sasjs_tables &&_webin_name&i;'; put '%end;'; put '%end;'; put '%else %if &action=OPEN %then %do;'; put '/* fix encoding */'; put 'OPTIONS NOBOMFILE;'; put '/**'; put '* check xengine type to avoid the below err message:'; put '* > Function is only valid for filerefs using the CACHE access method.'; put '*/'; put 'data _null_;'; put 'set sashelp.vextfl(where=(fileref="_WEBOUT"));'; put 'if xengine=''STREAM'' then do;'; put 'rc=stpsrv_header(''Content-type'',"text/html; encoding=utf-8");'; put 'end;'; put 'run;'; put '/* setup json */'; put 'data _null_;file &fref encoding=''utf-8'';'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutBEGIN<<'';'; put '%end;'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'run;'; put '%end;'; put '%else %if &action=ARR or &action=OBJ %then %do;'; put '%mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt,jref=&fref'; put ',engine=&jsonengine,missing=&missing,showmeta=&showmeta,maxobs=&maxobs'; put ')'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put '/* To avoid issues with _webout on EBI we use a temporary file */'; put 'filename _sjsref temp lrecl=131068;'; put '%if %str(&workobs) > 0 %then %do;'; put '/* if debug mode, send back first XX records of each work table also */'; put 'data;run;%let tempds=%scan(&syslast,2,.);'; put 'ods output Members=&tempds;'; put 'proc datasets library=WORK memtype=data;'; put '%local wtcnt;%let wtcnt=0;'; put 'data _null_;'; put 'set &tempds;'; put 'if not (upcase(name) =:"DATA"); /* ignore temp datasets */'; put 'i+1;'; put 'call symputx(cats(''wt'',i),name,''l'');'; put 'call symputx(''wtcnt'',i,''l'');'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put ",""WORK"":{";'; put '%do i=1 %to &wtcnt;'; put '%let wt=&&wt&i;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'dsid=open("WORK.&wt",''is'');'; put 'nlobs=attrn(dsid,''NLOBS'');'; put 'nvars=attrn(dsid,''NVARS'');'; put 'rc=close(dsid);'; put 'if &i>1 then put '',''@;'; put 'put " ""&wt"" : {";'; put 'put ''"nlobs":'' nlobs;'; put 'put '',"nvars":'' nvars;'; put '%mp_jsonout(OBJ,&wt,jref=_sjsref,dslabel=first10rows,showmeta=Y'; put ',maxobs=&workobs'; put ')'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put '%end;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put 'run;'; put '%end;'; put '/* close off json */'; put 'data _null_;file _sjsref mod encoding=''utf-8'';'; put 'length SYSPROCESSNAME syserrortext syswarningtext autoexec $512;'; put 'put ",""_DEBUG"" : ""&_debug"" ";'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'autoexec=quote(urlencode(trim(getoption(''autoexec''))));'; put 'put '',"AUTOEXEC" : '' autoexec;'; put 'put ",""MF_GETUSER"" : ""%mf_getuser()"" ";'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'put ",""SYSENCODING"" : ""&sysencoding"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSPROCESSID"" : ""&SYSPROCESSID"" ";'; put 'put ",""SYSPROCESSMODE"" : ""&SYSPROCESSMODE"" ";'; put 'SYSPROCESSNAME=quote(urlencode(cats(SYSPROCESSNAME)));'; put 'put ",""SYSPROCESSNAME"" : " SYSPROCESSNAME;'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put '',"SYSWARNINGTEXT" : '' syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'length memsize $32;'; put 'memsize="%sysfunc(INPUTN(%sysfunc(getoption(memsize)), best.),sizekmg.)";'; put 'memsize=quote(cats(memsize));'; put 'put '',"MEMSIZE" : '' memsize;'; put 'put "}" @;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutEND<<'';'; put '%end;'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjsref lrecl=1 recfm=n;'; put 'file &fref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjsref clear;'; put '%end;'; put '%mend mm_webout;'; put '%macro webout(action,ds,dslabel=,fmt=,missing=NULL,showmeta=NO,maxobs=MAX);'; put '%mm_webout(&action,ds=&ds,dslabel=&dslabel,fmt=&fmt'; put ',missing=&missing'; put ',showmeta=&showmeta'; put ',maxobs=&maxobs'; put ') %mend;'; put '/* provide additional debug info */'; put '%global _program;'; put '%put &=syscc;'; put '%put user=%mf_getuser();'; put '%put pgm=&_program;'; put '%put timestamp=%sysfunc(datetime(),datetime19.);'; put '* Service Variables start;'; put '* Service Variables end;'; put '* SAS Macros start;'; put '%macro mf_getapploc(pgm);'; put '%if "&pgm"="" %then %do;'; put '%if %symexist(_program) %then %let pgm=&_program;'; put '%else %do;'; put '%put &sysmacroname: No value provided and no _program variable available;'; put '%return;'; put '%end;'; put '%end;'; put '%local root;'; put '/**'; put '* First check we are not in the tests/macros folder (which has no subfolders)'; put '* or specifically in the testsetup or testteardown services'; put '*/'; put '%if %index(&pgm,/tests/macros/)'; put 'or %index(&pgm,/tests/testsetup)'; put 'or %index(&pgm,/tests/testteardown)'; put '%then %do;'; put '%let root=%substr(&pgm,1,%index(&pgm,/tests)-1);'; put '&root'; put '%return;'; put '%end;'; put '/**'; put '* Next, move up two levels to avoid matches on subfolder or service name'; put '*/'; put '%let root=%substr(&pgm,1,%length(&pgm)-%length(%scan(&pgm,-1,/))-1);'; put '%let root=%substr(&root,1,%length(&root)-%length(%scan(&root,-1,/))-1);'; put '%if %index(&root,/tests/) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/tests/)-1);'; put '%end;'; put '%else %if %index(&root,/services) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/services)-1);'; put '%end;'; put '%else %if %index(&root,/jobs) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/jobs)-1);'; put '%end;'; put '%else %put &sysmacroname: Could not find an app location from &pgm;'; put '&root'; put '%mend mf_getapploc ;'; put '%macro mf_getuniquefileref(prefix=_,maxtries=1000,lrecl=32767);'; put '%local rc fname;'; put '%if &prefix=0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%end;'; put '%else %do;'; put '%local x len;'; put '%let len=%eval(8-%length(&prefix));'; put '%let x=0;'; put '%do x=0 %to &maxtries;'; put '%let fname=&prefix%substr(%sysfunc(ranuni(0)),3,&len);'; put '%if %sysfunc(fileref(&fname)) > 0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%return;'; put '%end;'; put '%end;'; put '%put unable to find available fileref after &maxtries attempts;'; put '%end;'; put '%mend mf_getuniquefileref;'; put '%macro mp_abort(mac=mp_abort.sas, type=, msg=, iftrue=%str(1=1)'; put ', errds=work.mp_abort_errds'; put ', mode=REGULAR'; put ')/*/STORE SOURCE*/;'; put '%global sysprocessmode sysprocessname sasjs_stpsrv_header_loc sasjsprocessmode;'; put '%local fref fid i;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%put NOTE: /// mp_abort macro executing //;'; put '%if %length(&mac)>0 %then %put NOTE- called by &mac;'; put '%put NOTE - &msg;'; put '%if %symexist(_SYSINCLUDEFILEDEVICE)'; put '/* abort cancel FILE does not restart outside the INCLUDE on Viya 3.5 */'; put 'and %superq(SYSPROCESSNAME) ne %str(Compute Server)'; put '%then %do;'; put '%if "*&_SYSINCLUDEFILEDEVICE*" ne "**" %then %do;'; put 'data &errds;'; put 'iftrue=''1=1'';'; put 'length mac $100 msg $5000;'; put 'mac=symget(''mac'');'; put 'msg=symget(''msg'');'; put 'run;'; put 'data _null_;'; put 'abort cancel FILE;'; put 'run;'; put '%return;'; put '%end;'; put '%end;'; put '/* Web App Context */'; put '%if %symexist(_PROGRAM)'; put 'or %superq(SYSPROCESSNAME) = %str(Compute Server)'; put 'or &mode=INCLUDE'; put '%then %do;'; put 'options obs=max replace mprint;'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5"'; put '%then %do;'; put 'options nosyntaxcheck;'; put '%end;'; put '%if &mode=INCLUDE %then %do;'; put '%if %sysfunc(exist(&errds))=1 %then %do;'; put 'data _null_;'; put 'set &errds;'; put 'call symputx(''iftrue'',iftrue,''l'');'; put 'call symputx(''mac'',mac,''l'');'; put 'call symputx(''msg'',msg,''l'');'; put 'putlog (_all_)(=);'; put 'run;'; put '%if (&iftrue)=0 %then %return;'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: No include errors found;'; put '%return;'; put '%end;'; put '%end;'; put '/* extract log errs / warns, if exist */'; put '%local logloc logline;'; put '%global logmsg; /* capture global messages */'; put '%if %symexist(SYSPRINTTOLOG) %then %let logloc=&SYSPRINTTOLOG;'; put '%else %let logloc=%qsysfunc(getoption(LOG));'; put 'proc printto log=log;run;'; put '%let logline=0;'; put '%if %length(&logloc)>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input; putlog _infile_;'; put 'i=1;'; put 'retain logonce 0;'; put 'if ('; put '_infile_=:"%str(WARN)ING" or _infile_=:"%str(ERR)OR"'; put ') and logonce=0 then'; put 'do;'; put 'call symputx(''logline'',_n_);'; put 'logonce+1;'; put 'end;'; put 'run;'; put '/* capture log including lines BEFORE the err */'; put '%if &logline>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input;'; put 'i=1;'; put 'stoploop=0;'; put 'if _n_ ge &logline-15 and stoploop=0 then do until (i>22);'; put 'call symputx(''logmsg'',catx(''\n'',symget(''logmsg''),_infile_));'; put 'input;'; put 'i+1;'; put 'stoploop=1;'; put 'end;'; put 'if stoploop=1 then stop;'; put 'run;'; put '%end;'; put '%end;'; put '%if %symexist(SYS_JES_JOB_URI) %then %do;'; put '/* setup webout for Viya */'; put 'options nobomfile;'; put '%if "X&SYS_JES_JOB_URI.X"="XX" %then %do;'; put 'filename _webout temp lrecl=999999 mod;'; put '%end;'; put '%else %do;'; put 'filename _webout filesrvc parenturi="&SYS_JES_JOB_URI"'; put 'name="_webout.json" lrecl=999999 mod;'; put '%end;'; put '%end;'; put '%else %if %sysfunc(filename(fref,&sasjs_stpsrv_header_loc))=0 %then %do;'; put 'options nobomfile;'; put '/* set up http header for SASjs Server */'; put '%let fid=%sysfunc(fopen(&fref,A));'; put '%if &fid=0 %then %do;'; put '%put %str(ERR)OR: %sysfunc(sysmsg());'; put '%return;'; put '%end;'; put '%let rc=%sysfunc(fput(&fid,%str(Content-Type: application/json)));'; put '%let rc=%sysfunc(fwrite(&fid));'; put '%let rc=%sysfunc(fclose(&fid));'; put '%let rc=%sysfunc(filename(&fref));'; put '%end;'; put '/* send response in SASjs JSON format */'; put 'data _null_;'; put 'file _webout mod lrecl=32000 encoding=''utf-8'';'; put 'length msg syswarningtext syserrortext $32767 mode $10 ;'; put 'sasdatetime=datetime();'; put 'msg=symget(''msg'');'; put '%if &logline>0 %then %do;'; put 'msg=cats(msg,''\n\nLog Extract:\n'',symget(''logmsg''));'; put '%end;'; put '/* escape the escapes */'; put 'msg=tranwrd(msg,''\'',''\\'');'; put '/* escape the quotes */'; put 'msg=tranwrd(msg,''"'',''\"'');'; put '/* ditch the CRLFs as chrome complains */'; put 'msg=compress(msg,,''kw'');'; put '/* quote without quoting the quotes (which are escaped instead) */'; put 'msg=cats(''"'',msg,''"'');'; put 'if symexist(''_debug'') then debug=quote(trim(symget(''_debug'')));'; put 'else debug=''""'';'; put 'if symget(''sasjsprocessmode'')=''Stored Program'' then mode=''SASJS'';'; put 'if mode ne ''SASJS'' then put ''>>weboutBEGIN<<'';'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'put '',"sasjsAbort" : [{'';'; put 'put '' "MSG":'' msg ;'; put 'put '' ,"MAC": "'' "&mac" ''"}]'';'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'put '',"_DEBUG":'' debug ;'; put 'if symexist(''_metauser'') then do;'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put 'end;'; put 'if symexist(''SYS_JES_JOB_URI'') then do;'; put 'SYS_JES_JOB_URI=quote(trim(symget(''SYS_JES_JOB_URI'')));'; put 'put '',"SYS_JES_JOB_URI": '' SYS_JES_JOB_URI;'; put 'end;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put ",""SYSWARNINGTEXT"" : " syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'put "}" ;'; put 'if mode ne ''SASJS'' then put ''>>weboutEND<<'';'; put 'run;'; put '%put _all_;'; put '%if "&sysprocessmode " = "SAS Stored Process Server " %then %do;'; put 'data _null_;'; put 'putlog ''stpsrvset program err and syscc'';'; put 'rc=stpsrvset(''program error'', 0);'; put 'call symputx("syscc",0,"g");'; put 'run;'; put '%if &sysscp=WIN'; put 'and 1=0 /* deprecating this logic until we figure out a consistent abort */'; put 'and "%substr(%str(&sysvlong ),1,8)"="9.04.01M"'; put 'and "%substr(%str(&sysvlong ),9,1)">"5" %then %do;'; put '/* skip approach (below) does not work in windows m6+ envs */'; put 'endsas;'; put '%end;'; put '%else %do;'; put '/**'; put '* endsas kills 9.4m3 deployments by orphaning multibridges.'; put '* Abort variants are ungraceful (non zero return code)'; put '* This approach lets SAS run silently until the end :-)'; put '* Caution - fails when called within a %include within a macro'; put '* Use mp_include() to handle this.'; put '*/'; put 'filename skip temp;'; put 'data _null_;'; put 'file skip;'; put 'put ''%macro skip();'';'; put 'comment ''%mend skip; -> fix lint '';'; put 'put ''%macro skippy();'';'; put 'comment ''%mend skippy; -> fix lint '';'; put 'run;'; put '%inc skip;'; put '%end;'; put '%end;'; put '%else %if "&sysprocessmode " = "SAS Compute Server " %then %do;'; put '/* endsas kills the session making it harder to fetch results */'; put 'data _null_;'; put 'syswarningtext=symget(''syswarningtext'');'; put 'syserrortext=symget(''syserrortext'');'; put 'abort_msg=symget(''msg'');'; put 'syscc=symget(''syscc'');'; put 'sysuserid=symget(''sysuserid'');'; put 'iftrue=symget(''iftrue'');'; put 'put (_all_)(/=);'; put 'call symputx(''syscc'',0);'; put 'abort cancel nolist;'; put 'run;'; put '%end;'; put '%else %do;'; put '%abort cancel;'; put '%end;'; put '%end;'; put '%else %do;'; put '%put _all_;'; put '%abort cancel;'; put '%end;'; put '%mend mp_abort;'; put '/** @endcond */'; put '%macro mm_getstpcode('; put 'tree=/User Folders/sasdemo/somestp'; put ',name='; put ',outloc=0'; put ',outref=0'; put ',mDebug=1'; put ',showlog=NO'; put ');'; put '%local mD;'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing &sysmacroname..sas;'; put '%&mD.put _local_;'; put '%if %length(&name)>0 %then %let name=/&name;'; put '/* first, check if STP exists */'; put '%local tsuri;'; put '%let tsuri=stopifempty ;'; put 'data _null_;'; put 'format type uri tsuri value $200.;'; put 'call missing (of _all_);'; put 'path="&tree&name(StoredProcess)";'; put '/* first, find the STP ID */'; put 'if metadata_pathobj("",path,"StoredProcess",type,uri)>0 then do;'; put '/* get sourcecode */'; put 'cnt=1;'; put 'do while (metadata_getnasn(uri,"Notes",cnt,tsuri)>0);'; put 'rc=metadata_getattr(tsuri,"Name",value);'; put '&mD.put tsuri= value=;'; put 'if value="SourceCode" then do;'; put '/* found it! */'; put 'rc=metadata_getattr(tsuri,"Id",value);'; put 'call symputx(''tsuri'',value,''l'');'; put 'stop;'; put 'end;'; put 'cnt+1;'; put 'end;'; put 'end;'; put 'else put (_all_)(=);'; put 'run;'; put '%mp_abort(iftrue= (&tsuri=stopifempty)'; put ',mac=mm_getstpcode'; put ',msg=%str(&tree&name.(StoredProcess) not found!)'; put ')'; put '/**'; put '* Now we can extract the textstore'; put '*/'; put 'filename __getdoc temp lrecl=10000000;'; put 'proc metadata'; put 'in="$METAREPOSITORY'; put ''; put 'SAS1"'; put 'out=__getdoc ;'; put 'run;'; put '/* find the beginning of the text */'; put '%local start;'; put 'data _null_;'; put 'infile __getdoc lrecl=10000;'; put 'input;'; put 'start=index(_infile_,''StoredText="'');'; put 'if start then do;'; put 'call symputx("start",start+11);'; put '*putlog ''"'' _infile_ ''"'';'; put 'end;'; put 'stop;'; put '%local outeng;'; put '%if "&outloc"="0" %then %let outeng=TEMP;'; put '%else %let outeng="&outloc";'; put '%local fref;'; put '%if &outref=0 %then %let fref=%mf_getuniquefileref();'; put '%else %let fref=&outref;'; put '/* read the content, byte by byte, resolving escaped chars */'; put 'filename &fref &outeng lrecl=100000;'; put 'data _null_;'; put 'length filein 8 fileid 8;'; put 'filein = fopen("__getdoc","I",1,"B");'; put 'fileid = fopen("&fref","O",1,"B");'; put 'rec = "20"x;'; put 'length entity $6;'; put 'do while(fread(filein)=0);'; put 'x+1;'; put 'if x>&start then do;'; put 'rc = fget(filein,rec,1);'; put 'if rec=''"'' then leave;'; put 'else if rec="&" then do;'; put 'entity=rec;'; put 'do until (rec=";");'; put 'if fread(filein) ne 0 then goto getout;'; put 'rc = fget(filein,rec,1);'; put 'entity=cats(entity,rec);'; put 'end;'; put 'select (entity);'; put 'when (''&'' ) rec=''&'' ;'; put 'when (''<'' ) rec=''<'' ;'; put 'when (''>'' ) rec=''>'' ;'; put 'when (''''') rec="''" ;'; put 'when (''"'') rec=''"'' ;'; put 'when ('' '') rec=''0A''x;'; put 'when ('' '') rec=''0D''x;'; put 'when (''$'' ) rec=''$'' ;'; put 'when ('' '') rec=''09''x;'; put 'otherwise putlog "%str(WARN)ING: missing value for " entity=;'; put 'end;'; put 'rc =fput(fileid, substr(rec,1,1));'; put 'rc =fwrite(fileid);'; put 'end;'; put 'else do;'; put 'rc =fput(fileid,rec);'; put 'rc =fwrite(fileid);'; put 'end;'; put 'end;'; put 'end;'; put 'getout:'; put 'rc=fclose(filein);'; put 'rc=fclose(fileid);'; put 'run;'; put '%if &showlog=YES %then %do;'; put 'data _null_;'; put 'infile &fref lrecl=32767 end=last;'; put 'input;'; put 'if _n_=1 then putlog ''>>stpcodeBEGIN<<'';'; put 'putlog _infile_;'; put 'if last then putlog ''>>stpcodeEND<<'';'; put 'run;'; put '%end;'; put 'filename __getdoc clear;'; put '%if &outref=0 %then %do;'; put 'filename &fref clear;'; put '%end;'; put '%mend mm_getstpcode;'; put '%macro dc_getsettings();'; put '%global _program;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(syscc=&syscc on entry (&syswarningtext &syserrortext))'; put ')'; put '%if %length(&_PROGRAM)>1 %then %let root=&_program;'; put '%else %do;'; put '%global _metauser;'; put '%let _metauser=&sysuserid;'; put '/* to mimic a "real" _program we need to give a dummy role and stp name */'; put '%let root=/dummyRole/dummyName;'; put '%end;'; put '/* the DC precode is stored in the Admin folder in the root of'; put 'the project. Lets find that root. */'; put '%put &=root;'; put '%let root=%mf_getapploc();'; put '%put &=root;'; put '/* Now we know the root location we can retrieve the params */'; put '%let temploc=%sysfunc(pathname(work))/settings.txt;'; put '%mm_getstpcode(tree=&root/services/public'; put ',name=Data_Controller_Settings'; put ',outloc=&temploc'; put ')'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Unable to run getstpcode)'; put ')'; put 'filename _getsets "&temploc" lrecl=2000;'; put '/*'; put 'Do not use mp_include here - this puts a copy in every service, which creates'; put 'compilation problems when calling services from mp_include'; put '*/'; put '%inc _getsets/source2;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Problem running &sysmacroname (&syswarningtext &syserrortext))'; put ')'; put '%mend dc_getsettings;'; put '%macro mf_fmtdttm('; put ')/*/STORE SOURCE*/;'; put '%if "&sysver"="9.2" or "&sysver"="9.3"'; put 'or ("&sysver"="9.4" and "%substr(&SYSVLONG,9,1)" le "3")'; put 'or "%substr(&sysver,1,1)"="4"'; put 'or "%substr(&sysver,1,1)"="5"'; put '%then %do;DATETIME19.3%end;'; put '%else %do;E8601DT26.6%end;'; put '%mend mf_fmtdttm;'; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '%macro mp_init(prefix=SASJS'; put ')/*/STORE SOURCE*/;'; put '%if %symexist(SASJS_PREFIX) %then %return; /* only run once */'; put '%global'; put 'SASJS_PREFIX /* the ONLY hard-coded global macro variable in SASjs */'; put '&prefix._FUNCTIONS /* used in mcf_init() to track core function compilation */'; put '&prefix._INIT_NUM /* initialisation time as numeric */'; put '&prefix._INIT_DTTM /* initialisation time in E8601DT26.6 format */'; put '&prefix.WORK /* avoid typing %sysfunc(pathname(work)) every time */'; put ';'; put '%let sasjs_prefix=&prefix;'; put 'data _null_;'; put 'dttm=datetime();'; put 'call symputx("&sasjs_prefix._init_num",dttm,''g'');'; put 'call symputx("&sasjs_prefix._init_dttm",put(dttm,E8601DT26.6),''g'');'; put 'call symputx("&sasjs_prefix.work",pathname(''WORK''),''g'');'; put 'run;'; put 'options'; put 'compress=CHAR /* default is none so ensure we have something! */'; put 'datastmtchk=ALLKEYWORDS /* protection from overwriting input datasets */'; put 'errorcheck=STRICT /* catch errs in libname/filename statements */'; put 'fmterr /* ensure err when a format cannot be found */'; put 'mergenoby=%str(ERR)OR /* throw err when a merge has no BY variables */'; put 'missing=. /* changing this can cause hard to detect errs */'; put 'noquotelenmax /* avoid warnings for long strings */'; put 'noreplace /* avoid overwriting permanent datasets */'; put 'ps=max /* reduce log size slightly */'; put 'ls=max /* reduce log even more and avoid word truncation */'; put 'validmemname=COMPATIBLE /* avoid special characters etc in table names */'; put 'validvarname=V7 /* avoid special characters etc in variable names */'; put 'varinitchk=%str(ERR)OR /* avoid data mistakes from variable name typos */'; put 'varlenchk=%str(ERR)OR /* fail hard if truncation (data loss) can result */'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5" %then %do;'; put 'noautocorrect /* disallow misspelled procedure names */'; put 'dsoptions=note2err /* undocumented - convert bad NOTEs to ERRs */'; put '%end;'; put ';'; put '%mend mp_init;'; put '%macro mpeinit(fetch=YES);'; put '%global mpeinit'; put 'mpeadmins /* group with unrestricted Meditor access */'; put 'mpelocapprovals /* location for landing and staging files */'; put 'mpelib /* location of configuration tables for DC */'; put 'dc_repo_users /* location of user / group metadata */'; put 'dc_licence_key /* extracted in dc_getsettings */'; put 'dc_activation_key /* extracted in dc_getsettings */'; put 'dc_locale /* extracted in dc_getsettings */'; put 'dc_dttmtfmt /* can be overridden in dc_getsettings */'; put '_debug'; put ';'; put '%if &mpeinit=1 %then %return;'; put '%else %let mpeinit=1;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(Problem on service startup (&syswarningtext &syserrortext))'; put ')'; put '%mp_init()'; put '%if &fetch=YES %then %do;'; put '%webout(FETCH)'; put '%end;'; put '%global _CLIENTNAME;'; put '%mp_abort(iftrue= (&_CLIENTNAME=SAS Enterprise Guide)'; put ',mac=&_program..sas'; put ',msg=%str(Data Controller is a web app and should not be executed from EG)'; put ')'; put 'options urlencoding=utf8 nobomfile lrecl=32767;'; put '%let perf=%sysfunc(datetime());'; put '%put perfdiff: 0;'; put '%let dc_locale=SYSTEM; /* default if not set */'; put '/**'; put '* E8601DT26.6 has widest database support - but not all SAS flavours can'; put '* handle it. Override in the settings STP if needed.'; put '*/'; put 'data _null_;'; put 'dc_dttmtfmt=''"%sysfunc(datetime(),''!!"%mf_fmtdttm()"!!'')"dt'';'; put 'call symputx(''dc_dttmtfmt'',dc_dttmtfmt);'; put 'put dc_dttmtfmt=;'; put 'run;'; put '%put &=dc_dttmtfmt;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc prior to dc_getsettings)'; put ')'; put '%dc_getsettings()'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc after dc_getsettings)'; put ')'; put 'data _null_;'; put 'set &DC_LIBREF..mpe_config(where=('; put 'var_scope="DC"'; put 'and &dc_dttmtfmt lt tx_to'; put 'and var_active=1'; put '));'; put 'call symputx(var_name,var_value,''G'');'; put 'putlog var_name "=" var_value;'; put 'run;'; put '%let mpelib=&dc_libref;'; put '%let mpeadmins=&dc_admin_group;'; put '%let mpelocapprovals=&dc_staging_area;'; put '%let dc_repo_users=&dc_repo_users;'; put '%if &dc_locale ne SYSTEM %then %do;'; put 'options locale=&dc_locale;'; put '%end;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(Problem during compilation or with STP precode (&syswarningtext))'; put ')'; put '%mend mpeinit;'; put '%macro mf_mval(var);'; put '%if %symexist(&var) %then %do;'; put '%superq(&var)'; put '%end;'; put '%mend mf_mval;'; put '%macro mf_trimstr(basestr,trimstr);'; put '%local baselen trimlen trimval;'; put '/* return if basestr is shorter than trimstr (or 0) */'; put '%let baselen=%length(%superq(basestr));'; put '%let trimlen=%length(%superq(trimstr));'; put '%if &baselen < &trimlen or &baselen=0 %then %return;'; put '/* obtain the characters from the end of basestr */'; put '%let trimval=%qsubstr(%superq(basestr)'; put ',%length(%superq(basestr))-&trimlen+1'; put ',&trimlen);'; put '/* compare and if matching, chop it off! */'; put '%if %superq(basestr)=%superq(trimstr) %then %do;'; put '%return;'; put '%end;'; put '%else %if %superq(trimval)=%superq(trimstr) %then %do;'; put '%qsubstr(%superq(basestr),1,%length(%superq(basestr))-&trimlen)'; put '%end;'; put '%else %do;'; put '&basestr'; put '%end;'; put '%mend mf_trimstr;'; put '%macro mf_getplatform(switch'; put ')/*/STORE SOURCE*/;'; put '%local a b c;'; put '%if &switch.NONE=NONE %then %do;'; put '%if %symexist(sasjsprocessmode) %then %do;'; put '%if &sasjsprocessmode=Stored Program %then %do;'; put 'SASJS'; put '%return;'; put '%end;'; put '%end;'; put '%if %symexist(sysprocessmode) %then %do;'; put '%if "&sysprocessmode"="SAS Object Server"'; put 'or "&sysprocessmode"= "SAS Compute Server" %then %do;'; put 'SASVIYA'; put '%end;'; put '%else %if "&sysprocessmode"="SAS Stored Process Server"'; put 'or "&sysprocessmode"="SAS Workspace Server"'; put '%then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if %symexist(_metaport) or %symexist(_metauser) %then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if &switch=SASSTUDIO %then %do;'; put '/* return the version of SAS Studio else 0 */'; put '%if %mf_mval(_CLIENTAPP)=%str(SAS Studio) %then %do;'; put '%let a=%mf_mval(_CLIENTVERSION);'; put '%let b=%scan(&a,1,.);'; put '%if %eval(&b >2) %then %do;'; put '&b'; put '%end;'; put '%else 0;'; put '%end;'; put '%else 0;'; put '%end;'; put '%else %if &switch=VIYARESTAPI %then %do;'; put '%mf_trimstr(%sysfunc(getoption(servicesbaseurl)),/)'; put '%end;'; put '%mend mf_getplatform;'; put '%macro mpeterm();'; put '%local oldloc;'; put 'data _null_;'; put 'if symexist(''SYSPRINTTOLOG'') then oldloc=symget(''SYSPRINTTOLOG'');'; put 'else oldloc=getoption(''LOG'');'; put 'if subpad(oldloc,1,1) not in (''"'',"''",'' '') then oldloc=quote(cats(oldloc));'; put 'call symputx(''oldloc'',oldloc,''l'');'; put 'run;'; put '%if %length(&oldloc)>0 %then %do;'; put 'proc printto log=log;'; put 'run;'; put 'data _null_;'; put 'infile &oldloc;'; put 'input; putlog _infile_;'; put 'run;'; put '%end;'; put '%if %sysfunc(exist(&dc_libref..mpe_requests)) and %mf_getplatform() ne SASVIYA'; put '%then %do;'; put 'data ;'; put 'if 0 then set &dc_libref..mpe_requests;'; put 'request_dttm=%sysfunc(datetime());'; put 'request_user="%mf_getuser()";'; put 'request_service="%scan(&_program,-2,/)/%scan(&_program,-1,/)";'; put 'request_params='''';'; put 'output;stop;'; put 'proc append base=&dc_libref..mpe_requests data=&syslast force nowarn;'; put 'run;'; put '%end;'; put '%mend mpeterm;'; put '%macro mm_getGroups('; put 'user='; put ',outds=work.mm_getGroups'; put ',repo=foundation'; put ',mDebug=0'; put ')/*/STORE SOURCE*/;'; put '%local mD oldrepo;'; put '%let oldrepo=%sysfunc(getoption(metarepository));'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing mm_getGroups.sas;'; put '%&mD.put _local_;'; put '/* on some sites, user / group info is in a different metadata repo to the'; put 'default */'; put '%if &oldrepo ne &repo %then %do;'; put 'options metarepository=&repo;'; put '%end;'; put '%if %length(&user)=0 %then %do;'; put 'data &outds (keep=groupuri groupname groupdesc);'; put 'length groupuri groupname groupdesc group_or_role $256;'; put 'call missing(of _all_);'; put 'i+1;'; put 'do while'; put '(metadata_getnobj("omsobj:IdentityGroup?@Id contains ''.''",i,groupuri)>0);'; put 'rc=metadata_getattr(groupuri, "Name", groupname);'; put 'rc=metadata_getattr(groupuri, "Desc", groupdesc);'; put 'rc=metadata_getattr(groupuri,"PublicType",group_or_role);'; put 'if Group_or_Role = ''UserGroup'' then output;'; put 'i+1;'; put 'end;'; put 'run;'; put '%end;'; put '%else %do;'; put 'data &outds (keep=groupuri groupname groupdesc);'; put 'length uri groupuri groupname groupdesc group_or_role $256;'; put 'call missing(of _all_);'; put 'rc=metadata_getnobj("omsobj:Person?@Name=''&user''",1,uri);'; put 'if rc<=0 then do;'; put 'putlog "%str(WARN)ING: rc=" rc "&user not found "'; put '", or there was an issue reading the repository.";'; put 'stop;'; put 'end;'; put 'a=1;'; put 'grpassn=metadata_getnasn(uri,"IdentityGroups",a,groupuri);'; put 'if grpassn in (-3,-4) then do;'; put 'putlog "%str(WARN)ING: No metadata groups found for &user";'; put 'output;'; put 'end;'; put 'else do while (grpassn > 0);'; put 'rc=metadata_getattr(groupuri, "Name", groupname);'; put 'rc=metadata_getattr(groupuri, "Desc", groupdesc);'; put 'a+1;'; put 'rc=metadata_getattr(groupuri,"PublicType",group_or_role);'; put 'if Group_or_Role = ''UserGroup'' then output;'; put 'grpassn=metadata_getnasn(uri,"IdentityGroups",a,groupuri);'; put 'end;'; put 'run;'; put '%end;'; put '%if &oldrepo ne &repo %then %do;'; put 'options metarepository=&oldrepo;'; put '%end;'; put '%mend mm_getGroups;'; put '%macro dc_getusergroups(user=,outds=mm_getgroups);'; put '%global dc_repo_users;'; put '%if &dc_repo_users= %then %let dc_repo_users=foundation;'; put '%mm_getgroups(user=&user,outds=&outds,repo=&dc_repo_users)'; put '%mend dc_getusergroups;'; put '* SAS Macros end;'; put '* SAS Includes start;'; put '* SAS Includes end;'; put '* Binary Files start;'; put '* Binary Files end;'; put '* ServiceInit start;'; put 'options noquotelenmax ps=max;'; put '/* create dummy macros to prevent stpbegin / stpend from corrupting sessions */'; put '%macro stpbegin();'; put '%put NOTE: the STPBEGIN macro should not be used for web apps!;'; put '%mend stpbegin;'; put '%macro stpend();'; put '%put NOTE: the STPEND macro should not be used for web apps!;'; put '%mend stpend;'; put '* ServiceInit end;'; put '* Service start;'; put '/**'; put '@file'; put '@brief provides the web service to create the control tables'; put '@details'; put '

SAS Macros

'; put '@li dc_getusergroups.sas'; put '@li mf_getuser.sas'; put '@li mf_getapploc.sas'; put '@version 9.2'; put '@author 4GL Apps Ltd'; put '@copyright 4GL Apps Ltd. This code may only be used within Data Controller'; put 'and may not be re-distributed or re-sold without the express permission of'; put '4GL Apps Ltd.'; put '**/'; put '%let root=%mf_getapploc();'; put '/* create web page */'; put 'data _null_;'; put 'file _webout;'; put 'infile datalines ;'; put 'input;'; put 'put _infile_;'; put 'datalines4;'; put ''; put ''; put ''; put ''; put 'Data Controller'; put ''; put ''; put ''; put ''; put ''; put '
'; put '
'; put ''; put 'Data Controller'; put '
'; put '
'; put '
'; put '
'; put '
'; put '
'; put ''; put '
'; put '
'; put 'Terms and Conditions'; put '
'; put '
'; put '
'; put '

Due to the way the Demo version is compiled (in an easy-to-deploy but'; put 'inefficient-to-run format), it should not be deployed to production servers.'; put 'Before proceeding with configuration, please confirm that you have read,'; put 'understood, and agreed to the'; put 'Data Controller for SAS© Evaluation Agreement.'; put '

'; put '
'; put '
'; put '>'; put ''; put '
'; put '
'; put '
'; put ''; put ''; put '
'; put '
'; put '
'; put ''; put ''; put ';;;;'; put 'run;'; put '* Service end;'; run; %mm_createwebservice(path=&appLoc/&path, name=&service, code=sascode, server=&serverName, replace=yes) filename sascode clear; %let service=exportconfig; filename sascode temp lrecl=32767; data _null_; file sascode; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '/**'; put '@file mp_jsonout.sas'; put '@brief Writes JSON in SASjs format to a fileref'; put '@details This macro can be used to OPEN a JSON stream and send one or more'; put 'tables as arrays of rows, where each row can be an object or a nested array.'; put 'There are two engines available - DATASTEP or PROCJSON.'; put 'PROC JSON is fast but will produce errs like the ones below if'; put 'special chars are encountered.'; put '> (ERR)OR: Some code points did not transcode.'; put '> An object or array close is not valid at this point in the JSON text.'; put '> Date value out of range'; put 'If this happens, try running with ENGINE=DATASTEP.'; put 'The DATASTEP engine is used to handle special SAS missing numerics, and'; put 'can also convert entire datasets to formatted values. Output JSON is always'; put 'in UTF-8.'; put 'Usage:'; put 'filename tmp temp;'; put 'data class; set sashelp.class;run;'; put '%mp_jsonout(OPEN,jref=tmp)'; put '%mp_jsonout(OBJ,class,jref=tmp)'; put '%mp_jsonout(OBJ,class,dslabel=class2,jref=tmp,showmeta=Y)'; put '%mp_jsonout(CLOSE,jref=tmp)'; put 'data _null_;'; put 'infile tmp;'; put 'input;putlog _infile_;'; put 'run;'; put 'If you are building web apps with SAS then you are strongly encouraged to use'; put 'the mX_createwebservice macros in combination with the'; put '[sasjs adapter](https://github.com/sasjs/adapter).'; put 'For more information see https://sasjs.io'; put '@param [in] action Valid values:'; put '@li OPEN - opens the JSON'; put '@li OBJ - sends a table with each row as an object'; put '@li ARR - sends a table with each row in an array'; put '@li CLOSE - closes the JSON'; put '@param [in] ds The dataset to send. Must be a work table.'; put '@param [out] jref= (_webout) The fileref to which to send the JSON'; put '@param [out] dslabel= The name to give the table in the exported JSON'; put '@param [in] fmt= (Y) Whether to keep (Y) or strip (N) formats from the table'; put '@param [in] engine= (DATASTEP) Which engine to use to send the JSON. Options:'; put '@li PROCJSON (default)'; put '@li DATASTEP (more reliable when data has non standard characters)'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to JSON'; put '

Related Files

'; put '@li mp_ds2fmtds.sas'; put '@version 9.2'; put '@author Allan Bowe'; put '@source https://github.com/sasjs/core'; put '**/'; put '%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y'; put ',engine=DATASTEP'; put ',missing=NULL'; put ',showmeta=N'; put ',maxobs=MAX'; put ')/*/STORE SOURCE*/;'; put '%local tempds colinfo fmtds i numcols numobs stmt_obs lastobs optval'; put 'tmpds1 tmpds2 tmpds3 tmpds4;'; put '%let numcols=0;'; put '%if &maxobs ne MAX %then %let stmt_obs=%str(if _n_>&maxobs then stop;);'; put '%if &action=OPEN %then %do;'; put 'options nobomfile;'; put 'data _null_;file &jref encoding=''utf-8'' lrecl=200;'; put 'put ''{"PROCESSED_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''"'';'; put 'run;'; put '%end;'; put '%else %if (&action=ARR or &action=OBJ) %then %do;'; put '/* force variable names to always be uppercase in the JSON */'; put 'options validvarname=upcase;'; put '/* To avoid issues with _webout on EBI - such as encoding diffs and truncation'; put '(https://support.sas.com/kb/49/325.html) we use temporary files */'; put 'filename _sjs1 temp lrecl=200 ;'; put 'data _null_; file _sjs1 encoding=''utf-8'';'; put 'put ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":";'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjs1 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs1 clear;'; put '/* grab col defs */'; put 'proc contents noprint data=&ds'; put 'out=_data_(keep=name type length format formatl formatd varnum label);'; put 'run;'; put '%let colinfo=%scan(&syslast,2,.);'; put 'proc sort data=&colinfo;'; put 'by varnum;'; put 'run;'; put '/* move meta to mac vars */'; put 'data &colinfo;'; put 'if _n_=1 then call symputx(''numcols'',nobs,''l'');'; put 'set &colinfo end=last nobs=nobs;'; put 'name=upcase(name);'; put '/* fix formats */'; put 'if type=2 or type=6 then do;'; put 'typelong=''char'';'; put 'length fmt $49.;'; put 'if format='''' then fmt=cats(''$'',length,''.'');'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else fmt=cats(format,formatl,''.'');'; put 'end;'; put 'else do;'; put 'typelong=''num'';'; put 'if format='''' then fmt=''best.'';'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else if formatd=0 then fmt=cats(format,formatl,''.'');'; put 'else fmt=cats(format,formatl,''.'',formatd);'; put 'end;'; put '/* 32 char unique name */'; put 'newname=''sasjs''!!substr(cats(put(md5(name),$hex32.)),1,27);'; put 'call symputx(cats(''name'',_n_),name,''l'');'; put 'call symputx(cats(''newname'',_n_),newname,''l'');'; put 'call symputx(cats(''length'',_n_),length,''l'');'; put 'call symputx(cats(''fmt'',_n_),fmt,''l'');'; put 'call symputx(cats(''type'',_n_),type,''l'');'; put 'call symputx(cats(''typelong'',_n_),typelong,''l'');'; put 'call symputx(cats(''label'',_n_),coalescec(label,name),''l'');'; put '/* overwritten when fmt=Y and a custom format exists in catalog */'; put 'if typelong=''num'' then call symputx(cats(''fmtlen'',_n_),200,''l'');'; put 'else call symputx(cats(''fmtlen'',_n_),min(32767,ceil((length+10)*1.5)),''l'');'; put 'run;'; put '%let tempds=%substr(_%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql;'; put 'select count(*) into: lastobs from &ds;'; put '%if &maxobs ne MAX %then %let lastobs=%sysfunc(min(&lastobs,&maxobs));'; put '%if &engine=PROCJSON %then %do;'; put '%if &missing=STRING %then %do;'; put '%put &sysmacroname: Special Missings not supported in proc json.;'; put '%put &sysmacroname: Switching to DATASTEP engine;'; put '%goto datastep;'; put '%end;'; put 'data &tempds;'; put 'set &ds;'; put '&stmt_obs;'; put '%if &fmt=N %then format _numeric_ best32.;;'; put '/* PRETTY is necessary to avoid line truncation in large files */'; put 'filename _sjs2 temp lrecl=131068 encoding=''utf-8'';'; put 'proc json out=_sjs2 pretty'; put '%if &action=ARR %then nokeys ;'; put ';export &tempds / nosastags fmtnumeric;'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs2 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs2 clear;'; put '%end;'; put '%else %if &engine=DATASTEP %then %do;'; put '%datastep:'; put '%if %sysfunc(exist(&ds)) ne 1 & %sysfunc(exist(&ds,VIEW)) ne 1'; put '%then %do;'; put '%put &sysmacroname: &ds NOT FOUND!!!;'; put '%return;'; put '%end;'; put '%if &fmt=Y %then %do;'; put '/**'; put '* Extract format definitions'; put '* First, by getting library locations from dictionary.formats'; put '* Then, by exporting the width using proc format'; put '* Cannot use maxw from sashelp.vformat as not always populated'; put '* Cannot use fmtinfo() as not supported in all flavours'; put '*/'; put '%let tmpds1=%substr(fmtsum%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds2=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds3=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds4=%substr(col%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql noprint;'; put 'create table &tmpds1 as'; put 'select cats(libname,''.'',memname) as FMTCAT,'; put 'FMTNAME'; put 'from dictionary.formats'; put 'where fmttype=''F'' and libname is not null'; put 'and fmtname in (select format from &colinfo where format is not null)'; put 'order by 1;'; put 'create table &tmpds2('; put 'FMTNAME char(32),'; put 'LENGTH num'; put ');'; put '%local catlist cat fmtlist i;'; put 'select distinct fmtcat into: catlist separated by '' '' from &tmpds1;'; put '%do i=1 %to %sysfunc(countw(&catlist,%str( )));'; put '%let cat=%scan(&catlist,&i,%str( ));'; put 'proc sql;'; put 'select distinct fmtname into: fmtlist separated by '' '''; put 'from &tmpds1 where fmtcat="&cat";'; put 'proc format lib=&cat cntlout=&tmpds3(keep=fmtname length);'; put 'select &fmtlist;'; put 'run;'; put 'proc sql;'; put 'insert into &tmpds2 select distinct fmtname,length from &tmpds3;'; put '%end;'; put 'proc sql;'; put 'create table &tmpds4 as'; put 'select a.*, b.length as MAXW'; put 'from &colinfo a'; put 'left join &tmpds2 b'; put 'on cats(a.format)=cats(upcase(b.fmtname))'; put 'order by a.varnum;'; put 'data _null_;'; put 'set &tmpds4;'; put 'if not missing(maxw);'; put 'call symputx('; put 'cats(''fmtlen'',_n_),'; put '/* vars need extra padding due to JSON escaping of special chars */'; put 'min(32767,ceil((max(length,maxw)+10)*1.5))'; put ',''l'''; put ');'; put 'run;'; put '/* configure varlenchk - as we are explicitly shortening the variables */'; put '%let optval=%sysfunc(getoption(varlenchk));'; put 'options varlenchk=NOWARN;'; put 'data _data_(compress=char);'; put '/* shorten the new vars */'; put 'length'; put '%do i=1 %to &numcols;'; put '&&name&i $&&fmtlen&i'; put '%end;'; put ';'; put '/* rename on entry */'; put 'set &ds(rename=('; put '%do i=1 %to &numcols;'; put '&&name&i=&&newname&i'; put '%end;'; put '));'; put '&stmt_obs;'; put 'drop'; put '%do i=1 %to &numcols;'; put '&&newname&i'; put '%end;'; put ';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=num %then %do;'; put '&&name&i=cats(put(&&newname&i,&&fmt&i));'; put '%end;'; put '%else %do;'; put '&&name&i=put(&&newname&i,&&fmt&i);'; put '%end;'; put '%end;'; put 'if _error_ then do;'; put 'call symputx(''syscc'',1012);'; put 'stop;'; put 'end;'; put 'run;'; put '%let fmtds=&syslast;'; put 'options varlenchk=&optval;'; put '%end;'; put 'proc format; /* credit yabwon for special null removal */'; put 'value bart (default=40)'; put '%if &missing=NULL %then %do;'; put '._ - .z = null'; put '%end;'; put '%else %do;'; put '._ = [quote()]'; put '. = null'; put '.a - .z = [quote()]'; put '%end;'; put 'other = [best.];'; put 'data &tempds;'; put 'attrib _all_ label='''';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'length &&name&i $&&fmtlen&i...;'; put 'format &&name&i $&&fmtlen&i...;'; put '%end;'; put '%end;'; put '%if &fmt=Y %then %do;'; put 'set &fmtds;'; put '%end;'; put '%else %do;'; put 'set &ds;'; put '%end;'; put '&stmt_obs;'; put 'format _numeric_ bart.;'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'if findc(&&name&i,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put '&&name&i=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,&&name&i)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else &&name&i=quote(cats(&&name&i));'; put '%end;'; put '%end;'; put 'run;'; put 'filename _sjs3 temp lrecl=131068 ;'; put 'data _null_;'; put 'file _sjs3 encoding=''utf-8'';'; put 'if _n_=1 then put "[";'; put 'set &tempds;'; put 'if _n_>1 then put "," @; put'; put '%if &action=ARR %then "[" ; %else "{" ;'; put '%do i=1 %to &numcols;'; put '%if &i>1 %then "," ;'; put '%if &action=OBJ %then """&&name&i"":" ;'; put '"&&name&i"n /* name literal for reserved variable names */'; put '%end;'; put '%if &action=ARR %then "]" ; %else "}" ; ;'; put '/* close out the table */'; put 'data _null_;'; put 'file _sjs3 mod encoding=''utf-8'';'; put 'put '']'';'; put 'run;'; put 'data _null_;'; put 'infile _sjs3 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs3 clear;'; put '%end;'; put 'proc sql;'; put 'drop table &colinfo, &tempds;'; put '%if %substr(&showmeta,1,1)=Y %then %do;'; put 'filename _sjs4 temp lrecl=131068 encoding=''utf-8'';'; put 'data _null_;'; put 'file _sjs4;'; put 'length label $350;'; put 'put ", ""$%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":{""vars"":{";'; put 'do i=1 to &numcols;'; put 'name=quote(trim(symget(cats(''name'',i))));'; put 'format=quote(trim(symget(cats(''fmt'',i))));'; put 'label=quote(prxchange(''s/\\/\\\\/'',-1,trim(symget(cats(''label'',i)))));'; put 'length=quote(trim(symget(cats(''length'',i))));'; put 'type=quote(trim(symget(cats(''typelong'',i))));'; put 'if i>1 then put "," @@;'; put 'put name '':{"format":'' format '',"label":'' label'; put ''',"length":'' length '',"type":'' type ''}'';'; put 'end;'; put 'put ''}}'';'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs4 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs4 clear;'; put '%end;'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put 'data _null_; file &jref encoding=''utf-8'' mod ;'; put 'put "}";'; put 'run;'; put '%end;'; put '%mend mp_jsonout;'; put '/**'; put '@file mm_webout.sas'; put '@brief Send data to/from SAS Stored Processes'; put '@details This macro should be added to the start of each Stored Process,'; put '**immediately** followed by a call to:'; put '%mm_webout(FETCH)'; put 'This will read all the input data and create same-named SAS datasets in the'; put 'WORK library. You can then insert your code, and send data back using the'; put 'following syntax:'; put 'data some datasets; * make some data ;'; put 'retain some columns;'; put 'run;'; put '%mm_webout(OPEN)'; put '%mm_webout(ARR,some) * Array format, fast, suitable for large tables ;'; put '%mm_webout(OBJ,datasets) * Object format, easier to work with ;'; put 'Finally, wrap everything up send some helpful system variables too'; put '%mm_webout(CLOSE)'; put '@param [in] action Either FETCH, OPEN, ARR, OBJ or CLOSE'; put '@param [in] ds The dataset to send back to the frontend'; put '@param [out] dslabel= Value to use instead of table name for sending to JSON'; put '@param [in] fmt= (N) Setting Y converts all vars to their formatted values'; put '@param [out] fref= (_webout) The fileref to which to write the JSON'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] workobs= (0) When set to a positive integer, will create a new'; put 'output object (WORK) which contains this number of observations from all'; put 'tables in the WORK library.'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to output JSON'; put '

SAS Macros

'; put '@li mp_jsonout.sas'; put '

Related Macros

'; put '@li ms_webout.sas'; put '@li mv_webout.sas'; put '@version 9.3'; put '@author Allan Bowe'; put '**/'; put '%macro mm_webout(action,ds,dslabel=,fref=_webout,fmt=N,missing=NULL'; put ',showmeta=N,maxobs=MAX,workobs=0'; put ');'; put '%global _webin_file_count _webin_fileref1 _webin_name1 _program _debug'; put 'sasjs_tables;'; put '%local i tempds jsonengine;'; put '/* see https://github.com/sasjs/core/issues/41 */'; put '%if "%upcase(&SYSENCODING)" ne "UTF-8" %then %let jsonengine=PROCJSON;'; put '%else %let jsonengine=DATASTEP;'; put '%if &action=FETCH %then %do;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'options mprint notes mprintnest;'; put '%end;'; put '%let _webin_file_count=%eval(&_webin_file_count+0);'; put '/* now read in the data */'; put '%do i=1 %to &_webin_file_count;'; put '%if &_webin_file_count=1 %then %do;'; put '%let _webin_fileref1=&_webin_fileref;'; put '%let _webin_name1=&_webin_name;'; put '%end;'; put 'data _null_;'; put 'infile &&_webin_fileref&i termstr=crlf;'; put 'input;'; put 'call symputx(''input_statement'',_infile_);'; put 'putlog "&&_webin_name&i input statement: " _infile_;'; put 'stop;'; put 'data &&_webin_name&i;'; put 'infile &&_webin_fileref&i firstobs=2 dsd termstr=crlf encoding=''utf-8'';'; put 'input &input_statement;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'if _n_<20 then putlog _infile_;'; put '%end;'; put 'run;'; put '%let sasjs_tables=&sasjs_tables &&_webin_name&i;'; put '%end;'; put '%end;'; put '%else %if &action=OPEN %then %do;'; put '/* fix encoding */'; put 'OPTIONS NOBOMFILE;'; put '/**'; put '* check xengine type to avoid the below err message:'; put '* > Function is only valid for filerefs using the CACHE access method.'; put '*/'; put 'data _null_;'; put 'set sashelp.vextfl(where=(fileref="_WEBOUT"));'; put 'if xengine=''STREAM'' then do;'; put 'rc=stpsrv_header(''Content-type'',"text/html; encoding=utf-8");'; put 'end;'; put 'run;'; put '/* setup json */'; put 'data _null_;file &fref encoding=''utf-8'';'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutBEGIN<<'';'; put '%end;'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'run;'; put '%end;'; put '%else %if &action=ARR or &action=OBJ %then %do;'; put '%mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt,jref=&fref'; put ',engine=&jsonengine,missing=&missing,showmeta=&showmeta,maxobs=&maxobs'; put ')'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put '/* To avoid issues with _webout on EBI we use a temporary file */'; put 'filename _sjsref temp lrecl=131068;'; put '%if %str(&workobs) > 0 %then %do;'; put '/* if debug mode, send back first XX records of each work table also */'; put 'data;run;%let tempds=%scan(&syslast,2,.);'; put 'ods output Members=&tempds;'; put 'proc datasets library=WORK memtype=data;'; put '%local wtcnt;%let wtcnt=0;'; put 'data _null_;'; put 'set &tempds;'; put 'if not (upcase(name) =:"DATA"); /* ignore temp datasets */'; put 'i+1;'; put 'call symputx(cats(''wt'',i),name,''l'');'; put 'call symputx(''wtcnt'',i,''l'');'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put ",""WORK"":{";'; put '%do i=1 %to &wtcnt;'; put '%let wt=&&wt&i;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'dsid=open("WORK.&wt",''is'');'; put 'nlobs=attrn(dsid,''NLOBS'');'; put 'nvars=attrn(dsid,''NVARS'');'; put 'rc=close(dsid);'; put 'if &i>1 then put '',''@;'; put 'put " ""&wt"" : {";'; put 'put ''"nlobs":'' nlobs;'; put 'put '',"nvars":'' nvars;'; put '%mp_jsonout(OBJ,&wt,jref=_sjsref,dslabel=first10rows,showmeta=Y'; put ',maxobs=&workobs'; put ')'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put '%end;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put 'run;'; put '%end;'; put '/* close off json */'; put 'data _null_;file _sjsref mod encoding=''utf-8'';'; put 'length SYSPROCESSNAME syserrortext syswarningtext autoexec $512;'; put 'put ",""_DEBUG"" : ""&_debug"" ";'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'autoexec=quote(urlencode(trim(getoption(''autoexec''))));'; put 'put '',"AUTOEXEC" : '' autoexec;'; put 'put ",""MF_GETUSER"" : ""%mf_getuser()"" ";'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'put ",""SYSENCODING"" : ""&sysencoding"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSPROCESSID"" : ""&SYSPROCESSID"" ";'; put 'put ",""SYSPROCESSMODE"" : ""&SYSPROCESSMODE"" ";'; put 'SYSPROCESSNAME=quote(urlencode(cats(SYSPROCESSNAME)));'; put 'put ",""SYSPROCESSNAME"" : " SYSPROCESSNAME;'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put '',"SYSWARNINGTEXT" : '' syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'length memsize $32;'; put 'memsize="%sysfunc(INPUTN(%sysfunc(getoption(memsize)), best.),sizekmg.)";'; put 'memsize=quote(cats(memsize));'; put 'put '',"MEMSIZE" : '' memsize;'; put 'put "}" @;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutEND<<'';'; put '%end;'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjsref lrecl=1 recfm=n;'; put 'file &fref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjsref clear;'; put '%end;'; put '%mend mm_webout;'; put '%macro webout(action,ds,dslabel=,fmt=,missing=NULL,showmeta=NO,maxobs=MAX);'; put '%mm_webout(&action,ds=&ds,dslabel=&dslabel,fmt=&fmt'; put ',missing=&missing'; put ',showmeta=&showmeta'; put ',maxobs=&maxobs'; put ') %mend;'; put '/* provide additional debug info */'; put '%global _program;'; put '%put &=syscc;'; put '%put user=%mf_getuser();'; put '%put pgm=&_program;'; put '%put timestamp=%sysfunc(datetime(),datetime19.);'; put '* Service Variables start;'; put '* Service Variables end;'; put '* SAS Macros start;'; put '%macro mf_getapploc(pgm);'; put '%if "&pgm"="" %then %do;'; put '%if %symexist(_program) %then %let pgm=&_program;'; put '%else %do;'; put '%put &sysmacroname: No value provided and no _program variable available;'; put '%return;'; put '%end;'; put '%end;'; put '%local root;'; put '/**'; put '* First check we are not in the tests/macros folder (which has no subfolders)'; put '* or specifically in the testsetup or testteardown services'; put '*/'; put '%if %index(&pgm,/tests/macros/)'; put 'or %index(&pgm,/tests/testsetup)'; put 'or %index(&pgm,/tests/testteardown)'; put '%then %do;'; put '%let root=%substr(&pgm,1,%index(&pgm,/tests)-1);'; put '&root'; put '%return;'; put '%end;'; put '/**'; put '* Next, move up two levels to avoid matches on subfolder or service name'; put '*/'; put '%let root=%substr(&pgm,1,%length(&pgm)-%length(%scan(&pgm,-1,/))-1);'; put '%let root=%substr(&root,1,%length(&root)-%length(%scan(&root,-1,/))-1);'; put '%if %index(&root,/tests/) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/tests/)-1);'; put '%end;'; put '%else %if %index(&root,/services) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/services)-1);'; put '%end;'; put '%else %if %index(&root,/jobs) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/jobs)-1);'; put '%end;'; put '%else %put &sysmacroname: Could not find an app location from &pgm;'; put '&root'; put '%mend mf_getapploc ;'; put '%macro mf_getuniquefileref(prefix=_,maxtries=1000,lrecl=32767);'; put '%local rc fname;'; put '%if &prefix=0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%end;'; put '%else %do;'; put '%local x len;'; put '%let len=%eval(8-%length(&prefix));'; put '%let x=0;'; put '%do x=0 %to &maxtries;'; put '%let fname=&prefix%substr(%sysfunc(ranuni(0)),3,&len);'; put '%if %sysfunc(fileref(&fname)) > 0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%return;'; put '%end;'; put '%end;'; put '%put unable to find available fileref after &maxtries attempts;'; put '%end;'; put '%mend mf_getuniquefileref;'; put '%macro mp_abort(mac=mp_abort.sas, type=, msg=, iftrue=%str(1=1)'; put ', errds=work.mp_abort_errds'; put ', mode=REGULAR'; put ')/*/STORE SOURCE*/;'; put '%global sysprocessmode sysprocessname sasjs_stpsrv_header_loc sasjsprocessmode;'; put '%local fref fid i;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%put NOTE: /// mp_abort macro executing //;'; put '%if %length(&mac)>0 %then %put NOTE- called by &mac;'; put '%put NOTE - &msg;'; put '%if %symexist(_SYSINCLUDEFILEDEVICE)'; put '/* abort cancel FILE does not restart outside the INCLUDE on Viya 3.5 */'; put 'and %superq(SYSPROCESSNAME) ne %str(Compute Server)'; put '%then %do;'; put '%if "*&_SYSINCLUDEFILEDEVICE*" ne "**" %then %do;'; put 'data &errds;'; put 'iftrue=''1=1'';'; put 'length mac $100 msg $5000;'; put 'mac=symget(''mac'');'; put 'msg=symget(''msg'');'; put 'run;'; put 'data _null_;'; put 'abort cancel FILE;'; put 'run;'; put '%return;'; put '%end;'; put '%end;'; put '/* Web App Context */'; put '%if %symexist(_PROGRAM)'; put 'or %superq(SYSPROCESSNAME) = %str(Compute Server)'; put 'or &mode=INCLUDE'; put '%then %do;'; put 'options obs=max replace mprint;'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5"'; put '%then %do;'; put 'options nosyntaxcheck;'; put '%end;'; put '%if &mode=INCLUDE %then %do;'; put '%if %sysfunc(exist(&errds))=1 %then %do;'; put 'data _null_;'; put 'set &errds;'; put 'call symputx(''iftrue'',iftrue,''l'');'; put 'call symputx(''mac'',mac,''l'');'; put 'call symputx(''msg'',msg,''l'');'; put 'putlog (_all_)(=);'; put 'run;'; put '%if (&iftrue)=0 %then %return;'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: No include errors found;'; put '%return;'; put '%end;'; put '%end;'; put '/* extract log errs / warns, if exist */'; put '%local logloc logline;'; put '%global logmsg; /* capture global messages */'; put '%if %symexist(SYSPRINTTOLOG) %then %let logloc=&SYSPRINTTOLOG;'; put '%else %let logloc=%qsysfunc(getoption(LOG));'; put 'proc printto log=log;run;'; put '%let logline=0;'; put '%if %length(&logloc)>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input; putlog _infile_;'; put 'i=1;'; put 'retain logonce 0;'; put 'if ('; put '_infile_=:"%str(WARN)ING" or _infile_=:"%str(ERR)OR"'; put ') and logonce=0 then'; put 'do;'; put 'call symputx(''logline'',_n_);'; put 'logonce+1;'; put 'end;'; put 'run;'; put '/* capture log including lines BEFORE the err */'; put '%if &logline>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input;'; put 'i=1;'; put 'stoploop=0;'; put 'if _n_ ge &logline-15 and stoploop=0 then do until (i>22);'; put 'call symputx(''logmsg'',catx(''\n'',symget(''logmsg''),_infile_));'; put 'input;'; put 'i+1;'; put 'stoploop=1;'; put 'end;'; put 'if stoploop=1 then stop;'; put 'run;'; put '%end;'; put '%end;'; put '%if %symexist(SYS_JES_JOB_URI) %then %do;'; put '/* setup webout for Viya */'; put 'options nobomfile;'; put '%if "X&SYS_JES_JOB_URI.X"="XX" %then %do;'; put 'filename _webout temp lrecl=999999 mod;'; put '%end;'; put '%else %do;'; put 'filename _webout filesrvc parenturi="&SYS_JES_JOB_URI"'; put 'name="_webout.json" lrecl=999999 mod;'; put '%end;'; put '%end;'; put '%else %if %sysfunc(filename(fref,&sasjs_stpsrv_header_loc))=0 %then %do;'; put 'options nobomfile;'; put '/* set up http header for SASjs Server */'; put '%let fid=%sysfunc(fopen(&fref,A));'; put '%if &fid=0 %then %do;'; put '%put %str(ERR)OR: %sysfunc(sysmsg());'; put '%return;'; put '%end;'; put '%let rc=%sysfunc(fput(&fid,%str(Content-Type: application/json)));'; put '%let rc=%sysfunc(fwrite(&fid));'; put '%let rc=%sysfunc(fclose(&fid));'; put '%let rc=%sysfunc(filename(&fref));'; put '%end;'; put '/* send response in SASjs JSON format */'; put 'data _null_;'; put 'file _webout mod lrecl=32000 encoding=''utf-8'';'; put 'length msg syswarningtext syserrortext $32767 mode $10 ;'; put 'sasdatetime=datetime();'; put 'msg=symget(''msg'');'; put '%if &logline>0 %then %do;'; put 'msg=cats(msg,''\n\nLog Extract:\n'',symget(''logmsg''));'; put '%end;'; put '/* escape the escapes */'; put 'msg=tranwrd(msg,''\'',''\\'');'; put '/* escape the quotes */'; put 'msg=tranwrd(msg,''"'',''\"'');'; put '/* ditch the CRLFs as chrome complains */'; put 'msg=compress(msg,,''kw'');'; put '/* quote without quoting the quotes (which are escaped instead) */'; put 'msg=cats(''"'',msg,''"'');'; put 'if symexist(''_debug'') then debug=quote(trim(symget(''_debug'')));'; put 'else debug=''""'';'; put 'if symget(''sasjsprocessmode'')=''Stored Program'' then mode=''SASJS'';'; put 'if mode ne ''SASJS'' then put ''>>weboutBEGIN<<'';'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'put '',"sasjsAbort" : [{'';'; put 'put '' "MSG":'' msg ;'; put 'put '' ,"MAC": "'' "&mac" ''"}]'';'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'put '',"_DEBUG":'' debug ;'; put 'if symexist(''_metauser'') then do;'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put 'end;'; put 'if symexist(''SYS_JES_JOB_URI'') then do;'; put 'SYS_JES_JOB_URI=quote(trim(symget(''SYS_JES_JOB_URI'')));'; put 'put '',"SYS_JES_JOB_URI": '' SYS_JES_JOB_URI;'; put 'end;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put ",""SYSWARNINGTEXT"" : " syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'put "}" ;'; put 'if mode ne ''SASJS'' then put ''>>weboutEND<<'';'; put 'run;'; put '%put _all_;'; put '%if "&sysprocessmode " = "SAS Stored Process Server " %then %do;'; put 'data _null_;'; put 'putlog ''stpsrvset program err and syscc'';'; put 'rc=stpsrvset(''program error'', 0);'; put 'call symputx("syscc",0,"g");'; put 'run;'; put '%if &sysscp=WIN'; put 'and 1=0 /* deprecating this logic until we figure out a consistent abort */'; put 'and "%substr(%str(&sysvlong ),1,8)"="9.04.01M"'; put 'and "%substr(%str(&sysvlong ),9,1)">"5" %then %do;'; put '/* skip approach (below) does not work in windows m6+ envs */'; put 'endsas;'; put '%end;'; put '%else %do;'; put '/**'; put '* endsas kills 9.4m3 deployments by orphaning multibridges.'; put '* Abort variants are ungraceful (non zero return code)'; put '* This approach lets SAS run silently until the end :-)'; put '* Caution - fails when called within a %include within a macro'; put '* Use mp_include() to handle this.'; put '*/'; put 'filename skip temp;'; put 'data _null_;'; put 'file skip;'; put 'put ''%macro skip();'';'; put 'comment ''%mend skip; -> fix lint '';'; put 'put ''%macro skippy();'';'; put 'comment ''%mend skippy; -> fix lint '';'; put 'run;'; put '%inc skip;'; put '%end;'; put '%end;'; put '%else %if "&sysprocessmode " = "SAS Compute Server " %then %do;'; put '/* endsas kills the session making it harder to fetch results */'; put 'data _null_;'; put 'syswarningtext=symget(''syswarningtext'');'; put 'syserrortext=symget(''syserrortext'');'; put 'abort_msg=symget(''msg'');'; put 'syscc=symget(''syscc'');'; put 'sysuserid=symget(''sysuserid'');'; put 'iftrue=symget(''iftrue'');'; put 'put (_all_)(/=);'; put 'call symputx(''syscc'',0);'; put 'abort cancel nolist;'; put 'run;'; put '%end;'; put '%else %do;'; put '%abort cancel;'; put '%end;'; put '%end;'; put '%else %do;'; put '%put _all_;'; put '%abort cancel;'; put '%end;'; put '%mend mp_abort;'; put '/** @endcond */'; put '%macro mm_getstpcode('; put 'tree=/User Folders/sasdemo/somestp'; put ',name='; put ',outloc=0'; put ',outref=0'; put ',mDebug=1'; put ',showlog=NO'; put ');'; put '%local mD;'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing &sysmacroname..sas;'; put '%&mD.put _local_;'; put '%if %length(&name)>0 %then %let name=/&name;'; put '/* first, check if STP exists */'; put '%local tsuri;'; put '%let tsuri=stopifempty ;'; put 'data _null_;'; put 'format type uri tsuri value $200.;'; put 'call missing (of _all_);'; put 'path="&tree&name(StoredProcess)";'; put '/* first, find the STP ID */'; put 'if metadata_pathobj("",path,"StoredProcess",type,uri)>0 then do;'; put '/* get sourcecode */'; put 'cnt=1;'; put 'do while (metadata_getnasn(uri,"Notes",cnt,tsuri)>0);'; put 'rc=metadata_getattr(tsuri,"Name",value);'; put '&mD.put tsuri= value=;'; put 'if value="SourceCode" then do;'; put '/* found it! */'; put 'rc=metadata_getattr(tsuri,"Id",value);'; put 'call symputx(''tsuri'',value,''l'');'; put 'stop;'; put 'end;'; put 'cnt+1;'; put 'end;'; put 'end;'; put 'else put (_all_)(=);'; put 'run;'; put '%mp_abort(iftrue= (&tsuri=stopifempty)'; put ',mac=mm_getstpcode'; put ',msg=%str(&tree&name.(StoredProcess) not found!)'; put ')'; put '/**'; put '* Now we can extract the textstore'; put '*/'; put 'filename __getdoc temp lrecl=10000000;'; put 'proc metadata'; put 'in="$METAREPOSITORY'; put ''; put 'SAS1"'; put 'out=__getdoc ;'; put 'run;'; put '/* find the beginning of the text */'; put '%local start;'; put 'data _null_;'; put 'infile __getdoc lrecl=10000;'; put 'input;'; put 'start=index(_infile_,''StoredText="'');'; put 'if start then do;'; put 'call symputx("start",start+11);'; put '*putlog ''"'' _infile_ ''"'';'; put 'end;'; put 'stop;'; put '%local outeng;'; put '%if "&outloc"="0" %then %let outeng=TEMP;'; put '%else %let outeng="&outloc";'; put '%local fref;'; put '%if &outref=0 %then %let fref=%mf_getuniquefileref();'; put '%else %let fref=&outref;'; put '/* read the content, byte by byte, resolving escaped chars */'; put 'filename &fref &outeng lrecl=100000;'; put 'data _null_;'; put 'length filein 8 fileid 8;'; put 'filein = fopen("__getdoc","I",1,"B");'; put 'fileid = fopen("&fref","O",1,"B");'; put 'rec = "20"x;'; put 'length entity $6;'; put 'do while(fread(filein)=0);'; put 'x+1;'; put 'if x>&start then do;'; put 'rc = fget(filein,rec,1);'; put 'if rec=''"'' then leave;'; put 'else if rec="&" then do;'; put 'entity=rec;'; put 'do until (rec=";");'; put 'if fread(filein) ne 0 then goto getout;'; put 'rc = fget(filein,rec,1);'; put 'entity=cats(entity,rec);'; put 'end;'; put 'select (entity);'; put 'when (''&'' ) rec=''&'' ;'; put 'when (''<'' ) rec=''<'' ;'; put 'when (''>'' ) rec=''>'' ;'; put 'when (''''') rec="''" ;'; put 'when (''"'') rec=''"'' ;'; put 'when ('' '') rec=''0A''x;'; put 'when ('' '') rec=''0D''x;'; put 'when (''$'' ) rec=''$'' ;'; put 'when ('' '') rec=''09''x;'; put 'otherwise putlog "%str(WARN)ING: missing value for " entity=;'; put 'end;'; put 'rc =fput(fileid, substr(rec,1,1));'; put 'rc =fwrite(fileid);'; put 'end;'; put 'else do;'; put 'rc =fput(fileid,rec);'; put 'rc =fwrite(fileid);'; put 'end;'; put 'end;'; put 'end;'; put 'getout:'; put 'rc=fclose(filein);'; put 'rc=fclose(fileid);'; put 'run;'; put '%if &showlog=YES %then %do;'; put 'data _null_;'; put 'infile &fref lrecl=32767 end=last;'; put 'input;'; put 'if _n_=1 then putlog ''>>stpcodeBEGIN<<'';'; put 'putlog _infile_;'; put 'if last then putlog ''>>stpcodeEND<<'';'; put 'run;'; put '%end;'; put 'filename __getdoc clear;'; put '%if &outref=0 %then %do;'; put 'filename &fref clear;'; put '%end;'; put '%mend mm_getstpcode;'; put '%macro dc_getsettings();'; put '%global _program;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(syscc=&syscc on entry (&syswarningtext &syserrortext))'; put ')'; put '%if %length(&_PROGRAM)>1 %then %let root=&_program;'; put '%else %do;'; put '%global _metauser;'; put '%let _metauser=&sysuserid;'; put '/* to mimic a "real" _program we need to give a dummy role and stp name */'; put '%let root=/dummyRole/dummyName;'; put '%end;'; put '/* the DC precode is stored in the Admin folder in the root of'; put 'the project. Lets find that root. */'; put '%put &=root;'; put '%let root=%mf_getapploc();'; put '%put &=root;'; put '/* Now we know the root location we can retrieve the params */'; put '%let temploc=%sysfunc(pathname(work))/settings.txt;'; put '%mm_getstpcode(tree=&root/services/public'; put ',name=Data_Controller_Settings'; put ',outloc=&temploc'; put ')'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Unable to run getstpcode)'; put ')'; put 'filename _getsets "&temploc" lrecl=2000;'; put '/*'; put 'Do not use mp_include here - this puts a copy in every service, which creates'; put 'compilation problems when calling services from mp_include'; put '*/'; put '%inc _getsets/source2;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Problem running &sysmacroname (&syswarningtext &syserrortext))'; put ')'; put '%mend dc_getsettings;'; put '%macro mf_fmtdttm('; put ')/*/STORE SOURCE*/;'; put '%if "&sysver"="9.2" or "&sysver"="9.3"'; put 'or ("&sysver"="9.4" and "%substr(&SYSVLONG,9,1)" le "3")'; put 'or "%substr(&sysver,1,1)"="4"'; put 'or "%substr(&sysver,1,1)"="5"'; put '%then %do;DATETIME19.3%end;'; put '%else %do;E8601DT26.6%end;'; put '%mend mf_fmtdttm;'; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '%macro mp_init(prefix=SASJS'; put ')/*/STORE SOURCE*/;'; put '%if %symexist(SASJS_PREFIX) %then %return; /* only run once */'; put '%global'; put 'SASJS_PREFIX /* the ONLY hard-coded global macro variable in SASjs */'; put '&prefix._FUNCTIONS /* used in mcf_init() to track core function compilation */'; put '&prefix._INIT_NUM /* initialisation time as numeric */'; put '&prefix._INIT_DTTM /* initialisation time in E8601DT26.6 format */'; put '&prefix.WORK /* avoid typing %sysfunc(pathname(work)) every time */'; put ';'; put '%let sasjs_prefix=&prefix;'; put 'data _null_;'; put 'dttm=datetime();'; put 'call symputx("&sasjs_prefix._init_num",dttm,''g'');'; put 'call symputx("&sasjs_prefix._init_dttm",put(dttm,E8601DT26.6),''g'');'; put 'call symputx("&sasjs_prefix.work",pathname(''WORK''),''g'');'; put 'run;'; put 'options'; put 'compress=CHAR /* default is none so ensure we have something! */'; put 'datastmtchk=ALLKEYWORDS /* protection from overwriting input datasets */'; put 'errorcheck=STRICT /* catch errs in libname/filename statements */'; put 'fmterr /* ensure err when a format cannot be found */'; put 'mergenoby=%str(ERR)OR /* throw err when a merge has no BY variables */'; put 'missing=. /* changing this can cause hard to detect errs */'; put 'noquotelenmax /* avoid warnings for long strings */'; put 'noreplace /* avoid overwriting permanent datasets */'; put 'ps=max /* reduce log size slightly */'; put 'ls=max /* reduce log even more and avoid word truncation */'; put 'validmemname=COMPATIBLE /* avoid special characters etc in table names */'; put 'validvarname=V7 /* avoid special characters etc in variable names */'; put 'varinitchk=%str(ERR)OR /* avoid data mistakes from variable name typos */'; put 'varlenchk=%str(ERR)OR /* fail hard if truncation (data loss) can result */'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5" %then %do;'; put 'noautocorrect /* disallow misspelled procedure names */'; put 'dsoptions=note2err /* undocumented - convert bad NOTEs to ERRs */'; put '%end;'; put ';'; put '%mend mp_init;'; put '%macro mpeinit(fetch=YES);'; put '%global mpeinit'; put 'mpeadmins /* group with unrestricted Meditor access */'; put 'mpelocapprovals /* location for landing and staging files */'; put 'mpelib /* location of configuration tables for DC */'; put 'dc_repo_users /* location of user / group metadata */'; put 'dc_licence_key /* extracted in dc_getsettings */'; put 'dc_activation_key /* extracted in dc_getsettings */'; put 'dc_locale /* extracted in dc_getsettings */'; put 'dc_dttmtfmt /* can be overridden in dc_getsettings */'; put '_debug'; put ';'; put '%if &mpeinit=1 %then %return;'; put '%else %let mpeinit=1;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(Problem on service startup (&syswarningtext &syserrortext))'; put ')'; put '%mp_init()'; put '%if &fetch=YES %then %do;'; put '%webout(FETCH)'; put '%end;'; put '%global _CLIENTNAME;'; put '%mp_abort(iftrue= (&_CLIENTNAME=SAS Enterprise Guide)'; put ',mac=&_program..sas'; put ',msg=%str(Data Controller is a web app and should not be executed from EG)'; put ')'; put 'options urlencoding=utf8 nobomfile lrecl=32767;'; put '%let perf=%sysfunc(datetime());'; put '%put perfdiff: 0;'; put '%let dc_locale=SYSTEM; /* default if not set */'; put '/**'; put '* E8601DT26.6 has widest database support - but not all SAS flavours can'; put '* handle it. Override in the settings STP if needed.'; put '*/'; put 'data _null_;'; put 'dc_dttmtfmt=''"%sysfunc(datetime(),''!!"%mf_fmtdttm()"!!'')"dt'';'; put 'call symputx(''dc_dttmtfmt'',dc_dttmtfmt);'; put 'put dc_dttmtfmt=;'; put 'run;'; put '%put &=dc_dttmtfmt;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc prior to dc_getsettings)'; put ')'; put '%dc_getsettings()'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc after dc_getsettings)'; put ')'; put 'data _null_;'; put 'set &DC_LIBREF..mpe_config(where=('; put 'var_scope="DC"'; put 'and &dc_dttmtfmt lt tx_to'; put 'and var_active=1'; put '));'; put 'call symputx(var_name,var_value,''G'');'; put 'putlog var_name "=" var_value;'; put 'run;'; put '%let mpelib=&dc_libref;'; put '%let mpeadmins=&dc_admin_group;'; put '%let mpelocapprovals=&dc_staging_area;'; put '%let dc_repo_users=&dc_repo_users;'; put '%if &dc_locale ne SYSTEM %then %do;'; put 'options locale=&dc_locale;'; put '%end;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(Problem during compilation or with STP precode (&syswarningtext))'; put ')'; put '%mend mpeinit;'; put '%macro mf_mval(var);'; put '%if %symexist(&var) %then %do;'; put '%superq(&var)'; put '%end;'; put '%mend mf_mval;'; put '%macro mf_trimstr(basestr,trimstr);'; put '%local baselen trimlen trimval;'; put '/* return if basestr is shorter than trimstr (or 0) */'; put '%let baselen=%length(%superq(basestr));'; put '%let trimlen=%length(%superq(trimstr));'; put '%if &baselen < &trimlen or &baselen=0 %then %return;'; put '/* obtain the characters from the end of basestr */'; put '%let trimval=%qsubstr(%superq(basestr)'; put ',%length(%superq(basestr))-&trimlen+1'; put ',&trimlen);'; put '/* compare and if matching, chop it off! */'; put '%if %superq(basestr)=%superq(trimstr) %then %do;'; put '%return;'; put '%end;'; put '%else %if %superq(trimval)=%superq(trimstr) %then %do;'; put '%qsubstr(%superq(basestr),1,%length(%superq(basestr))-&trimlen)'; put '%end;'; put '%else %do;'; put '&basestr'; put '%end;'; put '%mend mf_trimstr;'; put '%macro mf_getplatform(switch'; put ')/*/STORE SOURCE*/;'; put '%local a b c;'; put '%if &switch.NONE=NONE %then %do;'; put '%if %symexist(sasjsprocessmode) %then %do;'; put '%if &sasjsprocessmode=Stored Program %then %do;'; put 'SASJS'; put '%return;'; put '%end;'; put '%end;'; put '%if %symexist(sysprocessmode) %then %do;'; put '%if "&sysprocessmode"="SAS Object Server"'; put 'or "&sysprocessmode"= "SAS Compute Server" %then %do;'; put 'SASVIYA'; put '%end;'; put '%else %if "&sysprocessmode"="SAS Stored Process Server"'; put 'or "&sysprocessmode"="SAS Workspace Server"'; put '%then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if %symexist(_metaport) or %symexist(_metauser) %then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if &switch=SASSTUDIO %then %do;'; put '/* return the version of SAS Studio else 0 */'; put '%if %mf_mval(_CLIENTAPP)=%str(SAS Studio) %then %do;'; put '%let a=%mf_mval(_CLIENTVERSION);'; put '%let b=%scan(&a,1,.);'; put '%if %eval(&b >2) %then %do;'; put '&b'; put '%end;'; put '%else 0;'; put '%end;'; put '%else 0;'; put '%end;'; put '%else %if &switch=VIYARESTAPI %then %do;'; put '%mf_trimstr(%sysfunc(getoption(servicesbaseurl)),/)'; put '%end;'; put '%mend mf_getplatform;'; put '%macro mpeterm();'; put '%local oldloc;'; put 'data _null_;'; put 'if symexist(''SYSPRINTTOLOG'') then oldloc=symget(''SYSPRINTTOLOG'');'; put 'else oldloc=getoption(''LOG'');'; put 'if subpad(oldloc,1,1) not in (''"'',"''",'' '') then oldloc=quote(cats(oldloc));'; put 'call symputx(''oldloc'',oldloc,''l'');'; put 'run;'; put '%if %length(&oldloc)>0 %then %do;'; put 'proc printto log=log;'; put 'run;'; put 'data _null_;'; put 'infile &oldloc;'; put 'input; putlog _infile_;'; put 'run;'; put '%end;'; put '%if %sysfunc(exist(&dc_libref..mpe_requests)) and %mf_getplatform() ne SASVIYA'; put '%then %do;'; put 'data ;'; put 'if 0 then set &dc_libref..mpe_requests;'; put 'request_dttm=%sysfunc(datetime());'; put 'request_user="%mf_getuser()";'; put 'request_service="%scan(&_program,-2,/)/%scan(&_program,-1,/)";'; put 'request_params='''';'; put 'output;stop;'; put 'proc append base=&dc_libref..mpe_requests data=&syslast force nowarn;'; put 'run;'; put '%end;'; put '%mend mpeterm;'; put '%macro mf_getattrn('; put 'libds'; put ',attr'; put ')/*/STORE SOURCE*/;'; put '%local dsid rc;'; put '%let dsid=%sysfunc(open(&libds,is));'; put '%if &dsid = 0 %then %do;'; put '%put %str(WARN)ING: Cannot open %trim(&libds), system message below;'; put '%put %sysfunc(sysmsg());'; put '-1'; put '%end;'; put '%else %do;'; put '%sysfunc(attrn(&dsid,&attr))'; put '%let rc=%sysfunc(close(&dsid));'; put '%end;'; put '%mend mf_getattrn;'; put '%macro mf_nobs(libds'; put ')/*/STORE SOURCE*/;'; put '%mf_getattrn(&libds,NLOBS)'; put '%mend mf_nobs;'; put '%macro mp_ds2cards(base_ds, tgt_ds='; put ',cards_file="%sysfunc(pathname(work))/cardgen.sas"'; put ',maxobs=max'; put ',random_sample=NO'; put ',showlog=YES'; put ',outencoding='; put ',append=NO'; put ')/*/STORE SOURCE*/;'; put '%local i setds nvars;'; put '%if not %sysfunc(exist(&base_ds)) %then %do;'; put '%put %str(WARN)ING: &base_ds does not exist;'; put '%return;'; put '%end;'; put '%if %index(&base_ds,.)=0 %then %let base_ds=WORK.&base_ds;'; put '%if (&tgt_ds = ) %then %let tgt_ds=&base_ds;'; put '%if %index(&tgt_ds,.)=0 %then %let tgt_ds=WORK.%scan(&base_ds,2,.);'; put '%if ("&outencoding" ne "") %then %let outencoding=encoding="&outencoding";'; put '%if ("&append" = "" or "&append" = "NO") %then %let append=;'; put '%else %let append=mod;'; put '/* get varcount */'; put '%let nvars=0;'; put 'proc sql noprint;'; put 'select count(*) into: nvars from dictionary.columns'; put 'where upcase(libname)="%scan(%upcase(&base_ds),1)"'; put 'and upcase(memname)="%scan(%upcase(&base_ds),2)";'; put '%if &nvars=0 %then %do;'; put '%put %str(WARN)ING: Dataset &base_ds has no variables, will not be converted.;'; put '%return;'; put '%end;'; put '/* get indexes */'; put 'proc sort'; put 'data=sashelp.vindex('; put 'where=(upcase(libname)="%scan(%upcase(&base_ds),1)"'; put 'and upcase(memname)="%scan(%upcase(&base_ds),2)")'; put ')'; put 'out=_data_;'; put 'by indxname indxpos;'; put 'run;'; put '%local indexes;'; put 'data _null_;'; put 'set &syslast end=last;'; put 'if _n_=1 then call symputx(''indexes'',''(index=('',''l'');'; put 'by indxname indxpos;'; put 'length vars $32767 nom uni $8;'; put 'retain vars;'; put 'if first.indxname then do;'; put 'idxcnt+1;'; put 'nom='''';'; put 'uni='''';'; put 'vars=name;'; put 'end;'; put 'else vars=catx('' '',vars,name);'; put 'if last.indxname then do;'; put 'if nomiss=''yes'' then nom=''/nomiss'';'; put 'if unique=''yes'' then uni=''/unique'';'; put 'call symputx(''indexes'''; put ',catx('' '',symget(''indexes''),indxname,''=('',vars,'')'',nom,uni)'; put ',''l'');'; put 'end;'; put 'if last then call symputx(''indexes'',cats(symget(''indexes''),''))''),''l'');'; put 'run;'; put 'data;run;'; put '%let setds=&syslast;'; put 'proc sql'; put '%if %datatyp(&maxobs)=NUMERIC %then %do;'; put 'outobs=&maxobs;'; put '%end;'; put ';'; put 'create table &setds as select * from &base_ds'; put '%if &random_sample=YES %then %do;'; put 'order by ranuni(42)'; put '%end;'; put ';'; put 'reset outobs=max;'; put 'create table datalines1 as'; put 'select name,type,length,varnum,format,label from dictionary.columns'; put 'where upcase(libname)="%upcase(%scan(&base_ds,1))"'; put 'and upcase(memname)="%upcase(%scan(&base_ds,2))";'; put '/**'; put 'Due to long decimals cannot use best. format'; put 'So - use bestd. format and then use character functions to strip trailing'; put 'zeros, if NOT an integer or missing!! Cannot use int() as it upsets'; put 'note2err when there are missings.'; put 'resolved code = ifc( mod(coalesce(VARIABLE,0),1)=0'; put ',put(VARIABLE,best32.)'; put ',substrn(put(VARIABLE,bestd32.),1'; put ',findc(put(VARIABLE,bestd32.),''0'',''TBK'')));'; put '**/'; put 'data datalines_2;'; put 'format dataline $32000.;'; put 'set datalines1 (where=(upcase(name) not in'; put '(''PROCESSED_DTTM'',''VALID_FROM_DTTM'',''VALID_TO_DTTM'')));'; put 'if type=''num'' then dataline='; put 'cats(''ifc(mod(coalesce('',name,'',0),1)=0'; put ',put('',name,'',best32.-l)'; put ',substrn(put('',name,'',bestd32.-l),1'; put ',findc(put('',name,'',bestd32.-l),"0","TBK")))'');'; put '/**'; put '* binary data must be converted, to store in text format. It is identified'; put '* by the presence of the $HEX keyword in the format.'; put '*/'; put 'else if upcase(format)=:''$HEX'' then'; put 'dataline=cats(''put(trim('',name,''),'',format,'')'');'; put '/**'; put '* There is no easy way to store line breaks in a cards file.'; put '* To discuss this, use: https://github.com/sasjs/core/issues/80'; put '* Removing all nonprintables with kw (keep writeable)'; put '*/'; put 'else dataline=cats(''compress('',name,'', ,"kw")'');'; put 'run;'; put 'proc sql noprint;'; put 'select dataline into: datalines separated by '','' from datalines_2;'; put '%local'; put 'process_dttm_flg'; put 'valid_from_dttm_flg'; put 'valid_to_dttm_flg'; put ';'; put '%let process_dttm_flg = N;'; put '%let valid_from_dttm_flg = N;'; put '%let valid_to_dttm_flg = N;'; put 'data _null_;'; put 'set datalines1 ;'; put '/* build attrib statement */'; put 'if type=''char'' then type2=''$'';'; put 'if strip(format) ne '''' then format2=cats(''format='',format);'; put 'if strip(label) ne '''' then label2=cats(''label='',quote(trim(label)));'; put 'str1=catx('' '',(put(name,$33.)||''length='')'; put ',put(cats(type2,length),$7.)||format2,label2);'; put '/* Build input statement */'; put 'if upcase(format)=:''$HEX'' then type3='':''!!format;'; put 'else if type=''char'' then type3='':$char.'';'; put 'str2=put(name,$33.)||type3;'; put 'if(upcase(name) = "PROCESSED_DTTM") then'; put 'call symputx("process_dttm_flg", "Y", "L");'; put 'if(upcase(name) = "VALID_FROM_DTTM") then'; put 'call symputx("valid_from_dttm_flg", "Y", "L");'; put 'if(upcase(name) = "VALID_TO_DTTM") then'; put 'call symputx("valid_to_dttm_flg", "Y", "L");'; put 'call symputx(cats("attrib_stmt_", put(_N_, 8.)), str1, "L");'; put 'call symputx(cats("input_stmt_", put(_N_, 8.))'; put ', ifc(upcase(name) not in'; put '(''PROCESSED_DTTM'',''VALID_FROM_DTTM'',''VALID_TO_DTTM''), str2, ""), "L");'; put 'run;'; put 'data _null_;'; put 'file &cards_file. &outencoding lrecl=32767 termstr=nl &append;'; put 'length __attrib $32767;'; put 'if _n_=1 then do;'; put 'put ''/**'';'; put 'put '' @file'';'; put 'put " @brief Datalines for %upcase(%scan(&base_ds,2)) dataset";'; put 'put " @details Generated by %nrstr(%%)mp_ds2cards()";'; put 'put " Source: https://github.com/sasjs/core";'; put 'put '' @cond '';'; put 'put ''**/'';'; put 'put "data &tgt_ds &indexes;";'; put 'put "attrib ";'; put '%do i = 1 %to &nvars;'; put '__attrib=symget("attrib_stmt_&i");'; put 'put __attrib;'; put '%end;'; put 'put ";";'; put '%if &process_dttm_flg. eq Y %then %do;'; put 'put ''retain PROCESSED_DTTM %sysfunc(datetime());'';'; put '%end;'; put '%if &valid_from_dttm_flg. eq Y %then %do;'; put 'put ''retain VALID_FROM_DTTM &low_date;'';'; put '%end;'; put '%if &valid_to_dttm_flg. eq Y %then %do;'; put 'put ''retain VALID_TO_DTTM &high_date;'';'; put '%end;'; put 'if __nobs=0 then do;'; put 'put ''call missing(of _all_);/* avoid uninitialised notes */'';'; put 'put ''stop;'';'; put 'put ''run;'';'; put 'end;'; put 'else do;'; put 'put "infile cards dsd;";'; put 'put "input ";'; put '%do i = 1 %to &nvars.;'; put '%if(%length(&&input_stmt_&i..)) %then'; put 'put " &&input_stmt_&i..";'; put ';'; put '%end;'; put 'put ";";'; put 'put ''missing a b c d e f g h i j k l m n o p q r s t u v w x y z _;'';'; put 'put "datalines4;";'; put 'end;'; put 'end;'; put 'set &setds end=__lastobs nobs=__nobs;'; put '/* remove all formats for write purposes - some have long underlying decimals */'; put 'format _numeric_ best30.29;'; put 'length __dataline $32767;'; put '__dataline=catq(''cqtmb'',&datalines);'; put 'put __dataline;'; put 'if __lastobs then do;'; put 'put '';;;;'';'; put 'put ''run;'';'; put 'put ''/** @endcond **/'';'; put 'stop;'; put 'end;'; put 'run;'; put 'proc sql;'; put 'drop table &setds;'; put 'quit;'; put '%if &showlog=YES %then %do;'; put 'data _null_;'; put 'infile &cards_file lrecl=32767;'; put 'input;'; put 'put _infile_;'; put 'run;'; put '%end;'; put '%put NOTE: CARDS FILE SAVED IN:;'; put '%put NOTE-;%put NOTE-;'; put '%put NOTE- %sysfunc(dequote(&cards_file.));'; put '%put NOTE-;%put NOTE-;'; put '%mend mp_ds2cards;'; put '/** @endcond **/'; put '%macro mp_binarycopy('; put 'inloc= /* full path and filename of the object to be copied */'; put ',outloc= /* full path and filename of object to be created */'; put ',inref=____in /* override default to use own filerefs */'; put ',outref=____out /* override default to use own filerefs */'; put ',mode=CREATE'; put ',iftrue=%str(1=1)'; put ')/*/STORE SOURCE*/;'; put '%local mod;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%if &mode=APPEND %then %let mod=mod;'; put '/* these IN and OUT filerefs can point to anything */'; put '%if &inref = ____in %then %do;'; put 'filename &inref &inloc lrecl=1048576 ;'; put '%end;'; put '%if &outref=____out %then %do;'; put 'filename &outref &outloc lrecl=1048576 &mod;'; put '%end;'; put '/* copy the file byte-for-byte */'; put 'data _null_;'; put 'infile &inref lrecl=1 recfm=n;'; put 'file &outref &mod recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put '%if &inref = ____in %then %do;'; put 'filename &inref clear;'; put '%end;'; put '%if &outref=____out %then %do;'; put 'filename &outref clear;'; put '%end;'; put '%mend mp_binarycopy;'; put '%macro mfs_httpheader(header_name'; put ',header_value'; put ')/*/STORE SOURCE*/;'; put '%global sasjs_stpsrv_header_loc;'; put '%local fref fid i;'; put '%if %sysfunc(filename(fref,&sasjs_stpsrv_header_loc)) ne 0 %then %do;'; put '%put &=fref &=sasjs_stpsrv_header_loc;'; put '%put %str(ERR)OR: %sysfunc(sysmsg());'; put '%return;'; put '%end;'; put '%let fid=%sysfunc(fopen(&fref,A));'; put '%if &fid=0 %then %do;'; put '%put %str(ERR)OR: %sysfunc(sysmsg());'; put '%return;'; put '%end;'; put '%let rc=%sysfunc(fput(&fid,%str(&header_name): %str(&header_value)));'; put '%let rc=%sysfunc(fwrite(&fid));'; put '%let rc=%sysfunc(fclose(&fid));'; put '%let rc=%sysfunc(filename(&fref));'; put '%mend mfs_httpheader;'; put '%macro mp_streamfile('; put 'contenttype=TEXT'; put ',inloc='; put ',inref=0'; put ',iftrue=%str(1=1)'; put ',outname='; put ',outref=_webout'; put ')/*/STORE SOURCE*/;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%let contentype=%upcase(&contenttype);'; put '%let outref=%upcase(&outref);'; put '%local platform; %let platform=%mf_getplatform();'; put '/**'; put '* check engine type to avoid the below err message:'; put '* > Function is only valid for filerefs using the CACHE access method.'; put '*/'; put '%local streamweb;'; put '%let streamweb=0;'; put 'data _null_;'; put 'set sashelp.vextfl(where=(upcase(fileref)="&outref"));'; put 'if xengine=''STREAM'' then call symputx(''streamweb'',1,''l'');'; put 'run;'; put '%if &contentype=CSV %then %do;'; put '%if (&platform=SASMETA and &streamweb=1) %then %do;'; put 'data _null_;'; put 'rc=stpsrv_header(''Content-Type'',''application/csv'');'; put 'rc=stpsrv_header(''Content-disposition'',"attachment; filename=&outname");'; put 'run;'; put '%end;'; put '%else %if &platform=SASVIYA %then %do;'; put 'filename &outref filesrvc parenturi="&SYS_JES_JOB_URI" name=''_webout.txt'''; put 'contenttype=''application/csv'''; put 'contentdisp="attachment; filename=&outname";'; put '%end;'; put '%else %if &platform=SASJS %then %do;'; put '%mfs_httpheader(Content-Type,application/csv)'; put '%mfs_httpheader(Content-disposition,%str(attachment; filename=&outname))'; put '%end;'; put '%end;'; put '%else %if &contentype=EXCEL %then %do;'; put '/* suitable for XLS format */'; put '%if (&platform=SASMETA and &streamweb=1) %then %do;'; put 'data _null_;'; put 'rc=stpsrv_header(''Content-Type'',''application/vnd.ms-excel'');'; put 'rc=stpsrv_header(''Content-disposition'',"attachment; filename=&outname");'; put 'run;'; put '%end;'; put '%else %if &platform=SASVIYA %then %do;'; put 'filename &outref filesrvc parenturi="&SYS_JES_JOB_URI" name=''_webout.xls'''; put 'contenttype=''application/vnd.ms-excel'''; put 'contentdisp="attachment; filename=&outname";'; put '%end;'; put '%else %if &platform=SASJS %then %do;'; put '%mfs_httpheader(Content-Type,application/vnd.ms-excel)'; put '%mfs_httpheader(Content-disposition,%str(attachment; filename=&outname))'; put '%end;'; put '%end;'; put '%else %if &contentype=GIF or &contentype=JPEG or &contentype=PNG %then %do;'; put '%if (&platform=SASMETA and &streamweb=1) %then %do;'; put 'data _null_;'; put 'rc=stpsrv_header(''Content-Type'',"image/%lowcase(&contenttype)");'; put 'run;'; put '%end;'; put '%else %if &platform=SASVIYA %then %do;'; put 'filename &outref filesrvc parenturi="&SYS_JES_JOB_URI"'; put 'contenttype="image/%lowcase(&contenttype)";'; put '%end;'; put '%else %if &platform=SASJS %then %do;'; put '%mfs_httpheader(Content-Type,image/%lowcase(&contenttype))'; put '%end;'; put '%end;'; put '%else %if &contentype=HTML or &contenttype=MARKDOWN %then %do;'; put '%if (&platform=SASMETA and &streamweb=1) %then %do;'; put 'data _null_;'; put 'rc=stpsrv_header(''Content-Type'',"text/%lowcase(&contenttype)");'; put 'rc=stpsrv_header(''Content-disposition'',"attachment; filename=&outname");'; put 'run;'; put '%end;'; put '%else %if &platform=SASVIYA %then %do;'; put 'filename &outref filesrvc parenturi="&SYS_JES_JOB_URI" name="_webout.json"'; put 'contenttype="text/%lowcase(&contenttype)"'; put 'contentdisp="attachment; filename=&outname";'; put '%end;'; put '%else %if &platform=SASJS %then %do;'; put '%mfs_httpheader(Content-Type,text/%lowcase(&contenttype))'; put '%mfs_httpheader(Content-disposition,%str(attachment; filename=&outname))'; put '%end;'; put '%end;'; put '%else %if &contentype=TEXT %then %do;'; put '%if (&platform=SASMETA and &streamweb=1) %then %do;'; put 'data _null_;'; put 'rc=stpsrv_header(''Content-Type'',''application/text'');'; put 'rc=stpsrv_header(''Content-disposition'',"attachment; filename=&outname");'; put 'run;'; put '%end;'; put '%else %if &platform=SASVIYA %then %do;'; put 'filename &outref filesrvc parenturi="&SYS_JES_JOB_URI" name=''_webout.txt'''; put 'contenttype=''application/text'''; put 'contentdisp="attachment; filename=&outname";'; put '%end;'; put '%else %if &platform=SASJS %then %do;'; put '%mfs_httpheader(Content-Type,application/text)'; put '%mfs_httpheader(Content-disposition,%str(attachment; filename=&outname))'; put '%end;'; put '%end;'; put '%else %if &contentype=WOFF or &contentype=WOFF2 or &contentype=TTF %then %do;'; put '%if (&platform=SASMETA and &streamweb=1) %then %do;'; put 'data _null_;'; put 'rc=stpsrv_header(''Content-Type'',"font/%lowcase(&contenttype)");'; put 'run;'; put '%end;'; put '%else %if &platform=SASVIYA %then %do;'; put 'filename &outref filesrvc parenturi="&SYS_JES_JOB_URI"'; put 'contenttype="font/%lowcase(&contenttype)";'; put '%end;'; put '%else %if &platform=SASJS %then %do;'; put '%mfs_httpheader(Content-Type,font/%lowcase(&contenttype))'; put '%end;'; put '%end;'; put '%else %if &contentype=XLSX %then %do;'; put '%if (&platform=SASMETA and &streamweb=1) %then %do;'; put 'data _null_;'; put 'rc=stpsrv_header(''Content-Type'','; put '''application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'');'; put 'rc=stpsrv_header(''Content-disposition'',"attachment; filename=&outname");'; put 'run;'; put '%end;'; put '%else %if &platform=SASVIYA %then %do;'; put 'filename &outref filesrvc parenturi="&SYS_JES_JOB_URI" name=''_webout.xls'''; put 'contenttype='; put '''application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'''; put 'contentdisp="attachment; filename=&outname";'; put '%end;'; put '%else %if &platform=SASJS %then %do;'; put '%mfs_httpheader(Content-Type'; put ',application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'; put ')'; put '%mfs_httpheader(Content-disposition,%str(attachment; filename=&outname))'; put '%end;'; put '%end;'; put '%else %if &contentype=ZIP %then %do;'; put '%if (&platform=SASMETA and &streamweb=1) %then %do;'; put 'data _null_;'; put 'rc=stpsrv_header(''Content-Type'',''application/zip'');'; put 'rc=stpsrv_header(''Content-disposition'',"attachment; filename=&outname");'; put 'run;'; put '%end;'; put '%else %if &platform=SASVIYA %then %do;'; put 'filename &outref filesrvc parenturi="&SYS_JES_JOB_URI" name=''_webout.zip'''; put 'contenttype=''application/zip'''; put 'contentdisp="attachment; filename=&outname";'; put '%end;'; put '%else %if &platform=SASJS %then %do;'; put '%mfs_httpheader(Content-Type,application/zip)'; put '%mfs_httpheader(Content-disposition,%str(attachment; filename=&outname))'; put '%end;'; put '%end;'; put '%else %do;'; put '%put %str(ERR)OR: Content Type &contenttype NOT SUPPORTED by &sysmacroname!;'; put '%end;'; put '%if &inref ne 0 %then %do;'; put '%mp_binarycopy(inref=&inref,outref=&outref)'; put '%end;'; put '%else %do;'; put '%mp_binarycopy(inloc="&inloc",outref=&outref)'; put '%end;'; put '%mend mp_streamfile;'; put '* SAS Macros end;'; put '* SAS Includes start;'; put '* SAS Includes end;'; put '* Binary Files start;'; put '* Binary Files end;'; put '* ServiceInit start;'; put 'options noquotelenmax ps=max;'; put '/* create dummy macros to prevent stpbegin / stpend from corrupting sessions */'; put '%macro stpbegin();'; put '%put NOTE: the STPBEGIN macro should not be used for web apps!;'; put '%mend stpbegin;'; put '%macro stpend();'; put '%put NOTE: the STPEND macro should not be used for web apps!;'; put '%mend stpend;'; put '* ServiceInit end;'; put '* Service start;'; put '/**'; put '@file'; put '@brief Downloads zip file of DC customer configurations'; put '@details Zip contains several excel files, containing the customer specific'; put '(non-DC) configurations. Useful when migrating to a new instance of'; put 'Data Controller.'; put '

SAS Macros

'; put '@li mf_getuser.sas'; put '@li mf_nobs.sas'; put '@li mp_ds2cards.sas'; put '@li mp_abort.sas'; put '@li mp_binarycopy.sas'; put '@li mp_streamfile.sas'; put '@author 4GL Apps Ltd'; put '@copyright 4GL Apps Ltd. This code may only be used within Data Controller'; put 'and may not be re-distributed or re-sold without the express permission of'; put '4GL Apps Ltd.'; put '**/'; put '%mpeinit()'; put '%let work=%sysfunc(pathname(work));'; put '/* excel does not work in all envs */'; put '%let mime=application/vnd.ms-excel;'; put '%let dbms=EXCEL;'; put '%let mime=application/csv;'; put '%let dbms=CSV;'; put '%let ext=csv;'; put '%macro conditional_export(ds);'; put '%if %mf_nobs(&ds)>0 %then %do;'; put 'PROC EXPORT DATA= &ds OUTFILE= "&work/&ds..&ext"'; put 'DBMS=&dbms REPLACE;'; put 'RUN;'; put 'ods package(ProdOutput) add file="&work/&ds..&ext" mimetype="&mime";'; put '%end;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%nrstr(syscc=&syscc after &ds prep)'; put ')'; put '%mend conditional_export;'; put 'ods package(ProdOutput) open nopf;'; put 'data MPE_ALERTS;'; put 'set &mpelib..MPE_ALERTS;'; put 'where &dc_dttmtfmt. le tx_to;'; put 'drop tx_: ;'; put 'run;'; put '%conditional_export(MPE_ALERTS)'; put 'data MPE_COLUMN_LEVEL_SECURITY;'; put 'set &mpelib..MPE_COLUMN_LEVEL_SECURITY;'; put 'where &dc_dttmtfmt. le tx_to;'; put 'where also CLS_LIBREF ne "&mpelib";'; put 'drop tx_: ;'; put 'run;'; put '%conditional_export(MPE_COLUMN_LEVEL_SECURITY)'; put 'data MPE_CONFIG;'; put 'set &mpelib..MPE_CONFIG;'; put 'where &dc_dttmtfmt. le tx_to;'; put 'drop tx_: ;'; put 'run;'; put '%conditional_export(MPE_CONFIG)'; put 'data MPE_DATADICTIONARY;'; put 'set &mpelib..MPE_DATADICTIONARY;'; put 'where &dc_dttmtfmt. le tx_to;'; put 'drop tx_: ;'; put 'if DD_SOURCE=:"&mpelib" then do;'; put '/* nothing */'; put 'end;'; put 'else output;'; put 'run;'; put '%conditional_export(MPE_DATADICTIONARY)'; put 'data MPE_EMAILS;'; put 'set &mpelib..MPE_EMAILS;'; put 'where &dc_dttmtfmt. le tx_to;'; put 'drop tx_: ;'; put 'run;'; put '%conditional_export(MPE_EMAILS)'; put 'data MPE_EXCEL_CONFIG;'; put 'set &mpelib..MPE_EXCEL_CONFIG;'; put 'where &dc_dttmtfmt. le tx_to;'; put 'drop tx_: ;'; put 'run;'; put '%conditional_export(MPE_EXCEL_CONFIG)'; put 'data MPE_GROUPS;'; put 'set &mpelib..MPE_GROUPS;'; put 'where &dc_dttmtfmt. le tx_to;'; put 'drop tx_: ;'; put 'run;'; put '%conditional_export(MPE_GROUPS)'; put 'data MPE_ROW_LEVEL_SECURITY;'; put 'set &mpelib..MPE_ROW_LEVEL_SECURITY;'; put 'where &dc_dttmtfmt. le tx_to;'; put 'drop tx_: ;'; put 'run;'; put '%conditional_export(MPE_ROW_LEVEL_SECURITY)'; put 'data MPE_SECURITY;'; put 'set &mpelib..MPE_SECURITY;'; put 'where &dc_dttmtfmt. le TX_TO;'; put 'drop tx_: ;'; put 'run;'; put '%conditional_export(MPE_SECURITY)'; put 'data MPE_SELECTBOX;'; put 'set &mpelib..MPE_SELECTBOX;'; put 'where &dc_dttmtfmt. le ver_to_dttm;'; put 'where also select_lib ne "&mpelib";'; put 'drop ver_: selectbox_rk;'; put 'run;'; put '%conditional_export(MPE_SELECTBOX)'; put 'data MPE_TABLES;'; put 'set &mpelib..MPE_TABLES;'; put 'where &dc_dttmtfmt. le TX_TO;'; put 'where also LIBREF ne "&mpelib";'; put 'drop tx_: ;'; put 'run;'; put '%conditional_export(MPE_TABLES)'; put 'data MPE_VALIDATIONS;'; put 'set &mpelib..MPE_VALIDATIONS;'; put 'where &dc_dttmtfmt. le TX_TO;'; put 'where also BASE_LIB ne "&mpelib";'; put 'drop tx_: ;'; put 'run;'; put '%conditional_export(MPE_VALIDATIONS)'; put '/* finish up zip file */'; put 'ods package(ProdOutput) publish archive properties'; put '(archive_name="DCBACKUP.zip" archive_path="&work");'; put 'ods package(ProdOutput) close;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%nrstr(syscc=&syscc after zip prep)'; put ')'; put '/* now serve zip file to client */'; put '%mp_streamfile(contenttype=ZIP'; put ',inloc=%str(&work/DCBACKUP.zip)'; put ',outname=DCBACKUP.zip'; put ')'; put '* Service end;'; run; %mm_createwebservice(path=&appLoc/&path, name=&service, code=sascode, server=&serverName, replace=yes) filename sascode clear; %let service=exportdb; filename sascode temp lrecl=32767; data _null_; file sascode; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '/**'; put '@file mp_jsonout.sas'; put '@brief Writes JSON in SASjs format to a fileref'; put '@details This macro can be used to OPEN a JSON stream and send one or more'; put 'tables as arrays of rows, where each row can be an object or a nested array.'; put 'There are two engines available - DATASTEP or PROCJSON.'; put 'PROC JSON is fast but will produce errs like the ones below if'; put 'special chars are encountered.'; put '> (ERR)OR: Some code points did not transcode.'; put '> An object or array close is not valid at this point in the JSON text.'; put '> Date value out of range'; put 'If this happens, try running with ENGINE=DATASTEP.'; put 'The DATASTEP engine is used to handle special SAS missing numerics, and'; put 'can also convert entire datasets to formatted values. Output JSON is always'; put 'in UTF-8.'; put 'Usage:'; put 'filename tmp temp;'; put 'data class; set sashelp.class;run;'; put '%mp_jsonout(OPEN,jref=tmp)'; put '%mp_jsonout(OBJ,class,jref=tmp)'; put '%mp_jsonout(OBJ,class,dslabel=class2,jref=tmp,showmeta=Y)'; put '%mp_jsonout(CLOSE,jref=tmp)'; put 'data _null_;'; put 'infile tmp;'; put 'input;putlog _infile_;'; put 'run;'; put 'If you are building web apps with SAS then you are strongly encouraged to use'; put 'the mX_createwebservice macros in combination with the'; put '[sasjs adapter](https://github.com/sasjs/adapter).'; put 'For more information see https://sasjs.io'; put '@param [in] action Valid values:'; put '@li OPEN - opens the JSON'; put '@li OBJ - sends a table with each row as an object'; put '@li ARR - sends a table with each row in an array'; put '@li CLOSE - closes the JSON'; put '@param [in] ds The dataset to send. Must be a work table.'; put '@param [out] jref= (_webout) The fileref to which to send the JSON'; put '@param [out] dslabel= The name to give the table in the exported JSON'; put '@param [in] fmt= (Y) Whether to keep (Y) or strip (N) formats from the table'; put '@param [in] engine= (DATASTEP) Which engine to use to send the JSON. Options:'; put '@li PROCJSON (default)'; put '@li DATASTEP (more reliable when data has non standard characters)'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to JSON'; put '

Related Files

'; put '@li mp_ds2fmtds.sas'; put '@version 9.2'; put '@author Allan Bowe'; put '@source https://github.com/sasjs/core'; put '**/'; put '%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y'; put ',engine=DATASTEP'; put ',missing=NULL'; put ',showmeta=N'; put ',maxobs=MAX'; put ')/*/STORE SOURCE*/;'; put '%local tempds colinfo fmtds i numcols numobs stmt_obs lastobs optval'; put 'tmpds1 tmpds2 tmpds3 tmpds4;'; put '%let numcols=0;'; put '%if &maxobs ne MAX %then %let stmt_obs=%str(if _n_>&maxobs then stop;);'; put '%if &action=OPEN %then %do;'; put 'options nobomfile;'; put 'data _null_;file &jref encoding=''utf-8'' lrecl=200;'; put 'put ''{"PROCESSED_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''"'';'; put 'run;'; put '%end;'; put '%else %if (&action=ARR or &action=OBJ) %then %do;'; put '/* force variable names to always be uppercase in the JSON */'; put 'options validvarname=upcase;'; put '/* To avoid issues with _webout on EBI - such as encoding diffs and truncation'; put '(https://support.sas.com/kb/49/325.html) we use temporary files */'; put 'filename _sjs1 temp lrecl=200 ;'; put 'data _null_; file _sjs1 encoding=''utf-8'';'; put 'put ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":";'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjs1 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs1 clear;'; put '/* grab col defs */'; put 'proc contents noprint data=&ds'; put 'out=_data_(keep=name type length format formatl formatd varnum label);'; put 'run;'; put '%let colinfo=%scan(&syslast,2,.);'; put 'proc sort data=&colinfo;'; put 'by varnum;'; put 'run;'; put '/* move meta to mac vars */'; put 'data &colinfo;'; put 'if _n_=1 then call symputx(''numcols'',nobs,''l'');'; put 'set &colinfo end=last nobs=nobs;'; put 'name=upcase(name);'; put '/* fix formats */'; put 'if type=2 or type=6 then do;'; put 'typelong=''char'';'; put 'length fmt $49.;'; put 'if format='''' then fmt=cats(''$'',length,''.'');'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else fmt=cats(format,formatl,''.'');'; put 'end;'; put 'else do;'; put 'typelong=''num'';'; put 'if format='''' then fmt=''best.'';'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else if formatd=0 then fmt=cats(format,formatl,''.'');'; put 'else fmt=cats(format,formatl,''.'',formatd);'; put 'end;'; put '/* 32 char unique name */'; put 'newname=''sasjs''!!substr(cats(put(md5(name),$hex32.)),1,27);'; put 'call symputx(cats(''name'',_n_),name,''l'');'; put 'call symputx(cats(''newname'',_n_),newname,''l'');'; put 'call symputx(cats(''length'',_n_),length,''l'');'; put 'call symputx(cats(''fmt'',_n_),fmt,''l'');'; put 'call symputx(cats(''type'',_n_),type,''l'');'; put 'call symputx(cats(''typelong'',_n_),typelong,''l'');'; put 'call symputx(cats(''label'',_n_),coalescec(label,name),''l'');'; put '/* overwritten when fmt=Y and a custom format exists in catalog */'; put 'if typelong=''num'' then call symputx(cats(''fmtlen'',_n_),200,''l'');'; put 'else call symputx(cats(''fmtlen'',_n_),min(32767,ceil((length+10)*1.5)),''l'');'; put 'run;'; put '%let tempds=%substr(_%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql;'; put 'select count(*) into: lastobs from &ds;'; put '%if &maxobs ne MAX %then %let lastobs=%sysfunc(min(&lastobs,&maxobs));'; put '%if &engine=PROCJSON %then %do;'; put '%if &missing=STRING %then %do;'; put '%put &sysmacroname: Special Missings not supported in proc json.;'; put '%put &sysmacroname: Switching to DATASTEP engine;'; put '%goto datastep;'; put '%end;'; put 'data &tempds;'; put 'set &ds;'; put '&stmt_obs;'; put '%if &fmt=N %then format _numeric_ best32.;;'; put '/* PRETTY is necessary to avoid line truncation in large files */'; put 'filename _sjs2 temp lrecl=131068 encoding=''utf-8'';'; put 'proc json out=_sjs2 pretty'; put '%if &action=ARR %then nokeys ;'; put ';export &tempds / nosastags fmtnumeric;'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs2 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs2 clear;'; put '%end;'; put '%else %if &engine=DATASTEP %then %do;'; put '%datastep:'; put '%if %sysfunc(exist(&ds)) ne 1 & %sysfunc(exist(&ds,VIEW)) ne 1'; put '%then %do;'; put '%put &sysmacroname: &ds NOT FOUND!!!;'; put '%return;'; put '%end;'; put '%if &fmt=Y %then %do;'; put '/**'; put '* Extract format definitions'; put '* First, by getting library locations from dictionary.formats'; put '* Then, by exporting the width using proc format'; put '* Cannot use maxw from sashelp.vformat as not always populated'; put '* Cannot use fmtinfo() as not supported in all flavours'; put '*/'; put '%let tmpds1=%substr(fmtsum%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds2=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds3=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds4=%substr(col%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql noprint;'; put 'create table &tmpds1 as'; put 'select cats(libname,''.'',memname) as FMTCAT,'; put 'FMTNAME'; put 'from dictionary.formats'; put 'where fmttype=''F'' and libname is not null'; put 'and fmtname in (select format from &colinfo where format is not null)'; put 'order by 1;'; put 'create table &tmpds2('; put 'FMTNAME char(32),'; put 'LENGTH num'; put ');'; put '%local catlist cat fmtlist i;'; put 'select distinct fmtcat into: catlist separated by '' '' from &tmpds1;'; put '%do i=1 %to %sysfunc(countw(&catlist,%str( )));'; put '%let cat=%scan(&catlist,&i,%str( ));'; put 'proc sql;'; put 'select distinct fmtname into: fmtlist separated by '' '''; put 'from &tmpds1 where fmtcat="&cat";'; put 'proc format lib=&cat cntlout=&tmpds3(keep=fmtname length);'; put 'select &fmtlist;'; put 'run;'; put 'proc sql;'; put 'insert into &tmpds2 select distinct fmtname,length from &tmpds3;'; put '%end;'; put 'proc sql;'; put 'create table &tmpds4 as'; put 'select a.*, b.length as MAXW'; put 'from &colinfo a'; put 'left join &tmpds2 b'; put 'on cats(a.format)=cats(upcase(b.fmtname))'; put 'order by a.varnum;'; put 'data _null_;'; put 'set &tmpds4;'; put 'if not missing(maxw);'; put 'call symputx('; put 'cats(''fmtlen'',_n_),'; put '/* vars need extra padding due to JSON escaping of special chars */'; put 'min(32767,ceil((max(length,maxw)+10)*1.5))'; put ',''l'''; put ');'; put 'run;'; put '/* configure varlenchk - as we are explicitly shortening the variables */'; put '%let optval=%sysfunc(getoption(varlenchk));'; put 'options varlenchk=NOWARN;'; put 'data _data_(compress=char);'; put '/* shorten the new vars */'; put 'length'; put '%do i=1 %to &numcols;'; put '&&name&i $&&fmtlen&i'; put '%end;'; put ';'; put '/* rename on entry */'; put 'set &ds(rename=('; put '%do i=1 %to &numcols;'; put '&&name&i=&&newname&i'; put '%end;'; put '));'; put '&stmt_obs;'; put 'drop'; put '%do i=1 %to &numcols;'; put '&&newname&i'; put '%end;'; put ';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=num %then %do;'; put '&&name&i=cats(put(&&newname&i,&&fmt&i));'; put '%end;'; put '%else %do;'; put '&&name&i=put(&&newname&i,&&fmt&i);'; put '%end;'; put '%end;'; put 'if _error_ then do;'; put 'call symputx(''syscc'',1012);'; put 'stop;'; put 'end;'; put 'run;'; put '%let fmtds=&syslast;'; put 'options varlenchk=&optval;'; put '%end;'; put 'proc format; /* credit yabwon for special null removal */'; put 'value bart (default=40)'; put '%if &missing=NULL %then %do;'; put '._ - .z = null'; put '%end;'; put '%else %do;'; put '._ = [quote()]'; put '. = null'; put '.a - .z = [quote()]'; put '%end;'; put 'other = [best.];'; put 'data &tempds;'; put 'attrib _all_ label='''';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'length &&name&i $&&fmtlen&i...;'; put 'format &&name&i $&&fmtlen&i...;'; put '%end;'; put '%end;'; put '%if &fmt=Y %then %do;'; put 'set &fmtds;'; put '%end;'; put '%else %do;'; put 'set &ds;'; put '%end;'; put '&stmt_obs;'; put 'format _numeric_ bart.;'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'if findc(&&name&i,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put '&&name&i=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,&&name&i)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else &&name&i=quote(cats(&&name&i));'; put '%end;'; put '%end;'; put 'run;'; put 'filename _sjs3 temp lrecl=131068 ;'; put 'data _null_;'; put 'file _sjs3 encoding=''utf-8'';'; put 'if _n_=1 then put "[";'; put 'set &tempds;'; put 'if _n_>1 then put "," @; put'; put '%if &action=ARR %then "[" ; %else "{" ;'; put '%do i=1 %to &numcols;'; put '%if &i>1 %then "," ;'; put '%if &action=OBJ %then """&&name&i"":" ;'; put '"&&name&i"n /* name literal for reserved variable names */'; put '%end;'; put '%if &action=ARR %then "]" ; %else "}" ; ;'; put '/* close out the table */'; put 'data _null_;'; put 'file _sjs3 mod encoding=''utf-8'';'; put 'put '']'';'; put 'run;'; put 'data _null_;'; put 'infile _sjs3 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs3 clear;'; put '%end;'; put 'proc sql;'; put 'drop table &colinfo, &tempds;'; put '%if %substr(&showmeta,1,1)=Y %then %do;'; put 'filename _sjs4 temp lrecl=131068 encoding=''utf-8'';'; put 'data _null_;'; put 'file _sjs4;'; put 'length label $350;'; put 'put ", ""$%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":{""vars"":{";'; put 'do i=1 to &numcols;'; put 'name=quote(trim(symget(cats(''name'',i))));'; put 'format=quote(trim(symget(cats(''fmt'',i))));'; put 'label=quote(prxchange(''s/\\/\\\\/'',-1,trim(symget(cats(''label'',i)))));'; put 'length=quote(trim(symget(cats(''length'',i))));'; put 'type=quote(trim(symget(cats(''typelong'',i))));'; put 'if i>1 then put "," @@;'; put 'put name '':{"format":'' format '',"label":'' label'; put ''',"length":'' length '',"type":'' type ''}'';'; put 'end;'; put 'put ''}}'';'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs4 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs4 clear;'; put '%end;'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put 'data _null_; file &jref encoding=''utf-8'' mod ;'; put 'put "}";'; put 'run;'; put '%end;'; put '%mend mp_jsonout;'; put '/**'; put '@file mm_webout.sas'; put '@brief Send data to/from SAS Stored Processes'; put '@details This macro should be added to the start of each Stored Process,'; put '**immediately** followed by a call to:'; put '%mm_webout(FETCH)'; put 'This will read all the input data and create same-named SAS datasets in the'; put 'WORK library. You can then insert your code, and send data back using the'; put 'following syntax:'; put 'data some datasets; * make some data ;'; put 'retain some columns;'; put 'run;'; put '%mm_webout(OPEN)'; put '%mm_webout(ARR,some) * Array format, fast, suitable for large tables ;'; put '%mm_webout(OBJ,datasets) * Object format, easier to work with ;'; put 'Finally, wrap everything up send some helpful system variables too'; put '%mm_webout(CLOSE)'; put '@param [in] action Either FETCH, OPEN, ARR, OBJ or CLOSE'; put '@param [in] ds The dataset to send back to the frontend'; put '@param [out] dslabel= Value to use instead of table name for sending to JSON'; put '@param [in] fmt= (N) Setting Y converts all vars to their formatted values'; put '@param [out] fref= (_webout) The fileref to which to write the JSON'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] workobs= (0) When set to a positive integer, will create a new'; put 'output object (WORK) which contains this number of observations from all'; put 'tables in the WORK library.'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to output JSON'; put '

SAS Macros

'; put '@li mp_jsonout.sas'; put '

Related Macros

'; put '@li ms_webout.sas'; put '@li mv_webout.sas'; put '@version 9.3'; put '@author Allan Bowe'; put '**/'; put '%macro mm_webout(action,ds,dslabel=,fref=_webout,fmt=N,missing=NULL'; put ',showmeta=N,maxobs=MAX,workobs=0'; put ');'; put '%global _webin_file_count _webin_fileref1 _webin_name1 _program _debug'; put 'sasjs_tables;'; put '%local i tempds jsonengine;'; put '/* see https://github.com/sasjs/core/issues/41 */'; put '%if "%upcase(&SYSENCODING)" ne "UTF-8" %then %let jsonengine=PROCJSON;'; put '%else %let jsonengine=DATASTEP;'; put '%if &action=FETCH %then %do;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'options mprint notes mprintnest;'; put '%end;'; put '%let _webin_file_count=%eval(&_webin_file_count+0);'; put '/* now read in the data */'; put '%do i=1 %to &_webin_file_count;'; put '%if &_webin_file_count=1 %then %do;'; put '%let _webin_fileref1=&_webin_fileref;'; put '%let _webin_name1=&_webin_name;'; put '%end;'; put 'data _null_;'; put 'infile &&_webin_fileref&i termstr=crlf;'; put 'input;'; put 'call symputx(''input_statement'',_infile_);'; put 'putlog "&&_webin_name&i input statement: " _infile_;'; put 'stop;'; put 'data &&_webin_name&i;'; put 'infile &&_webin_fileref&i firstobs=2 dsd termstr=crlf encoding=''utf-8'';'; put 'input &input_statement;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'if _n_<20 then putlog _infile_;'; put '%end;'; put 'run;'; put '%let sasjs_tables=&sasjs_tables &&_webin_name&i;'; put '%end;'; put '%end;'; put '%else %if &action=OPEN %then %do;'; put '/* fix encoding */'; put 'OPTIONS NOBOMFILE;'; put '/**'; put '* check xengine type to avoid the below err message:'; put '* > Function is only valid for filerefs using the CACHE access method.'; put '*/'; put 'data _null_;'; put 'set sashelp.vextfl(where=(fileref="_WEBOUT"));'; put 'if xengine=''STREAM'' then do;'; put 'rc=stpsrv_header(''Content-type'',"text/html; encoding=utf-8");'; put 'end;'; put 'run;'; put '/* setup json */'; put 'data _null_;file &fref encoding=''utf-8'';'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutBEGIN<<'';'; put '%end;'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'run;'; put '%end;'; put '%else %if &action=ARR or &action=OBJ %then %do;'; put '%mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt,jref=&fref'; put ',engine=&jsonengine,missing=&missing,showmeta=&showmeta,maxobs=&maxobs'; put ')'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put '/* To avoid issues with _webout on EBI we use a temporary file */'; put 'filename _sjsref temp lrecl=131068;'; put '%if %str(&workobs) > 0 %then %do;'; put '/* if debug mode, send back first XX records of each work table also */'; put 'data;run;%let tempds=%scan(&syslast,2,.);'; put 'ods output Members=&tempds;'; put 'proc datasets library=WORK memtype=data;'; put '%local wtcnt;%let wtcnt=0;'; put 'data _null_;'; put 'set &tempds;'; put 'if not (upcase(name) =:"DATA"); /* ignore temp datasets */'; put 'i+1;'; put 'call symputx(cats(''wt'',i),name,''l'');'; put 'call symputx(''wtcnt'',i,''l'');'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put ",""WORK"":{";'; put '%do i=1 %to &wtcnt;'; put '%let wt=&&wt&i;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'dsid=open("WORK.&wt",''is'');'; put 'nlobs=attrn(dsid,''NLOBS'');'; put 'nvars=attrn(dsid,''NVARS'');'; put 'rc=close(dsid);'; put 'if &i>1 then put '',''@;'; put 'put " ""&wt"" : {";'; put 'put ''"nlobs":'' nlobs;'; put 'put '',"nvars":'' nvars;'; put '%mp_jsonout(OBJ,&wt,jref=_sjsref,dslabel=first10rows,showmeta=Y'; put ',maxobs=&workobs'; put ')'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put '%end;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put 'run;'; put '%end;'; put '/* close off json */'; put 'data _null_;file _sjsref mod encoding=''utf-8'';'; put 'length SYSPROCESSNAME syserrortext syswarningtext autoexec $512;'; put 'put ",""_DEBUG"" : ""&_debug"" ";'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'autoexec=quote(urlencode(trim(getoption(''autoexec''))));'; put 'put '',"AUTOEXEC" : '' autoexec;'; put 'put ",""MF_GETUSER"" : ""%mf_getuser()"" ";'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'put ",""SYSENCODING"" : ""&sysencoding"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSPROCESSID"" : ""&SYSPROCESSID"" ";'; put 'put ",""SYSPROCESSMODE"" : ""&SYSPROCESSMODE"" ";'; put 'SYSPROCESSNAME=quote(urlencode(cats(SYSPROCESSNAME)));'; put 'put ",""SYSPROCESSNAME"" : " SYSPROCESSNAME;'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put '',"SYSWARNINGTEXT" : '' syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'length memsize $32;'; put 'memsize="%sysfunc(INPUTN(%sysfunc(getoption(memsize)), best.),sizekmg.)";'; put 'memsize=quote(cats(memsize));'; put 'put '',"MEMSIZE" : '' memsize;'; put 'put "}" @;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutEND<<'';'; put '%end;'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjsref lrecl=1 recfm=n;'; put 'file &fref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjsref clear;'; put '%end;'; put '%mend mm_webout;'; put '%macro webout(action,ds,dslabel=,fmt=,missing=NULL,showmeta=NO,maxobs=MAX);'; put '%mm_webout(&action,ds=&ds,dslabel=&dslabel,fmt=&fmt'; put ',missing=&missing'; put ',showmeta=&showmeta'; put ',maxobs=&maxobs'; put ') %mend;'; put '/* provide additional debug info */'; put '%global _program;'; put '%put &=syscc;'; put '%put user=%mf_getuser();'; put '%put pgm=&_program;'; put '%put timestamp=%sysfunc(datetime(),datetime19.);'; put '* Service Variables start;'; put '* Service Variables end;'; put '* SAS Macros start;'; put '%macro mf_getapploc(pgm);'; put '%if "&pgm"="" %then %do;'; put '%if %symexist(_program) %then %let pgm=&_program;'; put '%else %do;'; put '%put &sysmacroname: No value provided and no _program variable available;'; put '%return;'; put '%end;'; put '%end;'; put '%local root;'; put '/**'; put '* First check we are not in the tests/macros folder (which has no subfolders)'; put '* or specifically in the testsetup or testteardown services'; put '*/'; put '%if %index(&pgm,/tests/macros/)'; put 'or %index(&pgm,/tests/testsetup)'; put 'or %index(&pgm,/tests/testteardown)'; put '%then %do;'; put '%let root=%substr(&pgm,1,%index(&pgm,/tests)-1);'; put '&root'; put '%return;'; put '%end;'; put '/**'; put '* Next, move up two levels to avoid matches on subfolder or service name'; put '*/'; put '%let root=%substr(&pgm,1,%length(&pgm)-%length(%scan(&pgm,-1,/))-1);'; put '%let root=%substr(&root,1,%length(&root)-%length(%scan(&root,-1,/))-1);'; put '%if %index(&root,/tests/) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/tests/)-1);'; put '%end;'; put '%else %if %index(&root,/services) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/services)-1);'; put '%end;'; put '%else %if %index(&root,/jobs) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/jobs)-1);'; put '%end;'; put '%else %put &sysmacroname: Could not find an app location from &pgm;'; put '&root'; put '%mend mf_getapploc ;'; put '%macro mf_getuniquefileref(prefix=_,maxtries=1000,lrecl=32767);'; put '%local rc fname;'; put '%if &prefix=0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%end;'; put '%else %do;'; put '%local x len;'; put '%let len=%eval(8-%length(&prefix));'; put '%let x=0;'; put '%do x=0 %to &maxtries;'; put '%let fname=&prefix%substr(%sysfunc(ranuni(0)),3,&len);'; put '%if %sysfunc(fileref(&fname)) > 0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%return;'; put '%end;'; put '%end;'; put '%put unable to find available fileref after &maxtries attempts;'; put '%end;'; put '%mend mf_getuniquefileref;'; put '%macro mp_abort(mac=mp_abort.sas, type=, msg=, iftrue=%str(1=1)'; put ', errds=work.mp_abort_errds'; put ', mode=REGULAR'; put ')/*/STORE SOURCE*/;'; put '%global sysprocessmode sysprocessname sasjs_stpsrv_header_loc sasjsprocessmode;'; put '%local fref fid i;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%put NOTE: /// mp_abort macro executing //;'; put '%if %length(&mac)>0 %then %put NOTE- called by &mac;'; put '%put NOTE - &msg;'; put '%if %symexist(_SYSINCLUDEFILEDEVICE)'; put '/* abort cancel FILE does not restart outside the INCLUDE on Viya 3.5 */'; put 'and %superq(SYSPROCESSNAME) ne %str(Compute Server)'; put '%then %do;'; put '%if "*&_SYSINCLUDEFILEDEVICE*" ne "**" %then %do;'; put 'data &errds;'; put 'iftrue=''1=1'';'; put 'length mac $100 msg $5000;'; put 'mac=symget(''mac'');'; put 'msg=symget(''msg'');'; put 'run;'; put 'data _null_;'; put 'abort cancel FILE;'; put 'run;'; put '%return;'; put '%end;'; put '%end;'; put '/* Web App Context */'; put '%if %symexist(_PROGRAM)'; put 'or %superq(SYSPROCESSNAME) = %str(Compute Server)'; put 'or &mode=INCLUDE'; put '%then %do;'; put 'options obs=max replace mprint;'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5"'; put '%then %do;'; put 'options nosyntaxcheck;'; put '%end;'; put '%if &mode=INCLUDE %then %do;'; put '%if %sysfunc(exist(&errds))=1 %then %do;'; put 'data _null_;'; put 'set &errds;'; put 'call symputx(''iftrue'',iftrue,''l'');'; put 'call symputx(''mac'',mac,''l'');'; put 'call symputx(''msg'',msg,''l'');'; put 'putlog (_all_)(=);'; put 'run;'; put '%if (&iftrue)=0 %then %return;'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: No include errors found;'; put '%return;'; put '%end;'; put '%end;'; put '/* extract log errs / warns, if exist */'; put '%local logloc logline;'; put '%global logmsg; /* capture global messages */'; put '%if %symexist(SYSPRINTTOLOG) %then %let logloc=&SYSPRINTTOLOG;'; put '%else %let logloc=%qsysfunc(getoption(LOG));'; put 'proc printto log=log;run;'; put '%let logline=0;'; put '%if %length(&logloc)>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input; putlog _infile_;'; put 'i=1;'; put 'retain logonce 0;'; put 'if ('; put '_infile_=:"%str(WARN)ING" or _infile_=:"%str(ERR)OR"'; put ') and logonce=0 then'; put 'do;'; put 'call symputx(''logline'',_n_);'; put 'logonce+1;'; put 'end;'; put 'run;'; put '/* capture log including lines BEFORE the err */'; put '%if &logline>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input;'; put 'i=1;'; put 'stoploop=0;'; put 'if _n_ ge &logline-15 and stoploop=0 then do until (i>22);'; put 'call symputx(''logmsg'',catx(''\n'',symget(''logmsg''),_infile_));'; put 'input;'; put 'i+1;'; put 'stoploop=1;'; put 'end;'; put 'if stoploop=1 then stop;'; put 'run;'; put '%end;'; put '%end;'; put '%if %symexist(SYS_JES_JOB_URI) %then %do;'; put '/* setup webout for Viya */'; put 'options nobomfile;'; put '%if "X&SYS_JES_JOB_URI.X"="XX" %then %do;'; put 'filename _webout temp lrecl=999999 mod;'; put '%end;'; put '%else %do;'; put 'filename _webout filesrvc parenturi="&SYS_JES_JOB_URI"'; put 'name="_webout.json" lrecl=999999 mod;'; put '%end;'; put '%end;'; put '%else %if %sysfunc(filename(fref,&sasjs_stpsrv_header_loc))=0 %then %do;'; put 'options nobomfile;'; put '/* set up http header for SASjs Server */'; put '%let fid=%sysfunc(fopen(&fref,A));'; put '%if &fid=0 %then %do;'; put '%put %str(ERR)OR: %sysfunc(sysmsg());'; put '%return;'; put '%end;'; put '%let rc=%sysfunc(fput(&fid,%str(Content-Type: application/json)));'; put '%let rc=%sysfunc(fwrite(&fid));'; put '%let rc=%sysfunc(fclose(&fid));'; put '%let rc=%sysfunc(filename(&fref));'; put '%end;'; put '/* send response in SASjs JSON format */'; put 'data _null_;'; put 'file _webout mod lrecl=32000 encoding=''utf-8'';'; put 'length msg syswarningtext syserrortext $32767 mode $10 ;'; put 'sasdatetime=datetime();'; put 'msg=symget(''msg'');'; put '%if &logline>0 %then %do;'; put 'msg=cats(msg,''\n\nLog Extract:\n'',symget(''logmsg''));'; put '%end;'; put '/* escape the escapes */'; put 'msg=tranwrd(msg,''\'',''\\'');'; put '/* escape the quotes */'; put 'msg=tranwrd(msg,''"'',''\"'');'; put '/* ditch the CRLFs as chrome complains */'; put 'msg=compress(msg,,''kw'');'; put '/* quote without quoting the quotes (which are escaped instead) */'; put 'msg=cats(''"'',msg,''"'');'; put 'if symexist(''_debug'') then debug=quote(trim(symget(''_debug'')));'; put 'else debug=''""'';'; put 'if symget(''sasjsprocessmode'')=''Stored Program'' then mode=''SASJS'';'; put 'if mode ne ''SASJS'' then put ''>>weboutBEGIN<<'';'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'put '',"sasjsAbort" : [{'';'; put 'put '' "MSG":'' msg ;'; put 'put '' ,"MAC": "'' "&mac" ''"}]'';'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'put '',"_DEBUG":'' debug ;'; put 'if symexist(''_metauser'') then do;'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put 'end;'; put 'if symexist(''SYS_JES_JOB_URI'') then do;'; put 'SYS_JES_JOB_URI=quote(trim(symget(''SYS_JES_JOB_URI'')));'; put 'put '',"SYS_JES_JOB_URI": '' SYS_JES_JOB_URI;'; put 'end;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put ",""SYSWARNINGTEXT"" : " syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'put "}" ;'; put 'if mode ne ''SASJS'' then put ''>>weboutEND<<'';'; put 'run;'; put '%put _all_;'; put '%if "&sysprocessmode " = "SAS Stored Process Server " %then %do;'; put 'data _null_;'; put 'putlog ''stpsrvset program err and syscc'';'; put 'rc=stpsrvset(''program error'', 0);'; put 'call symputx("syscc",0,"g");'; put 'run;'; put '%if &sysscp=WIN'; put 'and 1=0 /* deprecating this logic until we figure out a consistent abort */'; put 'and "%substr(%str(&sysvlong ),1,8)"="9.04.01M"'; put 'and "%substr(%str(&sysvlong ),9,1)">"5" %then %do;'; put '/* skip approach (below) does not work in windows m6+ envs */'; put 'endsas;'; put '%end;'; put '%else %do;'; put '/**'; put '* endsas kills 9.4m3 deployments by orphaning multibridges.'; put '* Abort variants are ungraceful (non zero return code)'; put '* This approach lets SAS run silently until the end :-)'; put '* Caution - fails when called within a %include within a macro'; put '* Use mp_include() to handle this.'; put '*/'; put 'filename skip temp;'; put 'data _null_;'; put 'file skip;'; put 'put ''%macro skip();'';'; put 'comment ''%mend skip; -> fix lint '';'; put 'put ''%macro skippy();'';'; put 'comment ''%mend skippy; -> fix lint '';'; put 'run;'; put '%inc skip;'; put '%end;'; put '%end;'; put '%else %if "&sysprocessmode " = "SAS Compute Server " %then %do;'; put '/* endsas kills the session making it harder to fetch results */'; put 'data _null_;'; put 'syswarningtext=symget(''syswarningtext'');'; put 'syserrortext=symget(''syserrortext'');'; put 'abort_msg=symget(''msg'');'; put 'syscc=symget(''syscc'');'; put 'sysuserid=symget(''sysuserid'');'; put 'iftrue=symget(''iftrue'');'; put 'put (_all_)(/=);'; put 'call symputx(''syscc'',0);'; put 'abort cancel nolist;'; put 'run;'; put '%end;'; put '%else %do;'; put '%abort cancel;'; put '%end;'; put '%end;'; put '%else %do;'; put '%put _all_;'; put '%abort cancel;'; put '%end;'; put '%mend mp_abort;'; put '/** @endcond */'; put '%macro mm_getstpcode('; put 'tree=/User Folders/sasdemo/somestp'; put ',name='; put ',outloc=0'; put ',outref=0'; put ',mDebug=1'; put ',showlog=NO'; put ');'; put '%local mD;'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing &sysmacroname..sas;'; put '%&mD.put _local_;'; put '%if %length(&name)>0 %then %let name=/&name;'; put '/* first, check if STP exists */'; put '%local tsuri;'; put '%let tsuri=stopifempty ;'; put 'data _null_;'; put 'format type uri tsuri value $200.;'; put 'call missing (of _all_);'; put 'path="&tree&name(StoredProcess)";'; put '/* first, find the STP ID */'; put 'if metadata_pathobj("",path,"StoredProcess",type,uri)>0 then do;'; put '/* get sourcecode */'; put 'cnt=1;'; put 'do while (metadata_getnasn(uri,"Notes",cnt,tsuri)>0);'; put 'rc=metadata_getattr(tsuri,"Name",value);'; put '&mD.put tsuri= value=;'; put 'if value="SourceCode" then do;'; put '/* found it! */'; put 'rc=metadata_getattr(tsuri,"Id",value);'; put 'call symputx(''tsuri'',value,''l'');'; put 'stop;'; put 'end;'; put 'cnt+1;'; put 'end;'; put 'end;'; put 'else put (_all_)(=);'; put 'run;'; put '%mp_abort(iftrue= (&tsuri=stopifempty)'; put ',mac=mm_getstpcode'; put ',msg=%str(&tree&name.(StoredProcess) not found!)'; put ')'; put '/**'; put '* Now we can extract the textstore'; put '*/'; put 'filename __getdoc temp lrecl=10000000;'; put 'proc metadata'; put 'in="$METAREPOSITORY'; put ''; put 'SAS1"'; put 'out=__getdoc ;'; put 'run;'; put '/* find the beginning of the text */'; put '%local start;'; put 'data _null_;'; put 'infile __getdoc lrecl=10000;'; put 'input;'; put 'start=index(_infile_,''StoredText="'');'; put 'if start then do;'; put 'call symputx("start",start+11);'; put '*putlog ''"'' _infile_ ''"'';'; put 'end;'; put 'stop;'; put '%local outeng;'; put '%if "&outloc"="0" %then %let outeng=TEMP;'; put '%else %let outeng="&outloc";'; put '%local fref;'; put '%if &outref=0 %then %let fref=%mf_getuniquefileref();'; put '%else %let fref=&outref;'; put '/* read the content, byte by byte, resolving escaped chars */'; put 'filename &fref &outeng lrecl=100000;'; put 'data _null_;'; put 'length filein 8 fileid 8;'; put 'filein = fopen("__getdoc","I",1,"B");'; put 'fileid = fopen("&fref","O",1,"B");'; put 'rec = "20"x;'; put 'length entity $6;'; put 'do while(fread(filein)=0);'; put 'x+1;'; put 'if x>&start then do;'; put 'rc = fget(filein,rec,1);'; put 'if rec=''"'' then leave;'; put 'else if rec="&" then do;'; put 'entity=rec;'; put 'do until (rec=";");'; put 'if fread(filein) ne 0 then goto getout;'; put 'rc = fget(filein,rec,1);'; put 'entity=cats(entity,rec);'; put 'end;'; put 'select (entity);'; put 'when (''&'' ) rec=''&'' ;'; put 'when (''<'' ) rec=''<'' ;'; put 'when (''>'' ) rec=''>'' ;'; put 'when (''''') rec="''" ;'; put 'when (''"'') rec=''"'' ;'; put 'when ('' '') rec=''0A''x;'; put 'when ('' '') rec=''0D''x;'; put 'when (''$'' ) rec=''$'' ;'; put 'when ('' '') rec=''09''x;'; put 'otherwise putlog "%str(WARN)ING: missing value for " entity=;'; put 'end;'; put 'rc =fput(fileid, substr(rec,1,1));'; put 'rc =fwrite(fileid);'; put 'end;'; put 'else do;'; put 'rc =fput(fileid,rec);'; put 'rc =fwrite(fileid);'; put 'end;'; put 'end;'; put 'end;'; put 'getout:'; put 'rc=fclose(filein);'; put 'rc=fclose(fileid);'; put 'run;'; put '%if &showlog=YES %then %do;'; put 'data _null_;'; put 'infile &fref lrecl=32767 end=last;'; put 'input;'; put 'if _n_=1 then putlog ''>>stpcodeBEGIN<<'';'; put 'putlog _infile_;'; put 'if last then putlog ''>>stpcodeEND<<'';'; put 'run;'; put '%end;'; put 'filename __getdoc clear;'; put '%if &outref=0 %then %do;'; put 'filename &fref clear;'; put '%end;'; put '%mend mm_getstpcode;'; put '%macro dc_getsettings();'; put '%global _program;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(syscc=&syscc on entry (&syswarningtext &syserrortext))'; put ')'; put '%if %length(&_PROGRAM)>1 %then %let root=&_program;'; put '%else %do;'; put '%global _metauser;'; put '%let _metauser=&sysuserid;'; put '/* to mimic a "real" _program we need to give a dummy role and stp name */'; put '%let root=/dummyRole/dummyName;'; put '%end;'; put '/* the DC precode is stored in the Admin folder in the root of'; put 'the project. Lets find that root. */'; put '%put &=root;'; put '%let root=%mf_getapploc();'; put '%put &=root;'; put '/* Now we know the root location we can retrieve the params */'; put '%let temploc=%sysfunc(pathname(work))/settings.txt;'; put '%mm_getstpcode(tree=&root/services/public'; put ',name=Data_Controller_Settings'; put ',outloc=&temploc'; put ')'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Unable to run getstpcode)'; put ')'; put 'filename _getsets "&temploc" lrecl=2000;'; put '/*'; put 'Do not use mp_include here - this puts a copy in every service, which creates'; put 'compilation problems when calling services from mp_include'; put '*/'; put '%inc _getsets/source2;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Problem running &sysmacroname (&syswarningtext &syserrortext))'; put ')'; put '%mend dc_getsettings;'; put '%macro mf_fmtdttm('; put ')/*/STORE SOURCE*/;'; put '%if "&sysver"="9.2" or "&sysver"="9.3"'; put 'or ("&sysver"="9.4" and "%substr(&SYSVLONG,9,1)" le "3")'; put 'or "%substr(&sysver,1,1)"="4"'; put 'or "%substr(&sysver,1,1)"="5"'; put '%then %do;DATETIME19.3%end;'; put '%else %do;E8601DT26.6%end;'; put '%mend mf_fmtdttm;'; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '%macro mp_init(prefix=SASJS'; put ')/*/STORE SOURCE*/;'; put '%if %symexist(SASJS_PREFIX) %then %return; /* only run once */'; put '%global'; put 'SASJS_PREFIX /* the ONLY hard-coded global macro variable in SASjs */'; put '&prefix._FUNCTIONS /* used in mcf_init() to track core function compilation */'; put '&prefix._INIT_NUM /* initialisation time as numeric */'; put '&prefix._INIT_DTTM /* initialisation time in E8601DT26.6 format */'; put '&prefix.WORK /* avoid typing %sysfunc(pathname(work)) every time */'; put ';'; put '%let sasjs_prefix=&prefix;'; put 'data _null_;'; put 'dttm=datetime();'; put 'call symputx("&sasjs_prefix._init_num",dttm,''g'');'; put 'call symputx("&sasjs_prefix._init_dttm",put(dttm,E8601DT26.6),''g'');'; put 'call symputx("&sasjs_prefix.work",pathname(''WORK''),''g'');'; put 'run;'; put 'options'; put 'compress=CHAR /* default is none so ensure we have something! */'; put 'datastmtchk=ALLKEYWORDS /* protection from overwriting input datasets */'; put 'errorcheck=STRICT /* catch errs in libname/filename statements */'; put 'fmterr /* ensure err when a format cannot be found */'; put 'mergenoby=%str(ERR)OR /* throw err when a merge has no BY variables */'; put 'missing=. /* changing this can cause hard to detect errs */'; put 'noquotelenmax /* avoid warnings for long strings */'; put 'noreplace /* avoid overwriting permanent datasets */'; put 'ps=max /* reduce log size slightly */'; put 'ls=max /* reduce log even more and avoid word truncation */'; put 'validmemname=COMPATIBLE /* avoid special characters etc in table names */'; put 'validvarname=V7 /* avoid special characters etc in variable names */'; put 'varinitchk=%str(ERR)OR /* avoid data mistakes from variable name typos */'; put 'varlenchk=%str(ERR)OR /* fail hard if truncation (data loss) can result */'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5" %then %do;'; put 'noautocorrect /* disallow misspelled procedure names */'; put 'dsoptions=note2err /* undocumented - convert bad NOTEs to ERRs */'; put '%end;'; put ';'; put '%mend mp_init;'; put '%macro mpeinit(fetch=YES);'; put '%global mpeinit'; put 'mpeadmins /* group with unrestricted Meditor access */'; put 'mpelocapprovals /* location for landing and staging files */'; put 'mpelib /* location of configuration tables for DC */'; put 'dc_repo_users /* location of user / group metadata */'; put 'dc_licence_key /* extracted in dc_getsettings */'; put 'dc_activation_key /* extracted in dc_getsettings */'; put 'dc_locale /* extracted in dc_getsettings */'; put 'dc_dttmtfmt /* can be overridden in dc_getsettings */'; put '_debug'; put ';'; put '%if &mpeinit=1 %then %return;'; put '%else %let mpeinit=1;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(Problem on service startup (&syswarningtext &syserrortext))'; put ')'; put '%mp_init()'; put '%if &fetch=YES %then %do;'; put '%webout(FETCH)'; put '%end;'; put '%global _CLIENTNAME;'; put '%mp_abort(iftrue= (&_CLIENTNAME=SAS Enterprise Guide)'; put ',mac=&_program..sas'; put ',msg=%str(Data Controller is a web app and should not be executed from EG)'; put ')'; put 'options urlencoding=utf8 nobomfile lrecl=32767;'; put '%let perf=%sysfunc(datetime());'; put '%put perfdiff: 0;'; put '%let dc_locale=SYSTEM; /* default if not set */'; put '/**'; put '* E8601DT26.6 has widest database support - but not all SAS flavours can'; put '* handle it. Override in the settings STP if needed.'; put '*/'; put 'data _null_;'; put 'dc_dttmtfmt=''"%sysfunc(datetime(),''!!"%mf_fmtdttm()"!!'')"dt'';'; put 'call symputx(''dc_dttmtfmt'',dc_dttmtfmt);'; put 'put dc_dttmtfmt=;'; put 'run;'; put '%put &=dc_dttmtfmt;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc prior to dc_getsettings)'; put ')'; put '%dc_getsettings()'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc after dc_getsettings)'; put ')'; put 'data _null_;'; put 'set &DC_LIBREF..mpe_config(where=('; put 'var_scope="DC"'; put 'and &dc_dttmtfmt lt tx_to'; put 'and var_active=1'; put '));'; put 'call symputx(var_name,var_value,''G'');'; put 'putlog var_name "=" var_value;'; put 'run;'; put '%let mpelib=&dc_libref;'; put '%let mpeadmins=&dc_admin_group;'; put '%let mpelocapprovals=&dc_staging_area;'; put '%let dc_repo_users=&dc_repo_users;'; put '%if &dc_locale ne SYSTEM %then %do;'; put 'options locale=&dc_locale;'; put '%end;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(Problem during compilation or with STP precode (&syswarningtext))'; put ')'; put '%mend mpeinit;'; put '%macro mf_mval(var);'; put '%if %symexist(&var) %then %do;'; put '%superq(&var)'; put '%end;'; put '%mend mf_mval;'; put '%macro mf_trimstr(basestr,trimstr);'; put '%local baselen trimlen trimval;'; put '/* return if basestr is shorter than trimstr (or 0) */'; put '%let baselen=%length(%superq(basestr));'; put '%let trimlen=%length(%superq(trimstr));'; put '%if &baselen < &trimlen or &baselen=0 %then %return;'; put '/* obtain the characters from the end of basestr */'; put '%let trimval=%qsubstr(%superq(basestr)'; put ',%length(%superq(basestr))-&trimlen+1'; put ',&trimlen);'; put '/* compare and if matching, chop it off! */'; put '%if %superq(basestr)=%superq(trimstr) %then %do;'; put '%return;'; put '%end;'; put '%else %if %superq(trimval)=%superq(trimstr) %then %do;'; put '%qsubstr(%superq(basestr),1,%length(%superq(basestr))-&trimlen)'; put '%end;'; put '%else %do;'; put '&basestr'; put '%end;'; put '%mend mf_trimstr;'; put '%macro mf_getplatform(switch'; put ')/*/STORE SOURCE*/;'; put '%local a b c;'; put '%if &switch.NONE=NONE %then %do;'; put '%if %symexist(sasjsprocessmode) %then %do;'; put '%if &sasjsprocessmode=Stored Program %then %do;'; put 'SASJS'; put '%return;'; put '%end;'; put '%end;'; put '%if %symexist(sysprocessmode) %then %do;'; put '%if "&sysprocessmode"="SAS Object Server"'; put 'or "&sysprocessmode"= "SAS Compute Server" %then %do;'; put 'SASVIYA'; put '%end;'; put '%else %if "&sysprocessmode"="SAS Stored Process Server"'; put 'or "&sysprocessmode"="SAS Workspace Server"'; put '%then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if %symexist(_metaport) or %symexist(_metauser) %then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if &switch=SASSTUDIO %then %do;'; put '/* return the version of SAS Studio else 0 */'; put '%if %mf_mval(_CLIENTAPP)=%str(SAS Studio) %then %do;'; put '%let a=%mf_mval(_CLIENTVERSION);'; put '%let b=%scan(&a,1,.);'; put '%if %eval(&b >2) %then %do;'; put '&b'; put '%end;'; put '%else 0;'; put '%end;'; put '%else 0;'; put '%end;'; put '%else %if &switch=VIYARESTAPI %then %do;'; put '%mf_trimstr(%sysfunc(getoption(servicesbaseurl)),/)'; put '%end;'; put '%mend mf_getplatform;'; put '%macro mpeterm();'; put '%local oldloc;'; put 'data _null_;'; put 'if symexist(''SYSPRINTTOLOG'') then oldloc=symget(''SYSPRINTTOLOG'');'; put 'else oldloc=getoption(''LOG'');'; put 'if subpad(oldloc,1,1) not in (''"'',"''",'' '') then oldloc=quote(cats(oldloc));'; put 'call symputx(''oldloc'',oldloc,''l'');'; put 'run;'; put '%if %length(&oldloc)>0 %then %do;'; put 'proc printto log=log;'; put 'run;'; put 'data _null_;'; put 'infile &oldloc;'; put 'input; putlog _infile_;'; put 'run;'; put '%end;'; put '%if %sysfunc(exist(&dc_libref..mpe_requests)) and %mf_getplatform() ne SASVIYA'; put '%then %do;'; put 'data ;'; put 'if 0 then set &dc_libref..mpe_requests;'; put 'request_dttm=%sysfunc(datetime());'; put 'request_user="%mf_getuser()";'; put 'request_service="%scan(&_program,-2,/)/%scan(&_program,-1,/)";'; put 'request_params='''';'; put 'output;stop;'; put 'proc append base=&dc_libref..mpe_requests data=&syslast force nowarn;'; put 'run;'; put '%end;'; put '%mend mpeterm;'; put '%macro mm_getGroups('; put 'user='; put ',outds=work.mm_getGroups'; put ',repo=foundation'; put ',mDebug=0'; put ')/*/STORE SOURCE*/;'; put '%local mD oldrepo;'; put '%let oldrepo=%sysfunc(getoption(metarepository));'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing mm_getGroups.sas;'; put '%&mD.put _local_;'; put '/* on some sites, user / group info is in a different metadata repo to the'; put 'default */'; put '%if &oldrepo ne &repo %then %do;'; put 'options metarepository=&repo;'; put '%end;'; put '%if %length(&user)=0 %then %do;'; put 'data &outds (keep=groupuri groupname groupdesc);'; put 'length groupuri groupname groupdesc group_or_role $256;'; put 'call missing(of _all_);'; put 'i+1;'; put 'do while'; put '(metadata_getnobj("omsobj:IdentityGroup?@Id contains ''.''",i,groupuri)>0);'; put 'rc=metadata_getattr(groupuri, "Name", groupname);'; put 'rc=metadata_getattr(groupuri, "Desc", groupdesc);'; put 'rc=metadata_getattr(groupuri,"PublicType",group_or_role);'; put 'if Group_or_Role = ''UserGroup'' then output;'; put 'i+1;'; put 'end;'; put 'run;'; put '%end;'; put '%else %do;'; put 'data &outds (keep=groupuri groupname groupdesc);'; put 'length uri groupuri groupname groupdesc group_or_role $256;'; put 'call missing(of _all_);'; put 'rc=metadata_getnobj("omsobj:Person?@Name=''&user''",1,uri);'; put 'if rc<=0 then do;'; put 'putlog "%str(WARN)ING: rc=" rc "&user not found "'; put '", or there was an issue reading the repository.";'; put 'stop;'; put 'end;'; put 'a=1;'; put 'grpassn=metadata_getnasn(uri,"IdentityGroups",a,groupuri);'; put 'if grpassn in (-3,-4) then do;'; put 'putlog "%str(WARN)ING: No metadata groups found for &user";'; put 'output;'; put 'end;'; put 'else do while (grpassn > 0);'; put 'rc=metadata_getattr(groupuri, "Name", groupname);'; put 'rc=metadata_getattr(groupuri, "Desc", groupdesc);'; put 'a+1;'; put 'rc=metadata_getattr(groupuri,"PublicType",group_or_role);'; put 'if Group_or_Role = ''UserGroup'' then output;'; put 'grpassn=metadata_getnasn(uri,"IdentityGroups",a,groupuri);'; put 'end;'; put 'run;'; put '%end;'; put '%if &oldrepo ne &repo %then %do;'; put 'options metarepository=&oldrepo;'; put '%end;'; put '%mend mm_getGroups;'; put '%macro dc_getusergroups(user=,outds=mm_getgroups);'; put '%global dc_repo_users;'; put '%if &dc_repo_users= %then %let dc_repo_users=foundation;'; put '%mm_getgroups(user=&user,outds=&outds,repo=&dc_repo_users)'; put '%mend dc_getusergroups;'; put '%macro mpe_getgroups(user=,outds=);'; put '%if not %symexist(dc_repo_users) %then %let dc_repo_users=foundation;'; put '%dc_getusergroups(user=&user,outds=&outds)'; put 'data;'; put 'length groupname groupdesc $256;'; put 'set &dc_libref..mpe_groups;'; put 'where &dc_dttmtfmt. lt tx_to;'; put 'where also upcase(user_name)="%upcase(&user)";'; put 'groupname=group_name;'; put 'groupdesc=group_desc;'; put 'keep groupname groupdesc;'; put 'run;'; put 'data &outds;'; put 'set &syslast &outds(keep=groupname groupdesc);'; put 'run;'; put '%mend mpe_getgroups;'; put '%macro mf_existfileref(fref'; put ')/*/STORE SOURCE*/;'; put '%local rc;'; put '%let rc=%sysfunc(fileref(&fref));'; put '%if &rc=0 %then %do;'; put '1'; put '%end;'; put '%else %if &rc<0 %then %do;'; put '%put &sysmacroname: Fileref &fref exists but the underlying file does not;'; put '1'; put '%end;'; put '%else %do;'; put '0'; put '%end;'; put '%mend mf_existfileref;'; put '%macro mf_getvarcount(libds,typefilter=A'; put ')/*/STORE SOURCE*/;'; put '%local dsid nvars rc outcnt x;'; put '%let dsid=%sysfunc(open(&libds));'; put '%let nvars=.;'; put '%let outcnt=0;'; put '%let typefilter=%upcase(&typefilter);'; put '%if &dsid %then %do;'; put '%let nvars=%sysfunc(attrn(&dsid,NVARS));'; put '%if &typefilter=A %then %let outcnt=&nvars;'; put '%else %if &nvars>0 %then %do x=1 %to &nvars;'; put '/* increment based on variable type */'; put '%if %sysfunc(vartype(&dsid,&x))=&typefilter %then %do;'; put '%let outcnt=%eval(&outcnt+1);'; put '%end;'; put '%end;'; put '%let rc=%sysfunc(close(&dsid));'; put '%end;'; put '%else %do;'; put '%put unable to open &libds (rc=&dsid);'; put '%let rc=%sysfunc(close(&dsid));'; put '%end;'; put '&outcnt'; put '%mend mf_getvarcount;'; put '%macro mf_getuniquename(prefix=MC);'; put '&prefix.%substr(%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32-%length(&prefix))'; put '%mend mf_getuniquename;'; put '%macro mf_isblank(param'; put ')/*/STORE SOURCE*/;'; put '%sysevalf(%superq(param)=,boolean)'; put '%mend mf_isblank;'; put '%macro mp_dropmembers('; put 'list /* space separated list of datasets / views */'; put ',libref=WORK /* can only drop from a single library at a time */'; put ',iftrue=%str(1=1)'; put ')/*/STORE SOURCE*/;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%if %mf_isblank(&list) %then %do;'; put '%put NOTE: nothing to drop!;'; put '%return;'; put '%end;'; put 'proc datasets lib=&libref nolist;'; put 'delete &list;'; put 'delete &list /mtype=view;'; put 'run;'; put '%mend mp_dropmembers;'; put '%macro mp_getconstraints(lib=WORK'; put ',ds='; put ',outds=mp_getconstraints'; put ',mdebug=0'; put ')/*/STORE SOURCE*/;'; put '%let lib=%upcase(&lib);'; put '%let ds=%upcase(&ds);'; put '/**'; put '* Cater for environments where sashelp.vcncolu is not available'; put '*/'; put '%if %sysfunc(exist(sashelp.vcncolu,view))=0 %then %do;'; put 'proc sql;'; put 'create table &outds('; put 'libref char(8)'; put ',TABLE_NAME char(32)'; put ',constraint_type char(8) label=''Constraint Type'''; put ',constraint_name char(32) label=''Constraint Name'''; put ',column_name char(32) label=''Column'''; put ',constraint_order num'; put ');'; put '%return;'; put '%end;'; put '/**'; put '* Neither dictionary tables nor sashelp provides a constraint order column,'; put '* however they DO arrive in the correct order. So, create the col.'; put '**/'; put '%local vw;'; put '%let vw=%mf_getuniquename(prefix=mp_getconstraints_vw_);'; put 'data &vw /view=&vw;'; put 'set sashelp.vcncolu;'; put 'where table_catalog="&lib";'; put '/* use retain approach to reset the constraint order with each constraint */'; put 'length tmp $1000;'; put 'retain tmp;'; put 'drop tmp;'; put 'if tmp ne catx(''|'',table_catalog,table_name,constraint_name) then do;'; put 'constraint_order=1;'; put 'end;'; put 'else constraint_order+1;'; put 'tmp=catx(''|'',table_catalog, table_name,constraint_name);'; put 'run;'; put '/* must use SQL as proc datasets does not support length changes */'; put 'proc sql noprint;'; put 'create table &outds as'; put 'select upcase(a.TABLE_CATALOG) as libref'; put ',upcase(a.TABLE_NAME) as TABLE_NAME'; put ',a.constraint_type'; put ',a.constraint_name'; put ',b.column_name'; put ',b.constraint_order'; put 'from dictionary.TABLE_CONSTRAINTS a'; put 'left join &vw b'; put 'on upcase(a.TABLE_CATALOG)=upcase(b.TABLE_CATALOG)'; put 'and upcase(a.TABLE_NAME)=upcase(b.TABLE_NAME)'; put 'and a.constraint_name=b.constraint_name'; put '/**'; put '* We cannot apply this clause to the underlying dictionary table. See:'; put '* https://communities.sas.com/t5/SAS-Programming/Unexpected-Where-Clause-behaviour-in-dictionary-TABLE/m-p/771554#M244867'; put '* cannot use`where calculated libref="&lib"` either as it will STILL execute'; put '* all the underlying constraint queries, causing exception errors in some'; put '* cases: https://github.com/sasjs/core/issues/283'; put '*/'; put 'where a.TABLE_CATALOG="&lib"'; put '%if "&ds" ne "" %then %do;'; put 'and upcase(a.TABLE_NAME)="&ds"'; put 'and upcase(b.TABLE_NAME)="&ds"'; put '%end;'; put 'order by libref, table_name, constraint_name, constraint_order'; put ';'; put '/* tidy up */'; put '%mp_dropmembers('; put '&vw,'; put 'iftrue=(&mdebug=0)'; put ')'; put '%mend mp_getconstraints;'; put '%macro mp_getddl(libref,ds,fref=getddl,flavour=SAS,showlog=NO,schema='; put ',applydttm=NO'; put ')/*/STORE SOURCE*/;'; put '/* check fileref is assigned */'; put '%if %mf_existfileref(&fref)=0 %then %do;'; put 'filename &fref temp ;'; put '%end;'; put '%if %length(&libref)=0 %then %let libref=WORK;'; put '%let flavour=%upcase(&flavour);'; put 'proc sql noprint;'; put 'create table _data_ as'; put 'select * from dictionary.tables'; put 'where upcase(libname)="%upcase(&libref)"'; put 'and memtype=''DATA'' /* views not currently supported */'; put '%if %length(&ds)>0 %then %do;'; put 'and upcase(memname)="%upcase(&ds)"'; put '%end;'; put ';'; put '%local tabinfo; %let tabinfo=&syslast;'; put 'create table _data_ as'; put 'select * from dictionary.columns'; put 'where upcase(libname)="%upcase(&libref)"'; put '%if %length(&ds)>0 %then %do;'; put 'and upcase(memname)="%upcase(&ds)"'; put '%end;'; put ';'; put '%local colinfo; %let colinfo=&syslast;'; put '%local dsnlist;'; put 'select distinct upcase(memname) into: dsnlist'; put 'separated by '' '''; put 'from &syslast'; put ';'; put 'create table _data_ as'; put 'select * from dictionary.indexes'; put 'where upcase(libname)="%upcase(&libref)"'; put '%if %length(&ds)>0 %then %do;'; put 'and upcase(memname)="%upcase(&ds)"'; put '%end;'; put 'order by idxusage, indxname, indxpos'; put ';'; put '%local idxinfo; %let idxinfo=&syslast;'; put '/* Extract all Primary Key and Unique data constraints */'; put '%mp_getconstraints(lib=%upcase(&libref),ds=%upcase(&ds),outds=_data_)'; put '%local colconst; %let colconst=&syslast;'; put '%macro addConst();'; put '%global constraints_used;'; put 'data _null_;'; put 'length ctype $11 constraint_name_orig $256 constraints_used $5000;'; put 'set &colconst('; put 'where=(table_name="&curds" and constraint_type in (''PRIMARY'',''UNIQUE''))'; put ') end=last;'; put 'file &fref mod;'; put 'by constraint_type constraint_name;'; put 'retain constraints_used;'; put 'constraint_name_orig=constraint_name;'; put 'if upcase(strip(constraint_type)) = ''PRIMARY'' then ctype=''PRIMARY KEY'';'; put 'else ctype=strip(constraint_type);'; put '%if &flavour=TSQL %then %do;'; put 'column_name=catt(''['',column_name,'']'');'; put 'constraint_name=catt(''['',constraint_name,'']'');'; put '%end;'; put '%else %if &flavour=PGSQL %then %do;'; put 'column_name=catt(''"'',column_name,''"'');'; put 'constraint_name=catt(''"'',constraint_name,''"'');'; put '%end;'; put 'if first.constraint_name then do;'; put 'constraints_used = catx('' '', constraints_used, constraint_name_orig);'; put 'put " ,CONSTRAINT " constraint_name ctype "(" ;'; put 'put '' '' column_name;'; put 'end;'; put 'else put '' ,'' column_name;'; put 'if last.constraint_name then do;'; put 'put " )";'; put 'call symput(''constraints_used'',strip(constraints_used));'; put 'end;'; put 'run;'; put '%put &=constraints_used;'; put '%mend addConst;'; put 'data _null_;'; put 'file &fref mod;'; put 'put "/* DDL generated by &sysuserid on %sysfunc(datetime(),datetime19.) */";'; put 'run;'; put '%local x curds;'; put '%if &flavour=SAS %then %do;'; put '%do x=1 %to %sysfunc(countw(&dsnlist));'; put '%let curds=%scan(&dsnlist,&x);'; put 'data _null_;'; put 'file &fref mod;'; put 'put "/* SAS Flavour DDL for %upcase(&libref).&curds */";'; put 'put "proc sql;";'; put 'run;'; put 'data _null_;'; put 'file &fref mod;'; put 'length lab $1024 typ $20;'; put 'set &colinfo (where=(upcase(memname)="&curds")) end=last;'; put 'if _n_=1 then do;'; put 'if memtype=''DATA'' then do;'; put 'put "create table &libref..&curds(";'; put 'end;'; put 'else do;'; put '/* just a placeholder - we filter out views at the top */'; put 'put "create view &libref..&curds(";'; put 'end;'; put 'put " "@@;'; put 'end;'; put 'else put " ,"@@;'; put 'if length(format)>1 then fmt=" format="!!cats(format);'; put 'if length(label)>1 then'; put 'lab=" label="!!cats("''",tranwrd(label,"''","''''"),"''");'; put 'if notnull=''yes'' then notnul='' not null'';'; put 'if type=''char'' then typ=cats(''char('',length,'')'');'; put 'else if length ne 8 then typ=''num length=''!!cats(length);'; put 'else typ=''num'';'; put 'put name typ fmt notnul lab;'; put 'run;'; put '/* Extra step for data constraints */'; put '%addConst()'; put 'data _null_;'; put 'file &fref mod;'; put 'put '');'';'; put 'run;'; put '/* Create Unique Indexes, but only if they were not already defined within'; put 'the Constraints section. */'; put 'data _null_;'; put '*length ds $128;'; put 'set &idxinfo('; put 'where=('; put 'memname="&curds"'; put 'and unique=''yes'''; put 'and indxname not in ('; put '%sysfunc(tranwrd("&constraints_used",%str( ),%str(",")))'; put ')'; put ')'; put ');'; put 'file &fref mod;'; put 'by idxusage indxname;'; put '/* ds=cats(libname,''.'',memname); */'; put 'if first.indxname then do;'; put 'put ''CREATE UNIQUE INDEX '' indxname "ON &libref..&curds (" ;'; put 'put '' '' name ;'; put 'end;'; put 'else put '' ,'' name ;'; put '*else put '' ,'' name ;'; put 'if last.indxname then do;'; put 'put '');'';'; put 'end;'; put 'run;'; put '/*'; put 'ods output IntegrityConstraints=ic;'; put 'proc contents data=testali out2=info;'; put 'run;'; put '*/'; put '%end;'; put '%end;'; put '%else %if &flavour=TSQL %then %do;'; put '/* if schema does not exist, set to be same as libref */'; put '%local schemaactual;'; put 'proc sql noprint;'; put 'select sysvalue into: schemaactual'; put 'from dictionary.libnames'; put 'where upcase(libname)="&libref" and engine=''SQLSVR'';'; put '%let schema=%sysfunc(coalescec(&schemaactual,&schema,&libref));'; put '%do x=1 %to %sysfunc(countw(&dsnlist));'; put '%let curds=%scan(&dsnlist,&x);'; put 'data _null_;'; put 'file &fref mod;'; put 'put "/* TSQL Flavour DDL for &schema..&curds */";'; put 'data _null_;'; put 'file &fref mod;'; put 'set &colinfo (where=(upcase(memname)="&curds")) end=last;'; put 'if _n_=1 then do;'; put 'if memtype=''DATA'' then do;'; put 'put "create table [&schema].[&curds](";'; put 'end;'; put 'else do;'; put '/* just a placeholder - we filter out views at the top */'; put 'put "create view [&schema].[&curds](";'; put 'end;'; put 'put " "@@;'; put 'end;'; put 'else put " ,"@@;'; put 'format=upcase(format);'; put 'if 1=0 then; /* dummy if */'; put '%if &applydttm=YES %then %do;'; put 'else if format=:''DATETIME'' then fmt=''[datetime2](7) '';'; put '%end;'; put 'else if type=''num'' then fmt=''[decimal](18,2)'';'; put 'else if length le 8000 then fmt=''[varchar](''!!cats(length)!!'')'';'; put 'else fmt=cats(''[varchar](max)'');'; put 'if notnull=''yes'' then notnul='' NOT NULL'';'; put 'put "[" name +(-1) "]" fmt notnul;'; put 'run;'; put '/* Extra step for data constraints */'; put '%addConst()'; put '/* Create Unique Indexes, but only if they were not already defined within'; put 'the Constraints section. */'; put 'data _null_;'; put '*length ds $128;'; put 'set &idxinfo('; put 'where=('; put 'memname="&curds"'; put 'and unique=''yes'''; put 'and indxname not in ('; put '%sysfunc(tranwrd("&constraints_used",%str( ),%str(",")))'; put ')'; put ')'; put ');'; put 'file &fref mod;'; put 'by idxusage indxname;'; put '*ds=cats(libname,''.'',memname);'; put 'if first.indxname then do;'; put '/* add nonclustered in case of multiple unique indexes */'; put 'put '' ,index ['' indxname +(-1) ''] UNIQUE NONCLUSTERED ('';'; put 'put '' ['' name +(-1) '']'';'; put 'end;'; put 'else put '' ,['' name +(-1) '']'';'; put 'if last.indxname then do;'; put 'put '' )'';'; put 'end;'; put 'run;'; put 'data _null_;'; put 'file &fref mod;'; put 'put '')'';'; put 'put ''GO'';'; put 'run;'; put '/* add extended properties for labels */'; put 'data _null_;'; put 'file &fref mod;'; put 'length nm $64 lab $1024;'; put 'set &colinfo (where=(upcase(memname)="&curds" and label ne '''')) end=last;'; put 'nm=cats("N''",tranwrd(name,"''","''''"),"''");'; put 'lab=cats("N''",tranwrd(label,"''","''''"),"''");'; put 'put '' '';'; put 'put "EXEC sys.sp_addextendedproperty ";'; put 'put " @name=N''MS_Description'',@value=" lab ;'; put 'put " ,@level0type=N''SCHEMA'',@level0name=N''&schema'' ";'; put 'put " ,@level1type=N''TABLE'',@level1name=N''&curds''";'; put 'put " ,@level2type=N''COLUMN'',@level2name=" nm ;'; put 'if last then put ''GO'';'; put 'run;'; put '%end;'; put '%end;'; put '%else %if &flavour=PGSQL %then %do;'; put '/* if schema does not exist, set to be same as libref */'; put '%local schemaactual;'; put 'proc sql noprint;'; put 'select sysvalue into: schemaactual'; put 'from dictionary.libnames'; put 'where upcase(libname)="&libref" and engine=''POSTGRES'';'; put '%let schema=%sysfunc(coalescec(&schemaactual,&schema,&libref));'; put 'data _null_;'; put 'file &fref mod;'; put 'put "CREATE SCHEMA &schema;";'; put '%do x=1 %to %sysfunc(countw(&dsnlist));'; put '%let curds=%scan(&dsnlist,&x);'; put '%local curdsvarcount;'; put '%let curdsvarcount=%mf_getvarcount(&libref..&curds);'; put '%if &curdsvarcount>1600 %then %do;'; put 'data _null_;'; put 'file &fref mod;'; put 'put "/* &libref..&curds contains &curdsvarcount vars */";'; put 'put "/* Postgres cannot create tables with over 1600 vars */";'; put 'put "/* No DDL will be generated for this table";'; put 'run;'; put '%end;'; put '%else %do;'; put 'data _null_;'; put 'file &fref mod;'; put 'put "/* Postgres Flavour DDL for &schema..&curds */";'; put 'data _null_;'; put 'file &fref mod;'; put 'set &colinfo (where=(upcase(memname)="&curds")) end=last;'; put 'length fmt $32;'; put 'if _n_=1 then do;'; put 'if memtype=''DATA'' then do;'; put 'put "CREATE TABLE &schema..&curds (";'; put 'end;'; put 'else do;'; put '/* just a placeholder - we filter out views at the top */'; put 'put "CREATE VIEW &schema..&curds (";'; put 'end;'; put 'put " "@@;'; put 'end;'; put 'else put " ,"@@;'; put 'format=upcase(format);'; put 'if 1=0 then; /* dummy if */'; put '%if &applydttm=YES %then %do;'; put 'else if format=:''DATETIME'' then fmt='' TIMESTAMP '';'; put '%end;'; put 'else if type=''num'' then fmt='' DOUBLE PRECISION'';'; put 'else fmt=''VARCHAR(''!!cats(length)!!'')'';'; put 'if notnull=''yes'' then notnul='' NOT NULL'';'; put '/* quote column names in case they represent reserved words */'; put 'name2=quote(trim(name));'; put 'put name2 fmt notnul;'; put 'run;'; put '/* Extra step for data constraints */'; put '%addConst()'; put 'data _null_;'; put 'file &fref mod;'; put 'put '');'';'; put 'run;'; put '/* Create Unique Indexes, but only if they were not already defined within'; put 'the Constraints section. */'; put 'data _null_;'; put '*length ds $128;'; put 'set &idxinfo('; put 'where=('; put 'memname="&curds"'; put 'and unique=''yes'''; put 'and indxname not in ('; put '%sysfunc(tranwrd("&constraints_used",%str( ),%str(",")))'; put ')'; put ')'; put ');'; put 'file &fref mod;'; put 'by idxusage indxname;'; put 'if first.indxname then do;'; put 'put ''CREATE UNIQUE INDEX "'' indxname +(-1) ''" '' "ON &schema..&curds(";'; put 'put '' "'' name +(-1) ''"'' ;'; put 'end;'; put 'else put '' ,"'' name +(-1) ''"'';'; put 'if last.indxname then do;'; put 'put '');'';'; put 'end;'; put 'run;'; put '%end;'; put '%end;'; put '%end;'; put '%if %upcase(&showlog)=YES %then %do;'; put 'options ps=max;'; put 'data _null_;'; put 'infile &fref;'; put 'input;'; put 'putlog _infile_;'; put 'run;'; put '%end;'; put '%mend mp_getddl;'; put '%macro mf_getVarFormat(libds /* two level ds name */'; put ', var /* variable name from which to return the format */'; put ', force=0'; put ')/*/STORE SOURCE*/;'; put '%local dsid vnum vformat rc vlen vtype;'; put '/* Open dataset */'; put '%let dsid = %sysfunc(open(&libds));'; put '%if &dsid > 0 %then %do;'; put '/* Get variable number */'; put '%let vnum = %sysfunc(varnum(&dsid, &var));'; put '/* Get variable format */'; put '%if(&vnum > 0) %then %let vformat=%sysfunc(varfmt(&dsid, &vnum));'; put '%else %do;'; put '%put NOTE: Variable &var does not exist in &libds;'; put '%let rc = %sysfunc(close(&dsid));'; put '%return;'; put '%end;'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: dataset &libds not opened! (rc=&dsid);'; put '%put &sysmacroname: %sysfunc(sysmsg());'; put '%return;'; put '%end;'; put '/* supply a default if no format available */'; put '%if %length(&vformat)<2 & &force=1 %then %do;'; put '%let vlen = %sysfunc(varlen(&dsid, &vnum));'; put '%let vtype = %sysfunc(vartype(&dsid, &vnum.));'; put '%if &vtype=C %then %let vformat=$&vlen..;'; put '%else %let vformat=best.;'; put '%end;'; put '/* Close dataset */'; put '%let rc = %sysfunc(close(&dsid));'; put '/* Return variable format */'; put '&vformat'; put '%mend mf_getVarFormat;'; put '%macro mf_getvarlist(libds'; put ',dlm=%str( )'; put ',quote=no'; put ',typefilter=A'; put ')/*/STORE SOURCE*/;'; put '/* declare local vars */'; put '%local outvar dsid nvars x rc dlm q var vtype;'; put '/* credit Rowland Hale - byte34 is double quote, 39 is single quote */'; put '%if %upcase("e)=DOUBLE %then %let q=%qsysfunc(byte(34));'; put '%else %if %upcase("e)=SINGLE %then %let q=%qsysfunc(byte(39));'; put '/* open dataset in macro */'; put '%let dsid=%sysfunc(open(&libds));'; put '%if &dsid %then %do;'; put '%let nvars=%sysfunc(attrn(&dsid,NVARS));'; put '%if &nvars>0 %then %do;'; put '/* add variables with supplied delimeter */'; put '%do x=1 %to &nvars;'; put '/* get variable type */'; put '%let vtype=%sysfunc(vartype(&dsid,&x));'; put '%if &vtype=&typefilter or &typefilter=A %then %do;'; put '%let var=&q.%sysfunc(varname(&dsid,&x))&q.;'; put '%if &var=&q&q %then %do;'; put '%put &sysmacroname: Empty column found in &libds!;'; put '%let var=&q. &q.;'; put '%end;'; put '%if %quote(&outvar)=%quote() %then %let outvar=&var;'; put '%else %let outvar=&outvar.&dlm.&var.;'; put '%end;'; put '%end;'; put '%end;'; put '%let rc=%sysfunc(close(&dsid));'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: Unable to open &libds (rc=&dsid);'; put '%put &sysmacroname: SYSMSG= %sysfunc(sysmsg());'; put '%let rc=%sysfunc(close(&dsid));'; put '%end;'; put '%do;%unquote(&outvar)%end;'; put '%mend mf_getvarlist;'; put '%macro mf_getvartype(libds /* two level name */'; put ', var /* variable name from which to return the type */'; put ')/*/STORE SOURCE*/;'; put '%local dsid vnum vtype rc;'; put '/* Open dataset */'; put '%let dsid = %sysfunc(open(&libds));'; put '%if &dsid. > 0 %then %do;'; put '/* Get variable number */'; put '%let vnum = %sysfunc(varnum(&dsid, &var));'; put '/* Get variable type (C/N) */'; put '%if(&vnum. > 0) %then %let vtype = %sysfunc(vartype(&dsid, &vnum.));'; put '%else %do;'; put '%put NOTE: Variable &var does not exist in &libds;'; put '%let vtype = %str( );'; put '%end;'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: dataset &libds not opened! (rc=&dsid);'; put '%put &sysmacroname: %sysfunc(sysmsg());'; put '%return;'; put '%end;'; put '/* Close dataset */'; put '%let rc = %sysfunc(close(&dsid));'; put '/* Return variable type */'; put '&vtype'; put '%mend mf_getvartype;'; put '%macro mp_ds2inserts(ds, outref=0,schema=0,outds=0,flavour=SAS,maxobs=max'; put ',applydttm=YES'; put ')/*/STORE SOURCE*/;'; put '%if not %sysfunc(exist(&ds)) %then %do;'; put '%put %str(WAR)NING: &ds does not exist;'; put '%return;'; put '%end;'; put '%if not %sysfunc(exist(&ds)) %then %do;'; put '%put %str(WAR)NING: &ds does not exist;'; put '%return;'; put '%end;'; put '%if %index(&ds,.)=0 %then %let ds=WORK.&ds;'; put '%let flavour=%upcase(&flavour);'; put '%if &flavour ne SAS and &flavour ne PGSQL %then %do;'; put '%put %str(WAR)NING: &flavour is not supported;'; put '%return;'; put '%end;'; put '%if &outref=0 %then %do;'; put '%put %str(WAR)NING: Please provide a fileref;'; put '%return;'; put '%end;'; put '%if %mf_existfileref(&outref)=0 %then %do;'; put 'filename &outref temp lrecl=66000;'; put '%end;'; put '%if &schema=0 %then %let schema=;'; put '%else %let schema=&schema..;'; put '%if &outds=0 %then %let outds=%scan(&ds,2,.);'; put '%local nobs;'; put 'proc sql noprint;'; put 'select count(*) into: nobs TRIMMED from &ds;'; put '%if &nobs=0 %then %do;'; put 'data _null_;'; put 'file &outref mod;'; put 'put "/* No rows found in &ds */";'; put 'run;'; put '%end;'; put '%local vars;'; put '%let vars=%mf_getvarcount(&ds);'; put '%if &vars=0 %then %do;'; put 'data _null_;'; put 'file &outref mod;'; put 'put "/* No columns found in &schema.&ds */";'; put 'run;'; put '%return;'; put '%end;'; put '%else %if &vars>1600 and &flavour=PGSQL %then %do;'; put 'data _null_;'; put 'file &fref mod;'; put 'put "/* &schema.&ds contains &vars vars */";'; put 'put "/* Postgres cannot handle tables with over 1600 vars */";'; put 'put "/* No inserts will be generated for this table */";'; put 'run;'; put '%return;'; put '%end;'; put '%local varlist varlistcomma;'; put '%let varlist=%mf_getvarlist(&ds);'; put '%let varlistcomma=%mf_getvarlist(&ds,dlm=%str(,),quote=double);'; put '/* next, export data */'; put 'data _null_;'; put 'file &outref mod ;'; put 'if _n_=1 then put "/* &schema.&outds (&nobs rows, &vars columns) */";'; put 'set &ds;'; put '%if &maxobs ne max %then %do;'; put 'if _n_>&maxobs then stop;'; put '%end;'; put 'length _____str $32767;'; put 'call missing(_____str);'; put 'format _numeric_ best.;'; put 'format _character_ ;'; put '%local i comma var vtype vfmt;'; put '%do i=1 %to %sysfunc(countw(&varlist));'; put '%let var=%scan(&varlist,&i);'; put '%let vtype=%mf_getvartype(&ds,&var);'; put '%let vfmt=%upcase(%mf_getvarformat(&ds,&var,force=1));'; put '%if &i=1 %then %do;'; put '%if &flavour=SAS %then %do;'; put 'put "insert into &schema.&outds set ";'; put 'put " &var="@;'; put '%end;'; put '%else %if &flavour=PGSQL %then %do;'; put '_____str=cats('; put '"INSERT INTO &schema.&outds ("'; put ',symget(''varlistcomma'')'; put ',") VALUES ("'; put ');'; put 'put _____str;'; put 'put " "@;'; put '%end;'; put '%end;'; put '%else %do;'; put '%if &flavour=SAS %then %do;'; put 'put " ,&var="@;'; put '%end;'; put '%else %if &flavour=PGSQL %then %do;'; put 'put " ,"@;'; put '%end;'; put '%end;'; put '%if &vtype=N %then %do;'; put '%if &flavour=SAS %then %do;'; put 'put &var;'; put '%end;'; put '%else %if &flavour=PGSQL %then %do;'; put 'if missing(&var) then put ''NULL'';'; put '%if &applydttm=YES and "%substr(&vfmt.xxxxxxxx,1,8)"="DATETIME"'; put '%then %do;'; put 'else put "TIMESTAMP ''" &var E8601DT25.6 "''";'; put '%end;'; put '%else %do;'; put 'else put &var;'; put '%end;'; put '%end;'; put '%end;'; put '%else %do;'; put '_____str="''"!!trim(tranwrd(&var,"''","''''"))!!"''";'; put 'put _____str;'; put '%end;'; put '%end;'; put '%if &flavour=SAS %then %do;'; put 'put '';'';'; put '%end;'; put '%else %if &flavour=PGSQL %then %do;'; put 'put '');'';'; put '%end;'; put 'if _n_=&nobs then put /;'; put 'run;'; put '%mend mp_ds2inserts;'; put '%macro mp_lib2inserts(lib'; put ',flavour=SAS'; put ',outref=0'; put ',schema=0'; put ',maxobs=max'; put ',applydttm=YES'; put ')/*/STORE SOURCE*/;'; put '/* Find the tables */'; put '%local x ds memlist;'; put 'proc sql noprint;'; put 'select distinct lowcase(memname)'; put 'into: memlist'; put 'separated by '' '''; put 'from dictionary.tables'; put 'where upcase(libname)="%upcase(&lib)"'; put 'and memtype=''DATA''; /* exclude views */'; put '%let flavour=%upcase(&flavour);'; put '%if &flavour ne SAS and &flavour ne PGSQL %then %do;'; put '%put %str(WAR)NING: &flavour is not supported;'; put '%return;'; put '%end;'; put '/* create the inserts */'; put '%do x=1 %to %sysfunc(countw(&memlist));'; put '%let ds=%scan(&memlist,&x);'; put '%mp_ds2inserts(&lib..&ds'; put ',outref=&outref'; put ',schema=&schema'; put ',outds=&ds'; put ',flavour=&flavour'; put ',maxobs=&maxobs'; put ',applydttm=&applydttm'; put ')'; put '%end;'; put '%mend mp_lib2inserts;'; put '%macro mfs_httpheader(header_name'; put ',header_value'; put ')/*/STORE SOURCE*/;'; put '%global sasjs_stpsrv_header_loc;'; put '%local fref fid i;'; put '%if %sysfunc(filename(fref,&sasjs_stpsrv_header_loc)) ne 0 %then %do;'; put '%put &=fref &=sasjs_stpsrv_header_loc;'; put '%put %str(ERR)OR: %sysfunc(sysmsg());'; put '%return;'; put '%end;'; put '%let fid=%sysfunc(fopen(&fref,A));'; put '%if &fid=0 %then %do;'; put '%put %str(ERR)OR: %sysfunc(sysmsg());'; put '%return;'; put '%end;'; put '%let rc=%sysfunc(fput(&fid,%str(&header_name): %str(&header_value)));'; put '%let rc=%sysfunc(fwrite(&fid));'; put '%let rc=%sysfunc(fclose(&fid));'; put '%let rc=%sysfunc(filename(&fref));'; put '%mend mfs_httpheader;'; put '%macro mp_binarycopy('; put 'inloc= /* full path and filename of the object to be copied */'; put ',outloc= /* full path and filename of object to be created */'; put ',inref=____in /* override default to use own filerefs */'; put ',outref=____out /* override default to use own filerefs */'; put ',mode=CREATE'; put ',iftrue=%str(1=1)'; put ')/*/STORE SOURCE*/;'; put '%local mod;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%if &mode=APPEND %then %let mod=mod;'; put '/* these IN and OUT filerefs can point to anything */'; put '%if &inref = ____in %then %do;'; put 'filename &inref &inloc lrecl=1048576 ;'; put '%end;'; put '%if &outref=____out %then %do;'; put 'filename &outref &outloc lrecl=1048576 &mod;'; put '%end;'; put '/* copy the file byte-for-byte */'; put 'data _null_;'; put 'infile &inref lrecl=1 recfm=n;'; put 'file &outref &mod recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put '%if &inref = ____in %then %do;'; put 'filename &inref clear;'; put '%end;'; put '%if &outref=____out %then %do;'; put 'filename &outref clear;'; put '%end;'; put '%mend mp_binarycopy;'; put '%macro mp_streamfile('; put 'contenttype=TEXT'; put ',inloc='; put ',inref=0'; put ',iftrue=%str(1=1)'; put ',outname='; put ',outref=_webout'; put ')/*/STORE SOURCE*/;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%let contentype=%upcase(&contenttype);'; put '%let outref=%upcase(&outref);'; put '%local platform; %let platform=%mf_getplatform();'; put '/**'; put '* check engine type to avoid the below err message:'; put '* > Function is only valid for filerefs using the CACHE access method.'; put '*/'; put '%local streamweb;'; put '%let streamweb=0;'; put 'data _null_;'; put 'set sashelp.vextfl(where=(upcase(fileref)="&outref"));'; put 'if xengine=''STREAM'' then call symputx(''streamweb'',1,''l'');'; put 'run;'; put '%if &contentype=CSV %then %do;'; put '%if (&platform=SASMETA and &streamweb=1) %then %do;'; put 'data _null_;'; put 'rc=stpsrv_header(''Content-Type'',''application/csv'');'; put 'rc=stpsrv_header(''Content-disposition'',"attachment; filename=&outname");'; put 'run;'; put '%end;'; put '%else %if &platform=SASVIYA %then %do;'; put 'filename &outref filesrvc parenturi="&SYS_JES_JOB_URI" name=''_webout.txt'''; put 'contenttype=''application/csv'''; put 'contentdisp="attachment; filename=&outname";'; put '%end;'; put '%else %if &platform=SASJS %then %do;'; put '%mfs_httpheader(Content-Type,application/csv)'; put '%mfs_httpheader(Content-disposition,%str(attachment; filename=&outname))'; put '%end;'; put '%end;'; put '%else %if &contentype=EXCEL %then %do;'; put '/* suitable for XLS format */'; put '%if (&platform=SASMETA and &streamweb=1) %then %do;'; put 'data _null_;'; put 'rc=stpsrv_header(''Content-Type'',''application/vnd.ms-excel'');'; put 'rc=stpsrv_header(''Content-disposition'',"attachment; filename=&outname");'; put 'run;'; put '%end;'; put '%else %if &platform=SASVIYA %then %do;'; put 'filename &outref filesrvc parenturi="&SYS_JES_JOB_URI" name=''_webout.xls'''; put 'contenttype=''application/vnd.ms-excel'''; put 'contentdisp="attachment; filename=&outname";'; put '%end;'; put '%else %if &platform=SASJS %then %do;'; put '%mfs_httpheader(Content-Type,application/vnd.ms-excel)'; put '%mfs_httpheader(Content-disposition,%str(attachment; filename=&outname))'; put '%end;'; put '%end;'; put '%else %if &contentype=GIF or &contentype=JPEG or &contentype=PNG %then %do;'; put '%if (&platform=SASMETA and &streamweb=1) %then %do;'; put 'data _null_;'; put 'rc=stpsrv_header(''Content-Type'',"image/%lowcase(&contenttype)");'; put 'run;'; put '%end;'; put '%else %if &platform=SASVIYA %then %do;'; put 'filename &outref filesrvc parenturi="&SYS_JES_JOB_URI"'; put 'contenttype="image/%lowcase(&contenttype)";'; put '%end;'; put '%else %if &platform=SASJS %then %do;'; put '%mfs_httpheader(Content-Type,image/%lowcase(&contenttype))'; put '%end;'; put '%end;'; put '%else %if &contentype=HTML or &contenttype=MARKDOWN %then %do;'; put '%if (&platform=SASMETA and &streamweb=1) %then %do;'; put 'data _null_;'; put 'rc=stpsrv_header(''Content-Type'',"text/%lowcase(&contenttype)");'; put 'rc=stpsrv_header(''Content-disposition'',"attachment; filename=&outname");'; put 'run;'; put '%end;'; put '%else %if &platform=SASVIYA %then %do;'; put 'filename &outref filesrvc parenturi="&SYS_JES_JOB_URI" name="_webout.json"'; put 'contenttype="text/%lowcase(&contenttype)"'; put 'contentdisp="attachment; filename=&outname";'; put '%end;'; put '%else %if &platform=SASJS %then %do;'; put '%mfs_httpheader(Content-Type,text/%lowcase(&contenttype))'; put '%mfs_httpheader(Content-disposition,%str(attachment; filename=&outname))'; put '%end;'; put '%end;'; put '%else %if &contentype=TEXT %then %do;'; put '%if (&platform=SASMETA and &streamweb=1) %then %do;'; put 'data _null_;'; put 'rc=stpsrv_header(''Content-Type'',''application/text'');'; put 'rc=stpsrv_header(''Content-disposition'',"attachment; filename=&outname");'; put 'run;'; put '%end;'; put '%else %if &platform=SASVIYA %then %do;'; put 'filename &outref filesrvc parenturi="&SYS_JES_JOB_URI" name=''_webout.txt'''; put 'contenttype=''application/text'''; put 'contentdisp="attachment; filename=&outname";'; put '%end;'; put '%else %if &platform=SASJS %then %do;'; put '%mfs_httpheader(Content-Type,application/text)'; put '%mfs_httpheader(Content-disposition,%str(attachment; filename=&outname))'; put '%end;'; put '%end;'; put '%else %if &contentype=WOFF or &contentype=WOFF2 or &contentype=TTF %then %do;'; put '%if (&platform=SASMETA and &streamweb=1) %then %do;'; put 'data _null_;'; put 'rc=stpsrv_header(''Content-Type'',"font/%lowcase(&contenttype)");'; put 'run;'; put '%end;'; put '%else %if &platform=SASVIYA %then %do;'; put 'filename &outref filesrvc parenturi="&SYS_JES_JOB_URI"'; put 'contenttype="font/%lowcase(&contenttype)";'; put '%end;'; put '%else %if &platform=SASJS %then %do;'; put '%mfs_httpheader(Content-Type,font/%lowcase(&contenttype))'; put '%end;'; put '%end;'; put '%else %if &contentype=XLSX %then %do;'; put '%if (&platform=SASMETA and &streamweb=1) %then %do;'; put 'data _null_;'; put 'rc=stpsrv_header(''Content-Type'','; put '''application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'');'; put 'rc=stpsrv_header(''Content-disposition'',"attachment; filename=&outname");'; put 'run;'; put '%end;'; put '%else %if &platform=SASVIYA %then %do;'; put 'filename &outref filesrvc parenturi="&SYS_JES_JOB_URI" name=''_webout.xls'''; put 'contenttype='; put '''application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'''; put 'contentdisp="attachment; filename=&outname";'; put '%end;'; put '%else %if &platform=SASJS %then %do;'; put '%mfs_httpheader(Content-Type'; put ',application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'; put ')'; put '%mfs_httpheader(Content-disposition,%str(attachment; filename=&outname))'; put '%end;'; put '%end;'; put '%else %if &contentype=ZIP %then %do;'; put '%if (&platform=SASMETA and &streamweb=1) %then %do;'; put 'data _null_;'; put 'rc=stpsrv_header(''Content-Type'',''application/zip'');'; put 'rc=stpsrv_header(''Content-disposition'',"attachment; filename=&outname");'; put 'run;'; put '%end;'; put '%else %if &platform=SASVIYA %then %do;'; put 'filename &outref filesrvc parenturi="&SYS_JES_JOB_URI" name=''_webout.zip'''; put 'contenttype=''application/zip'''; put 'contentdisp="attachment; filename=&outname";'; put '%end;'; put '%else %if &platform=SASJS %then %do;'; put '%mfs_httpheader(Content-Type,application/zip)'; put '%mfs_httpheader(Content-disposition,%str(attachment; filename=&outname))'; put '%end;'; put '%end;'; put '%else %do;'; put '%put %str(ERR)OR: Content Type &contenttype NOT SUPPORTED by &sysmacroname!;'; put '%end;'; put '%if &inref ne 0 %then %do;'; put '%mp_binarycopy(inref=&inref,outref=&outref)'; put '%end;'; put '%else %do;'; put '%mp_binarycopy(inloc="&inloc",outref=&outref)'; put '%end;'; put '%mend mp_streamfile;'; put '* SAS Macros end;'; put '* SAS Includes start;'; put '* SAS Includes end;'; put '* Binary Files start;'; put '* Binary Files end;'; put '* ServiceInit start;'; put 'options noquotelenmax ps=max;'; put '/* create dummy macros to prevent stpbegin / stpend from corrupting sessions */'; put '%macro stpbegin();'; put '%put NOTE: the STPBEGIN macro should not be used for web apps!;'; put '%mend stpbegin;'; put '%macro stpend();'; put '%put NOTE: the STPEND macro should not be used for web apps!;'; put '%mend stpend;'; put '* ServiceInit end;'; put '* Service start;'; put '/**'; put '@file'; put '@brief Exports the data controller library in DB specific DDL'; put '@details If user is in the administrator group, they can call this'; put 'service directly adding the following URL params:'; put '@li &flavour= (only PGSQL supported at this time)'; put '@li &schema= (optional, if target schema is needed)'; put '

SAS Macros

'; put '@li mf_getuser.sas'; put '@li mp_abort.sas'; put '@li mp_getddl.sas'; put '@li mp_lib2inserts.sas'; put '@li mp_streamfile.sas'; put '@li mpe_getgroups.sas'; put '@version 9.2'; put '@author 4GL Apps Ltd'; put '@copyright 4GL Apps Ltd. This code may only be used within Data Controller'; put 'and may not be re-distributed or re-sold without the express permission of'; put '4GL Apps Ltd.'; put '**/'; put '%mpeinit()'; put '%global flavour schema;'; put '/* if no flavour is specified, default to SAS */'; put '%let flavour=%sysfunc(coalescec(&flavour,SAS));'; put '/* if no schema var provided, DC Libref is used */'; put '%let schema=%sysfunc(coalescec(&schema,&dc_libref));'; put '/* check user is in admin group */'; put '%mpe_getgroups(user=%mf_getuser(),outds=work.usergroups)'; put 'data work.admins;'; put 'set work.usergroups;'; put 'put (_all_)(=);'; put 'run;'; put '%let cnt=0;'; put 'proc sql noprint;'; put 'select count(*) into:cnt'; put 'from usergroups'; put 'where groupname="&mpeadmins";'; put '%put &=cnt;'; put '%mp_abort(iftrue= (&cnt=0)'; put ',mac=&_program'; put ',msg=%str(The &DC_LIBREF library can only be exported by &mpeadmins members)'; put ')'; put '%mp_getddl(&DC_LIBREF'; put ',flavour=&flavour'; put ',schema=&schema'; put ',applydttm=YES'; put ',fref=tmpref'; put ')'; put '%mp_lib2inserts(&DC_LIBREF,flavour=&flavour,schema=&schema, outref=tmpref)'; put '%mp_streamfile(contenttype=TEXT'; put ',inref=tmpref'; put ',outname=&dc_libref..ddl'; put ')'; put '* Service end;'; run; %mm_createwebservice(path=&appLoc/&path, name=&service, code=sascode, server=&serverName, replace=yes) filename sascode clear; %let service=makedata; filename sascode temp lrecl=32767; data _null_; file sascode; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '/**'; put '@file mp_jsonout.sas'; put '@brief Writes JSON in SASjs format to a fileref'; put '@details This macro can be used to OPEN a JSON stream and send one or more'; put 'tables as arrays of rows, where each row can be an object or a nested array.'; put 'There are two engines available - DATASTEP or PROCJSON.'; put 'PROC JSON is fast but will produce errs like the ones below if'; put 'special chars are encountered.'; put '> (ERR)OR: Some code points did not transcode.'; put '> An object or array close is not valid at this point in the JSON text.'; put '> Date value out of range'; put 'If this happens, try running with ENGINE=DATASTEP.'; put 'The DATASTEP engine is used to handle special SAS missing numerics, and'; put 'can also convert entire datasets to formatted values. Output JSON is always'; put 'in UTF-8.'; put 'Usage:'; put 'filename tmp temp;'; put 'data class; set sashelp.class;run;'; put '%mp_jsonout(OPEN,jref=tmp)'; put '%mp_jsonout(OBJ,class,jref=tmp)'; put '%mp_jsonout(OBJ,class,dslabel=class2,jref=tmp,showmeta=Y)'; put '%mp_jsonout(CLOSE,jref=tmp)'; put 'data _null_;'; put 'infile tmp;'; put 'input;putlog _infile_;'; put 'run;'; put 'If you are building web apps with SAS then you are strongly encouraged to use'; put 'the mX_createwebservice macros in combination with the'; put '[sasjs adapter](https://github.com/sasjs/adapter).'; put 'For more information see https://sasjs.io'; put '@param [in] action Valid values:'; put '@li OPEN - opens the JSON'; put '@li OBJ - sends a table with each row as an object'; put '@li ARR - sends a table with each row in an array'; put '@li CLOSE - closes the JSON'; put '@param [in] ds The dataset to send. Must be a work table.'; put '@param [out] jref= (_webout) The fileref to which to send the JSON'; put '@param [out] dslabel= The name to give the table in the exported JSON'; put '@param [in] fmt= (Y) Whether to keep (Y) or strip (N) formats from the table'; put '@param [in] engine= (DATASTEP) Which engine to use to send the JSON. Options:'; put '@li PROCJSON (default)'; put '@li DATASTEP (more reliable when data has non standard characters)'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to JSON'; put '

Related Files

'; put '@li mp_ds2fmtds.sas'; put '@version 9.2'; put '@author Allan Bowe'; put '@source https://github.com/sasjs/core'; put '**/'; put '%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y'; put ',engine=DATASTEP'; put ',missing=NULL'; put ',showmeta=N'; put ',maxobs=MAX'; put ')/*/STORE SOURCE*/;'; put '%local tempds colinfo fmtds i numcols numobs stmt_obs lastobs optval'; put 'tmpds1 tmpds2 tmpds3 tmpds4;'; put '%let numcols=0;'; put '%if &maxobs ne MAX %then %let stmt_obs=%str(if _n_>&maxobs then stop;);'; put '%if &action=OPEN %then %do;'; put 'options nobomfile;'; put 'data _null_;file &jref encoding=''utf-8'' lrecl=200;'; put 'put ''{"PROCESSED_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''"'';'; put 'run;'; put '%end;'; put '%else %if (&action=ARR or &action=OBJ) %then %do;'; put '/* force variable names to always be uppercase in the JSON */'; put 'options validvarname=upcase;'; put '/* To avoid issues with _webout on EBI - such as encoding diffs and truncation'; put '(https://support.sas.com/kb/49/325.html) we use temporary files */'; put 'filename _sjs1 temp lrecl=200 ;'; put 'data _null_; file _sjs1 encoding=''utf-8'';'; put 'put ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":";'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjs1 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs1 clear;'; put '/* grab col defs */'; put 'proc contents noprint data=&ds'; put 'out=_data_(keep=name type length format formatl formatd varnum label);'; put 'run;'; put '%let colinfo=%scan(&syslast,2,.);'; put 'proc sort data=&colinfo;'; put 'by varnum;'; put 'run;'; put '/* move meta to mac vars */'; put 'data &colinfo;'; put 'if _n_=1 then call symputx(''numcols'',nobs,''l'');'; put 'set &colinfo end=last nobs=nobs;'; put 'name=upcase(name);'; put '/* fix formats */'; put 'if type=2 or type=6 then do;'; put 'typelong=''char'';'; put 'length fmt $49.;'; put 'if format='''' then fmt=cats(''$'',length,''.'');'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else fmt=cats(format,formatl,''.'');'; put 'end;'; put 'else do;'; put 'typelong=''num'';'; put 'if format='''' then fmt=''best.'';'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else if formatd=0 then fmt=cats(format,formatl,''.'');'; put 'else fmt=cats(format,formatl,''.'',formatd);'; put 'end;'; put '/* 32 char unique name */'; put 'newname=''sasjs''!!substr(cats(put(md5(name),$hex32.)),1,27);'; put 'call symputx(cats(''name'',_n_),name,''l'');'; put 'call symputx(cats(''newname'',_n_),newname,''l'');'; put 'call symputx(cats(''length'',_n_),length,''l'');'; put 'call symputx(cats(''fmt'',_n_),fmt,''l'');'; put 'call symputx(cats(''type'',_n_),type,''l'');'; put 'call symputx(cats(''typelong'',_n_),typelong,''l'');'; put 'call symputx(cats(''label'',_n_),coalescec(label,name),''l'');'; put '/* overwritten when fmt=Y and a custom format exists in catalog */'; put 'if typelong=''num'' then call symputx(cats(''fmtlen'',_n_),200,''l'');'; put 'else call symputx(cats(''fmtlen'',_n_),min(32767,ceil((length+10)*1.5)),''l'');'; put 'run;'; put '%let tempds=%substr(_%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql;'; put 'select count(*) into: lastobs from &ds;'; put '%if &maxobs ne MAX %then %let lastobs=%sysfunc(min(&lastobs,&maxobs));'; put '%if &engine=PROCJSON %then %do;'; put '%if &missing=STRING %then %do;'; put '%put &sysmacroname: Special Missings not supported in proc json.;'; put '%put &sysmacroname: Switching to DATASTEP engine;'; put '%goto datastep;'; put '%end;'; put 'data &tempds;'; put 'set &ds;'; put '&stmt_obs;'; put '%if &fmt=N %then format _numeric_ best32.;;'; put '/* PRETTY is necessary to avoid line truncation in large files */'; put 'filename _sjs2 temp lrecl=131068 encoding=''utf-8'';'; put 'proc json out=_sjs2 pretty'; put '%if &action=ARR %then nokeys ;'; put ';export &tempds / nosastags fmtnumeric;'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs2 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs2 clear;'; put '%end;'; put '%else %if &engine=DATASTEP %then %do;'; put '%datastep:'; put '%if %sysfunc(exist(&ds)) ne 1 & %sysfunc(exist(&ds,VIEW)) ne 1'; put '%then %do;'; put '%put &sysmacroname: &ds NOT FOUND!!!;'; put '%return;'; put '%end;'; put '%if &fmt=Y %then %do;'; put '/**'; put '* Extract format definitions'; put '* First, by getting library locations from dictionary.formats'; put '* Then, by exporting the width using proc format'; put '* Cannot use maxw from sashelp.vformat as not always populated'; put '* Cannot use fmtinfo() as not supported in all flavours'; put '*/'; put '%let tmpds1=%substr(fmtsum%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds2=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds3=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds4=%substr(col%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql noprint;'; put 'create table &tmpds1 as'; put 'select cats(libname,''.'',memname) as FMTCAT,'; put 'FMTNAME'; put 'from dictionary.formats'; put 'where fmttype=''F'' and libname is not null'; put 'and fmtname in (select format from &colinfo where format is not null)'; put 'order by 1;'; put 'create table &tmpds2('; put 'FMTNAME char(32),'; put 'LENGTH num'; put ');'; put '%local catlist cat fmtlist i;'; put 'select distinct fmtcat into: catlist separated by '' '' from &tmpds1;'; put '%do i=1 %to %sysfunc(countw(&catlist,%str( )));'; put '%let cat=%scan(&catlist,&i,%str( ));'; put 'proc sql;'; put 'select distinct fmtname into: fmtlist separated by '' '''; put 'from &tmpds1 where fmtcat="&cat";'; put 'proc format lib=&cat cntlout=&tmpds3(keep=fmtname length);'; put 'select &fmtlist;'; put 'run;'; put 'proc sql;'; put 'insert into &tmpds2 select distinct fmtname,length from &tmpds3;'; put '%end;'; put 'proc sql;'; put 'create table &tmpds4 as'; put 'select a.*, b.length as MAXW'; put 'from &colinfo a'; put 'left join &tmpds2 b'; put 'on cats(a.format)=cats(upcase(b.fmtname))'; put 'order by a.varnum;'; put 'data _null_;'; put 'set &tmpds4;'; put 'if not missing(maxw);'; put 'call symputx('; put 'cats(''fmtlen'',_n_),'; put '/* vars need extra padding due to JSON escaping of special chars */'; put 'min(32767,ceil((max(length,maxw)+10)*1.5))'; put ',''l'''; put ');'; put 'run;'; put '/* configure varlenchk - as we are explicitly shortening the variables */'; put '%let optval=%sysfunc(getoption(varlenchk));'; put 'options varlenchk=NOWARN;'; put 'data _data_(compress=char);'; put '/* shorten the new vars */'; put 'length'; put '%do i=1 %to &numcols;'; put '&&name&i $&&fmtlen&i'; put '%end;'; put ';'; put '/* rename on entry */'; put 'set &ds(rename=('; put '%do i=1 %to &numcols;'; put '&&name&i=&&newname&i'; put '%end;'; put '));'; put '&stmt_obs;'; put 'drop'; put '%do i=1 %to &numcols;'; put '&&newname&i'; put '%end;'; put ';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=num %then %do;'; put '&&name&i=cats(put(&&newname&i,&&fmt&i));'; put '%end;'; put '%else %do;'; put '&&name&i=put(&&newname&i,&&fmt&i);'; put '%end;'; put '%end;'; put 'if _error_ then do;'; put 'call symputx(''syscc'',1012);'; put 'stop;'; put 'end;'; put 'run;'; put '%let fmtds=&syslast;'; put 'options varlenchk=&optval;'; put '%end;'; put 'proc format; /* credit yabwon for special null removal */'; put 'value bart (default=40)'; put '%if &missing=NULL %then %do;'; put '._ - .z = null'; put '%end;'; put '%else %do;'; put '._ = [quote()]'; put '. = null'; put '.a - .z = [quote()]'; put '%end;'; put 'other = [best.];'; put 'data &tempds;'; put 'attrib _all_ label='''';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'length &&name&i $&&fmtlen&i...;'; put 'format &&name&i $&&fmtlen&i...;'; put '%end;'; put '%end;'; put '%if &fmt=Y %then %do;'; put 'set &fmtds;'; put '%end;'; put '%else %do;'; put 'set &ds;'; put '%end;'; put '&stmt_obs;'; put 'format _numeric_ bart.;'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'if findc(&&name&i,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put '&&name&i=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,&&name&i)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else &&name&i=quote(cats(&&name&i));'; put '%end;'; put '%end;'; put 'run;'; put 'filename _sjs3 temp lrecl=131068 ;'; put 'data _null_;'; put 'file _sjs3 encoding=''utf-8'';'; put 'if _n_=1 then put "[";'; put 'set &tempds;'; put 'if _n_>1 then put "," @; put'; put '%if &action=ARR %then "[" ; %else "{" ;'; put '%do i=1 %to &numcols;'; put '%if &i>1 %then "," ;'; put '%if &action=OBJ %then """&&name&i"":" ;'; put '"&&name&i"n /* name literal for reserved variable names */'; put '%end;'; put '%if &action=ARR %then "]" ; %else "}" ; ;'; put '/* close out the table */'; put 'data _null_;'; put 'file _sjs3 mod encoding=''utf-8'';'; put 'put '']'';'; put 'run;'; put 'data _null_;'; put 'infile _sjs3 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs3 clear;'; put '%end;'; put 'proc sql;'; put 'drop table &colinfo, &tempds;'; put '%if %substr(&showmeta,1,1)=Y %then %do;'; put 'filename _sjs4 temp lrecl=131068 encoding=''utf-8'';'; put 'data _null_;'; put 'file _sjs4;'; put 'length label $350;'; put 'put ", ""$%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":{""vars"":{";'; put 'do i=1 to &numcols;'; put 'name=quote(trim(symget(cats(''name'',i))));'; put 'format=quote(trim(symget(cats(''fmt'',i))));'; put 'label=quote(prxchange(''s/\\/\\\\/'',-1,trim(symget(cats(''label'',i)))));'; put 'length=quote(trim(symget(cats(''length'',i))));'; put 'type=quote(trim(symget(cats(''typelong'',i))));'; put 'if i>1 then put "," @@;'; put 'put name '':{"format":'' format '',"label":'' label'; put ''',"length":'' length '',"type":'' type ''}'';'; put 'end;'; put 'put ''}}'';'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs4 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs4 clear;'; put '%end;'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put 'data _null_; file &jref encoding=''utf-8'' mod ;'; put 'put "}";'; put 'run;'; put '%end;'; put '%mend mp_jsonout;'; put '/**'; put '@file mm_webout.sas'; put '@brief Send data to/from SAS Stored Processes'; put '@details This macro should be added to the start of each Stored Process,'; put '**immediately** followed by a call to:'; put '%mm_webout(FETCH)'; put 'This will read all the input data and create same-named SAS datasets in the'; put 'WORK library. You can then insert your code, and send data back using the'; put 'following syntax:'; put 'data some datasets; * make some data ;'; put 'retain some columns;'; put 'run;'; put '%mm_webout(OPEN)'; put '%mm_webout(ARR,some) * Array format, fast, suitable for large tables ;'; put '%mm_webout(OBJ,datasets) * Object format, easier to work with ;'; put 'Finally, wrap everything up send some helpful system variables too'; put '%mm_webout(CLOSE)'; put '@param [in] action Either FETCH, OPEN, ARR, OBJ or CLOSE'; put '@param [in] ds The dataset to send back to the frontend'; put '@param [out] dslabel= Value to use instead of table name for sending to JSON'; put '@param [in] fmt= (N) Setting Y converts all vars to their formatted values'; put '@param [out] fref= (_webout) The fileref to which to write the JSON'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] workobs= (0) When set to a positive integer, will create a new'; put 'output object (WORK) which contains this number of observations from all'; put 'tables in the WORK library.'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to output JSON'; put '

SAS Macros

'; put '@li mp_jsonout.sas'; put '

Related Macros

'; put '@li ms_webout.sas'; put '@li mv_webout.sas'; put '@version 9.3'; put '@author Allan Bowe'; put '**/'; put '%macro mm_webout(action,ds,dslabel=,fref=_webout,fmt=N,missing=NULL'; put ',showmeta=N,maxobs=MAX,workobs=0'; put ');'; put '%global _webin_file_count _webin_fileref1 _webin_name1 _program _debug'; put 'sasjs_tables;'; put '%local i tempds jsonengine;'; put '/* see https://github.com/sasjs/core/issues/41 */'; put '%if "%upcase(&SYSENCODING)" ne "UTF-8" %then %let jsonengine=PROCJSON;'; put '%else %let jsonengine=DATASTEP;'; put '%if &action=FETCH %then %do;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'options mprint notes mprintnest;'; put '%end;'; put '%let _webin_file_count=%eval(&_webin_file_count+0);'; put '/* now read in the data */'; put '%do i=1 %to &_webin_file_count;'; put '%if &_webin_file_count=1 %then %do;'; put '%let _webin_fileref1=&_webin_fileref;'; put '%let _webin_name1=&_webin_name;'; put '%end;'; put 'data _null_;'; put 'infile &&_webin_fileref&i termstr=crlf;'; put 'input;'; put 'call symputx(''input_statement'',_infile_);'; put 'putlog "&&_webin_name&i input statement: " _infile_;'; put 'stop;'; put 'data &&_webin_name&i;'; put 'infile &&_webin_fileref&i firstobs=2 dsd termstr=crlf encoding=''utf-8'';'; put 'input &input_statement;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'if _n_<20 then putlog _infile_;'; put '%end;'; put 'run;'; put '%let sasjs_tables=&sasjs_tables &&_webin_name&i;'; put '%end;'; put '%end;'; put '%else %if &action=OPEN %then %do;'; put '/* fix encoding */'; put 'OPTIONS NOBOMFILE;'; put '/**'; put '* check xengine type to avoid the below err message:'; put '* > Function is only valid for filerefs using the CACHE access method.'; put '*/'; put 'data _null_;'; put 'set sashelp.vextfl(where=(fileref="_WEBOUT"));'; put 'if xengine=''STREAM'' then do;'; put 'rc=stpsrv_header(''Content-type'',"text/html; encoding=utf-8");'; put 'end;'; put 'run;'; put '/* setup json */'; put 'data _null_;file &fref encoding=''utf-8'';'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutBEGIN<<'';'; put '%end;'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'run;'; put '%end;'; put '%else %if &action=ARR or &action=OBJ %then %do;'; put '%mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt,jref=&fref'; put ',engine=&jsonengine,missing=&missing,showmeta=&showmeta,maxobs=&maxobs'; put ')'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put '/* To avoid issues with _webout on EBI we use a temporary file */'; put 'filename _sjsref temp lrecl=131068;'; put '%if %str(&workobs) > 0 %then %do;'; put '/* if debug mode, send back first XX records of each work table also */'; put 'data;run;%let tempds=%scan(&syslast,2,.);'; put 'ods output Members=&tempds;'; put 'proc datasets library=WORK memtype=data;'; put '%local wtcnt;%let wtcnt=0;'; put 'data _null_;'; put 'set &tempds;'; put 'if not (upcase(name) =:"DATA"); /* ignore temp datasets */'; put 'i+1;'; put 'call symputx(cats(''wt'',i),name,''l'');'; put 'call symputx(''wtcnt'',i,''l'');'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put ",""WORK"":{";'; put '%do i=1 %to &wtcnt;'; put '%let wt=&&wt&i;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'dsid=open("WORK.&wt",''is'');'; put 'nlobs=attrn(dsid,''NLOBS'');'; put 'nvars=attrn(dsid,''NVARS'');'; put 'rc=close(dsid);'; put 'if &i>1 then put '',''@;'; put 'put " ""&wt"" : {";'; put 'put ''"nlobs":'' nlobs;'; put 'put '',"nvars":'' nvars;'; put '%mp_jsonout(OBJ,&wt,jref=_sjsref,dslabel=first10rows,showmeta=Y'; put ',maxobs=&workobs'; put ')'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put '%end;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put 'run;'; put '%end;'; put '/* close off json */'; put 'data _null_;file _sjsref mod encoding=''utf-8'';'; put 'length SYSPROCESSNAME syserrortext syswarningtext autoexec $512;'; put 'put ",""_DEBUG"" : ""&_debug"" ";'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'autoexec=quote(urlencode(trim(getoption(''autoexec''))));'; put 'put '',"AUTOEXEC" : '' autoexec;'; put 'put ",""MF_GETUSER"" : ""%mf_getuser()"" ";'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'put ",""SYSENCODING"" : ""&sysencoding"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSPROCESSID"" : ""&SYSPROCESSID"" ";'; put 'put ",""SYSPROCESSMODE"" : ""&SYSPROCESSMODE"" ";'; put 'SYSPROCESSNAME=quote(urlencode(cats(SYSPROCESSNAME)));'; put 'put ",""SYSPROCESSNAME"" : " SYSPROCESSNAME;'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put '',"SYSWARNINGTEXT" : '' syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'length memsize $32;'; put 'memsize="%sysfunc(INPUTN(%sysfunc(getoption(memsize)), best.),sizekmg.)";'; put 'memsize=quote(cats(memsize));'; put 'put '',"MEMSIZE" : '' memsize;'; put 'put "}" @;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutEND<<'';'; put '%end;'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjsref lrecl=1 recfm=n;'; put 'file &fref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjsref clear;'; put '%end;'; put '%mend mm_webout;'; put '%macro webout(action,ds,dslabel=,fmt=,missing=NULL,showmeta=NO,maxobs=MAX);'; put '%mm_webout(&action,ds=&ds,dslabel=&dslabel,fmt=&fmt'; put ',missing=&missing'; put ',showmeta=&showmeta'; put ',maxobs=&maxobs'; put ') %mend;'; put '/* provide additional debug info */'; put '%global _program;'; put '%put &=syscc;'; put '%put user=%mf_getuser();'; put '%put pgm=&_program;'; put '%put timestamp=%sysfunc(datetime(),datetime19.);'; put '* Service Variables start;'; put '* Service Variables end;'; put '* SAS Macros start;'; put '%macro mf_getapploc(pgm);'; put '%if "&pgm"="" %then %do;'; put '%if %symexist(_program) %then %let pgm=&_program;'; put '%else %do;'; put '%put &sysmacroname: No value provided and no _program variable available;'; put '%return;'; put '%end;'; put '%end;'; put '%local root;'; put '/**'; put '* First check we are not in the tests/macros folder (which has no subfolders)'; put '* or specifically in the testsetup or testteardown services'; put '*/'; put '%if %index(&pgm,/tests/macros/)'; put 'or %index(&pgm,/tests/testsetup)'; put 'or %index(&pgm,/tests/testteardown)'; put '%then %do;'; put '%let root=%substr(&pgm,1,%index(&pgm,/tests)-1);'; put '&root'; put '%return;'; put '%end;'; put '/**'; put '* Next, move up two levels to avoid matches on subfolder or service name'; put '*/'; put '%let root=%substr(&pgm,1,%length(&pgm)-%length(%scan(&pgm,-1,/))-1);'; put '%let root=%substr(&root,1,%length(&root)-%length(%scan(&root,-1,/))-1);'; put '%if %index(&root,/tests/) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/tests/)-1);'; put '%end;'; put '%else %if %index(&root,/services) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/services)-1);'; put '%end;'; put '%else %if %index(&root,/jobs) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/jobs)-1);'; put '%end;'; put '%else %put &sysmacroname: Could not find an app location from &pgm;'; put '&root'; put '%mend mf_getapploc ;'; put '%macro mf_getuniquefileref(prefix=_,maxtries=1000,lrecl=32767);'; put '%local rc fname;'; put '%if &prefix=0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%end;'; put '%else %do;'; put '%local x len;'; put '%let len=%eval(8-%length(&prefix));'; put '%let x=0;'; put '%do x=0 %to &maxtries;'; put '%let fname=&prefix%substr(%sysfunc(ranuni(0)),3,&len);'; put '%if %sysfunc(fileref(&fname)) > 0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%return;'; put '%end;'; put '%end;'; put '%put unable to find available fileref after &maxtries attempts;'; put '%end;'; put '%mend mf_getuniquefileref;'; put '%macro mp_abort(mac=mp_abort.sas, type=, msg=, iftrue=%str(1=1)'; put ', errds=work.mp_abort_errds'; put ', mode=REGULAR'; put ')/*/STORE SOURCE*/;'; put '%global sysprocessmode sysprocessname sasjs_stpsrv_header_loc sasjsprocessmode;'; put '%local fref fid i;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%put NOTE: /// mp_abort macro executing //;'; put '%if %length(&mac)>0 %then %put NOTE- called by &mac;'; put '%put NOTE - &msg;'; put '%if %symexist(_SYSINCLUDEFILEDEVICE)'; put '/* abort cancel FILE does not restart outside the INCLUDE on Viya 3.5 */'; put 'and %superq(SYSPROCESSNAME) ne %str(Compute Server)'; put '%then %do;'; put '%if "*&_SYSINCLUDEFILEDEVICE*" ne "**" %then %do;'; put 'data &errds;'; put 'iftrue=''1=1'';'; put 'length mac $100 msg $5000;'; put 'mac=symget(''mac'');'; put 'msg=symget(''msg'');'; put 'run;'; put 'data _null_;'; put 'abort cancel FILE;'; put 'run;'; put '%return;'; put '%end;'; put '%end;'; put '/* Web App Context */'; put '%if %symexist(_PROGRAM)'; put 'or %superq(SYSPROCESSNAME) = %str(Compute Server)'; put 'or &mode=INCLUDE'; put '%then %do;'; put 'options obs=max replace mprint;'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5"'; put '%then %do;'; put 'options nosyntaxcheck;'; put '%end;'; put '%if &mode=INCLUDE %then %do;'; put '%if %sysfunc(exist(&errds))=1 %then %do;'; put 'data _null_;'; put 'set &errds;'; put 'call symputx(''iftrue'',iftrue,''l'');'; put 'call symputx(''mac'',mac,''l'');'; put 'call symputx(''msg'',msg,''l'');'; put 'putlog (_all_)(=);'; put 'run;'; put '%if (&iftrue)=0 %then %return;'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: No include errors found;'; put '%return;'; put '%end;'; put '%end;'; put '/* extract log errs / warns, if exist */'; put '%local logloc logline;'; put '%global logmsg; /* capture global messages */'; put '%if %symexist(SYSPRINTTOLOG) %then %let logloc=&SYSPRINTTOLOG;'; put '%else %let logloc=%qsysfunc(getoption(LOG));'; put 'proc printto log=log;run;'; put '%let logline=0;'; put '%if %length(&logloc)>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input; putlog _infile_;'; put 'i=1;'; put 'retain logonce 0;'; put 'if ('; put '_infile_=:"%str(WARN)ING" or _infile_=:"%str(ERR)OR"'; put ') and logonce=0 then'; put 'do;'; put 'call symputx(''logline'',_n_);'; put 'logonce+1;'; put 'end;'; put 'run;'; put '/* capture log including lines BEFORE the err */'; put '%if &logline>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input;'; put 'i=1;'; put 'stoploop=0;'; put 'if _n_ ge &logline-15 and stoploop=0 then do until (i>22);'; put 'call symputx(''logmsg'',catx(''\n'',symget(''logmsg''),_infile_));'; put 'input;'; put 'i+1;'; put 'stoploop=1;'; put 'end;'; put 'if stoploop=1 then stop;'; put 'run;'; put '%end;'; put '%end;'; put '%if %symexist(SYS_JES_JOB_URI) %then %do;'; put '/* setup webout for Viya */'; put 'options nobomfile;'; put '%if "X&SYS_JES_JOB_URI.X"="XX" %then %do;'; put 'filename _webout temp lrecl=999999 mod;'; put '%end;'; put '%else %do;'; put 'filename _webout filesrvc parenturi="&SYS_JES_JOB_URI"'; put 'name="_webout.json" lrecl=999999 mod;'; put '%end;'; put '%end;'; put '%else %if %sysfunc(filename(fref,&sasjs_stpsrv_header_loc))=0 %then %do;'; put 'options nobomfile;'; put '/* set up http header for SASjs Server */'; put '%let fid=%sysfunc(fopen(&fref,A));'; put '%if &fid=0 %then %do;'; put '%put %str(ERR)OR: %sysfunc(sysmsg());'; put '%return;'; put '%end;'; put '%let rc=%sysfunc(fput(&fid,%str(Content-Type: application/json)));'; put '%let rc=%sysfunc(fwrite(&fid));'; put '%let rc=%sysfunc(fclose(&fid));'; put '%let rc=%sysfunc(filename(&fref));'; put '%end;'; put '/* send response in SASjs JSON format */'; put 'data _null_;'; put 'file _webout mod lrecl=32000 encoding=''utf-8'';'; put 'length msg syswarningtext syserrortext $32767 mode $10 ;'; put 'sasdatetime=datetime();'; put 'msg=symget(''msg'');'; put '%if &logline>0 %then %do;'; put 'msg=cats(msg,''\n\nLog Extract:\n'',symget(''logmsg''));'; put '%end;'; put '/* escape the escapes */'; put 'msg=tranwrd(msg,''\'',''\\'');'; put '/* escape the quotes */'; put 'msg=tranwrd(msg,''"'',''\"'');'; put '/* ditch the CRLFs as chrome complains */'; put 'msg=compress(msg,,''kw'');'; put '/* quote without quoting the quotes (which are escaped instead) */'; put 'msg=cats(''"'',msg,''"'');'; put 'if symexist(''_debug'') then debug=quote(trim(symget(''_debug'')));'; put 'else debug=''""'';'; put 'if symget(''sasjsprocessmode'')=''Stored Program'' then mode=''SASJS'';'; put 'if mode ne ''SASJS'' then put ''>>weboutBEGIN<<'';'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'put '',"sasjsAbort" : [{'';'; put 'put '' "MSG":'' msg ;'; put 'put '' ,"MAC": "'' "&mac" ''"}]'';'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'put '',"_DEBUG":'' debug ;'; put 'if symexist(''_metauser'') then do;'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put 'end;'; put 'if symexist(''SYS_JES_JOB_URI'') then do;'; put 'SYS_JES_JOB_URI=quote(trim(symget(''SYS_JES_JOB_URI'')));'; put 'put '',"SYS_JES_JOB_URI": '' SYS_JES_JOB_URI;'; put 'end;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put ",""SYSWARNINGTEXT"" : " syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'put "}" ;'; put 'if mode ne ''SASJS'' then put ''>>weboutEND<<'';'; put 'run;'; put '%put _all_;'; put '%if "&sysprocessmode " = "SAS Stored Process Server " %then %do;'; put 'data _null_;'; put 'putlog ''stpsrvset program err and syscc'';'; put 'rc=stpsrvset(''program error'', 0);'; put 'call symputx("syscc",0,"g");'; put 'run;'; put '%if &sysscp=WIN'; put 'and 1=0 /* deprecating this logic until we figure out a consistent abort */'; put 'and "%substr(%str(&sysvlong ),1,8)"="9.04.01M"'; put 'and "%substr(%str(&sysvlong ),9,1)">"5" %then %do;'; put '/* skip approach (below) does not work in windows m6+ envs */'; put 'endsas;'; put '%end;'; put '%else %do;'; put '/**'; put '* endsas kills 9.4m3 deployments by orphaning multibridges.'; put '* Abort variants are ungraceful (non zero return code)'; put '* This approach lets SAS run silently until the end :-)'; put '* Caution - fails when called within a %include within a macro'; put '* Use mp_include() to handle this.'; put '*/'; put 'filename skip temp;'; put 'data _null_;'; put 'file skip;'; put 'put ''%macro skip();'';'; put 'comment ''%mend skip; -> fix lint '';'; put 'put ''%macro skippy();'';'; put 'comment ''%mend skippy; -> fix lint '';'; put 'run;'; put '%inc skip;'; put '%end;'; put '%end;'; put '%else %if "&sysprocessmode " = "SAS Compute Server " %then %do;'; put '/* endsas kills the session making it harder to fetch results */'; put 'data _null_;'; put 'syswarningtext=symget(''syswarningtext'');'; put 'syserrortext=symget(''syserrortext'');'; put 'abort_msg=symget(''msg'');'; put 'syscc=symget(''syscc'');'; put 'sysuserid=symget(''sysuserid'');'; put 'iftrue=symget(''iftrue'');'; put 'put (_all_)(/=);'; put 'call symputx(''syscc'',0);'; put 'abort cancel nolist;'; put 'run;'; put '%end;'; put '%else %do;'; put '%abort cancel;'; put '%end;'; put '%end;'; put '%else %do;'; put '%put _all_;'; put '%abort cancel;'; put '%end;'; put '%mend mp_abort;'; put '/** @endcond */'; put '%macro mm_getstpcode('; put 'tree=/User Folders/sasdemo/somestp'; put ',name='; put ',outloc=0'; put ',outref=0'; put ',mDebug=1'; put ',showlog=NO'; put ');'; put '%local mD;'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing &sysmacroname..sas;'; put '%&mD.put _local_;'; put '%if %length(&name)>0 %then %let name=/&name;'; put '/* first, check if STP exists */'; put '%local tsuri;'; put '%let tsuri=stopifempty ;'; put 'data _null_;'; put 'format type uri tsuri value $200.;'; put 'call missing (of _all_);'; put 'path="&tree&name(StoredProcess)";'; put '/* first, find the STP ID */'; put 'if metadata_pathobj("",path,"StoredProcess",type,uri)>0 then do;'; put '/* get sourcecode */'; put 'cnt=1;'; put 'do while (metadata_getnasn(uri,"Notes",cnt,tsuri)>0);'; put 'rc=metadata_getattr(tsuri,"Name",value);'; put '&mD.put tsuri= value=;'; put 'if value="SourceCode" then do;'; put '/* found it! */'; put 'rc=metadata_getattr(tsuri,"Id",value);'; put 'call symputx(''tsuri'',value,''l'');'; put 'stop;'; put 'end;'; put 'cnt+1;'; put 'end;'; put 'end;'; put 'else put (_all_)(=);'; put 'run;'; put '%mp_abort(iftrue= (&tsuri=stopifempty)'; put ',mac=mm_getstpcode'; put ',msg=%str(&tree&name.(StoredProcess) not found!)'; put ')'; put '/**'; put '* Now we can extract the textstore'; put '*/'; put 'filename __getdoc temp lrecl=10000000;'; put 'proc metadata'; put 'in="$METAREPOSITORY'; put ''; put 'SAS1"'; put 'out=__getdoc ;'; put 'run;'; put '/* find the beginning of the text */'; put '%local start;'; put 'data _null_;'; put 'infile __getdoc lrecl=10000;'; put 'input;'; put 'start=index(_infile_,''StoredText="'');'; put 'if start then do;'; put 'call symputx("start",start+11);'; put '*putlog ''"'' _infile_ ''"'';'; put 'end;'; put 'stop;'; put '%local outeng;'; put '%if "&outloc"="0" %then %let outeng=TEMP;'; put '%else %let outeng="&outloc";'; put '%local fref;'; put '%if &outref=0 %then %let fref=%mf_getuniquefileref();'; put '%else %let fref=&outref;'; put '/* read the content, byte by byte, resolving escaped chars */'; put 'filename &fref &outeng lrecl=100000;'; put 'data _null_;'; put 'length filein 8 fileid 8;'; put 'filein = fopen("__getdoc","I",1,"B");'; put 'fileid = fopen("&fref","O",1,"B");'; put 'rec = "20"x;'; put 'length entity $6;'; put 'do while(fread(filein)=0);'; put 'x+1;'; put 'if x>&start then do;'; put 'rc = fget(filein,rec,1);'; put 'if rec=''"'' then leave;'; put 'else if rec="&" then do;'; put 'entity=rec;'; put 'do until (rec=";");'; put 'if fread(filein) ne 0 then goto getout;'; put 'rc = fget(filein,rec,1);'; put 'entity=cats(entity,rec);'; put 'end;'; put 'select (entity);'; put 'when (''&'' ) rec=''&'' ;'; put 'when (''<'' ) rec=''<'' ;'; put 'when (''>'' ) rec=''>'' ;'; put 'when (''''') rec="''" ;'; put 'when (''"'') rec=''"'' ;'; put 'when ('' '') rec=''0A''x;'; put 'when ('' '') rec=''0D''x;'; put 'when (''$'' ) rec=''$'' ;'; put 'when ('' '') rec=''09''x;'; put 'otherwise putlog "%str(WARN)ING: missing value for " entity=;'; put 'end;'; put 'rc =fput(fileid, substr(rec,1,1));'; put 'rc =fwrite(fileid);'; put 'end;'; put 'else do;'; put 'rc =fput(fileid,rec);'; put 'rc =fwrite(fileid);'; put 'end;'; put 'end;'; put 'end;'; put 'getout:'; put 'rc=fclose(filein);'; put 'rc=fclose(fileid);'; put 'run;'; put '%if &showlog=YES %then %do;'; put 'data _null_;'; put 'infile &fref lrecl=32767 end=last;'; put 'input;'; put 'if _n_=1 then putlog ''>>stpcodeBEGIN<<'';'; put 'putlog _infile_;'; put 'if last then putlog ''>>stpcodeEND<<'';'; put 'run;'; put '%end;'; put 'filename __getdoc clear;'; put '%if &outref=0 %then %do;'; put 'filename &fref clear;'; put '%end;'; put '%mend mm_getstpcode;'; put '%macro dc_getsettings();'; put '%global _program;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(syscc=&syscc on entry (&syswarningtext &syserrortext))'; put ')'; put '%if %length(&_PROGRAM)>1 %then %let root=&_program;'; put '%else %do;'; put '%global _metauser;'; put '%let _metauser=&sysuserid;'; put '/* to mimic a "real" _program we need to give a dummy role and stp name */'; put '%let root=/dummyRole/dummyName;'; put '%end;'; put '/* the DC precode is stored in the Admin folder in the root of'; put 'the project. Lets find that root. */'; put '%put &=root;'; put '%let root=%mf_getapploc();'; put '%put &=root;'; put '/* Now we know the root location we can retrieve the params */'; put '%let temploc=%sysfunc(pathname(work))/settings.txt;'; put '%mm_getstpcode(tree=&root/services/public'; put ',name=Data_Controller_Settings'; put ',outloc=&temploc'; put ')'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Unable to run getstpcode)'; put ')'; put 'filename _getsets "&temploc" lrecl=2000;'; put '/*'; put 'Do not use mp_include here - this puts a copy in every service, which creates'; put 'compilation problems when calling services from mp_include'; put '*/'; put '%inc _getsets/source2;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Problem running &sysmacroname (&syswarningtext &syserrortext))'; put ')'; put '%mend dc_getsettings;'; put '%macro mf_fmtdttm('; put ')/*/STORE SOURCE*/;'; put '%if "&sysver"="9.2" or "&sysver"="9.3"'; put 'or ("&sysver"="9.4" and "%substr(&SYSVLONG,9,1)" le "3")'; put 'or "%substr(&sysver,1,1)"="4"'; put 'or "%substr(&sysver,1,1)"="5"'; put '%then %do;DATETIME19.3%end;'; put '%else %do;E8601DT26.6%end;'; put '%mend mf_fmtdttm;'; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '%macro mp_init(prefix=SASJS'; put ')/*/STORE SOURCE*/;'; put '%if %symexist(SASJS_PREFIX) %then %return; /* only run once */'; put '%global'; put 'SASJS_PREFIX /* the ONLY hard-coded global macro variable in SASjs */'; put '&prefix._FUNCTIONS /* used in mcf_init() to track core function compilation */'; put '&prefix._INIT_NUM /* initialisation time as numeric */'; put '&prefix._INIT_DTTM /* initialisation time in E8601DT26.6 format */'; put '&prefix.WORK /* avoid typing %sysfunc(pathname(work)) every time */'; put ';'; put '%let sasjs_prefix=&prefix;'; put 'data _null_;'; put 'dttm=datetime();'; put 'call symputx("&sasjs_prefix._init_num",dttm,''g'');'; put 'call symputx("&sasjs_prefix._init_dttm",put(dttm,E8601DT26.6),''g'');'; put 'call symputx("&sasjs_prefix.work",pathname(''WORK''),''g'');'; put 'run;'; put 'options'; put 'compress=CHAR /* default is none so ensure we have something! */'; put 'datastmtchk=ALLKEYWORDS /* protection from overwriting input datasets */'; put 'errorcheck=STRICT /* catch errs in libname/filename statements */'; put 'fmterr /* ensure err when a format cannot be found */'; put 'mergenoby=%str(ERR)OR /* throw err when a merge has no BY variables */'; put 'missing=. /* changing this can cause hard to detect errs */'; put 'noquotelenmax /* avoid warnings for long strings */'; put 'noreplace /* avoid overwriting permanent datasets */'; put 'ps=max /* reduce log size slightly */'; put 'ls=max /* reduce log even more and avoid word truncation */'; put 'validmemname=COMPATIBLE /* avoid special characters etc in table names */'; put 'validvarname=V7 /* avoid special characters etc in variable names */'; put 'varinitchk=%str(ERR)OR /* avoid data mistakes from variable name typos */'; put 'varlenchk=%str(ERR)OR /* fail hard if truncation (data loss) can result */'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5" %then %do;'; put 'noautocorrect /* disallow misspelled procedure names */'; put 'dsoptions=note2err /* undocumented - convert bad NOTEs to ERRs */'; put '%end;'; put ';'; put '%mend mp_init;'; put '%macro mpeinit(fetch=YES);'; put '%global mpeinit'; put 'mpeadmins /* group with unrestricted Meditor access */'; put 'mpelocapprovals /* location for landing and staging files */'; put 'mpelib /* location of configuration tables for DC */'; put 'dc_repo_users /* location of user / group metadata */'; put 'dc_licence_key /* extracted in dc_getsettings */'; put 'dc_activation_key /* extracted in dc_getsettings */'; put 'dc_locale /* extracted in dc_getsettings */'; put 'dc_dttmtfmt /* can be overridden in dc_getsettings */'; put '_debug'; put ';'; put '%if &mpeinit=1 %then %return;'; put '%else %let mpeinit=1;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(Problem on service startup (&syswarningtext &syserrortext))'; put ')'; put '%mp_init()'; put '%if &fetch=YES %then %do;'; put '%webout(FETCH)'; put '%end;'; put '%global _CLIENTNAME;'; put '%mp_abort(iftrue= (&_CLIENTNAME=SAS Enterprise Guide)'; put ',mac=&_program..sas'; put ',msg=%str(Data Controller is a web app and should not be executed from EG)'; put ')'; put 'options urlencoding=utf8 nobomfile lrecl=32767;'; put '%let perf=%sysfunc(datetime());'; put '%put perfdiff: 0;'; put '%let dc_locale=SYSTEM; /* default if not set */'; put '/**'; put '* E8601DT26.6 has widest database support - but not all SAS flavours can'; put '* handle it. Override in the settings STP if needed.'; put '*/'; put 'data _null_;'; put 'dc_dttmtfmt=''"%sysfunc(datetime(),''!!"%mf_fmtdttm()"!!'')"dt'';'; put 'call symputx(''dc_dttmtfmt'',dc_dttmtfmt);'; put 'put dc_dttmtfmt=;'; put 'run;'; put '%put &=dc_dttmtfmt;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc prior to dc_getsettings)'; put ')'; put '%dc_getsettings()'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc after dc_getsettings)'; put ')'; put 'data _null_;'; put 'set &DC_LIBREF..mpe_config(where=('; put 'var_scope="DC"'; put 'and &dc_dttmtfmt lt tx_to'; put 'and var_active=1'; put '));'; put 'call symputx(var_name,var_value,''G'');'; put 'putlog var_name "=" var_value;'; put 'run;'; put '%let mpelib=&dc_libref;'; put '%let mpeadmins=&dc_admin_group;'; put '%let mpelocapprovals=&dc_staging_area;'; put '%let dc_repo_users=&dc_repo_users;'; put '%if &dc_locale ne SYSTEM %then %do;'; put 'options locale=&dc_locale;'; put '%end;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(Problem during compilation or with STP precode (&syswarningtext))'; put ')'; put '%mend mpeinit;'; put '%macro mf_mval(var);'; put '%if %symexist(&var) %then %do;'; put '%superq(&var)'; put '%end;'; put '%mend mf_mval;'; put '%macro mf_trimstr(basestr,trimstr);'; put '%local baselen trimlen trimval;'; put '/* return if basestr is shorter than trimstr (or 0) */'; put '%let baselen=%length(%superq(basestr));'; put '%let trimlen=%length(%superq(trimstr));'; put '%if &baselen < &trimlen or &baselen=0 %then %return;'; put '/* obtain the characters from the end of basestr */'; put '%let trimval=%qsubstr(%superq(basestr)'; put ',%length(%superq(basestr))-&trimlen+1'; put ',&trimlen);'; put '/* compare and if matching, chop it off! */'; put '%if %superq(basestr)=%superq(trimstr) %then %do;'; put '%return;'; put '%end;'; put '%else %if %superq(trimval)=%superq(trimstr) %then %do;'; put '%qsubstr(%superq(basestr),1,%length(%superq(basestr))-&trimlen)'; put '%end;'; put '%else %do;'; put '&basestr'; put '%end;'; put '%mend mf_trimstr;'; put '%macro mf_getplatform(switch'; put ')/*/STORE SOURCE*/;'; put '%local a b c;'; put '%if &switch.NONE=NONE %then %do;'; put '%if %symexist(sasjsprocessmode) %then %do;'; put '%if &sasjsprocessmode=Stored Program %then %do;'; put 'SASJS'; put '%return;'; put '%end;'; put '%end;'; put '%if %symexist(sysprocessmode) %then %do;'; put '%if "&sysprocessmode"="SAS Object Server"'; put 'or "&sysprocessmode"= "SAS Compute Server" %then %do;'; put 'SASVIYA'; put '%end;'; put '%else %if "&sysprocessmode"="SAS Stored Process Server"'; put 'or "&sysprocessmode"="SAS Workspace Server"'; put '%then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if %symexist(_metaport) or %symexist(_metauser) %then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if &switch=SASSTUDIO %then %do;'; put '/* return the version of SAS Studio else 0 */'; put '%if %mf_mval(_CLIENTAPP)=%str(SAS Studio) %then %do;'; put '%let a=%mf_mval(_CLIENTVERSION);'; put '%let b=%scan(&a,1,.);'; put '%if %eval(&b >2) %then %do;'; put '&b'; put '%end;'; put '%else 0;'; put '%end;'; put '%else 0;'; put '%end;'; put '%else %if &switch=VIYARESTAPI %then %do;'; put '%mf_trimstr(%sysfunc(getoption(servicesbaseurl)),/)'; put '%end;'; put '%mend mf_getplatform;'; put '%macro mpeterm();'; put '%local oldloc;'; put 'data _null_;'; put 'if symexist(''SYSPRINTTOLOG'') then oldloc=symget(''SYSPRINTTOLOG'');'; put 'else oldloc=getoption(''LOG'');'; put 'if subpad(oldloc,1,1) not in (''"'',"''",'' '') then oldloc=quote(cats(oldloc));'; put 'call symputx(''oldloc'',oldloc,''l'');'; put 'run;'; put '%if %length(&oldloc)>0 %then %do;'; put 'proc printto log=log;'; put 'run;'; put 'data _null_;'; put 'infile &oldloc;'; put 'input; putlog _infile_;'; put 'run;'; put '%end;'; put '%if %sysfunc(exist(&dc_libref..mpe_requests)) and %mf_getplatform() ne SASVIYA'; put '%then %do;'; put 'data ;'; put 'if 0 then set &dc_libref..mpe_requests;'; put 'request_dttm=%sysfunc(datetime());'; put 'request_user="%mf_getuser()";'; put 'request_service="%scan(&_program,-2,/)/%scan(&_program,-1,/)";'; put 'request_params='''';'; put 'output;stop;'; put 'proc append base=&dc_libref..mpe_requests data=&syslast force nowarn;'; put 'run;'; put '%end;'; put '%mend mpeterm;'; put '%macro mf_increment(macro_name,incr=1);'; put '/* iterate the value */'; put '%let ¯o_name=%eval(&&¯o_name+&incr);'; put '/* return the value */'; put '&&¯o_name'; put '%mend mf_increment;'; put '%macro mpe_makedata(lib=,mpeadmins=,path=);'; put '%if &syscc ne 0 %then %do;'; put '%put syscc=&syscc exiting &sysmacroname;'; put '%return;'; put '%end;'; put 'proc sql;'; put 'insert into &lib..mpe_alerts set'; put 'tx_from=0'; put ',tx_to=''31DEC9999:23:59:59''dt'; put ',alert_event=''*ALL*'''; put ',alert_lib=''*ALL*'''; put ',alert_ds=''*ALL*'''; put ',alert_user="&sysuserid";'; put 'insert into &lib..mpe_column_level_security set'; put 'tx_from=0'; put ',tx_to=''31DEC9999:23:59:59''dt'; put ',CLS_SCOPE=''EDIT'''; put ',CLS_GROUP=''AllUsers'''; put ',CLS_LIBREF="&lib"'; put ',CLS_TABLE=''MPE_LOCKANYTABLE'''; put ',CLS_VARIABLE_NM=''LOCK_STATUS_CD'''; put ',CLS_ACTIVE=1'; put ',CLS_HIDE=0;'; put 'insert into &lib..mpe_config set'; put 'tx_from=0'; put ',tx_to=''31DEC9999:23:59:59''dt'; put ',var_scope="DC"'; put ',var_name="DC_EMAIL_ALERTS"'; put ',var_value=''NO'''; put ',var_active=1'; put ',var_desc=''YES or NO to enable email alerts. Note - this requires email '''; put '!!''options to be preconfigured! They can be configured in the '''; put '!!''settings stp if needed.'';'; put 'insert into &lib..mpe_config set'; put 'tx_from=0'; put ',tx_to=''31DEC9999:23:59:59''dt'; put ',var_scope="DC"'; put ',var_name="DC_VIEWLIB_CHECK"'; put ',var_value=''NO'''; put ',var_active=1'; put ',var_desc='; put '''Set to YES to enable library validity checking in viewLibs service.'''; put '!!'' Note: this can make the service very slow if there are lots of '''; put '!!''external libraries. If enabled, this removes empty libraries from '''; put '!!''the viewer library dropdown. To switch off, set to NO.'';'; put 'insert into &lib..mpe_config set'; put 'tx_from=0'; put ',tx_to=''31DEC9999:23:59:59''dt'; put ',var_scope="DC"'; put ',var_name="DC_MACROS"'; put ',var_value=cats(symget(''path''),"/dc_macros")'; put ',var_active=1'; put ',var_desc=''Location of underlying macros - EUC feature.'';'; put 'insert into &lib..mpe_config set'; put 'tx_from=0'; put ',tx_to=''31DEC9999:23:59:59''dt'; put ',var_scope="DC"'; put ',var_name="DC_MAXOBS_WEBEDIT"'; put ',var_value="100"'; put ',var_active=1'; put ',var_desc=''This sets the maximum number of observations that can be loaded'''; put '!!'' into the browser for editing in the EDIT screen. A higher number'''; put '!!'' will require a decent browser (ie, not IE) and more memory on the'''; put '!!'' client side.'';'; put 'insert into &lib..mpe_config set'; put 'tx_from=0'; put ',tx_to=''31DEC9999:23:59:59''dt'; put ',var_scope="DC"'; put ',var_name="DC_RESTRICT_VIEWER"'; put ',var_value="NO"'; put ',var_active=1'; put ',var_desc=''YES will restrict the list of libraries and tables in VIEWER to'''; put '!!'' those explicitly set to VIEW in the MPE_SECURITY table. Default=NO.'';'; put 'insert into &lib..mpe_config set'; put 'tx_from=0'; put ',tx_to=''31DEC9999:23:59:59''dt'; put ',var_scope="DC"'; put ',var_name="DC_RESTRICT_EDITRECORD"'; put ',var_value="NO"'; put ',var_active=1'; put ',var_desc=''Setting YES will prevent the EDIT RECORD dialog appearing in the'''; put '!!'' EDIT screen by removing the "Edit Row" option in the right click menu'''; put '!!'', and the "ADD RECORD" button in the bottom left. Default=NO.'';'; put 'insert into &lib..mpe_config set'; put 'tx_from=0'; put ',tx_to=''31DEC9999:23:59:59''dt'; put ',var_scope="DC_CATALOG"'; put ',var_name="DC_IGNORELIBS"'; put ',var_value="|MAPSSAS|MAPS|"'; put ',var_active=1'; put ',var_desc=''Pipe seperated list of librefs (uppercase) to be ignored when'''; put '!!'' running the Data Catalog refresh process. This can enable a clean'''; put '!!'' run when invalid librefs are returned by the mpe_refreshlibs macro.'';'; put 'insert into &lib..mpe_config set'; put 'tx_from=0'; put ',tx_to=''31DEC9999:23:59:59''dt'; put ',var_scope="DC"'; put ',var_name="DC_LOCALE"'; put ',var_value="SYSTEM"'; put ',var_active=1'; put ',var_desc=''Set to a locale (such as en_gb or en_be) to override the system'''; put '!!'' value (which can be driven from the browser settings). This is '''; put '!!''useful when importing ambiguous dates from CSV or Excel (eg 1/2/20 vs '''; put '!!''2/1/20) as DC uses the anydtdtm informats for import. Default=SYSTEM.'';'; put 'insert into &lib..mpe_config set'; put 'tx_from=0'; put ',tx_to=''31DEC9999:23:59:59''dt'; put ',var_scope="DCBL_REDSH"'; put ',var_name="BULKLOAD"'; put ',var_value="YES"'; put ',var_active=0'; put ',var_desc=''Set to YES to enable BULKLOAD=YES in redshift'';'; put 'insert into &lib..mpe_config set'; put 'tx_from=0'; put ',tx_to=''31DEC9999:23:59:59''dt'; put ',var_scope="DCBL_REDSH"'; put ',var_name="BL_BUCKET"'; put ',var_value="''your-aws-bucket/Exchange''"'; put ',var_active=0'; put ',var_desc=''Set to the (quoted) value of the AWS bucket to'''; put '!!'' use for s3 uploads in redshift'';'; put 'insert into &lib..mpe_config set'; put 'tx_from=0'; put ',tx_to=''31DEC9999:23:59:59''dt'; put ',var_scope="DCBL_REDSH"'; put ',var_name="BL_AWS_CREDENTIALS_FILE"'; put ',var_value="''/path/to/your/aws/s3/.credentials''"'; put ',var_active=0'; put ',var_desc=''Set to the (quoted) value of the AWS creds file'';'; put 'insert into &lib..mpe_config set'; put 'tx_from=0'; put ',tx_to=''31DEC9999:23:59:59''dt'; put ',var_scope="DCBL_REDSH"'; put ',var_name="BL_REGION"'; put ',var_value="''eu-west-1''"'; put ',var_active=0'; put ',var_desc=''Set to the (quoted) AWS region in use'';'; put 'insert into &lib..mpe_config set'; put 'tx_from=0'; put ',tx_to=''31DEC9999:23:59:59''dt'; put ',var_scope="DCBL_REDSH"'; put ',var_name="BL_COMPRESS"'; put ',var_value="YES"'; put ',var_active=0'; put ',var_desc=''Set to YES to perform compression ahead of the COPY command'';'; put 'insert into &lib..mpe_config set'; put 'tx_from=0'; put ',tx_to=''31DEC9999:23:59:59''dt'; put ',var_scope="DCBL_REDSH"'; put ',var_name="BL_USE_SSL"'; put ',var_value="YES"'; put ',var_active=0'; put ',var_desc=''Set to YES to use SSL encryption'';'; put 'insert into &lib..mpe_config set'; put 'tx_from=0'; put ',tx_to=''31DEC9999:23:59:59''dt'; put ',var_scope="DC_REVIEW"'; put ',var_name="HISTORY_ROWS"'; put ',var_value=''100'''; put ',var_active=1'; put ',var_desc=''Number of rows (or additional rows) to return in the HISTORY '''; put '!!''page'';'; put 'insert into &lib..mpe_config set'; put 'tx_from=0'; put ',tx_to=''31DEC9999:23:59:59''dt'; put ',var_scope="DC"'; put ',var_name="DC_LICENCE_KEY"'; put ',var_value='' '''; put ',var_active=1'; put ',var_desc=''Licence Key'';'; put 'insert into &lib..mpe_config set'; put 'tx_from=0'; put ',tx_to=''31DEC9999:23:59:59''dt'; put ',var_scope="DC"'; put ',var_name="DC_ACTIVATION_KEY"'; put ',var_value='' '''; put ',var_active=1'; put ',var_desc=''Activation Key'';'; put 'insert into &lib..mpe_datadictionary set'; put 'tx_from=0'; put ',DD_TYPE=''LIBRARY'''; put ',DD_SOURCE="&lib"'; put ',DD_SHORTDESC="Data Controller Control Tables"'; put ',DD_LONGDESC="# The Data Controller Library"'; put ',DD_OWNER="&sysuserid"'; put ',DD_RESPONSIBLE="&sysuserid"'; put ',DD_SENSITIVITY="Low"'; put ',tx_to=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..mpe_datadictionary set'; put 'tx_from=0'; put ',DD_TYPE=''TABLE'''; put ',DD_SOURCE="&lib..MPE_TABLES"'; put ',DD_SHORTDESC="Configuration of new tables for Data Controller"'; put ',DD_LONGDESC="# MPE_TABLES - adding new tabels to Data Controller"'; put ',DD_OWNER="&sysuserid"'; put ',DD_RESPONSIBLE="&sysuserid"'; put ',DD_SENSITIVITY="Low"'; put ',tx_to=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..mpe_datadictionary set'; put 'tx_from=0'; put ',DD_TYPE=''COLUMN'''; put ',DD_SOURCE="&lib..MPE_TABLES.DSN"'; put ',DD_SHORTDESC="Dataset Name to be edited"'; put ',DD_LONGDESC="_DSN_ - must be UPCASE"'; put ',DD_OWNER="&sysuserid"'; put ',DD_RESPONSIBLE="&sysuserid"'; put ',DD_SENSITIVITY="Low"'; put ',tx_to=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..mpe_datadictionary set'; put 'tx_from=0'; put ',DD_TYPE=''DIRECTORY'''; put ',DD_SOURCE="/some/directory"'; put ',DD_SHORTDESC="Directory for some purpose"'; put ',DD_LONGDESC="This directory is great. It''s great directory.'; put 'It trumps all other directories."'; put ',DD_OWNER="&sysuserid"'; put ',DD_RESPONSIBLE="&sysuserid"'; put ',DD_SENSITIVITY="Low"'; put ',tx_to=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..mpe_datadictionary set'; put 'tx_from=0'; put ',DD_TYPE=''TABLE'''; put ',DD_SOURCE="&lib"'; put ',DD_SHORTDESC="Transaction table for capturing Data Controller users"'; put ',DD_LONGDESC="After a user accepts the Data Controller EULA they are "'; put '!!"registered as a user in this table."'; put ',DD_OWNER="&sysuserid"'; put ',DD_RESPONSIBLE="&sysuserid"'; put ',DD_SENSITIVITY="Low"'; put ',tx_to=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..mpe_datadictionary set'; put 'tx_from=0'; put ',DD_TYPE=''COLUMN'''; put ',DD_SOURCE="&lib..MPE_CONFIG.VAR_ACTIVE"'; put ',DD_SHORTDESC="Set to 1 to make an option active"'; put ',DD_LONGDESC="This value is used as a filter by data controller whenever "'; put '!!"querying for option settings."'; put ',DD_OWNER="&sysuserid"'; put ',DD_RESPONSIBLE="&sysuserid"'; put ',DD_SENSITIVITY="Low"'; put ',tx_to=''31DEC5999:23:59:59''dt;'; put '/**'; put '* mpe_xlmap_info'; put '*/'; put 'insert into &lib..mpe_xlmap_info set'; put 'tx_from=0'; put ',tx_to=''31DEC5999:23:59:59''dt'; put ',xlmap_id=''BASEL-KM1'''; put ',xlmap_description=''Basel 3 Key Metrics report'''; put ',XLMAP_TARGETLIBDS="&lib..MPE_XLMAP_DATA";'; put '/**'; put '* mpe_xlmap_rules'; put '*/'; put 'insert into &lib..mpe_xlmap_rules set'; put 'tx_from=0'; put ',tx_to=''31DEC5999:23:59:59''dt'; put ',xlmap_id=''BASEL-KM1'''; put ',xlmap_range_id=''KM1:a'''; put ',xlmap_sheet=''KM1'''; put ',xlmap_start=''MATCH 4 R[2]C[0]:a'';'; put 'insert into &lib..mpe_xlmap_rules set'; put 'tx_from=0'; put ',tx_to=''31DEC5999:23:59:59''dt'; put ',xlmap_id=''BASEL-KM1'''; put ',xlmap_range_id=''KM1:b'''; put ',xlmap_sheet=''KM1'''; put ',xlmap_start=''MATCH 4 R[2]C[0]:b'';'; put 'insert into &lib..mpe_xlmap_rules set'; put 'tx_from=0'; put ',tx_to=''31DEC5999:23:59:59''dt'; put ',xlmap_id=''BASEL-KM1'''; put ',xlmap_range_id=''KM1:c'''; put ',xlmap_sheet=''KM1'''; put ',xlmap_start=''MATCH 4 R[2]C[0]:c'';'; put 'insert into &lib..mpe_xlmap_rules set'; put 'tx_from=0'; put ',tx_to=''31DEC5999:23:59:59''dt'; put ',xlmap_id=''BASEL-KM1'''; put ',xlmap_range_id=''KM1:d'''; put ',xlmap_sheet=''KM1'''; put ',xlmap_start=''MATCH 4 R[2]C[0]:d'';'; put 'insert into &lib..mpe_xlmap_rules set'; put 'tx_from=0'; put ',tx_to=''31DEC5999:23:59:59''dt'; put ',xlmap_id=''BASEL-KM1'''; put ',xlmap_range_id=''KM1:e'''; put ',xlmap_sheet=''KM1'''; put ',xlmap_start=''MATCH 4 R[2]C[0]:e'';'; put 'insert into &lib..mpe_xlmap_rules set'; put 'tx_from=0'; put ',tx_to=''31DEC5999:23:59:59''dt'; put ',xlmap_id=''BASEL-KM1'''; put ',xlmap_range_id=''KM1:f'''; put ',xlmap_sheet=''KM1'''; put ',xlmap_start=''MATCH 4 R[2]C[0]:f'';'; put 'insert into &lib..mpe_xlmap_rules set'; put 'tx_from=0'; put ',tx_to=''31DEC5999:23:59:59''dt'; put ',xlmap_id=''BASEL-KM1'''; put ',xlmap_range_id=''KM1:1/a'''; put ',xlmap_sheet=''KM1'''; put ',xlmap_start=''MATCH C R[0]C[1]:Common Equity Tier 1 (CET1)'';'; put 'insert into &lib..mpe_xlmap_rules set'; put 'tx_from=0'; put ',tx_to=''31DEC5999:23:59:59''dt'; put ',xlmap_id=''BASEL-KM1'''; put ',xlmap_range_id=''KM1:1/b'''; put ',xlmap_sheet=''KM1'''; put ',xlmap_start=''MATCH C R[0]C[2]:Common Equity Tier 1 (CET1)'';'; put 'insert into &lib..mpe_xlmap_rules set'; put 'tx_from=0'; put ',tx_to=''31DEC5999:23:59:59''dt'; put ',xlmap_id=''BASEL-KM1'''; put ',xlmap_range_id=''KM1:1/c'''; put ',xlmap_sheet=''KM1'''; put ',xlmap_start=''MATCH C R[0]C[3]:Common Equity Tier 1 (CET1)'';'; put 'insert into &lib..mpe_xlmap_rules set'; put 'tx_from=0'; put ',tx_to=''31DEC5999:23:59:59''dt'; put ',xlmap_id=''BASEL-KM1'''; put ',xlmap_range_id=''KM1:1/d'''; put ',xlmap_sheet=''KM1'''; put ',xlmap_start=''MATCH C R[0]C[4]:Common Equity Tier 1 (CET1)'';'; put 'insert into &lib..mpe_xlmap_rules set'; put 'tx_from=0'; put ',tx_to=''31DEC5999:23:59:59''dt'; put ',xlmap_id=''BASEL-KM1'''; put ',xlmap_range_id=''KM1:1/e'''; put ',xlmap_sheet=''KM1'''; put ',xlmap_start=''MATCH C R[0]C[5]:Common Equity Tier 1 (CET1)'';'; put 'insert into &lib..mpe_xlmap_rules set'; put 'tx_from=0'; put ',tx_to=''31DEC5999:23:59:59''dt'; put ',xlmap_id=''BASEL-KM1'''; put ',xlmap_range_id=''KM1:1/f'''; put ',xlmap_sheet=''KM1'''; put ',xlmap_start=''MATCH C R[0]C[6]:Common Equity Tier 1 (CET1)'';'; put 'insert into &lib..mpe_xlmap_rules set'; put 'tx_from=0'; put ',tx_to=''31DEC5999:23:59:59''dt'; put ',xlmap_id=''BASEL-KM1'''; put ',xlmap_range_id=''KM1:1a/e'''; put ',xlmap_sheet=''KM1'''; put ',xlmap_start=''MATCH C R[1]C[5]:Common Equity Tier 1 (CET1)'';'; put 'insert into &lib..mpe_xlmap_rules set'; put 'tx_from=0'; put ',tx_to=''31DEC5999:23:59:59''dt'; put ',xlmap_id=''BASEL-KM1'''; put ',xlmap_range_id=''KM1:1a/f'''; put ',xlmap_sheet=''KM1'''; put ',xlmap_start=''MATCH C R[1]C[6]:Common Equity Tier 1 (CET1)'';'; put 'insert into &lib..mpe_xlmap_rules set'; put 'tx_from=0'; put ',tx_to=''31DEC5999:23:59:59''dt'; put ',xlmap_id=''BASEL-KM1'''; put ',xlmap_range_id=''KM1:2/a'''; put ',xlmap_sheet=''KM1'''; put ',xlmap_start=''ABSOLUTE D10'';'; put 'insert into &lib..mpe_xlmap_rules set'; put 'tx_from=0'; put ',tx_to=''31DEC5999:23:59:59''dt'; put ',xlmap_id=''BASEL-KM1'''; put ',xlmap_range_id=''KM1:2/b'''; put ',xlmap_sheet=''/3'''; put ',xlmap_start=''ABSOLUTE E10'';'; put 'insert into &lib..mpe_xlmap_rules set'; put 'tx_from=0'; put ',tx_to=''31DEC5999:23:59:59''dt'; put ',xlmap_id=''BASEL-KM1'''; put ',xlmap_range_id=''KM1:2/c'''; put ',xlmap_sheet=''/3'''; put ',xlmap_start=''RELATIVE R[10]C[6]'';'; put 'insert into &lib..mpe_xlmap_rules set'; put 'tx_from=0'; put ',tx_to=''31DEC5999:23:59:59''dt'; put ',xlmap_id=''BASEL-KM1'''; put ',xlmap_range_id=''KM1:2/d'''; put ',xlmap_sheet=''/3'''; put ',xlmap_start=''RELATIVE R[10]C[8]'';'; put 'insert into &lib..mpe_xlmap_rules set'; put 'tx_from=0'; put ',tx_to=''31DEC5999:23:59:59''dt'; put ',xlmap_id=''BASEL-KM1'''; put ',xlmap_range_id=''KM1:2/e'''; put ',xlmap_sheet=''/3'''; put ',xlmap_start=''RELATIVE R[10]C[9]'';'; put 'insert into &lib..mpe_xlmap_rules set'; put 'tx_from=0'; put ',tx_to=''31DEC5999:23:59:59''dt'; put ',xlmap_id=''BASEL-KM1'''; put ',xlmap_range_id=''KM1:2/f'''; put ',xlmap_sheet=''/3'''; put ',xlmap_start=''RELATIVE R[10]C[10]'';'; put 'insert into &lib..mpe_xlmap_rules set'; put 'tx_from=0'; put ',tx_to=''31DEC5999:23:59:59''dt'; put ',xlmap_id=''BASEL-KM1'''; put ',xlmap_range_id=''KM1:2a'''; put ',xlmap_sheet=''KM1'''; put ',xlmap_start=''ABSOLUTE H11'''; put ',xlmap_finish=''RELATIVE R[0]C[1]'';'; put 'insert into &lib..mpe_xlmap_rules set'; put 'tx_from=0'; put ',tx_to=''31DEC5999:23:59:59''dt'; put ',xlmap_id=''BASEL-KM1'''; put ',xlmap_range_id=''KM1:3'''; put ',xlmap_sheet=''KM1'''; put ',xlmap_start=''RELATIVE R[12]C[4]'''; put ',xlmap_finish=''ABSOLUTE I13'';'; put 'insert into &lib..mpe_xlmap_rules set'; put 'tx_from=0'; put ',tx_to=''31DEC5999:23:59:59''dt'; put ',xlmap_id=''BASEL-CR2'''; put ',xlmap_range_id=''CR2-sec1'''; put ',xlmap_sheet=''CR2'''; put ',xlmap_start=''ABSOLUTE D8'''; put ',xlmap_finish=''BLANKROW'';'; put 'insert into &lib..mpe_xlmap_rules set'; put 'tx_from=0'; put ',tx_to=''31DEC5999:23:59:59''dt'; put ',xlmap_id=''BASEL-CR2'''; put ',xlmap_range_id=''CR2-sec2'''; put ',xlmap_sheet=''CR2'''; put ',xlmap_start=''ABSOLUTE D18'''; put ',xlmap_finish=''LASTDOWN'';'; put 'insert into &lib..mpe_xlmap_rules set'; put 'tx_from=0'; put ',tx_to=''31DEC5999:23:59:59''dt'; put ',xlmap_id=''SAMPLE'''; put ',xlmap_range_id=''header'''; put ',xlmap_sheet=''/1'''; put ',xlmap_start=''ABSOLUTE B3'''; put ',xlmap_finish=''ABSOLUTE B8'';'; put 'insert into &lib..mpe_xlmap_rules set'; put 'tx_from=0'; put ',tx_to=''31DEC5999:23:59:59''dt'; put ',xlmap_id=''SAMPLE'''; put ',xlmap_range_id=''data'''; put ',xlmap_sheet=''/1'''; put ',xlmap_start=''ABSOLUTE B13'''; put ',xlmap_finish=''ABSOLUTE E16'';'; put '/**'; put '* MPE_GROUPS'; put '*/'; put 'insert into &lib..mpe_groups set'; put 'tx_from=0'; put ',group_name="dc-admin"'; put ',group_desc="Custom Group for Data Controller Purposes"'; put ',user_name="allbow"'; put ',tx_to=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..mpe_groups set'; put 'tx_from=0'; put ',group_name="dc-admin"'; put ',group_desc="Custom Group for Data Controller Purposes"'; put ',user_name="dctestuser1"'; put ',tx_to=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..mpe_groups set'; put 'tx_from=0'; put ',group_name="dc-admin"'; put ',group_desc="Custom Group for Data Controller Purposes"'; put ',user_name="mihmed"'; put ',tx_to=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..mpe_groups set'; put 'tx_from=0'; put ',group_name="sec-sas9-prd-ext-sasplatform-300115datacontroller"'; put ',group_desc="Custom Group for Data Controller Purposes"'; put ',user_name="DCTest"'; put ',tx_to=''31DEC5999:23:59:59''dt;'; put '/**'; put '* MPE_ROW_LEVEL_SECURITY'; put '*/'; put 'insert into &lib..mpe_row_level_security set'; put 'tx_from=0'; put ',tx_to=''31DEC5999:23:59:59''dt'; put ',RLS_RK=1'; put ',RLS_SCOPE=''ALL'''; put ',RLS_GROUP=''sec-sas9-prd-int-sasplatform-300114sasjs'''; put ',RLS_LIBREF="&lib."'; put ',RLS_TABLE="MPE_GROUPS"'; put ',RLS_GROUP_LOGIC=''AND'''; put ',RLS_SUBGROUP_LOGIC=''OR'''; put ',RLS_SUBGROUP_ID=0'; put ',RLS_VARIABLE_NM=''GROUP_NAME'''; put ',RLS_OPERATOR_NM=''NE'''; put ',RLS_RAW_VALUE="''-1''"'; put ',RLS_ACTIVE=1;'; put 'insert into &lib..mpe_row_level_security set'; put 'tx_from=0'; put ',tx_to=''31DEC5999:23:59:59''dt'; put ',RLS_RK=2'; put ',RLS_SCOPE=''ALL'''; put ',RLS_GROUP=''sec-sas9-prd-int-sasplatform-300114sasjs'''; put ',RLS_LIBREF="&lib"'; put ',RLS_TABLE="MPE_ROW_LEVEL_SECURITY"'; put ',RLS_GROUP_LOGIC=''AND'''; put ',RLS_SUBGROUP_LOGIC=''OR'''; put ',RLS_SUBGROUP_ID=0'; put ',RLS_VARIABLE_NM=''RLS_RK'''; put ',RLS_OPERATOR_NM=''>'''; put ',RLS_RAW_VALUE=''0'''; put ',RLS_ACTIVE=1;'; put 'insert into &lib..mpe_row_level_security set'; put 'tx_from=0'; put ',tx_to=''31DEC5999:23:59:59''dt'; put ',RLS_RK=3'; put ',RLS_SCOPE=''ALL'''; put ',RLS_GROUP=''DC Demo Group'''; put ',RLS_LIBREF="&lib"'; put ',RLS_TABLE="MPE_SECURITY"'; put ',RLS_GROUP_LOGIC=''AND'''; put ',RLS_SUBGROUP_LOGIC=''OR'''; put ',RLS_SUBGROUP_ID=0'; put ',RLS_VARIABLE_NM=''ACCESS_LEVEL'''; put ',RLS_OPERATOR_NM=''NE'''; put ',RLS_RAW_VALUE="''N/A''"'; put ',RLS_ACTIVE=1;'; put '/**'; put '* MPE_SECURITY'; put '*/'; put 'insert into &lib..mpe_security set'; put 'tx_from=0'; put ',libref="*ALL*"'; put ',dsn="*ALL*"'; put ',access_level="APPROVE"'; put ',sas_group="sec-sas9-prd-int-sasplatform-300114sasjs"'; put ',tx_to=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..mpe_security set'; put 'tx_from=0'; put ',libref="*ALL*"'; put ',dsn="*ALL*"'; put ',access_level="EDIT"'; put ',sas_group="sec-sas9-prd-int-sasplatform-300114sasjs"'; put ',tx_to=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..mpe_security set'; put 'tx_from=0'; put ',libref="*ALL*"'; put ',dsn="*ALL*"'; put ',access_level="APPROVE"'; put ',sas_group="sec-sas9-prd-ext-sasplatform-300114sasjs"'; put ',tx_to=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..mpe_security set'; put 'tx_from=0'; put ',libref="*ALL*"'; put ',dsn="*ALL*"'; put ',access_level="EDIT"'; put ',sas_group="sec-sas9-prd-ext-sasplatform-300114sasjs"'; put ',tx_to=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..mpe_security set'; put 'tx_from=0'; put ',libref="*ALL*"'; put ',dsn="*ALL*"'; put ',access_level="EDIT"'; put ',sas_group="dc-admin"'; put ',tx_to=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..mpe_security set'; put 'tx_from=0'; put ',libref="*ALL*"'; put ',dsn="*ALL*"'; put ',access_level="APPROVE"'; put ',sas_group="dc-admin"'; put ',tx_to=''31DEC5999:23:59:59''dt;'; put '/* mpe_selectbox */'; put '%let rk=1;'; put 'insert into &lib..mpe_selectbox set'; put 'selectbox_rk=&rk'; put ',ver_from_dttm=0'; put ',select_lib="&lib"'; put ',select_ds="MPE_LOCKANYTABLE"'; put ',base_column="LOCK_STATUS_CD"'; put ',selectbox_value=''LOCKED'''; put ',selectbox_order=1'; put ',ver_to_dttm=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..mpe_selectbox set'; put 'selectbox_rk=%mf_increment(rk)'; put ',ver_from_dttm=0'; put ',select_lib="&lib"'; put ',select_ds="MPE_LOCKANYTABLE"'; put ',base_column="LOCK_STATUS_CD"'; put ',selectbox_value=''UNLOCKED'''; put ',selectbox_order=2'; put ',ver_to_dttm=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..mpe_selectbox set'; put 'selectbox_rk=%mf_increment(rk)'; put ',ver_from_dttm=0'; put ',select_lib="&lib"'; put ',select_ds="MPE_SECURITY"'; put ',base_column="ACCESS_LEVEL"'; put ',selectbox_value=''EDIT'''; put ',selectbox_order=0'; put ',ver_to_dttm=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..mpe_selectbox set'; put 'selectbox_rk=%mf_increment(rk)'; put ',ver_from_dttm=0'; put ',select_lib="&lib"'; put ',select_ds="MPE_SECURITY"'; put ',base_column="ACCESS_LEVEL"'; put ',selectbox_value=''APPROVE'''; put ',selectbox_order=1'; put ',ver_to_dttm=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..mpe_selectbox set'; put 'selectbox_rk=%mf_increment(rk)'; put ',ver_from_dttm=0'; put ',select_lib="&lib"'; put ',select_ds="MPE_SECURITY"'; put ',base_column="ACCESS_LEVEL"'; put ',selectbox_value=''VIEW'''; put ',selectbox_order=2'; put ',ver_to_dttm=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..mpe_selectbox set'; put 'selectbox_rk=%mf_increment(rk)'; put ',ver_from_dttm=0'; put ',select_lib="&lib"'; put ',select_ds="MPE_SECURITY"'; put ',base_column="ACCESS_LEVEL"'; put ',selectbox_value=''SIGNOFF'''; put ',selectbox_order=3'; put ',ver_to_dttm=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..mpe_selectbox set'; put 'selectbox_rk=%mf_increment(rk)'; put ',ver_from_dttm=0'; put ',select_lib="&lib"'; put ',select_ds="MPE_TABLES"'; put ',base_column="LOADTYPE"'; put ',selectbox_value=''UPDATE'''; put ',selectbox_order=1'; put ',ver_to_dttm=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..mpe_selectbox set'; put 'selectbox_rk=%mf_increment(rk)'; put ',ver_from_dttm=0'; put ',select_lib="&lib"'; put ',select_ds="MPE_TABLES"'; put ',base_column="LOADTYPE"'; put ',selectbox_value=''REPLACE'''; put ',selectbox_order=2'; put ',ver_to_dttm=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..mpe_selectbox set'; put 'selectbox_rk=%mf_increment(rk)'; put ',ver_from_dttm=0'; put ',select_lib="&lib"'; put ',select_ds="MPE_TABLES"'; put ',base_column="LOADTYPE"'; put ',selectbox_value=''TXTEMPORAL'''; put ',selectbox_order=3'; put ',ver_to_dttm=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..mpe_selectbox set'; put 'selectbox_rk=%mf_increment(rk)'; put ',ver_from_dttm=0'; put ',select_lib="&lib"'; put ',select_ds="MPE_TABLES"'; put ',base_column="LOADTYPE"'; put ',selectbox_value=''BITEMPORAL'''; put ',selectbox_order=4'; put ',ver_to_dttm=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..mpe_selectbox set'; put 'selectbox_rk=%mf_increment(rk)'; put ',ver_from_dttm=0'; put ',select_lib="&lib"'; put ',select_ds="MPE_TABLES"'; put ',base_column="LOADTYPE"'; put ',selectbox_value=''FORMAT_CAT'''; put ',selectbox_order=5'; put ',ver_to_dttm=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..mpe_selectbox set'; put 'selectbox_rk=%mf_increment(rk)'; put ',ver_from_dttm=0'; put ',select_lib="&lib"'; put ',select_ds="MPE_ALERTS"'; put ',base_column="ALERT_EVENT"'; put ',selectbox_value=''*ALL*'''; put ',selectbox_order=1'; put ',ver_to_dttm=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..mpe_selectbox set'; put 'selectbox_rk=%mf_increment(rk)'; put ',ver_from_dttm=0'; put ',select_lib="&lib"'; put ',select_ds="MPE_ALERTS"'; put ',base_column="ALERT_EVENT"'; put ',selectbox_value=''SUBMITTED'''; put ',selectbox_order=2'; put ',ver_to_dttm=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..mpe_selectbox set'; put 'selectbox_rk=%mf_increment(rk)'; put ',ver_from_dttm=0'; put ',select_lib="&lib"'; put ',select_ds="MPE_ALERTS"'; put ',base_column="ALERT_EVENT"'; put ',selectbox_value=''APPROVED'''; put ',selectbox_order=3'; put ',ver_to_dttm=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..mpe_selectbox set'; put 'selectbox_rk=%mf_increment(rk)'; put ',ver_from_dttm=0'; put ',select_lib="&lib"'; put ',select_ds="MPE_ALERTS"'; put ',base_column="ALERT_EVENT"'; put ',selectbox_value=''REJECTED'''; put ',selectbox_order=4'; put ',ver_to_dttm=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..mpe_selectbox set'; put 'selectbox_rk=%mf_increment(rk)'; put ',ver_from_dttm=0'; put ',select_lib="&lib"'; put ',select_ds="MPE_X_TEST"'; put ',base_column="SOME_DROPDOWN"'; put ',selectbox_value=''Option 1'''; put ',selectbox_order=1'; put ',ver_to_dttm=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..mpe_selectbox set'; put 'selectbox_rk=%mf_increment(rk)'; put ',ver_from_dttm=0'; put ',select_lib="&lib"'; put ',select_ds="MPE_X_TEST"'; put ',base_column="SOME_DROPDOWN"'; put ',selectbox_value=''Option 2'''; put ',selectbox_order=2'; put ',ver_to_dttm=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..mpe_selectbox set'; put 'selectbox_rk=%mf_increment(rk)'; put ',ver_from_dttm=0'; put ',select_lib="&lib"'; put ',select_ds="MPE_X_TEST"'; put ',base_column="SOME_DROPDOWN"'; put ',selectbox_value=''Option 3'''; put ',selectbox_order=2'; put ',ver_to_dttm=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..mpe_selectbox set'; put 'selectbox_rk=%mf_increment(rk)'; put ',ver_from_dttm=0'; put ',select_lib="&lib"'; put ',select_ds="MPE_X_TEST"'; put ',base_column="SOME_DROPDOWN"'; put ',selectbox_value="This is a long option. This option is very long. "'; put '!!"It is optional, though."'; put ',selectbox_order=3'; put ',ver_to_dttm=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..mpe_selectbox set'; put 'selectbox_rk=%mf_increment(rk)'; put ',ver_from_dttm=0'; put ',select_lib="&lib"'; put ',select_ds="MPE_VALIDATIONS"'; put ',base_column="RULE_TYPE"'; put ',selectbox_value="CASE"'; put ',selectbox_order=1'; put ',ver_to_dttm=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..mpe_selectbox set'; put 'selectbox_rk=%mf_increment(rk)'; put ',ver_from_dttm=0'; put ',select_lib="&lib"'; put ',select_ds="MPE_VALIDATIONS"'; put ',base_column="RULE_TYPE"'; put ',selectbox_value="MINVAL"'; put ',selectbox_order=2'; put ',ver_to_dttm=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..mpe_selectbox set'; put 'selectbox_rk=%mf_increment(rk)'; put ',ver_from_dttm=0'; put ',select_lib="&lib"'; put ',select_ds="MPE_VALIDATIONS"'; put ',base_column="RULE_TYPE"'; put ',selectbox_value="MAXVAL"'; put ',selectbox_order=3'; put ',ver_to_dttm=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..mpe_selectbox set'; put 'selectbox_rk=%mf_increment(rk)'; put ',ver_from_dttm=0'; put ',select_lib="&lib"'; put ',select_ds="MPE_VALIDATIONS"'; put ',base_column="RULE_TYPE"'; put ',selectbox_value="HARDSELECT"'; put ',selectbox_order=4'; put ',ver_to_dttm=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..mpe_selectbox set'; put 'selectbox_rk=%mf_increment(rk)'; put ',ver_from_dttm=0'; put ',select_lib="&lib"'; put ',select_ds="MPE_VALIDATIONS"'; put ',base_column="RULE_TYPE"'; put ',selectbox_value="SOFTSELECT"'; put ',selectbox_order=5'; put ',ver_to_dttm=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..mpe_selectbox set'; put 'selectbox_rk=%mf_increment(rk)'; put ',ver_from_dttm=0'; put ',select_lib="&lib"'; put ',select_ds="MPE_VALIDATIONS"'; put ',base_column="RULE_TYPE"'; put ',selectbox_value="NOTNULL"'; put ',selectbox_order=6'; put ',ver_to_dttm=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..mpe_selectbox set'; put 'selectbox_rk=%mf_increment(rk)'; put ',ver_from_dttm=0'; put ',select_lib="&lib"'; put ',select_ds="MPE_SECURITY"'; put ',base_column="DSN"'; put ',selectbox_value="SOME_DATASET"'; put ',selectbox_order=1'; put ',ver_to_dttm=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..mpe_selectbox set'; put 'selectbox_rk=%mf_increment(rk)'; put ',ver_from_dttm=0'; put ',select_lib="&lib"'; put ',select_ds="MPE_SECURITY"'; put ',base_column="DSN"'; put ',selectbox_value="EXAMPLE"'; put ',selectbox_order=2'; put ',ver_to_dttm=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..mpe_selectbox set'; put 'selectbox_rk=%mf_increment(rk)'; put ',ver_from_dttm=0'; put ',select_lib="&lib"'; put ',select_ds="MPE_DATADICTIONARY"'; put ',base_column="DD_TYPE"'; put ',selectbox_value="COLUMN"'; put ',selectbox_order=1'; put ',ver_to_dttm=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..mpe_selectbox set'; put 'selectbox_rk=%mf_increment(rk)'; put ',ver_from_dttm=0'; put ',select_lib="&lib"'; put ',select_ds="MPE_DATADICTIONARY"'; put ',base_column="DD_TYPE"'; put ',selectbox_value="TABLE"'; put ',selectbox_order=2'; put ',ver_to_dttm=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..mpe_selectbox set'; put 'selectbox_rk=%mf_increment(rk)'; put ',ver_from_dttm=0'; put ',select_lib="&lib"'; put ',select_ds="MPE_DATADICTIONARY"'; put ',base_column="DD_TYPE"'; put ',selectbox_value="LIBRARY"'; put ',selectbox_order=3'; put ',ver_to_dttm=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..mpe_selectbox set'; put 'selectbox_rk=%mf_increment(rk)'; put ',ver_from_dttm=0'; put ',select_lib="&lib"'; put ',select_ds="MPE_DATADICTIONARY"'; put ',base_column="DD_TYPE"'; put ',selectbox_value="CATALOG"'; put ',selectbox_order=3'; put ',ver_to_dttm=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..mpe_selectbox set'; put 'selectbox_rk=%mf_increment(rk)'; put ',ver_from_dttm=0'; put ',select_lib="&lib"'; put ',select_ds="MPE_DATADICTIONARY"'; put ',base_column="DD_TYPE"'; put ',selectbox_value="FORMAT"'; put ',selectbox_order=3'; put ',ver_to_dttm=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..mpe_selectbox set'; put 'selectbox_rk=%mf_increment(rk)'; put ',ver_from_dttm=0'; put ',select_lib="&lib"'; put ',select_ds="MPE_SECURITY"'; put ',base_column="LIBREF"'; put ',selectbox_value=''*ALL*'''; put ',selectbox_order=1'; put ',ver_to_dttm=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..mpe_selectbox set'; put 'selectbox_rk=%mf_increment(rk)'; put ',ver_from_dttm=0'; put ',select_lib="&lib"'; put ',select_ds="MPE_SECURITY"'; put ',base_column="ACCESS_LEVEL"'; put ',selectbox_value=''AUDIT'''; put ',selectbox_order=4'; put ',ver_to_dttm=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..mpe_selectbox set'; put 'selectbox_rk=%mf_increment(rk)'; put ',ver_from_dttm=0'; put ',select_lib="&lib"'; put ',select_ds="MPE_VALIDATIONS"'; put ',base_column="RULE_TYPE"'; put ',selectbox_value="HARDSELECT_HOOK"'; put ',selectbox_order=7'; put ',ver_to_dttm=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..mpe_selectbox set'; put 'selectbox_rk=%mf_increment(rk)'; put ',ver_from_dttm=0'; put ',select_lib="&lib"'; put ',select_ds="MPE_VALIDATIONS"'; put ',base_column="RULE_TYPE"'; put ',selectbox_value="SOFTSELECT_HOOK"'; put ',selectbox_order=7'; put ',ver_to_dttm=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..mpe_selectbox set'; put 'selectbox_rk=%mf_increment(rk)'; put ',ver_from_dttm=0'; put ',select_lib="&lib"'; put ',select_ds="MPE_ROW_LEVEL_SECURITY"'; put ',base_column="RLS_SCOPE"'; put ',selectbox_value="ALL"'; put ',selectbox_order=1'; put ',ver_to_dttm=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..mpe_selectbox set'; put 'selectbox_rk=%mf_increment(rk)'; put ',ver_from_dttm=0'; put ',select_lib="&lib"'; put ',select_ds="MPE_ROW_LEVEL_SECURITY"'; put ',base_column="RLS_SCOPE"'; put ',selectbox_value="EDIT"'; put ',selectbox_order=1'; put ',ver_to_dttm=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..mpe_selectbox set'; put 'selectbox_rk=%mf_increment(rk)'; put ',ver_from_dttm=0'; put ',select_lib="&lib"'; put ',select_ds="MPE_ROW_LEVEL_SECURITY"'; put ',base_column="RLS_SCOPE"'; put ',selectbox_value="VIEW"'; put ',selectbox_order=1'; put ',ver_to_dttm=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..mpe_selectbox set'; put 'selectbox_rk=%mf_increment(rk)'; put ',ver_from_dttm=0'; put ',select_lib="&lib"'; put ',select_ds="MPE_ROW_LEVEL_SECURITY"'; put ',base_column="RLS_GROUP_LOGIC"'; put ',selectbox_value="AND"'; put ',selectbox_order=1'; put ',ver_to_dttm=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..mpe_selectbox set'; put 'selectbox_rk=%mf_increment(rk)'; put ',ver_from_dttm=0'; put ',select_lib="&lib"'; put ',select_ds="MPE_ROW_LEVEL_SECURITY"'; put ',base_column="RLS_GROUP_LOGIC"'; put ',selectbox_value="OR"'; put ',selectbox_order=2'; put ',ver_to_dttm=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..mpe_selectbox set'; put 'selectbox_rk=%mf_increment(rk)'; put ',ver_from_dttm=0'; put ',select_lib="&lib"'; put ',select_ds="MPE_ROW_LEVEL_SECURITY"'; put ',base_column="RLS_SUBGROUP_LOGIC"'; put ',selectbox_value="AND"'; put ',selectbox_order=1'; put ',ver_to_dttm=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..mpe_selectbox set'; put 'selectbox_rk=%mf_increment(rk)'; put ',ver_from_dttm=0'; put ',select_lib="&lib"'; put ',select_ds="MPE_ROW_LEVEL_SECURITY"'; put ',base_column="RLS_SUBGROUP_LOGIC"'; put ',selectbox_value="OR"'; put ',selectbox_order=2'; put ',ver_to_dttm=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..mpe_selectbox set'; put 'selectbox_rk=%mf_increment(rk)'; put ',ver_from_dttm=0'; put ',select_lib="&lib"'; put ',select_ds="MPE_ROW_LEVEL_SECURITY"'; put ',base_column="RLS_OPERATOR_NM"'; put ',selectbox_value="="'; put ',selectbox_order=0'; put ',ver_to_dttm=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..mpe_selectbox set'; put 'selectbox_rk=%mf_increment(rk)'; put ',ver_from_dttm=0'; put ',select_lib="&lib"'; put ',select_ds="MPE_ROW_LEVEL_SECURITY"'; put ',base_column="RLS_OPERATOR_NM"'; put ',selectbox_value=">"'; put ',selectbox_order=1'; put ',ver_to_dttm=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..mpe_selectbox set'; put 'selectbox_rk=%mf_increment(rk)'; put ',ver_from_dttm=0'; put ',select_lib="&lib"'; put ',select_ds="MPE_ROW_LEVEL_SECURITY"'; put ',base_column="RLS_OPERATOR_NM"'; put ',selectbox_value="<"'; put ',selectbox_order=1'; put ',ver_to_dttm=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..mpe_selectbox set'; put 'selectbox_rk=%mf_increment(rk)'; put ',ver_from_dttm=0'; put ',select_lib="&lib"'; put ',select_ds="MPE_ROW_LEVEL_SECURITY"'; put ',base_column="RLS_OPERATOR_NM"'; put ',selectbox_value="<="'; put ',selectbox_order=1'; put ',ver_to_dttm=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..mpe_selectbox set'; put 'selectbox_rk=%mf_increment(rk)'; put ',ver_from_dttm=0'; put ',select_lib="&lib"'; put ',select_ds="MPE_ROW_LEVEL_SECURITY"'; put ',base_column="RLS_OPERATOR_NM"'; put ',selectbox_value=">="'; put ',selectbox_order=1'; put ',ver_to_dttm=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..mpe_selectbox set'; put 'selectbox_rk=%mf_increment(rk)'; put ',ver_from_dttm=0'; put ',select_lib="&lib"'; put ',select_ds="MPE_ROW_LEVEL_SECURITY"'; put ',base_column="RLS_OPERATOR_NM"'; put ',selectbox_value="BETWEEN"'; put ',selectbox_order=1'; put ',ver_to_dttm=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..mpe_selectbox set'; put 'selectbox_rk=%mf_increment(rk)'; put ',ver_from_dttm=0'; put ',select_lib="&lib"'; put ',select_ds="MPE_ROW_LEVEL_SECURITY"'; put ',base_column="RLS_OPERATOR_NM"'; put ',selectbox_value="IN"'; put ',selectbox_order=1'; put ',ver_to_dttm=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..mpe_selectbox set'; put 'selectbox_rk=%mf_increment(rk)'; put ',ver_from_dttm=0'; put ',select_lib="&lib"'; put ',select_ds="MPE_ROW_LEVEL_SECURITY"'; put ',base_column="RLS_OPERATOR_NM"'; put ',selectbox_value="NOT IN"'; put ',selectbox_order=1'; put ',ver_to_dttm=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..mpe_selectbox set'; put 'selectbox_rk=%mf_increment(rk)'; put ',ver_from_dttm=0'; put ',select_lib="&lib"'; put ',select_ds="MPE_ROW_LEVEL_SECURITY"'; put ',base_column="RLS_OPERATOR_NM"'; put ',selectbox_value="NE"'; put ',selectbox_order=1'; put ',ver_to_dttm=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..mpe_selectbox set'; put 'selectbox_rk=%mf_increment(rk)'; put ',ver_from_dttm=0'; put ',select_lib="&lib"'; put ',select_ds="MPE_ROW_LEVEL_SECURITY"'; put ',base_column="RLS_OPERATOR_NM"'; put ',selectbox_value="CONTAINS"'; put ',selectbox_order=1'; put ',ver_to_dttm=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..mpe_selectbox set'; put 'selectbox_rk=%mf_increment(rk)'; put ',ver_from_dttm=0'; put ',select_lib="&lib"'; put ',select_ds="MPE_EXCEL_CONFIG"'; put ',base_column="XL_RULE"'; put ',selectbox_value="FORMULA"'; put ',selectbox_order=1'; put ',ver_to_dttm=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..mpe_selectbox set'; put 'selectbox_rk=%mf_increment(rk)'; put ',ver_from_dttm=0'; put ',select_lib="&lib"'; put ',select_ds="MPE_ROW_LEVEL_SECURITY"'; put ',base_column="RLS_ACTIVE"'; put ',selectbox_value="1"'; put ',selectbox_order=1'; put ',ver_to_dttm=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..mpe_selectbox set'; put 'selectbox_rk=%mf_increment(rk)'; put ',ver_from_dttm=0'; put ',select_lib="&lib"'; put ',select_ds="MPE_ROW_LEVEL_SECURITY"'; put ',base_column="RLS_ACTIVE"'; put ',selectbox_value="0"'; put ',selectbox_order=2'; put ',ver_to_dttm=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..mpe_selectbox set'; put 'selectbox_rk=%mf_increment(rk)'; put ',ver_from_dttm=0'; put ',select_lib="&lib"'; put ',select_ds="MPE_COLUMN_LEVEL_SECURITY"'; put ',base_column="CLS_ACTIVE"'; put ',selectbox_value="1"'; put ',selectbox_order=1'; put ',ver_to_dttm=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..mpe_selectbox set'; put 'selectbox_rk=%mf_increment(rk)'; put ',ver_from_dttm=0'; put ',select_lib="&lib"'; put ',select_ds="MPE_COLUMN_LEVEL_SECURITY"'; put ',base_column="CLS_ACTIVE"'; put ',selectbox_value="0"'; put ',selectbox_order=2'; put ',ver_to_dttm=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..mpe_selectbox set'; put 'selectbox_rk=%mf_increment(rk)'; put ',ver_from_dttm=0'; put ',select_lib="&lib"'; put ',select_ds="MPE_COLUMN_LEVEL_SECURITY"'; put ',base_column="CLS_SCOPE"'; put ',selectbox_value="EDIT"'; put ',selectbox_order=1'; put ',ver_to_dttm=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..mpe_selectbox set'; put 'selectbox_rk=%mf_increment(rk)'; put ',ver_from_dttm=0'; put ',select_lib="&lib"'; put ',select_ds="MPE_COLUMN_LEVEL_SECURITY"'; put ',base_column="CLS_SCOPE"'; put ',selectbox_value="VIEW"'; put ',selectbox_order=2'; put ',ver_to_dttm=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..mpe_selectbox set'; put 'selectbox_rk=%mf_increment(rk)'; put ',ver_from_dttm=0'; put ',select_lib="&lib"'; put ',select_ds="MPE_COLUMN_LEVEL_SECURITY"'; put ',base_column="CLS_SCOPE"'; put ',selectbox_value="ALL"'; put ',selectbox_order=3'; put ',ver_to_dttm=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..mpe_selectbox set'; put 'selectbox_rk=%mf_increment(rk)'; put ',ver_from_dttm=0'; put ',select_lib="&lib"'; put ',select_ds="MPE_COLUMN_LEVEL_SECURITY"'; put ',base_column="CLS_HIDE"'; put ',selectbox_value="0"'; put ',selectbox_order=1'; put ',ver_to_dttm=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..mpe_selectbox set'; put 'selectbox_rk=%mf_increment(rk)'; put ',ver_from_dttm=0'; put ',select_lib="&lib"'; put ',select_ds="MPE_COLUMN_LEVEL_SECURITY"'; put ',base_column="CLS_HIDE"'; put ',selectbox_value="1"'; put ',selectbox_order=2'; put ',ver_to_dttm=''31DEC5999:23:59:59''dt;'; put '/**'; put '* MPE_TABLES'; put '*/'; put 'insert into &lib..mpe_tables'; put 'set tx_from=0'; put ',tx_to=''31DEC5999:23:59:59''dt'; put ',libref="&lib"'; put ',dsn=''MPE_COLUMN_LEVEL_SECURITY'''; put ',num_of_approvals_required=1'; put ',loadtype=''TXTEMPORAL'''; put ',var_txfrom=''TX_FROM'''; put ',var_txto=''TX_TO'''; put ',buskey=''CLS_SCOPE CLS_GROUP CLS_LIBREF CLS_TABLE CLS_VARIABLE_NM'''; put ',notes=''Docs: https://docs.datacontroller.io/column-level-security'''; put ',post_edit_hook=''services/hooks/mpe_column_level_security_postedit'''; put ';'; put 'insert into &lib..mpe_tables'; put 'set tx_from=0'; put ',tx_to=''31DEC5999:23:59:59''dt'; put ',libref="&lib"'; put ',dsn=''MPE_XLMAP_INFO'''; put ',num_of_approvals_required=1'; put ',loadtype=''TXTEMPORAL'''; put ',var_txfrom=''TX_FROM'''; put ',var_txto=''TX_TO'''; put ',buskey=''XLMAP_ID'''; put ',notes=''Docs: https://docs.datacontroller.io/complex-excel-uploads'''; put ',post_edit_hook=''services/hooks/mpe_xlmap_info_postedit'''; put ';'; put 'insert into &lib..mpe_tables'; put 'set tx_from=0'; put ',tx_to=''31DEC5999:23:59:59''dt'; put ',libref="&lib"'; put ',dsn=''MPE_XLMAP_RULES'''; put ',num_of_approvals_required=1'; put ',loadtype=''TXTEMPORAL'''; put ',var_txfrom=''TX_FROM'''; put ',var_txto=''TX_TO'''; put ',buskey=''XLMAP_ID XLMAP_RANGE_ID'''; put ',notes=''Docs: https://docs.datacontroller.io/complex-excel-uploads'''; put ',post_edit_hook=''services/hooks/mpe_xlmap_rules_postedit'''; put ';'; put 'insert into &lib..mpe_tables'; put 'set tx_from=0'; put ',tx_to=''31DEC5999:23:59:59''dt'; put ',libref="&lib"'; put ',dsn=''MPE_XLMAP_DATA'''; put ',num_of_approvals_required=1'; put ',loadtype=''UPDATE'''; put ',buskey=''LOAD_REF XLMAP_ID XLMAP_RANGE_ID ROW_NO COL_NO'''; put ',notes=''Docs: https://docs.datacontroller.io/complex-excel-uploads'''; put ';'; put 'insert into &lib..mpe_tables'; put 'set tx_from=0'; put ',tx_to=''31DEC5999:23:59:59''dt'; put ',libref="&lib"'; put ',dsn=''MPE_LOCKANYTABLE'''; put ',num_of_approvals_required=1'; put ',loadtype=''UPDATE'''; put ',buskey=''LOCK_LIB LOCK_DS'''; put ',notes=''This table may be edited when a process failed and left a lock'''; put ';'; put 'insert into &lib..mpe_tables'; put 'set tx_from=0'; put ',tx_to=''31DEC5999:23:59:59''dt'; put ',libref="&lib"'; put ',dsn=''MPE_TABLES'''; put ',num_of_approvals_required=1'; put ',loadtype=''TXTEMPORAL'''; put ',buskey=''LIBREF DSN'''; put ',var_txfrom=''TX_FROM'''; put ',var_txto=''TX_TO'''; put ',notes=''This entry allows the MP Editor to edit itself!'''; put ',post_edit_hook=''services/hooks/mpe_tables_postedit'''; put ';'; put 'insert into &lib..mpe_tables'; put 'set tx_from=0'; put ',tx_to=''31DEC5999:23:59:59''dt'; put ',libref="&lib"'; put ',dsn=''MPE_SECURITY'''; put ',num_of_approvals_required=1'; put ',loadtype=''TXTEMPORAL'''; put ',buskey=''LIBREF DSN ACCESS_LEVEL SAS_GROUP'''; put ',var_txfrom=''TX_FROM'''; put ',var_txto=''TX_TO'''; put ',notes=''Determines which groups can view/edit/approve which tables'''; put ',post_edit_hook=''services/hooks/mpe_security_postedit'''; put ';'; put 'insert into &lib..mpe_tables'; put 'set tx_from=0'; put ',tx_to=''31DEC5999:23:59:59''dt'; put ',libref="&lib"'; put ',dsn=''MPE_SELECTBOX'''; put ',num_of_approvals_required=1'; put ',loadtype=''TXTEMPORAL'''; put ',buskey=''SELECTBOX_RK'''; put ',var_txfrom=''VER_FROM_DTTM'''; put ',var_txto=''VER_TO_DTTM'''; put ',notes=''Can configure dropdowns for the front end'''; put ',rk_underlying=''SELECT_LIB SELECT_DS BASE_COLUMN SELECTBOX_VALUE'''; put ';'; put 'insert into &lib..mpe_tables'; put 'set tx_from=0'; put ',tx_to=''31DEC5999:23:59:59''dt'; put ',libref="&lib"'; put ',dsn=''MPE_X_TEST'''; put ',num_of_approvals_required=1'; put ',loadtype=''UPDATE'''; put ',buskey=''PRIMARY_KEY_FIELD'''; put ',notes=''Test table for controller'''; put ';'; put 'insert into &lib..mpe_tables'; put 'set tx_from=0'; put ',tx_to=''31DEC5999:23:59:59''dt'; put ',libref="&lib"'; put ',dsn=''MPE_EMAILS'''; put ',num_of_approvals_required=1'; put ',loadtype=''TXTEMPORAL'''; put ',buskey=''USER_NAME'''; put ',notes=''Primary Emails Table (backup is metadata)'''; put ',var_txfrom=''TX_FROM'''; put ',var_txto=''TX_TO'''; put ';'; put 'insert into &lib..mpe_tables'; put 'set tx_from=0'; put ',tx_to=''31DEC5999:23:59:59''dt'; put ',libref="&lib"'; put ',dsn=''MPE_CONFIG'''; put ',num_of_approvals_required=1'; put ',loadtype=''TXTEMPORAL'''; put ',buskey=''VAR_SCOPE VAR_NAME'''; put ',notes=''Configuration variables for Data Controller'''; put ',var_txfrom=''TX_FROM'''; put ',var_txto=''TX_TO'''; put ';'; put 'insert into &lib..mpe_tables'; put 'set tx_from=0'; put ',tx_to=''31DEC5999:23:59:59''dt'; put ',libref="&lib"'; put ',dsn=''MPE_ALERTS'''; put ',num_of_approvals_required=1'; put ',loadtype=''TXTEMPORAL'''; put ',buskey=''ALERT_EVENT ALERT_LIB ALERT_DS ALERT_USER'''; put ',notes=''Configuration for alert email events'''; put ',var_txfrom=''TX_FROM'''; put ',var_txto=''TX_TO'''; put ';'; put 'insert into &lib..mpe_tables'; put 'set tx_from=0'; put ',tx_to=''31DEC5999:23:59:59''dt'; put ',libref="&lib"'; put ',dsn=''MPE_GROUPS'''; put ',num_of_approvals_required=1'; put ',loadtype=''TXTEMPORAL'''; put ',buskey=''GROUP_NAME USER_NAME'''; put ',notes=''Configuration for additional groups within Data Controller'''; put ',var_txfrom=''TX_FROM'''; put ',var_txto=''TX_TO'''; put ';'; put 'insert into &lib..mpe_tables'; put 'set tx_from=0'; put ',tx_to=''31DEC5999:23:59:59''dt'; put ',libref="&lib"'; put ',dsn=''MPE_VALIDATIONS'''; put ',num_of_approvals_required=1'; put ',loadtype=''TXTEMPORAL'''; put ',buskey=''BASE_LIB BASE_DS BASE_COL RULE_TYPE'''; put ',notes=''Configuration of data quality rules in Editor component'''; put ',var_txfrom=''TX_FROM'''; put ',var_txto=''TX_TO'''; put ',post_edit_hook=''services/hooks/mpe_validations_postedit'''; put ';'; put 'insert into &lib..mpe_tables'; put 'set tx_from=0'; put ',tx_to=''31DEC5999:23:59:59''dt'; put ',libref="&lib"'; put ',dsn=''MPE_DATADICTIONARY'''; put ',num_of_approvals_required=1'; put ',loadtype=''TXTEMPORAL'''; put ',buskey=''DD_TYPE DD_SOURCE'''; put ',notes=''Configuration of data dictionary'''; put ',var_txfrom=''TX_FROM'''; put ',var_txto=''TX_TO'''; put ';'; put 'insert into &lib..mpe_tables'; put 'set tx_from=0'; put ',tx_to=''31DEC5999:23:59:59''dt'; put ',libref="&lib"'; put ',dsn=''MPE_EXCEL_CONFIG'''; put ',num_of_approvals_required=1'; put ',loadtype=''TXTEMPORAL'''; put ',buskey=''XL_LIBREF XL_TABLE XL_COLUMN'''; put ',notes=''Configuration of the excel import rules'''; put ',var_txfrom=''TX_FROM'''; put ',var_txto=''TX_TO'''; put ';'; put 'insert into &lib..mpe_tables'; put 'set tx_from=0'; put ',tx_to=''31DEC5999:23:59:59''dt'; put ',libref="&lib"'; put ',dsn=''MPE_ROW_LEVEL_SECURITY'''; put ',num_of_approvals_required=1'; put ',loadtype=''TXTEMPORAL'''; put ',buskey=''RLS_RK'''; put ',notes=''Configuration of Row Level Security'''; put ',var_txfrom=''TX_FROM'''; put ',var_txto=''TX_TO'''; put ',rk_underlying=''RLS_SCOPE RLS_GROUP RLS_LIBREF RLS_TABLE RLS_GROUP_LOGIC '''; put '!!''RLS_SUBGROUP_LOGIC RLS_SUBGROUP_ID RLS_VARIABLE_NM RLS_OPERATOR_NM '''; put '!!''RLS_RAW_VALUE '''; put ',post_edit_hook=''services/hooks/mpe_row_level_security_postedit'''; put ';'; put 'insert into &lib..mpe_tables'; put 'set tx_from=0'; put ',tx_to=''31DEC5999:23:59:59''dt'; put ',libref="&lib"'; put ',dsn=''MPE_X_CATALOG-FC'''; put ',num_of_approvals_required=1'; put ',loadtype=''FORMAT_CAT'''; put ',buskey=''TYPE FMTNAME FMTROW'''; put ',notes=''Sample Format Catalog'''; put ';'; put '/* mpe_validations */'; put 'insert into &lib..MPE_VALIDATIONS set'; put 'tx_from=0'; put ',base_lib="&lib"'; put ',base_ds="MPE_COLUMN_LEVEL_SECURITY"'; put ',base_col="CLS_SCOPE"'; put ',rule_type=''CASE'''; put ',rule_value=''UPCASE'''; put ',rule_active=1'; put ',tx_to=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..MPE_VALIDATIONS set'; put 'tx_from=0'; put ',base_lib="&lib"'; put ',base_ds="MPE_COLUMN_LEVEL_SECURITY"'; put ',base_col="CLS_LIBREF"'; put ',rule_type=''CASE'''; put ',rule_value=''UPCASE'''; put ',rule_active=1'; put ',tx_to=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..MPE_VALIDATIONS set'; put 'tx_from=0'; put ',base_lib="&lib"'; put ',base_ds="MPE_COLUMN_LEVEL_SECURITY"'; put ',base_col="CLS_LIBREF"'; put ',rule_type=''SOFTSELECT_HOOK'''; put ',rule_value="services/validations/libraries_all"'; put ',rule_active=1'; put ',tx_to=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..MPE_VALIDATIONS set'; put 'tx_from=0'; put ',base_lib="&lib"'; put ',base_ds="MPE_COLUMN_LEVEL_SECURITY"'; put ',base_col="CLS_TABLE"'; put ',rule_type=''CASE'''; put ',rule_value=''UPCASE'''; put ',rule_active=1'; put ',tx_to=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..MPE_VALIDATIONS set'; put 'tx_from=0'; put ',base_lib="&lib"'; put ',base_ds="MPE_COLUMN_LEVEL_SECURITY"'; put ',base_col="CLS_TABLE"'; put ',rule_type=''SOFTSELECT_HOOK'''; put ',rule_value="services/validations/tables_all"'; put ',rule_active=1'; put ',tx_to=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..MPE_VALIDATIONS set'; put 'tx_from=0'; put ',base_lib="&lib"'; put ',base_ds="MPE_COLUMN_LEVEL_SECURITY"'; put ',base_col="CLS_VARIABLE_NM"'; put ',rule_type=''CASE'''; put ',rule_value=''UPCASE'''; put ',rule_active=1'; put ',tx_to=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..MPE_VALIDATIONS set'; put 'tx_from=0'; put ',base_lib="&lib"'; put ',base_ds="MPE_COLUMN_LEVEL_SECURITY"'; put ',base_col="CLS_VARIABLE_NM"'; put ',rule_type=''SOFTSELECT_HOOK'''; put ',rule_value="services/validations/columns_in_libds"'; put ',rule_active=1'; put ',tx_to=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..MPE_VALIDATIONS set'; put 'tx_from=0'; put ',base_lib="&lib"'; put ',base_ds="MPE_COLUMN_LEVEL_SECURITY"'; put ',base_col="CLS_ACTIVE"'; put ',rule_type=''MAXVAL'''; put ',rule_value=''1'''; put ',rule_active=1'; put ',tx_to=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..MPE_VALIDATIONS set'; put 'tx_from=0'; put ',base_lib="&lib"'; put ',base_ds="MPE_COLUMN_LEVEL_SECURITY"'; put ',base_col="CLS_HIDE"'; put ',rule_type=''MAXVAL'''; put ',rule_value=''1'''; put ',rule_active=1'; put ',tx_to=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..MPE_VALIDATIONS set'; put 'tx_from=0'; put ',base_lib="&lib"'; put ',base_ds="MPE_COLUMN_LEVEL_SECURITY"'; put ',base_col="CLS_GROUP"'; put ',rule_type=''SOFTSELECT_HOOK'''; put ',rule_value="services/validations/sas_groups"'; put ',rule_active=1'; put ',tx_to=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..MPE_VALIDATIONS set'; put 'tx_from=0'; put ',base_lib="&lib"'; put ',base_ds="MPE_ALERTS"'; put ',base_col="ALERT_LIB"'; put ',rule_type=''HARDSELECT_HOOK'''; put ',rule_value="services/validations/mpe_alerts.alert_lib"'; put ',rule_active=1'; put ',tx_to=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..MPE_VALIDATIONS set'; put 'tx_from=0'; put ',base_lib="&lib"'; put ',base_ds="MPE_XLMAP_INFO"'; put ',base_col="XLMAP_ID"'; put ',rule_type=''CASE'''; put ',rule_value=''UPCASE'''; put ',rule_active=1'; put ',tx_to=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..MPE_VALIDATIONS set'; put 'tx_from=0'; put ',base_lib="&lib"'; put ',base_ds="MPE_XLMAP_RULES"'; put ',base_col="XLMAP_ID"'; put ',rule_type=''CASE'''; put ',rule_value=''UPCASE'''; put ',rule_active=1'; put ',tx_to=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..MPE_VALIDATIONS set'; put 'tx_from=0'; put ',base_lib="&lib"'; put ',base_ds="MPE_TABLES"'; put ',base_col="LIBREF"'; put ',rule_type=''CASE'''; put ',rule_value=''UPCASE'''; put ',rule_active=1'; put ',tx_to=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..MPE_VALIDATIONS set'; put 'tx_from=0'; put ',base_lib="&lib"'; put ',base_ds="MPE_TABLES"'; put ',base_col="DSN"'; put ',rule_type=''CASE'''; put ',rule_value=''UPCASE'''; put ',rule_active=1'; put ',tx_to=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..MPE_VALIDATIONS set'; put 'tx_from=0'; put ',base_lib="&lib"'; put ',base_ds="MPE_TABLES"'; put ',base_col="LIBREF"'; put ',rule_type=''NOTNULL'''; put ',rule_value='' '''; put ',rule_active=1'; put ',tx_to=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..MPE_VALIDATIONS set'; put 'tx_from=0'; put ',base_lib="&lib"'; put ',base_ds="MPE_TABLES"'; put ',base_col="DSN"'; put ',rule_type=''NOTNULL'''; put ',rule_value='' '''; put ',rule_active=1'; put ',tx_to=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..MPE_VALIDATIONS set'; put 'tx_from=0'; put ',base_lib="&lib"'; put ',base_ds="MPE_TABLES"'; put ',base_col="NUM_OF_APPROVALS_REQUIRED"'; put ',rule_type=''MINVAL'''; put ',rule_value=''1'''; put ',rule_active=1'; put ',tx_to=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..MPE_VALIDATIONS set'; put 'tx_from=0'; put ',base_lib="&lib"'; put ',base_ds="MPE_TABLES"'; put ',base_col="BUSKEY"'; put ',rule_type=''CASE'''; put ',rule_value=''UPCASE'''; put ',rule_active=1'; put ',tx_to=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..MPE_VALIDATIONS set'; put 'tx_from=0'; put ',base_lib="&lib"'; put ',base_ds="MPE_TABLES"'; put ',base_col="BUSKEY"'; put ',rule_type=''NOTNULL'''; put ',rule_value=" "'; put ',rule_active=1'; put ',tx_to=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..MPE_VALIDATIONS set'; put 'tx_from=0'; put ',base_lib="&lib"'; put ',base_ds="MPE_TABLES"'; put ',base_col="VAR_TXFROM"'; put ',rule_type=''CASE'''; put ',rule_value=''UPCASE'''; put ',rule_active=1'; put ',tx_to=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..MPE_VALIDATIONS set'; put 'tx_from=0'; put ',base_lib="&lib"'; put ',base_ds="MPE_TABLES"'; put ',base_col="VAR_TXTO"'; put ',rule_type=''CASE'''; put ',rule_value=''UPCASE'''; put ',rule_active=1'; put ',tx_to=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..MPE_VALIDATIONS set'; put 'tx_from=0'; put ',base_lib="&lib"'; put ',base_ds="MPE_TABLES"'; put ',base_col="VAR_BUSFROM"'; put ',rule_type=''CASE'''; put ',rule_value=''UPCASE'''; put ',rule_active=1'; put ',tx_to=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..MPE_VALIDATIONS set'; put 'tx_from=0'; put ',base_lib="&lib"'; put ',base_ds="MPE_TABLES"'; put ',base_col="VAR_BUSTO"'; put ',rule_type=''CASE'''; put ',rule_value=''UPCASE'''; put ',rule_active=1'; put ',tx_to=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..MPE_VALIDATIONS set'; put 'tx_from=0'; put ',base_lib="&lib"'; put ',base_ds="MPE_SECURITY"'; put ',base_col="LIBREF"'; put ',rule_type=''CASE'''; put ',rule_value="UPCASE"'; put ',rule_active=1'; put ',tx_to=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..MPE_VALIDATIONS set'; put 'tx_from=0'; put ',base_lib="&lib"'; put ',base_ds="MPE_TABLES"'; put ',base_col="VAR_PROCESSED"'; put ',rule_type=''CASE'''; put ',rule_value=''UPCASE'''; put ',rule_active=1'; put ',tx_to=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..MPE_VALIDATIONS set'; put 'tx_from=0'; put ',base_lib="&lib"'; put ',base_ds="MPE_SECURITY"'; put ',base_col="LIBREF"'; put ',rule_type=''HARDSELECT'''; put ',rule_value="&lib..MPE_TABLES.LIBREF"'; put ',rule_active=1'; put ',tx_to=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..MPE_VALIDATIONS set'; put 'tx_from=0'; put ',base_lib="&lib"'; put ',base_ds="MPE_SECURITY"'; put ',base_col="DSN"'; put ',rule_type=''CASE'''; put ',rule_value="UPCASE"'; put ',rule_active=1'; put ',tx_to=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..MPE_VALIDATIONS set'; put 'tx_from=0'; put ',base_lib="&lib"'; put ',base_ds="MPE_SECURITY"'; put ',base_col="DSN"'; put ',rule_type=''SOFTSELECT'''; put ',rule_value="&lib..MPE_TABLES.DSN"'; put ',rule_active=1'; put ',tx_to=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..MPE_VALIDATIONS set'; put 'tx_from=0'; put ',base_lib="&lib"'; put ',base_ds="MPE_SECURITY"'; put ',base_col="SAS_GROUP"'; put ',rule_type=''SOFTSELECT_HOOK'''; put ',rule_value="services/validations/sas_groups"'; put ',rule_active=1'; put ',tx_to=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..MPE_VALIDATIONS set'; put 'tx_from=0'; put ',base_lib="&lib"'; put ',base_ds="MPE_VALIDATIONS"'; put ',base_col="BASE_LIB"'; put ',rule_type=''SOFTSELECT_HOOK'''; put ',rule_value="services/validations/libraries_editable"'; put ',rule_active=1'; put ',tx_to=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..MPE_VALIDATIONS set'; put 'tx_from=0'; put ',base_lib="&lib"'; put ',base_ds="MPE_VALIDATIONS"'; put ',base_col="BASE_DS"'; put ',rule_type=''SOFTSELECT_HOOK'''; put ',rule_value="services/validations/tables_editable"'; put ',rule_active=1'; put ',tx_to=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..MPE_VALIDATIONS set'; put 'tx_from=0'; put ',base_lib="&lib"'; put ',base_ds="MPE_VALIDATIONS"'; put ',base_col="BASE_COL"'; put ',rule_type=''SOFTSELECT_HOOK'''; put ',rule_value="services/validations/columns_in_libds"'; put ',rule_active=1'; put ',tx_to=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..MPE_VALIDATIONS set'; put 'tx_from=0'; put ',base_lib="&lib"'; put ',base_ds="MPE_VALIDATIONS"'; put ',base_col="RULE_ACTIVE"'; put ',rule_type=''MINVAL'''; put ',rule_value="0"'; put ',rule_active=1'; put ',tx_to=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..MPE_VALIDATIONS set'; put 'tx_from=0'; put ',base_lib="&lib"'; put ',base_ds="MPE_VALIDATIONS"'; put ',base_col="RULE_ACTIVE"'; put ',rule_type=''MAXVAL'''; put ',rule_value="1"'; put ',rule_active=1'; put ',tx_to=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..MPE_VALIDATIONS set'; put 'tx_from=0'; put ',base_lib="&lib"'; put ',base_ds="MPE_EXCEL_CONFIG"'; put ',base_col="XL_LIBREF"'; put ',rule_type=''SOFTSELECT_HOOK'''; put ',rule_value="services/validations/libraries_editable"'; put ',rule_active=1'; put ',tx_to=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..MPE_VALIDATIONS set'; put 'tx_from=0'; put ',base_lib="&lib"'; put ',base_ds="MPE_EXCEL_CONFIG"'; put ',base_col="XL_TABLE"'; put ',rule_type=''SOFTSELECT_HOOK'''; put ',rule_value="services/validations/tables_editable"'; put ',rule_active=1'; put ',tx_to=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..MPE_VALIDATIONS set'; put 'tx_from=0'; put ',base_lib="&lib"'; put ',base_ds="MPE_EXCEL_CONFIG"'; put ',base_col="XL_COLUMN"'; put ',rule_type=''SOFTSELECT_HOOK'''; put ',rule_value="services/validations/columns_in_libds"'; put ',rule_active=1'; put ',tx_to=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..MPE_VALIDATIONS set'; put 'tx_from=0'; put ',base_lib="&lib"'; put ',base_ds="MPE_TABLES"'; put ',base_col="LIBREF"'; put ',rule_type=''SOFTSELECT_HOOK'''; put ',rule_value="services/validations/libraries_all"'; put ',rule_active=1'; put ',tx_to=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..MPE_VALIDATIONS set'; put 'tx_from=0'; put ',base_lib="&lib"'; put ',base_ds="MPE_TABLES"'; put ',base_col="DSN"'; put ',rule_type=''SOFTSELECT_HOOK'''; put ',rule_value="services/validations/mpe_tables.dsn"'; put ',rule_active=1'; put ',tx_to=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..MPE_VALIDATIONS set'; put 'tx_from=0'; put ',base_lib="&lib"'; put ',base_ds="MPE_TABLES"'; put ',base_col="VAR_TXFROM"'; put ',rule_type=''SOFTSELECT_HOOK'''; put ',rule_value="services/validations/columns_in_libds"'; put ',rule_active=1'; put ',tx_to=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..MPE_VALIDATIONS set'; put 'tx_from=0'; put ',base_lib="&lib"'; put ',base_ds="MPE_TABLES"'; put ',base_col="VAR_TXTO"'; put ',rule_type=''SOFTSELECT_HOOK'''; put ',rule_value="services/validations/columns_in_libds"'; put ',rule_active=1'; put ',tx_to=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..MPE_VALIDATIONS set'; put 'tx_from=0'; put ',base_lib="&lib"'; put ',base_ds="MPE_TABLES"'; put ',base_col="VAR_BUSFROM"'; put ',rule_type=''SOFTSELECT_HOOK'''; put ',rule_value="services/validations/columns_in_libds"'; put ',rule_active=1'; put ',tx_to=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..MPE_VALIDATIONS set'; put 'tx_from=0'; put ',base_lib="&lib"'; put ',base_ds="MPE_TABLES"'; put ',base_col="VAR_BUSTO"'; put ',rule_type=''SOFTSELECT_HOOK'''; put ',rule_value="services/validations/columns_in_libds"'; put ',rule_active=1'; put ',tx_to=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..MPE_VALIDATIONS set'; put 'tx_from=0'; put ',base_lib="&lib"'; put ',base_ds="MPE_TABLES"'; put ',base_col="VAR_PROCESSED"'; put ',rule_type=''SOFTSELECT_HOOK'''; put ',rule_value="services/validations/columns_in_libds"'; put ',rule_active=1'; put ',tx_to=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..MPE_VALIDATIONS set'; put 'tx_from=0'; put ',base_lib="&lib"'; put ',base_ds="MPE_SELECTBOX"'; put ',base_col="SELECT_LIB"'; put ',rule_type=''SOFTSELECT_HOOK'''; put ',rule_value="services/validations/libraries_editable"'; put ',rule_active=1'; put ',tx_to=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..MPE_VALIDATIONS set'; put 'tx_from=0'; put ',base_lib="&lib"'; put ',base_ds="MPE_SELECTBOX"'; put ',base_col="SELECT_DS"'; put ',rule_type=''SOFTSELECT_HOOK'''; put ',rule_value="services/validations/tables_editable"'; put ',rule_active=1'; put ',tx_to=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..MPE_VALIDATIONS set'; put 'tx_from=0'; put ',base_lib="&lib"'; put ',base_ds="MPE_SELECTBOX"'; put ',base_col="BASE_COLUMN"'; put ',rule_type=''SOFTSELECT_HOOK'''; put ',rule_value="services/validations/columns_in_libds"'; put ',rule_active=1'; put ',tx_to=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..MPE_VALIDATIONS set'; put 'tx_from=0'; put ',base_lib="&lib"'; put ',base_ds="MPE_ROW_LEVEL_SECURITY"'; put ',base_col="RLS_GROUP"'; put ',rule_type=''SOFTSELECT_HOOK'''; put ',rule_value="services/validations/sas_groups"'; put ',rule_active=1'; put ',tx_to=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..MPE_VALIDATIONS set'; put 'tx_from=0'; put ',base_lib="&lib"'; put ',base_ds="MPE_ROW_LEVEL_SECURITY"'; put ',base_col="RLS_LIBREF"'; put ',rule_type=''SOFTSELECT_HOOK'''; put ',rule_value="services/validations/libraries_all"'; put ',rule_active=1'; put ',tx_to=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..MPE_VALIDATIONS set'; put 'tx_from=0'; put ',base_lib="&lib"'; put ',base_ds="MPE_ROW_LEVEL_SECURITY"'; put ',base_col="RLS_TABLE"'; put ',rule_type=''SOFTSELECT_HOOK'''; put ',rule_value="services/validations/tables_all"'; put ',rule_active=1'; put ',tx_to=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..MPE_VALIDATIONS set'; put 'tx_from=0'; put ',base_lib="&lib"'; put ',base_ds="MPE_ROW_LEVEL_SECURITY"'; put ',base_col="RLS_SUBGROUP_ID"'; put ',rule_type=''MINVAL'''; put ',rule_value=''0'''; put ',rule_active=1'; put ',tx_to=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..MPE_VALIDATIONS set'; put 'tx_from=0'; put ',base_lib="&lib"'; put ',base_ds="MPE_ROW_LEVEL_SECURITY"'; put ',base_col="RLS_VARIABLE_NM"'; put ',rule_type=''SOFTSELECT_HOOK'''; put ',rule_value="services/validations/columns_in_libds"'; put ',rule_active=1'; put ',tx_to=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..MPE_VALIDATIONS set'; put 'tx_from=0'; put ',base_lib="&lib"'; put ',base_ds="MPE_X_TEST"'; put ',base_col="SOME_NUM"'; put ',rule_type=''HARDSELECT_HOOK'''; put ',rule_value="services/validations/mpe_x_test.some_num"'; put ',rule_active=1'; put ',tx_to=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..MPE_VALIDATIONS set'; put 'tx_from=0'; put ',base_lib="&lib"'; put ',base_ds="MPE_EXCEL_CONFIG"'; put ',base_col="XL_ACTIVE"'; put ',rule_type=''MINVAL'''; put ',rule_value=''0'''; put ',rule_active=1'; put ',tx_to=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..MPE_VALIDATIONS set'; put 'tx_from=0'; put ',base_lib="&lib"'; put ',base_ds="MPE_EXCEL_CONFIG"'; put ',base_col="XL_ACTIVE"'; put ',rule_type=''MAXVAL'''; put ',rule_value=''1'''; put ',rule_active=1'; put ',tx_to=''31DEC5999:23:59:59''dt;'; put 'insert into &lib..MPE_VALIDATIONS set'; put 'tx_from=0'; put ',base_lib="&lib"'; put ',base_ds="MPE_XLMAP_INFO"'; put ',base_col="XLMAP_ID"'; put ',rule_type=''SOFTSELECT'''; put ',rule_value="&lib..MPE_XLMAP_RULES.XLMAP_ID"'; put ',rule_active=1'; put ',tx_to=''31DEC5999:23:59:59''dt;'; put '/**'; put '* MPE_X_TEST'; put '*/'; put 'insert into &lib..mpe_x_test'; put 'set primary_key_field=0'; put ',some_char=''this is dummy data'''; put ',some_dropdown=''Option 1'''; put ',some_num=42'; put ',some_date=42'; put ',some_datetime=42'; put ',some_time=42'; put ',some_shortnum=3'; put ',some_bestnum=44;'; put 'insert into &lib..mpe_x_test'; put 'set primary_key_field=1'; put ',some_char=''more dummy data'''; put ',some_dropdown=''Option 2'''; put ',some_num=42'; put ',some_date=42'; put ',some_datetime=42'; put ',some_time=422'; put ',some_shortnum=3'; put ',some_bestnum=44;'; put 'insert into &lib..mpe_x_test'; put 'set primary_key_field=2'; put ',some_char=''even more dummy data'''; put ',some_dropdown=''Option 3'''; put ',some_num=42'; put ',some_date=42'; put ',some_datetime=42'; put ',some_time=142'; put ',some_shortnum=3'; put ',some_bestnum=44;'; put 'insert into &lib..mpe_x_test'; put 'set primary_key_field=3'; put ',some_char=repeat(''It was a dark and stormy night. The wind was blowing'''; put '!!'' a gale! The captain said to his mate - mate, tell us a tale. And'''; put '!!'' this, is the tale he told: '',3)'; put ',some_dropdown=''Option 2'''; put ',some_num=1613.001'; put ',some_date=423'; put ',some_datetime=423'; put ',some_time=44'; put ',some_shortnum=3'; put ',some_bestnum=44;'; put 'insert into &lib..mpe_x_test'; put 'set primary_key_field=4'; put ',some_char=''if you can fill the unforgiving minute'''; put ',some_dropdown=''Option 1'''; put ',some_num=1613.001123456'; put ',some_date=4231'; put ',some_datetime=423123123'; put ',some_time=412'; put ',some_shortnum=3'; put ',some_bestnum=44;'; put '%do x=10 %to 500;'; put 'insert into &lib..mpe_x_test'; put 'set primary_key_field=10&x'; put ',some_char="&x bottles of beer on the wall"'; put ',some_dropdown=''Option 1'''; put ',some_num=ranuni(0)'; put ',some_date=round(ranuni(0)*1000,1)'; put ',some_datetime=round(ranuni(0)*50000,1)'; put ',some_time=round(ranuni(0)*100,1)'; put ',some_shortnum=round(ranuni(0)*100,1)'; put ',some_bestnum=round(ranuni(0)*100,1);'; put '%end;'; put '/* https://support.sas.com/resources/papers/proceedings/proceedings/sugi27/p056-27.pdf */'; put 'proc format library=&lib..mpe_x_catalog;'; put 'value otdate'; put '.Z = ''Some Zs'''; put '.N = ''Some 9s'''; put 'other = [date9.]'; put ';'; put 'invalue disc'; put '''ABC'' = 0.20'; put '''DEF'' = 0.25'; put '''XYZ'' = 0.00'; put 'other = 0.00'; put ';'; put 'invalue indate'; put '''00000000'' = .Z'; put '''99999999'' = .N'; put 'other = [yymmdd8.]'; put ';'; put 'value age(multilabel)'; put '20 - 29 = ''20 - 29'''; put '30 - 39 = ''30 - 39'''; put '40 - 49 = ''40 - 49'''; put '50 - 59 = ''50 - 59'''; put '60 - high = ''60 +++'''; put '20 - 35 = ''20 - 35'''; put '36 - 55 = ''36 - 55'''; put '55 - high = ''55 +++'''; put ';'; put '/* https://libguides.library.kent.edu/SAS/UserDefinedFormats */'; put 'VALUE $GENDERLABEL'; put '"M" = "Male"'; put '"F" = "Female"'; put ';'; put 'VALUE LIKERT_SEVEN'; put '1 = "Strongly Disagree"'; put '2 = "Disagree"'; put '3 = "Slightly Disagree"'; put '4 = "Neither Agree nor Disagree"'; put '5 = "Slightly Agree"'; put '6 = "Agree"'; put '7 = "Strongly Agree"'; put ';'; put 'VALUE LIKERT7_ELEVEN'; put '1,2,3 = "Disagree"'; put '4 = "Neither Agree nor Disagree"'; put '5,6,7 = "Agree"'; put ';'; put 'VALUE LIKERT7_SISTERS'; put '1-3 = "Disagree"'; put '4 = "Neither Agree nor Disagree"'; put '5-7 = "Agree"'; put ';'; put 'VALUE INCOME'; put 'LOW -< 20000 = "Low"'; put '20000 -< 60000 = "Middle"'; put '60000 - HIGH = "High"'; put ';'; put 'VALUE RACE'; put '1 = "White"'; put '2 = "Black"'; put 'OTHER = "Other"'; put ';'; put 'VALUE GENDERCODE'; put '0 = ''Male'''; put '1 = ''Female'';'; put 'VALUE ATHLETECODE'; put '0 = ''Non-athlete'''; put '1 = ''Athlete'';'; put 'VALUE SMOKINGCODE'; put '0 = ''Nonsmoker'''; put '1 = ''Past smoker'''; put '2 = ''Current smoker'';'; put '/* https://documentation.sas.com/doc/en/pgmsascdc/v_017/proc/p1upn25lbfo6mkn1wncu4dyh9q91.htm */'; put 'value $state'; put '''Delaware''=''DE'''; put '''Florida''=''FL'''; put '''Ohio''=''OH'';'; put 'value MYfmt'; put '/* Format dates prior to 31DEC2011 using only a year. */'; put 'low-''31DEC2011''d=[year4.]'; put '/* Format 2012 dates using the month and year. */'; put '''01jan2012''d-''31DEC12''d=[monyy7.]'; put '/* Format dates 01JAN2013 and beyond using the day, month, and year. */'; put '''01JAN2013''d-high=[date9.]'; put '/* Catch missing values. */'; put 'other=''n/a'';'; put 'value newfmt .=''N/A'' other=[12.1];'; put '/* https://www.lexjansen.com/nesug/nesug08/cc/cc14.pdf */'; put 'value $genderml (multilabel)'; put '''1''=''Male'''; put '''2''=''Female'''; put '''1'',''2'','' ''=''Total people'';'; put 'value agemla (multilabel)'; put '1-4=''Preschool'''; put '1-18=''Children'''; put '19-120=''Adults'';'; put 'value agemlb (multilabel)'; put '19-120=''Adults'''; put '1-18=''Children'''; put '1-4=''Preschool'';'; put 'value agemlc (multilabel notsorted)'; put '19-120=''Adults'''; put '1-18=''Children'''; put '1-4=''Preschool'';'; put '%mend mpe_makedata;'; put '/** @cond */'; put '%macro mf_existfeature(feature'; put ')/*/STORE SOURCE*/;'; put '%let feature=%upcase(&feature);'; put '%local platform;'; put '%let platform=%mf_getplatform();'; put '%if &feature= %then %do;'; put '%put No feature was requested for detection;'; put '%end;'; put '%else %if &feature=COLCONSTRAINTS %then %do;'; put '%if "%substr(&sysver,1,1)"="4" or "%substr(&sysver,1,1)"="5" %then 0;'; put '%else 1;'; put '%end;'; put '%else %if &feature=PROCLUA %then %do;'; put '/* https://blogs.sas.com/content/sasdummy/2015/08/03/using-lua-within-your-sas-programs */'; put '%if &platform=SASVIYA %then 1;'; put '%else %if "&sysver"="9.2" or "&sysver"="9.3" %then 0;'; put '%else %if "&SYSVLONG" < "9.04.01M3" %then 0;'; put '%else 1;'; put '%end;'; put '%else %if &feature=DBMS_MEMTYPE %then %do;'; put '/* does dbms_memtype exist in dictionary.tables? */'; put '%if "%substr(&sysver,1,1)"="4" or "%substr(&sysver,1,1)"="5" %then 0;'; put '%else 1;'; put '%end;'; put '%else %if &feature=EXPORTXLS %then %do;'; put '/* is it possible to PROC EXPORT an excel file? */'; put '%if "%substr(&sysver,1,1)"="4" or "%substr(&sysver,1,1)"="5" %then 1;'; put '%else %if %sysfunc(sysprod(SAS/ACCESS Interface to PC Files)) = 1 %then 1;'; put '%else 0;'; put '%end;'; put '%else %do;'; put '-1'; put '%put &sysmacroname: &feature not found;'; put '%end;'; put '%mend mf_existfeature;'; put '/** @endcond */'; put '%macro mpe_makedatamodel(lib=);'; put '%if &syscc ne 0 %then %do;'; put '%put syscc=&syscc exiting &sysmacroname;'; put '%return;'; put '%end;'; put '%local notnull;'; put '%if %mf_existfeature(COLCONSTRAINTS)=1 %then %let notnull=not null;'; put '%put &=notnull;'; put 'proc sql;'; put 'create table &lib..mpe_alerts('; put 'tx_from num format=datetime19.3,'; put 'alert_event char(20),'; put 'alert_lib char(8),'; put 'alert_ds char(32),'; put 'alert_user char(100) ,'; put 'tx_to num ¬null format=datetime19.3'; put ');quit;'; put 'proc datasets lib=&lib noprint;'; put 'modify mpe_alerts;'; put 'index create'; put 'pk_mpealerts=(tx_from alert_event alert_lib alert_ds alert_user)'; put '/nomiss unique;'; put 'quit;'; put 'proc sql;'; put 'create table &lib..mpe_audit('; put 'load_ref char(36) label=''unique load reference'','; put 'libref char(8) label=''Library Reference (8 chars)'','; put 'dsn char(32) label=''Dataset Name (32 chars)'','; put 'key_hash char(32) label='; put '''MD5 Hash of primary key values (pipe seperated)'','; put 'tgtvar_nm char(32) label=''Target variable name (32 chars)'','; put 'move_type char(1) label=''Either (A)ppended, (D)eleted or (M)odified'','; put 'processed_dttm num format=E8601DT26.6 label=''Processed at timestamp'','; put 'is_pk num label=''Is Primary Key Field? (1/0)'','; put 'is_diff num label='; put '''Did value change? (1/0/-1). Always -1 for appends and deletes.'','; put 'tgtvar_type char(1) label=''Either (C)haracter or (N)umeric'','; put 'oldval_num num format=best32. label=''Old (numeric) value'','; put 'newval_num num format=best32. label=''New (numeric) value'','; put 'oldval_char char(32765) label=''Old (character) value'','; put 'newval_char char(32765) label=''New (character) value'''; put ');quit;'; put 'proc datasets lib=&lib noprint;'; put 'modify mpe_audit;'; put 'index create'; put 'pk_mpe_audit=(load_ref libref dsn key_hash tgtvar_nm)'; put '/nomiss unique;'; put 'quit;'; put 'proc sql;'; put 'create table &lib..mpe_column_level_security('; put 'tx_from num ¬null format=datetime19.3,'; put 'tx_to num ¬null format=datetime19.3,'; put 'CLS_SCOPE char(4) ¬null,'; put 'CLS_GROUP char(64) ¬null,'; put 'CLS_LIBREF char(8) ¬null,'; put 'CLS_TABLE char(32) ¬null,'; put 'CLS_VARIABLE_NM char(32) ¬null,'; put 'CLS_ACTIVE num ¬null,'; put 'CLS_HIDE num'; put ');quit;'; put 'proc datasets lib=&lib noprint;'; put 'modify mpe_column_level_security;'; put 'index create'; put 'pk_mpe_column_level_security='; put '(tx_to CLS_SCOPE CLS_GROUP CLS_LIBREF CLS_TABLE CLS_VARIABLE_NM)'; put '/nomiss unique;'; put 'quit;'; put 'proc sql;'; put 'create table &lib..mpe_config('; put 'tx_from num ¬null format=datetime19.3'; put ',tx_to num ¬null format=datetime19.3'; put ',var_scope varchar(10) ¬null'; put ',var_name varchar(32) ¬null'; put ',var_value varchar(5000)'; put ',var_active num'; put ',var_desc varchar(300)'; put ');quit;'; put 'proc datasets lib=&lib noprint;'; put 'modify mpe_config;'; put 'index create'; put 'pk_mpe_config=(tx_to var_scope var_name)'; put '/nomiss unique;'; put 'quit;'; put 'proc sql;'; put 'create table &lib..mpe_datacatalog_libs('; put 'TX_FROM num ¬null format=datetime19.3,'; put 'TX_TO num ¬null format=datetime19.3,'; put 'libref char(8) label=''Library Ref'','; put 'engine char(32) label=''Library Engine'','; put 'libname char(256) format=$256. label=''Library Name'','; put 'paths char(8192) label=''Library Paths'','; put 'perms char(500) label=''Library Permissions (if BASE)'','; put 'owners char(500) label=''Library Owners (if BASE)'','; put 'schemas char(500) label=''Library Schemas (if DB)'','; put 'libid char(17) label=''LibraryId'''; put ');quit;'; put 'proc datasets lib=&lib noprint;'; put 'modify mpe_datacatalog_libs;'; put 'index create'; put 'pk_mpe_datacatalog_libs=(libref tx_to)'; put '/nomiss unique;'; put 'quit;'; put 'proc sql;'; put 'create table &lib..mpe_datacatalog_TABS('; put 'TX_FROM num ¬null format=datetime19.3,'; put 'TX_TO num ¬null format=datetime19.3,'; put 'libref char(8) label=''Library Name'','; put 'dsn char(64) label=''Member Name'','; put 'memtype char(8) label=''Member Type'','; put 'dbms_memtype char(32) label=''DBMS Member Type'','; put 'memlabel char(512) label=''Data Set Label'','; put 'typemem char(8) label=''Data Set Type'','; put 'nvar num label=''Number of Variables'','; put 'compress char(8) label=''Compression Routine'','; put 'pk_fields char(512)'; put 'label=''Primary Key Fields (identified by being in a constraint that is both Unique and Not Null)'''; put ');quit;'; put 'proc datasets lib=&lib noprint;'; put 'modify mpe_datacatalog_TABS;'; put 'index create'; put 'pk_mpe_datacatalog_TABS=(libref dsn tx_to)'; put '/nomiss unique;'; put 'quit;'; put 'proc sql;'; put 'create table &lib..mpe_datacatalog_vars('; put 'TX_FROM num ¬null format=datetime19.3,'; put 'TX_TO num ¬null format=datetime19.3,'; put 'libref char(8) label=''Library Name'','; put 'dsn char(64) label=''Table Name'','; put 'name char(64) label=''Column Name'','; put 'memtype char(8) label=''Member Type'','; put 'type char(16) label=''Column Type'','; put 'length num label=''Column Length'','; put 'varnum num label=''Column Number in Table'','; put 'label char(512) label=''Column Label'','; put 'format char(49) label=''Column Format'','; put 'idxusage char(9) label=''Column Index Type'','; put 'notnull char(3) label=''Not NULL?'','; put 'pk_ind num label=''Primary Key Indicator (1=Primary Key field)'''; put ');quit;'; put 'proc datasets lib=&lib noprint;'; put 'modify mpe_datacatalog_vars;'; put 'index create'; put 'pk_mpe_datacatalog_vars=(libref dsn name tx_to)'; put '/nomiss unique;'; put 'quit;'; put 'proc sql;'; put 'create table &lib..mpe_datastatus_libs('; put 'TX_FROM num ¬null format=datetime19.3,'; put 'TX_TO num ¬null format=datetime19.3,'; put 'libref char(8) label=''Library Name'','; put 'libsize num format=SIZEKMG. label=''Size of library'','; put 'table_cnt num label=''Number of Tables'''; put ');quit;'; put 'proc datasets lib=&lib noprint;'; put 'modify mpe_datastatus_libs;'; put 'index create'; put 'pk_mpe_datastatus_libs=(libref tx_to)'; put '/nomiss unique;'; put 'quit;'; put 'proc sql;'; put 'create table &lib..mpe_datastatus_tabs('; put 'TX_FROM num ¬null format=datetime19.3,'; put 'TX_TO num ¬null format=datetime19.3,'; put 'libref char(8) label=''Library Name'','; put 'dsn char(64) label=''Member Name'','; put 'filesize num format=SIZEKMG. label=''Size of file'','; put 'crdate num format=DATETIME. informat=DATETIME. label=''Date Created'','; put 'modate num format=DATETIME. informat=DATETIME. label=''Date Modified'','; put 'nobs num label=''Number of Physical (Actual, inc. deleted) Observations'''; put ');quit;'; put 'proc datasets lib=&lib noprint;'; put 'modify mpe_datastatus_tabs;'; put 'index create'; put 'pk_mpe_datastatus_tabs=(libref dsn tx_to)'; put '/nomiss unique;'; put 'quit;'; put 'proc sql;'; put 'create table &lib..mpe_datadictionary'; put '('; put 'TX_FROM num ¬null format=datetime19.3,'; put 'TX_TO num ¬null format=datetime19.3,'; put 'DD_TYPE char(16),'; put 'DD_SOURCE char(1024),'; put 'DD_SHORTDESC char(256),'; put 'DD_LONGDESC char(32767),'; put 'DD_OWNER char(128),'; put 'DD_RESPONSIBLE char(128),'; put 'DD_SENSITIVITY char(64)'; put ');quit;'; put 'proc datasets lib=&lib noprint;'; put 'modify mpe_datadictionary;'; put 'index create'; put 'pk_mpe_datadictionary=(tx_to dd_type dd_source)'; put '/nomiss unique;'; put 'quit;'; put 'proc sql;'; put 'create table &lib..mpe_dataloads('; put 'libref varchar(8) ¬null,'; put 'dsn varchar(32) ¬null,'; put 'etlsource varchar(100) ¬null,'; put 'loadtype varchar(20) ¬null,'; put 'changed_records int,'; put 'new_records int,'; put 'deleted_records int,'; put 'duration num,'; put 'user_nm varchar(50) ¬null,'; put 'processed_dttm num format=datetime19.3,'; put 'mac_ver varchar(5)'; put ');quit;'; put 'proc datasets lib=&lib noprint;'; put 'modify mpe_dataloads;'; put 'index create'; put 'pk_mpe_dataloads=(processed_dttm libref dsn etlsource)'; put '/nomiss unique;'; put 'quit;'; put 'proc sql;'; put 'create table &lib..mpe_emails('; put 'tx_from num ¬null format=datetime19.3,'; put 'tx_to num ¬null format=datetime19.3,'; put 'user_name char(50) ¬null,'; put 'user_displayname char(100),'; put 'user_email char(100) ¬null'; put ');quit;'; put 'proc datasets lib=&lib noprint;'; put 'modify mpe_emails;'; put 'index create'; put 'pk_mpe_emails=(tx_to user_name)'; put '/nomiss unique;'; put 'quit;'; put 'proc sql;'; put 'create table &lib..mpe_excel_config('; put 'tx_from num ¬null format=datetime19.3,'; put 'tx_to num ¬null format=datetime19.3,'; put 'xl_libref char(8),'; put 'xl_table char(32),'; put 'xl_column char(32),'; put 'xl_rule char(32),'; put 'xl_active num'; put ');quit;'; put 'proc datasets lib=&lib noprint;'; put 'modify mpe_excel_config;'; put 'index create'; put 'pk_mpe_excel_config=(tx_to xl_libref xl_table xl_column)'; put '/nomiss unique;'; put 'quit;'; put 'proc sql;'; put 'create table &lib..MPE_XLMAP_DATA('; put 'LOAD_REF char(32) ¬null,'; put 'XLMAP_ID char(32) ¬null,'; put 'XLMAP_RANGE_ID char(32) ¬null,'; put 'ROW_NO num ¬null,'; put 'COL_NO num ¬null,'; put 'VALUE_TXT char(4000)'; put ');quit;'; put 'proc datasets lib=&lib noprint;'; put 'modify MPE_XLMAP_DATA;'; put 'index create'; put 'pk_MPE_XLMAP_DATA=(load_ref xlmap_id xlmap_range_id row_no col_no)'; put '/nomiss unique;'; put 'quit;'; put 'proc sql;'; put 'create table &lib..mpe_xlmap_info('; put 'tx_from num ¬null,'; put 'tx_to num ¬null,'; put 'XLMAP_ID char(32) ¬null,'; put 'XLMAP_DESCRIPTION char(1000) ¬null,'; put 'XLMAP_TARGETLIBDS char(41) ¬null'; put ');quit;'; put 'proc datasets lib=&lib noprint;'; put 'modify mpe_xlmap_info;'; put 'index create'; put 'pk_mpe_xlmap_info=(tx_to xlmap_id)'; put '/nomiss unique;'; put 'quit;'; put 'proc sql;'; put 'create table &lib..mpe_xlmap_rules('; put 'tx_from num ¬null,'; put 'tx_to num ¬null,'; put 'XLMAP_ID char(32) ¬null,'; put 'XLMAP_RANGE_ID char(32) ¬null,'; put 'XLMAP_SHEET char(32) ¬null,'; put 'XLMAP_START char(1000) ¬null,'; put 'XLMAP_FINISH char(1000)'; put ');quit;'; put 'proc datasets lib=&lib noprint;'; put 'modify mpe_xlmap_rules;'; put 'index create'; put 'pk_mpe_xlmap_rules=(tx_to xlmap_id xlmap_range_id)'; put '/nomiss unique;'; put 'quit;'; put 'proc sql;'; put 'create table &lib..mpe_filteranytable('; put 'filter_rk num ¬null,'; put 'filter_hash char(32) ¬null,'; put 'filter_table char(41) ¬null,'; put 'processed_dttm num ¬null format=datetime19.'; put ');quit;'; put 'proc datasets lib=&lib noprint;'; put 'modify mpe_filteranytable;'; put 'index create filter_rk /nomiss unique;'; put 'quit;'; put 'proc sql;'; put 'create table &lib..mpe_filtersource('; put 'filter_hash char(32) ¬null,'; put 'filter_line num ¬null,'; put 'group_logic char(3) ¬null,'; put 'subgroup_logic char(3) ¬null,'; put 'subgroup_id num ¬null,'; put 'variable_nm varchar(32) ¬null,'; put 'operator_nm varchar(12) ¬null,'; put 'raw_value varchar(4000) ¬null,'; put 'processed_dttm num ¬null format=datetime19.'; put ');quit;'; put 'proc datasets lib=&lib noprint;'; put 'modify mpe_filtersource;'; put 'index create'; put 'pk_mpe_filtersource=(filter_hash filter_line)'; put '/nomiss unique;'; put 'quit;'; put 'proc sql;'; put 'create table &lib..mpe_groups('; put 'tx_from num ¬null format=datetime19.3,'; put 'tx_to num ¬null format=datetime19.3,'; put 'group_name char(100) ¬null,'; put 'user_name char(50) ¬null,'; put 'group_desc char(256)'; put ');quit;'; put 'proc datasets lib=&lib noprint;'; put 'modify mpe_groups;'; put 'index create'; put 'pk_mpe_groups=(tx_to group_name user_name)'; put '/nomiss unique;'; put 'quit;'; put 'proc sql;'; put 'create table &lib..mpe_lineage_cols'; put '('; put 'col_id char(32),'; put 'direction char(1),'; put 'sourcecoluri char(256),'; put 'map_type char(256),'; put 'map_transform char(256),'; put 'jobname char(256),'; put 'sourcetablename char(256),'; put 'sourcecolname char(256),'; put 'targettablename char(256),'; put 'targetcolname char(256),'; put 'targetcoluri char(256),'; put 'Derived_Rule char(500),'; put 'level int,'; put 'modified_dttm num format=datetime19.3,'; put 'modified_by char(64)'; put ');quit;'; put 'proc datasets lib=&lib noprint;'; put 'modify mpe_lineage_cols;'; put 'index create'; put 'pk_mpe_lineage_cols=(col_id direction sourcecoluri targetcoluri map_type map_transform)'; put '/nomiss unique;'; put 'quit;'; put 'proc sql;'; put 'create table &lib..MPE_LINEAGE_TABS'; put '('; put 'tx_from num ¬null format=datetime19.3,'; put 'jobid char(17),'; put 'srctableid char(17),'; put 'tgttableid char(17),'; put 'jobname char(128),'; put 'srctabletype char(16),'; put 'srctablename char(64),'; put 'srclibref char(8),'; put 'tgttabletype char(16),'; put 'tgttablename char(64),'; put 'tgtlibref char(8),'; put 'tx_to num ¬null format=datetime19.3'; put ');quit;'; put 'proc datasets lib=&lib noprint;'; put 'modify mpe_lineage_tabs;'; put 'index create'; put 'pk_mpe_lineage_tabs=(tx_to jobid srctableid tgttableid)'; put '/nomiss unique;'; put 'quit;'; put 'proc sql;'; put 'create table &lib..mpe_loads('; put 'csv_dir char(255),'; put 'user_nm char(50) ,'; put 'status char(15) ,'; put 'duration num ,'; put 'processed_dttm num format=datetime19.3,'; put 'reason_txt char(2048) ,'; put 'approvals char(64)'; put ');quit;'; put 'proc datasets lib=&lib noprint;'; put 'modify mpe_loads;'; put 'index create csv_dir /nomiss unique;'; put 'quit;'; put 'proc sql;'; put 'create table &lib..mpe_lockanytable('; put 'lock_lib varchar(8) ¬null ,'; put 'lock_ds varchar(32) ¬null,'; put 'lock_status_cd varchar(10) ¬null,'; put 'lock_user_nm varchar(100) ¬null ,'; put 'lock_ref varchar(200),'; put 'lock_pid varchar(10),'; put 'lock_start_dttm num format=E8601DT26.6,'; put 'lock_end_dttm num format=E8601DT26.6'; put ');quit;'; put 'proc datasets lib=&lib noprint;'; put 'modify mpe_lockanytable;'; put 'index create'; put 'pk_mpe_lockanytable=(lock_lib lock_ds)'; put '/nomiss unique;'; put 'quit;'; put 'proc sql;'; put 'create table &lib..mpe_maxkeyvalues('; put 'keytable varchar(41) label=''Base table in libref.dataset format'','; put 'keycolumn char(32) format=$32.'; put 'label=''The Surrogate / Retained key field containing the key values.'','; put 'max_key num label='; put '''Integer value representing current max RK or SK value in the KEYTABLE'','; put 'processed_dttm num format=E8601DT26.6'; put 'label=''Datetime this value was last updated'''; put ');quit;'; put 'proc datasets lib=&lib noprint;'; put 'modify mpe_maxkeyvalues;'; put 'index create keytable /nomiss unique;'; put 'quit;'; put '/* no PK defined as it is a transaction table */'; put 'proc sql;'; put 'create table &lib..mpe_requests('; put 'request_dttm num ¬null format=datetime19.,'; put 'request_user char(64) ¬null,'; put 'request_service char(64) ¬null,'; put 'request_params char(128)'; put ');'; put 'proc sql;'; put 'create table &lib..mpe_review('; put 'table_id varchar(32) ¬null,'; put 'reviewed_by_nm varchar(100) ¬null,'; put 'base_table varchar(41) ¬null,'; put 'review_status_id varchar(10) ¬null,'; put 'reviewed_on_dttm num ¬null format=datetime19.3,'; put 'review_reason_txt varchar(400)'; put ');quit;'; put 'proc datasets lib=&lib noprint;'; put 'modify mpe_review;'; put 'index create'; put 'pk_mpe_review=(table_id reviewed_by_nm)'; put '/nomiss unique;'; put 'quit;'; put 'proc sql;'; put 'create table &lib..mpe_row_level_security('; put 'tx_from num ¬null format=datetime19.3,'; put 'tx_to num ¬null format=datetime19.3,'; put 'RLS_RK num ¬null,'; put 'RLS_SCOPE char(8) ¬null,'; put 'RLS_GROUP char(128) ¬null,'; put 'RLS_LIBREF char(8) ¬null,'; put 'RLS_TABLE char(32) ¬null,'; put 'RLS_GROUP_LOGIC char(3) ¬null,'; put 'RLS_SUBGROUP_LOGIC char(3) ¬null,'; put 'RLS_SUBGROUP_ID num ¬null,'; put 'RLS_VARIABLE_NM varchar(32) ¬null,'; put 'RLS_OPERATOR_NM varchar(12) ¬null,'; put 'RLS_RAW_VALUE varchar(4000) ¬null,'; put 'RLS_ACTIVE num ¬null'; put ');quit;'; put 'proc datasets lib=&lib noprint;'; put 'modify mpe_row_level_security;'; put 'index create'; put 'pk_mpe_row_level_security=(tx_to RLS_RK)'; put '/nomiss unique;'; put 'quit;'; put 'proc sql;'; put 'create table &lib..mpe_security('; put 'tx_from num ¬null format=datetime19.3,'; put 'tx_to num ¬null format=datetime19.3,'; put 'libref char(8) ¬null,'; put 'dsn char(32) ¬null,'; put 'access_level char(10) ¬null,'; put 'sas_group char(100) ¬null'; put ');quit;'; put 'proc datasets lib=&lib noprint;'; put 'modify mpe_security;'; put 'index create'; put 'pk_mpe_security=(tx_to libref dsn access_level sas_group)'; put '/nomiss unique;'; put 'quit;'; put 'proc sql;'; put 'create table &lib..mpe_selectbox('; put 'ver_from_dttm num ¬null format=datetime19.3,/* timestamp for versioning*/'; put 'ver_to_dttm num ¬null format=datetime19.3, /* timestamp for versioning */'; put 'selectbox_rk num ¬null, /* surrogate key */'; put 'select_lib varchar(17) ¬null, /* libref (big enough for uri)*/'; put 'select_ds varchar(32) ¬null,'; put 'base_column varchar(36) ¬null, /* variable name against which to apply selectbox */'; put 'selectbox_value varchar(500) ¬null, /* selectbox value */'; put 'selectbox_order num , /* optional ordering (1 comes before 2) */'; put 'selectbox_type varchar(32) /* column type (blank for default, else'; put 'sas or js to indicate relevant system functions)*/'; put ');quit;'; put 'proc datasets lib=&lib noprint;'; put 'modify mpe_selectbox;'; put 'index create'; put 'pk_mpe_selectbox=(ver_to_dttm selectbox_rk)'; put '/nomiss unique;'; put 'quit;'; put 'proc sql;'; put 'create table &lib..mpe_signoffs('; put 'tech_from_dttm num ¬null format=datetime19.3,'; put 'tech_to_dttm num ¬null format=datetime19.3,'; put 'signoff_table varchar(50) ¬null,'; put 'signoff_section_rk num ¬null,'; put 'signoff_version_rk num ¬null,'; put 'signoff_name varchar(100) ¬null'; put ');quit;'; put 'proc datasets lib=&lib noprint;'; put 'modify mpe_signoffs;'; put 'index create'; put 'pk_mpe_signoffs=(tech_to_dttm signoff_table signoff_section_rk)'; put '/nomiss unique;'; put 'quit;'; put '/* mpe_submit */'; put 'proc sql;'; put 'create table &lib..mpe_submit('; put 'table_id varchar(32) ¬null,'; put 'submit_status_cd varchar(10) ¬null,'; put 'base_lib char(8) ¬null,'; put 'base_ds char(32) ¬null,'; put 'submitted_by_nm varchar(100) ¬null,'; put 'submitted_on_dttm num ¬null format=datetime19.3,'; put 'submitted_reason_txt varchar(400),'; put 'input_obs num,'; put 'input_vars num,'; put 'num_of_approvals_required num ¬null ,'; put 'num_of_approvals_remaining num ¬null ,'; put 'reviewed_by_nm char(100),'; put 'reviewed_on_dttm num'; put ');quit;'; put 'proc datasets lib=&lib noprint;'; put 'modify mpe_submit;'; put 'index create table_id /nomiss unique;'; put 'quit;'; put 'proc sql;'; put 'create table &lib..mpe_tables('; put 'tx_from num ¬null format=datetime19.3,'; put 'tx_to num ¬null format=datetime19.3,'; put 'libref char(8) ¬null,'; put 'dsn char(32) ¬null,'; put 'num_of_approvals_required int,'; put 'loadtype char(12) ,'; put 'buskey char(1000) ,'; put 'var_txfrom char(32) ,'; put 'var_txto char(32) ,'; put 'var_busfrom char(32) ,'; put 'var_busto char(32) ,'; put 'var_processed char(32) ,'; put 'close_vars varchar(500),'; put 'pre_edit_hook char(200),'; put 'post_edit_hook char(200),'; put 'pre_approve_hook char(200) ,'; put 'post_approve_hook char(200) ,'; put 'signoff_cols varchar(500),'; put 'signoff_hook varchar(200),'; put 'notes char(1000) ,'; put 'rk_underlying char(1000) ,'; put 'audit_libds char(41)'; put ');quit;'; put 'proc datasets lib=&lib noprint;'; put 'modify mpe_tables;'; put 'index create'; put 'pk_mpe_tables=(tx_to libref dsn)'; put '/nomiss unique;'; put 'quit;'; put 'proc sql;'; put 'create table &lib..mpe_users('; put 'user_id char(50) ¬null,'; put 'last_seen_dt num ¬null format=date9.,'; put 'registered_dt num ¬null format=date9.'; put ');quit;'; put 'proc datasets lib=&lib noprint;'; put 'modify mpe_users;'; put 'index create user_id /nomiss unique;'; put 'quit;'; put 'proc sql;'; put 'create table &lib..MPE_VALIDATIONS'; put '('; put 'TX_FROM num ¬null format=datetime19.3,'; put 'BASE_LIB varchar(8),'; put 'BASE_DS varchar(32),'; put 'BASE_COL varchar(32),'; put 'RULE_TYPE varchar(32),'; put 'RULE_VALUE varchar(128),'; put 'RULE_ACTIVE num ,'; put 'TX_TO num ¬null format=datetime19.3'; put ');quit;'; put 'proc datasets lib=&lib noprint;'; put 'modify mpe_validations;'; put 'index create'; put 'pk_mpe_validations=(tx_from base_lib base_ds base_col rule_type)'; put '/nomiss unique;'; put 'quit;'; put 'proc sql;'; put 'create table &lib..mpe_x_test('; put 'primary_key_field num ¬null,'; put 'some_char char(32767) ,'; put 'some_dropdown char(128),'; put 'some_num num ,'; put 'some_date num format=date9.,'; put 'some_datetime num format=datetime19. informat=ANYDTDTM19.,'; put 'some_time num format=time8.,'; put 'some_shortnum num length=4,'; put 'some_bestnum num format=best.'; put ');quit;'; put 'proc datasets lib=&lib noprint;'; put 'modify mpe_x_test;'; put 'index create primary_key_field /nomiss unique;'; put 'quit;'; put '%mend mpe_makedatamodel;'; put '%macro mf_mkdir(dir'; put ')/*/STORE SOURCE*/;'; put '%local lastchar child parent;'; put '%let lastchar = %substr(&dir, %length(&dir));'; put '%if (%bquote(&lastchar) eq %str(:)) %then %do;'; put '/* Cannot create drive mappings */'; put '%return;'; put '%end;'; put '%if (%bquote(&lastchar)=%str(/)) or (%bquote(&lastchar)=%str(\)) %then %do;'; put '/* last char is a slash */'; put '%if (%length(&dir) eq 1) %then %do;'; put '/* one single slash - root location is assumed to exist */'; put '%return;'; put '%end;'; put '%else %do;'; put '/* strip last slash */'; put '%let dir = %substr(&dir, 1, %length(&dir)-1);'; put '%end;'; put '%end;'; put '%if (%sysfunc(fileexist(%bquote(&dir))) = 0) %then %do;'; put '/* directory does not exist so prepare to create */'; put '/* first get the childmost directory */'; put '%let child = %scan(&dir, -1, %str(/\:));'; put '/*'; put 'If child name = path name then there are no parents to create. Else'; put 'they must be recursively scanned.'; put '*/'; put '%if (%length(&dir) gt %length(&child)) %then %do;'; put '%let parent = %substr(&dir, 1, %length(&dir)-%length(&child));'; put '%mf_mkdir(&parent)'; put '%end;'; put '/*'; put 'Now create the directory. Complain loudly of any errs.'; put '*/'; put '%let dname = %sysfunc(dcreate(&child, &parent));'; put '%if (%bquote(&dname) eq ) %then %do;'; put '%put %str(ERR)OR: could not create &parent + &child;'; put '%abort cancel;'; put '%end;'; put '%else %do;'; put '%put Directory created: &dir;'; put '%end;'; put '%end;'; put '/* exit quietly if directory did exist.*/'; put '%mend mf_mkdir;'; put '%macro mf_abort(mac=mf_abort.sas, msg=, iftrue=%str(1=1)'; put ')/des=''ungraceful abort'' /*STORE SOURCE*/;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%put NOTE: /// mf_abort macro executing //;'; put '%if %length(&mac)>0 %then %put NOTE- called by &mac;'; put '%put NOTE - &msg;'; put '%abort;'; put '%mend mf_abort;'; put '/** @endcond */'; put '%macro mf_verifymacvars('; put 'verifyVars /* list of macro variable NAMES */'; put ',makeUpcase=NO /* set to YES to make all the variable VALUES uppercase */'; put ',mAbort=SOFT'; put ')/*/STORE SOURCE*/;'; put '%local verifyIterator verifyVar abortmsg;'; put '%do verifyIterator=1 %to %sysfunc(countw(&verifyVars,%str( )));'; put '%let verifyVar=%qscan(&verifyVars,&verifyIterator,%str( ));'; put '%if not %symexist(&verifyvar) %then %do;'; put '%let abortmsg= Variable &verifyVar is MISSING;'; put '%goto exit_err;'; put '%end;'; put '%if %length(%trim(&&&verifyVar))=0 %then %do;'; put '%let abortmsg= Variable &verifyVar is EMPTY;'; put '%goto exit_err;'; put '%end;'; put '%if &makeupcase=YES %then %do;'; put '%let &verifyVar=%upcase(&&&verifyvar);'; put '%end;'; put '%end;'; put '%goto exit_success;'; put '%exit_err:'; put '%put &abortmsg;'; put '%mf_abort(iftrue=(&mabort ne SOFT),'; put 'mac=mf_verifymacvars,'; put 'msg=%str(&abortmsg)'; put ')'; put '0'; put '%return;'; put '%exit_success:'; put '1'; put '%mend mf_verifymacvars;'; put '%macro mm_createdocument('; put 'tree=/User Folders/sasdemo'; put ',name=myNote'; put ',desc=Created by &sysmacroname'; put ',textrole='; put ',frefin=mm_in'; put ',frefout=mm_out'; put ',mDebug=1'; put ');'; put '%local mD;'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing &sysmacroname..sas;'; put '%&mD.put _local_;'; put '%mp_abort(iftrue= (%mf_verifymacvars(tree name)=0)'; put ',mac=&sysmacroname'; put ',msg=%str(Empty inputs: tree name)'; put ')'; put '/**'; put '* check tree exists'; put '*/'; put 'data _null_;'; put 'length type uri $256;'; put 'rc=metadata_pathobj("","&tree","Folder",type,uri);'; put 'call symputx(''type'',type,''l'');'; put 'call symputx(''treeuri'',uri,''l'');'; put 'run;'; put '%mp_abort('; put 'iftrue= (&type ne Tree)'; put ',mac=mm_createdocument.sas'; put ',msg=Tree &tree does not exist!'; put ')'; put '/**'; put '* Check object does not exist already'; put '*/'; put 'data _null_;'; put 'length type uri $256;'; put 'rc=metadata_pathobj("","&tree/&name","Note",type,uri);'; put 'call symputx(''type'',type,''l'');'; put 'call symputx(''docuri'',uri,''l'');'; put 'putlog (_all_)(=);'; put 'run;'; put '%if &type = Document %then %do;'; put '%put Document &name already exists in &tree!;'; put '%return;'; put '%end;'; put '/**'; put '* Now we can create the document'; put '*/'; put 'filename &frefin temp;'; put '/* write header XML */'; put 'data _null_;'; put 'file &frefin;'; put 'name=quote("&name");'; put 'desc=quote("&desc");'; put 'textrole=quote("&textrole");'; put 'treeuri=quote("&treeuri");'; put 'put "$METAREPOSITORY"/'; put '''"/'; put '" "/'; put ''' '' /'; put '''''/'; put '/*URI="Document for public note" */'; put '""/'; put '"SAS"/'; put '"268435456";'; put 'run;'; put 'filename &frefout temp;'; put 'proc metadata in= &frefin out=&frefout verbose;'; put 'run;'; put '%if &mdebug=1 %then %do;'; put '/* write the response to the log for debugging */'; put 'data _null_;'; put 'infile &frefout lrecl=1048576;'; put 'input;'; put 'put _infile_;'; put 'run;'; put '%end;'; put '%mend mm_createdocument;'; put '%macro mm_createfolder(path=,mDebug=0);'; put '%put &sysmacroname: execution started for &path;'; put '%local dbg errorcheck;'; put '%if &mDebug=0 %then %let dbg=*;'; put '%local parentFolderObjId child errorcheck paths;'; put '%let paths=0;'; put '%let errorcheck=1;'; put '%if &syscc ge 4 %then %do;'; put '%put SYSCC=&syscc - this macro requires a clean session;'; put '%return;'; put '%end;'; put 'data _null_;'; put 'length objId parentId objType parent child $200'; put 'folderPath $1000;'; put 'call missing (of _all_);'; put 'folderPath = "%trim(&path)";'; put '* remove any trailing slash ;'; put 'if ( substr(folderPath,length(folderPath),1) = ''/'' ) then'; put 'folderPath=substr(folderPath,1,length(folderPath)-1);'; put '* name must not be blank;'; put 'if ( folderPath = '''' ) then do;'; put 'put ''ERR'' +(-1) "OR: &sysmacroname PATH parameter value must be non-blank";'; put 'end;'; put '* must have a starting slash ;'; put 'if ( substr(folderPath,1,1) ne ''/'' ) then do;'; put 'put ''ERR'' +(-1) "OR: &sysmacroname PATH param value must have starting slash";'; put 'stop;'; put 'end;'; put '* check if folder already exists ;'; put 'rc=metadata_pathobj('''',cats(folderPath,"(Folder)"),"",objType,objId);'; put 'if rc ge 1 then do;'; put 'put "NOTE: Folder " folderPath " already exists!";'; put 'stop;'; put 'end;'; put '* do not create a root (one level) folder ;'; put 'if countc(folderPath,''/'')=1 then do;'; put 'put ''ERR'' +(-1) "OR: &sysmacroname will not create a new ROOT folder";'; put 'stop;'; put 'end;'; put '* check that root folder exists ;'; put 'root=cats(''/'',scan(folderpath,1,''/''),"(Folder)");'; put 'if metadata_pathobj('''',root,"",objType,parentId)<1 then do;'; put 'put ''ERR'' +(-1) "OR: " root " does not exist!";'; put 'stop;'; put 'end;'; put '* check that parent folder exists ;'; put 'child=scan(folderPath,-1,''/'');'; put 'parent=substr(folderpath,1,length(folderpath)-length(child)-1);'; put 'rc=metadata_pathobj('''',cats(parent,"(Folder)"),"",objType,parentId);'; put 'if rc<1 then do;'; put 'putlog ''The following folders will be created:'';'; put '/* folder does not exist - so start from top and work down */'; put 'length newpath $1000;'; put 'paths=0;'; put 'do x=2 to countw(folderpath,''/'');'; put 'newpath='''';'; put 'do i=1 to x;'; put 'newpath=cats(newpath,''/'',scan(folderpath,i,''/''));'; put 'end;'; put 'rc=metadata_pathobj('''',cats(newpath,"(Folder)"),"",objType,parentId);'; put 'if rc<1 then do;'; put 'paths+1;'; put 'call symputx(cats(''path'',paths),newpath);'; put 'putlog newpath;'; put 'end;'; put 'call symputx(''paths'',paths);'; put 'end;'; put 'end;'; put 'else putlog "parent " parent " exists";'; put 'call symputx(''parentFolderObjId'',parentId,''l'');'; put 'call symputx(''child'',child,''l'');'; put 'call symputx(''errorcheck'',0,''l'');'; put '&dbg put (_all_)(=);'; put 'run;'; put '%if &errorcheck=1 or &syscc ge 4 %then %return;'; put '%if &paths>0 %then %do x=1 %to &paths;'; put '%put executing recursive call for &&path&x;'; put '%mm_createfolder(path=&&path&x)'; put '%end;'; put '%else %do;'; put 'filename __newdir temp;'; put 'options noquotelenmax;'; put '%local inmeta;'; put '%put creating: &path;'; put '%let inmeta=$METAREPOSITORY'; put ''; put 'SAS268435456'; put ';'; put 'proc metadata in="&inmeta" out=__newdir verbose;'; put 'run ;'; put '/* check it was successful */'; put 'data _null_;'; put 'length objId parentId objType parent child $200 ;'; put 'call missing (of _all_);'; put 'rc=metadata_pathobj('''',cats("&path","(Folder)"),"",objType,objId);'; put 'if rc ge 1 then do;'; put 'putlog "SUCCCESS! &path created.";'; put 'end;'; put 'else do;'; put 'putlog ''ERR'' +(-1) "OR: unsuccessful attempt to create &path";'; put 'call symputx(''syscc'',8);'; put 'end;'; put 'run;'; put '/* write the response to the log for debugging */'; put '%if &mDebug ne 0 %then %do;'; put 'data _null_;'; put 'infile __newdir lrecl=32767;'; put 'input;'; put 'put _infile_;'; put 'run;'; put '%end;'; put 'filename __newdir clear;'; put '%end;'; put '%put &sysmacroname: execution finished for &path;'; put '%mend mm_createfolder;'; put '%macro mm_createlibrary('; put 'libname=My New Library'; put ',libref=mynewlib'; put ',libdesc=Created automatically using the mm_createlibrary macro'; put ',engine=BASE'; put ',tree=/User Folders/sasdemo'; put ',servercontext=SASApp'; put ',directory=/tmp/somelib'; put ',IsPreassigned=0'; put ',mDebug=0'; put ',frefin=mm_in'; put ',frefout=mm_out'; put ')/*/STORE SOURCE*/;'; put '%local mD;'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing &sysmacroname..sas;'; put '%&mD.put _local_;'; put '%let libref=%upcase(&libref);'; put '/**'; put '* Check Library does not exist already with this libname'; put '*/'; put 'data _null_;'; put 'length type uri $256;'; put 'rc=metadata_resolve("omsobj:SASLibrary?@Name=''&libname''",type,uri);'; put 'call symputx(''checktype'',type,''l'');'; put 'call symputx(''liburi'',uri,''l'');'; put 'putlog (_all_)(=);'; put 'run;'; put '%if &checktype = SASLibrary %then %do;'; put '%put %str(WARN)ING: Library (&liburi) already exists with libname (&libname);'; put '%return;'; put '%end;'; put '/**'; put '* Check Library does not exist already with this libref'; put '*/'; put 'data _null_;'; put 'length type uri $256;'; put 'rc=metadata_resolve("omsobj:SASLibrary?@Libref=''&libref''",type,uri);'; put 'call symputx(''checktype'',type,''l'');'; put 'call symputx(''liburi'',uri,''l'');'; put 'putlog (_all_)(=);'; put 'run;'; put '%if &checktype = SASLibrary %then %do;'; put '%put %str(WARN)ING: Library (&liburi) already exists with libref (&libref) ;'; put '%return;'; put '%end;'; put '/**'; put '* Attempt to create tree'; put '*/'; put '%mm_createfolder(path=&tree)'; put '/**'; put '* check tree exists'; put '*/'; put 'data _null_;'; put 'length type uri $256;'; put 'rc=metadata_pathobj("","&tree","Folder",type,uri);'; put 'call symputx(''foldertype'',type,''l'');'; put 'call symputx(''treeuri'',uri,''l'');'; put 'run;'; put '%if &foldertype ne Tree %then %do;'; put '%put %str(WARN)ING: Tree &tree does not exist!;'; put '%return;'; put '%end;'; put '/**'; put '* Create filerefs for proc metadata call'; put '*/'; put 'filename &frefin temp;'; put 'filename &frefout temp;'; put '%mp_abort(iftrue= ('; put '&engine=BASE & %mf_verifymacvars(libname libref engine servercontext tree)=0'; put ')'; put ',mac=&sysmacroname'; put ',msg=%str(Empty inputs: libname libref engine servercontext tree)'; put ')'; put '%if &engine=BASE %then %do;'; put '/**'; put '* Check that the ServerContext exists'; put '*/'; put 'data _null_;'; put 'length type uri $256;'; put 'rc=metadata_resolve("omsobj:ServerContext?@Name=''&ServerContext''",type,uri);'; put 'call symputx(''checktype'',type,''l'');'; put 'call symputx(''serveruri'',uri,''l'');'; put 'putlog (_all_)(=);'; put 'run;'; put '%if &checktype ne ServerContext %then %do;'; put '%put %str(ERR)OR: ServerContext (&ServerContext) does not exist!;'; put '%return;'; put '%end;'; put '/**'; put '* Get prototype info'; put '*/'; put 'data _null_;'; put 'length type uri str $256;'; put 'str="omsobj:Prototype?@Name=''Library.SAS.Prototype.Name.xmlKey.txt''";'; put 'rc=metadata_resolve(str,type,uri);'; put 'call symputx(''checktype'',type,''l'');'; put 'call symputx(''prototypeuri'',uri,''l'');'; put 'putlog (_all_)(=);'; put 'run;'; put '%if &checktype ne Prototype %then %do;'; put '%put %str(ERR)OR: Prototype Library.SAS.Prototype.Name.xmlKey.txt not found;'; put '%return;'; put '%end;'; put '/**'; put '* Check that Physical location exists'; put '*/'; put '%if %sysfunc(fileexist(&directory))=0 %then %do;'; put '%put %str(ERR)OR: Physical directory (&directory) does not appear to exist!;'; put '%return;'; put '%end;'; put '/**'; put '* Check that Directory Object exists in metadata'; put '*/'; put 'data _null_;'; put 'length type uri $256;'; put 'rc=metadata_resolve("omsobj:Directory?@DirectoryRole=''LibraryPath''"'; put '!!" and @DirectoryName=''&directory''",type,uri);'; put 'call symputx(''checktype'',type,''l'');'; put 'call symputx(''directoryuri'',uri,''l'');'; put 'putlog (_all_)(=);'; put 'run;'; put '%if &checktype ne Directory %then %do;'; put '%put NOTE: Directory object does not exist for (&directory) location;'; put '%put NOTE: It will now be created;'; put 'data _null_;'; put 'file &frefin;'; put 'directory=quote(symget(''directory''));'; put 'put "$METAREPOSITORY "/'; put '''''/'; put '"SAS"/'; put '"268435456";'; put 'run;'; put 'proc metadata in= &frefin out=&frefout %if &mdebug=1 %then verbose;;'; put 'run;'; put '%if &mdebug=1 %then %do;'; put 'data _null_;'; put 'infile &frefout lrecl=1048576;'; put 'input; put _infile_;'; put 'run;'; put '%end;'; put '%put NOTE: Checking to ensure directory (&directory) object was created;'; put 'data _null_;'; put 'length type uri $256;'; put 'rc=metadata_resolve("omsobj:Directory?@DirectoryRole=''LibraryPath''"'; put '!!" and @DirectoryName=''&directory''",type,uri);'; put 'call symputx(''checktype2'',type,''l'');'; put 'call symputx(''directoryuri'',uri,''l'');'; put '%if &mdebug=1 %then putlog (_all_)(=);;'; put 'run;'; put '%if &checktype2 ne Directory %then %do;'; put '%put %str(ERR)OR: Directory (&directory) object was NOT created!;'; put '%return;'; put '%end;'; put '%else %put NOTE: Directory (&directoryuri) successfully created!;'; put '%end;'; put '/**'; put '* check SAS version'; put '*/'; put '%if %sysevalf(&sysver lt 9.3) %then %do;'; put '%put %str(WARN)ING: Version 9.3 or later required;'; put '%return;'; put '%end;'; put '/**'; put '* Prepare the XML and create the library'; put '*/'; put 'data _null_;'; put 'file &frefin;'; put 'treeuri=quote(symget(''treeuri''));'; put 'serveruri=quote(symget(''serveruri''));'; put 'directoryuri=quote(symget(''directoryuri''));'; put 'libname=quote(symget(''libname''));'; put 'libref=quote(symget(''libref''));'; put 'IsPreassigned=quote(symget(''IsPreassigned''));'; put 'prototypeuri=quote(symget(''prototypeuri''));'; put '/* escape description so it can be stored as XML */'; put 'libdesc=tranwrd(symget(''libdesc''),''&'',''&'');'; put 'libdesc=tranwrd(libdesc,''<'',''<'');'; put 'libdesc=tranwrd(libdesc,''>'',''>'');'; put 'libdesc=tranwrd(libdesc,"''",''''');'; put 'libdesc=tranwrd(libdesc,''"'',''"'');'; put 'libdesc=tranwrd(libdesc,''0A''x,'' '');'; put 'libdesc=tranwrd(libdesc,''0D''x,'' '');'; put 'libdesc=tranwrd(libdesc,''$'',''$'');'; put 'libdesc=quote(trim(libdesc));'; put 'put "$METAREPOSITORY "/'; put '''''/'; put ''' ''/'; put ''' "/'; put ''' ''/'; put ''' ''/'; put ''' ''/'; put ''' ''/'; put '" "/'; put ''' ''/'; put ''' ''/'; put ''' ''/'; put ''' ''/'; put ''' ''/'; put ''' ''/'; put '''SAS''/'; put '''268435456'';'; put 'run;'; put 'proc metadata in= &frefin out=&frefout %if &mdebug=1 %then verbose ;;'; put 'run;'; put '%if &mdebug=1 %then %do;'; put 'data _null_;'; put 'infile &frefout lrecl=1048576;'; put 'input;put _infile_;'; put 'run;'; put '%end;'; put '%put NOTE: Checking to ensure library (&libname) was created;'; put 'data _null_;'; put 'length type uri $256;'; put 'rc=metadata_pathobj("","&tree/&libname","Library",type,uri);'; put 'call symputx(''libtype'',type,''l'');'; put 'call symputx(''liburi'',uri,''l'');'; put '%if &mdebug=1 %then putlog (_all_)(=);;'; put 'run;'; put '%if &libtype ne SASLibrary %then %do;'; put '%put %str(ERR)OR: Could not find (&libname) at (&tree)!!;'; put '%return;'; put '%end;'; put '%else %put NOTE: Library (&libname) successfully created in (&tree)!;'; put '%end;'; put '%else %do;'; put '%put %str(ERR)OR: Other library engine types are not yet supported!!;'; put '%end;'; put '/**'; put '* Wrap up'; put '*/'; put '%if &mdebug ne 1 %then %do;'; put 'filename &frefin clear;'; put 'filename &frefout clear;'; put '%end;'; put '%mend mm_createlibrary;'; put '%macro mf_getattrn('; put 'libds'; put ',attr'; put ')/*/STORE SOURCE*/;'; put '%local dsid rc;'; put '%let dsid=%sysfunc(open(&libds,is));'; put '%if &dsid = 0 %then %do;'; put '%put %str(WARN)ING: Cannot open %trim(&libds), system message below;'; put '%put %sysfunc(sysmsg());'; put '-1'; put '%end;'; put '%else %do;'; put '%sysfunc(attrn(&dsid,&attr))'; put '%let rc=%sysfunc(close(&dsid));'; put '%end;'; put '%mend mf_getattrn;'; put '%macro mf_nobs(libds'; put ')/*/STORE SOURCE*/;'; put '%mf_getattrn(&libds,NLOBS)'; put '%mend mf_nobs;'; put '%macro mm_getDirectories('; put 'path='; put ',outds=work.mm_getDirectories'; put ',mDebug=0'; put ')/*/STORE SOURCE*/;'; put '%local mD;'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing mm_getDirectories.sas;'; put '%&mD.put _local_;'; put 'data &outds (keep=directoryuri name directoryname directorydesc );'; put 'length directoryuri name directoryname directorydesc $256;'; put 'call missing(of _all_);'; put '__i+1;'; put '%if %length(&path)=0 %then %do;'; put 'do while'; put '(metadata_getnobj("omsobj:Directory?@Id contains ''.''",__i,directoryuri)>0);'; put '%end; %else %do;'; put 'do while('; put 'metadata_getnobj("omsobj:Directory?@DirectoryName=''&path''",__i,directoryuri)'; put '>0'; put ');'; put '%end;'; put '__rc1=metadata_getattr(directoryuri, "Name", name);'; put '__rc2=metadata_getattr(directoryuri, "DirectoryName", directoryname);'; put '__rc3=metadata_getattr(directoryuri, "Desc", directorydesc);'; put '&mD.putlog (_all_) (=);'; put 'drop __:;'; put '__i+1;'; put 'if sum(of __rc1-__rc3)=0 then output;'; put 'end;'; put 'run;'; put '%mend mm_getDirectories;'; put '%macro mm_updatestpsourcecode(stp='; put ',stpcode='; put ',minify=NO'; put ',mdebug=0'; put ');'; put '/* first, check if STP exists */'; put '%local tsuri;'; put '%let tsuri=stopifempty ;'; put 'data _null_;'; put 'format type uri tsuri value $200.;'; put 'call missing (of _all_);'; put 'path="&stp.(StoredProcess)";'; put '/* first, find the STP ID */'; put 'if metadata_pathobj("",path,"StoredProcess",type,uri)>0 then do;'; put '/* get sourcecode */'; put 'cnt=1;'; put 'do while (metadata_getnasn(uri,"Notes",cnt,tsuri)>0);'; put 'rc=metadata_getattr(tsuri,"Name",value);'; put '%if &mdebug=1 %then %do;'; put 'put tsuri= value=;'; put '%end;'; put 'if value="SourceCode" then do;'; put '/* found it! */'; put 'rc=metadata_getattr(tsuri,"Id",value);'; put 'call symputx(''tsuri'',value,''l'');'; put 'stop;'; put 'end;'; put 'cnt+1;'; put 'end;'; put 'end;'; put 'else put (_all_)(=);'; put 'run;'; put '%if &tsuri=stopifempty %then %do;'; put '%put %str(WARN)ING: &stp.(StoredProcess) not found!;'; put '%return;'; put '%end;'; put '%if %length(&stpcode)<2 %then %do;'; put '%put %str(WARN)ING: No SAS code supplied!!;'; put '%return;'; put '%end;'; put '%local frefin frefout;'; put '%let frefin=%mf_getuniquefileref();'; put '%let frefout=%mf_getuniquefileref();'; put '/* write header XML */'; put 'data _null_;'; put 'file &frefin;'; put 'put "$METAREPOSITORY'; put '2 %then %do;'; put 'data _null_;'; put 'file &frefin lrecl=32767 mod;'; put 'infile &stpcode lrecl=32767;'; put 'length outstr $32767;'; put 'input outstr ;'; put '/* escape code so it can be stored as XML */'; put 'outstr=tranwrd(_infile_,''&'',''&'');'; put 'outstr=tranwrd(outstr,''<'',''<'');'; put 'outstr=tranwrd(outstr,''>'',''>'');'; put 'outstr=tranwrd(outstr,"''",''''');'; put 'outstr=tranwrd(outstr,''"'',''"'');'; put 'outstr=tranwrd(outstr,''0A''x,'' '');'; put 'outstr=tranwrd(outstr,''0D''x,'' '');'; put 'outstr=tranwrd(outstr,''$'',''$'');'; put '%if &minify=YES %then %do;'; put 'outstr=cats(outstr);'; put 'if outstr ne '''';'; put 'if not (outstr=:''/*'' and subpad(left(reverse(outstr)),1,2)=''/*'');'; put '%end;'; put 'outstr=trim(outstr);'; put 'put outstr '' '';'; put 'run;'; put '%end;'; put 'data _null_;'; put 'file &frefin mod;'; put 'put "''>SAS268435456'; put '";'; put 'run;'; put 'proc metadata in= &frefin out=&frefout;'; put 'run;'; put '%if &mdebug=1 %then %do;'; put '/* write the response to the log for debugging */'; put 'data _null_;'; put 'infile &frefout lrecl=32767;'; put 'input;'; put 'put _infile_;'; put 'run;'; put '%end;'; put '%else %do;'; put 'filename &frefin clear;'; put 'filename &frefout clear;'; put '%end;'; put '%mend mm_updatestpsourcecode;'; put '%macro mm_getrepos('; put 'outds=work.mm_getrepos'; put ')/*/STORE SOURCE*/;'; put '* use a temporary fileref to hold the response;'; put 'filename response temp;'; put '/* get list of libraries */'; put 'proc metadata in='; put '"1"'; put 'out=response;'; put 'run;'; put '/* write the response to the log for debugging */'; put '/*'; put 'data _null_;'; put 'infile response lrecl=1048576;'; put 'input;'; put 'put _infile_;'; put 'run;'; put '*/'; put '/* create an XML map to read the response */'; put 'filename sxlemap temp;'; put 'data _null_;'; put 'file sxlemap;'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository";'; put 'put "";'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Id";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Name";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Desc";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@DefaultNS";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@RepositoryType";'; put 'put "characterstring20";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@RepositoryFormat";'; put 'put "characterstring10";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@Access";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@CurrentAccess";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@PauseState";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Path";'; put 'put "";'; put 'put "characterstring256";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Engine";'; put 'put "";'; put 'put "characterstring8";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Options";'; put 'put "";'; put 'put "characterstring32";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@MetadataCreated";'; put 'put "characterstring24";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@MetadataUpdated";'; put 'put "characterstring24";'; put 'put '''';'; put 'put ''
'';'; put 'run;'; put 'libname _XML_ xml xmlfileref=response xmlmap=sxlemap;'; put 'proc sort data= _XML_.SASRepos out=&outds;'; put 'by name;'; put 'run;'; put '/* clear references */'; put 'filename sxlemap clear;'; put 'filename response clear;'; put 'libname _XML_ clear;'; put '%mend mm_getrepos;'; put '%macro mm_getservercontexts('; put 'outds=work.mm_getrepos'; put ')/*/STORE SOURCE*/;'; put '%local repo repocnt x;'; put '%let repo=%sysfunc(getoption(metarepository));'; put '/* first get list of available repos */'; put '%mm_getrepos(outds=work.repos)'; put '%let repocnt=0;'; put 'data _null_;'; put 'set repos;'; put 'where repositorytype in(''CUSTOM'',''FOUNDATION'');'; put 'keep id name ;'; put 'call symputx(cats(''repo'',_n_),name,''l'');'; put 'call symputx(''repocnt'',_n_,''l'');'; put 'run;'; put 'filename __mc1 temp;'; put 'filename __mc2 temp;'; put 'data &outds;'; put 'length serveruri servername $200;'; put 'call missing (of _all_);'; put 'stop;'; put 'run;'; put '%do x=1 %to &repocnt;'; put 'options metarepository=&&repo&x;'; put 'proc metadata in='; put '"$METAREPOSITORY'; put 'ServerContextSAS'; put '0"'; put 'out=__mc1;'; put 'run;'; put '/*'; put 'data _null_;'; put 'infile __mc1 lrecl=1048576;'; put 'input;'; put 'put _infile_;'; put 'run;'; put '*/'; put 'data _null_;'; put 'file __mc2;'; put 'put '''';'; put 'put "/GetMetadataObjects/Objects/ServerContext";'; put 'put "";'; put 'put '''';'; put 'put "/GetMetadataObjects/Objects/ServerContext/@Id";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "/GetMetadataObjects/Objects/ServerContext/@Name";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put ''
'';'; put 'run;'; put 'libname __mc3 xml xmlfileref=__mc1 xmlmap=__mc2;'; put 'proc append base=&outds data=__mc3.SASContexts;run;'; put 'libname __mc3 clear;'; put '%end;'; put 'options metarepository=&repo;'; put 'filename __mc1 clear;'; put 'filename __mc2 clear;'; put '%mend mm_getservercontexts;'; put '%macro mf_isblank(param'; put ')/*/STORE SOURCE*/;'; put '%sysevalf(%superq(param)=,boolean)'; put '%mend mf_isblank;'; put '%macro mp_dropmembers('; put 'list /* space separated list of datasets / views */'; put ',libref=WORK /* can only drop from a single library at a time */'; put ',iftrue=%str(1=1)'; put ')/*/STORE SOURCE*/;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%if %mf_isblank(&list) %then %do;'; put '%put NOTE: nothing to drop!;'; put '%return;'; put '%end;'; put 'proc datasets lib=&libref nolist;'; put 'delete &list;'; put 'delete &list /mtype=view;'; put 'run;'; put '%mend mp_dropmembers;'; put '%macro mm_createstp('; put 'stpname=SASjs Default STP'; put ',stpdesc=This stp was created automatically by the mm_createstp macro'; put ',filename=mm_createstp.sas'; put ',directory=SASEnvironment/SASCode'; put ',tree=/User Folders/sasdemo'; put ',package=false'; put ',streaming=true'; put ',outds=work.mm_createstp'; put ',mDebug=0'; put ',server=SASApp'; put ',stptype=1'; put ',minify=NO'; put ',frefin=mm_in'; put ',frefout=mm_out'; put ',LogicalServerType=Sps'; put ')/*/STORE SOURCE*/;'; put '%local mD;'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing mm_CreateSTP.sas;'; put '%&mD.put _local_;'; put '%mp_abort('; put 'iftrue=(%mf_verifymacvars(stpname filename directory tree)=0)'; put ',mac=&sysmacroname'; put ',msg=%str(Empty inputs: stpname filename directory tree)'; put ')'; put '%mp_dropmembers(%scan(&outds,2,.))'; put '/* check LogicalServerType validity */'; put '%mp_abort('; put 'iftrue=('; put '&LogicalServerType ne Sps'; put 'and &LogicalServerType ne Wks'; put 'and &LogicalServerType ne Any'; put ')'; put ',mac=&sysmacroname'; put ',msg=%str(Invalid value for LogicalServerType (&LogicalServerType))'; put ')'; put '/**'; put '* check tree exists'; put '*/'; put 'data _null_;'; put 'length type uri $256;'; put 'rc=metadata_pathobj("","&tree","Folder",type,uri);'; put 'call symputx(''foldertype'',type,''l'');'; put 'call symputx(''treeuri'',uri,''l'');'; put 'run;'; put '%if &foldertype ne Tree %then %do;'; put '%put %str(WARN)ING: Tree &tree does not exist!;'; put '%return;'; put '%end;'; put '/**'; put '* Check STP does not exist already'; put '*/'; put '%local cmtype;'; put 'data _null_;'; put 'length type uri $256;'; put 'rc=metadata_pathobj("","&tree/&stpname",''StoredProcess'',type,uri);'; put 'call symputx(''cmtype'',type,''l'');'; put 'call symputx(''stpuri'',uri,''l'');'; put 'run;'; put '%if &cmtype = ClassifierMap %then %do;'; put '%put %str(WARN)ING: Stored Process &stpname already exists in &tree!;'; put '%return;'; put '%end;'; put '/**'; put '* Check that the physical file exists'; put '*/'; put '%if %sysfunc(fileexist(&directory/&filename)) ne 1 %then %do;'; put '%put %str(WARN)ING: FILE *&directory/&filename* NOT FOUND!;'; put '%return;'; put '%end;'; put '%if &stptype=1 %then %do;'; put '/* type 1 STP - where code is stored on filesystem */'; put '%if %sysevalf(&sysver lt 9.2) %then %do;'; put '%put %str(WARN)ING: Version 9.2 or later required;'; put '%return;'; put '%end;'; put '/* check directory object (where 9.2 source code reference is stored) */'; put 'data _null_;'; put 'length id $20 dirtype $256;'; put 'rc=metadata_resolve("&directory",dirtype,id);'; put 'call symputx(''checkdirtype'',dirtype,''l'');'; put 'run;'; put '%if &checkdirtype ne Directory %then %do;'; put '%mm_getdirectories(path=&directory,outds=&outds ,mDebug=&mDebug)'; put '%if %mf_nobs(&outds)=0 or %sysfunc(exist(&outds))=0 %then %do;'; put '%put %str(WARN)ING: The directory object does not exist for &directory;'; put '%return;'; put '%end;'; put '%end;'; put '%else %do;'; put 'data &outds;'; put 'directoryuri="&directory";'; put 'run;'; put '%end;'; put 'data &outds (keep=stpuri prompturi fileuri texturi);'; put 'length stpuri prompturi fileuri texturi serveruri $256 ;'; put 'if _n_=1 then call missing (of _all_);'; put 'set &outds;'; put '/* final checks on uris */'; put 'length id $20 type $256;'; put '__rc=metadata_resolve("&treeuri",type,id);'; put 'if type ne ''Tree'' then do;'; put 'putlog "%str(WARN)ING: Invalid tree URI: &treeuri";'; put 'stopme=1;'; put 'end;'; put '__rc=metadata_resolve(directoryuri,type,id);'; put 'if type ne ''Directory'' then do;'; put 'putlog "%str(WARN)ING: Invalid directory URI: " directoryuri;'; put 'stopme=1;'; put 'end;'; put '/* get server info */'; put '__rc=metadata_resolve("&server",type,serveruri);'; put 'if type ne ''LogicalServer'' then do;'; put '__rc=metadata_getnobj("omsobj:LogicalServer?@Name=''&server''",1,serveruri);'; put 'if serveruri='''' then do;'; put 'putlog "%str(WARN)ING: Invalid server: &server";'; put 'stopme=1;'; put 'end;'; put 'end;'; put 'if stopme=1 then do;'; put 'putlog (_all_)(=);'; put 'stop;'; put 'end;'; put '/* create empty prompt */'; put 'rc1=METADATA_NEWOBJ(''PromptGroup'',prompturi,''Parameters'');'; put 'rc2=METADATA_SETATTR(prompturi, ''UsageVersion'', ''1000000'');'; put 'rc3=METADATA_SETATTR(prompturi, ''GroupType'',''2'');'; put 'rc4=METADATA_SETATTR(prompturi, ''Name'',''Parameters'');'; put 'rc5=METADATA_SETATTR(prompturi, ''PublicType'',''Embedded:PromptGroup'');'; put 'GroupInfo='; put '"";'; put 'rc6 = METADATA_SETATTR(prompturi, ''GroupInfo'',groupinfo);'; put 'if sum(of rc1-rc6) ne 0 then do;'; put 'putlog "%str(WARN)ING: Issue creating prompt.";'; put 'if prompturi ne . then do;'; put 'putlog '' Removing orphan: '' prompturi;'; put 'rc = METADATA_DELOBJ(prompturi);'; put 'put rc=;'; put 'end;'; put 'stop;'; put 'end;'; put '/* create a file uri */'; put 'rc7=METADATA_NEWOBJ(''File'',fileuri,''SP Source File'');'; put 'rc8=METADATA_SETATTR(fileuri, ''FileName'',"&filename");'; put 'rc9=METADATA_SETATTR(fileuri, ''IsARelativeName'',''1'');'; put 'rc10=METADATA_SETASSN(fileuri, ''Directories'',''MODIFY'',directoryuri);'; put 'if sum(of rc7-rc10) ne 0 then do;'; put 'putlog "%str(WARN)ING: Issue creating file.";'; put 'if fileuri ne . then do;'; put 'putlog '' Removing orphans:'' prompturi fileuri;'; put 'rc = METADATA_DELOBJ(prompturi);'; put 'rc = METADATA_DELOBJ(fileuri);'; put 'put (_all_)(=);'; put 'end;'; put 'stop;'; put 'end;'; put '/* create a TextStore object */'; put 'rc11= METADATA_NEWOBJ(''TextStore'',texturi,''Stored Process'');'; put 'rc12= METADATA_SETATTR(texturi, ''TextRole'',''StoredProcessConfiguration'');'; put 'rc13= METADATA_SETATTR(texturi, ''TextType'',''XML'');'; put 'storedtext='''''; put '!!""'; put '!!"";'; put 'rc14= METADATA_SETATTR(texturi, ''StoredText'',storedtext);'; put 'if sum(of rc11-rc14) ne 0 then do;'; put 'putlog "%str(WARN)ING: Issue creating TextStore.";'; put 'if texturi ne . then do;'; put 'putlog '' Removing orphans: '' prompturi fileuri texturi;'; put 'rc = METADATA_DELOBJ(prompturi);'; put 'rc = METADATA_DELOBJ(fileuri);'; put 'rc = METADATA_DELOBJ(texturi);'; put 'put (_all_)(=);'; put 'end;'; put 'stop;'; put 'end;'; put '/* create meta obj */'; put 'rc15= METADATA_NEWOBJ(''ClassifierMap'',stpuri,"&stpname");'; put 'rc16= METADATA_SETASSN(stpuri, ''Trees'',''MODIFY'',treeuri);'; put 'rc17= METADATA_SETASSN(stpuri, ''ComputeLocations'',''MODIFY'',serveruri);'; put 'rc18= METADATA_SETASSN(stpuri, ''SourceCode'',''MODIFY'',fileuri);'; put 'rc19= METADATA_SETASSN(stpuri, ''Prompts'',''MODIFY'',prompturi);'; put 'rc20= METADATA_SETASSN(stpuri, ''Notes'',''MODIFY'',texturi);'; put 'rc21= METADATA_SETATTR(stpuri, ''PublicType'', ''StoredProcess'');'; put 'rc22= METADATA_SETATTR(stpuri, ''TransformRole'', ''StoredProcess'');'; put 'rc23= METADATA_SETATTR(stpuri, ''UsageVersion'', ''1000000'');'; put 'rc24= METADATA_SETATTR(stpuri, ''Desc'', "&stpdesc");'; put '/* tidy up if err */'; put 'if sum(of rc15-rc24) ne 0 then do;'; put 'putlog "%str(WARN)ING: Issue creating STP.";'; put 'if stpuri ne . then do;'; put 'putlog '' Removing orphans: '' prompturi fileuri texturi stpuri;'; put 'rc = METADATA_DELOBJ(prompturi);'; put 'rc = METADATA_DELOBJ(fileuri);'; put 'rc = METADATA_DELOBJ(texturi);'; put 'rc = METADATA_DELOBJ(stpuri);'; put 'put (_all_)(=);'; put 'end;'; put 'end;'; put 'else do;'; put 'fullpath=cats(''_program='',treepath,"/&stpname");'; put 'putlog "NOTE: Stored Process Created!";'; put 'putlog "NOTE- "; putlog "NOTE-"; putlog "NOTE-" fullpath;'; put 'putlog "NOTE- "; putlog "NOTE-";'; put 'end;'; put 'output;'; put 'stop;'; put 'run;'; put '%end;'; put '%else %if &stptype=2 %then %do;'; put '/* type 2 stp - code is stored in metadata */'; put '%if %sysevalf(&sysver lt 9.3) %then %do;'; put '%put %str(WARN)ING: SAS version 9.3 or later required to create type2 STPs;'; put '%return;'; put '%end;'; put '/* check we have the correct ServerContext */'; put '%mm_getservercontexts(outds=contexts)'; put '%local serveruri; %let serveruri=NOTFOUND;'; put 'data _null_;'; put 'set contexts;'; put 'where upcase(servername)="%upcase(&server)";'; put 'call symputx(''serveruri'',serveruri);'; put 'run;'; put '%if &serveruri=NOTFOUND %then %do;'; put '%put %str(WARN)ING: ServerContext *&server* not found!;'; put '%return;'; put '%end;'; put '/**'; put '* First, create a Hello World type 2 stored process'; put '*/'; put 'filename &frefin temp;'; put 'data _null_;'; put 'file &frefin;'; put 'treeuri=quote(symget(''treeuri''));'; put 'serveruri=quote(symget(''serveruri''));'; put 'stpdesc=quote(symget(''stpdesc''));'; put 'stpname=quote(symget(''stpname''));'; put 'put "$METAREPOSITORY "/'; put '''''/'; put '" "/'; put '" "/'; put '" "/'; put '" "/'; put ''' ''/'; put ''' '' /'; put '" "/'; put '" "/'; put ''' ''/'; put '" "/'; put '""/'; put '"SAS"/'; put '"268435456";'; put 'run;'; put 'filename &frefout temp;'; put 'proc metadata in= &frefin out=&frefout ;'; put 'run;'; put '%if &mdebug=1 %then %do;'; put '/* write the response to the log for debugging */'; put 'data _null_;'; put 'infile &frefout lrecl=1048576;'; put 'input;'; put 'put _infile_;'; put 'run;'; put '%end;'; put '/**'; put '* Next, add the source code'; put '*/'; put '%mm_updatestpsourcecode(stp=&tree/&stpname'; put ',stpcode="&directory/&filename"'; put ',mdebug=&mdebug'; put ',minify=&minify)'; put '%end;'; put '%else %do;'; put '%put %str(WARN)ING: STPTYPE=*&stptype* not recognised!;'; put '%end;'; put '%mend mm_createstp;'; put '%macro mm_deletedocument('; put 'target='; put ')/*/STORE SOURCE*/;'; put '/**'; put '* Check document exist'; put '*/'; put '%local type;'; put 'data _null_;'; put 'length type uri $256;'; put 'rc=metadata_pathobj("","&target",''Note'',type,uri);'; put 'call symputx(''type'',type,''l'');'; put 'call symputx(''stpuri'',uri,''l'');'; put 'run;'; put '%if &type ne Document %then %do;'; put '%put %str(WARN)ING: No Document found at ⌖'; put '%return;'; put '%end;'; put 'filename __in temp lrecl=10000;'; put 'filename __out temp lrecl=10000;'; put 'data _null_ ;'; put 'file __in ;'; put 'put "";'; put 'put "SAS268436480";'; put 'put "";'; put 'run ;'; put 'proc metadata in=__in out=__out verbose;run;'; put '/* list the result */'; put 'data _null_;infile __out; input; list; run;'; put 'filename __in clear;'; put 'filename __out clear;'; put '/**'; put '* Check deletion'; put '*/'; put '%local isgone;'; put 'data _null_;'; put 'length type uri $256;'; put 'call missing (of _all_);'; put 'rc=metadata_pathobj("","&target",''Note'',type,uri);'; put 'call symputx(''isgone'',type,''l'');'; put 'run;'; put '%if &isgone = Document %then %do;'; put '%put %str(ERR)OR: Document not deleted from ⌖'; put '%let syscc=4;'; put '%return;'; put '%end;'; put '%mend mm_deletedocument;'; put '%macro mm_deletestp('; put 'target='; put ')/*/STORE SOURCE*/;'; put '/**'; put '* Check STP does exist'; put '*/'; put '%local cmtype;'; put 'data _null_;'; put 'length type uri $256;'; put 'rc=metadata_pathobj("","&target",''StoredProcess'',type,uri);'; put 'call symputx(''cmtype'',type,''l'');'; put 'call symputx(''stpuri'',uri,''l'');'; put 'run;'; put '%if &cmtype ne ClassifierMap %then %do;'; put '%put NOTE: No Stored Process found at ⌖'; put '%return;'; put '%end;'; put 'filename __in temp lrecl=10000;'; put 'filename __out temp lrecl=10000;'; put 'data _null_ ;'; put 'file __in ;'; put 'put "";'; put 'put "SAS268436480";'; put 'put "";'; put 'run ;'; put 'proc metadata in=__in out=__out verbose;run;'; put '/* list the result */'; put 'data _null_;infile __out; input; list; run;'; put 'filename __in clear;'; put 'filename __out clear;'; put '/**'; put '* Check deletion'; put '*/'; put '%local isgone;'; put 'data _null_;'; put 'length type uri $256;'; put 'call missing (of _all_);'; put 'rc=metadata_pathobj("","&target",''Note'',type,uri);'; put 'call symputx(''isgone'',type,''l'');'; put 'run;'; put '%if &isgone = ClassifierMap %then %do;'; put '%put %str(ERR)OR: STP not deleted from ⌖'; put '%let syscc=4;'; put '%return;'; put '%end;'; put '%mend mm_deletestp;'; put '%macro mm_getstpinfo(pgm'; put ',outds=work.mm_getstpinfo'; put ',mDebug=0'; put ');'; put '%local mD;'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing &sysmacroname..sas;'; put '%&mD.put _local_;'; put 'data &outds;'; put 'length type stp_uri tsuri servercontext value $200'; put 'StoredProcessConfiguration $1000 sourcecode_first32k $32767;'; put 'keep path stp_uri sourcecode_first32k StoredProcessConfiguration'; put 'servercontext;'; put 'call missing (of _all_);'; put 'path="&pgm(StoredProcess)";'; put '/* first, find the STP ID */'; put 'if metadata_pathobj("",path,"StoredProcess",type,stp_uri)>0 then do;'; put '/* get attributes */'; put 'cnt=1;'; put 'do while (metadata_getnasn(stp_uri,"Notes",cnt,tsuri)>0);'; put 'rc1=metadata_getattr(tsuri,"Name",value);'; put '&mD.put tsuri= value=;'; put 'if value="SourceCode" then do;'; put 'rc2=metadata_getattr(tsuri,"StoredText",sourcecode_first32k);'; put 'end;'; put 'else if value="Stored Process" then do;'; put 'rc3=metadata_getattr(tsuri,"StoredText",StoredProcessConfiguration);'; put 'end;'; put 'cnt+1;'; put 'end;'; put '/* get context (should only be one) */'; put 'rc4=metadata_getnasn(stp_uri,"ComputeLocations",1,tsuri);'; put 'rc5=metadata_getattr(tsuri,"Name",servercontext);'; put 'end;'; put 'else do;'; put 'put ''ERR'' +(-1) "OR: could not find " path;'; put 'put (_all_)(=);'; put 'end;'; put '&md.put (_all_)(=);'; put 'run;'; put '%mend mm_getstpinfo ;'; put '* SAS Macros end;'; put '* SAS Includes start;'; put '* SAS Includes end;'; put '* Binary Files start;'; put '* Binary Files end;'; put '* ServiceInit start;'; put 'options noquotelenmax ps=max;'; put '/* create dummy macros to prevent stpbegin / stpend from corrupting sessions */'; put '%macro stpbegin();'; put '%put NOTE: the STPBEGIN macro should not be used for web apps!;'; put '%mend stpbegin;'; put '%macro stpend();'; put '%put NOTE: the STPEND macro should not be used for web apps!;'; put '%mend stpend;'; put '* ServiceInit end;'; put '* Service start;'; put '/**'; put '@file'; put '@brief service for creating the configuration tables in DEMO mode.'; put '@details'; put 'STP for creating the configuration tables in DEMO mode.'; put 'It also adds the STAGING directory as subdirectory to the BASE'; put 'library location.'; put 'Note - the CURLIB var is added during the build process.'; put '@warning This STP self destructs! It will delete itself after a successful run'; put 'to avoid being executed twice (and overwriting actual data)'; put '

SAS Macros

'; put '@li mf_getapploc.sas'; put '@li mf_getuser.sas'; put '@li mf_mkdir.sas'; put '@li mm_createdocument.sas'; put '@li mm_createlibrary.sas'; put '@li mm_createstp.sas'; put '@li mm_deletedocument.sas'; put '@li mm_deletestp.sas'; put '@li mm_getstpcode.sas'; put '@li mm_getstpinfo.sas'; put '@li mp_abort.sas'; put '@li mp_init.sas'; put '@li mpe_makedata.sas'; put '@li mpe_makedatamodel.sas'; put '@version 9.2'; put '@author 4GL Apps Ltd'; put '@copyright 4GL Apps Ltd. This code may only be used within Data Controller'; put 'and may not be re-distributed or re-sold without the express permission of'; put '4GL Apps Ltd.'; put '**/'; put '%mp_init()'; put '%global admin repo path; /* URL params from configurator */'; put '%let repo=%sysfunc(coalescec(&repo,Foundation));'; put '%let admin=%sysfunc(coalescec(&admin,dc-admin));'; put '%let dcpath=%sysfunc(coalescec(&path,NOTFOUND));'; put '%mp_abort(iftrue= ("&dcpath" = "NOTFOUND")'; put ',mac=&_program'; put ',msg=%str(PATH variable was not provided)'; put ')'; put '%let root=%mf_getapploc();'; put '%let work=%sysfunc(pathname(work));'; put '%let dc_libref=%upcase(DC%substr(%sysevalf(%sysfunc(datetime())/60),3,6));'; put '%let DC_LIBNAME=Data Controller(&dc_libref);'; put '%let dcpath=&dcpath/&dc_libref;'; put '%put _all_;'; put '%mf_mkdir(&dcpath/dc_staging)'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(Unable to create &dcpath using &sysuserid)'; put ')'; put '/* check we have the admin rights to update the items in the Admin folder */'; put '%mm_createdocument(tree=&root/services/admin,name=permTest)'; put '%mm_deletedocument(target=&root/services/admin/permTest)'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(User &_metaperson does not have WriteMetadata on metadata folder:'; put '&root )'; put ')'; put '/* check we have physical permissions to the DCLIB folder */'; put 'data _null_;'; put 'putlog "dcpath=&dcpath/permTest.txt";'; put 'putlog "sysuserid=&sysuserid";'; put 'data _null_;'; put 'file "&dcpath/permTest.txt";'; put 'run;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(User &sysuserid does not have WRITE permissions on physical'; put 'directory: &dcpath )'; put ')'; put 'filename delfile "&dcpath/permTest.txt";'; put 'data _null_;'; put 'rc=fdelete(''delfile'');'; put 'run;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(User &sysuserid could create (but not delete) &dcpath/permTest.txt )'; put ')'; put '/* get the server context from the current STP */'; put '%mm_getstpinfo(&_program,outds=work.stpinfo)'; put 'data _null_;'; put 'set work.stpinfo;'; put 'call symputx(''serverContext'',serverContext);'; put 'run;'; put '/* create the library */'; put '%mm_createlibrary('; put 'libname=&DC_LIBNAME'; put ',libref=&dc_libref'; put ',libdesc=Data Controller for SAS configuration tables'; put ',engine=BASE'; put ',tree=&root/data'; put ',servercontext=&serverContext'; put ',directory=&dcpath'; put ',mDebug=1'; put ')'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=buildtermsas9'; put ',msg=%str(Unable to create &dc_libref library)'; put ')'; put '/* assign the library */'; put 'libname &dc_libref "&dcpath";'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=buildtermsas9'; put ',msg=%str(Unable to assign the &dc_libref library)'; put ')'; put '/* make the tables */'; put '/* SASAdministrators */'; put '%mpe_makedatamodel(lib=&dc_libref)'; put '%mpe_makedata(lib=&DC_LIBREF,mpeadmins=&admin,path=%str(&dcpath))'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(Problem creating tables in &DC_LIBREF library\n'; put 'SYSERRORTEXT=&SYSERRORTEXT \n'; put 'SYSWARNINGTEXT=&SYSWARNINGTEXT)'; put ')'; put '/* register tables in metadata */'; put 'proc metalib;'; put 'omr (library="&DC_LIBNAME");'; put 'folder="&root/data";'; put 'update_rule=(delete);'; put 'run;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(Problem registering metadata for tables in &DC_LIBREF library \n'; put 'os user=&sysuserid \n'; put 'metaperson=%mf_getuser() \n'; put 'SYSERRORTEXT=&SYSERRORTEXT \n'; put 'SYSWARNINGTEXT=&SYSWARNINGTEXT'; put ')'; put ')'; put '/* finally, update the app component. The user will need WM perms for this. */'; put 'data _null_;'; put 'file "&work/settings.sas" mod ;'; put 'put '' '';'; put 'put ''%global DC_LIBREF DC_LIBNAME DC_LIBLOC DC_STAGING_AREA DC_ADMIN_GROUP'';'; put 'put '' DC_REPO_USERS DC_DTTMTFMT DC_MACROS;'';'; put 'put '' '';'; put 'put ''/* This metadata library (libref) contains control datasets for DC */'';'; put 'put ''/* If a different libref must be used, configure it below */'';'; put 'put ''%let DC_LIBREF='' "&DC_LIBREF;";'; put 'put ''%let DC_LIBNAME='' "&DC_LIBNAME;";'; put 'put ''%let DC_LIBLOC='' "&dcpath;";'; put 'put '' '';'; put 'put ''libname &DC_LIBREF "&dc_libloc";'';'; put 'put '' '';'; put 'put ''/* This metadata group has unrestricted access to Data Controller */'';'; put 'put ''%let dc_admin_group='' "&admin;";'; put 'put '' '';'; put 'put ''/* This repository is used to query for users and groups */'';'; put 'put ''%let dc_repo_users='' "&repo;";'; put 'put '' '';'; put 'put ''/* This physical location is used for staging data and audit history */'';'; put 'put ''%let dc_staging_area=&dc_libloc/dc_staging;'';'; put 'put '' '';'; put 'put ''data _null_;'';'; put 'put '' set &DC_LIBREF..mpe_config(where=('';'; put 'put '' var_scope="DC" '';'; put 'put '' and &dc_dttmtfmt. lt tx_to'';'; put 'put '' and var_active=1'';'; put 'put " ));";'; put 'put " call symputx(var_name,var_value,''G'');";'; put 'put '' putlog var_name "=" var_value;'';'; put 'put "run;";'; put 'put '' '';'; put 'put ''/* to override any DC macros with client versions, place them below */'';'; put 'put ''options insert=(sasautos=("&dc_macros"));'';'; put 'run;'; put '/*'; put 'put ''data _null_;'';'; put 'put '' length lib_uri up_uri path $256;'';'; put 'put '' call missing (of _all_);'';'; put 'put '' rc=metadata_getnobj("omsobj:SASLibrary?@Libref =''''&dc_libref''''",1,lib_uri);'';'; put 'put '' rc=metadata_getnasn(lib_uri,"UsingPackages",1,up_uri);'';'; put 'put '' rc=metadata_getattr(up_uri,"DirectoryName",path);'';'; put 'put '' call symputx("dc_libloc",path);'';'; put 'put ''run;'';'; put '*/'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=syscc=&syscc when preparing data_controller_settings)'; put ')'; put '%mm_createstp(stpname=Data_Controller_Settings'; put ',filename=settings.sas'; put ',directory=&work'; put ',tree=&root/services/public'; put ',Server=&serverContext'; put ',stptype=2'; put ')'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str('; put 'Problem updating the dc_staging area in'; put '&root/services/public/DataController Settings \n'; put 'SYSERRORTEXT=&SYSERRORTEXT \n'; put 'SYSWARNINGTEXT=&SYSWARNINGTEXT'; put ')'; put ')'; put 'data _null_;'; put 'file _webout;'; put 'put ''

Data Controller Config

'';'; put 'put ''

The following items have been successfully configured:

'';'; put 'put "";'; put 'put "

Next Steps:

";'; put 'put "
  1. Populate ";'; put 'put "Table Lineage
  2. ";'; put 'put "
  3. Populate";'; put 'put "Data Catalog
  4. ";'; put 'put "
  5. Now, ";'; put 'put "hereLaunch!
";'; put 'run;'; put '/* We ran successfully, now remove configurator and makedata STPs */'; put '%mm_deletestp(target=&root/services/admin/configurator)'; put '%mm_deletestp(target=&_program)'; put '* Service end;'; run; %mm_createwebservice(path=&appLoc/&path, name=&service, code=sascode, server=&serverName, replace=yes) filename sascode clear; %let service=makelib; filename sascode temp lrecl=32767; data _null_; file sascode; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '/**'; put '@file mp_jsonout.sas'; put '@brief Writes JSON in SASjs format to a fileref'; put '@details This macro can be used to OPEN a JSON stream and send one or more'; put 'tables as arrays of rows, where each row can be an object or a nested array.'; put 'There are two engines available - DATASTEP or PROCJSON.'; put 'PROC JSON is fast but will produce errs like the ones below if'; put 'special chars are encountered.'; put '> (ERR)OR: Some code points did not transcode.'; put '> An object or array close is not valid at this point in the JSON text.'; put '> Date value out of range'; put 'If this happens, try running with ENGINE=DATASTEP.'; put 'The DATASTEP engine is used to handle special SAS missing numerics, and'; put 'can also convert entire datasets to formatted values. Output JSON is always'; put 'in UTF-8.'; put 'Usage:'; put 'filename tmp temp;'; put 'data class; set sashelp.class;run;'; put '%mp_jsonout(OPEN,jref=tmp)'; put '%mp_jsonout(OBJ,class,jref=tmp)'; put '%mp_jsonout(OBJ,class,dslabel=class2,jref=tmp,showmeta=Y)'; put '%mp_jsonout(CLOSE,jref=tmp)'; put 'data _null_;'; put 'infile tmp;'; put 'input;putlog _infile_;'; put 'run;'; put 'If you are building web apps with SAS then you are strongly encouraged to use'; put 'the mX_createwebservice macros in combination with the'; put '[sasjs adapter](https://github.com/sasjs/adapter).'; put 'For more information see https://sasjs.io'; put '@param [in] action Valid values:'; put '@li OPEN - opens the JSON'; put '@li OBJ - sends a table with each row as an object'; put '@li ARR - sends a table with each row in an array'; put '@li CLOSE - closes the JSON'; put '@param [in] ds The dataset to send. Must be a work table.'; put '@param [out] jref= (_webout) The fileref to which to send the JSON'; put '@param [out] dslabel= The name to give the table in the exported JSON'; put '@param [in] fmt= (Y) Whether to keep (Y) or strip (N) formats from the table'; put '@param [in] engine= (DATASTEP) Which engine to use to send the JSON. Options:'; put '@li PROCJSON (default)'; put '@li DATASTEP (more reliable when data has non standard characters)'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to JSON'; put '

Related Files

'; put '@li mp_ds2fmtds.sas'; put '@version 9.2'; put '@author Allan Bowe'; put '@source https://github.com/sasjs/core'; put '**/'; put '%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y'; put ',engine=DATASTEP'; put ',missing=NULL'; put ',showmeta=N'; put ',maxobs=MAX'; put ')/*/STORE SOURCE*/;'; put '%local tempds colinfo fmtds i numcols numobs stmt_obs lastobs optval'; put 'tmpds1 tmpds2 tmpds3 tmpds4;'; put '%let numcols=0;'; put '%if &maxobs ne MAX %then %let stmt_obs=%str(if _n_>&maxobs then stop;);'; put '%if &action=OPEN %then %do;'; put 'options nobomfile;'; put 'data _null_;file &jref encoding=''utf-8'' lrecl=200;'; put 'put ''{"PROCESSED_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''"'';'; put 'run;'; put '%end;'; put '%else %if (&action=ARR or &action=OBJ) %then %do;'; put '/* force variable names to always be uppercase in the JSON */'; put 'options validvarname=upcase;'; put '/* To avoid issues with _webout on EBI - such as encoding diffs and truncation'; put '(https://support.sas.com/kb/49/325.html) we use temporary files */'; put 'filename _sjs1 temp lrecl=200 ;'; put 'data _null_; file _sjs1 encoding=''utf-8'';'; put 'put ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":";'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjs1 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs1 clear;'; put '/* grab col defs */'; put 'proc contents noprint data=&ds'; put 'out=_data_(keep=name type length format formatl formatd varnum label);'; put 'run;'; put '%let colinfo=%scan(&syslast,2,.);'; put 'proc sort data=&colinfo;'; put 'by varnum;'; put 'run;'; put '/* move meta to mac vars */'; put 'data &colinfo;'; put 'if _n_=1 then call symputx(''numcols'',nobs,''l'');'; put 'set &colinfo end=last nobs=nobs;'; put 'name=upcase(name);'; put '/* fix formats */'; put 'if type=2 or type=6 then do;'; put 'typelong=''char'';'; put 'length fmt $49.;'; put 'if format='''' then fmt=cats(''$'',length,''.'');'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else fmt=cats(format,formatl,''.'');'; put 'end;'; put 'else do;'; put 'typelong=''num'';'; put 'if format='''' then fmt=''best.'';'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else if formatd=0 then fmt=cats(format,formatl,''.'');'; put 'else fmt=cats(format,formatl,''.'',formatd);'; put 'end;'; put '/* 32 char unique name */'; put 'newname=''sasjs''!!substr(cats(put(md5(name),$hex32.)),1,27);'; put 'call symputx(cats(''name'',_n_),name,''l'');'; put 'call symputx(cats(''newname'',_n_),newname,''l'');'; put 'call symputx(cats(''length'',_n_),length,''l'');'; put 'call symputx(cats(''fmt'',_n_),fmt,''l'');'; put 'call symputx(cats(''type'',_n_),type,''l'');'; put 'call symputx(cats(''typelong'',_n_),typelong,''l'');'; put 'call symputx(cats(''label'',_n_),coalescec(label,name),''l'');'; put '/* overwritten when fmt=Y and a custom format exists in catalog */'; put 'if typelong=''num'' then call symputx(cats(''fmtlen'',_n_),200,''l'');'; put 'else call symputx(cats(''fmtlen'',_n_),min(32767,ceil((length+10)*1.5)),''l'');'; put 'run;'; put '%let tempds=%substr(_%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql;'; put 'select count(*) into: lastobs from &ds;'; put '%if &maxobs ne MAX %then %let lastobs=%sysfunc(min(&lastobs,&maxobs));'; put '%if &engine=PROCJSON %then %do;'; put '%if &missing=STRING %then %do;'; put '%put &sysmacroname: Special Missings not supported in proc json.;'; put '%put &sysmacroname: Switching to DATASTEP engine;'; put '%goto datastep;'; put '%end;'; put 'data &tempds;'; put 'set &ds;'; put '&stmt_obs;'; put '%if &fmt=N %then format _numeric_ best32.;;'; put '/* PRETTY is necessary to avoid line truncation in large files */'; put 'filename _sjs2 temp lrecl=131068 encoding=''utf-8'';'; put 'proc json out=_sjs2 pretty'; put '%if &action=ARR %then nokeys ;'; put ';export &tempds / nosastags fmtnumeric;'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs2 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs2 clear;'; put '%end;'; put '%else %if &engine=DATASTEP %then %do;'; put '%datastep:'; put '%if %sysfunc(exist(&ds)) ne 1 & %sysfunc(exist(&ds,VIEW)) ne 1'; put '%then %do;'; put '%put &sysmacroname: &ds NOT FOUND!!!;'; put '%return;'; put '%end;'; put '%if &fmt=Y %then %do;'; put '/**'; put '* Extract format definitions'; put '* First, by getting library locations from dictionary.formats'; put '* Then, by exporting the width using proc format'; put '* Cannot use maxw from sashelp.vformat as not always populated'; put '* Cannot use fmtinfo() as not supported in all flavours'; put '*/'; put '%let tmpds1=%substr(fmtsum%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds2=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds3=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds4=%substr(col%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql noprint;'; put 'create table &tmpds1 as'; put 'select cats(libname,''.'',memname) as FMTCAT,'; put 'FMTNAME'; put 'from dictionary.formats'; put 'where fmttype=''F'' and libname is not null'; put 'and fmtname in (select format from &colinfo where format is not null)'; put 'order by 1;'; put 'create table &tmpds2('; put 'FMTNAME char(32),'; put 'LENGTH num'; put ');'; put '%local catlist cat fmtlist i;'; put 'select distinct fmtcat into: catlist separated by '' '' from &tmpds1;'; put '%do i=1 %to %sysfunc(countw(&catlist,%str( )));'; put '%let cat=%scan(&catlist,&i,%str( ));'; put 'proc sql;'; put 'select distinct fmtname into: fmtlist separated by '' '''; put 'from &tmpds1 where fmtcat="&cat";'; put 'proc format lib=&cat cntlout=&tmpds3(keep=fmtname length);'; put 'select &fmtlist;'; put 'run;'; put 'proc sql;'; put 'insert into &tmpds2 select distinct fmtname,length from &tmpds3;'; put '%end;'; put 'proc sql;'; put 'create table &tmpds4 as'; put 'select a.*, b.length as MAXW'; put 'from &colinfo a'; put 'left join &tmpds2 b'; put 'on cats(a.format)=cats(upcase(b.fmtname))'; put 'order by a.varnum;'; put 'data _null_;'; put 'set &tmpds4;'; put 'if not missing(maxw);'; put 'call symputx('; put 'cats(''fmtlen'',_n_),'; put '/* vars need extra padding due to JSON escaping of special chars */'; put 'min(32767,ceil((max(length,maxw)+10)*1.5))'; put ',''l'''; put ');'; put 'run;'; put '/* configure varlenchk - as we are explicitly shortening the variables */'; put '%let optval=%sysfunc(getoption(varlenchk));'; put 'options varlenchk=NOWARN;'; put 'data _data_(compress=char);'; put '/* shorten the new vars */'; put 'length'; put '%do i=1 %to &numcols;'; put '&&name&i $&&fmtlen&i'; put '%end;'; put ';'; put '/* rename on entry */'; put 'set &ds(rename=('; put '%do i=1 %to &numcols;'; put '&&name&i=&&newname&i'; put '%end;'; put '));'; put '&stmt_obs;'; put 'drop'; put '%do i=1 %to &numcols;'; put '&&newname&i'; put '%end;'; put ';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=num %then %do;'; put '&&name&i=cats(put(&&newname&i,&&fmt&i));'; put '%end;'; put '%else %do;'; put '&&name&i=put(&&newname&i,&&fmt&i);'; put '%end;'; put '%end;'; put 'if _error_ then do;'; put 'call symputx(''syscc'',1012);'; put 'stop;'; put 'end;'; put 'run;'; put '%let fmtds=&syslast;'; put 'options varlenchk=&optval;'; put '%end;'; put 'proc format; /* credit yabwon for special null removal */'; put 'value bart (default=40)'; put '%if &missing=NULL %then %do;'; put '._ - .z = null'; put '%end;'; put '%else %do;'; put '._ = [quote()]'; put '. = null'; put '.a - .z = [quote()]'; put '%end;'; put 'other = [best.];'; put 'data &tempds;'; put 'attrib _all_ label='''';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'length &&name&i $&&fmtlen&i...;'; put 'format &&name&i $&&fmtlen&i...;'; put '%end;'; put '%end;'; put '%if &fmt=Y %then %do;'; put 'set &fmtds;'; put '%end;'; put '%else %do;'; put 'set &ds;'; put '%end;'; put '&stmt_obs;'; put 'format _numeric_ bart.;'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'if findc(&&name&i,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put '&&name&i=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,&&name&i)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else &&name&i=quote(cats(&&name&i));'; put '%end;'; put '%end;'; put 'run;'; put 'filename _sjs3 temp lrecl=131068 ;'; put 'data _null_;'; put 'file _sjs3 encoding=''utf-8'';'; put 'if _n_=1 then put "[";'; put 'set &tempds;'; put 'if _n_>1 then put "," @; put'; put '%if &action=ARR %then "[" ; %else "{" ;'; put '%do i=1 %to &numcols;'; put '%if &i>1 %then "," ;'; put '%if &action=OBJ %then """&&name&i"":" ;'; put '"&&name&i"n /* name literal for reserved variable names */'; put '%end;'; put '%if &action=ARR %then "]" ; %else "}" ; ;'; put '/* close out the table */'; put 'data _null_;'; put 'file _sjs3 mod encoding=''utf-8'';'; put 'put '']'';'; put 'run;'; put 'data _null_;'; put 'infile _sjs3 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs3 clear;'; put '%end;'; put 'proc sql;'; put 'drop table &colinfo, &tempds;'; put '%if %substr(&showmeta,1,1)=Y %then %do;'; put 'filename _sjs4 temp lrecl=131068 encoding=''utf-8'';'; put 'data _null_;'; put 'file _sjs4;'; put 'length label $350;'; put 'put ", ""$%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":{""vars"":{";'; put 'do i=1 to &numcols;'; put 'name=quote(trim(symget(cats(''name'',i))));'; put 'format=quote(trim(symget(cats(''fmt'',i))));'; put 'label=quote(prxchange(''s/\\/\\\\/'',-1,trim(symget(cats(''label'',i)))));'; put 'length=quote(trim(symget(cats(''length'',i))));'; put 'type=quote(trim(symget(cats(''typelong'',i))));'; put 'if i>1 then put "," @@;'; put 'put name '':{"format":'' format '',"label":'' label'; put ''',"length":'' length '',"type":'' type ''}'';'; put 'end;'; put 'put ''}}'';'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs4 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs4 clear;'; put '%end;'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put 'data _null_; file &jref encoding=''utf-8'' mod ;'; put 'put "}";'; put 'run;'; put '%end;'; put '%mend mp_jsonout;'; put '/**'; put '@file mm_webout.sas'; put '@brief Send data to/from SAS Stored Processes'; put '@details This macro should be added to the start of each Stored Process,'; put '**immediately** followed by a call to:'; put '%mm_webout(FETCH)'; put 'This will read all the input data and create same-named SAS datasets in the'; put 'WORK library. You can then insert your code, and send data back using the'; put 'following syntax:'; put 'data some datasets; * make some data ;'; put 'retain some columns;'; put 'run;'; put '%mm_webout(OPEN)'; put '%mm_webout(ARR,some) * Array format, fast, suitable for large tables ;'; put '%mm_webout(OBJ,datasets) * Object format, easier to work with ;'; put 'Finally, wrap everything up send some helpful system variables too'; put '%mm_webout(CLOSE)'; put '@param [in] action Either FETCH, OPEN, ARR, OBJ or CLOSE'; put '@param [in] ds The dataset to send back to the frontend'; put '@param [out] dslabel= Value to use instead of table name for sending to JSON'; put '@param [in] fmt= (N) Setting Y converts all vars to their formatted values'; put '@param [out] fref= (_webout) The fileref to which to write the JSON'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] workobs= (0) When set to a positive integer, will create a new'; put 'output object (WORK) which contains this number of observations from all'; put 'tables in the WORK library.'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to output JSON'; put '

SAS Macros

'; put '@li mp_jsonout.sas'; put '

Related Macros

'; put '@li ms_webout.sas'; put '@li mv_webout.sas'; put '@version 9.3'; put '@author Allan Bowe'; put '**/'; put '%macro mm_webout(action,ds,dslabel=,fref=_webout,fmt=N,missing=NULL'; put ',showmeta=N,maxobs=MAX,workobs=0'; put ');'; put '%global _webin_file_count _webin_fileref1 _webin_name1 _program _debug'; put 'sasjs_tables;'; put '%local i tempds jsonengine;'; put '/* see https://github.com/sasjs/core/issues/41 */'; put '%if "%upcase(&SYSENCODING)" ne "UTF-8" %then %let jsonengine=PROCJSON;'; put '%else %let jsonengine=DATASTEP;'; put '%if &action=FETCH %then %do;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'options mprint notes mprintnest;'; put '%end;'; put '%let _webin_file_count=%eval(&_webin_file_count+0);'; put '/* now read in the data */'; put '%do i=1 %to &_webin_file_count;'; put '%if &_webin_file_count=1 %then %do;'; put '%let _webin_fileref1=&_webin_fileref;'; put '%let _webin_name1=&_webin_name;'; put '%end;'; put 'data _null_;'; put 'infile &&_webin_fileref&i termstr=crlf;'; put 'input;'; put 'call symputx(''input_statement'',_infile_);'; put 'putlog "&&_webin_name&i input statement: " _infile_;'; put 'stop;'; put 'data &&_webin_name&i;'; put 'infile &&_webin_fileref&i firstobs=2 dsd termstr=crlf encoding=''utf-8'';'; put 'input &input_statement;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'if _n_<20 then putlog _infile_;'; put '%end;'; put 'run;'; put '%let sasjs_tables=&sasjs_tables &&_webin_name&i;'; put '%end;'; put '%end;'; put '%else %if &action=OPEN %then %do;'; put '/* fix encoding */'; put 'OPTIONS NOBOMFILE;'; put '/**'; put '* check xengine type to avoid the below err message:'; put '* > Function is only valid for filerefs using the CACHE access method.'; put '*/'; put 'data _null_;'; put 'set sashelp.vextfl(where=(fileref="_WEBOUT"));'; put 'if xengine=''STREAM'' then do;'; put 'rc=stpsrv_header(''Content-type'',"text/html; encoding=utf-8");'; put 'end;'; put 'run;'; put '/* setup json */'; put 'data _null_;file &fref encoding=''utf-8'';'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutBEGIN<<'';'; put '%end;'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'run;'; put '%end;'; put '%else %if &action=ARR or &action=OBJ %then %do;'; put '%mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt,jref=&fref'; put ',engine=&jsonengine,missing=&missing,showmeta=&showmeta,maxobs=&maxobs'; put ')'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put '/* To avoid issues with _webout on EBI we use a temporary file */'; put 'filename _sjsref temp lrecl=131068;'; put '%if %str(&workobs) > 0 %then %do;'; put '/* if debug mode, send back first XX records of each work table also */'; put 'data;run;%let tempds=%scan(&syslast,2,.);'; put 'ods output Members=&tempds;'; put 'proc datasets library=WORK memtype=data;'; put '%local wtcnt;%let wtcnt=0;'; put 'data _null_;'; put 'set &tempds;'; put 'if not (upcase(name) =:"DATA"); /* ignore temp datasets */'; put 'i+1;'; put 'call symputx(cats(''wt'',i),name,''l'');'; put 'call symputx(''wtcnt'',i,''l'');'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put ",""WORK"":{";'; put '%do i=1 %to &wtcnt;'; put '%let wt=&&wt&i;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'dsid=open("WORK.&wt",''is'');'; put 'nlobs=attrn(dsid,''NLOBS'');'; put 'nvars=attrn(dsid,''NVARS'');'; put 'rc=close(dsid);'; put 'if &i>1 then put '',''@;'; put 'put " ""&wt"" : {";'; put 'put ''"nlobs":'' nlobs;'; put 'put '',"nvars":'' nvars;'; put '%mp_jsonout(OBJ,&wt,jref=_sjsref,dslabel=first10rows,showmeta=Y'; put ',maxobs=&workobs'; put ')'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put '%end;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put 'run;'; put '%end;'; put '/* close off json */'; put 'data _null_;file _sjsref mod encoding=''utf-8'';'; put 'length SYSPROCESSNAME syserrortext syswarningtext autoexec $512;'; put 'put ",""_DEBUG"" : ""&_debug"" ";'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'autoexec=quote(urlencode(trim(getoption(''autoexec''))));'; put 'put '',"AUTOEXEC" : '' autoexec;'; put 'put ",""MF_GETUSER"" : ""%mf_getuser()"" ";'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'put ",""SYSENCODING"" : ""&sysencoding"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSPROCESSID"" : ""&SYSPROCESSID"" ";'; put 'put ",""SYSPROCESSMODE"" : ""&SYSPROCESSMODE"" ";'; put 'SYSPROCESSNAME=quote(urlencode(cats(SYSPROCESSNAME)));'; put 'put ",""SYSPROCESSNAME"" : " SYSPROCESSNAME;'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put '',"SYSWARNINGTEXT" : '' syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'length memsize $32;'; put 'memsize="%sysfunc(INPUTN(%sysfunc(getoption(memsize)), best.),sizekmg.)";'; put 'memsize=quote(cats(memsize));'; put 'put '',"MEMSIZE" : '' memsize;'; put 'put "}" @;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutEND<<'';'; put '%end;'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjsref lrecl=1 recfm=n;'; put 'file &fref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjsref clear;'; put '%end;'; put '%mend mm_webout;'; put '%macro webout(action,ds,dslabel=,fmt=,missing=NULL,showmeta=NO,maxobs=MAX);'; put '%mm_webout(&action,ds=&ds,dslabel=&dslabel,fmt=&fmt'; put ',missing=&missing'; put ',showmeta=&showmeta'; put ',maxobs=&maxobs'; put ') %mend;'; put '/* provide additional debug info */'; put '%global _program;'; put '%put &=syscc;'; put '%put user=%mf_getuser();'; put '%put pgm=&_program;'; put '%put timestamp=%sysfunc(datetime(),datetime19.);'; put '* Service Variables start;'; put '* Service Variables end;'; put '* SAS Macros start;'; put '%macro mf_getapploc(pgm);'; put '%if "&pgm"="" %then %do;'; put '%if %symexist(_program) %then %let pgm=&_program;'; put '%else %do;'; put '%put &sysmacroname: No value provided and no _program variable available;'; put '%return;'; put '%end;'; put '%end;'; put '%local root;'; put '/**'; put '* First check we are not in the tests/macros folder (which has no subfolders)'; put '* or specifically in the testsetup or testteardown services'; put '*/'; put '%if %index(&pgm,/tests/macros/)'; put 'or %index(&pgm,/tests/testsetup)'; put 'or %index(&pgm,/tests/testteardown)'; put '%then %do;'; put '%let root=%substr(&pgm,1,%index(&pgm,/tests)-1);'; put '&root'; put '%return;'; put '%end;'; put '/**'; put '* Next, move up two levels to avoid matches on subfolder or service name'; put '*/'; put '%let root=%substr(&pgm,1,%length(&pgm)-%length(%scan(&pgm,-1,/))-1);'; put '%let root=%substr(&root,1,%length(&root)-%length(%scan(&root,-1,/))-1);'; put '%if %index(&root,/tests/) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/tests/)-1);'; put '%end;'; put '%else %if %index(&root,/services) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/services)-1);'; put '%end;'; put '%else %if %index(&root,/jobs) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/jobs)-1);'; put '%end;'; put '%else %put &sysmacroname: Could not find an app location from &pgm;'; put '&root'; put '%mend mf_getapploc ;'; put '%macro mf_getuniquefileref(prefix=_,maxtries=1000,lrecl=32767);'; put '%local rc fname;'; put '%if &prefix=0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%end;'; put '%else %do;'; put '%local x len;'; put '%let len=%eval(8-%length(&prefix));'; put '%let x=0;'; put '%do x=0 %to &maxtries;'; put '%let fname=&prefix%substr(%sysfunc(ranuni(0)),3,&len);'; put '%if %sysfunc(fileref(&fname)) > 0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%return;'; put '%end;'; put '%end;'; put '%put unable to find available fileref after &maxtries attempts;'; put '%end;'; put '%mend mf_getuniquefileref;'; put '%macro mp_abort(mac=mp_abort.sas, type=, msg=, iftrue=%str(1=1)'; put ', errds=work.mp_abort_errds'; put ', mode=REGULAR'; put ')/*/STORE SOURCE*/;'; put '%global sysprocessmode sysprocessname sasjs_stpsrv_header_loc sasjsprocessmode;'; put '%local fref fid i;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%put NOTE: /// mp_abort macro executing //;'; put '%if %length(&mac)>0 %then %put NOTE- called by &mac;'; put '%put NOTE - &msg;'; put '%if %symexist(_SYSINCLUDEFILEDEVICE)'; put '/* abort cancel FILE does not restart outside the INCLUDE on Viya 3.5 */'; put 'and %superq(SYSPROCESSNAME) ne %str(Compute Server)'; put '%then %do;'; put '%if "*&_SYSINCLUDEFILEDEVICE*" ne "**" %then %do;'; put 'data &errds;'; put 'iftrue=''1=1'';'; put 'length mac $100 msg $5000;'; put 'mac=symget(''mac'');'; put 'msg=symget(''msg'');'; put 'run;'; put 'data _null_;'; put 'abort cancel FILE;'; put 'run;'; put '%return;'; put '%end;'; put '%end;'; put '/* Web App Context */'; put '%if %symexist(_PROGRAM)'; put 'or %superq(SYSPROCESSNAME) = %str(Compute Server)'; put 'or &mode=INCLUDE'; put '%then %do;'; put 'options obs=max replace mprint;'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5"'; put '%then %do;'; put 'options nosyntaxcheck;'; put '%end;'; put '%if &mode=INCLUDE %then %do;'; put '%if %sysfunc(exist(&errds))=1 %then %do;'; put 'data _null_;'; put 'set &errds;'; put 'call symputx(''iftrue'',iftrue,''l'');'; put 'call symputx(''mac'',mac,''l'');'; put 'call symputx(''msg'',msg,''l'');'; put 'putlog (_all_)(=);'; put 'run;'; put '%if (&iftrue)=0 %then %return;'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: No include errors found;'; put '%return;'; put '%end;'; put '%end;'; put '/* extract log errs / warns, if exist */'; put '%local logloc logline;'; put '%global logmsg; /* capture global messages */'; put '%if %symexist(SYSPRINTTOLOG) %then %let logloc=&SYSPRINTTOLOG;'; put '%else %let logloc=%qsysfunc(getoption(LOG));'; put 'proc printto log=log;run;'; put '%let logline=0;'; put '%if %length(&logloc)>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input; putlog _infile_;'; put 'i=1;'; put 'retain logonce 0;'; put 'if ('; put '_infile_=:"%str(WARN)ING" or _infile_=:"%str(ERR)OR"'; put ') and logonce=0 then'; put 'do;'; put 'call symputx(''logline'',_n_);'; put 'logonce+1;'; put 'end;'; put 'run;'; put '/* capture log including lines BEFORE the err */'; put '%if &logline>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input;'; put 'i=1;'; put 'stoploop=0;'; put 'if _n_ ge &logline-15 and stoploop=0 then do until (i>22);'; put 'call symputx(''logmsg'',catx(''\n'',symget(''logmsg''),_infile_));'; put 'input;'; put 'i+1;'; put 'stoploop=1;'; put 'end;'; put 'if stoploop=1 then stop;'; put 'run;'; put '%end;'; put '%end;'; put '%if %symexist(SYS_JES_JOB_URI) %then %do;'; put '/* setup webout for Viya */'; put 'options nobomfile;'; put '%if "X&SYS_JES_JOB_URI.X"="XX" %then %do;'; put 'filename _webout temp lrecl=999999 mod;'; put '%end;'; put '%else %do;'; put 'filename _webout filesrvc parenturi="&SYS_JES_JOB_URI"'; put 'name="_webout.json" lrecl=999999 mod;'; put '%end;'; put '%end;'; put '%else %if %sysfunc(filename(fref,&sasjs_stpsrv_header_loc))=0 %then %do;'; put 'options nobomfile;'; put '/* set up http header for SASjs Server */'; put '%let fid=%sysfunc(fopen(&fref,A));'; put '%if &fid=0 %then %do;'; put '%put %str(ERR)OR: %sysfunc(sysmsg());'; put '%return;'; put '%end;'; put '%let rc=%sysfunc(fput(&fid,%str(Content-Type: application/json)));'; put '%let rc=%sysfunc(fwrite(&fid));'; put '%let rc=%sysfunc(fclose(&fid));'; put '%let rc=%sysfunc(filename(&fref));'; put '%end;'; put '/* send response in SASjs JSON format */'; put 'data _null_;'; put 'file _webout mod lrecl=32000 encoding=''utf-8'';'; put 'length msg syswarningtext syserrortext $32767 mode $10 ;'; put 'sasdatetime=datetime();'; put 'msg=symget(''msg'');'; put '%if &logline>0 %then %do;'; put 'msg=cats(msg,''\n\nLog Extract:\n'',symget(''logmsg''));'; put '%end;'; put '/* escape the escapes */'; put 'msg=tranwrd(msg,''\'',''\\'');'; put '/* escape the quotes */'; put 'msg=tranwrd(msg,''"'',''\"'');'; put '/* ditch the CRLFs as chrome complains */'; put 'msg=compress(msg,,''kw'');'; put '/* quote without quoting the quotes (which are escaped instead) */'; put 'msg=cats(''"'',msg,''"'');'; put 'if symexist(''_debug'') then debug=quote(trim(symget(''_debug'')));'; put 'else debug=''""'';'; put 'if symget(''sasjsprocessmode'')=''Stored Program'' then mode=''SASJS'';'; put 'if mode ne ''SASJS'' then put ''>>weboutBEGIN<<'';'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'put '',"sasjsAbort" : [{'';'; put 'put '' "MSG":'' msg ;'; put 'put '' ,"MAC": "'' "&mac" ''"}]'';'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'put '',"_DEBUG":'' debug ;'; put 'if symexist(''_metauser'') then do;'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put 'end;'; put 'if symexist(''SYS_JES_JOB_URI'') then do;'; put 'SYS_JES_JOB_URI=quote(trim(symget(''SYS_JES_JOB_URI'')));'; put 'put '',"SYS_JES_JOB_URI": '' SYS_JES_JOB_URI;'; put 'end;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put ",""SYSWARNINGTEXT"" : " syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'put "}" ;'; put 'if mode ne ''SASJS'' then put ''>>weboutEND<<'';'; put 'run;'; put '%put _all_;'; put '%if "&sysprocessmode " = "SAS Stored Process Server " %then %do;'; put 'data _null_;'; put 'putlog ''stpsrvset program err and syscc'';'; put 'rc=stpsrvset(''program error'', 0);'; put 'call symputx("syscc",0,"g");'; put 'run;'; put '%if &sysscp=WIN'; put 'and 1=0 /* deprecating this logic until we figure out a consistent abort */'; put 'and "%substr(%str(&sysvlong ),1,8)"="9.04.01M"'; put 'and "%substr(%str(&sysvlong ),9,1)">"5" %then %do;'; put '/* skip approach (below) does not work in windows m6+ envs */'; put 'endsas;'; put '%end;'; put '%else %do;'; put '/**'; put '* endsas kills 9.4m3 deployments by orphaning multibridges.'; put '* Abort variants are ungraceful (non zero return code)'; put '* This approach lets SAS run silently until the end :-)'; put '* Caution - fails when called within a %include within a macro'; put '* Use mp_include() to handle this.'; put '*/'; put 'filename skip temp;'; put 'data _null_;'; put 'file skip;'; put 'put ''%macro skip();'';'; put 'comment ''%mend skip; -> fix lint '';'; put 'put ''%macro skippy();'';'; put 'comment ''%mend skippy; -> fix lint '';'; put 'run;'; put '%inc skip;'; put '%end;'; put '%end;'; put '%else %if "&sysprocessmode " = "SAS Compute Server " %then %do;'; put '/* endsas kills the session making it harder to fetch results */'; put 'data _null_;'; put 'syswarningtext=symget(''syswarningtext'');'; put 'syserrortext=symget(''syserrortext'');'; put 'abort_msg=symget(''msg'');'; put 'syscc=symget(''syscc'');'; put 'sysuserid=symget(''sysuserid'');'; put 'iftrue=symget(''iftrue'');'; put 'put (_all_)(/=);'; put 'call symputx(''syscc'',0);'; put 'abort cancel nolist;'; put 'run;'; put '%end;'; put '%else %do;'; put '%abort cancel;'; put '%end;'; put '%end;'; put '%else %do;'; put '%put _all_;'; put '%abort cancel;'; put '%end;'; put '%mend mp_abort;'; put '/** @endcond */'; put '%macro mm_getstpcode('; put 'tree=/User Folders/sasdemo/somestp'; put ',name='; put ',outloc=0'; put ',outref=0'; put ',mDebug=1'; put ',showlog=NO'; put ');'; put '%local mD;'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing &sysmacroname..sas;'; put '%&mD.put _local_;'; put '%if %length(&name)>0 %then %let name=/&name;'; put '/* first, check if STP exists */'; put '%local tsuri;'; put '%let tsuri=stopifempty ;'; put 'data _null_;'; put 'format type uri tsuri value $200.;'; put 'call missing (of _all_);'; put 'path="&tree&name(StoredProcess)";'; put '/* first, find the STP ID */'; put 'if metadata_pathobj("",path,"StoredProcess",type,uri)>0 then do;'; put '/* get sourcecode */'; put 'cnt=1;'; put 'do while (metadata_getnasn(uri,"Notes",cnt,tsuri)>0);'; put 'rc=metadata_getattr(tsuri,"Name",value);'; put '&mD.put tsuri= value=;'; put 'if value="SourceCode" then do;'; put '/* found it! */'; put 'rc=metadata_getattr(tsuri,"Id",value);'; put 'call symputx(''tsuri'',value,''l'');'; put 'stop;'; put 'end;'; put 'cnt+1;'; put 'end;'; put 'end;'; put 'else put (_all_)(=);'; put 'run;'; put '%mp_abort(iftrue= (&tsuri=stopifempty)'; put ',mac=mm_getstpcode'; put ',msg=%str(&tree&name.(StoredProcess) not found!)'; put ')'; put '/**'; put '* Now we can extract the textstore'; put '*/'; put 'filename __getdoc temp lrecl=10000000;'; put 'proc metadata'; put 'in="$METAREPOSITORY'; put ''; put 'SAS1"'; put 'out=__getdoc ;'; put 'run;'; put '/* find the beginning of the text */'; put '%local start;'; put 'data _null_;'; put 'infile __getdoc lrecl=10000;'; put 'input;'; put 'start=index(_infile_,''StoredText="'');'; put 'if start then do;'; put 'call symputx("start",start+11);'; put '*putlog ''"'' _infile_ ''"'';'; put 'end;'; put 'stop;'; put '%local outeng;'; put '%if "&outloc"="0" %then %let outeng=TEMP;'; put '%else %let outeng="&outloc";'; put '%local fref;'; put '%if &outref=0 %then %let fref=%mf_getuniquefileref();'; put '%else %let fref=&outref;'; put '/* read the content, byte by byte, resolving escaped chars */'; put 'filename &fref &outeng lrecl=100000;'; put 'data _null_;'; put 'length filein 8 fileid 8;'; put 'filein = fopen("__getdoc","I",1,"B");'; put 'fileid = fopen("&fref","O",1,"B");'; put 'rec = "20"x;'; put 'length entity $6;'; put 'do while(fread(filein)=0);'; put 'x+1;'; put 'if x>&start then do;'; put 'rc = fget(filein,rec,1);'; put 'if rec=''"'' then leave;'; put 'else if rec="&" then do;'; put 'entity=rec;'; put 'do until (rec=";");'; put 'if fread(filein) ne 0 then goto getout;'; put 'rc = fget(filein,rec,1);'; put 'entity=cats(entity,rec);'; put 'end;'; put 'select (entity);'; put 'when (''&'' ) rec=''&'' ;'; put 'when (''<'' ) rec=''<'' ;'; put 'when (''>'' ) rec=''>'' ;'; put 'when (''''') rec="''" ;'; put 'when (''"'') rec=''"'' ;'; put 'when ('' '') rec=''0A''x;'; put 'when ('' '') rec=''0D''x;'; put 'when (''$'' ) rec=''$'' ;'; put 'when ('' '') rec=''09''x;'; put 'otherwise putlog "%str(WARN)ING: missing value for " entity=;'; put 'end;'; put 'rc =fput(fileid, substr(rec,1,1));'; put 'rc =fwrite(fileid);'; put 'end;'; put 'else do;'; put 'rc =fput(fileid,rec);'; put 'rc =fwrite(fileid);'; put 'end;'; put 'end;'; put 'end;'; put 'getout:'; put 'rc=fclose(filein);'; put 'rc=fclose(fileid);'; put 'run;'; put '%if &showlog=YES %then %do;'; put 'data _null_;'; put 'infile &fref lrecl=32767 end=last;'; put 'input;'; put 'if _n_=1 then putlog ''>>stpcodeBEGIN<<'';'; put 'putlog _infile_;'; put 'if last then putlog ''>>stpcodeEND<<'';'; put 'run;'; put '%end;'; put 'filename __getdoc clear;'; put '%if &outref=0 %then %do;'; put 'filename &fref clear;'; put '%end;'; put '%mend mm_getstpcode;'; put '%macro dc_getsettings();'; put '%global _program;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(syscc=&syscc on entry (&syswarningtext &syserrortext))'; put ')'; put '%if %length(&_PROGRAM)>1 %then %let root=&_program;'; put '%else %do;'; put '%global _metauser;'; put '%let _metauser=&sysuserid;'; put '/* to mimic a "real" _program we need to give a dummy role and stp name */'; put '%let root=/dummyRole/dummyName;'; put '%end;'; put '/* the DC precode is stored in the Admin folder in the root of'; put 'the project. Lets find that root. */'; put '%put &=root;'; put '%let root=%mf_getapploc();'; put '%put &=root;'; put '/* Now we know the root location we can retrieve the params */'; put '%let temploc=%sysfunc(pathname(work))/settings.txt;'; put '%mm_getstpcode(tree=&root/services/public'; put ',name=Data_Controller_Settings'; put ',outloc=&temploc'; put ')'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Unable to run getstpcode)'; put ')'; put 'filename _getsets "&temploc" lrecl=2000;'; put '/*'; put 'Do not use mp_include here - this puts a copy in every service, which creates'; put 'compilation problems when calling services from mp_include'; put '*/'; put '%inc _getsets/source2;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Problem running &sysmacroname (&syswarningtext &syserrortext))'; put ')'; put '%mend dc_getsettings;'; put '%macro mf_fmtdttm('; put ')/*/STORE SOURCE*/;'; put '%if "&sysver"="9.2" or "&sysver"="9.3"'; put 'or ("&sysver"="9.4" and "%substr(&SYSVLONG,9,1)" le "3")'; put 'or "%substr(&sysver,1,1)"="4"'; put 'or "%substr(&sysver,1,1)"="5"'; put '%then %do;DATETIME19.3%end;'; put '%else %do;E8601DT26.6%end;'; put '%mend mf_fmtdttm;'; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '%macro mp_init(prefix=SASJS'; put ')/*/STORE SOURCE*/;'; put '%if %symexist(SASJS_PREFIX) %then %return; /* only run once */'; put '%global'; put 'SASJS_PREFIX /* the ONLY hard-coded global macro variable in SASjs */'; put '&prefix._FUNCTIONS /* used in mcf_init() to track core function compilation */'; put '&prefix._INIT_NUM /* initialisation time as numeric */'; put '&prefix._INIT_DTTM /* initialisation time in E8601DT26.6 format */'; put '&prefix.WORK /* avoid typing %sysfunc(pathname(work)) every time */'; put ';'; put '%let sasjs_prefix=&prefix;'; put 'data _null_;'; put 'dttm=datetime();'; put 'call symputx("&sasjs_prefix._init_num",dttm,''g'');'; put 'call symputx("&sasjs_prefix._init_dttm",put(dttm,E8601DT26.6),''g'');'; put 'call symputx("&sasjs_prefix.work",pathname(''WORK''),''g'');'; put 'run;'; put 'options'; put 'compress=CHAR /* default is none so ensure we have something! */'; put 'datastmtchk=ALLKEYWORDS /* protection from overwriting input datasets */'; put 'errorcheck=STRICT /* catch errs in libname/filename statements */'; put 'fmterr /* ensure err when a format cannot be found */'; put 'mergenoby=%str(ERR)OR /* throw err when a merge has no BY variables */'; put 'missing=. /* changing this can cause hard to detect errs */'; put 'noquotelenmax /* avoid warnings for long strings */'; put 'noreplace /* avoid overwriting permanent datasets */'; put 'ps=max /* reduce log size slightly */'; put 'ls=max /* reduce log even more and avoid word truncation */'; put 'validmemname=COMPATIBLE /* avoid special characters etc in table names */'; put 'validvarname=V7 /* avoid special characters etc in variable names */'; put 'varinitchk=%str(ERR)OR /* avoid data mistakes from variable name typos */'; put 'varlenchk=%str(ERR)OR /* fail hard if truncation (data loss) can result */'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5" %then %do;'; put 'noautocorrect /* disallow misspelled procedure names */'; put 'dsoptions=note2err /* undocumented - convert bad NOTEs to ERRs */'; put '%end;'; put ';'; put '%mend mp_init;'; put '%macro mpeinit(fetch=YES);'; put '%global mpeinit'; put 'mpeadmins /* group with unrestricted Meditor access */'; put 'mpelocapprovals /* location for landing and staging files */'; put 'mpelib /* location of configuration tables for DC */'; put 'dc_repo_users /* location of user / group metadata */'; put 'dc_licence_key /* extracted in dc_getsettings */'; put 'dc_activation_key /* extracted in dc_getsettings */'; put 'dc_locale /* extracted in dc_getsettings */'; put 'dc_dttmtfmt /* can be overridden in dc_getsettings */'; put '_debug'; put ';'; put '%if &mpeinit=1 %then %return;'; put '%else %let mpeinit=1;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(Problem on service startup (&syswarningtext &syserrortext))'; put ')'; put '%mp_init()'; put '%if &fetch=YES %then %do;'; put '%webout(FETCH)'; put '%end;'; put '%global _CLIENTNAME;'; put '%mp_abort(iftrue= (&_CLIENTNAME=SAS Enterprise Guide)'; put ',mac=&_program..sas'; put ',msg=%str(Data Controller is a web app and should not be executed from EG)'; put ')'; put 'options urlencoding=utf8 nobomfile lrecl=32767;'; put '%let perf=%sysfunc(datetime());'; put '%put perfdiff: 0;'; put '%let dc_locale=SYSTEM; /* default if not set */'; put '/**'; put '* E8601DT26.6 has widest database support - but not all SAS flavours can'; put '* handle it. Override in the settings STP if needed.'; put '*/'; put 'data _null_;'; put 'dc_dttmtfmt=''"%sysfunc(datetime(),''!!"%mf_fmtdttm()"!!'')"dt'';'; put 'call symputx(''dc_dttmtfmt'',dc_dttmtfmt);'; put 'put dc_dttmtfmt=;'; put 'run;'; put '%put &=dc_dttmtfmt;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc prior to dc_getsettings)'; put ')'; put '%dc_getsettings()'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc after dc_getsettings)'; put ')'; put 'data _null_;'; put 'set &DC_LIBREF..mpe_config(where=('; put 'var_scope="DC"'; put 'and &dc_dttmtfmt lt tx_to'; put 'and var_active=1'; put '));'; put 'call symputx(var_name,var_value,''G'');'; put 'putlog var_name "=" var_value;'; put 'run;'; put '%let mpelib=&dc_libref;'; put '%let mpeadmins=&dc_admin_group;'; put '%let mpelocapprovals=&dc_staging_area;'; put '%let dc_repo_users=&dc_repo_users;'; put '%if &dc_locale ne SYSTEM %then %do;'; put 'options locale=&dc_locale;'; put '%end;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(Problem during compilation or with STP precode (&syswarningtext))'; put ')'; put '%mend mpeinit;'; put '%macro mf_mval(var);'; put '%if %symexist(&var) %then %do;'; put '%superq(&var)'; put '%end;'; put '%mend mf_mval;'; put '%macro mf_trimstr(basestr,trimstr);'; put '%local baselen trimlen trimval;'; put '/* return if basestr is shorter than trimstr (or 0) */'; put '%let baselen=%length(%superq(basestr));'; put '%let trimlen=%length(%superq(trimstr));'; put '%if &baselen < &trimlen or &baselen=0 %then %return;'; put '/* obtain the characters from the end of basestr */'; put '%let trimval=%qsubstr(%superq(basestr)'; put ',%length(%superq(basestr))-&trimlen+1'; put ',&trimlen);'; put '/* compare and if matching, chop it off! */'; put '%if %superq(basestr)=%superq(trimstr) %then %do;'; put '%return;'; put '%end;'; put '%else %if %superq(trimval)=%superq(trimstr) %then %do;'; put '%qsubstr(%superq(basestr),1,%length(%superq(basestr))-&trimlen)'; put '%end;'; put '%else %do;'; put '&basestr'; put '%end;'; put '%mend mf_trimstr;'; put '%macro mf_getplatform(switch'; put ')/*/STORE SOURCE*/;'; put '%local a b c;'; put '%if &switch.NONE=NONE %then %do;'; put '%if %symexist(sasjsprocessmode) %then %do;'; put '%if &sasjsprocessmode=Stored Program %then %do;'; put 'SASJS'; put '%return;'; put '%end;'; put '%end;'; put '%if %symexist(sysprocessmode) %then %do;'; put '%if "&sysprocessmode"="SAS Object Server"'; put 'or "&sysprocessmode"= "SAS Compute Server" %then %do;'; put 'SASVIYA'; put '%end;'; put '%else %if "&sysprocessmode"="SAS Stored Process Server"'; put 'or "&sysprocessmode"="SAS Workspace Server"'; put '%then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if %symexist(_metaport) or %symexist(_metauser) %then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if &switch=SASSTUDIO %then %do;'; put '/* return the version of SAS Studio else 0 */'; put '%if %mf_mval(_CLIENTAPP)=%str(SAS Studio) %then %do;'; put '%let a=%mf_mval(_CLIENTVERSION);'; put '%let b=%scan(&a,1,.);'; put '%if %eval(&b >2) %then %do;'; put '&b'; put '%end;'; put '%else 0;'; put '%end;'; put '%else 0;'; put '%end;'; put '%else %if &switch=VIYARESTAPI %then %do;'; put '%mf_trimstr(%sysfunc(getoption(servicesbaseurl)),/)'; put '%end;'; put '%mend mf_getplatform;'; put '%macro mpeterm();'; put '%local oldloc;'; put 'data _null_;'; put 'if symexist(''SYSPRINTTOLOG'') then oldloc=symget(''SYSPRINTTOLOG'');'; put 'else oldloc=getoption(''LOG'');'; put 'if subpad(oldloc,1,1) not in (''"'',"''",'' '') then oldloc=quote(cats(oldloc));'; put 'call symputx(''oldloc'',oldloc,''l'');'; put 'run;'; put '%if %length(&oldloc)>0 %then %do;'; put 'proc printto log=log;'; put 'run;'; put 'data _null_;'; put 'infile &oldloc;'; put 'input; putlog _infile_;'; put 'run;'; put '%end;'; put '%if %sysfunc(exist(&dc_libref..mpe_requests)) and %mf_getplatform() ne SASVIYA'; put '%then %do;'; put 'data ;'; put 'if 0 then set &dc_libref..mpe_requests;'; put 'request_dttm=%sysfunc(datetime());'; put 'request_user="%mf_getuser()";'; put 'request_service="%scan(&_program,-2,/)/%scan(&_program,-1,/)";'; put 'request_params='''';'; put 'output;stop;'; put 'proc append base=&dc_libref..mpe_requests data=&syslast force nowarn;'; put 'run;'; put '%end;'; put '%mend mpeterm;'; put '%macro mm_assignlib('; put 'libref'; put ',mAbort=HARD'; put ')/*/STORE SOURCE*/;'; put '%local mp_abort msg;'; put '%let mp_abort=0;'; put '%if %sysfunc(libref(&libref)) %then %do;'; put 'data _null_;'; put 'length liburi LibName msg $200;'; put 'call missing(of _all_);'; put 'nobj=metadata_getnobj("omsobj:SASLibrary?@Libref=''&libref''",1,liburi);'; put 'if nobj=1 then do;'; put 'rc=metadata_getattr(liburi,"Name",LibName);'; put '/* now try and assign it */'; put 'if libname("&libref",,''meta'',cats(''liburi="'',liburi,''";'')) ne 0 then do;'; put 'putlog "&libref could not be assigned";'; put 'putlog liburi=;'; put '/**'; put '* Fetch the system message for display in the abort modal. This is'; put '* not always helpful though. One example, previously received:'; put '* NOTE: Libref XX refers to the same library metadata as libref XX.'; put '*/'; put 'msg=sysmsg();'; put 'if msg=:''ERROR: Libref SAVE is not assigned.'' then do;'; put 'msg=catx(" ",'; put '"Could not assign %upcase(&libref).",'; put '"Please check metadata permissions! Libname:",libname,'; put '"Liburi:",liburi'; put ');'; put 'end;'; put 'else if msg="ERROR: User does not have appropriate authorization "!!'; put '"level for library SAVE."'; put 'then do;'; put 'msg=catx(" ",'; put '"ERROR: User does not have appropriate authorization level",'; put '"for library %upcase(&libref), libname:",libname,'; put '"Liburi:",liburi'; put ');'; put 'end;'; put 'call symputx(''msg'',msg,''l'');'; put 'if "&mabort"=''HARD'' then call symputx(''mp_abort'',1,''l'');'; put 'end;'; put 'else do;'; put 'put (_all_)(=);'; put 'call symputx(''libname'',libname,''L'');'; put 'call symputx(''liburi'',liburi,''L'');'; put 'end;'; put 'end;'; put 'else if nobj>1 then do;'; put 'if "&mabort"=''HARD'' then call symputx(''mp_abort'',1);'; put 'call symputx(''msg'',"More than one library with libref=&libref");'; put 'end;'; put 'else do;'; put 'if "&mabort"=''HARD'' then call symputx(''mp_abort'',1);'; put 'call symputx(''msg'',"Library &libref not found in metadata");'; put 'end;'; put 'run;'; put '%put NOTE: &msg;'; put '%end;'; put '%else %do;'; put '%put NOTE: Library &libref is already assigned;'; put '%end;'; put '%mp_abort(iftrue= (&mp_abort=1)'; put ',mac=mm_assignlib.sas'; put ',msg=%superq(msg)'; put ')'; put '%mend mm_assignlib;'; put '/** @cond */'; put '%macro mf_getengine(libref'; put ')/*/STORE SOURCE*/;'; put '%local dsid engnum rc engine;'; put '/* in case the parameter is a libref.tablename, pull off just the libref */'; put '%let libref = %upcase(%scan(&libref, 1, %str(.)));'; put '%let dsid=%sysfunc('; put 'open(sashelp.vlibnam(where=(libname="%upcase(&libref)")),i)'; put ');'; put '%if (&dsid ^= 0) %then %do;'; put '%let engnum=%sysfunc(varnum(&dsid,ENGINE));'; put '%let rc=%sysfunc(fetch(&dsid));'; put '%let engine=%sysfunc(getvarc(&dsid,&engnum));'; put '%put &libref. ENGINE is &engine.;'; put '%let rc= %sysfunc(close(&dsid));'; put '%end;'; put '%upcase(&engine)'; put '%mend mf_getengine;'; put '/** @endcond */'; put '%macro mm_assigndirectlib('; put 'libref'; put ',open_passthrough='; put ',sql_options='; put ',mDebug=0'; put ',mAbort=0'; put ')/*/STORE SOURCE*/;'; put '%local mD;'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing mm_assigndirectlib.sas;'; put '%&mD.put _local_;'; put '%if &mAbort=1 %then %let mAbort=;'; put '%else %let mAbort=%str(*);'; put '%&mD.put NOTE: Creating direct (non META) connection to &libref library;'; put '%local cur_engine;'; put '%let cur_engine=%mf_getengine(&libref);'; put '%if &cur_engine ne META and &cur_engine ne %then %do;'; put '%put NOTE: &libref already has a direct (&cur_engine) libname connection;'; put '%return;'; put '%end;'; put '%else %if %upcase(&libref)=WORK %then %do;'; put '%put NOTE: We already have a direct connection to WORK :-) ;'; put '%return;'; put '%end;'; put '/* need to determine the library ENGINE first */'; put '%local engine;'; put 'data _null_;'; put 'length lib_uri engine $256;'; put 'call missing (of _all_);'; put '/* get URI for the particular library */'; put 'rc1=metadata_getnobj("omsobj:SASLibrary?@Libref =''&libref''",1,lib_uri);'; put '/* get the Engine attribute of the previous object */'; put 'rc2=metadata_getattr(lib_uri,''Engine'',engine);'; put 'putlog "mm_assigndirectlib for &libref:" rc1= lib_uri= rc2= engine=;'; put 'call symputx("liburi",lib_uri,''l'');'; put 'call symputx("engine",engine,''l'');'; put 'run;'; put '/* now obtain engine specific connection details */'; put '%if &engine=BASE %then %do;'; put '%&mD.put NOTE: Retrieving BASE library path;'; put 'data _null_;'; put 'length up_uri $256 path cat_path $1024;'; put 'retain cat_path;'; put 'call missing (of _all_);'; put '/* get all the filepaths of the UsingPackages association */'; put 'i=1;'; put 'rc3=metadata_getnasn("&liburi",''UsingPackages'',i,up_uri);'; put 'do while (rc3>0);'; put '/* get the DirectoryName attribute of the previous object */'; put 'rc4=metadata_getattr(up_uri,''DirectoryName'',path);'; put 'if i=1 then path = ''("''!!trim(path)!!''" '';'; put 'else path ='' "''!!trim(path)!!''" '';'; put 'cat_path = trim(cat_path) !! " " !! trim(path) ;'; put 'i+1;'; put 'rc3=metadata_getnasn("&liburi",''UsingPackages'',i,up_uri);'; put 'end;'; put 'cat_path = trim(cat_path) !! ")";'; put '&mD.putlog "NOTE: Getting physical path for &libref library";'; put '&mD.putlog rc3= up_uri= rc4= cat_path= path=;'; put '&mD.putlog "NOTE: Libname cmd will be:";'; put '&mD.putlog "libname &libref" cat_path;'; put 'call symputx("filepath",cat_path,''l'');'; put 'run;'; put '%if %sysevalf(&sysver<9.4) %then %do;'; put 'libname &libref &filepath;'; put '%end;'; put '%else %do;'; put '/* apply the new filelocks option to cater for temporary locks */'; put 'libname &libref &filepath filelockwait=5;'; put '%end;'; put '%end;'; put '%else %if &engine=REMOTE %then %do;'; put 'data x;'; put 'length rcCon rcProp rc k 3 uriCon uriProp PropertyValue PropertyName'; put 'Delimiter $256 properties $2048;'; put 'retain properties;'; put 'rcCon = metadata_getnasn("&liburi", "LibraryConnection", 1, uriCon);'; put 'rcProp = metadata_getnasn(uriCon, "Properties", 1, uriProp);'; put 'k = 1;'; put 'rcProp = metadata_getnasn(uriCon, "Properties", k, uriProp);'; put 'do while (rcProp > 0);'; put 'rc = metadata_getattr(uriProp , "DefaultValue",PropertyValue);'; put 'rc = metadata_getattr(uriProp , "PropertyName",PropertyName);'; put 'rc = metadata_getattr(uriProp , "Delimiter",Delimiter);'; put 'properties = trim(properties) !! " " !! trim(PropertyName)'; put '!! trim(Delimiter) !! trim(PropertyValue);'; put 'output;'; put 'k+1;'; put 'rcProp = metadata_getnasn(uriCon, "Properties", k, uriProp);'; put 'end;'; put '%&mD.put NOTE: Getting properties for REMOTE SHARE &libref library;'; put '&mD.put _all_;'; put '%&mD.put NOTE: Libname cmd will be:;'; put '%&mD.put libname &libref &engine &properties slibref=&libref;'; put 'call symputx ("properties",trim(properties),''l'');'; put 'run;'; put 'libname &libref &engine &properties slibref=&libref;'; put '%end;'; put '%else %if &engine=OLEDB %then %do;'; put '%&mD.put NOTE: Retrieving OLEDB connection details;'; put 'data _null_;'; put 'length domain datasource provider properties schema'; put 'connx_uri domain_uri conprop_uri lib_uri schema_uri value $256.;'; put 'call missing (of _all_);'; put '/* get source connection ID */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,connx_uri);'; put '/* get connection domain */'; put 'rc1=metadata_getnasn(connx_uri,''Domain'',1,domain_uri);'; put 'rc2=metadata_getattr(domain_uri,''Name'',domain);'; put '&mD.putlog / ''NOTE: '' // ''NOTE- connection id: '' connx_uri ;'; put '&mD.putlog ''NOTE- domain: '' domain;'; put '/* get DSN and PROVIDER from connection properties */'; put 'i=0;'; put 'do until (rc<0);'; put 'i+1;'; put 'rc=metadata_getnasn(connx_uri,''Properties'',i,conprop_uri);'; put 'rc2=metadata_getattr(conprop_uri,''Name'',value);'; put 'if value=''Connection.OLE.Property.DATASOURCE.Name.xmlKey.txt'' then do;'; put 'rc3=metadata_getattr(conprop_uri,''DefaultValue'',datasource);'; put 'end;'; put 'else if value=''Connection.OLE.Property.PROVIDER.Name.xmlKey.txt'' then do;'; put 'rc4=metadata_getattr(conprop_uri,''DefaultValue'',provider);'; put 'end;'; put 'else if value=''Connection.OLE.Property.PROPERTIES.Name.xmlKey.txt'' then'; put 'do;'; put 'rc5=metadata_getattr(conprop_uri,''DefaultValue'',properties);'; put 'end;'; put 'end;'; put '&mD.putlog ''NOTE- dsn/provider/properties: '' /'; put 'datasource provider properties;'; put '&mD.putlog ''NOTE- schema: '' schema // ''NOTE-'';'; put '/* get SCHEMA */'; put 'rc6=metadata_getnasn("&liburi",''UsingPackages'',1,lib_uri);'; put 'rc7=metadata_getattr(lib_uri,''SchemaName'',schema);'; put 'call symputx(''SQL_domain'',domain,''l'');'; put 'call symputx(''SQL_dsn'',datasource,''l'');'; put 'call symputx(''SQL_provider'',provider,''l'');'; put 'call symputx(''SQL_properties'',properties,''l'');'; put 'call symputx(''SQL_schema'',schema,''l'');'; put 'run;'; put '%if %length(&open_passthrough)>0 %then %do;'; put 'proc sql &sql_options;'; put 'connect to OLEDB as &open_passthrough(INSERT_SQL=YES'; put '/* need additional properties to make this work */'; put 'properties=(''Integrated Security''=SSPI'; put '''Persist Security Info''=True'; put '%sysfunc(compress(%str(&SQL_properties),%str(())))'; put ')'; put 'DATASOURCE=&sql_dsn PROMPT=NO'; put 'PROVIDER=&sql_provider SCHEMA=&sql_schema CONNECTION = GLOBAL);'; put '%end;'; put '%else %do;'; put 'LIBNAME &libref OLEDB PROPERTIES=&sql_properties'; put 'DATASOURCE=&sql_dsn PROVIDER=&sql_provider SCHEMA=&sql_schema'; put '%if %length(&sql_domain)>0 %then %do;'; put 'authdomain="&sql_domain"'; put '%end;'; put 'connection=shared;'; put '%end;'; put '%end;'; put '%else %if &engine=ODBC %then %do;'; put '%&mD.put NOTE: Retrieving ODBC connection details;'; put 'data _null_;'; put 'length connx_uri conprop_uri value datasource up_uri schema domprop_uri authdomain $256.;'; put 'call missing (of _all_);'; put '/* get source connection ID */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,connx_uri);'; put '/* get connection properties */'; put 'i=0;'; put 'do until (rc2<0);'; put 'i+1;'; put 'rc2=metadata_getnasn(connx_uri,''Properties'',i,conprop_uri);'; put 'rc3=metadata_getattr(conprop_uri,''Name'',value);'; put 'if value=''Connection.ODBC.Property.DATASRC.Name.xmlKey.txt'' then do;'; put 'rc4=metadata_getattr(conprop_uri,''DefaultValue'',datasource);'; put 'rc2=-1;'; put 'end;'; put 'end;'; put '/* get auth domain */'; put 'autrc=metadata_getnasn(connx_uri,"Domain",1,domprop_uri);'; put 'arc=metadata_getattr(domprop_uri,"Name",authdomain);'; put 'if not missing(authdomain) then authdomain=cats(''AUTHDOMAIN='',authdomain);'; put 'call symputx(''authdomain'',authdomain,''l'');'; put '/* get SCHEMA */'; put 'rc6=metadata_getnasn("&liburi",''UsingPackages'',1,up_uri);'; put 'rc7=metadata_getattr(up_uri,''SchemaName'',schema);'; put '&mD.put rc= connx_uri= rc2= conprop_uri= rc3= value= rc4= datasource='; put 'rc6= up_uri= rc7= schema=;'; put 'call symputx(''SQL_schema'',schema,''l'');'; put 'call symputx(''SQL_dsn'',datasource,''l'');'; put 'run;'; put '%if %length(&open_passthrough)>0 %then %do;'; put 'proc sql &sql_options;'; put 'connect to ODBC as &open_passthrough'; put '(INSERT_SQL=YES DATASRC=&sql_dsn. CONNECTION=global);'; put '%end;'; put '%else %do;'; put 'libname &libref ODBC DATASRC=&sql_dsn SCHEMA=&sql_schema &authdomain;'; put '%end;'; put '%end;'; put '%else %if &engine=POSTGRES %then %do;'; put '%put NOTE: Obtaining POSTGRES library details;'; put 'data _null_;'; put 'length database ignore_read_only_columns direct_exe preserve_col_names'; put 'preserve_tab_names server schema authdomain user password'; put 'prop name value uri urisrc $256.;'; put 'call missing (of _all_);'; put '/* get database value */'; put 'prop=''Connection.DBMS.Property.DB.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,database,"");'; put 'if database^='''' then database=''database=''!!quote(trim(database));'; put 'call symputx(''database'',database,''l'');'; put '/* get IGNORE_READ_ONLY_COLUMNS value */'; put 'prop=''Library.DBMS.Property.DBIROC.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,ignore_read_only_columns,"");'; put 'if ignore_read_only_columns^='''' then ignore_read_only_columns='; put '''ignore_read_only_columns=''!!ignore_read_only_columns;'; put 'call symputx(''ignore_read_only_columns'',ignore_read_only_columns,''l'');'; put '/* get DIRECT_EXE value */'; put 'prop=''Library.DBMS.Property.DirectExe.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,direct_exe,"");'; put 'if direct_exe^='''' then direct_exe=''direct_exe=''!!direct_exe;'; put 'call symputx(''direct_exe'',direct_exe,''l'');'; put '/* get PRESERVE_COL_NAMES value */'; put 'prop=''Library.DBMS.Property.PreserveColNames.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,preserve_col_names,"");'; put 'if preserve_col_names^='''' then preserve_col_names='; put '''preserve_col_names=''!!preserve_col_names;'; put 'call symputx(''preserve_col_names'',preserve_col_names,''l'');'; put '/* get PRESERVE_TAB_NAMES value */'; put '/* be careful with PRESERVE_TAB_NAMES=YES - it will mean your table will'; put 'become case sensitive!! */'; put 'prop=''Library.DBMS.Property.PreserveTabNames.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,preserve_tab_names,"");'; put 'if preserve_tab_names^='''' then preserve_tab_names='; put '''preserve_tab_names=''!!preserve_tab_names;'; put 'call symputx(''preserve_tab_names'',preserve_tab_names,''l'');'; put '/* get SERVER value */'; put 'if metadata_getnasn("&liburi","LibraryConnection",1,uri)>0 then do;'; put 'prop=''Connection.DBMS.Property.SERVER.Name.xmlKey.txt'';'; put 'rc=metadata_getprop(uri,prop,server,"");'; put 'end;'; put 'if server^='''' then server=''server=''!!quote(cats(server));'; put 'call symputx(''server'',server,''l'');'; put '/* get SCHEMA value */'; put 'if metadata_getnasn("&liburi","UsingPackages",1,uri)>0 then do;'; put 'rc=metadata_getattr(uri,"SchemaName",schema);'; put 'end;'; put 'if schema^='''' then schema=''schema=''!!schema;'; put 'call symputx(''schema'',schema,''l'');'; put '/* get AUTHDOMAIN value */'; put '/* this is only useful if the user account contains that auth domain'; put 'if metadata_getnasn("&liburi","DefaultLogin",1,uri)>0 then do;'; put 'rc=metadata_getnasn(uri,"Domain",1,urisrc);'; put 'rc=metadata_getattr(urisrc,"Name",authdomain);'; put 'end;'; put 'if authdomain^='''' then authdomain=''authdomain=''!!quote(trim(authdomain));'; put '*/'; put 'call symputx(''authdomain'',authdomain,''l'');'; put '/* get user & pass */'; put 'if authdomain='''' & metadata_getnasn("&liburi","DefaultLogin",1,uri)>0 then'; put 'do;'; put 'rc=metadata_getattr(uri,"UserID",user);'; put 'rc=metadata_getattr(uri,"Password",password);'; put 'end;'; put 'if user^='''' then do;'; put 'user=''user=''!!quote(trim(user));'; put 'password=''password=''!!quote(trim(password));'; put 'end;'; put 'call symputx(''user'',user,''l'');'; put 'call symputx(''password'',password,''l'');'; put '&md.put _all_;'; put 'run;'; put '%if %length(&open_passthrough)>0 %then %do;'; put '%put %str(WARN)ING: Passthrough option for postgres not yet supported;'; put '%return;'; put '%end;'; put '%else %do;'; put '%if &mdebug=1 %then %do;'; put '%put NOTE: Executing the following:/;'; put '%put NOTE- libname &libref POSTGRES &database &ignore_read_only_columns;'; put '%put NOTE- &direct_exe &preserve_col_names &preserve_tab_names;'; put '%put NOTE- &server &schema &authdomain &user &password //;'; put '%end;'; put 'libname &libref POSTGRES &database &ignore_read_only_columns &direct_exe'; put '&preserve_col_names &preserve_tab_names &server &schema &authdomain'; put '&user &password;'; put '%end;'; put '%end;'; put '%else %if &engine=ORACLE %then %do;'; put '%put NOTE: Obtaining &engine library details;'; put 'data _null_;'; put 'length assocuri1 assocuri2 assocuri3 authdomain path schema $256;'; put 'call missing (of _all_);'; put '/* get auth domain */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,assocuri1);'; put 'rc=metadata_getnasn(assocuri1,''Domain'',1,assocuri2);'; put 'rc=metadata_getattr(assocuri2,"Name",authdomain);'; put 'call symputx(''authdomain'',authdomain,''l'');'; put '/* path */'; put 'rc=metadata_getprop(assocuri1,'; put '''Connection.Oracle.Property.PATH.Name.xmlKey.txt'',path);'; put 'call symputx(''path'',path,''l'');'; put '/* schema */'; put 'rc=metadata_getnasn("&liburi",''UsingPackages'',1,assocuri3);'; put 'rc=metadata_getattr(assocuri3,''SchemaName'',schema);'; put 'call symputx(''schema'',schema,''l'');'; put 'run;'; put '%put NOTE: Executing the following:/; %put NOTE-;'; put '%put NOTE- libname &libref ORACLE path=&path schema=&schema;'; put '%put NOTE- authdomain=&authdomain;'; put '%put NOTE-;'; put 'libname &libref ORACLE path=&path schema=&schema authdomain=&authdomain;'; put '%end;'; put '%else %if &engine=SQLSVR %then %do;'; put '%put NOTE: Obtaining &engine library details;'; put 'data _null;'; put 'length assocuri1 assocuri2 assocuri3 authdomain path schema userid'; put 'passwd $256;'; put 'call missing (of _all_);'; put 'rc=metadata_getnasn("&liburi",''DefaultLogin'',1,assocuri1);'; put 'rc=metadata_getattr(assocuri1,"UserID",userid);'; put 'rc=metadata_getattr(assocuri1,"Password",passwd);'; put 'call symputx(''user'',userid,''l'');'; put 'call symputx(''pass'',passwd,''l'');'; put '/* path */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,assocuri2);'; put 'rc=metadata_getprop(assocuri2,'; put '''Connection.SQL.Property.Datasrc.Name.xmlKey.txt'',path);'; put 'call symputx(''path'',path,''l'');'; put '/* schema */'; put 'rc=metadata_getnasn("&liburi",''UsingPackages'',1,assocuri3);'; put 'rc=metadata_getattr(assocuri3,''SchemaName'',schema);'; put 'call symputx(''schema'',schema,''l'');'; put 'run;'; put '%put NOTE: Executing the following:/; %put NOTE-;'; put '%put NOTE- libname &libref SQLSVR datasrc=&path schema=&schema ;'; put '%put NOTE- user="&user" pass="XXX";'; put '%put NOTE-;'; put 'libname &libref SQLSVR datasrc=&path schema=&schema user="&user" pass="&pass";'; put '%end;'; put '%else %if &engine=TERADATA %then %do;'; put '%put NOTE: Obtaining &engine library details;'; put 'data _null;'; put 'length assocuri1 assocuri2 assocuri3 authdomain path schema userid'; put 'passwd $256;'; put 'call missing (of _all_);'; put '/* get auth domain */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,assocuri1);'; put 'rc=metadata_getnasn(assocuri1,''Domain'',1,assocuri2);'; put 'rc=metadata_getattr(assocuri2,"Name",authdomain);'; put 'call symputx(''authdomain'',authdomain,''l'');'; put '/*'; put 'rc=metadata_getnasn("&liburi",''DefaultLogin'',1,assocuri1);'; put 'rc=metadata_getattr(assocuri1,"UserID",userid);'; put 'rc=metadata_getattr(assocuri1,"Password",passwd);'; put 'call symputx(''user'',userid,''l'');'; put 'call symputx(''pass'',passwd,''l'');'; put '*/'; put '/* path */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,assocuri2);'; put 'rc=metadata_getprop(assocuri2,'; put '''Connection.Teradata.Property.SERVER.Name.xmlKey.txt'',path);'; put 'call symputx(''path'',path,''l'');'; put '/* schema */'; put 'rc=metadata_getnasn("&liburi",''UsingPackages'',1,assocuri3);'; put 'rc=metadata_getattr(assocuri3,''SchemaName'',schema);'; put 'call symputx(''schema'',schema,''l'');'; put 'run;'; put '%put NOTE: Executing the following:/; %put NOTE-;'; put '%put NOTE- libname &libref TERADATA server="&path" schema=&schema ;'; put '%put NOTe- authdomain=&authdomain;'; put '%put NOTE-;'; put 'libname &libref TERADATA server="&path" schema=&schema authdomain=&authdomain;'; put '%end;'; put '%else %if &engine= %then %do;'; put '%put NOTE: Libref &libref is not registered in metadata;'; put '%&mAbort.mp_abort('; put 'msg=%str(ERR)OR: Libref &libref is not registered in metadata'; put ',mac=mm_assigndirectlib.sas);'; put '%return;'; put '%end;'; put '%else %do;'; put '%put %str(WARN)ING: Engine &engine is currently unsupported;'; put '%put %str(WARN)ING- Please contact your support team.;'; put '%return;'; put '%end;'; put '%mend mm_assigndirectlib;'; put '%macro mm_getrepos('; put 'outds=work.mm_getrepos'; put ')/*/STORE SOURCE*/;'; put '* use a temporary fileref to hold the response;'; put 'filename response temp;'; put '/* get list of libraries */'; put 'proc metadata in='; put '"1"'; put 'out=response;'; put 'run;'; put '/* write the response to the log for debugging */'; put '/*'; put 'data _null_;'; put 'infile response lrecl=1048576;'; put 'input;'; put 'put _infile_;'; put 'run;'; put '*/'; put '/* create an XML map to read the response */'; put 'filename sxlemap temp;'; put 'data _null_;'; put 'file sxlemap;'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository";'; put 'put "";'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Id";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Name";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Desc";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@DefaultNS";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@RepositoryType";'; put 'put "characterstring20";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@RepositoryFormat";'; put 'put "characterstring10";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@Access";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@CurrentAccess";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@PauseState";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Path";'; put 'put "";'; put 'put "characterstring256";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Engine";'; put 'put "";'; put 'put "characterstring8";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Options";'; put 'put "";'; put 'put "characterstring32";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@MetadataCreated";'; put 'put "characterstring24";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@MetadataUpdated";'; put 'put "characterstring24";'; put 'put '''';'; put 'put ''
'';'; put 'run;'; put 'libname _XML_ xml xmlfileref=response xmlmap=sxlemap;'; put 'proc sort data= _XML_.SASRepos out=&outds;'; put 'by name;'; put 'run;'; put '/* clear references */'; put 'filename sxlemap clear;'; put 'filename response clear;'; put 'libname _XML_ clear;'; put '%mend mm_getrepos;'; put '%macro dc_assignlib(type,libref,passthru=);'; put '%put &sysmacroname entry vars:;'; put '%put _local_;'; put '/* take current repository */'; put '%local repo repocnt x;'; put '%let repo=%sysfunc(getoption(metarepository));'; put '/* get list of repositories and filter */'; put '%mm_getrepos(outds=repos)'; put '%let repocnt=0;'; put 'data repos;'; put 'set repos;'; put 'where repositorytype in(''CUSTOM'',''FOUNDATION'')'; put '& upcase(name) ne "%upcase(&repo)";'; put 'keep id name ;'; put 'call symputx(cats(''repo'',_n_),name,''l'');'; put 'call symputx(''repocnt'',_n_,''l'');'; put 'run;'; put '/* find out which of these repositories has the libref we are searching for */'; put '%local lib_uri;'; put '%let lib_uri=NOTFOUND;'; put 'data _null_; /* check default repo first */'; put 'length lib_uri $256;'; put 'call missing (of _all_);'; put 'rc=metadata_getnobj("omsobj:SASLibrary?@Libref =''&libref''",1,lib_uri);'; put 'call symputx(''lib_uri'',coalescec(lib_uri,''NOTFOUND''),''l'');'; put 'run;'; put '%if &lib_uri=NOTFOUND %then %do;'; put '%do x=1 %to &repocnt;'; put 'options metarepository=&&repo&x;'; put 'data _null_;'; put 'length lib_uri $256;'; put 'call missing (of _all_);'; put 'rc=metadata_getnobj("omsobj:SASLibrary?@Libref =''&libref''",1,lib_uri);'; put 'call symputx(''lib_uri'',coalescec(lib_uri,''NOTFOUND''),''l'');'; put 'run;'; put '%if &lib_uri ne NOTFOUND %then %let x=%eval(&repocnt+1);'; put '%end;'; put '%end;'; put '%if &type=READ %then %do;'; put '%mm_assignlib(&libref)'; put '%end;'; put '%else %if &type=WRITE %then %do;'; put '%mm_assigndirectlib(&libref,open_passthrough=&passthru)'; put '%end;'; put 'options metarepository=&repo;'; put '%mend dc_assignlib;'; put '%macro mf_abort(mac=mf_abort.sas, msg=, iftrue=%str(1=1)'; put ')/des=''ungraceful abort'' /*STORE SOURCE*/;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%put NOTE: /// mf_abort macro executing //;'; put '%if %length(&mac)>0 %then %put NOTE- called by &mac;'; put '%put NOTE - &msg;'; put '%abort;'; put '%mend mf_abort;'; put '/** @endcond */'; put '%macro mf_verifymacvars('; put 'verifyVars /* list of macro variable NAMES */'; put ',makeUpcase=NO /* set to YES to make all the variable VALUES uppercase */'; put ',mAbort=SOFT'; put ')/*/STORE SOURCE*/;'; put '%local verifyIterator verifyVar abortmsg;'; put '%do verifyIterator=1 %to %sysfunc(countw(&verifyVars,%str( )));'; put '%let verifyVar=%qscan(&verifyVars,&verifyIterator,%str( ));'; put '%if not %symexist(&verifyvar) %then %do;'; put '%let abortmsg= Variable &verifyVar is MISSING;'; put '%goto exit_err;'; put '%end;'; put '%if %length(%trim(&&&verifyVar))=0 %then %do;'; put '%let abortmsg= Variable &verifyVar is EMPTY;'; put '%goto exit_err;'; put '%end;'; put '%if &makeupcase=YES %then %do;'; put '%let &verifyVar=%upcase(&&&verifyvar);'; put '%end;'; put '%end;'; put '%goto exit_success;'; put '%exit_err:'; put '%put &abortmsg;'; put '%mf_abort(iftrue=(&mabort ne SOFT),'; put 'mac=mf_verifymacvars,'; put 'msg=%str(&abortmsg)'; put ')'; put '0'; put '%return;'; put '%exit_success:'; put '1'; put '%mend mf_verifymacvars;'; put '%macro mm_createfolder(path=,mDebug=0);'; put '%put &sysmacroname: execution started for &path;'; put '%local dbg errorcheck;'; put '%if &mDebug=0 %then %let dbg=*;'; put '%local parentFolderObjId child errorcheck paths;'; put '%let paths=0;'; put '%let errorcheck=1;'; put '%if &syscc ge 4 %then %do;'; put '%put SYSCC=&syscc - this macro requires a clean session;'; put '%return;'; put '%end;'; put 'data _null_;'; put 'length objId parentId objType parent child $200'; put 'folderPath $1000;'; put 'call missing (of _all_);'; put 'folderPath = "%trim(&path)";'; put '* remove any trailing slash ;'; put 'if ( substr(folderPath,length(folderPath),1) = ''/'' ) then'; put 'folderPath=substr(folderPath,1,length(folderPath)-1);'; put '* name must not be blank;'; put 'if ( folderPath = '''' ) then do;'; put 'put ''ERR'' +(-1) "OR: &sysmacroname PATH parameter value must be non-blank";'; put 'end;'; put '* must have a starting slash ;'; put 'if ( substr(folderPath,1,1) ne ''/'' ) then do;'; put 'put ''ERR'' +(-1) "OR: &sysmacroname PATH param value must have starting slash";'; put 'stop;'; put 'end;'; put '* check if folder already exists ;'; put 'rc=metadata_pathobj('''',cats(folderPath,"(Folder)"),"",objType,objId);'; put 'if rc ge 1 then do;'; put 'put "NOTE: Folder " folderPath " already exists!";'; put 'stop;'; put 'end;'; put '* do not create a root (one level) folder ;'; put 'if countc(folderPath,''/'')=1 then do;'; put 'put ''ERR'' +(-1) "OR: &sysmacroname will not create a new ROOT folder";'; put 'stop;'; put 'end;'; put '* check that root folder exists ;'; put 'root=cats(''/'',scan(folderpath,1,''/''),"(Folder)");'; put 'if metadata_pathobj('''',root,"",objType,parentId)<1 then do;'; put 'put ''ERR'' +(-1) "OR: " root " does not exist!";'; put 'stop;'; put 'end;'; put '* check that parent folder exists ;'; put 'child=scan(folderPath,-1,''/'');'; put 'parent=substr(folderpath,1,length(folderpath)-length(child)-1);'; put 'rc=metadata_pathobj('''',cats(parent,"(Folder)"),"",objType,parentId);'; put 'if rc<1 then do;'; put 'putlog ''The following folders will be created:'';'; put '/* folder does not exist - so start from top and work down */'; put 'length newpath $1000;'; put 'paths=0;'; put 'do x=2 to countw(folderpath,''/'');'; put 'newpath='''';'; put 'do i=1 to x;'; put 'newpath=cats(newpath,''/'',scan(folderpath,i,''/''));'; put 'end;'; put 'rc=metadata_pathobj('''',cats(newpath,"(Folder)"),"",objType,parentId);'; put 'if rc<1 then do;'; put 'paths+1;'; put 'call symputx(cats(''path'',paths),newpath);'; put 'putlog newpath;'; put 'end;'; put 'call symputx(''paths'',paths);'; put 'end;'; put 'end;'; put 'else putlog "parent " parent " exists";'; put 'call symputx(''parentFolderObjId'',parentId,''l'');'; put 'call symputx(''child'',child,''l'');'; put 'call symputx(''errorcheck'',0,''l'');'; put '&dbg put (_all_)(=);'; put 'run;'; put '%if &errorcheck=1 or &syscc ge 4 %then %return;'; put '%if &paths>0 %then %do x=1 %to &paths;'; put '%put executing recursive call for &&path&x;'; put '%mm_createfolder(path=&&path&x)'; put '%end;'; put '%else %do;'; put 'filename __newdir temp;'; put 'options noquotelenmax;'; put '%local inmeta;'; put '%put creating: &path;'; put '%let inmeta=$METAREPOSITORY'; put ''; put 'SAS268435456'; put ';'; put 'proc metadata in="&inmeta" out=__newdir verbose;'; put 'run ;'; put '/* check it was successful */'; put 'data _null_;'; put 'length objId parentId objType parent child $200 ;'; put 'call missing (of _all_);'; put 'rc=metadata_pathobj('''',cats("&path","(Folder)"),"",objType,objId);'; put 'if rc ge 1 then do;'; put 'putlog "SUCCCESS! &path created.";'; put 'end;'; put 'else do;'; put 'putlog ''ERR'' +(-1) "OR: unsuccessful attempt to create &path";'; put 'call symputx(''syscc'',8);'; put 'end;'; put 'run;'; put '/* write the response to the log for debugging */'; put '%if &mDebug ne 0 %then %do;'; put 'data _null_;'; put 'infile __newdir lrecl=32767;'; put 'input;'; put 'put _infile_;'; put 'run;'; put '%end;'; put 'filename __newdir clear;'; put '%end;'; put '%put &sysmacroname: execution finished for &path;'; put '%mend mm_createfolder;'; put '%macro mm_createlibrary('; put 'libname=My New Library'; put ',libref=mynewlib'; put ',libdesc=Created automatically using the mm_createlibrary macro'; put ',engine=BASE'; put ',tree=/User Folders/sasdemo'; put ',servercontext=SASApp'; put ',directory=/tmp/somelib'; put ',IsPreassigned=0'; put ',mDebug=0'; put ',frefin=mm_in'; put ',frefout=mm_out'; put ')/*/STORE SOURCE*/;'; put '%local mD;'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing &sysmacroname..sas;'; put '%&mD.put _local_;'; put '%let libref=%upcase(&libref);'; put '/**'; put '* Check Library does not exist already with this libname'; put '*/'; put 'data _null_;'; put 'length type uri $256;'; put 'rc=metadata_resolve("omsobj:SASLibrary?@Name=''&libname''",type,uri);'; put 'call symputx(''checktype'',type,''l'');'; put 'call symputx(''liburi'',uri,''l'');'; put 'putlog (_all_)(=);'; put 'run;'; put '%if &checktype = SASLibrary %then %do;'; put '%put %str(WARN)ING: Library (&liburi) already exists with libname (&libname);'; put '%return;'; put '%end;'; put '/**'; put '* Check Library does not exist already with this libref'; put '*/'; put 'data _null_;'; put 'length type uri $256;'; put 'rc=metadata_resolve("omsobj:SASLibrary?@Libref=''&libref''",type,uri);'; put 'call symputx(''checktype'',type,''l'');'; put 'call symputx(''liburi'',uri,''l'');'; put 'putlog (_all_)(=);'; put 'run;'; put '%if &checktype = SASLibrary %then %do;'; put '%put %str(WARN)ING: Library (&liburi) already exists with libref (&libref) ;'; put '%return;'; put '%end;'; put '/**'; put '* Attempt to create tree'; put '*/'; put '%mm_createfolder(path=&tree)'; put '/**'; put '* check tree exists'; put '*/'; put 'data _null_;'; put 'length type uri $256;'; put 'rc=metadata_pathobj("","&tree","Folder",type,uri);'; put 'call symputx(''foldertype'',type,''l'');'; put 'call symputx(''treeuri'',uri,''l'');'; put 'run;'; put '%if &foldertype ne Tree %then %do;'; put '%put %str(WARN)ING: Tree &tree does not exist!;'; put '%return;'; put '%end;'; put '/**'; put '* Create filerefs for proc metadata call'; put '*/'; put 'filename &frefin temp;'; put 'filename &frefout temp;'; put '%mp_abort(iftrue= ('; put '&engine=BASE & %mf_verifymacvars(libname libref engine servercontext tree)=0'; put ')'; put ',mac=&sysmacroname'; put ',msg=%str(Empty inputs: libname libref engine servercontext tree)'; put ')'; put '%if &engine=BASE %then %do;'; put '/**'; put '* Check that the ServerContext exists'; put '*/'; put 'data _null_;'; put 'length type uri $256;'; put 'rc=metadata_resolve("omsobj:ServerContext?@Name=''&ServerContext''",type,uri);'; put 'call symputx(''checktype'',type,''l'');'; put 'call symputx(''serveruri'',uri,''l'');'; put 'putlog (_all_)(=);'; put 'run;'; put '%if &checktype ne ServerContext %then %do;'; put '%put %str(ERR)OR: ServerContext (&ServerContext) does not exist!;'; put '%return;'; put '%end;'; put '/**'; put '* Get prototype info'; put '*/'; put 'data _null_;'; put 'length type uri str $256;'; put 'str="omsobj:Prototype?@Name=''Library.SAS.Prototype.Name.xmlKey.txt''";'; put 'rc=metadata_resolve(str,type,uri);'; put 'call symputx(''checktype'',type,''l'');'; put 'call symputx(''prototypeuri'',uri,''l'');'; put 'putlog (_all_)(=);'; put 'run;'; put '%if &checktype ne Prototype %then %do;'; put '%put %str(ERR)OR: Prototype Library.SAS.Prototype.Name.xmlKey.txt not found;'; put '%return;'; put '%end;'; put '/**'; put '* Check that Physical location exists'; put '*/'; put '%if %sysfunc(fileexist(&directory))=0 %then %do;'; put '%put %str(ERR)OR: Physical directory (&directory) does not appear to exist!;'; put '%return;'; put '%end;'; put '/**'; put '* Check that Directory Object exists in metadata'; put '*/'; put 'data _null_;'; put 'length type uri $256;'; put 'rc=metadata_resolve("omsobj:Directory?@DirectoryRole=''LibraryPath''"'; put '!!" and @DirectoryName=''&directory''",type,uri);'; put 'call symputx(''checktype'',type,''l'');'; put 'call symputx(''directoryuri'',uri,''l'');'; put 'putlog (_all_)(=);'; put 'run;'; put '%if &checktype ne Directory %then %do;'; put '%put NOTE: Directory object does not exist for (&directory) location;'; put '%put NOTE: It will now be created;'; put 'data _null_;'; put 'file &frefin;'; put 'directory=quote(symget(''directory''));'; put 'put "$METAREPOSITORY "/'; put '''''/'; put '"SAS"/'; put '"268435456";'; put 'run;'; put 'proc metadata in= &frefin out=&frefout %if &mdebug=1 %then verbose;;'; put 'run;'; put '%if &mdebug=1 %then %do;'; put 'data _null_;'; put 'infile &frefout lrecl=1048576;'; put 'input; put _infile_;'; put 'run;'; put '%end;'; put '%put NOTE: Checking to ensure directory (&directory) object was created;'; put 'data _null_;'; put 'length type uri $256;'; put 'rc=metadata_resolve("omsobj:Directory?@DirectoryRole=''LibraryPath''"'; put '!!" and @DirectoryName=''&directory''",type,uri);'; put 'call symputx(''checktype2'',type,''l'');'; put 'call symputx(''directoryuri'',uri,''l'');'; put '%if &mdebug=1 %then putlog (_all_)(=);;'; put 'run;'; put '%if &checktype2 ne Directory %then %do;'; put '%put %str(ERR)OR: Directory (&directory) object was NOT created!;'; put '%return;'; put '%end;'; put '%else %put NOTE: Directory (&directoryuri) successfully created!;'; put '%end;'; put '/**'; put '* check SAS version'; put '*/'; put '%if %sysevalf(&sysver lt 9.3) %then %do;'; put '%put %str(WARN)ING: Version 9.3 or later required;'; put '%return;'; put '%end;'; put '/**'; put '* Prepare the XML and create the library'; put '*/'; put 'data _null_;'; put 'file &frefin;'; put 'treeuri=quote(symget(''treeuri''));'; put 'serveruri=quote(symget(''serveruri''));'; put 'directoryuri=quote(symget(''directoryuri''));'; put 'libname=quote(symget(''libname''));'; put 'libref=quote(symget(''libref''));'; put 'IsPreassigned=quote(symget(''IsPreassigned''));'; put 'prototypeuri=quote(symget(''prototypeuri''));'; put '/* escape description so it can be stored as XML */'; put 'libdesc=tranwrd(symget(''libdesc''),''&'',''&'');'; put 'libdesc=tranwrd(libdesc,''<'',''<'');'; put 'libdesc=tranwrd(libdesc,''>'',''>'');'; put 'libdesc=tranwrd(libdesc,"''",''''');'; put 'libdesc=tranwrd(libdesc,''"'',''"'');'; put 'libdesc=tranwrd(libdesc,''0A''x,'' '');'; put 'libdesc=tranwrd(libdesc,''0D''x,'' '');'; put 'libdesc=tranwrd(libdesc,''$'',''$'');'; put 'libdesc=quote(trim(libdesc));'; put 'put "$METAREPOSITORY "/'; put '''''/'; put ''' ''/'; put ''' "/'; put ''' ''/'; put ''' ''/'; put ''' ''/'; put ''' ''/'; put '" "/'; put ''' ''/'; put ''' ''/'; put ''' ''/'; put ''' ''/'; put ''' ''/'; put ''' ''/'; put '''SAS''/'; put '''268435456'';'; put 'run;'; put 'proc metadata in= &frefin out=&frefout %if &mdebug=1 %then verbose ;;'; put 'run;'; put '%if &mdebug=1 %then %do;'; put 'data _null_;'; put 'infile &frefout lrecl=1048576;'; put 'input;put _infile_;'; put 'run;'; put '%end;'; put '%put NOTE: Checking to ensure library (&libname) was created;'; put 'data _null_;'; put 'length type uri $256;'; put 'rc=metadata_pathobj("","&tree/&libname","Library",type,uri);'; put 'call symputx(''libtype'',type,''l'');'; put 'call symputx(''liburi'',uri,''l'');'; put '%if &mdebug=1 %then putlog (_all_)(=);;'; put 'run;'; put '%if &libtype ne SASLibrary %then %do;'; put '%put %str(ERR)OR: Could not find (&libname) at (&tree)!!;'; put '%return;'; put '%end;'; put '%else %put NOTE: Library (&libname) successfully created in (&tree)!;'; put '%end;'; put '%else %do;'; put '%put %str(ERR)OR: Other library engine types are not yet supported!!;'; put '%end;'; put '/**'; put '* Wrap up'; put '*/'; put '%if &mdebug ne 1 %then %do;'; put 'filename &frefin clear;'; put 'filename &frefout clear;'; put '%end;'; put '%mend mm_createlibrary;'; put '%macro mm_createdocument('; put 'tree=/User Folders/sasdemo'; put ',name=myNote'; put ',desc=Created by &sysmacroname'; put ',textrole='; put ',frefin=mm_in'; put ',frefout=mm_out'; put ',mDebug=1'; put ');'; put '%local mD;'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing &sysmacroname..sas;'; put '%&mD.put _local_;'; put '%mp_abort(iftrue= (%mf_verifymacvars(tree name)=0)'; put ',mac=&sysmacroname'; put ',msg=%str(Empty inputs: tree name)'; put ')'; put '/**'; put '* check tree exists'; put '*/'; put 'data _null_;'; put 'length type uri $256;'; put 'rc=metadata_pathobj("","&tree","Folder",type,uri);'; put 'call symputx(''type'',type,''l'');'; put 'call symputx(''treeuri'',uri,''l'');'; put 'run;'; put '%mp_abort('; put 'iftrue= (&type ne Tree)'; put ',mac=mm_createdocument.sas'; put ',msg=Tree &tree does not exist!'; put ')'; put '/**'; put '* Check object does not exist already'; put '*/'; put 'data _null_;'; put 'length type uri $256;'; put 'rc=metadata_pathobj("","&tree/&name","Note",type,uri);'; put 'call symputx(''type'',type,''l'');'; put 'call symputx(''docuri'',uri,''l'');'; put 'putlog (_all_)(=);'; put 'run;'; put '%if &type = Document %then %do;'; put '%put Document &name already exists in &tree!;'; put '%return;'; put '%end;'; put '/**'; put '* Now we can create the document'; put '*/'; put 'filename &frefin temp;'; put '/* write header XML */'; put 'data _null_;'; put 'file &frefin;'; put 'name=quote("&name");'; put 'desc=quote("&desc");'; put 'textrole=quote("&textrole");'; put 'treeuri=quote("&treeuri");'; put 'put "$METAREPOSITORY"/'; put '''"/'; put '" "/'; put ''' '' /'; put '''''/'; put '/*URI="Document for public note" */'; put '""/'; put '"SAS"/'; put '"268435456";'; put 'run;'; put 'filename &frefout temp;'; put 'proc metadata in= &frefin out=&frefout verbose;'; put 'run;'; put '%if &mdebug=1 %then %do;'; put '/* write the response to the log for debugging */'; put 'data _null_;'; put 'infile &frefout lrecl=1048576;'; put 'input;'; put 'put _infile_;'; put 'run;'; put '%end;'; put '%mend mm_createdocument;'; put '%macro mm_deletedocument('; put 'target='; put ')/*/STORE SOURCE*/;'; put '/**'; put '* Check document exist'; put '*/'; put '%local type;'; put 'data _null_;'; put 'length type uri $256;'; put 'rc=metadata_pathobj("","&target",''Note'',type,uri);'; put 'call symputx(''type'',type,''l'');'; put 'call symputx(''stpuri'',uri,''l'');'; put 'run;'; put '%if &type ne Document %then %do;'; put '%put %str(WARN)ING: No Document found at ⌖'; put '%return;'; put '%end;'; put 'filename __in temp lrecl=10000;'; put 'filename __out temp lrecl=10000;'; put 'data _null_ ;'; put 'file __in ;'; put 'put "";'; put 'put "SAS268436480";'; put 'put "";'; put 'run ;'; put 'proc metadata in=__in out=__out verbose;run;'; put '/* list the result */'; put 'data _null_;infile __out; input; list; run;'; put 'filename __in clear;'; put 'filename __out clear;'; put '/**'; put '* Check deletion'; put '*/'; put '%local isgone;'; put 'data _null_;'; put 'length type uri $256;'; put 'call missing (of _all_);'; put 'rc=metadata_pathobj("","&target",''Note'',type,uri);'; put 'call symputx(''isgone'',type,''l'');'; put 'run;'; put '%if &isgone = Document %then %do;'; put '%put %str(ERR)OR: Document not deleted from ⌖'; put '%let syscc=4;'; put '%return;'; put '%end;'; put '%mend mm_deletedocument;'; put '%macro mm_deletestp('; put 'target='; put ')/*/STORE SOURCE*/;'; put '/**'; put '* Check STP does exist'; put '*/'; put '%local cmtype;'; put 'data _null_;'; put 'length type uri $256;'; put 'rc=metadata_pathobj("","&target",''StoredProcess'',type,uri);'; put 'call symputx(''cmtype'',type,''l'');'; put 'call symputx(''stpuri'',uri,''l'');'; put 'run;'; put '%if &cmtype ne ClassifierMap %then %do;'; put '%put NOTE: No Stored Process found at ⌖'; put '%return;'; put '%end;'; put 'filename __in temp lrecl=10000;'; put 'filename __out temp lrecl=10000;'; put 'data _null_ ;'; put 'file __in ;'; put 'put "";'; put 'put "SAS268436480";'; put 'put "";'; put 'run ;'; put 'proc metadata in=__in out=__out verbose;run;'; put '/* list the result */'; put 'data _null_;infile __out; input; list; run;'; put 'filename __in clear;'; put 'filename __out clear;'; put '/**'; put '* Check deletion'; put '*/'; put '%local isgone;'; put 'data _null_;'; put 'length type uri $256;'; put 'call missing (of _all_);'; put 'rc=metadata_pathobj("","&target",''Note'',type,uri);'; put 'call symputx(''isgone'',type,''l'');'; put 'run;'; put '%if &isgone = ClassifierMap %then %do;'; put '%put %str(ERR)OR: STP not deleted from ⌖'; put '%let syscc=4;'; put '%return;'; put '%end;'; put '%mend mm_deletestp;'; put '%macro mf_getattrn('; put 'libds'; put ',attr'; put ')/*/STORE SOURCE*/;'; put '%local dsid rc;'; put '%let dsid=%sysfunc(open(&libds,is));'; put '%if &dsid = 0 %then %do;'; put '%put %str(WARN)ING: Cannot open %trim(&libds), system message below;'; put '%put %sysfunc(sysmsg());'; put '-1'; put '%end;'; put '%else %do;'; put '%sysfunc(attrn(&dsid,&attr))'; put '%let rc=%sysfunc(close(&dsid));'; put '%end;'; put '%mend mf_getattrn;'; put '%macro mf_nobs(libds'; put ')/*/STORE SOURCE*/;'; put '%mf_getattrn(&libds,NLOBS)'; put '%mend mf_nobs;'; put '%macro mm_getDirectories('; put 'path='; put ',outds=work.mm_getDirectories'; put ',mDebug=0'; put ')/*/STORE SOURCE*/;'; put '%local mD;'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing mm_getDirectories.sas;'; put '%&mD.put _local_;'; put 'data &outds (keep=directoryuri name directoryname directorydesc );'; put 'length directoryuri name directoryname directorydesc $256;'; put 'call missing(of _all_);'; put '__i+1;'; put '%if %length(&path)=0 %then %do;'; put 'do while'; put '(metadata_getnobj("omsobj:Directory?@Id contains ''.''",__i,directoryuri)>0);'; put '%end; %else %do;'; put 'do while('; put 'metadata_getnobj("omsobj:Directory?@DirectoryName=''&path''",__i,directoryuri)'; put '>0'; put ');'; put '%end;'; put '__rc1=metadata_getattr(directoryuri, "Name", name);'; put '__rc2=metadata_getattr(directoryuri, "DirectoryName", directoryname);'; put '__rc3=metadata_getattr(directoryuri, "Desc", directorydesc);'; put '&mD.putlog (_all_) (=);'; put 'drop __:;'; put '__i+1;'; put 'if sum(of __rc1-__rc3)=0 then output;'; put 'end;'; put 'run;'; put '%mend mm_getDirectories;'; put '%macro mm_updatestpsourcecode(stp='; put ',stpcode='; put ',minify=NO'; put ',mdebug=0'; put ');'; put '/* first, check if STP exists */'; put '%local tsuri;'; put '%let tsuri=stopifempty ;'; put 'data _null_;'; put 'format type uri tsuri value $200.;'; put 'call missing (of _all_);'; put 'path="&stp.(StoredProcess)";'; put '/* first, find the STP ID */'; put 'if metadata_pathobj("",path,"StoredProcess",type,uri)>0 then do;'; put '/* get sourcecode */'; put 'cnt=1;'; put 'do while (metadata_getnasn(uri,"Notes",cnt,tsuri)>0);'; put 'rc=metadata_getattr(tsuri,"Name",value);'; put '%if &mdebug=1 %then %do;'; put 'put tsuri= value=;'; put '%end;'; put 'if value="SourceCode" then do;'; put '/* found it! */'; put 'rc=metadata_getattr(tsuri,"Id",value);'; put 'call symputx(''tsuri'',value,''l'');'; put 'stop;'; put 'end;'; put 'cnt+1;'; put 'end;'; put 'end;'; put 'else put (_all_)(=);'; put 'run;'; put '%if &tsuri=stopifempty %then %do;'; put '%put %str(WARN)ING: &stp.(StoredProcess) not found!;'; put '%return;'; put '%end;'; put '%if %length(&stpcode)<2 %then %do;'; put '%put %str(WARN)ING: No SAS code supplied!!;'; put '%return;'; put '%end;'; put '%local frefin frefout;'; put '%let frefin=%mf_getuniquefileref();'; put '%let frefout=%mf_getuniquefileref();'; put '/* write header XML */'; put 'data _null_;'; put 'file &frefin;'; put 'put "$METAREPOSITORY'; put '2 %then %do;'; put 'data _null_;'; put 'file &frefin lrecl=32767 mod;'; put 'infile &stpcode lrecl=32767;'; put 'length outstr $32767;'; put 'input outstr ;'; put '/* escape code so it can be stored as XML */'; put 'outstr=tranwrd(_infile_,''&'',''&'');'; put 'outstr=tranwrd(outstr,''<'',''<'');'; put 'outstr=tranwrd(outstr,''>'',''>'');'; put 'outstr=tranwrd(outstr,"''",''''');'; put 'outstr=tranwrd(outstr,''"'',''"'');'; put 'outstr=tranwrd(outstr,''0A''x,'' '');'; put 'outstr=tranwrd(outstr,''0D''x,'' '');'; put 'outstr=tranwrd(outstr,''$'',''$'');'; put '%if &minify=YES %then %do;'; put 'outstr=cats(outstr);'; put 'if outstr ne '''';'; put 'if not (outstr=:''/*'' and subpad(left(reverse(outstr)),1,2)=''/*'');'; put '%end;'; put 'outstr=trim(outstr);'; put 'put outstr '' '';'; put 'run;'; put '%end;'; put 'data _null_;'; put 'file &frefin mod;'; put 'put "''>SAS268435456'; put '";'; put 'run;'; put 'proc metadata in= &frefin out=&frefout;'; put 'run;'; put '%if &mdebug=1 %then %do;'; put '/* write the response to the log for debugging */'; put 'data _null_;'; put 'infile &frefout lrecl=32767;'; put 'input;'; put 'put _infile_;'; put 'run;'; put '%end;'; put '%else %do;'; put 'filename &frefin clear;'; put 'filename &frefout clear;'; put '%end;'; put '%mend mm_updatestpsourcecode;'; put '%macro mm_getservercontexts('; put 'outds=work.mm_getrepos'; put ')/*/STORE SOURCE*/;'; put '%local repo repocnt x;'; put '%let repo=%sysfunc(getoption(metarepository));'; put '/* first get list of available repos */'; put '%mm_getrepos(outds=work.repos)'; put '%let repocnt=0;'; put 'data _null_;'; put 'set repos;'; put 'where repositorytype in(''CUSTOM'',''FOUNDATION'');'; put 'keep id name ;'; put 'call symputx(cats(''repo'',_n_),name,''l'');'; put 'call symputx(''repocnt'',_n_,''l'');'; put 'run;'; put 'filename __mc1 temp;'; put 'filename __mc2 temp;'; put 'data &outds;'; put 'length serveruri servername $200;'; put 'call missing (of _all_);'; put 'stop;'; put 'run;'; put '%do x=1 %to &repocnt;'; put 'options metarepository=&&repo&x;'; put 'proc metadata in='; put '"$METAREPOSITORY'; put 'ServerContextSAS'; put '0"'; put 'out=__mc1;'; put 'run;'; put '/*'; put 'data _null_;'; put 'infile __mc1 lrecl=1048576;'; put 'input;'; put 'put _infile_;'; put 'run;'; put '*/'; put 'data _null_;'; put 'file __mc2;'; put 'put '''';'; put 'put "/GetMetadataObjects/Objects/ServerContext";'; put 'put "";'; put 'put '''';'; put 'put "/GetMetadataObjects/Objects/ServerContext/@Id";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "/GetMetadataObjects/Objects/ServerContext/@Name";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put ''
'';'; put 'run;'; put 'libname __mc3 xml xmlfileref=__mc1 xmlmap=__mc2;'; put 'proc append base=&outds data=__mc3.SASContexts;run;'; put 'libname __mc3 clear;'; put '%end;'; put 'options metarepository=&repo;'; put 'filename __mc1 clear;'; put 'filename __mc2 clear;'; put '%mend mm_getservercontexts;'; put '%macro mf_isblank(param'; put ')/*/STORE SOURCE*/;'; put '%sysevalf(%superq(param)=,boolean)'; put '%mend mf_isblank;'; put '%macro mp_dropmembers('; put 'list /* space separated list of datasets / views */'; put ',libref=WORK /* can only drop from a single library at a time */'; put ',iftrue=%str(1=1)'; put ')/*/STORE SOURCE*/;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%if %mf_isblank(&list) %then %do;'; put '%put NOTE: nothing to drop!;'; put '%return;'; put '%end;'; put 'proc datasets lib=&libref nolist;'; put 'delete &list;'; put 'delete &list /mtype=view;'; put 'run;'; put '%mend mp_dropmembers;'; put '%macro mm_createstp('; put 'stpname=SASjs Default STP'; put ',stpdesc=This stp was created automatically by the mm_createstp macro'; put ',filename=mm_createstp.sas'; put ',directory=SASEnvironment/SASCode'; put ',tree=/User Folders/sasdemo'; put ',package=false'; put ',streaming=true'; put ',outds=work.mm_createstp'; put ',mDebug=0'; put ',server=SASApp'; put ',stptype=1'; put ',minify=NO'; put ',frefin=mm_in'; put ',frefout=mm_out'; put ',LogicalServerType=Sps'; put ')/*/STORE SOURCE*/;'; put '%local mD;'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing mm_CreateSTP.sas;'; put '%&mD.put _local_;'; put '%mp_abort('; put 'iftrue=(%mf_verifymacvars(stpname filename directory tree)=0)'; put ',mac=&sysmacroname'; put ',msg=%str(Empty inputs: stpname filename directory tree)'; put ')'; put '%mp_dropmembers(%scan(&outds,2,.))'; put '/* check LogicalServerType validity */'; put '%mp_abort('; put 'iftrue=('; put '&LogicalServerType ne Sps'; put 'and &LogicalServerType ne Wks'; put 'and &LogicalServerType ne Any'; put ')'; put ',mac=&sysmacroname'; put ',msg=%str(Invalid value for LogicalServerType (&LogicalServerType))'; put ')'; put '/**'; put '* check tree exists'; put '*/'; put 'data _null_;'; put 'length type uri $256;'; put 'rc=metadata_pathobj("","&tree","Folder",type,uri);'; put 'call symputx(''foldertype'',type,''l'');'; put 'call symputx(''treeuri'',uri,''l'');'; put 'run;'; put '%if &foldertype ne Tree %then %do;'; put '%put %str(WARN)ING: Tree &tree does not exist!;'; put '%return;'; put '%end;'; put '/**'; put '* Check STP does not exist already'; put '*/'; put '%local cmtype;'; put 'data _null_;'; put 'length type uri $256;'; put 'rc=metadata_pathobj("","&tree/&stpname",''StoredProcess'',type,uri);'; put 'call symputx(''cmtype'',type,''l'');'; put 'call symputx(''stpuri'',uri,''l'');'; put 'run;'; put '%if &cmtype = ClassifierMap %then %do;'; put '%put %str(WARN)ING: Stored Process &stpname already exists in &tree!;'; put '%return;'; put '%end;'; put '/**'; put '* Check that the physical file exists'; put '*/'; put '%if %sysfunc(fileexist(&directory/&filename)) ne 1 %then %do;'; put '%put %str(WARN)ING: FILE *&directory/&filename* NOT FOUND!;'; put '%return;'; put '%end;'; put '%if &stptype=1 %then %do;'; put '/* type 1 STP - where code is stored on filesystem */'; put '%if %sysevalf(&sysver lt 9.2) %then %do;'; put '%put %str(WARN)ING: Version 9.2 or later required;'; put '%return;'; put '%end;'; put '/* check directory object (where 9.2 source code reference is stored) */'; put 'data _null_;'; put 'length id $20 dirtype $256;'; put 'rc=metadata_resolve("&directory",dirtype,id);'; put 'call symputx(''checkdirtype'',dirtype,''l'');'; put 'run;'; put '%if &checkdirtype ne Directory %then %do;'; put '%mm_getdirectories(path=&directory,outds=&outds ,mDebug=&mDebug)'; put '%if %mf_nobs(&outds)=0 or %sysfunc(exist(&outds))=0 %then %do;'; put '%put %str(WARN)ING: The directory object does not exist for &directory;'; put '%return;'; put '%end;'; put '%end;'; put '%else %do;'; put 'data &outds;'; put 'directoryuri="&directory";'; put 'run;'; put '%end;'; put 'data &outds (keep=stpuri prompturi fileuri texturi);'; put 'length stpuri prompturi fileuri texturi serveruri $256 ;'; put 'if _n_=1 then call missing (of _all_);'; put 'set &outds;'; put '/* final checks on uris */'; put 'length id $20 type $256;'; put '__rc=metadata_resolve("&treeuri",type,id);'; put 'if type ne ''Tree'' then do;'; put 'putlog "%str(WARN)ING: Invalid tree URI: &treeuri";'; put 'stopme=1;'; put 'end;'; put '__rc=metadata_resolve(directoryuri,type,id);'; put 'if type ne ''Directory'' then do;'; put 'putlog "%str(WARN)ING: Invalid directory URI: " directoryuri;'; put 'stopme=1;'; put 'end;'; put '/* get server info */'; put '__rc=metadata_resolve("&server",type,serveruri);'; put 'if type ne ''LogicalServer'' then do;'; put '__rc=metadata_getnobj("omsobj:LogicalServer?@Name=''&server''",1,serveruri);'; put 'if serveruri='''' then do;'; put 'putlog "%str(WARN)ING: Invalid server: &server";'; put 'stopme=1;'; put 'end;'; put 'end;'; put 'if stopme=1 then do;'; put 'putlog (_all_)(=);'; put 'stop;'; put 'end;'; put '/* create empty prompt */'; put 'rc1=METADATA_NEWOBJ(''PromptGroup'',prompturi,''Parameters'');'; put 'rc2=METADATA_SETATTR(prompturi, ''UsageVersion'', ''1000000'');'; put 'rc3=METADATA_SETATTR(prompturi, ''GroupType'',''2'');'; put 'rc4=METADATA_SETATTR(prompturi, ''Name'',''Parameters'');'; put 'rc5=METADATA_SETATTR(prompturi, ''PublicType'',''Embedded:PromptGroup'');'; put 'GroupInfo='; put '"";'; put 'rc6 = METADATA_SETATTR(prompturi, ''GroupInfo'',groupinfo);'; put 'if sum(of rc1-rc6) ne 0 then do;'; put 'putlog "%str(WARN)ING: Issue creating prompt.";'; put 'if prompturi ne . then do;'; put 'putlog '' Removing orphan: '' prompturi;'; put 'rc = METADATA_DELOBJ(prompturi);'; put 'put rc=;'; put 'end;'; put 'stop;'; put 'end;'; put '/* create a file uri */'; put 'rc7=METADATA_NEWOBJ(''File'',fileuri,''SP Source File'');'; put 'rc8=METADATA_SETATTR(fileuri, ''FileName'',"&filename");'; put 'rc9=METADATA_SETATTR(fileuri, ''IsARelativeName'',''1'');'; put 'rc10=METADATA_SETASSN(fileuri, ''Directories'',''MODIFY'',directoryuri);'; put 'if sum(of rc7-rc10) ne 0 then do;'; put 'putlog "%str(WARN)ING: Issue creating file.";'; put 'if fileuri ne . then do;'; put 'putlog '' Removing orphans:'' prompturi fileuri;'; put 'rc = METADATA_DELOBJ(prompturi);'; put 'rc = METADATA_DELOBJ(fileuri);'; put 'put (_all_)(=);'; put 'end;'; put 'stop;'; put 'end;'; put '/* create a TextStore object */'; put 'rc11= METADATA_NEWOBJ(''TextStore'',texturi,''Stored Process'');'; put 'rc12= METADATA_SETATTR(texturi, ''TextRole'',''StoredProcessConfiguration'');'; put 'rc13= METADATA_SETATTR(texturi, ''TextType'',''XML'');'; put 'storedtext='''''; put '!!""'; put '!!"";'; put 'rc14= METADATA_SETATTR(texturi, ''StoredText'',storedtext);'; put 'if sum(of rc11-rc14) ne 0 then do;'; put 'putlog "%str(WARN)ING: Issue creating TextStore.";'; put 'if texturi ne . then do;'; put 'putlog '' Removing orphans: '' prompturi fileuri texturi;'; put 'rc = METADATA_DELOBJ(prompturi);'; put 'rc = METADATA_DELOBJ(fileuri);'; put 'rc = METADATA_DELOBJ(texturi);'; put 'put (_all_)(=);'; put 'end;'; put 'stop;'; put 'end;'; put '/* create meta obj */'; put 'rc15= METADATA_NEWOBJ(''ClassifierMap'',stpuri,"&stpname");'; put 'rc16= METADATA_SETASSN(stpuri, ''Trees'',''MODIFY'',treeuri);'; put 'rc17= METADATA_SETASSN(stpuri, ''ComputeLocations'',''MODIFY'',serveruri);'; put 'rc18= METADATA_SETASSN(stpuri, ''SourceCode'',''MODIFY'',fileuri);'; put 'rc19= METADATA_SETASSN(stpuri, ''Prompts'',''MODIFY'',prompturi);'; put 'rc20= METADATA_SETASSN(stpuri, ''Notes'',''MODIFY'',texturi);'; put 'rc21= METADATA_SETATTR(stpuri, ''PublicType'', ''StoredProcess'');'; put 'rc22= METADATA_SETATTR(stpuri, ''TransformRole'', ''StoredProcess'');'; put 'rc23= METADATA_SETATTR(stpuri, ''UsageVersion'', ''1000000'');'; put 'rc24= METADATA_SETATTR(stpuri, ''Desc'', "&stpdesc");'; put '/* tidy up if err */'; put 'if sum(of rc15-rc24) ne 0 then do;'; put 'putlog "%str(WARN)ING: Issue creating STP.";'; put 'if stpuri ne . then do;'; put 'putlog '' Removing orphans: '' prompturi fileuri texturi stpuri;'; put 'rc = METADATA_DELOBJ(prompturi);'; put 'rc = METADATA_DELOBJ(fileuri);'; put 'rc = METADATA_DELOBJ(texturi);'; put 'rc = METADATA_DELOBJ(stpuri);'; put 'put (_all_)(=);'; put 'end;'; put 'end;'; put 'else do;'; put 'fullpath=cats(''_program='',treepath,"/&stpname");'; put 'putlog "NOTE: Stored Process Created!";'; put 'putlog "NOTE- "; putlog "NOTE-"; putlog "NOTE-" fullpath;'; put 'putlog "NOTE- "; putlog "NOTE-";'; put 'end;'; put 'output;'; put 'stop;'; put 'run;'; put '%end;'; put '%else %if &stptype=2 %then %do;'; put '/* type 2 stp - code is stored in metadata */'; put '%if %sysevalf(&sysver lt 9.3) %then %do;'; put '%put %str(WARN)ING: SAS version 9.3 or later required to create type2 STPs;'; put '%return;'; put '%end;'; put '/* check we have the correct ServerContext */'; put '%mm_getservercontexts(outds=contexts)'; put '%local serveruri; %let serveruri=NOTFOUND;'; put 'data _null_;'; put 'set contexts;'; put 'where upcase(servername)="%upcase(&server)";'; put 'call symputx(''serveruri'',serveruri);'; put 'run;'; put '%if &serveruri=NOTFOUND %then %do;'; put '%put %str(WARN)ING: ServerContext *&server* not found!;'; put '%return;'; put '%end;'; put '/**'; put '* First, create a Hello World type 2 stored process'; put '*/'; put 'filename &frefin temp;'; put 'data _null_;'; put 'file &frefin;'; put 'treeuri=quote(symget(''treeuri''));'; put 'serveruri=quote(symget(''serveruri''));'; put 'stpdesc=quote(symget(''stpdesc''));'; put 'stpname=quote(symget(''stpname''));'; put 'put "$METAREPOSITORY "/'; put '''''/'; put '" "/'; put '" "/'; put '" "/'; put '" "/'; put ''' ''/'; put ''' '' /'; put '" "/'; put '" "/'; put ''' ''/'; put '" "/'; put '""/'; put '"SAS"/'; put '"268435456";'; put 'run;'; put 'filename &frefout temp;'; put 'proc metadata in= &frefin out=&frefout ;'; put 'run;'; put '%if &mdebug=1 %then %do;'; put '/* write the response to the log for debugging */'; put 'data _null_;'; put 'infile &frefout lrecl=1048576;'; put 'input;'; put 'put _infile_;'; put 'run;'; put '%end;'; put '/**'; put '* Next, add the source code'; put '*/'; put '%mm_updatestpsourcecode(stp=&tree/&stpname'; put ',stpcode="&directory/&filename"'; put ',mdebug=&mdebug'; put ',minify=&minify)'; put '%end;'; put '%else %do;'; put '%put %str(WARN)ING: STPTYPE=*&stptype* not recognised!;'; put '%end;'; put '%mend mm_createstp;'; put '%macro mf_mkdir(dir'; put ')/*/STORE SOURCE*/;'; put '%local lastchar child parent;'; put '%let lastchar = %substr(&dir, %length(&dir));'; put '%if (%bquote(&lastchar) eq %str(:)) %then %do;'; put '/* Cannot create drive mappings */'; put '%return;'; put '%end;'; put '%if (%bquote(&lastchar)=%str(/)) or (%bquote(&lastchar)=%str(\)) %then %do;'; put '/* last char is a slash */'; put '%if (%length(&dir) eq 1) %then %do;'; put '/* one single slash - root location is assumed to exist */'; put '%return;'; put '%end;'; put '%else %do;'; put '/* strip last slash */'; put '%let dir = %substr(&dir, 1, %length(&dir)-1);'; put '%end;'; put '%end;'; put '%if (%sysfunc(fileexist(%bquote(&dir))) = 0) %then %do;'; put '/* directory does not exist so prepare to create */'; put '/* first get the childmost directory */'; put '%let child = %scan(&dir, -1, %str(/\:));'; put '/*'; put 'If child name = path name then there are no parents to create. Else'; put 'they must be recursively scanned.'; put '*/'; put '%if (%length(&dir) gt %length(&child)) %then %do;'; put '%let parent = %substr(&dir, 1, %length(&dir)-%length(&child));'; put '%mf_mkdir(&parent)'; put '%end;'; put '/*'; put 'Now create the directory. Complain loudly of any errs.'; put '*/'; put '%let dname = %sysfunc(dcreate(&child, &parent));'; put '%if (%bquote(&dname) eq ) %then %do;'; put '%put %str(ERR)OR: could not create &parent + &child;'; put '%abort cancel;'; put '%end;'; put '%else %do;'; put '%put Directory created: &dir;'; put '%end;'; put '%end;'; put '/* exit quietly if directory did exist.*/'; put '%mend mf_mkdir;'; put '* SAS Macros end;'; put '* SAS Includes start;'; put '* SAS Includes end;'; put '* Binary Files start;'; put '* Binary Files end;'; put '* ServiceInit start;'; put 'options noquotelenmax ps=max;'; put '/* create dummy macros to prevent stpbegin / stpend from corrupting sessions */'; put '%macro stpbegin();'; put '%put NOTE: the STPBEGIN macro should not be used for web apps!;'; put '%mend stpbegin;'; put '%macro stpend();'; put '%put NOTE: the STPEND macro should not be used for web apps!;'; put '%mend stpend;'; put '* ServiceInit end;'; put '* Service start;'; put '/**'; put '@file'; put '@brief Create a demo (base engine) library'; put '@details'; put 'Creates the library'; put '@warning This STP self destructs! It will delete itself after a successful run'; put 'to avoid being executed twice (and overwriting actual data)'; put '

SAS Macros

'; put '@li mm_createlibrary.sas'; put '@li mm_createdocument.sas'; put '@li mm_deletedocument.sas'; put '@li mp_abort.sas'; put '@li dc_assignlib.sas'; put '@li mm_deletestp.sas'; put '@li mm_createstp.sas'; put '@li mf_mkdir.sas'; put '@version 9.2'; put '@author 4GL Apps Ltd'; put '@copyright 4GL Apps Ltd. This code may only be used within Data Controller'; put 'and may not be re-distributed or re-sold without the express permission of'; put '4GL Apps Ltd.'; put '**/'; put '%global dcpath;'; put '%let dclib=%upcase(DC%substr(%sysevalf(%sysfunc(datetime())/60),3,6));'; put '%let dclibname=Data Controller(&dclib);'; put '%let work=%sysfunc(pathname(work));'; put '%let dcpath=&dcpath/&dclib;'; put '%mf_mkdir(&dcpath)'; put '%mf_mkdir(&work)'; put '%put &=dcpath;'; put '/* check we have physical permissions to the DCLIB folder */'; put 'data _null_;'; put 'file "&dcpath/permTest.txt";'; put 'run;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(User &sysuserid does not have WRITE permissions on physical'; put 'directory: &dcpath )'; put ')'; put 'filename delfile "&dcpath/permTest.txt";'; put 'data _null_;'; put 'rc=fdelete(''delfile'');'; put 'run;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(User &sysuserid could create (but not delete) &dcpath/permTest.txt )'; put ')'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(Unable to write to &dcpath)'; put ')'; put 'data _null_;'; put 'pgm="&_program";'; put 'rootlen=length(trim(pgm))-length("/services/admin/makelib");'; put 'root=substr(pgm,1,rootlen);'; put 'putlog root=;'; put 'call symputx(''deploy_dir'',root);'; put 'run;'; put 'options noquotelenmax ps=max;'; put '/* check we have the admin rights to update the items in the Admin folder */'; put '%mm_createdocument(tree=&deploy_dir/services/admin,name=permTest)'; put '%mm_deletedocument(target=&deploy_dir/services/admin/permTest)'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(User &_metaperson does not have WriteMetadata on SAS folder:'; put '&deploy_dir )'; put ')'; put '/**'; put '* Create library and load data'; put '*/'; put '%let mpelibname=Data Controller (&dclib);'; put '%mm_createlibrary('; put 'libname=&mpelibname'; put ',libref=&dclib'; put ',libdesc=Configuration tables for the MacroPeople Data Controller application'; put ',engine=BASE'; put ',tree=&deploy_dir/data'; put ',servercontext=SASApp'; put ',directory=&dcpath'; put ',mDebug=1)'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(Unable to create &dclib library)'; put ')'; put '/* get direct libref */'; put '%dc_assignlib(READ,&dclib)'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(Unable to assign &dclib library)'; put ')'; put '/* create an initial settings service (this will be overwritten in the'; put 'data update step)'; put '*/'; put '%let temploc=&work/temp.txt;'; put 'data _null_;'; put 'file "&temploc" ;'; put 'put ''/* Data Controller Precode */'' / / ;'; put 'put '' '';'; put 'put ''options noquotelenmax ps=max;'';'; put 'put ''%global DC_LIBREF DC_LIBNAME;'';'; put 'put '' '';'; put 'put ''/* This metadata library (libref) contains control datasets for DC */'';'; put 'put ''/* If a different libref must be used, configure it below */'';'; put 'put ''%let DC_LIBREF='' "&dclib;";'; put 'put ''%let DC_LIBNAME='' "&mpelibname;";'; put 'put '' '';'; put 'put ''/* get physical path for direct libname - needed to track requests */'';'; put 'put ''data _null_;'';'; put 'put '' length lib_uri up_uri path $256;'';'; put 'put '' call missing (of _all_);'';'; put 'put '' rc=metadata_getnobj("omsobj:SASLibrary?@Libref=''''&dc_libref''''",1,lib_uri);'';'; put 'put '' rc=metadata_getnasn(lib_uri,"UsingPackages",1,up_uri);'';'; put 'put '' rc=metadata_getattr(up_uri,"DirectoryName",path);'';'; put 'put '' call symputx("dc_libloc",path);'';'; put 'put ''run;'';'; put 'put ''libname &DC_LIBREF "&dc_libloc";'';'; put 'put '' '';'; put 'run;'; put '%mm_deletestp(target=&deploy_dir/services/public/Data_Controller_Settings)'; put '%mm_createstp(stpname=Data_Controller_Settings'; put ',filename=temp.txt'; put ',directory=&work'; put ',tree=&deploy_dir/services/public'; put ',Server=SASApp'; put ',stptype=2'; put ',mdebug=1'; put ',stpdesc=Data Controller Configuration'; put ',minify=NO)'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(Issue creating settings stp)'; put ')'; put 'data _null_;'; put 'file _webout;'; put 'put "

Library &dclib successfully assigned

";'; put 'run;'; put '* Service end;'; run; %mm_createwebservice(path=&appLoc/&path, name=&service, code=sascode, server=&serverName, replace=yes) filename sascode clear; %let service=refreshcatalog; filename sascode temp lrecl=32767; data _null_; file sascode; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '/**'; put '@file mp_jsonout.sas'; put '@brief Writes JSON in SASjs format to a fileref'; put '@details This macro can be used to OPEN a JSON stream and send one or more'; put 'tables as arrays of rows, where each row can be an object or a nested array.'; put 'There are two engines available - DATASTEP or PROCJSON.'; put 'PROC JSON is fast but will produce errs like the ones below if'; put 'special chars are encountered.'; put '> (ERR)OR: Some code points did not transcode.'; put '> An object or array close is not valid at this point in the JSON text.'; put '> Date value out of range'; put 'If this happens, try running with ENGINE=DATASTEP.'; put 'The DATASTEP engine is used to handle special SAS missing numerics, and'; put 'can also convert entire datasets to formatted values. Output JSON is always'; put 'in UTF-8.'; put 'Usage:'; put 'filename tmp temp;'; put 'data class; set sashelp.class;run;'; put '%mp_jsonout(OPEN,jref=tmp)'; put '%mp_jsonout(OBJ,class,jref=tmp)'; put '%mp_jsonout(OBJ,class,dslabel=class2,jref=tmp,showmeta=Y)'; put '%mp_jsonout(CLOSE,jref=tmp)'; put 'data _null_;'; put 'infile tmp;'; put 'input;putlog _infile_;'; put 'run;'; put 'If you are building web apps with SAS then you are strongly encouraged to use'; put 'the mX_createwebservice macros in combination with the'; put '[sasjs adapter](https://github.com/sasjs/adapter).'; put 'For more information see https://sasjs.io'; put '@param [in] action Valid values:'; put '@li OPEN - opens the JSON'; put '@li OBJ - sends a table with each row as an object'; put '@li ARR - sends a table with each row in an array'; put '@li CLOSE - closes the JSON'; put '@param [in] ds The dataset to send. Must be a work table.'; put '@param [out] jref= (_webout) The fileref to which to send the JSON'; put '@param [out] dslabel= The name to give the table in the exported JSON'; put '@param [in] fmt= (Y) Whether to keep (Y) or strip (N) formats from the table'; put '@param [in] engine= (DATASTEP) Which engine to use to send the JSON. Options:'; put '@li PROCJSON (default)'; put '@li DATASTEP (more reliable when data has non standard characters)'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to JSON'; put '

Related Files

'; put '@li mp_ds2fmtds.sas'; put '@version 9.2'; put '@author Allan Bowe'; put '@source https://github.com/sasjs/core'; put '**/'; put '%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y'; put ',engine=DATASTEP'; put ',missing=NULL'; put ',showmeta=N'; put ',maxobs=MAX'; put ')/*/STORE SOURCE*/;'; put '%local tempds colinfo fmtds i numcols numobs stmt_obs lastobs optval'; put 'tmpds1 tmpds2 tmpds3 tmpds4;'; put '%let numcols=0;'; put '%if &maxobs ne MAX %then %let stmt_obs=%str(if _n_>&maxobs then stop;);'; put '%if &action=OPEN %then %do;'; put 'options nobomfile;'; put 'data _null_;file &jref encoding=''utf-8'' lrecl=200;'; put 'put ''{"PROCESSED_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''"'';'; put 'run;'; put '%end;'; put '%else %if (&action=ARR or &action=OBJ) %then %do;'; put '/* force variable names to always be uppercase in the JSON */'; put 'options validvarname=upcase;'; put '/* To avoid issues with _webout on EBI - such as encoding diffs and truncation'; put '(https://support.sas.com/kb/49/325.html) we use temporary files */'; put 'filename _sjs1 temp lrecl=200 ;'; put 'data _null_; file _sjs1 encoding=''utf-8'';'; put 'put ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":";'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjs1 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs1 clear;'; put '/* grab col defs */'; put 'proc contents noprint data=&ds'; put 'out=_data_(keep=name type length format formatl formatd varnum label);'; put 'run;'; put '%let colinfo=%scan(&syslast,2,.);'; put 'proc sort data=&colinfo;'; put 'by varnum;'; put 'run;'; put '/* move meta to mac vars */'; put 'data &colinfo;'; put 'if _n_=1 then call symputx(''numcols'',nobs,''l'');'; put 'set &colinfo end=last nobs=nobs;'; put 'name=upcase(name);'; put '/* fix formats */'; put 'if type=2 or type=6 then do;'; put 'typelong=''char'';'; put 'length fmt $49.;'; put 'if format='''' then fmt=cats(''$'',length,''.'');'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else fmt=cats(format,formatl,''.'');'; put 'end;'; put 'else do;'; put 'typelong=''num'';'; put 'if format='''' then fmt=''best.'';'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else if formatd=0 then fmt=cats(format,formatl,''.'');'; put 'else fmt=cats(format,formatl,''.'',formatd);'; put 'end;'; put '/* 32 char unique name */'; put 'newname=''sasjs''!!substr(cats(put(md5(name),$hex32.)),1,27);'; put 'call symputx(cats(''name'',_n_),name,''l'');'; put 'call symputx(cats(''newname'',_n_),newname,''l'');'; put 'call symputx(cats(''length'',_n_),length,''l'');'; put 'call symputx(cats(''fmt'',_n_),fmt,''l'');'; put 'call symputx(cats(''type'',_n_),type,''l'');'; put 'call symputx(cats(''typelong'',_n_),typelong,''l'');'; put 'call symputx(cats(''label'',_n_),coalescec(label,name),''l'');'; put '/* overwritten when fmt=Y and a custom format exists in catalog */'; put 'if typelong=''num'' then call symputx(cats(''fmtlen'',_n_),200,''l'');'; put 'else call symputx(cats(''fmtlen'',_n_),min(32767,ceil((length+10)*1.5)),''l'');'; put 'run;'; put '%let tempds=%substr(_%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql;'; put 'select count(*) into: lastobs from &ds;'; put '%if &maxobs ne MAX %then %let lastobs=%sysfunc(min(&lastobs,&maxobs));'; put '%if &engine=PROCJSON %then %do;'; put '%if &missing=STRING %then %do;'; put '%put &sysmacroname: Special Missings not supported in proc json.;'; put '%put &sysmacroname: Switching to DATASTEP engine;'; put '%goto datastep;'; put '%end;'; put 'data &tempds;'; put 'set &ds;'; put '&stmt_obs;'; put '%if &fmt=N %then format _numeric_ best32.;;'; put '/* PRETTY is necessary to avoid line truncation in large files */'; put 'filename _sjs2 temp lrecl=131068 encoding=''utf-8'';'; put 'proc json out=_sjs2 pretty'; put '%if &action=ARR %then nokeys ;'; put ';export &tempds / nosastags fmtnumeric;'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs2 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs2 clear;'; put '%end;'; put '%else %if &engine=DATASTEP %then %do;'; put '%datastep:'; put '%if %sysfunc(exist(&ds)) ne 1 & %sysfunc(exist(&ds,VIEW)) ne 1'; put '%then %do;'; put '%put &sysmacroname: &ds NOT FOUND!!!;'; put '%return;'; put '%end;'; put '%if &fmt=Y %then %do;'; put '/**'; put '* Extract format definitions'; put '* First, by getting library locations from dictionary.formats'; put '* Then, by exporting the width using proc format'; put '* Cannot use maxw from sashelp.vformat as not always populated'; put '* Cannot use fmtinfo() as not supported in all flavours'; put '*/'; put '%let tmpds1=%substr(fmtsum%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds2=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds3=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds4=%substr(col%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql noprint;'; put 'create table &tmpds1 as'; put 'select cats(libname,''.'',memname) as FMTCAT,'; put 'FMTNAME'; put 'from dictionary.formats'; put 'where fmttype=''F'' and libname is not null'; put 'and fmtname in (select format from &colinfo where format is not null)'; put 'order by 1;'; put 'create table &tmpds2('; put 'FMTNAME char(32),'; put 'LENGTH num'; put ');'; put '%local catlist cat fmtlist i;'; put 'select distinct fmtcat into: catlist separated by '' '' from &tmpds1;'; put '%do i=1 %to %sysfunc(countw(&catlist,%str( )));'; put '%let cat=%scan(&catlist,&i,%str( ));'; put 'proc sql;'; put 'select distinct fmtname into: fmtlist separated by '' '''; put 'from &tmpds1 where fmtcat="&cat";'; put 'proc format lib=&cat cntlout=&tmpds3(keep=fmtname length);'; put 'select &fmtlist;'; put 'run;'; put 'proc sql;'; put 'insert into &tmpds2 select distinct fmtname,length from &tmpds3;'; put '%end;'; put 'proc sql;'; put 'create table &tmpds4 as'; put 'select a.*, b.length as MAXW'; put 'from &colinfo a'; put 'left join &tmpds2 b'; put 'on cats(a.format)=cats(upcase(b.fmtname))'; put 'order by a.varnum;'; put 'data _null_;'; put 'set &tmpds4;'; put 'if not missing(maxw);'; put 'call symputx('; put 'cats(''fmtlen'',_n_),'; put '/* vars need extra padding due to JSON escaping of special chars */'; put 'min(32767,ceil((max(length,maxw)+10)*1.5))'; put ',''l'''; put ');'; put 'run;'; put '/* configure varlenchk - as we are explicitly shortening the variables */'; put '%let optval=%sysfunc(getoption(varlenchk));'; put 'options varlenchk=NOWARN;'; put 'data _data_(compress=char);'; put '/* shorten the new vars */'; put 'length'; put '%do i=1 %to &numcols;'; put '&&name&i $&&fmtlen&i'; put '%end;'; put ';'; put '/* rename on entry */'; put 'set &ds(rename=('; put '%do i=1 %to &numcols;'; put '&&name&i=&&newname&i'; put '%end;'; put '));'; put '&stmt_obs;'; put 'drop'; put '%do i=1 %to &numcols;'; put '&&newname&i'; put '%end;'; put ';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=num %then %do;'; put '&&name&i=cats(put(&&newname&i,&&fmt&i));'; put '%end;'; put '%else %do;'; put '&&name&i=put(&&newname&i,&&fmt&i);'; put '%end;'; put '%end;'; put 'if _error_ then do;'; put 'call symputx(''syscc'',1012);'; put 'stop;'; put 'end;'; put 'run;'; put '%let fmtds=&syslast;'; put 'options varlenchk=&optval;'; put '%end;'; put 'proc format; /* credit yabwon for special null removal */'; put 'value bart (default=40)'; put '%if &missing=NULL %then %do;'; put '._ - .z = null'; put '%end;'; put '%else %do;'; put '._ = [quote()]'; put '. = null'; put '.a - .z = [quote()]'; put '%end;'; put 'other = [best.];'; put 'data &tempds;'; put 'attrib _all_ label='''';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'length &&name&i $&&fmtlen&i...;'; put 'format &&name&i $&&fmtlen&i...;'; put '%end;'; put '%end;'; put '%if &fmt=Y %then %do;'; put 'set &fmtds;'; put '%end;'; put '%else %do;'; put 'set &ds;'; put '%end;'; put '&stmt_obs;'; put 'format _numeric_ bart.;'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'if findc(&&name&i,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put '&&name&i=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,&&name&i)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else &&name&i=quote(cats(&&name&i));'; put '%end;'; put '%end;'; put 'run;'; put 'filename _sjs3 temp lrecl=131068 ;'; put 'data _null_;'; put 'file _sjs3 encoding=''utf-8'';'; put 'if _n_=1 then put "[";'; put 'set &tempds;'; put 'if _n_>1 then put "," @; put'; put '%if &action=ARR %then "[" ; %else "{" ;'; put '%do i=1 %to &numcols;'; put '%if &i>1 %then "," ;'; put '%if &action=OBJ %then """&&name&i"":" ;'; put '"&&name&i"n /* name literal for reserved variable names */'; put '%end;'; put '%if &action=ARR %then "]" ; %else "}" ; ;'; put '/* close out the table */'; put 'data _null_;'; put 'file _sjs3 mod encoding=''utf-8'';'; put 'put '']'';'; put 'run;'; put 'data _null_;'; put 'infile _sjs3 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs3 clear;'; put '%end;'; put 'proc sql;'; put 'drop table &colinfo, &tempds;'; put '%if %substr(&showmeta,1,1)=Y %then %do;'; put 'filename _sjs4 temp lrecl=131068 encoding=''utf-8'';'; put 'data _null_;'; put 'file _sjs4;'; put 'length label $350;'; put 'put ", ""$%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":{""vars"":{";'; put 'do i=1 to &numcols;'; put 'name=quote(trim(symget(cats(''name'',i))));'; put 'format=quote(trim(symget(cats(''fmt'',i))));'; put 'label=quote(prxchange(''s/\\/\\\\/'',-1,trim(symget(cats(''label'',i)))));'; put 'length=quote(trim(symget(cats(''length'',i))));'; put 'type=quote(trim(symget(cats(''typelong'',i))));'; put 'if i>1 then put "," @@;'; put 'put name '':{"format":'' format '',"label":'' label'; put ''',"length":'' length '',"type":'' type ''}'';'; put 'end;'; put 'put ''}}'';'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs4 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs4 clear;'; put '%end;'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put 'data _null_; file &jref encoding=''utf-8'' mod ;'; put 'put "}";'; put 'run;'; put '%end;'; put '%mend mp_jsonout;'; put '/**'; put '@file mm_webout.sas'; put '@brief Send data to/from SAS Stored Processes'; put '@details This macro should be added to the start of each Stored Process,'; put '**immediately** followed by a call to:'; put '%mm_webout(FETCH)'; put 'This will read all the input data and create same-named SAS datasets in the'; put 'WORK library. You can then insert your code, and send data back using the'; put 'following syntax:'; put 'data some datasets; * make some data ;'; put 'retain some columns;'; put 'run;'; put '%mm_webout(OPEN)'; put '%mm_webout(ARR,some) * Array format, fast, suitable for large tables ;'; put '%mm_webout(OBJ,datasets) * Object format, easier to work with ;'; put 'Finally, wrap everything up send some helpful system variables too'; put '%mm_webout(CLOSE)'; put '@param [in] action Either FETCH, OPEN, ARR, OBJ or CLOSE'; put '@param [in] ds The dataset to send back to the frontend'; put '@param [out] dslabel= Value to use instead of table name for sending to JSON'; put '@param [in] fmt= (N) Setting Y converts all vars to their formatted values'; put '@param [out] fref= (_webout) The fileref to which to write the JSON'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] workobs= (0) When set to a positive integer, will create a new'; put 'output object (WORK) which contains this number of observations from all'; put 'tables in the WORK library.'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to output JSON'; put '

SAS Macros

'; put '@li mp_jsonout.sas'; put '

Related Macros

'; put '@li ms_webout.sas'; put '@li mv_webout.sas'; put '@version 9.3'; put '@author Allan Bowe'; put '**/'; put '%macro mm_webout(action,ds,dslabel=,fref=_webout,fmt=N,missing=NULL'; put ',showmeta=N,maxobs=MAX,workobs=0'; put ');'; put '%global _webin_file_count _webin_fileref1 _webin_name1 _program _debug'; put 'sasjs_tables;'; put '%local i tempds jsonengine;'; put '/* see https://github.com/sasjs/core/issues/41 */'; put '%if "%upcase(&SYSENCODING)" ne "UTF-8" %then %let jsonengine=PROCJSON;'; put '%else %let jsonengine=DATASTEP;'; put '%if &action=FETCH %then %do;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'options mprint notes mprintnest;'; put '%end;'; put '%let _webin_file_count=%eval(&_webin_file_count+0);'; put '/* now read in the data */'; put '%do i=1 %to &_webin_file_count;'; put '%if &_webin_file_count=1 %then %do;'; put '%let _webin_fileref1=&_webin_fileref;'; put '%let _webin_name1=&_webin_name;'; put '%end;'; put 'data _null_;'; put 'infile &&_webin_fileref&i termstr=crlf;'; put 'input;'; put 'call symputx(''input_statement'',_infile_);'; put 'putlog "&&_webin_name&i input statement: " _infile_;'; put 'stop;'; put 'data &&_webin_name&i;'; put 'infile &&_webin_fileref&i firstobs=2 dsd termstr=crlf encoding=''utf-8'';'; put 'input &input_statement;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'if _n_<20 then putlog _infile_;'; put '%end;'; put 'run;'; put '%let sasjs_tables=&sasjs_tables &&_webin_name&i;'; put '%end;'; put '%end;'; put '%else %if &action=OPEN %then %do;'; put '/* fix encoding */'; put 'OPTIONS NOBOMFILE;'; put '/**'; put '* check xengine type to avoid the below err message:'; put '* > Function is only valid for filerefs using the CACHE access method.'; put '*/'; put 'data _null_;'; put 'set sashelp.vextfl(where=(fileref="_WEBOUT"));'; put 'if xengine=''STREAM'' then do;'; put 'rc=stpsrv_header(''Content-type'',"text/html; encoding=utf-8");'; put 'end;'; put 'run;'; put '/* setup json */'; put 'data _null_;file &fref encoding=''utf-8'';'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutBEGIN<<'';'; put '%end;'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'run;'; put '%end;'; put '%else %if &action=ARR or &action=OBJ %then %do;'; put '%mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt,jref=&fref'; put ',engine=&jsonengine,missing=&missing,showmeta=&showmeta,maxobs=&maxobs'; put ')'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put '/* To avoid issues with _webout on EBI we use a temporary file */'; put 'filename _sjsref temp lrecl=131068;'; put '%if %str(&workobs) > 0 %then %do;'; put '/* if debug mode, send back first XX records of each work table also */'; put 'data;run;%let tempds=%scan(&syslast,2,.);'; put 'ods output Members=&tempds;'; put 'proc datasets library=WORK memtype=data;'; put '%local wtcnt;%let wtcnt=0;'; put 'data _null_;'; put 'set &tempds;'; put 'if not (upcase(name) =:"DATA"); /* ignore temp datasets */'; put 'i+1;'; put 'call symputx(cats(''wt'',i),name,''l'');'; put 'call symputx(''wtcnt'',i,''l'');'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put ",""WORK"":{";'; put '%do i=1 %to &wtcnt;'; put '%let wt=&&wt&i;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'dsid=open("WORK.&wt",''is'');'; put 'nlobs=attrn(dsid,''NLOBS'');'; put 'nvars=attrn(dsid,''NVARS'');'; put 'rc=close(dsid);'; put 'if &i>1 then put '',''@;'; put 'put " ""&wt"" : {";'; put 'put ''"nlobs":'' nlobs;'; put 'put '',"nvars":'' nvars;'; put '%mp_jsonout(OBJ,&wt,jref=_sjsref,dslabel=first10rows,showmeta=Y'; put ',maxobs=&workobs'; put ')'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put '%end;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put 'run;'; put '%end;'; put '/* close off json */'; put 'data _null_;file _sjsref mod encoding=''utf-8'';'; put 'length SYSPROCESSNAME syserrortext syswarningtext autoexec $512;'; put 'put ",""_DEBUG"" : ""&_debug"" ";'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'autoexec=quote(urlencode(trim(getoption(''autoexec''))));'; put 'put '',"AUTOEXEC" : '' autoexec;'; put 'put ",""MF_GETUSER"" : ""%mf_getuser()"" ";'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'put ",""SYSENCODING"" : ""&sysencoding"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSPROCESSID"" : ""&SYSPROCESSID"" ";'; put 'put ",""SYSPROCESSMODE"" : ""&SYSPROCESSMODE"" ";'; put 'SYSPROCESSNAME=quote(urlencode(cats(SYSPROCESSNAME)));'; put 'put ",""SYSPROCESSNAME"" : " SYSPROCESSNAME;'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put '',"SYSWARNINGTEXT" : '' syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'length memsize $32;'; put 'memsize="%sysfunc(INPUTN(%sysfunc(getoption(memsize)), best.),sizekmg.)";'; put 'memsize=quote(cats(memsize));'; put 'put '',"MEMSIZE" : '' memsize;'; put 'put "}" @;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutEND<<'';'; put '%end;'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjsref lrecl=1 recfm=n;'; put 'file &fref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjsref clear;'; put '%end;'; put '%mend mm_webout;'; put '%macro webout(action,ds,dslabel=,fmt=,missing=NULL,showmeta=NO,maxobs=MAX);'; put '%mm_webout(&action,ds=&ds,dslabel=&dslabel,fmt=&fmt'; put ',missing=&missing'; put ',showmeta=&showmeta'; put ',maxobs=&maxobs'; put ') %mend;'; put '/* provide additional debug info */'; put '%global _program;'; put '%put &=syscc;'; put '%put user=%mf_getuser();'; put '%put pgm=&_program;'; put '%put timestamp=%sysfunc(datetime(),datetime19.);'; put '* Service Variables start;'; put '* Service Variables end;'; put '* SAS Macros start;'; put '%macro mf_getapploc(pgm);'; put '%if "&pgm"="" %then %do;'; put '%if %symexist(_program) %then %let pgm=&_program;'; put '%else %do;'; put '%put &sysmacroname: No value provided and no _program variable available;'; put '%return;'; put '%end;'; put '%end;'; put '%local root;'; put '/**'; put '* First check we are not in the tests/macros folder (which has no subfolders)'; put '* or specifically in the testsetup or testteardown services'; put '*/'; put '%if %index(&pgm,/tests/macros/)'; put 'or %index(&pgm,/tests/testsetup)'; put 'or %index(&pgm,/tests/testteardown)'; put '%then %do;'; put '%let root=%substr(&pgm,1,%index(&pgm,/tests)-1);'; put '&root'; put '%return;'; put '%end;'; put '/**'; put '* Next, move up two levels to avoid matches on subfolder or service name'; put '*/'; put '%let root=%substr(&pgm,1,%length(&pgm)-%length(%scan(&pgm,-1,/))-1);'; put '%let root=%substr(&root,1,%length(&root)-%length(%scan(&root,-1,/))-1);'; put '%if %index(&root,/tests/) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/tests/)-1);'; put '%end;'; put '%else %if %index(&root,/services) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/services)-1);'; put '%end;'; put '%else %if %index(&root,/jobs) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/jobs)-1);'; put '%end;'; put '%else %put &sysmacroname: Could not find an app location from &pgm;'; put '&root'; put '%mend mf_getapploc ;'; put '%macro mf_getuniquefileref(prefix=_,maxtries=1000,lrecl=32767);'; put '%local rc fname;'; put '%if &prefix=0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%end;'; put '%else %do;'; put '%local x len;'; put '%let len=%eval(8-%length(&prefix));'; put '%let x=0;'; put '%do x=0 %to &maxtries;'; put '%let fname=&prefix%substr(%sysfunc(ranuni(0)),3,&len);'; put '%if %sysfunc(fileref(&fname)) > 0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%return;'; put '%end;'; put '%end;'; put '%put unable to find available fileref after &maxtries attempts;'; put '%end;'; put '%mend mf_getuniquefileref;'; put '%macro mp_abort(mac=mp_abort.sas, type=, msg=, iftrue=%str(1=1)'; put ', errds=work.mp_abort_errds'; put ', mode=REGULAR'; put ')/*/STORE SOURCE*/;'; put '%global sysprocessmode sysprocessname sasjs_stpsrv_header_loc sasjsprocessmode;'; put '%local fref fid i;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%put NOTE: /// mp_abort macro executing //;'; put '%if %length(&mac)>0 %then %put NOTE- called by &mac;'; put '%put NOTE - &msg;'; put '%if %symexist(_SYSINCLUDEFILEDEVICE)'; put '/* abort cancel FILE does not restart outside the INCLUDE on Viya 3.5 */'; put 'and %superq(SYSPROCESSNAME) ne %str(Compute Server)'; put '%then %do;'; put '%if "*&_SYSINCLUDEFILEDEVICE*" ne "**" %then %do;'; put 'data &errds;'; put 'iftrue=''1=1'';'; put 'length mac $100 msg $5000;'; put 'mac=symget(''mac'');'; put 'msg=symget(''msg'');'; put 'run;'; put 'data _null_;'; put 'abort cancel FILE;'; put 'run;'; put '%return;'; put '%end;'; put '%end;'; put '/* Web App Context */'; put '%if %symexist(_PROGRAM)'; put 'or %superq(SYSPROCESSNAME) = %str(Compute Server)'; put 'or &mode=INCLUDE'; put '%then %do;'; put 'options obs=max replace mprint;'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5"'; put '%then %do;'; put 'options nosyntaxcheck;'; put '%end;'; put '%if &mode=INCLUDE %then %do;'; put '%if %sysfunc(exist(&errds))=1 %then %do;'; put 'data _null_;'; put 'set &errds;'; put 'call symputx(''iftrue'',iftrue,''l'');'; put 'call symputx(''mac'',mac,''l'');'; put 'call symputx(''msg'',msg,''l'');'; put 'putlog (_all_)(=);'; put 'run;'; put '%if (&iftrue)=0 %then %return;'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: No include errors found;'; put '%return;'; put '%end;'; put '%end;'; put '/* extract log errs / warns, if exist */'; put '%local logloc logline;'; put '%global logmsg; /* capture global messages */'; put '%if %symexist(SYSPRINTTOLOG) %then %let logloc=&SYSPRINTTOLOG;'; put '%else %let logloc=%qsysfunc(getoption(LOG));'; put 'proc printto log=log;run;'; put '%let logline=0;'; put '%if %length(&logloc)>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input; putlog _infile_;'; put 'i=1;'; put 'retain logonce 0;'; put 'if ('; put '_infile_=:"%str(WARN)ING" or _infile_=:"%str(ERR)OR"'; put ') and logonce=0 then'; put 'do;'; put 'call symputx(''logline'',_n_);'; put 'logonce+1;'; put 'end;'; put 'run;'; put '/* capture log including lines BEFORE the err */'; put '%if &logline>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input;'; put 'i=1;'; put 'stoploop=0;'; put 'if _n_ ge &logline-15 and stoploop=0 then do until (i>22);'; put 'call symputx(''logmsg'',catx(''\n'',symget(''logmsg''),_infile_));'; put 'input;'; put 'i+1;'; put 'stoploop=1;'; put 'end;'; put 'if stoploop=1 then stop;'; put 'run;'; put '%end;'; put '%end;'; put '%if %symexist(SYS_JES_JOB_URI) %then %do;'; put '/* setup webout for Viya */'; put 'options nobomfile;'; put '%if "X&SYS_JES_JOB_URI.X"="XX" %then %do;'; put 'filename _webout temp lrecl=999999 mod;'; put '%end;'; put '%else %do;'; put 'filename _webout filesrvc parenturi="&SYS_JES_JOB_URI"'; put 'name="_webout.json" lrecl=999999 mod;'; put '%end;'; put '%end;'; put '%else %if %sysfunc(filename(fref,&sasjs_stpsrv_header_loc))=0 %then %do;'; put 'options nobomfile;'; put '/* set up http header for SASjs Server */'; put '%let fid=%sysfunc(fopen(&fref,A));'; put '%if &fid=0 %then %do;'; put '%put %str(ERR)OR: %sysfunc(sysmsg());'; put '%return;'; put '%end;'; put '%let rc=%sysfunc(fput(&fid,%str(Content-Type: application/json)));'; put '%let rc=%sysfunc(fwrite(&fid));'; put '%let rc=%sysfunc(fclose(&fid));'; put '%let rc=%sysfunc(filename(&fref));'; put '%end;'; put '/* send response in SASjs JSON format */'; put 'data _null_;'; put 'file _webout mod lrecl=32000 encoding=''utf-8'';'; put 'length msg syswarningtext syserrortext $32767 mode $10 ;'; put 'sasdatetime=datetime();'; put 'msg=symget(''msg'');'; put '%if &logline>0 %then %do;'; put 'msg=cats(msg,''\n\nLog Extract:\n'',symget(''logmsg''));'; put '%end;'; put '/* escape the escapes */'; put 'msg=tranwrd(msg,''\'',''\\'');'; put '/* escape the quotes */'; put 'msg=tranwrd(msg,''"'',''\"'');'; put '/* ditch the CRLFs as chrome complains */'; put 'msg=compress(msg,,''kw'');'; put '/* quote without quoting the quotes (which are escaped instead) */'; put 'msg=cats(''"'',msg,''"'');'; put 'if symexist(''_debug'') then debug=quote(trim(symget(''_debug'')));'; put 'else debug=''""'';'; put 'if symget(''sasjsprocessmode'')=''Stored Program'' then mode=''SASJS'';'; put 'if mode ne ''SASJS'' then put ''>>weboutBEGIN<<'';'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'put '',"sasjsAbort" : [{'';'; put 'put '' "MSG":'' msg ;'; put 'put '' ,"MAC": "'' "&mac" ''"}]'';'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'put '',"_DEBUG":'' debug ;'; put 'if symexist(''_metauser'') then do;'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put 'end;'; put 'if symexist(''SYS_JES_JOB_URI'') then do;'; put 'SYS_JES_JOB_URI=quote(trim(symget(''SYS_JES_JOB_URI'')));'; put 'put '',"SYS_JES_JOB_URI": '' SYS_JES_JOB_URI;'; put 'end;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put ",""SYSWARNINGTEXT"" : " syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'put "}" ;'; put 'if mode ne ''SASJS'' then put ''>>weboutEND<<'';'; put 'run;'; put '%put _all_;'; put '%if "&sysprocessmode " = "SAS Stored Process Server " %then %do;'; put 'data _null_;'; put 'putlog ''stpsrvset program err and syscc'';'; put 'rc=stpsrvset(''program error'', 0);'; put 'call symputx("syscc",0,"g");'; put 'run;'; put '%if &sysscp=WIN'; put 'and 1=0 /* deprecating this logic until we figure out a consistent abort */'; put 'and "%substr(%str(&sysvlong ),1,8)"="9.04.01M"'; put 'and "%substr(%str(&sysvlong ),9,1)">"5" %then %do;'; put '/* skip approach (below) does not work in windows m6+ envs */'; put 'endsas;'; put '%end;'; put '%else %do;'; put '/**'; put '* endsas kills 9.4m3 deployments by orphaning multibridges.'; put '* Abort variants are ungraceful (non zero return code)'; put '* This approach lets SAS run silently until the end :-)'; put '* Caution - fails when called within a %include within a macro'; put '* Use mp_include() to handle this.'; put '*/'; put 'filename skip temp;'; put 'data _null_;'; put 'file skip;'; put 'put ''%macro skip();'';'; put 'comment ''%mend skip; -> fix lint '';'; put 'put ''%macro skippy();'';'; put 'comment ''%mend skippy; -> fix lint '';'; put 'run;'; put '%inc skip;'; put '%end;'; put '%end;'; put '%else %if "&sysprocessmode " = "SAS Compute Server " %then %do;'; put '/* endsas kills the session making it harder to fetch results */'; put 'data _null_;'; put 'syswarningtext=symget(''syswarningtext'');'; put 'syserrortext=symget(''syserrortext'');'; put 'abort_msg=symget(''msg'');'; put 'syscc=symget(''syscc'');'; put 'sysuserid=symget(''sysuserid'');'; put 'iftrue=symget(''iftrue'');'; put 'put (_all_)(/=);'; put 'call symputx(''syscc'',0);'; put 'abort cancel nolist;'; put 'run;'; put '%end;'; put '%else %do;'; put '%abort cancel;'; put '%end;'; put '%end;'; put '%else %do;'; put '%put _all_;'; put '%abort cancel;'; put '%end;'; put '%mend mp_abort;'; put '/** @endcond */'; put '%macro mm_getstpcode('; put 'tree=/User Folders/sasdemo/somestp'; put ',name='; put ',outloc=0'; put ',outref=0'; put ',mDebug=1'; put ',showlog=NO'; put ');'; put '%local mD;'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing &sysmacroname..sas;'; put '%&mD.put _local_;'; put '%if %length(&name)>0 %then %let name=/&name;'; put '/* first, check if STP exists */'; put '%local tsuri;'; put '%let tsuri=stopifempty ;'; put 'data _null_;'; put 'format type uri tsuri value $200.;'; put 'call missing (of _all_);'; put 'path="&tree&name(StoredProcess)";'; put '/* first, find the STP ID */'; put 'if metadata_pathobj("",path,"StoredProcess",type,uri)>0 then do;'; put '/* get sourcecode */'; put 'cnt=1;'; put 'do while (metadata_getnasn(uri,"Notes",cnt,tsuri)>0);'; put 'rc=metadata_getattr(tsuri,"Name",value);'; put '&mD.put tsuri= value=;'; put 'if value="SourceCode" then do;'; put '/* found it! */'; put 'rc=metadata_getattr(tsuri,"Id",value);'; put 'call symputx(''tsuri'',value,''l'');'; put 'stop;'; put 'end;'; put 'cnt+1;'; put 'end;'; put 'end;'; put 'else put (_all_)(=);'; put 'run;'; put '%mp_abort(iftrue= (&tsuri=stopifempty)'; put ',mac=mm_getstpcode'; put ',msg=%str(&tree&name.(StoredProcess) not found!)'; put ')'; put '/**'; put '* Now we can extract the textstore'; put '*/'; put 'filename __getdoc temp lrecl=10000000;'; put 'proc metadata'; put 'in="$METAREPOSITORY'; put ''; put 'SAS1"'; put 'out=__getdoc ;'; put 'run;'; put '/* find the beginning of the text */'; put '%local start;'; put 'data _null_;'; put 'infile __getdoc lrecl=10000;'; put 'input;'; put 'start=index(_infile_,''StoredText="'');'; put 'if start then do;'; put 'call symputx("start",start+11);'; put '*putlog ''"'' _infile_ ''"'';'; put 'end;'; put 'stop;'; put '%local outeng;'; put '%if "&outloc"="0" %then %let outeng=TEMP;'; put '%else %let outeng="&outloc";'; put '%local fref;'; put '%if &outref=0 %then %let fref=%mf_getuniquefileref();'; put '%else %let fref=&outref;'; put '/* read the content, byte by byte, resolving escaped chars */'; put 'filename &fref &outeng lrecl=100000;'; put 'data _null_;'; put 'length filein 8 fileid 8;'; put 'filein = fopen("__getdoc","I",1,"B");'; put 'fileid = fopen("&fref","O",1,"B");'; put 'rec = "20"x;'; put 'length entity $6;'; put 'do while(fread(filein)=0);'; put 'x+1;'; put 'if x>&start then do;'; put 'rc = fget(filein,rec,1);'; put 'if rec=''"'' then leave;'; put 'else if rec="&" then do;'; put 'entity=rec;'; put 'do until (rec=";");'; put 'if fread(filein) ne 0 then goto getout;'; put 'rc = fget(filein,rec,1);'; put 'entity=cats(entity,rec);'; put 'end;'; put 'select (entity);'; put 'when (''&'' ) rec=''&'' ;'; put 'when (''<'' ) rec=''<'' ;'; put 'when (''>'' ) rec=''>'' ;'; put 'when (''''') rec="''" ;'; put 'when (''"'') rec=''"'' ;'; put 'when ('' '') rec=''0A''x;'; put 'when ('' '') rec=''0D''x;'; put 'when (''$'' ) rec=''$'' ;'; put 'when ('' '') rec=''09''x;'; put 'otherwise putlog "%str(WARN)ING: missing value for " entity=;'; put 'end;'; put 'rc =fput(fileid, substr(rec,1,1));'; put 'rc =fwrite(fileid);'; put 'end;'; put 'else do;'; put 'rc =fput(fileid,rec);'; put 'rc =fwrite(fileid);'; put 'end;'; put 'end;'; put 'end;'; put 'getout:'; put 'rc=fclose(filein);'; put 'rc=fclose(fileid);'; put 'run;'; put '%if &showlog=YES %then %do;'; put 'data _null_;'; put 'infile &fref lrecl=32767 end=last;'; put 'input;'; put 'if _n_=1 then putlog ''>>stpcodeBEGIN<<'';'; put 'putlog _infile_;'; put 'if last then putlog ''>>stpcodeEND<<'';'; put 'run;'; put '%end;'; put 'filename __getdoc clear;'; put '%if &outref=0 %then %do;'; put 'filename &fref clear;'; put '%end;'; put '%mend mm_getstpcode;'; put '%macro dc_getsettings();'; put '%global _program;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(syscc=&syscc on entry (&syswarningtext &syserrortext))'; put ')'; put '%if %length(&_PROGRAM)>1 %then %let root=&_program;'; put '%else %do;'; put '%global _metauser;'; put '%let _metauser=&sysuserid;'; put '/* to mimic a "real" _program we need to give a dummy role and stp name */'; put '%let root=/dummyRole/dummyName;'; put '%end;'; put '/* the DC precode is stored in the Admin folder in the root of'; put 'the project. Lets find that root. */'; put '%put &=root;'; put '%let root=%mf_getapploc();'; put '%put &=root;'; put '/* Now we know the root location we can retrieve the params */'; put '%let temploc=%sysfunc(pathname(work))/settings.txt;'; put '%mm_getstpcode(tree=&root/services/public'; put ',name=Data_Controller_Settings'; put ',outloc=&temploc'; put ')'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Unable to run getstpcode)'; put ')'; put 'filename _getsets "&temploc" lrecl=2000;'; put '/*'; put 'Do not use mp_include here - this puts a copy in every service, which creates'; put 'compilation problems when calling services from mp_include'; put '*/'; put '%inc _getsets/source2;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Problem running &sysmacroname (&syswarningtext &syserrortext))'; put ')'; put '%mend dc_getsettings;'; put '%macro mf_fmtdttm('; put ')/*/STORE SOURCE*/;'; put '%if "&sysver"="9.2" or "&sysver"="9.3"'; put 'or ("&sysver"="9.4" and "%substr(&SYSVLONG,9,1)" le "3")'; put 'or "%substr(&sysver,1,1)"="4"'; put 'or "%substr(&sysver,1,1)"="5"'; put '%then %do;DATETIME19.3%end;'; put '%else %do;E8601DT26.6%end;'; put '%mend mf_fmtdttm;'; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '%macro mp_init(prefix=SASJS'; put ')/*/STORE SOURCE*/;'; put '%if %symexist(SASJS_PREFIX) %then %return; /* only run once */'; put '%global'; put 'SASJS_PREFIX /* the ONLY hard-coded global macro variable in SASjs */'; put '&prefix._FUNCTIONS /* used in mcf_init() to track core function compilation */'; put '&prefix._INIT_NUM /* initialisation time as numeric */'; put '&prefix._INIT_DTTM /* initialisation time in E8601DT26.6 format */'; put '&prefix.WORK /* avoid typing %sysfunc(pathname(work)) every time */'; put ';'; put '%let sasjs_prefix=&prefix;'; put 'data _null_;'; put 'dttm=datetime();'; put 'call symputx("&sasjs_prefix._init_num",dttm,''g'');'; put 'call symputx("&sasjs_prefix._init_dttm",put(dttm,E8601DT26.6),''g'');'; put 'call symputx("&sasjs_prefix.work",pathname(''WORK''),''g'');'; put 'run;'; put 'options'; put 'compress=CHAR /* default is none so ensure we have something! */'; put 'datastmtchk=ALLKEYWORDS /* protection from overwriting input datasets */'; put 'errorcheck=STRICT /* catch errs in libname/filename statements */'; put 'fmterr /* ensure err when a format cannot be found */'; put 'mergenoby=%str(ERR)OR /* throw err when a merge has no BY variables */'; put 'missing=. /* changing this can cause hard to detect errs */'; put 'noquotelenmax /* avoid warnings for long strings */'; put 'noreplace /* avoid overwriting permanent datasets */'; put 'ps=max /* reduce log size slightly */'; put 'ls=max /* reduce log even more and avoid word truncation */'; put 'validmemname=COMPATIBLE /* avoid special characters etc in table names */'; put 'validvarname=V7 /* avoid special characters etc in variable names */'; put 'varinitchk=%str(ERR)OR /* avoid data mistakes from variable name typos */'; put 'varlenchk=%str(ERR)OR /* fail hard if truncation (data loss) can result */'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5" %then %do;'; put 'noautocorrect /* disallow misspelled procedure names */'; put 'dsoptions=note2err /* undocumented - convert bad NOTEs to ERRs */'; put '%end;'; put ';'; put '%mend mp_init;'; put '%macro mpeinit(fetch=YES);'; put '%global mpeinit'; put 'mpeadmins /* group with unrestricted Meditor access */'; put 'mpelocapprovals /* location for landing and staging files */'; put 'mpelib /* location of configuration tables for DC */'; put 'dc_repo_users /* location of user / group metadata */'; put 'dc_licence_key /* extracted in dc_getsettings */'; put 'dc_activation_key /* extracted in dc_getsettings */'; put 'dc_locale /* extracted in dc_getsettings */'; put 'dc_dttmtfmt /* can be overridden in dc_getsettings */'; put '_debug'; put ';'; put '%if &mpeinit=1 %then %return;'; put '%else %let mpeinit=1;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(Problem on service startup (&syswarningtext &syserrortext))'; put ')'; put '%mp_init()'; put '%if &fetch=YES %then %do;'; put '%webout(FETCH)'; put '%end;'; put '%global _CLIENTNAME;'; put '%mp_abort(iftrue= (&_CLIENTNAME=SAS Enterprise Guide)'; put ',mac=&_program..sas'; put ',msg=%str(Data Controller is a web app and should not be executed from EG)'; put ')'; put 'options urlencoding=utf8 nobomfile lrecl=32767;'; put '%let perf=%sysfunc(datetime());'; put '%put perfdiff: 0;'; put '%let dc_locale=SYSTEM; /* default if not set */'; put '/**'; put '* E8601DT26.6 has widest database support - but not all SAS flavours can'; put '* handle it. Override in the settings STP if needed.'; put '*/'; put 'data _null_;'; put 'dc_dttmtfmt=''"%sysfunc(datetime(),''!!"%mf_fmtdttm()"!!'')"dt'';'; put 'call symputx(''dc_dttmtfmt'',dc_dttmtfmt);'; put 'put dc_dttmtfmt=;'; put 'run;'; put '%put &=dc_dttmtfmt;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc prior to dc_getsettings)'; put ')'; put '%dc_getsettings()'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc after dc_getsettings)'; put ')'; put 'data _null_;'; put 'set &DC_LIBREF..mpe_config(where=('; put 'var_scope="DC"'; put 'and &dc_dttmtfmt lt tx_to'; put 'and var_active=1'; put '));'; put 'call symputx(var_name,var_value,''G'');'; put 'putlog var_name "=" var_value;'; put 'run;'; put '%let mpelib=&dc_libref;'; put '%let mpeadmins=&dc_admin_group;'; put '%let mpelocapprovals=&dc_staging_area;'; put '%let dc_repo_users=&dc_repo_users;'; put '%if &dc_locale ne SYSTEM %then %do;'; put 'options locale=&dc_locale;'; put '%end;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(Problem during compilation or with STP precode (&syswarningtext))'; put ')'; put '%mend mpeinit;'; put '%macro mf_mval(var);'; put '%if %symexist(&var) %then %do;'; put '%superq(&var)'; put '%end;'; put '%mend mf_mval;'; put '%macro mf_trimstr(basestr,trimstr);'; put '%local baselen trimlen trimval;'; put '/* return if basestr is shorter than trimstr (or 0) */'; put '%let baselen=%length(%superq(basestr));'; put '%let trimlen=%length(%superq(trimstr));'; put '%if &baselen < &trimlen or &baselen=0 %then %return;'; put '/* obtain the characters from the end of basestr */'; put '%let trimval=%qsubstr(%superq(basestr)'; put ',%length(%superq(basestr))-&trimlen+1'; put ',&trimlen);'; put '/* compare and if matching, chop it off! */'; put '%if %superq(basestr)=%superq(trimstr) %then %do;'; put '%return;'; put '%end;'; put '%else %if %superq(trimval)=%superq(trimstr) %then %do;'; put '%qsubstr(%superq(basestr),1,%length(%superq(basestr))-&trimlen)'; put '%end;'; put '%else %do;'; put '&basestr'; put '%end;'; put '%mend mf_trimstr;'; put '%macro mf_getplatform(switch'; put ')/*/STORE SOURCE*/;'; put '%local a b c;'; put '%if &switch.NONE=NONE %then %do;'; put '%if %symexist(sasjsprocessmode) %then %do;'; put '%if &sasjsprocessmode=Stored Program %then %do;'; put 'SASJS'; put '%return;'; put '%end;'; put '%end;'; put '%if %symexist(sysprocessmode) %then %do;'; put '%if "&sysprocessmode"="SAS Object Server"'; put 'or "&sysprocessmode"= "SAS Compute Server" %then %do;'; put 'SASVIYA'; put '%end;'; put '%else %if "&sysprocessmode"="SAS Stored Process Server"'; put 'or "&sysprocessmode"="SAS Workspace Server"'; put '%then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if %symexist(_metaport) or %symexist(_metauser) %then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if &switch=SASSTUDIO %then %do;'; put '/* return the version of SAS Studio else 0 */'; put '%if %mf_mval(_CLIENTAPP)=%str(SAS Studio) %then %do;'; put '%let a=%mf_mval(_CLIENTVERSION);'; put '%let b=%scan(&a,1,.);'; put '%if %eval(&b >2) %then %do;'; put '&b'; put '%end;'; put '%else 0;'; put '%end;'; put '%else 0;'; put '%end;'; put '%else %if &switch=VIYARESTAPI %then %do;'; put '%mf_trimstr(%sysfunc(getoption(servicesbaseurl)),/)'; put '%end;'; put '%mend mf_getplatform;'; put '%macro mpeterm();'; put '%local oldloc;'; put 'data _null_;'; put 'if symexist(''SYSPRINTTOLOG'') then oldloc=symget(''SYSPRINTTOLOG'');'; put 'else oldloc=getoption(''LOG'');'; put 'if subpad(oldloc,1,1) not in (''"'',"''",'' '') then oldloc=quote(cats(oldloc));'; put 'call symputx(''oldloc'',oldloc,''l'');'; put 'run;'; put '%if %length(&oldloc)>0 %then %do;'; put 'proc printto log=log;'; put 'run;'; put 'data _null_;'; put 'infile &oldloc;'; put 'input; putlog _infile_;'; put 'run;'; put '%end;'; put '%if %sysfunc(exist(&dc_libref..mpe_requests)) and %mf_getplatform() ne SASVIYA'; put '%then %do;'; put 'data ;'; put 'if 0 then set &dc_libref..mpe_requests;'; put 'request_dttm=%sysfunc(datetime());'; put 'request_user="%mf_getuser()";'; put 'request_service="%scan(&_program,-2,/)/%scan(&_program,-1,/)";'; put 'request_params='''';'; put 'output;stop;'; put 'proc append base=&dc_libref..mpe_requests data=&syslast force nowarn;'; put 'run;'; put '%end;'; put '%mend mpeterm;'; put '%macro mm_assignlib('; put 'libref'; put ',mAbort=HARD'; put ')/*/STORE SOURCE*/;'; put '%local mp_abort msg;'; put '%let mp_abort=0;'; put '%if %sysfunc(libref(&libref)) %then %do;'; put 'data _null_;'; put 'length liburi LibName msg $200;'; put 'call missing(of _all_);'; put 'nobj=metadata_getnobj("omsobj:SASLibrary?@Libref=''&libref''",1,liburi);'; put 'if nobj=1 then do;'; put 'rc=metadata_getattr(liburi,"Name",LibName);'; put '/* now try and assign it */'; put 'if libname("&libref",,''meta'',cats(''liburi="'',liburi,''";'')) ne 0 then do;'; put 'putlog "&libref could not be assigned";'; put 'putlog liburi=;'; put '/**'; put '* Fetch the system message for display in the abort modal. This is'; put '* not always helpful though. One example, previously received:'; put '* NOTE: Libref XX refers to the same library metadata as libref XX.'; put '*/'; put 'msg=sysmsg();'; put 'if msg=:''ERROR: Libref SAVE is not assigned.'' then do;'; put 'msg=catx(" ",'; put '"Could not assign %upcase(&libref).",'; put '"Please check metadata permissions! Libname:",libname,'; put '"Liburi:",liburi'; put ');'; put 'end;'; put 'else if msg="ERROR: User does not have appropriate authorization "!!'; put '"level for library SAVE."'; put 'then do;'; put 'msg=catx(" ",'; put '"ERROR: User does not have appropriate authorization level",'; put '"for library %upcase(&libref), libname:",libname,'; put '"Liburi:",liburi'; put ');'; put 'end;'; put 'call symputx(''msg'',msg,''l'');'; put 'if "&mabort"=''HARD'' then call symputx(''mp_abort'',1,''l'');'; put 'end;'; put 'else do;'; put 'put (_all_)(=);'; put 'call symputx(''libname'',libname,''L'');'; put 'call symputx(''liburi'',liburi,''L'');'; put 'end;'; put 'end;'; put 'else if nobj>1 then do;'; put 'if "&mabort"=''HARD'' then call symputx(''mp_abort'',1);'; put 'call symputx(''msg'',"More than one library with libref=&libref");'; put 'end;'; put 'else do;'; put 'if "&mabort"=''HARD'' then call symputx(''mp_abort'',1);'; put 'call symputx(''msg'',"Library &libref not found in metadata");'; put 'end;'; put 'run;'; put '%put NOTE: &msg;'; put '%end;'; put '%else %do;'; put '%put NOTE: Library &libref is already assigned;'; put '%end;'; put '%mp_abort(iftrue= (&mp_abort=1)'; put ',mac=mm_assignlib.sas'; put ',msg=%superq(msg)'; put ')'; put '%mend mm_assignlib;'; put '/** @cond */'; put '%macro mf_getengine(libref'; put ')/*/STORE SOURCE*/;'; put '%local dsid engnum rc engine;'; put '/* in case the parameter is a libref.tablename, pull off just the libref */'; put '%let libref = %upcase(%scan(&libref, 1, %str(.)));'; put '%let dsid=%sysfunc('; put 'open(sashelp.vlibnam(where=(libname="%upcase(&libref)")),i)'; put ');'; put '%if (&dsid ^= 0) %then %do;'; put '%let engnum=%sysfunc(varnum(&dsid,ENGINE));'; put '%let rc=%sysfunc(fetch(&dsid));'; put '%let engine=%sysfunc(getvarc(&dsid,&engnum));'; put '%put &libref. ENGINE is &engine.;'; put '%let rc= %sysfunc(close(&dsid));'; put '%end;'; put '%upcase(&engine)'; put '%mend mf_getengine;'; put '/** @endcond */'; put '%macro mm_assigndirectlib('; put 'libref'; put ',open_passthrough='; put ',sql_options='; put ',mDebug=0'; put ',mAbort=0'; put ')/*/STORE SOURCE*/;'; put '%local mD;'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing mm_assigndirectlib.sas;'; put '%&mD.put _local_;'; put '%if &mAbort=1 %then %let mAbort=;'; put '%else %let mAbort=%str(*);'; put '%&mD.put NOTE: Creating direct (non META) connection to &libref library;'; put '%local cur_engine;'; put '%let cur_engine=%mf_getengine(&libref);'; put '%if &cur_engine ne META and &cur_engine ne %then %do;'; put '%put NOTE: &libref already has a direct (&cur_engine) libname connection;'; put '%return;'; put '%end;'; put '%else %if %upcase(&libref)=WORK %then %do;'; put '%put NOTE: We already have a direct connection to WORK :-) ;'; put '%return;'; put '%end;'; put '/* need to determine the library ENGINE first */'; put '%local engine;'; put 'data _null_;'; put 'length lib_uri engine $256;'; put 'call missing (of _all_);'; put '/* get URI for the particular library */'; put 'rc1=metadata_getnobj("omsobj:SASLibrary?@Libref =''&libref''",1,lib_uri);'; put '/* get the Engine attribute of the previous object */'; put 'rc2=metadata_getattr(lib_uri,''Engine'',engine);'; put 'putlog "mm_assigndirectlib for &libref:" rc1= lib_uri= rc2= engine=;'; put 'call symputx("liburi",lib_uri,''l'');'; put 'call symputx("engine",engine,''l'');'; put 'run;'; put '/* now obtain engine specific connection details */'; put '%if &engine=BASE %then %do;'; put '%&mD.put NOTE: Retrieving BASE library path;'; put 'data _null_;'; put 'length up_uri $256 path cat_path $1024;'; put 'retain cat_path;'; put 'call missing (of _all_);'; put '/* get all the filepaths of the UsingPackages association */'; put 'i=1;'; put 'rc3=metadata_getnasn("&liburi",''UsingPackages'',i,up_uri);'; put 'do while (rc3>0);'; put '/* get the DirectoryName attribute of the previous object */'; put 'rc4=metadata_getattr(up_uri,''DirectoryName'',path);'; put 'if i=1 then path = ''("''!!trim(path)!!''" '';'; put 'else path ='' "''!!trim(path)!!''" '';'; put 'cat_path = trim(cat_path) !! " " !! trim(path) ;'; put 'i+1;'; put 'rc3=metadata_getnasn("&liburi",''UsingPackages'',i,up_uri);'; put 'end;'; put 'cat_path = trim(cat_path) !! ")";'; put '&mD.putlog "NOTE: Getting physical path for &libref library";'; put '&mD.putlog rc3= up_uri= rc4= cat_path= path=;'; put '&mD.putlog "NOTE: Libname cmd will be:";'; put '&mD.putlog "libname &libref" cat_path;'; put 'call symputx("filepath",cat_path,''l'');'; put 'run;'; put '%if %sysevalf(&sysver<9.4) %then %do;'; put 'libname &libref &filepath;'; put '%end;'; put '%else %do;'; put '/* apply the new filelocks option to cater for temporary locks */'; put 'libname &libref &filepath filelockwait=5;'; put '%end;'; put '%end;'; put '%else %if &engine=REMOTE %then %do;'; put 'data x;'; put 'length rcCon rcProp rc k 3 uriCon uriProp PropertyValue PropertyName'; put 'Delimiter $256 properties $2048;'; put 'retain properties;'; put 'rcCon = metadata_getnasn("&liburi", "LibraryConnection", 1, uriCon);'; put 'rcProp = metadata_getnasn(uriCon, "Properties", 1, uriProp);'; put 'k = 1;'; put 'rcProp = metadata_getnasn(uriCon, "Properties", k, uriProp);'; put 'do while (rcProp > 0);'; put 'rc = metadata_getattr(uriProp , "DefaultValue",PropertyValue);'; put 'rc = metadata_getattr(uriProp , "PropertyName",PropertyName);'; put 'rc = metadata_getattr(uriProp , "Delimiter",Delimiter);'; put 'properties = trim(properties) !! " " !! trim(PropertyName)'; put '!! trim(Delimiter) !! trim(PropertyValue);'; put 'output;'; put 'k+1;'; put 'rcProp = metadata_getnasn(uriCon, "Properties", k, uriProp);'; put 'end;'; put '%&mD.put NOTE: Getting properties for REMOTE SHARE &libref library;'; put '&mD.put _all_;'; put '%&mD.put NOTE: Libname cmd will be:;'; put '%&mD.put libname &libref &engine &properties slibref=&libref;'; put 'call symputx ("properties",trim(properties),''l'');'; put 'run;'; put 'libname &libref &engine &properties slibref=&libref;'; put '%end;'; put '%else %if &engine=OLEDB %then %do;'; put '%&mD.put NOTE: Retrieving OLEDB connection details;'; put 'data _null_;'; put 'length domain datasource provider properties schema'; put 'connx_uri domain_uri conprop_uri lib_uri schema_uri value $256.;'; put 'call missing (of _all_);'; put '/* get source connection ID */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,connx_uri);'; put '/* get connection domain */'; put 'rc1=metadata_getnasn(connx_uri,''Domain'',1,domain_uri);'; put 'rc2=metadata_getattr(domain_uri,''Name'',domain);'; put '&mD.putlog / ''NOTE: '' // ''NOTE- connection id: '' connx_uri ;'; put '&mD.putlog ''NOTE- domain: '' domain;'; put '/* get DSN and PROVIDER from connection properties */'; put 'i=0;'; put 'do until (rc<0);'; put 'i+1;'; put 'rc=metadata_getnasn(connx_uri,''Properties'',i,conprop_uri);'; put 'rc2=metadata_getattr(conprop_uri,''Name'',value);'; put 'if value=''Connection.OLE.Property.DATASOURCE.Name.xmlKey.txt'' then do;'; put 'rc3=metadata_getattr(conprop_uri,''DefaultValue'',datasource);'; put 'end;'; put 'else if value=''Connection.OLE.Property.PROVIDER.Name.xmlKey.txt'' then do;'; put 'rc4=metadata_getattr(conprop_uri,''DefaultValue'',provider);'; put 'end;'; put 'else if value=''Connection.OLE.Property.PROPERTIES.Name.xmlKey.txt'' then'; put 'do;'; put 'rc5=metadata_getattr(conprop_uri,''DefaultValue'',properties);'; put 'end;'; put 'end;'; put '&mD.putlog ''NOTE- dsn/provider/properties: '' /'; put 'datasource provider properties;'; put '&mD.putlog ''NOTE- schema: '' schema // ''NOTE-'';'; put '/* get SCHEMA */'; put 'rc6=metadata_getnasn("&liburi",''UsingPackages'',1,lib_uri);'; put 'rc7=metadata_getattr(lib_uri,''SchemaName'',schema);'; put 'call symputx(''SQL_domain'',domain,''l'');'; put 'call symputx(''SQL_dsn'',datasource,''l'');'; put 'call symputx(''SQL_provider'',provider,''l'');'; put 'call symputx(''SQL_properties'',properties,''l'');'; put 'call symputx(''SQL_schema'',schema,''l'');'; put 'run;'; put '%if %length(&open_passthrough)>0 %then %do;'; put 'proc sql &sql_options;'; put 'connect to OLEDB as &open_passthrough(INSERT_SQL=YES'; put '/* need additional properties to make this work */'; put 'properties=(''Integrated Security''=SSPI'; put '''Persist Security Info''=True'; put '%sysfunc(compress(%str(&SQL_properties),%str(())))'; put ')'; put 'DATASOURCE=&sql_dsn PROMPT=NO'; put 'PROVIDER=&sql_provider SCHEMA=&sql_schema CONNECTION = GLOBAL);'; put '%end;'; put '%else %do;'; put 'LIBNAME &libref OLEDB PROPERTIES=&sql_properties'; put 'DATASOURCE=&sql_dsn PROVIDER=&sql_provider SCHEMA=&sql_schema'; put '%if %length(&sql_domain)>0 %then %do;'; put 'authdomain="&sql_domain"'; put '%end;'; put 'connection=shared;'; put '%end;'; put '%end;'; put '%else %if &engine=ODBC %then %do;'; put '%&mD.put NOTE: Retrieving ODBC connection details;'; put 'data _null_;'; put 'length connx_uri conprop_uri value datasource up_uri schema domprop_uri authdomain $256.;'; put 'call missing (of _all_);'; put '/* get source connection ID */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,connx_uri);'; put '/* get connection properties */'; put 'i=0;'; put 'do until (rc2<0);'; put 'i+1;'; put 'rc2=metadata_getnasn(connx_uri,''Properties'',i,conprop_uri);'; put 'rc3=metadata_getattr(conprop_uri,''Name'',value);'; put 'if value=''Connection.ODBC.Property.DATASRC.Name.xmlKey.txt'' then do;'; put 'rc4=metadata_getattr(conprop_uri,''DefaultValue'',datasource);'; put 'rc2=-1;'; put 'end;'; put 'end;'; put '/* get auth domain */'; put 'autrc=metadata_getnasn(connx_uri,"Domain",1,domprop_uri);'; put 'arc=metadata_getattr(domprop_uri,"Name",authdomain);'; put 'if not missing(authdomain) then authdomain=cats(''AUTHDOMAIN='',authdomain);'; put 'call symputx(''authdomain'',authdomain,''l'');'; put '/* get SCHEMA */'; put 'rc6=metadata_getnasn("&liburi",''UsingPackages'',1,up_uri);'; put 'rc7=metadata_getattr(up_uri,''SchemaName'',schema);'; put '&mD.put rc= connx_uri= rc2= conprop_uri= rc3= value= rc4= datasource='; put 'rc6= up_uri= rc7= schema=;'; put 'call symputx(''SQL_schema'',schema,''l'');'; put 'call symputx(''SQL_dsn'',datasource,''l'');'; put 'run;'; put '%if %length(&open_passthrough)>0 %then %do;'; put 'proc sql &sql_options;'; put 'connect to ODBC as &open_passthrough'; put '(INSERT_SQL=YES DATASRC=&sql_dsn. CONNECTION=global);'; put '%end;'; put '%else %do;'; put 'libname &libref ODBC DATASRC=&sql_dsn SCHEMA=&sql_schema &authdomain;'; put '%end;'; put '%end;'; put '%else %if &engine=POSTGRES %then %do;'; put '%put NOTE: Obtaining POSTGRES library details;'; put 'data _null_;'; put 'length database ignore_read_only_columns direct_exe preserve_col_names'; put 'preserve_tab_names server schema authdomain user password'; put 'prop name value uri urisrc $256.;'; put 'call missing (of _all_);'; put '/* get database value */'; put 'prop=''Connection.DBMS.Property.DB.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,database,"");'; put 'if database^='''' then database=''database=''!!quote(trim(database));'; put 'call symputx(''database'',database,''l'');'; put '/* get IGNORE_READ_ONLY_COLUMNS value */'; put 'prop=''Library.DBMS.Property.DBIROC.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,ignore_read_only_columns,"");'; put 'if ignore_read_only_columns^='''' then ignore_read_only_columns='; put '''ignore_read_only_columns=''!!ignore_read_only_columns;'; put 'call symputx(''ignore_read_only_columns'',ignore_read_only_columns,''l'');'; put '/* get DIRECT_EXE value */'; put 'prop=''Library.DBMS.Property.DirectExe.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,direct_exe,"");'; put 'if direct_exe^='''' then direct_exe=''direct_exe=''!!direct_exe;'; put 'call symputx(''direct_exe'',direct_exe,''l'');'; put '/* get PRESERVE_COL_NAMES value */'; put 'prop=''Library.DBMS.Property.PreserveColNames.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,preserve_col_names,"");'; put 'if preserve_col_names^='''' then preserve_col_names='; put '''preserve_col_names=''!!preserve_col_names;'; put 'call symputx(''preserve_col_names'',preserve_col_names,''l'');'; put '/* get PRESERVE_TAB_NAMES value */'; put '/* be careful with PRESERVE_TAB_NAMES=YES - it will mean your table will'; put 'become case sensitive!! */'; put 'prop=''Library.DBMS.Property.PreserveTabNames.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,preserve_tab_names,"");'; put 'if preserve_tab_names^='''' then preserve_tab_names='; put '''preserve_tab_names=''!!preserve_tab_names;'; put 'call symputx(''preserve_tab_names'',preserve_tab_names,''l'');'; put '/* get SERVER value */'; put 'if metadata_getnasn("&liburi","LibraryConnection",1,uri)>0 then do;'; put 'prop=''Connection.DBMS.Property.SERVER.Name.xmlKey.txt'';'; put 'rc=metadata_getprop(uri,prop,server,"");'; put 'end;'; put 'if server^='''' then server=''server=''!!quote(cats(server));'; put 'call symputx(''server'',server,''l'');'; put '/* get SCHEMA value */'; put 'if metadata_getnasn("&liburi","UsingPackages",1,uri)>0 then do;'; put 'rc=metadata_getattr(uri,"SchemaName",schema);'; put 'end;'; put 'if schema^='''' then schema=''schema=''!!schema;'; put 'call symputx(''schema'',schema,''l'');'; put '/* get AUTHDOMAIN value */'; put '/* this is only useful if the user account contains that auth domain'; put 'if metadata_getnasn("&liburi","DefaultLogin",1,uri)>0 then do;'; put 'rc=metadata_getnasn(uri,"Domain",1,urisrc);'; put 'rc=metadata_getattr(urisrc,"Name",authdomain);'; put 'end;'; put 'if authdomain^='''' then authdomain=''authdomain=''!!quote(trim(authdomain));'; put '*/'; put 'call symputx(''authdomain'',authdomain,''l'');'; put '/* get user & pass */'; put 'if authdomain='''' & metadata_getnasn("&liburi","DefaultLogin",1,uri)>0 then'; put 'do;'; put 'rc=metadata_getattr(uri,"UserID",user);'; put 'rc=metadata_getattr(uri,"Password",password);'; put 'end;'; put 'if user^='''' then do;'; put 'user=''user=''!!quote(trim(user));'; put 'password=''password=''!!quote(trim(password));'; put 'end;'; put 'call symputx(''user'',user,''l'');'; put 'call symputx(''password'',password,''l'');'; put '&md.put _all_;'; put 'run;'; put '%if %length(&open_passthrough)>0 %then %do;'; put '%put %str(WARN)ING: Passthrough option for postgres not yet supported;'; put '%return;'; put '%end;'; put '%else %do;'; put '%if &mdebug=1 %then %do;'; put '%put NOTE: Executing the following:/;'; put '%put NOTE- libname &libref POSTGRES &database &ignore_read_only_columns;'; put '%put NOTE- &direct_exe &preserve_col_names &preserve_tab_names;'; put '%put NOTE- &server &schema &authdomain &user &password //;'; put '%end;'; put 'libname &libref POSTGRES &database &ignore_read_only_columns &direct_exe'; put '&preserve_col_names &preserve_tab_names &server &schema &authdomain'; put '&user &password;'; put '%end;'; put '%end;'; put '%else %if &engine=ORACLE %then %do;'; put '%put NOTE: Obtaining &engine library details;'; put 'data _null_;'; put 'length assocuri1 assocuri2 assocuri3 authdomain path schema $256;'; put 'call missing (of _all_);'; put '/* get auth domain */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,assocuri1);'; put 'rc=metadata_getnasn(assocuri1,''Domain'',1,assocuri2);'; put 'rc=metadata_getattr(assocuri2,"Name",authdomain);'; put 'call symputx(''authdomain'',authdomain,''l'');'; put '/* path */'; put 'rc=metadata_getprop(assocuri1,'; put '''Connection.Oracle.Property.PATH.Name.xmlKey.txt'',path);'; put 'call symputx(''path'',path,''l'');'; put '/* schema */'; put 'rc=metadata_getnasn("&liburi",''UsingPackages'',1,assocuri3);'; put 'rc=metadata_getattr(assocuri3,''SchemaName'',schema);'; put 'call symputx(''schema'',schema,''l'');'; put 'run;'; put '%put NOTE: Executing the following:/; %put NOTE-;'; put '%put NOTE- libname &libref ORACLE path=&path schema=&schema;'; put '%put NOTE- authdomain=&authdomain;'; put '%put NOTE-;'; put 'libname &libref ORACLE path=&path schema=&schema authdomain=&authdomain;'; put '%end;'; put '%else %if &engine=SQLSVR %then %do;'; put '%put NOTE: Obtaining &engine library details;'; put 'data _null;'; put 'length assocuri1 assocuri2 assocuri3 authdomain path schema userid'; put 'passwd $256;'; put 'call missing (of _all_);'; put 'rc=metadata_getnasn("&liburi",''DefaultLogin'',1,assocuri1);'; put 'rc=metadata_getattr(assocuri1,"UserID",userid);'; put 'rc=metadata_getattr(assocuri1,"Password",passwd);'; put 'call symputx(''user'',userid,''l'');'; put 'call symputx(''pass'',passwd,''l'');'; put '/* path */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,assocuri2);'; put 'rc=metadata_getprop(assocuri2,'; put '''Connection.SQL.Property.Datasrc.Name.xmlKey.txt'',path);'; put 'call symputx(''path'',path,''l'');'; put '/* schema */'; put 'rc=metadata_getnasn("&liburi",''UsingPackages'',1,assocuri3);'; put 'rc=metadata_getattr(assocuri3,''SchemaName'',schema);'; put 'call symputx(''schema'',schema,''l'');'; put 'run;'; put '%put NOTE: Executing the following:/; %put NOTE-;'; put '%put NOTE- libname &libref SQLSVR datasrc=&path schema=&schema ;'; put '%put NOTE- user="&user" pass="XXX";'; put '%put NOTE-;'; put 'libname &libref SQLSVR datasrc=&path schema=&schema user="&user" pass="&pass";'; put '%end;'; put '%else %if &engine=TERADATA %then %do;'; put '%put NOTE: Obtaining &engine library details;'; put 'data _null;'; put 'length assocuri1 assocuri2 assocuri3 authdomain path schema userid'; put 'passwd $256;'; put 'call missing (of _all_);'; put '/* get auth domain */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,assocuri1);'; put 'rc=metadata_getnasn(assocuri1,''Domain'',1,assocuri2);'; put 'rc=metadata_getattr(assocuri2,"Name",authdomain);'; put 'call symputx(''authdomain'',authdomain,''l'');'; put '/*'; put 'rc=metadata_getnasn("&liburi",''DefaultLogin'',1,assocuri1);'; put 'rc=metadata_getattr(assocuri1,"UserID",userid);'; put 'rc=metadata_getattr(assocuri1,"Password",passwd);'; put 'call symputx(''user'',userid,''l'');'; put 'call symputx(''pass'',passwd,''l'');'; put '*/'; put '/* path */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,assocuri2);'; put 'rc=metadata_getprop(assocuri2,'; put '''Connection.Teradata.Property.SERVER.Name.xmlKey.txt'',path);'; put 'call symputx(''path'',path,''l'');'; put '/* schema */'; put 'rc=metadata_getnasn("&liburi",''UsingPackages'',1,assocuri3);'; put 'rc=metadata_getattr(assocuri3,''SchemaName'',schema);'; put 'call symputx(''schema'',schema,''l'');'; put 'run;'; put '%put NOTE: Executing the following:/; %put NOTE-;'; put '%put NOTE- libname &libref TERADATA server="&path" schema=&schema ;'; put '%put NOTe- authdomain=&authdomain;'; put '%put NOTE-;'; put 'libname &libref TERADATA server="&path" schema=&schema authdomain=&authdomain;'; put '%end;'; put '%else %if &engine= %then %do;'; put '%put NOTE: Libref &libref is not registered in metadata;'; put '%&mAbort.mp_abort('; put 'msg=%str(ERR)OR: Libref &libref is not registered in metadata'; put ',mac=mm_assigndirectlib.sas);'; put '%return;'; put '%end;'; put '%else %do;'; put '%put %str(WARN)ING: Engine &engine is currently unsupported;'; put '%put %str(WARN)ING- Please contact your support team.;'; put '%return;'; put '%end;'; put '%mend mm_assigndirectlib;'; put '%macro mm_getrepos('; put 'outds=work.mm_getrepos'; put ')/*/STORE SOURCE*/;'; put '* use a temporary fileref to hold the response;'; put 'filename response temp;'; put '/* get list of libraries */'; put 'proc metadata in='; put '"1"'; put 'out=response;'; put 'run;'; put '/* write the response to the log for debugging */'; put '/*'; put 'data _null_;'; put 'infile response lrecl=1048576;'; put 'input;'; put 'put _infile_;'; put 'run;'; put '*/'; put '/* create an XML map to read the response */'; put 'filename sxlemap temp;'; put 'data _null_;'; put 'file sxlemap;'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository";'; put 'put "";'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Id";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Name";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Desc";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@DefaultNS";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@RepositoryType";'; put 'put "characterstring20";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@RepositoryFormat";'; put 'put "characterstring10";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@Access";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@CurrentAccess";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@PauseState";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Path";'; put 'put "";'; put 'put "characterstring256";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Engine";'; put 'put "";'; put 'put "characterstring8";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Options";'; put 'put "";'; put 'put "characterstring32";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@MetadataCreated";'; put 'put "characterstring24";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@MetadataUpdated";'; put 'put "characterstring24";'; put 'put '''';'; put 'put ''
'';'; put 'run;'; put 'libname _XML_ xml xmlfileref=response xmlmap=sxlemap;'; put 'proc sort data= _XML_.SASRepos out=&outds;'; put 'by name;'; put 'run;'; put '/* clear references */'; put 'filename sxlemap clear;'; put 'filename response clear;'; put 'libname _XML_ clear;'; put '%mend mm_getrepos;'; put '%macro dc_assignlib(type,libref,passthru=);'; put '%put &sysmacroname entry vars:;'; put '%put _local_;'; put '/* take current repository */'; put '%local repo repocnt x;'; put '%let repo=%sysfunc(getoption(metarepository));'; put '/* get list of repositories and filter */'; put '%mm_getrepos(outds=repos)'; put '%let repocnt=0;'; put 'data repos;'; put 'set repos;'; put 'where repositorytype in(''CUSTOM'',''FOUNDATION'')'; put '& upcase(name) ne "%upcase(&repo)";'; put 'keep id name ;'; put 'call symputx(cats(''repo'',_n_),name,''l'');'; put 'call symputx(''repocnt'',_n_,''l'');'; put 'run;'; put '/* find out which of these repositories has the libref we are searching for */'; put '%local lib_uri;'; put '%let lib_uri=NOTFOUND;'; put 'data _null_; /* check default repo first */'; put 'length lib_uri $256;'; put 'call missing (of _all_);'; put 'rc=metadata_getnobj("omsobj:SASLibrary?@Libref =''&libref''",1,lib_uri);'; put 'call symputx(''lib_uri'',coalescec(lib_uri,''NOTFOUND''),''l'');'; put 'run;'; put '%if &lib_uri=NOTFOUND %then %do;'; put '%do x=1 %to &repocnt;'; put 'options metarepository=&&repo&x;'; put 'data _null_;'; put 'length lib_uri $256;'; put 'call missing (of _all_);'; put 'rc=metadata_getnobj("omsobj:SASLibrary?@Libref =''&libref''",1,lib_uri);'; put 'call symputx(''lib_uri'',coalescec(lib_uri,''NOTFOUND''),''l'');'; put 'run;'; put '%if &lib_uri ne NOTFOUND %then %let x=%eval(&repocnt+1);'; put '%end;'; put '%end;'; put '%if &type=READ %then %do;'; put '%mm_assignlib(&libref)'; put '%end;'; put '%else %if &type=WRITE %then %do;'; put '%mm_assigndirectlib(&libref,open_passthrough=&passthru)'; put '%end;'; put 'options metarepository=&repo;'; put '%mend dc_assignlib;'; put '/** @cond */'; put '%macro mf_existvar(libds /* 2 part dataset name */'; put ', var /* variable name */'; put ')/*/STORE SOURCE*/;'; put '%local dsid rc;'; put '%let dsid=%sysfunc(open(&libds,is));'; put '%if &dsid=0 %then %do;'; put '%put %sysfunc(sysmsg());'; put '0'; put '%end;'; put '%else %if %length(&var)=0 %then %do;'; put '0'; put '%let rc=%sysfunc(close(&dsid));'; put '%end;'; put '%else %do;'; put '%sysfunc(varnum(&dsid,&var))'; put '%let rc=%sysfunc(close(&dsid));'; put '%end;'; put '%mend mf_existvar;'; put '/** @endcond */'; put '%macro mf_getattrn('; put 'libds'; put ',attr'; put ')/*/STORE SOURCE*/;'; put '%local dsid rc;'; put '%let dsid=%sysfunc(open(&libds,is));'; put '%if &dsid = 0 %then %do;'; put '%put %str(WARN)ING: Cannot open %trim(&libds), system message below;'; put '%put %sysfunc(sysmsg());'; put '-1'; put '%end;'; put '%else %do;'; put '%sysfunc(attrn(&dsid,&attr))'; put '%let rc=%sysfunc(close(&dsid));'; put '%end;'; put '%mend mf_getattrn;'; put '%macro mf_getvartype(libds /* two level name */'; put ', var /* variable name from which to return the type */'; put ')/*/STORE SOURCE*/;'; put '%local dsid vnum vtype rc;'; put '/* Open dataset */'; put '%let dsid = %sysfunc(open(&libds));'; put '%if &dsid. > 0 %then %do;'; put '/* Get variable number */'; put '%let vnum = %sysfunc(varnum(&dsid, &var));'; put '/* Get variable type (C/N) */'; put '%if(&vnum. > 0) %then %let vtype = %sysfunc(vartype(&dsid, &vnum.));'; put '%else %do;'; put '%put NOTE: Variable &var does not exist in &libds;'; put '%let vtype = %str( );'; put '%end;'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: dataset &libds not opened! (rc=&dsid);'; put '%put &sysmacroname: %sysfunc(sysmsg());'; put '%return;'; put '%end;'; put '/* Close dataset */'; put '%let rc = %sysfunc(close(&dsid));'; put '/* Return variable type */'; put '&vtype'; put '%mend mf_getvartype;'; put '%macro mf_getattrc('; put 'libds'; put ',attr'; put ')/*/STORE SOURCE*/;'; put '%local dsid rc;'; put '%let dsid=%sysfunc(open(&libds,is));'; put '%if &dsid = 0 %then %do;'; put '%put %str(WARN)ING: Cannot open %trim(&libds), system message below;'; put '%put %sysfunc(sysmsg());'; put '-1'; put '%end;'; put '%else %do;'; put '%sysfunc(attrc(&dsid,&attr))'; put '%let rc=%sysfunc(close(&dsid));'; put '%end;'; put '%mend mf_getattrc;'; put '%macro mp_lockfilecheck('; put 'libds'; put ')/*/STORE SOURCE*/;'; put 'data _null_;'; put 'if _n_=1 then putlog "&sysmacroname entry vars:";'; put 'set sashelp.vmacro;'; put 'where scope="&sysmacroname";'; put 'put name ''='' value;'; put 'run;'; put '%mp_abort(iftrue= (&syscc>0)'; put ',mac=checklock.sas'; put ',msg=Aborting with syscc=&syscc on entry.'; put ')'; put '%mp_abort(iftrue= ("&libds"="0")'; put ',mac=&sysmacroname'; put ',msg=%str(libds not provided)'; put ')'; put '%local msg lib ds;'; put '%let lib=%upcase(%scan(&libds,1,.));'; put '%let ds=%upcase(%scan(&libds,2,.));'; put '/* in DC, format catalogs are passed with a -FC suffix. No saslock here! */'; put '%if %scan(&libds,2,-)=FC %then %do;'; put '%put &sysmacroname: Format Catalog detected, no lockfile applied to &libds;'; put '%return;'; put '%end;'; put '/* do not proceed if no observations can be processed */'; put '%let msg=options obs = 0. syserrortext=%superq(syserrortext);'; put '%mp_abort(iftrue= (%sysfunc(getoption(OBS))=0)'; put ',mac=checklock.sas'; put ',msg=%superq(msg)'; put ')'; put 'data _null_;'; put 'putlog "Checking engine & member type";'; put 'run;'; put '%local engine memtype;'; put '%let memtype=%mf_getattrc(&libds,MTYPE);'; put '%let engine=%mf_getattrc(&libds,ENGINE);'; put '%if &engine ne V9 and &engine ne BASE %then %do;'; put 'data _null_;'; put 'putlog "Lib &lib is not assigned using BASE engine - uses &engine instead";'; put 'putlog "SAS lock check will not be performed";'; put 'run;'; put '%return;'; put '%end;'; put '%else %if &memtype ne DATA %then %do;'; put '%put NOTE: Cannot lock a VIEW!! Memtype=&memtype;'; put '%return;'; put '%end;'; put 'data _null_;'; put 'putlog "Engine = &engine, memtype=&memtype";'; put 'putlog "Attempting lock statement";'; put 'run;'; put 'lock &libds;'; put '%local abortme;'; put '%let abortme=0;'; put '%if &syscc>0 or &SYSLCKRC ne 0 %then %do;'; put '%let msg=Unable to apply lock on &libds (SYSLCKRC=&SYSLCKRC syscc=&syscc);'; put '%put %str(ERR)OR: &sysmacroname: &msg;'; put '%let abortme=1;'; put '%end;'; put 'lock &libds clear;'; put '%mp_abort(iftrue= (&abortme=1)'; put ',mac=&sysmacroname'; put ',msg=%superq(msg)'; put ')'; put '%mend mp_lockfilecheck;'; put '%macro mp_lockanytable('; put 'action'; put ',lib= WORK'; put ',ds=0'; put ',ref='; put ',ctl_ds=0'; put ',loops=25'; put ',loop_secs=1'; put ');'; put 'data _null_;'; put 'if _n_=1 then putlog "&sysmacroname entry vars:";'; put 'set sashelp.vmacro;'; put 'where scope="&sysmacroname";'; put 'put name ''='' value;'; put 'run;'; put '%mp_abort(iftrue= ("&ds"="0" and &action ne MAKETABLE)'; put ',mac=&sysmacroname'; put ',msg=%str(dataset was not provided)'; put ')'; put '%mp_abort(iftrue= (&ctl_ds=0)'; put ',mac=&sysmacroname'; put ',msg=%str(Control dataset was not provided)'; put ')'; put '/* set up lib & mac vars */'; put '%let lib=%upcase(&lib);'; put '%let ds=%upcase(&ds);'; put '%let action=%upcase(&action);'; put '%local user x trans msg abortme;'; put '%let user=%mf_getuser();'; put '%let abortme=0;'; put '%mp_abort(iftrue= (&action ne LOCK & &action ne UNLOCK & &action ne MAKETABLE)'; put ',mac=&sysmacroname'; put ',msg=%str(Invalid action (&action) provided)'; put ')'; put '/* if an err condition exists, exit before we even begin */'; put '%mp_abort(iftrue= (&syscc>0 and &action=LOCK)'; put ',mac=&sysmacroname'; put ',msg=%str(aborting due to syscc=&syscc on LOCK entry)'; put ')'; put '/* do not bother locking work tables (else may affect all WORK libraries) */'; put '%if (%upcase(&lib)=WORK or %str(&lib)=%str()) & &action ne MAKETABLE %then %do;'; put '%put NOTE: WORK libraries will not be registered in the locking system.;'; put '%return;'; put '%end;'; put '/* do not proceed if no observations can be processed */'; put '%mp_abort(iftrue= (%sysfunc(getoption(OBS))=0)'; put ',mac=&sysmacroname'; put ',msg=%str(cannot continue when options obs = 0)'; put ')'; put '%if &ACTION=LOCK %then %do;'; put '/* abort if a SAS lock is already in place, or cannot be applied */'; put '%mp_lockfilecheck(&lib..&ds)'; put '/* next, check there is a record for this table */'; put '%local record_exists_check;'; put 'proc sql noprint;'; put 'select count(*) into: record_exists_check from &ctl_ds'; put 'where LOCK_LIB ="&lib" and LOCK_DS="&ds";'; put 'quit;'; put '%if &syscc>0 %then %put syscc=&syscc sqlrc=&sqlrc;'; put '%if &record_exists_check=0 %then %do;'; put 'data _null_;'; put 'putlog "&sysmacroname: adding record to lock table..";'; put 'run;'; put 'data ;'; put 'if 0 then set &ctl_ds;'; put 'LOCK_LIB ="&lib";'; put 'LOCK_DS="&ds";'; put 'LOCK_STATUS_CD=''LOCKED'';'; put 'LOCK_START_DTTM="%sysfunc(datetime(),%mf_fmtdttm())"dt;'; put 'LOCK_USER_NM="&user";'; put 'LOCK_PID="&sysjobid";'; put 'LOCK_REF="&ref";'; put 'output;stop;'; put 'run;'; put '%let trans=&syslast;'; put 'proc append base=&ctl_ds data=&trans;'; put 'run;'; put '%end;'; put '/* if record does exist, perform lock attempts */'; put '%else %do x=1 %to &loops;'; put 'data _null_;'; put 'putlog "&sysmacroname: attempting lock (iteration &x) "@;'; put 'putlog "at %sysfunc(datetime(),datetime19.) ..";'; put 'run;'; put 'proc sql;'; put 'update &ctl_ds'; put 'set LOCK_STATUS_CD=''LOCKED'''; put ', LOCK_START_DTTM="%sysfunc(datetime(),%mf_fmtdttm())"dt'; put ', LOCK_USER_NM="&user"'; put ', LOCK_PID="&sysjobid"'; put ', LOCK_REF="&ref"'; put 'where LOCK_LIB ="&lib" and LOCK_DS="&ds";'; put 'quit;'; put '/**'; put '* NOTE - occasionally SQL server will return an err code (deadlocked'; put '* transaction). If so, ignore it, keep calm, and carry on..'; put '*/'; put '%if &syscc>0 %then %do;'; put 'data _null_;'; put 'putlog ''NOTE-'' / ''NOTE-'';'; put 'putlog "NOTE- &sysmacroname: Update failed. "@;'; put 'putlog "Resetting err conditions and re-attempting.";'; put 'putlog "NOTE- syscc=&syscc syserr=&syserr sqlrc=&sqlrc";'; put 'putlog ''NOTE-'' / ''NOTE-'';'; put 'run;'; put '%let syscc=0;'; put '%let sqlrc=0;'; put '%end;'; put '/* now check if the record was successfully updated */'; put '%local success_check;'; put 'proc sql noprint;'; put 'select count(*) into: success_check from &ctl_ds'; put 'where LOCK_LIB ="&lib" and LOCK_DS="&ds"'; put 'and LOCK_PID="&sysjobid" and LOCK_STATUS_CD=''LOCKED'';'; put 'quit;'; put '%if &success_check=0 %then %do;'; put '%if &x < &loops %then %do;'; put '/* pause before next check */'; put 'data _null_;'; put 'putlog ''NOTE-'' / ''NOTE-'';'; put 'putlog "NOTE- &sysmacroname: table locked, waiting "@;'; put 'putlog "%sysfunc(sleep(&loop_secs)) seconds.. ";'; put 'putlog "NOTE- (iteration &x of &loops)";'; put 'putlog ''NOTE-'' / ''NOTE-'';'; put 'run;'; put '%end;'; put '%else %do;'; put '%let msg=Unable to lock &lib..&ds via &ctl_ds after &loops attempts.\n'; put 'Please ask your administrator to investigate!;'; put '%let abortme=1;'; put '%end;'; put '%end;'; put '%else %do;'; put 'data _null_;'; put 'putlog ''NOTE-'' / ''NOTE-'';'; put 'putlog "NOTE- &sysmacroname: Table &lib..&ds locked at "@;'; put 'putlog " %sysfunc(datetime(),datetime19.) (iteration &x)"@;'; put 'putlog ''NOTE-'' / ''NOTE-'';'; put 'run;'; put '%if &syscc>0 %then %do;'; put '%put setting syscc(&syscc) back to 0;'; put '%let syscc=0;'; put '%end;'; put '%let x=&loops; /* no more iterations needed */'; put '%end;'; put '%end;'; put '%end;'; put '%else %if &ACTION=UNLOCK %then %do;'; put '%local status cnt;'; put '%let cnt=0;'; put 'proc sql noprint;'; put 'select count(*) into: cnt from &ctl_ds where LOCK_LIB ="&lib" & LOCK_DS="&ds";'; put '%if &cnt=0 %then %do;'; put '%put %str(WAR)NING: &lib..&ds was not previously locked in &ctl_ds!;'; put '%end;'; put '%else %do;'; put 'select LOCK_STATUS_CD into: status from &ctl_ds'; put 'where LOCK_LIB ="&lib" and LOCK_DS="&ds";'; put 'quit;'; put '%if &syscc>0 %then %put syscc=&syscc sqlrc=&sqlrc;'; put '%if &status=LOCKED %then %do;'; put 'data _null_;'; put 'putlog "&sysmacroname: unlocking &lib..&ds:";'; put 'run;'; put 'proc sql;'; put 'update &ctl_ds'; put 'set LOCK_STATUS_CD=''UNLOCKED'''; put ', LOCK_END_DTTM="%sysfunc(datetime(),%mf_fmtdttm())"dt'; put ', LOCK_USER_NM="&user"'; put ', LOCK_PID="&sysjobid"'; put ', LOCK_REF="&ref"'; put 'where LOCK_LIB ="&lib" and LOCK_DS="&ds";'; put 'quit;'; put '%end;'; put '%else %if &status=UNLOCKED %then %do;'; put '%put %str(WAR)NING: &lib..&ds is already unlocked!;'; put '%end;'; put '%else %do;'; put '%put NOTE: Unrecognised STATUS_CD (&status) in &ctl_ds;'; put '%let abortme=1;'; put '%end;'; put '%end;'; put '%end;'; put '%else %do;'; put '%let msg=lock_anytable given unsupported action (&action);'; put '%let abortme=1;'; put '%end;'; put '/* catch errs - mp_abort must be called outside of a logic block */'; put '%mp_abort(iftrue=(&abortme=1),'; put 'msg=%superq(msg),'; put 'mac=&sysmacroname'; put ')'; put '%exit_macro:'; put 'data _null_;'; put 'put "&sysmacroname: Exit vars: action=&action lib=&lib ds=&ds";'; put 'put " syscc=&syscc sqlrc=&sqlrc syserr=&syserr";'; put 'run;'; put '%mend mp_lockanytable;'; put '%macro bitemporal_closeouts('; put 'tech_from=tx_from_dttm'; put ',tech_to = tx_to_dttm /* Technical TO datetime variable.'; put 'Req''d on BASE table only. */'; put ',base_lib=WORK /* Libref of the BASE table. */'; put ',base_dsn=BASETABLE /* Name of BASE table. */'; put ',append_lib=WORK /* Libref of the STAGING table. */'; put ',append_dsn=APPENDTABLE /* Name of STAGING table. */'; put ',PK= name sex /* Business key, space separated. */'; put '/* Should INCLUDE BUS_FROM field if relevant. */'; put ',NOW=DEFINE'; put ',FILTER= /* supply a filter to limit the update */'; put ',outdest= /* supply an unquoted filepath/filename.ext to get'; put 'a text file containing the update statements */'; put ',loadtype='; put ',loadtarget=YES /* if <> YES will return without changing anything */'; put ');'; put '%put ENTERING &sysmacroname;'; put '%local x var start;'; put '%let start=%sysfunc(datetime());'; put '%dc_assignlib(WRITE,&base_lib)'; put '%dc_assignlib(WRITE,&append_lib)'; put '%if &now=DEFINE %then %let now=&dc_dttmtfmt.;'; put '%put &=now;'; put '/**'; put '* perform basic checks'; put '*/'; put '/* do tables exist? */'; put '%if not %sysfunc(exist(&base_lib..&base_dsn)) %then %do;'; put '%mp_abort(msg=&base_lib..&base_dsn does not exist)'; put '%end;'; put '%else %if %sysfunc(exist(&append_lib..&append_dsn))=0'; put 'and %sysfunc(exist(&append_lib..&append_dsn,VIEW))=0 %then %do;'; put '%mp_abort(msg=&append_lib..&append_dsn does not exist)'; put '%end;'; put '/* do TX columns exist? */'; put '%if &loadtype ne UPDATE %then %do;'; put '%if not %mf_existvar(&base_lib..&base_dsn,&tech_from) %then %do;'; put '%mp_abort(msg=&tech_from does not exist on &base_lib..&base_dsn)'; put '%end;'; put '%else %if not %mf_existvar(&base_lib..&base_dsn,&tech_to) %then %do;'; put '%mp_abort(msg=&tech_to does not exist on &base_lib..&base_dsn)'; put '%end;'; put '%end;'; put '/* do PK columns exist? */'; put '%do x=1 %to %sysfunc(countw(&PK));'; put '%let var=%scan(&pk,&x,%str( ));'; put '%if not %mf_existvar(&base_lib..&base_dsn,&var) %then %do;'; put '%mp_abort(msg=&var does not exist on &base_lib..&base_dsn)'; put '%end;'; put '%else %if not %mf_existvar(&append_lib..&append_dsn,&var) %then %do;'; put '%mp_abort(msg=&var does not exist on &append_lib..&append_dsn)'; put '%end;'; put '%end;'; put '/* check uniqueness */'; put 'proc sort data=&append_lib..&append_dsn'; put 'out=___closeout1 noduprecs dupout=___closeout1a;'; put 'by &pk;'; put 'run;'; put '%if %mf_getattrn(___closeout1a,NLOBS)>0 %then'; put '%put NOTE: dups on (&PK) in (&append_lib..&append_dsn);'; put '/* is &NOW value within a tolerance? Should not allow renegade closeouts.. */'; put '%local gap;'; put '%let gap=0;'; put 'data _null_;'; put 'now=&now;'; put 'gap=intck(''HOURS'',now,datetime());'; put 'call symputx(''gap'',gap,''l'');'; put 'run;'; put '%mf_abort('; put 'iftrue=(&gap > 24),'; put 'msg=NOW variable (&now) is not within a 24hr tolerance'; put ')'; put '/* have any warnings / errs occurred thus far? If so, abort */'; put '%mf_abort('; put 'iftrue=(&syscc>0),'; put 'msg=Aborted due to SYSCC=&SYSCC status'; put ')'; put '/**'; put '* Create closeout statements. These are sent as individual SQL statements'; put '* to ensure pass-through utilisation. The update_cnt variable monitors'; put '* how many records were actually updated on the target table.'; put '*/'; put '%local update_cnt;'; put '%let update_cnt=0;'; put 'filename tmp temp;'; put 'data _null_;'; put 'set ___closeout1;'; put 'file tmp;'; put 'if _n_=1 then put ''proc sql noprint;'' ;'; put 'length string $32767.;'; put '%if &loadtype=UPDATE %then %do;'; put 'put "delete from &base_lib..&base_dsn where 1";'; put '%end;'; put '%else %do;'; put 'now=symget(''now'');'; put 'put "update &base_lib..&base_dsn set &tech_to= " now @;'; put '%if %mf_existvar(&base_lib..&base_dsn,PROCESSED_DTTM) %then %do;'; put 'put " ,PROCESSED_DTTM=" now @;'; put '%end;'; put 'put " where " now " lt &tech_to ";'; put '%end;'; put '%do x=1 %to %sysfunc(countw(&PK));'; put '%let var=%scan(&pk,&x,%str( ));'; put '%if %mf_getvartype(&base_lib..&base_dsn,&var)=C %then %do;'; put '/* use single quotes to avoid ampersand resolution in data */'; put 'string=" & &var=''"!!trim(prxchange("s/''/''''/",-1,&var))!!"''";'; put '%end;'; put '%else %do;'; put 'string=cats(" & &var=",&var);'; put '%end;'; put 'put string;'; put '%end;'; put 'put "&filter ;";'; put 'put ''%let update_cnt=%eval(&update_cnt+&sqlobs);%put update_cnt=&update_cnt;'';'; put 'run;'; put 'data _null_;'; put 'infile tmp;'; put 'input;'; put 'putlog _infile_;'; put 'run;'; put '%if &loadtarget ne YES %then %return;'; put '/* ensure we have a lock */'; put '%mp_lockanytable(LOCK,'; put 'lib=&base_lib,ds=&base_dsn'; put ',ref=bitemporal_closeouts'; put ',ctl_ds=&mpelib..mpe_lockanytable'; put ')'; put 'options source2;'; put '%inc tmp;'; put 'filename tmp clear;'; put '/**'; put '* Update audit tracker'; put '*/'; put '%local newobs; %let newobs=%mf_getattrn(work.___closeout1,NLOBS);'; put '%local user; %let user=%mf_getuser();'; put 'proc sql;'; put 'insert into &mpelib..mpe_dataloads'; put 'set libref=%upcase("&base_lib")'; put ',DSN=%upcase("&base_dsn")'; put ',ETLSOURCE="&append_lib..&append_dsn contained &newobs records"'; put ',LOADTYPE="CLOSEOUT"'; put ',DELETED_RECORDS=&update_cnt'; put ',NEW_RECORDS=0'; put ',DURATION=%sysfunc(datetime())-&start'; put ',USER_NM="&user"'; put ',PROCESSED_DTTM=&now;'; put 'quit;'; put '%mend bitemporal_closeouts;'; put '%macro mf_existds(libds'; put ')/*/STORE SOURCE*/;'; put '%if %sysfunc(exist(&libds)) ne 1 & %sysfunc(exist(&libds,VIEW)) ne 1 %then 0;'; put '%else 1;'; put '%mend mf_existds;'; put '%macro mf_getschema(libref'; put ')/*/STORE SOURCE*/;'; put '%local dsid vnum rc schema;'; put '/* in case the parameter is a libref.tablename, pull off just the libref */'; put '%let libref = %upcase(%scan(&libref, 1, %str(.)));'; put '%let dsid=%sysfunc(open(sashelp.vlibnam(where=('; put 'libname="%upcase(&libref)" and sysname=''Schema/Owner'''; put ')),i));'; put '%if (&dsid ^= 0) %then %do;'; put '%let vnum=%sysfunc(varnum(&dsid,SYSVALUE));'; put '%let rc=%sysfunc(fetch(&dsid));'; put '%let schema=%sysfunc(getvarc(&dsid,&vnum));'; put '%put &libref. schema is &schema.;'; put '%let rc= %sysfunc(close(&dsid));'; put '%end;'; put '&schema'; put '%mend mf_getschema;'; put '/** @endcond */'; put '%macro mf_getuniquename(prefix=MC);'; put '&prefix.%substr(%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32-%length(&prefix))'; put '%mend mf_getuniquename;'; put '%macro mf_getvarlist(libds'; put ',dlm=%str( )'; put ',quote=no'; put ',typefilter=A'; put ')/*/STORE SOURCE*/;'; put '/* declare local vars */'; put '%local outvar dsid nvars x rc dlm q var vtype;'; put '/* credit Rowland Hale - byte34 is double quote, 39 is single quote */'; put '%if %upcase("e)=DOUBLE %then %let q=%qsysfunc(byte(34));'; put '%else %if %upcase("e)=SINGLE %then %let q=%qsysfunc(byte(39));'; put '/* open dataset in macro */'; put '%let dsid=%sysfunc(open(&libds));'; put '%if &dsid %then %do;'; put '%let nvars=%sysfunc(attrn(&dsid,NVARS));'; put '%if &nvars>0 %then %do;'; put '/* add variables with supplied delimeter */'; put '%do x=1 %to &nvars;'; put '/* get variable type */'; put '%let vtype=%sysfunc(vartype(&dsid,&x));'; put '%if &vtype=&typefilter or &typefilter=A %then %do;'; put '%let var=&q.%sysfunc(varname(&dsid,&x))&q.;'; put '%if &var=&q&q %then %do;'; put '%put &sysmacroname: Empty column found in &libds!;'; put '%let var=&q. &q.;'; put '%end;'; put '%if %quote(&outvar)=%quote() %then %let outvar=&var;'; put '%else %let outvar=&outvar.&dlm.&var.;'; put '%end;'; put '%end;'; put '%end;'; put '%let rc=%sysfunc(close(&dsid));'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: Unable to open &libds (rc=&dsid);'; put '%put &sysmacroname: SYSMSG= %sysfunc(sysmsg());'; put '%let rc=%sysfunc(close(&dsid));'; put '%end;'; put '%do;%unquote(&outvar)%end;'; put '%mend mf_getvarlist;'; put '%macro mf_abort(mac=mf_abort.sas, msg=, iftrue=%str(1=1)'; put ')/des=''ungraceful abort'' /*STORE SOURCE*/;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%put NOTE: /// mf_abort macro executing //;'; put '%if %length(&mac)>0 %then %put NOTE- called by &mac;'; put '%put NOTE - &msg;'; put '%abort;'; put '%mend mf_abort;'; put '/** @endcond */'; put '%macro mf_verifymacvars('; put 'verifyVars /* list of macro variable NAMES */'; put ',makeUpcase=NO /* set to YES to make all the variable VALUES uppercase */'; put ',mAbort=SOFT'; put ')/*/STORE SOURCE*/;'; put '%local verifyIterator verifyVar abortmsg;'; put '%do verifyIterator=1 %to %sysfunc(countw(&verifyVars,%str( )));'; put '%let verifyVar=%qscan(&verifyVars,&verifyIterator,%str( ));'; put '%if not %symexist(&verifyvar) %then %do;'; put '%let abortmsg= Variable &verifyVar is MISSING;'; put '%goto exit_err;'; put '%end;'; put '%if %length(%trim(&&&verifyVar))=0 %then %do;'; put '%let abortmsg= Variable &verifyVar is EMPTY;'; put '%goto exit_err;'; put '%end;'; put '%if &makeupcase=YES %then %do;'; put '%let &verifyVar=%upcase(&&&verifyvar);'; put '%end;'; put '%end;'; put '%goto exit_success;'; put '%exit_err:'; put '%put &abortmsg;'; put '%mf_abort(iftrue=(&mabort ne SOFT),'; put 'mac=mf_verifymacvars,'; put 'msg=%str(&abortmsg)'; put ')'; put '0'; put '%return;'; put '%exit_success:'; put '1'; put '%mend mf_verifymacvars;'; put '%macro mf_wordsInStr1ButNotStr2('; put 'Str1= /* string containing words to extract */'; put ',Str2= /* used to compare with the extract string */'; put ')/*/STORE SOURCE*/;'; put '%local count_base count_extr i i2 extr_word base_word match outvar;'; put '%if %length(&str1)=0 or %length(&str2)=0 %then %do;'; put '%put base string (str1)= &str1;'; put '%put compare string (str2) = &str2;'; put '%return;'; put '%end;'; put '%let count_base=%sysfunc(countw(&Str2));'; put '%let count_extr=%sysfunc(countw(&Str1));'; put '%do i=1 %to &count_extr;'; put '%let extr_word=%scan(&Str1,&i,%str( ));'; put '%let match=0;'; put '%do i2=1 %to &count_base;'; put '%let base_word=%scan(&Str2,&i2,%str( ));'; put '%if &extr_word=&base_word %then %let match=1;'; put '%end;'; put '%if &match=0 %then %let outvar=&outvar &extr_word;'; put '%end;'; put '&outvar'; put '%mend mf_wordsInStr1ButNotStr2;'; put '%macro mf_isblank(param'; put ')/*/STORE SOURCE*/;'; put '%sysevalf(%superq(param)=,boolean)'; put '%mend mf_isblank;'; put '%macro mp_dropmembers('; put 'list /* space separated list of datasets / views */'; put ',libref=WORK /* can only drop from a single library at a time */'; put ',iftrue=%str(1=1)'; put ')/*/STORE SOURCE*/;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%if %mf_isblank(&list) %then %do;'; put '%put NOTE: nothing to drop!;'; put '%return;'; put '%end;'; put 'proc datasets lib=&libref nolist;'; put 'delete &list;'; put 'delete &list /mtype=view;'; put 'run;'; put '%mend mp_dropmembers;'; put '%macro mf_getquotedstr(IN_STR'; put ',DLM=%str(,)'; put ',QUOTE=S'; put ',indlm=%str( )'; put ')/*/STORE SOURCE*/;'; put '/* credit Rowland Hale - byte34 is double quote, 39 is single quote */'; put '%if "e=S %then %let quote=%qsysfunc(byte(39));'; put '%else %if "e=D %then %let quote=%qsysfunc(byte(34));'; put '%else %if "e=N %then %let quote=;'; put '%local i item buffer;'; put '%let i=1;'; put '%do %while (%qscan(&IN_STR,&i,%str(&indlm)) ne %str() ) ;'; put '%let item=%qscan(&IN_STR,&i,%str(&indlm));'; put '%if %bquote("E) ne %then %let item="E%qtrim(&item)"E;'; put '%else %let item=%qtrim(&item);'; put '%if (&i = 1) %then %let buffer =%qtrim(&item);'; put '%else %let buffer =&buffer&DLM%qtrim(&item);'; put '%let i = %eval(&i+1);'; put '%end;'; put '%let buffer=%sysfunc(coalescec(%qtrim(&buffer),"E"E));'; put '&buffer'; put '%mend mf_getquotedstr;'; put '%macro mf_nobs(libds'; put ')/*/STORE SOURCE*/;'; put '%mf_getattrn(&libds,NLOBS)'; put '%mend mf_nobs;'; put '%macro mp_retainedkey('; put 'base_lib=WORK'; put ',base_dsn=BASETABLE'; put ',append_lib=WORK'; put ',append_dsn=APPENDTABLE'; put ',retained_key=DEFAULT_RK'; put ',business_key= PK1 PK2'; put ',check_uniqueness=NO'; put ',maxkeytable=0'; put ',locktable=0'; put ',outds=WORK.APPEND'; put ',filter_str='; put ');'; put '%put &sysmacroname entry vars:;'; put '%put _local_;'; put '%local base_libds app_libds key_field check maxkey idx_pk newkey_cnt iserr'; put 'msg x tempds1 tempds2 comma_pk appnobs checknobs dropvar tempvar idx_val;'; put '%let base_libds=%upcase(&base_lib..&base_dsn);'; put '%let app_libds=%upcase(&append_lib..&append_dsn);'; put '%let tempds1=%mf_getuniquename();'; put '%let tempds2=%mf_getuniquename();'; put '%let comma_pk=%mf_getquotedstr(in_str=%str(&business_key),dlm=%str(,),quote=);'; put '%let outds=%sysfunc(ifc(%index(&outds,.)=0,work.&outds,&outds));'; put '/* validation checks */'; put '%let iserr=0;'; put '%if &syscc>0 %then %do;'; put '%let iserr=1;'; put '%let msg=%str(SYSCC=&syscc on macro entry);'; put '%end;'; put '%else %if %sysfunc(exist(&base_libds))=0 %then %do;'; put '%let iserr=1;'; put '%let msg=%str(Base LIBDS (&base_libds) expected but NOT FOUND);'; put '%end;'; put '%else %if %sysfunc(exist(&app_libds))=0 %then %do;'; put '%let iserr=1;'; put '%let msg=%str(Append LIBDS (&app_libds) expected but NOT FOUND);'; put '%end;'; put '%else %if &maxkeytable ne 0 and %sysfunc(exist(&maxkeytable))=0 %then %do;'; put '%let iserr=1;'; put '%let msg=%str(Maxkeytable (&maxkeytable) expected but NOT FOUND);'; put '%end;'; put '%else %if &maxkeytable ne 0 and %sysfunc(exist(&locktable))=0 %then %do;'; put '%let iserr=1;'; put '%let msg=%str(Locktable (&locktable) expected but NOT FOUND);'; put '%end;'; put '%else %if %length(&business_key)=0 %then %do;'; put '%let iserr=1;'; put '%let msg=%str(Business key (&business_key) expected but NOT FOUND);'; put '%end;'; put '%do x=1 %to %sysfunc(countw(&business_key));'; put '/* check business key values exist */'; put '%let key_field=%scan(&business_key,&x,%str( ));'; put '%if not %mf_existvar(&app_libds,&key_field) %then %do;'; put '%let iserr=1;'; put '%let msg=Business key (&key_field) not found on &app_libds!;'; put '%goto err;'; put '%end;'; put '%else %if not %mf_existvar(&base_libds,&key_field) %then %do;'; put '%let iserr=1;'; put '%let msg=Business key (&key_field) not found on &base_libds!;'; put '%goto err;'; put '%end;'; put '%end;'; put '%err:'; put '%if &iserr=1 %then %do;'; put '/* err case so first perform an unlock of the base table before exiting */'; put '%mp_lockanytable('; put 'UNLOCK,lib=&base_lib,ds=&base_dsn,ref=%superq(msg),ctl_ds=&locktable'; put ')'; put '%end;'; put '%mp_abort(iftrue=(&iserr=1),mac=mp_retainedkey,msg=%superq(msg))'; put 'proc sql noprint;'; put 'select sum(max(&retained_key),0) into: maxkey from &base_libds;'; put '/**'; put '* get base table RK and bus field values for lookup'; put '*/'; put 'proc sql noprint;'; put 'create table &tempds1 as'; put 'select distinct &comma_pk,&retained_key'; put 'from &base_libds &filter_str'; put 'order by &comma_pk,&retained_key;'; put '%if &check_uniqueness=YES %then %do;'; put 'select count(*) into:checknobs'; put 'from (select distinct &comma_pk from &app_libds);'; put 'select count(*) into: appnobs from &app_libds; /* might be view */'; put '%if &checknobs ne &appnobs %then %do;'; put '%let msg=Source table &app_libds is not unique on (&business_key);'; put '%let iserr=1;'; put '%end;'; put '%end;'; put '%if &iserr=1 %then %do;'; put '/* err case so first perform an unlock of the base table before exiting */'; put '%mp_lockanytable('; put 'UNLOCK,lib=&base_lib,ds=&base_dsn,ref=%superq(msg),ctl_ds=&locktable'; put ')'; put '%end;'; put '%mp_abort(iftrue= (&iserr=1),mac=mp_retainedkey,msg=%superq(msg))'; put '%if %mf_existvar(&app_libds,&retained_key)'; put '%then %let dropvar=(drop=&retained_key);'; put '/* prepare interim table with retained key populated for matching keys */'; put 'proc sql noprint;'; put 'create table &tempds2 as'; put 'select b.&retained_key, a.*'; put 'from &app_libds &dropvar a'; put 'left join &tempds1 b'; put 'on 1'; put '%do idx_pk=1 %to %sysfunc(countw(&business_key));'; put '%let idx_val=%scan(&business_key,&idx_pk);'; put 'and a.&idx_val=b.&idx_val'; put '%end;'; put 'order by &retained_key;'; put '/* identify the number of entries without retained keys (new records) */'; put 'select count(*) into: newkey_cnt'; put 'from &tempds2'; put 'where missing(&retained_key);'; put 'quit;'; put '/**'; put '* Update maxkey table if link provided'; put '*/'; put '%if &maxkeytable ne 0 %then %do;'; put 'proc sql noprint;'; put 'select count(*) into: check from &maxkeytable'; put 'where upcase(keytable)="&base_libds";'; put '%mp_lockanytable(LOCK'; put ',lib=%scan(&maxkeytable,1,.)'; put ',ds=%scan(&maxkeytable,2,.)'; put ',ref=Updating maxkeyvalues with mp_retainedkey'; put ',ctl_ds=&locktable'; put ')'; put 'proc sql;'; put '%if &check=0 %then %do;'; put 'insert into &maxkeytable'; put 'set keytable="&base_libds"'; put ',keycolumn="&retained_key"'; put ',max_key=%eval(&maxkey+&newkey_cnt)'; put ',processed_dttm="%sysfunc(datetime(),%mf_fmtdttm())"dt;'; put '%end;'; put '%else %do;'; put 'update &maxkeytable'; put 'set max_key=%eval(&maxkey+&newkey_cnt)'; put ',processed_dttm="%sysfunc(datetime(),%mf_fmtdttm())"dt'; put 'where keytable="&base_libds";'; put '%end;'; put '%mp_lockanytable(UNLOCK'; put ',lib=%scan(&maxkeytable,1,.)'; put ',ds=%scan(&maxkeytable,2,.)'; put ',ref=Updating maxkeyvalues with maxkey=%eval(&maxkey+&newkey_cnt)'; put ',ctl_ds=&locktable'; put ')'; put '%end;'; put '/* fill in the missing retained key values */'; put '%let tempvar=%mf_getuniquename();'; put 'data &outds(drop=&tempvar);'; put 'retain &tempvar %eval(&maxkey+1);'; put 'set &tempds2;'; put 'if &retained_key =. then &retained_key=&tempvar;'; put '&tempvar=&tempvar+1;'; put 'run;'; put '%mend mp_retainedkey;'; put '/** @cond */'; put '%macro mp_storediffs(libds'; put ',origds'; put ',key'; put ',delds=0'; put ',appds=0'; put ',modds=0'; put ',outds=work.mp_storediffs'; put ',loadref=0'; put ',processed_dttm=0'; put ',mdebug=0'; put ')/*/STORE SOURCE*/;'; put '%local dbg;'; put '%if &mdebug=1 %then %do;'; put '%put &sysmacroname entry vars:;'; put '%put _local_;'; put '%end;'; put '%else %let dbg=*;'; put '/* set up unique and temporary vars */'; put '%local ds1 ds2 ds3 ds4 hashkey inds_auto inds_keep dslist vlist;'; put '%let ds1=%upcase(work.%mf_getuniquename(prefix=mpsd_ds1));'; put '%let ds2=%upcase(work.%mf_getuniquename(prefix=mpsd_ds2));'; put '%let ds3=%upcase(work.%mf_getuniquename(prefix=mpsd_ds3));'; put '%let ds4=%upcase(work.%mf_getuniquename(prefix=mpsd_ds4));'; put '%let hashkey=%upcase(%mf_getuniquename(prefix=mpsd_hashkey));'; put '%let inds_auto=%upcase(%mf_getuniquename(prefix=mpsd_inds_auto));'; put '%let inds_keep=%upcase(%mf_getuniquename(prefix=mpsd_inds_keep));'; put '%let dslist=&origds;'; put '%if &delds ne 0 %then %do;'; put '%let delds=%upcase(&delds);'; put '%if %scan(&delds,-1,.)=&delds %then %let delds=WORK.&delds;'; put '%let dslist=&dslist &delds;'; put '%end;'; put '%if &appds ne 0 %then %do;'; put '%let appds=%upcase(&appds);'; put '%if %scan(&appds,-1,.)=&appds %then %let appds=WORK.&appds;'; put '%let dslist=&dslist &appds;'; put '%end;'; put '%if &modds ne 0 %then %do;'; put '%let modds=%upcase(&modds);'; put '%if %scan(&modds,-1,.)=&modds %then %let modds=WORK.&modds;'; put '%let dslist=&dslist &modds;'; put '%end;'; put '%let origds=%upcase(&origds);'; put '%if %scan(&origds,-1,.)=&origds %then %let origds=WORK.&origds;'; put '%let key=%upcase(&key);'; put '/* hash the key and append all the tables (marking the source) */'; put 'data &ds1;'; put 'set &dslist indsname=&inds_auto;'; put '&hashkey=put(md5(catx(''|'',%mf_getquotedstr(&key,quote=N))),$hex32.);'; put '&inds_keep=upcase(&inds_auto);'; put 'proc sort;'; put 'by &inds_keep &hashkey;'; put 'run;'; put '/* transpose numeric & char vars */'; put 'proc transpose data=&ds1'; put 'out=&ds2(rename=(&hashkey=key_hash _name_=tgtvar_nm col1=newval_num));'; put 'by &inds_keep &hashkey;'; put 'var _numeric_;'; put 'run;'; put 'proc transpose data=&ds1'; put 'out=&ds3('; put 'rename=(&hashkey=key_hash _name_=tgtvar_nm col1=newval_char)'; put 'where=(tgtvar_nm not in ("&hashkey","&inds_keep"))'; put ');'; put 'by &inds_keep &hashkey;'; put 'var _character_;'; put 'run;'; put '%if %index(&libds,-)>0 and %scan(&libds,2,-)=FC %then %do;'; put '/* this is a format catalog - cannot query cols directly */'; put '%let vlist="TYPE","FMTNAME","FMTROW","START","END","LABEL","MIN","MAX"'; put ',"DEFAULT","LENGTH","FUZZ","PREFIX","MULT","FILL","NOEDIT","SEXCL"'; put ',"EEXCL","HLO","DECSEP","DIG3SEP","DATATYPE","LANGUAGE";'; put '%end;'; put '%else %let vlist=%mf_getvarlist(&libds,dlm=%str(,),quote=DOUBLE);'; put 'data &ds4;'; put 'length &inds_keep $41 tgtvar_nm $32 _label_ $256;'; put 'if _n_=1 then call missing(_label_);'; put 'drop _label_;'; put 'set &ds2 &ds3 indsname=&inds_auto;'; put 'tgtvar_nm=upcase(tgtvar_nm);'; put 'if tgtvar_nm in (%upcase(&vlist));'; put 'if upcase(&inds_auto)="&ds2" then tgtvar_type=''N'';'; put 'else if upcase(&inds_auto)="&ds3" then tgtvar_type=''C'';'; put 'else do;'; put 'putlog ''ERR'' +(-1) "OR: unidentified vartype input!" &inds_auto;'; put 'call symputx(''syscc'',98);'; put 'end;'; put 'if &inds_keep="&appds" then move_type=''A'';'; put 'else if &inds_keep="&delds" then move_type=''D'';'; put 'else if &inds_keep="&modds" then move_type=''M'';'; put 'else if &inds_keep="&origds" then move_type=''O'';'; put 'else do;'; put 'putlog ''ERR'' +(-1) "OR: unidentified movetype input!" &inds_keep;'; put 'call symputx(''syscc'',99);'; put 'end;'; put 'tgtvar_nm=upcase(tgtvar_nm);'; put 'if tgtvar_nm in (%mf_getquotedstr(&key)) then is_pk=1;'; put 'else is_pk=0;'; put 'drop &inds_keep;'; put 'run;'; put '%if "&loadref"="0" %then %let loadref=%sysfunc(uuidgen());'; put '%if &processed_dttm=0 %then %let processed_dttm=%sysfunc(datetime());'; put '%let libds=%upcase(&libds);'; put '/* join orig vals for modified & deleted */'; put 'proc sql;'; put 'create table &outds as'; put 'select "&loadref" as load_ref length=36'; put ',&processed_dttm as processed_dttm format=E8601DT26.6'; put ',"%scan(&libds,1,.)" as libref length=8'; put ',"%scan(&libds,2,.)" as dsn length=32'; put ',b.key_hash length=32'; put ',b.move_type length=1'; put ',b.tgtvar_nm length=32'; put ',b.is_pk'; put ',case when b.move_type ne ''M'' then -1'; put 'when a.newval_num=b.newval_num and a.newval_char=b.newval_char then 0'; put 'else 1'; put 'end as is_diff'; put ',b.tgtvar_type length=1'; put ',case when b.move_type=''D'' then b.newval_num'; put 'else a.newval_num'; put 'end as oldval_num format=best32.'; put ',case when b.move_type=''D'' then .'; put 'else b.newval_num'; put 'end as newval_num format=best32.'; put ',case when b.move_type=''D'' then b.newval_char'; put 'else a.newval_char'; put 'end as oldval_char length=32765'; put ',case when b.move_type=''D'' then '''''; put 'else b.newval_char'; put 'end as newval_char length=32765'; put 'from &ds4(where=(move_type=''O'')) as a'; put 'right join &ds4(where=(move_type ne ''O'')) as b'; put 'on a.tgtvar_nm=b.tgtvar_nm'; put 'and a.key_hash=b.key_hash'; put 'order by move_type, key_hash,is_pk desc, tgtvar_nm;'; put '%if &mdebug=0 %then %do;'; put 'proc sql;'; put 'drop table &ds1, &ds2, &ds3, &ds4;'; put '%end;'; put '%mend mp_storediffs;'; put '/** @endcond */'; put '%macro bitemporal_dataloader('; put 'bus_from= /* Business FROM datetime variable. Req''d on'; put 'STAGING & BASE tables.*/'; put ',bus_to = /* Business TO datetime variable. Req''d on'; put 'STAGING & BASE tables. */'; put ',bus_from_override= /* Provide a hard coded BUS_FROM datetime value.*/'; put ',bus_to_override= /* provide a hard coded BUS_TO datetime value */'; put ',tech_from= /* Technical FROM datetime variable. Req''d on'; put 'BASE table only. */'; put ',tech_to = /* Technical TO datetime variable. Req''d on BASE'; put 'table only. */'; put ',processed= 0'; put ',base_lib=WORK /* Libref of the BASE table. */'; put ',base_dsn=BASETABLE /* Name of BASE table. */'; put ',append_lib=WORK /* Libref of the STAGING table. */'; put ',append_dsn=APPENDTABLE'; put ',high_date=''01JAN5999:00:00:00''dt /* High date to close out records */'; put ',PK= name sex'; put ',RK_UNDERLYING='; put ',KEEPVARS= /* Provides option for removing unwanted vars from append table */'; put ',RK_UPDATE_MAXKEYTABLE=NO /* If switching (or mix matching) with regular'; put 'SCD2 loader then set this switch to YES to'; put 'ensure the MAXKEYTABLE is updated with the'; put 'current maximum RK value for the target table'; put '*/'; put ',CHECK_UNIQUENESS=YES /* Perform a check of the APPEND table to ensure it is'; put 'unique on its business key */'; put ',ETLSOURCE=demo /* supply a value ($50.) to show as ETLSOURCE in'; put '&dclib..DATALOADS */'; put ',LOADTYPE=BITEMPORAL'; put ',RK_MAXKEYTABLE= mpe_maxkeyvalues'; put ',LOG=1 /* Switch to 0 to prevent records being added to'; put '&mpelib..mpe_DATALOADS (ie when testing)*/'; put ',DELETE_COL= _____DELETE__THIS__RECORD_____'; put '/* If this variable is found in the append dataset'; put 'then records are closed out (or deleted) in the'; put 'append table where that variable= "Yes" */'; put ',LOADTARGET=YES /* set to anything but uppercase YES to switch off'; put 'target table load and generate temp tables only */'; put ',CLOSE_VARS='; put '/*a problem with regular SCD2 or TXTEMPORAL loads is that there is'; put 'no facility to close out removed records (all records are'; put 'assumed new or changed). But how does one determine which'; put 'records are removed? Short of loading the entire table'; put 'each time? This parameter allows a set of variables'; put '(this should be a subset of the PK) to be declared, and'; put 'the macro will determine which records in the base table'; put 'need to be closed out ahead of the load.'; put 'For instance, given the following:'; put 'Base Table Staging Table'; put 'DATE ENTITY AMOUNT DATE ENTITY AMOUNT'; put 'JAN ACME4 66 JAN ACME4 66'; put 'FEB ACME4 99 FEB ACME4 99'; put 'FEB ACME1 22'; put 'By supplying DATE in CLOSE_VARS and DATE ENTITY as the PK,'; put 'the "FEB PAG 22" record would get closed out.'; put '*/'; put ',config_table=&dclib..MPE_CONFIG'; put ',dclib=&dc_libref'; put ',outds_del=work.outds_del'; put ',outds_add=work.outds_add'; put ',outds_mod=work.outds_mod'; put ',outds_audit=0'; put ');'; put '/* when changing this macro, update the version num here */'; put '%local ver;'; put '%let ver=32;'; put '%put &sysmacroname entry vars:;'; put '%put _local_;'; put '%dc_assignlib(WRITE,&base_lib) /* may not already be assigned */'; put '/* return straight away if nothing to load */'; put '%let nobs= %mf_getattrn(&append_lib..&append_dsn,NLOBS);'; put '%if &nobs=-1 %then %do;'; put 'proc sql noprint; select count(*) into: nobs from &append_lib..&append_dsn;'; put '%end;'; put '%if &nobs=0 %then %do;'; put '%put NOTE:; %put NOTE-;%put NOTE-;%put NOTE-;'; put '%put NOTE- Base dataset &append_lib..&append_dsn is empty. Nothing to upload!;'; put '%put NOTE-;%put NOTE-;%put NOTE-;'; put '%return;'; put '%end;'; put '/* hard exit if err condition exists */'; put '%mp_abort(iftrue= (&syscc > 0)'; put ',mac=bitemporal_dataloader'; put ',msg=%str(Bitemporal transform / job aborted due to SYSCC=&SYSCC status;)'; put ')'; put '%local engine_type;'; put '%let engine_type=%mf_getengine(&base_lib);'; put '%if (&engine_type=REDSHIFT or &engine_type=POSTGRES) and %length(&CLOSE_VARS)>0'; put '%then %do;'; put '%put NOTE:; %put NOTE-;%put NOTE-;%put NOTE-;'; put '%put NOTE- CLOSE_VARS functionality not yet supported in &engine_type;'; put '%put NOTE-;%put NOTE-;%put NOTE-;'; put '%return;'; put '%end;'; put '/**'; put '* The metadata functions (eg mf_existvar) will fail if the base table has a'; put '* SAS lock. So, make a snapshot of the base table for further use.'; put '* Also, make output tables (regardless).'; put '*/'; put '%local basecopy;'; put '%let basecopy=%mf_getuniquename(prefix=basecopy);'; put 'data &basecopy &outds_mod &outds_add &outds_del;'; put 'set &base_lib..&base_dsn;'; put 'stop;'; put 'run;'; put '%mp_abort(iftrue= (&syscc > 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc after base table copy - aborting due to table lock)'; put ')'; put '%local cols idx_pk md5_col ;'; put '%let md5_col=___TMP___md5;'; put '%let check_uniqueness=%upcase(&check_uniqueness);'; put '%let RK_UPDATE_MAXKEYTABLE=%upcase(&RK_UPDATE_MAXKEYTABLE);'; put '%let high_date=%unquote(&high_date);'; put '%let loadtype=%upcase(&loadtype);'; put '/* ensure irrelevant variables are cleared */'; put '%if &loadtype=BUSTEMPORAL %then %do;'; put '%let tech_from=;'; put '%let tech_to=;'; put '%end;'; put '%else %if &loadtype=TXTEMPORAL or &loadtype=UPDATE %then %do;'; put '%let bus_from=;'; put '%let bus_to=;'; put '%end;'; put '/* ensure relevant variables are supplied */'; put '%mp_abort(iftrue=(&loadtype=BITEMPORAL & %mf_verifymacvars(bus_from bus_to)=0)'; put ',mac=bitemporal_dataloader'; put ',msg=%str(Missing BUS_FROM / BUS_TO)'; put ')'; put '%mp_abort(iftrue=(&loadtype=TXTEMPORAL & %mf_verifymacvars(tech_from tech_to)=0)'; put ',mac=bitemporal_dataloader'; put ',msg=%str(Missing TECH_FROM / TECH_TO)'; put ')'; put '/**'; put '* drop any tables (may be defined as views or vice versa preventing overwrite)'; put '*/'; put '%mp_dropmembers(append bitemp0_append bitemp_cols)'; put '/* SQL Server requires its own time values */'; put '/* 9.2 will only give picture format down to seconds. 9.3 allows'; put 'milliseconds by using lower S and defining the decimal in the format name..*/'; put 'PROC FORMAT;'; put 'picture MyMSdt other=''%0Y-%0m-%0dT%0H:%0M:%0S'' (datatype=datetime);'; put 'RUN;'; put '%local dbnow;'; put '%let dbnow="%sysfunc(datetime(),%mf_fmtdttm())"dt;'; put 'data _null_;'; put '/* convert space separated macvar to comma separated for SQL processing */'; put 'call symputx(''PK_COMMA'',tranwrd(compbl("&pk"),'' '','',''),''L'');'; put 'call symputx(''PK_CNT'',countw("&pk",'' ''),''L'');'; put 'now=&dbnow;'; put 'call symputx(''NOW'',now,''L'');'; put 'call symputx(''SQLNOW'',cats("''",put(now,MyMSdt.),"''"),''L'');'; put 'length etlsource $100;'; put 'etlsource=subpad(symget(''etlsource''),1,100);'; put 'call symputx(''etlsource'',etlsource,''l'');'; put 'run;'; put '/**'; put '* Even if no PROCESSED var provided, assume that any variable named'; put '* PROCESSED_DTTM should be updated'; put '*/'; put '%if &processed=0 %then %do;'; put '%if %mf_existvar(&basecopy,PROCESSED_DTTM)'; put '%then %let processed=PROCESSED_DTTM;'; put '%else %let processed=;'; put '%end;'; put '/* extract colnames for md5 creation / change tracking */'; put 'proc contents noprint data=&base_lib..&base_dsn'; put 'out=work.bitemp_cols (keep=name type length varnum format:);'; put 'run;'; put 'proc sql noprint;'; put 'select name into: cols separated by '','''; put 'from work.bitemp_cols'; put 'where upcase(name) not in'; put '(%upcase("&bus_from","&bus_to"'; put ',"&tech_from","&tech_to"'; put ',"&processed","&delete_col")) ;'; put 'select case when type in (2,6) then cats(''put(md5(trim('',name,'')),$hex32.)'')'; put '/* multiply by 1 to strip precision errors (eg 0 != 0) */'; put '/* but ONLY if not missing, else will lose any special missing values */'; put 'else cats(''put(md5(trim(put(ifn(missing('''; put ',name,''),'',name,'','',name,''*1),binary64.))),$hex32.)'') end'; put 'into: stripcols separated by ''||'''; put 'from work.bitemp_cols'; put 'where upcase(name) not in'; put '(%upcase("&bus_from","&bus_to"'; put ',"&tech_from","&tech_to"'; put ',"&processed","&delete_col")) ;'; put '/* set default formats*/'; put '%let bus_from_fmt = datetime19.;'; put '%let bus_to_fmt = datetime19.;'; put '%let processed_fmt = datetime19.;'; put '%let tech_from_fmt = format=datetime19.;'; put '%let tech_to_fmt = format=datetime19.;'; put '%put &=stripcols;'; put '%put &=pk;'; put 'data _null_;'; put 'set work.bitemp_cols;'; put 'if type=2 or type=6 then do;'; put 'length fmt $49.;'; put 'if format='''' then fmt=cats(''$'',length,''.'');'; put 'else fmt=cats(format,formatl,''.'');'; put 'end;'; put 'else do;'; put 'if format='''' then fmt=cats(length,''.'');'; put 'else fmt=cats(format,formatl,''.'',formatd);'; put 'end;'; put 'if upcase(name)="%upcase(&bus_from)" then'; put 'call symputx(''bus_from_fmt'',fmt,''L'');'; put 'else if upcase(name)="%upcase(&bus_to)" then'; put 'call symputx(''bus_to_fmt'',fmt,''L'');'; put 'else if upcase(name)="%upcase(&tech_from)" then'; put 'call symputx(''tech_from_fmt'',"format="!!fmt,''L'');'; put 'else if upcase(name)="%upcase(&tech_to)" then'; put 'call symputx(''tech_to_fmt'',"format="!!fmt,''L'');'; put 'else if upcase(name)="%upcase(&processed)" then'; put 'call symputx(''processed_fmt'',fmt,''L'');'; put 'run;'; put '%if %index(%quote(&cols),___TMP___) %then %do;'; put '%let msg=%str(Table contains a variable name containing "___TMP___".%trim('; put ') This may conflict with temp variable generation!!);'; put '%mp_abort(msg=&msg,mac=bitemporal_dataloader);'; put '%let syscc=5;'; put '%return;'; put '%end;'; put '/* if transaction dates appear on the APPEND table, need to remove them */'; put '%local drop_tx_dates /* used in append table */'; put 'drop_tx_dates_noobs /* used to take the base table structure */;'; put '%if %mf_existvar(&append_lib..&append_dsn, &tech_from)'; put '%then %let drop_tx_dates=&tech_from;'; put '%if %mf_existvar(&append_lib..&append_dsn, &tech_to)'; put '%then %let drop_tx_dates=&drop_tx_dates &tech_to;'; put '%if %length(%trim(&drop_tx_dates))>0'; put '%then %let drop_tx_dates=(drop=&drop_tx_dates);'; put '%if %mf_existvar(&basecopy, &tech_from)'; put '%then %let drop_tx_dates_noobs=&tech_from;'; put '%if %mf_existvar(&basecopy, &tech_to)'; put '%then %let drop_tx_dates_noobs=&drop_tx_dates_noobs &tech_to;'; put '%if %length(%trim(&drop_tx_dates_noobs))>0'; put '%then %let drop_tx_dates_noobs=(drop=&drop_tx_dates_noobs obs=0);'; put '%else %let drop_tx_dates_noobs=(obs=0);'; put '/**'; put '* Lock the table. This is necessary as we are doing a two part update (first'; put '* closing records then appending new records). It is theoretically possible'; put '* that an upload may occur whilst preparing the staging tables. And the'; put '* staging tables are about to be prepared..'; put '*/'; put '%if &LOADTARGET = YES %then %do;'; put '%put locking &base_lib..&base_dsn;'; put '%mp_lockanytable(LOCK,'; put 'lib=&base_lib,ds=&base_dsn,ref=&ETLSOURCE,ctl_ds=&dclib..mpe_lockanytable'; put ')'; put '%if "&outds_audit" ne "0" %then %do;'; put '%put locking &outds_audit;'; put '%mp_lockanytable(LOCK'; put ',lib=%scan(&outds_audit,1,.)'; put ',ds=%scan(&outds_audit,2,.)'; put ',ref=&ETLSOURCE'; put ',ctl_ds=&dclib..mpe_lockanytable'; put ')'; put '%end;'; put '%end;'; put '%else %do;'; put '/* not an actual load, so avoid updating the max key table in next step. */'; put '%let rk_update_maxkeytable=NO;'; put '%end;'; put '%if %length(&RK_UNDERLYING)>0 %then %do;'; put '%mp_retainedkey('; put 'base_lib=&base_lib'; put ',base_dsn=&base_dsn'; put ',append_lib=&append_lib'; put ',append_dsn=&append_dsn'; put ',retained_key=&pk'; put ',business_key=&rk_underlying'; put ',check_uniqueness=&CHECK_UNIQUENESS'; put ',outds=work.append'; put '%if &rk_update_maxkeytable=NO %then %do;'; put ',maxkeytable=0'; put '%end;'; put '%else %do;'; put ',maxkeytable=&dclib..&RK_MAXKEYTABLE'; put '%end;'; put ',locktable=&dclib..mpe_lockanytable'; put '%if &loadtype=BITEMPORAL or &loadtype=TXTEMPORAL %then %do;'; put ',filter_str=%str( (where=( &now < &tech_to)) )'; put '%end;'; put ')'; put '%end;'; put '%else %do;'; put 'proc sql;'; put 'create view work.append as select * from &append_lib..&append_dsn;'; put '%end;'; put '/**'; put '* generate md5 for append table'; put '*/'; put '/* it is possible the source dataset has additional (unwanted) columns.'; put 'Drop if specified; */'; put '%if %length(&keepvars)>0 %then %do;'; put '/* remove tech dates from keepvars as they are generated later */'; put '%let keepvars=%sysfunc(tranwrd(%str( &keepvars ),%str( &tech_from ),%str( )));'; put '%let keepvars=%sysfunc(tranwrd(%str( &keepvars ),%str( &tech_to ),%str( )));'; put '%let keepvars=(keep=&keepvars &bus_from &bus_to &processed &md5_col);'; put '%end;'; put '/* CAS varchar types cause append issues here, so perform autoconvert'; put 'by creating empty local table first */'; put 'data;'; put 'set &base_lib..&base_dsn &drop_tx_dates_noobs;'; put 'run;'; put '%local emptybasetable; %let emptybasetable=&syslast;'; put 'data work.bitemp0_append &keepvars &outds_del(drop=&md5_col )'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5" %then %do;'; put '/nonote2err'; put '%end;'; put ';'; put '/* apply formats for bitemporal vars but not tx dates which are added later */'; put '%if %length(&keepvars)>0 and &loadtype=BITEMPORAL %then %do;'; put 'format &bus_from &bus_from_fmt;'; put 'format &bus_to &bus_to_fmt;'; put '%end;'; put 'set &emptybasetable /* base table reqd in case append has fewer cols */'; put 'work.append &drop_tx_dates;'; put '%if %length(%str(&bus_from_override))>0 %then %do;'; put '&bus_from= %unquote(&bus_from_override) ;'; put '%end;'; put '%if %length(%str(&bus_to_override))>0 %then %do;'; put '&bus_to= %unquote(&bus_to_override) ;'; put '%end;'; put 'length &md5_col $32;'; put '&md5_col=put(md5(&stripcols),hex32.);'; put '%if %length(&processed)>0 %then %do;'; put 'format &processed &processed_fmt;'; put '&processed=&now;'; put '%end;'; put '/**'; put '* If a delete column exists then create the delete dataset'; put '*/'; put '%if %mf_existvar(&append_lib..&append_dsn, &delete_col) %then %do;'; put 'drop &delete_col;'; put 'if upcase(&delete_col) = "YES" then output &outds_del ;'; put 'else output work.bitemp0_append ;'; put 'run;'; put '%if %mf_getattrn(&outds_del,NLOBS)>0 %then %do;'; put '%bitemporal_closeouts('; put 'tech_from=&tech_from'; put ',tech_to = &tech_to'; put ',base_lib=&base_lib'; put ',base_dsn=&base_dsn'; put ',append_lib=work'; put ',append_dsn=%scan(&outds_del,-1,.)'; put ',PK=&bus_from &pk'; put ',NOW=&dbnow'; put ',loadtarget=&loadtarget'; put ',loadtype=&loadtype'; put ')'; put '%end;'; put '%end;'; put '%else %do;'; put 'output work.bitemp0_append;'; put 'run;'; put '%end;'; put '%mp_abort(iftrue= (&syscc gt 0 at line 494)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc)'; put ')'; put '%if %length(&close_vars)>0 %then %do;'; put '/**'; put '* need to close out records that are not provided'; put '*/'; put 'proc sql;'; put 'create table bitemp1_closevars1 as'; put 'select distinct a.%mf_getquotedstr(in_str=&pk,dlm=%str(,a.),quote=)'; put 'from &base_lib..&base_dsn a'; put 'inner join work.bitemp0_append b'; put 'on 1=1'; put '/* join on closevars key */'; put '%do idx_pk=1 %to %sysfunc(countw(&close_vars));'; put '%let idx_val=%scan(&close_vars,&idx_pk);'; put 'and a.&idx_val=b.&idx_val'; put '%end;'; put '/* filter base on tech dates if necessary */'; put '%if &loadtype=TXTEMPORAL %then %do;'; put 'where a.&tech_from <=&now and &now < a.&tech_to'; put '%end;'; put ';'; put 'create table bitemp1_closevars2 as'; put 'select distinct a.*'; put 'from bitemp1_closevars1 a'; put 'left join work.bitemp0_append b'; put 'on 1=1'; put '/* join on primary key */'; put '%do idx_pk=1 %to %sysfunc(countw(&pk));'; put '%let idx_val=%scan(&pk,&idx_pk);'; put 'and a.&idx_val=b.&idx_val'; put '%end;'; put '/* identify removed records by null value in a field in PK but not close_vars'; put '*/'; put 'where b.%scan('; put '%mf_wordsInStr1ButNotStr2(Str1=&pk,Str2=&close_vars),1,%str( )'; put ') IS NULL'; put ';'; put '%if %mf_getattrn(bitemp1_closevars2,NLOBS)>0 %then %do;'; put '%bitemporal_closeouts('; put 'tech_from=&tech_from'; put ',tech_to = &tech_to'; put ',base_lib=&base_lib'; put ',base_dsn=&base_dsn'; put ',append_lib=work'; put ',append_dsn=bitemp1_closevars2'; put ',PK=&bus_from &pk'; put ',NOW=&dbnow'; put ',loadtarget=&loadtarget'; put ',loadtype=&loadtype'; put ')'; put '%end;'; put '%end;'; put '/* return if nothing to load (was just deletes) */'; put '%if %mf_getattrn(work.bitemp0_append,NLOBS)=0 %then %do;'; put '%put NOTE:; %put NOTE-;%put NOTE-;%put NOTE-;'; put '%put NOTE- No updates - just deletes!;'; put '%put NOTE-;%put NOTE-;%put NOTE-;'; put '%end;'; put '/**'; put '* If applying manual overrides to business dates, then the input table MUST'; put '* be unique on the PK. Check, and if not - abort.'; put '*/'; put '%local msg;'; put '%if %length(&bus_from_override.&bus_to_override)>0 or &CHECK_UNIQUENESS=YES'; put '%then %do;'; put 'proc sort data=work.bitemp0_append out=work.bitemp0_check nodupkey;'; put 'by &pk;'; put 'run;'; put '%if %mf_getattrn(work.bitemp0_check,NLOBS)'; put 'ne %mf_getattrn(work.bitemp0_append,NLOBS)'; put '%then %do;'; put '%let msg=INPUT table &append_lib..&append_dsn is not unique on PK (&pk);'; put '%mp_lockanytable(UNLOCK,lib=&base_lib,ds=&base_dsn,ref=&ETLSOURCE (&msg),'; put 'ctl_ds=&dclib..mpe_lockanytable'; put ')'; put '%mp_lockanytable(UNLOCK'; put ',lib=%scan(&outds_audit,1,.)'; put ',ds=%scan(&outds_audit,2,.)'; put ',ref=&ETLSOURCE'; put ',ctl_ds=&dclib..mpe_lockanytable'; put ')'; put '%mp_abort(msg=&msg,mac=bitemporal_dataloader.sas);'; put '%end;'; put '%end;'; put '/**'; put '* extract from BASE table. Only want matching records, as could be very BIG.'; put '* New records are subsequently identified via left join and test for nulls.'; put '*/'; put '%local temp_table temp_table2 base_table baselib_schema;'; put '%put DCNOTE: Extracting matching observations from &base_lib..&base_dsn;'; put '%if &engine_type=OLEDB %then %do;'; put '%let temp_table=##BITEMP_&base_dsn;'; put '%if &loadtype=BITEMPORAL or &loadtype=TXTEMPORAL %then'; put '%let base_table=(select * from [dbo].&base_dsn'; put 'where convert(datetime,&SQLNOW) < &tech_to );'; put '%else %let base_table=[dbo].&base_dsn;'; put 'proc sql;'; put 'create table &base_lib.."&temp_table"n as'; put 'select * from work.bitemp0_append;'; put '/* open up a connection for pass through SQL */'; put '%dc_assignlib(WRITE,&base_lib,passthru=myAlias)'; put 'create table work.bitemp0_base as select * from connection to myAlias('; put '%end;'; put '%else %if &engine_type=REDSHIFT or &engine_type=POSTGRES %then %do;'; put '/* grab schema */'; put '%let baselib_schema=%mf_getschema(&base_lib);'; put '%if &baselib_schema.X ne X %then %let baselib_schema=&baselib_schema..;'; put '/* grab redshift config */'; put '%local redcnt; %let redcnt=0;'; put '%if &engine_type=REDSHIFT %then %do;'; put 'data _null_;'; put 'set &config_table(where=(var_scope=''DCBL_REDSH'' and var_active=1));'; put 'x+1;'; put 'call symputx(cats(''rednm'',x),var_value,''l'');'; put 'call symputx(cats(''redval'',x),var_value,''l'');'; put 'call symputx(''redcnt'',x,''l'');'; put 'run;'; put '%end;'; put '/* cannot persist temp tables so must create a temporary permanent table */'; put '%let temp_table=%mf_getuniquename(prefix=XDCTEMP);'; put '%if &loadtype=BITEMPORAL or &loadtype=TXTEMPORAL %then'; put '%let base_table=(select * from &baselib_schema.&base_dsn'; put 'where timestamp &sqlnow < &tech_to );'; put '%else %let base_table=&baselib_schema.&base_dsn;'; put '/* make empty table first - must clone & drop extra cols as autoload is bad */'; put '%dc_assignlib(WRITE,&base_lib,passthru=myAlias)'; put 'exec (create table &temp_table (like &baselib_schema.&base_dsn)) by myAlias;'; put '%if &engine_type=REDSHIFT %then %do;'; put 'exec (alter table &temp_table alter sortkey none) by myAlias;'; put '%end;'; put '%local dropcols;'; put '%let dropcols=%mf_wordsinstr1butnotstr2('; put 'str1=%upcase(%mf_getvarlist(&basecopy))'; put ',str2=%upcase(&pk)'; put ');'; put '%if %length(&dropcols>0) %then %do idx_pk=1 %to %sysfunc(countw(&dropcols));'; put '%put &=dropcols;'; put '%let idx_val=%scan(&dropcols,&idx_pk);'; put 'exec(alter table &temp_table drop column &idx_val;) by myAlias;'; put '%end;'; put 'exec (alter table &temp_table add column &md5_col varchar(32);) by myAlias;'; put '/* create view to strip formats and avoid warns in log */'; put 'data work.vw_bitemp0/view=work.vw_bitemp0;'; put 'set work.bitemp0_append(keep=&pk &md5_col);'; put 'format _all_;'; put 'run;'; put 'proc append base=&base_lib..&temp_table'; put '%if &engine_type=REDSHIFT %then %do;'; put '('; put '%do idx_pk=1 %to &redcnt;'; put '&&rednm&idx_pk = &&redval&idxpk'; put '%end;'; put ')'; put '%end;'; put 'data=work.vw_bitemp0 force nowarn;'; put 'run;'; put '/* open up a connection for pass through SQL */'; put '%dc_assignlib(WRITE,&base_lib,passthru=myAlias)'; put 'create table work.bitemp0_base as select * from connection to myAlias('; put '%end;'; put '%else %if &engine_type=CAS %then %do;'; put '%if &loadtype=BITEMPORAL or &loadtype=TXTEMPORAL %then'; put '%let base_table=&base_lib..&base_dsn'; put '(where=(&tech_from <=&now and &now < &tech_to));'; put '%else %let base_table=&base_lib..&base_dsn;'; put '%let temp_table=CASUSER.%mf_getuniquename(prefix=DC);'; put 'data &temp_table;'; put 'set work.bitemp0_append;'; put 'run;'; put '%let bitemp0base=CASUSER.%mf_getuniquename(prefix=DC);'; put 'proc fedsql sessref=dcsession;'; put 'create table &bitemp0base{options replace=true} as'; put '%end;'; put '%else %do;'; put '%let temp_table=work.bitemp0_append;'; put '%if &loadtype=BITEMPORAL or &loadtype=TXTEMPORAL %then'; put '%let base_table=&base_lib..&base_dsn'; put '(where=(&tech_from <=&now and &now < &tech_to));'; put '%else %let base_table=&base_lib..&base_dsn;'; put 'proc sql;'; put 'create table work.bitemp0_base as'; put '%end;'; put 'select a.&md5_col /* this identifies NEW records */'; put ', b.*'; put '/* assume first PK field cannot be null (if defined in a PK constraint then'; put 'it definitely cannot be null) */'; put ', case when b.%scan(&pk,1) IS NULL then 1 else 0 end as ___TMP___NEW_FLG'; put 'from &baselib_schema.&temp_table a'; put 'left join &base_table b'; put 'on 1=1'; put '%do idx_pk=1 %to &pk_cnt;'; put '%let idx_val=%scan(&pk,&idx_pk);'; put 'and a.&idx_val=b.&idx_val'; put '%end;'; put '%if &engine_type=OLEDB or &engine_type=REDSHIFT or &engine_type=POSTGRES'; put '%then %do;'; put '); proc sql; drop table &base_lib.."&temp_table"n;'; put '%end;'; put '%else %if &engine_type=CAS %then %do;'; put ';'; put 'quit;'; put 'data work.bitemp0_base;'; put 'set &bitemp0base;'; put 'run;'; put 'proc sql;'; put 'drop table &temp_table;'; put 'drop table &bitemp0base;'; put '%end;'; put '%else %do;'; put ';'; put '%end;'; put '/**'; put '* matching & changed records are those without NULL key values'; put '* &idx_val resolves to rightmost PK value (loop above)'; put '*/'; put '%put syscc (line525)=&syscc, sqlrc=&sqlrc;'; put '%mp_abort(iftrue= (&syscc gt 0 or &sqlrc>0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc sqlrc=&sqlrc)'; put ')'; put '%put hashcols2=&stripcols;'; put 'proc sql;'; put 'create table work.bitemp1_current(drop=___TMP___NEW_FLG) as'; put 'select *'; put ', put(md5(&stripcols),$hex32.) as &md5_col'; put 'from work.bitemp0_base (drop=&md5_col)'; put 'where ___TMP___NEW_FLG=0;'; put '/**'; put '* NEW records were identified in ___TMP___NEW_FLG in bitemp0_base'; put '*/'; put 'proc sql;'; put 'create table &outds_add'; put '(drop=&md5_col'; put '%if %mf_existvar(work.bitemp0_base, &delete_col) %then %do;'; put '&delete_col'; put '%end;'; put ')'; put 'as select a.*'; put '%if &loadtype=BITEMPORAL or &loadtype=TXTEMPORAL %then %do;'; put ',&now as &tech_from &tech_from_fmt'; put ',&high_date as &tech_to &tech_to_fmt'; put '%end;'; put 'from work.bitemp0_append a /* STAGING records (mix of existing & new) */'; put ', work.bitemp0_base b /* BASE records (contains null values for new) */'; put 'where a.&md5_col=b.&md5_col /* took staging md5 across in left join */'; put 'and b.___TMP___NEW_FLG=1; /* NEW records also identified in bitemp0_base */'; put '/**'; put '* identify INSERTS. These are records with the same business key but'; put '* the bus_from and bus_to value are higher / lower (respectively)'; put '* such that the existing record needs to be SPLIT to surround the new'; put '* record.'; put '* eg: OLD RECORD from=1 to=10'; put '* NEW RECORD from=5 to=7'; put '*'; put '* APPENDED RECORDS:'; put '* - from=1 to=5'; put '* - from=5 to=7'; put '* - from=7 to=10'; put '*/'; put '/* inserts cannot happen with TXTEMPORAL */'; put '%if &loadtype=BITEMPORAL or &loadtype=BUSTEMPORAL %then %do;'; put '/* IDENTIFY */'; put 'create table work.bitemp3_inserts as'; put 'select b.*'; put ',a.&bus_from as ___TMP___from'; put ',a.&bus_to as ___TMP___to'; put 'from work.bitemp0_append a'; put ',work.bitemp1_current b'; put 'where a.&bus_from > b.&bus_from'; put 'and a.&bus_to < b.&bus_to'; put '%do idx_pk=1 %to &pk_cnt;'; put '%let idx_val=%scan(&pk,&idx_pk);'; put 'and a.&idx_val=b.&idx_val'; put '%end;'; put 'order by'; put '/* compress blanks and then insert commas (as the datetime fields may'; put 'not be in use) */'; put '%sysfunc(tranwrd(%sysfunc(compbl('; put '&pk &bus_from &bus_to &processed'; put ')),%str( ), %str(,)))'; put ';'; put '/* SPLIT */'; put 'data work.bitemp3a_inserts (drop=___TMP___from ___TMP___retain ___TMP___to) ;'; put 'set work.bitemp3_inserts;'; put 'by &pk &bus_from &bus_to &processed;'; put 'if first.&idx_val then do;'; put '___TMP___retain=&bus_to;'; put '&bus_to=___TMP___from;'; put 'output;'; put '&bus_to=___TMP___retain;'; put 'end;'; put 'if last.&idx_val then do;'; put '&bus_from=___TMP___to;'; put 'output;'; put 'end;'; put 'run;'; put '%end;'; put '%else %do;'; put '/* TX temporal load */'; put 'data work.bitemp3a_inserts;'; put 'set work.bitemp1_current;'; put 'stop;'; put 'run;'; put '%end;'; put '/* APPEND */'; put 'proc sql;'; put 'create view work.bitemp3a_view as'; put 'select * from work.bitemp1_current'; put 'where &md5_col not in (select &md5_col from work.bitemp3a_inserts);'; put 'data bitemp3b_newbase;'; put 'set work.bitemp3a_inserts work.bitemp3a_view;'; put 'run;'; put '/** do not use! this converts short numerics into 8 bytes'; put 'proc sql;'; put 'create table work.bitemp3b_newbase as'; put 'select * from work.bitemp3a_inserts'; put 'union corr'; put 'select * from work.bitemp1_current'; put 'where &md5_col not in (select &md5_col from work.bitemp3a_inserts);'; put '*/'; put '/**'; put '* identify CHANGED records from staging.'; put '* Same business key with different temporal dates or md5 value'; put '* This table must be overlayed onto / into existing business history'; put '*/'; put 'proc sql;'; put 'create table work.bitemp4_updated as select distinct a.*'; put 'from work.bitemp0_append a'; put ',work.bitemp3b_newbase b'; put 'where 1=1'; put '%do idx_pk=1 %to &pk_cnt;'; put '%let idx_val=%scan(&pk,&idx_pk);'; put 'and a.&idx_val=b.&idx_val'; put '%end;'; put 'and ( a.&md5_col ne b.&md5_col'; put '%if &loadtype=BITEMPORAL or &loadtype=BUSTEMPORAL %then %do;'; put 'OR (a.&bus_from ne b.&bus_from or a.&bus_to ne b.&bus_to)'; put '%end;'; put ')'; put ';'; put '/**'; put '* This section would have been one simple step with union all'; put '* but that converts short numerics into 8 bytes!'; put '* so, convoluted alternative to retain the same functionality.'; put '*/'; put '/* base records */'; put 'create view work.bitemp4_prep1 as'; put 'select ''BASE'' as ___TMP___'; put ',b.*'; put 'from work.bitemp4_updated a'; put ',work.bitemp3b_newbase b'; put 'where 1'; put '%do idx_pk=1 %to &pk_cnt;'; put '%let idx_val=%scan(&pk,&idx_pk);'; put 'and a.&idx_val=b.&idx_val'; put '%end;'; put ';'; put '/* updated records */'; put 'create view work.bitemp4_prep2 as'; put 'select ''STAG'' as ___TMP___ ,*'; put 'from work.bitemp4_updated;'; put '/* ensure we only keep columns that appear in both */'; put '%local bp1 bp2 bp3 bp4;'; put '%let bp1=%mf_getvarlist(bitemp4_prep1);'; put '%let bp2=%mf_getvarlist(bitemp4_prep2);'; put '%let bp3=%mf_wordsInStr1ButNotStr2(Str1=&bp1,Str2=&bp2);'; put '%let bp4=%mf_wordsInStr1ButNotStr2(Str1=&bp2,Str2=&bp1);'; put 'data work.bitemp4_prep3/view=bitemp4_prep3;'; put 'set bitemp4_prep1 bitemp4_prep2;'; put '%if %length(XX&bp3&bp4)>2 %then %do;'; put 'drop &bp3 &bp4 ;'; put '%end;'; put 'run;'; put '/* remove duplicates */'; put 'proc sql;'; put 'create table work.bitemp4a_allrecs as'; put 'select distinct *'; put 'from work.bitemp4_prep3'; put 'order by'; put '/* compress blanks and then insert commas (as the datetime fields'; put 'may not be in use) */'; put '%sysfunc(tranwrd(%sysfunc(compbl('; put '&pk &bus_from &bus_to &processed'; put ')),%str( ), %str(,)))'; put ';'; put '%if &loadtype=BITEMPORAL or &loadtype=BUSTEMPORAL %then %do;'; put '/* this section aligns the business dates'; put '(eg for inserts or overlaps in the range) */'; put 'data work.bitemp4b_firstpass (drop=___TMP___cond ___TMP___from ___TMP___to );'; put 'set work.bitemp4a_allrecs;'; put 'by &pk &bus_from &bus_to &processed;'; put 'retain ___TMP___cond ''Name of Condition'';'; put 'retain ___TMP___from ___TMP___to 0;'; put '___TMP___md5lag=lag(&md5_col);'; put '/* reset retained variables */'; put 'if first.&idx_val then do;'; put 'call missing (___TMP___cond, ___TMP___from, ___TMP___to,___TMP___md5lag);'; put 'end;'; put 'else do;'; put '/* if record is identical, carry forward bus_from (and bus_to if higher)*/'; put 'if &md5_col=___TMP___md5lag then do;'; put '&bus_from=___TMP___from;'; put 'if &bus_to<___TMP___to then &bus_to=___TMP___to;'; put 'end;'; put 'end;'; put 'if ___TMP___=''STAG'' then do;'; put '/* need to carry forward the closing record */'; put '___TMP___cond=''Condition 1'';'; put 'end;'; put 'else if ___TMP___cond=''Condition 1'' then do;'; put '/* else ensure bus_from starts from prior record bus_to */'; put 'if &md5_col ne ___TMP___md5lag and &bus_from <= ___TMP___to'; put 'then &bus_from= ___TMP___to;'; put '/* new record may replace old record entirely */'; put 'if &bus_to <= &bus_from then delete;'; put 'else call missing (___TMP___cond, ___TMP___from, ___TMP___to);'; put 'end;'; put '___TMP___from=&bus_from;'; put '___TMP___to=&bus_to;'; put 'run;'; put '%end;'; put '%else %do;'; put '/* keep staged records only */'; put 'data work.bitemp4b_firstpass;'; put 'set work.bitemp4a_allrecs;'; put 'if ___TMP___=''STAG'';'; put 'run;'; put '%end;'; put '/* next phase is to pass through in reverse - so set up the sort statement */'; put '%local byvar;'; put '%do idx_pk=1 %to &pk_cnt;'; put '%let byvar=&byvar descending %scan(&pk,&idx_pk);'; put '%end;'; put '%if &loadtype=BITEMPORAL or &loadtype=BUSTEMPORAL'; put '%then %let byvar=&byvar descending &bus_from descending &bus_to;'; put '/* if matching bus dates supplied, need to ensure we also have a sort'; put 'between BASE and STAGING tables */'; put '%let byvar=&byvar descending ___TMP___;'; put 'proc sort data=work.bitemp4b_firstpass out=work.bitemp4c_sort ;'; put 'by &byvar;'; put 'run;'; put '/**'; put '* Now (in reverse) pass back business start dates'; put '*/'; put 'data work.bitemp4d_secondpass;'; put '%if &loadtype=BITEMPORAL or &loadtype=TXTEMPORAL %then %do;'; put '&tech_from=&now;'; put '&tech_to=&high_date;'; put '%end;'; put 'set work.bitemp4c_sort ;'; put 'by &byvar;'; put 'retain ___TMP___cond ''Name of Condition'';'; put 'retain ___TMP___from ___TMP___to 0;'; put '%if &loadtype=BITEMPORAL or &loadtype=BUSTEMPORAL %then %do;'; put '/* put / _all_ /;*/'; put '___TMP___md5lag=lag(&md5_col);'; put 'if first.&idx_val then do;'; put '/* reset retained variables */'; put 'call missing (___TMP___cond,___TMP___from,___TMP___to,___TMP___md5lag);'; put 'end;'; put 'else do;'; put '/* if record is identical, carry back bus_to */'; put 'if &md5_col=___TMP___md5lag then &bus_to=___TMP___to;'; put 'end;'; put 'if ___TMP___=''STAG'' then do;'; put '/* need to carry forward the closing record */'; put '___TMP___cond=''Condition 2'';'; put 'end;'; put 'else if ___TMP___cond=''Condition 2'' then do;'; put '/* else ensure bus_to stops at subsequent record bus_from */'; put 'if &md5_col ne ___TMP___md5lag and &bus_to >= ___TMP___from'; put 'then &bus_to= ___TMP___from;'; put '/* new record may replace old record entirely */'; put 'if &bus_from >= &bus_to then delete;'; put 'if &bus_from=___TMP___from and &bus_to=___TMP___to then delete;'; put 'else call missing (___TMP___cond, ___TMP___from, ___TMP___to);'; put 'end;'; put '___TMP___from=&bus_from;'; put '___TMP___to=&bus_to;'; put '%end;'; put 'run;'; put '%put syscc (line600)=&syscc;'; put '/**'; put 'There may still be some records (eg old business history) which have not'; put 'changed.'; put 'Need to identify these and remove from the append so they are not updated'; put 'unnecessarily. This is done by generating a new md5 (which INCLUDES the'; put 'business key) and any matching / identical records are split out (from those'; put 'that need to be updated).'; put '*/'; put '%if &loadtype=BITEMPORAL %then %do;'; put '%let cat_string=catx(''|'' ,&bus_from,&bus_to);'; put 'data bitemp5a_lkp (keep=&md5_col);'; put 'set bitemp0_base;'; put '/* for BITEMPORAL we need to compare business dates also */'; put '&md5_col=put(md5(&cat_string!!''|''!!&stripcols),$hex32.);'; put 'run;'; put 'data bitemp5b_updates;'; put 'set bitemp4d_secondpass;'; put 'if _n_=1 then do;'; put 'dcl hash md5_lkp(dataset:''bitemp5a_lkp'');'; put 'md5_lkp.definekey("&md5_col");'; put 'md5_lkp.definedone();'; put 'end;'; put '/* drop old md5 col as will rebuild with new business dates */'; put '&md5_col=put(md5(&cat_string!!''|''!!&stripcols),$hex32.) ;'; put 'if md5_lkp.check()=0 then delete;'; put 'run;'; put 'proc sql;'; put '/* get min bus from as will update (close out) all records from this point'; put '(for that PK)*/'; put 'create table work.bitemp5d_subquery as'; put 'select &pk_comma, min(&bus_from)as &bus_from, max(&bus_to) as &bus_to'; put 'from work.bitemp5b_updates'; put 'group by &pk_comma;'; put '/* index has a huge efficiency impact on upcoming nested subquery */'; put 'create index index1 on work.bitemp5d_subquery(&pk_comma,&bus_from, &bus_to);'; put '%let lastds=work.bitemp5b_updates;'; put '%end;'; put '%else %if &loadtype=TXTEMPORAL or &loadtype=UPDATE %then %do;'; put 'proc sql;'; put 'create table work.bitemp5d_subquery as'; put 'select distinct &pk_comma'; put 'from bitemp4d_secondpass;'; put '%let lastds=work.bitemp4d_secondpass;'; put '%end;'; put '%else %let lastds=work.bitemp4d_secondpass;'; put '/* create single append table (an overlapped pre-sert may be classed as'; put 'both an update AND a new record). Also create temp views that may be'; put 'used for pre-load analysis. */'; put 'data &outds_mod;'; put 'set &lastds(drop=___TMP___: &md5_col);'; put 'run;'; put 'data bitemp6_allrecs / view=bitemp6_allrecs;'; put 'set &outds_mod /* UPDATED records */'; put '&outds_add /* NEW records */;'; put 'run;'; put 'proc sort data=work.bitemp6_allrecs'; put 'out=work.bitemp6_unique'; put 'noduprec'; put 'dupout=work.xx_BADBADBAD;'; put 'by _all_;'; put 'run;'; put '/* we have all our temp tables now so exit if this is all that is needed */'; put '%if &LOADTARGET ne YES %then %return;'; put '/* also exit if an err condition exists */'; put '%if &syscc>0 %then %do;'; put '%put syscc=&syscc;'; put '%mp_lockanytable(UNLOCK,lib=&base_lib,ds=&base_dsn,ref=&ETLSOURCE,'; put 'ctl_ds=&dclib..mpe_lockanytable'; put ')'; put '%if "&outds_audit" ne "0" %then %do;'; put '%mp_lockanytable(UNLOCK'; put ',lib=%scan(&outds_audit,1,.)'; put ',ds=%scan(&outds_audit,2,.)'; put ',ref=&ETLSOURCE'; put ',ctl_ds=&dclib..mpe_lockanytable'; put ')'; put '%end;'; put '%end;'; put '%mp_abort(iftrue= (&syscc>0)'; put ',mac=&sysmacroname in &_program'; put ',msg=%str(Bitemporal transform / job aborted due to SYSCC=&SYSCC status)'; put ')'; put '/* final check - abort if a lock has appeared on the target or audit table */'; put '%mp_lockfilecheck(libds=&base_lib..&base_dsn)'; put '%if %mf_existds(&outds_audit) %then %do;'; put '%mp_lockfilecheck(libds=&outds_audit)'; put '%end;'; put '/**'; put '* STAGING TABLES PREPARED, ERR CONDITION TESTED FOR.. NOW TO LOAD!!'; put '*/'; put '/**'; put '* First, CLOSE OUT changed records (if not a REPLACE)'; put '* Note that SAS does not support ANSI standard for UPDATE with a join condition.'; put '* However - this can be worked around using a nested subquery..'; put '*/'; put 'data _null_;'; put 'putlog "&sysmacroname: CLOSEOUTS commencing";'; put 'run;'; put '%if %mf_getattrn(&lastds,NLOBS)=0 %then %do;'; put 'data _null_;'; put 'putlog "&sysmacroname: No closeouts needed";'; put 'run;'; put '%end;'; put '%else %if &engine_type=CAS %then %do;'; put '%mp_abort(iftrue= (&loadtype=BITEMPORAL or &loadtype=TXTEMPORAL)'; put ',mac=&sysmacroname in &_program'; put ',msg=%str(&loadtype not yet supported in CAS engine)'; put ')'; put '/* create temp table for deletions */'; put '%local delds;%let delds=%mf_getuniquename(prefix=DC);'; put 'data casuser.&delds;'; put 'set work.bitemp5d_subquery;'; put 'run;'; put '/* delete the records */'; put 'proc cas ;'; put 'table.deleteRows / table={'; put 'caslib="&base_lib",'; put 'name="&base_dsn",'; put 'where="1=1",'; put 'whereTable={caslib=''CASUSER'',name="&delds"}'; put '};'; put 'quit;'; put '/* drop temp table */'; put 'proc sql;'; put 'drop table CASUSER.&delds;'; put '%end;'; put '%else %if (&loadtype=BITEMPORAL or &loadtype=TXTEMPORAL or &loadtype=UPDATE)'; put '%then %do;'; put 'data _null_;'; put 'putlog "&sysmacroname: &loadtype operation using &engine_type engine";'; put 'run;'; put '%local flexinow;'; put 'proc sql;'; put '/* if OLEDB then create a temp table for efficiency */'; put '%local innertable;'; put '%if &engine_type=OLEDB %then %do;'; put '%let innertable=[##BITEMP_&base_dsn];'; put '%let top_table=[dbo].&base_dsn;'; put '%let flexinow=&SQLNOW;'; put 'create table &base_lib.."##BITEMP_&base_dsn"n as'; put 'select * from work.bitemp5d_subquery;'; put '/* open up a connection for pass through SQL */'; put '%dc_assignlib(WRITE,&base_lib,passthru=myAlias)'; put 'execute('; put '%end;'; put '%else %if &engine_type=REDSHIFT or &engine_type=POSTGRES %then %do;'; put '%let innertable=%mf_getuniquename(prefix=XDCTEMP);'; put '%let top_table=&baselib_schema.&base_dsn;'; put '%let flexinow=timestamp &SQLNOW;'; put '/* make empty table first - must clone & drop extra cols'; put 'as autoload is bad */'; put '%dc_assignlib(WRITE,&base_lib,passthru=myAlias)'; put 'exec (create table &innertable (like &baselib_schema.&base_dsn)) by myAlias;'; put '%if &engine_type=REDSHIFT %then %do;'; put 'exec (alter table &innertable alter sortkey none) by myAlias;'; put '%end;'; put '%let dropcols=%mf_wordsinstr1butnotstr2('; put 'str1=%upcase(%mf_getvarlist(&basecopy))'; put ',str2=%upcase(%mf_getvarlist(work.bitemp5d_subquery))'; put ');'; put '%if %length(&dropcols>0) %then %do idx_pk=1 %to %sysfunc(countw(&dropcols));'; put '%put &=dropcols;'; put '%let idx_val=%scan(&dropcols,&idx_pk);'; put 'exec(alter table &innertable drop column &idx_val;) by myAlias;;'; put '%end;'; put '/* create view to strip formats and avoid warns in log */'; put 'data work.vw_bitemp5d/view=work.vw_bitemp5d;'; put 'set work.bitemp5d_subquery;'; put 'format _all_;'; put 'run;'; put 'proc append base=&base_lib..&innertable ('; put '%do idx_pk=1 %to &redcnt;'; put '&&rednm&idx_pk = &&redval&idxpk'; put '%end;'; put ')'; put 'data=work.vw_bitemp5d force nowarn;'; put 'run;'; put '/* open up a connection for pass through SQL */'; put '%dc_assignlib(WRITE,&base_lib,passthru=myAlias)'; put 'execute('; put '%end;'; put '%else %do;'; put '%let innertable=bitemp5d_subquery;'; put '%let top_table=&base_lib..&base_dsn;'; put '%let flexinow=&now;'; put '%end;'; put '%if &loadtype=BITEMPORAL or &loadtype=TXTEMPORAL %then %do;'; put 'update &top_table set &tech_to=&flexinow'; put '%if %length(&processed)>0 %then %do;'; put ',&processed=&flexinow'; put '%end;'; put 'where &tech_from <= &flexinow and &flexinow < &tech_to and'; put '%end;'; put '%else %if &loadtype=UPDATE %then %do;'; put '/* changed records are deleted then re-appended when doing UPDATEs */'; put 'delete from &top_table where'; put '%end;'; put '%else %do;'; put '%put %str(ERR)OR: BUSTEMPORAL NOT YET SUPPORTED;'; put '%let syscc=5;'; put '%mp_lockanytable(UNLOCK,lib=&base_lib,ds=&base_dsn,ref=&ETLSOURCE,'; put 'ctl_ds=&dclib..mpe_lockanytable'; put ')'; put '%mp_lockanytable(UNLOCK'; put ',lib=%scan(&outds_audit,1,.)'; put ',ds=%scan(&outds_audit,2,.)'; put ',ref=&ETLSOURCE'; put ',ctl_ds=&dclib..mpe_lockanytable'; put ')'; put '%goto end_of_macro;'; put '%end;'; put '/* perform join inside query as per'; put 'http://stackoverflow.com/questions/24629793/update-with-a-proc-sql */'; put 'exists( select 1 from &baselib_schema.&innertable where'; put '/* loop PK join */'; put '%do idx_pk=1 %to &pk_cnt;'; put '%let idx_val=%scan(&pk,&idx_pk);'; put '&base_dsn..&idx_val=&innertable..&idx_val and'; put '%end;'; put '%if &loadtype=BITEMPORAL %then %do;'; put '&base_dsn..&bus_from >= &innertable..&bus_from'; put 'and &base_dsn..&bus_to <= &innertable..&bus_to and'; put '%end;'; put '/* close the statement */'; put '1=1);'; put '%if &engine_type=OLEDB or &engine_type=REDSHIFT or &engine_type=POSTGRES'; put '%then %do;'; put ') by myAlias;'; put 'execute (drop table &baselib_schema.&innertable) by myAlias;'; put '%end;'; put '%end;'; put 'quit;'; put 'data _null_;'; put 'putlog "&sysmacroname: Closeout complete";'; put 'run;'; put '/**'; put '* Append the new / updated records'; put '*/'; put '%if &engine_type=CAS %then %do;'; put '/* get varchar variables ready for casting */'; put '%local vcfmt vcrename vcassign vcdrop;'; put 'data _null_;'; put 'set work.bitemp_cols(where=(type=6)) end=last;'; put 'length vcrename vcassign vcdrop vcfmt $32767 rancol $32;'; put 'retain vcrename vcassign vcdrop vcfmt;'; put 'if _n_=1 then vcrename=''(rename=('';'; put 'rancol=resolve(''%mf_getuniquename()'');'; put 'vcfmt=trim(vcfmt)!!''length ''!!cats(name)!!'' varchar(*);'';'; put 'vcrename=trim(vcrename)!!'' ''!!cats(name,''='',rancol);'; put 'vcassign=cats(vcassign,name,''='',rancol,'';'');'; put 'vcdrop=cats(vcdrop,''drop ''!!rancol,'';'');'; put 'if last then do;'; put 'vcrename=cats(vcrename,''))'');'; put 'call symputx(''vcfmt'',vcfmt);'; put 'call symputx(''vcrename'',vcrename);'; put 'call symputx(''vcassign'',vcassign);'; put 'call symputx(''vcdrop'',vcdrop);'; put 'end;'; put 'run;'; put '/* prepare a temp cas table with varchars casted */'; put '%let tmp=%mf_getuniquename();'; put 'data casuser.&tmp ;'; put '&vcfmt'; put 'set work.bitemp6_unique &vcrename;'; put '&vcassign'; put '&vcdrop'; put 'run;'; put '/* load the table with varchars applied*/'; put 'data &base_lib..&base_dsn (append=yes )/sessref=dcsession ;'; put 'set casuser.&tmp;'; put 'run;'; put '/* drop temp table */'; put 'proc sql;'; put 'drop table CASUSER.&tmp;'; put '/* this code will not work as regular tables do not have varchars */'; put '/*'; put 'proc casutil;'; put 'load data=work.bitemp6_unique'; put 'outcaslib="&base_lib" casout="&base_dsn" append ;'; put 'quit;'; put '*/'; put '%end;'; put '%else %if &engine_type=REDSHIFT or &engine_type=POSTGRES %then %do;'; put 'proc append base=&base_lib..&base_dsn'; put '%if &engine_type=REDSHIFT %then %do;'; put '('; put '%do idx_pk=1 %to &redcnt;'; put '&&rednm&idx_pk = &&redval&idxpk'; put '%end;'; put ')'; put '%end;'; put 'data=bitemp6_unique force nowarn;'; put 'run;'; put '%end;'; put '%else %do;'; put 'proc append base=&base_lib..&base_dsn data=bitemp6_unique force nowarn; run;'; put '%end;'; put '%mp_lockanytable(UNLOCK,lib=&base_lib,ds=&base_dsn,ref=&ETLSOURCE,'; put 'ctl_ds=&dclib..mpe_lockanytable'; put ')'; put '/* final check on syscc */'; put '%mp_abort(iftrue= (&syscc >4)'; put ',mac=&_program'; put ',msg=%str(!!Upload NOT successful!! Failed on actual update / append stage..)'; put ')'; put '%if &outds_audit ne 0 and &LOADTARGET=YES %then %do;'; put 'data work.vw_outds_orig /view=work.vw_outds_orig;'; put 'set work.bitemp0_base (drop=&md5_col);'; put 'where ___TMP___NEW_FLG=0;'; put 'drop ___TMP___NEW_FLG;'; put 'run;'; put '/* update the AUDIT table */'; put '%if %mf_existds(&outds_audit) %then %do;'; put 'options mprint;'; put '%mp_storediffs(&base_lib..&base_dsn'; put ',work.vw_outds_orig'; put ',&pk &bus_from'; put ',delds=&outds_del'; put ',modds=&outds_mod'; put ',appds=&outds_add'; put ',outds=work.mp_storediffs'; put ',processed_dttm=&now'; put ',loadref=%superq(etlsource)'; put ')'; put '/* exclude unchanged values in modified rows */'; put 'data work.mp_storediffs;'; put 'set work.mp_storediffs;'; put 'if MOVE_TYPE="M" and IS_PK=0 and IS_DIFF=0 then delete;'; put '* putlog load_ref= libref= dsn= key_hash= tgtvar_nm=;'; put 'run;'; put 'proc append base=&outds_audit data=work.mp_storediffs;'; put 'run;'; put '%mp_lockanytable(UNLOCK'; put ',lib=%scan(&outds_audit,1,.)'; put ',ds=%scan(&outds_audit,2,.)'; put ',ref=&ETLSOURCE'; put ',ctl_ds=&dclib..mpe_lockanytable'; put ')'; put '%end;'; put '%end;'; put '%mp_abort(iftrue= (&syscc >4)'; put ',mac=bitemporal_dataloader'; put ',msg=%str(Problem in audit stage (&outds_audit))'; put ')'; put '%let user=%mf_getUser();'; put '/**'; put 'Notify as appropriate EMAILS DISABLED'; put '%sumo_alerts(ALERT_EVENT=UPDATE'; put ', ALERT_TARGET=&base_lib..&base_dsn'; put ', from_user= &user);'; put '*/'; put '/* monitor BiTemporal usage */'; put '%if &log=1 %then %do;'; put '%put syscc=&syscc;'; put '/* do not perform duration calc in pass through */'; put '%local dur;'; put 'data _null_;'; put 'now=symget(''now'');'; put 'dur=%sysfunc(datetime())-&now;'; put 'call symputx(''dur'',dur,''l'');'; put 'run;'; put 'proc sql;'; put 'insert into &dclib..mpe_dataloads'; put 'set libref=%upcase("&base_lib")'; put ',DSN=%upcase("&base_dsn")'; put ',ETLSOURCE="&ETLSOURCE"'; put ',LOADTYPE="&loadtype"'; put ',CHANGED_RECORDS=%mf_getattrn(&lastds,NLOBS)'; put ',NEW_RECORDS=%mf_getattrn(&outds_add,NLOBS)'; put ',DELETED_RECORDS=%mf_getattrn(&outds_del,NLOBS)'; put ',DURATION=&dur'; put ',MAC_VER="v&ver"'; put ',user_nm="&user"'; put ',PROCESSED_DTTM=&now;'; put 'quit;'; put '%put syscc=&syscc;'; put '%end;'; put '%end_of_macro:'; put '%mend bitemporal_dataloader;'; put '%macro mm_getlibs('; put 'outds=work.mm_getLibs'; put ')/*/STORE SOURCE*/;'; put '/*'; put 'flags:'; put 'OMI_SUCCINCT (2048) Do not return attributes with null values.'; put 'OMI_GET_METADATA (256) Executes a GetMetadata call for each object that'; put 'is returned by the GetMetadataObjects method.'; put 'OMI_ALL_SIMPLE (8) Gets all of the attributes of the requested object.'; put '*/'; put 'data _null_;'; put 'flags=2048+256+8;'; put 'call symputx(''flags'',flags,''l'');'; put 'run;'; put '* use a temporary fileref to hold the response;'; put 'filename response temp;'; put '/* get list of libraries */'; put 'proc metadata in='; put ''''; put '$METAREPOSITORY'; put 'SASLibrary'; put ''; put 'SAS'; put '&flags'; put ''; put ''''; put 'out=response;'; put 'run;'; put '/* write the response to the log for debugging */'; put 'data _null_;'; put 'infile response lrecl=32767;'; put 'input;'; put 'put _infile_;'; put 'run;'; put '/* create an XML map to read the response */'; put 'filename sxlemap temp;'; put 'data _null_;'; put 'file sxlemap;'; put 'put '''';'; put 'put '''';'; put 'put ''//Objects/SASLibrary'';'; put 'put ''>17'';'; put 'put ''//Objects/SASLibrary/@Id'';'; put 'put ''256>'';'; put 'put ''//Objects/SASLibrary/@Name'';'; put 'put ''8'';'; put 'put ''//Objects/SASLibrary/@Libref'';'; put 'put ''>12'';'; put 'put ''//Objects/SASLibrary/@Engine'';'; put 'put ''
'';'; put 'run;'; put 'libname _XML_ xml xmlfileref=response xmlmap=sxlemap;'; put '/* sort the response by library name */'; put 'proc sort data=_XML_.saslibrary out=&outds;'; put 'by libraryname;'; put 'run;'; put '/* clear references */'; put 'filename sxlemap clear;'; put 'filename response clear;'; put 'libname _XML_ clear;'; put '%mend mm_getlibs;'; put '%macro dc_getlibs(outds=mm_getlibs);'; put '/* take current repository */'; put '%local repo repocnt x;'; put '%let repo=%sysfunc(getoption(metarepository));'; put '/* get list of repositories and filter */'; put '%mm_getrepos(outds=repos)'; put '%let repocnt=0;'; put 'data repos;'; put 'set repos;'; put 'where repositorytype in(''CUSTOM'',''FOUNDATION'')'; put '& upcase(name) ne "%upcase(&repo)";'; put 'keep id name ;'; put 'call symputx(''repo''!!cats(_n_),name,''l'');'; put 'call symputx(''repocnt'',_n_,''l'');'; put 'run;'; put '%put _local_;'; put '%mm_getlibs(outds=&outds)'; put '%do x=1 %to &repocnt;'; put 'options metarepository=&&repo&x;'; put '%mm_getlibs(outds=&outds.a)'; put 'proc append base=&outds data=&outds.a;'; put 'run;'; put '%end;'; put 'options metarepository=&repo;'; put '%mend dc_getlibs;'; put '%macro mpe_refreshlibs(lib=0);'; put '%dc_getlibs(outds=work.mm_getLibs)'; put 'proc sort data=mm_getlibs;'; put 'by libraryref libraryname;'; put 'run;'; put 'data libs0;'; put 'set mm_getlibs;'; put 'by libraryref;'; put '%if &lib ne 0 %then %do;'; put 'where upcase(libraryref)="%upcase(&lib)";'; put '%end;'; put 'if "%mf_getplatform()"="SASMETA" then do;'; put '/* note - invalid libraries can result in exception errors. If this happens,'; put 'configure the dc_viewlib_check variable to NO in Data Controller Settings */'; put 'rc=libname(libraryref,,''meta'',cats(''library="'',libraryname,''";''));'; put 'drop rc;'; put 'if rc ne 0 then do;'; put 'putlog "NOTE: Library " libraryname " does not exist!!";'; put 'putlog (_all_) (=);'; put 'delete;'; put 'end;'; put 'end;'; put 'if not first.libraryref then delete;'; put 'run;'; put 'proc sql;'; put 'create table libs1 as'; put 'select distinct libname'; put ',engine'; put ',path'; put ',level'; put ',sysname'; put ',sysvalue'; put 'from dictionary.libnames'; put 'order by libname, level,engine,path;'; put 'data libs2;'; put 'set libs1;'; put 'length tran $1024;'; put 'if missing(sysname) then sysname=''Missing'';'; put 'select(sysname);'; put 'when(''Access Permission'') tran=''Permissions'';'; put 'when(''Owner Name'') tran=''Owner'';'; put 'when(''Schema/Owner'') tran=''schema'';'; put 'otherwise tran=sysname;'; put 'end;'; put 'run;'; put 'proc transpose data=libs2 out=libs3;'; put 'by libname level engine path;'; put 'var sysvalue;'; put 'id tran;'; put 'run;'; put 'data libs4(rename=(libname=libref));'; put 'length paths $8192 perms owners schemas $500 permissions owner schema $1024;'; put 'if _n_=1 then call missing (of _all_);'; put 'set libs3;'; put 'by libname;'; put 'if engine=''V9'' then engine=''BASE'';'; put 'if first.libname then do;'; put 'retain paths perms owners schemas;'; put 'paths=''(''!!quote(trim(path));'; put 'perms=permissions;'; put 'owners=owner;'; put 'schemas=schema;'; put 'end;'; put 'else do;'; put 'paths=trim(paths)!!'' ''!!quote(trim(path));'; put 'perms=trim(perms)!!'',''!!trim(permissions);'; put 'owners=trim(owners)!!'',''!!trim(owner);'; put 'schemas=trim(schemas)!!'' ''!!trim(schema);'; put 'end;'; put 'if last.libname then do;'; put 'paths=trim(paths)!!'')'';'; put 'schemas=cats(schemas);'; put 'output;'; put 'end;'; put 'keep libname engine paths perms owners schemas;'; put 'run;'; put 'proc sql;'; put 'create table libs5 as'; put 'select a.libref'; put ',coalescec(b.engine,a.engine) as engine length=32'; put ',b.libraryname as libname'; put ',a.paths'; put ',a.perms'; put ',a.owners'; put ',a.schemas'; put ',b.libraryid as libid'; put 'from libs4 a'; put 'left join libs0 b'; put 'on upcase(a.libref)=upcase(b.libraryref)'; put 'where libref not in (''SASWORK'',''WORK'',''SASUSER'',''CASUSER'',''TEMP'',''STPSAMP'''; put ',''MAPSGFK'');'; put '%bitemporal_dataloader(base_lib=&dc_libref'; put ',base_dsn=MPE_DATACATALOG_LIBS'; put ',append_dsn=libs5'; put ',PK=LIBREF'; put ',etlsource=&_program'; put ',loadtype=TXTEMPORAL'; put ',tech_from=TX_FROM'; put ',tech_to=TX_TO'; put ',dclib=&dc_libref'; put ')'; put '%mend mpe_refreshlibs;'; put '/** @cond */'; put '%macro mf_existfeature(feature'; put ')/*/STORE SOURCE*/;'; put '%let feature=%upcase(&feature);'; put '%local platform;'; put '%let platform=%mf_getplatform();'; put '%if &feature= %then %do;'; put '%put No feature was requested for detection;'; put '%end;'; put '%else %if &feature=COLCONSTRAINTS %then %do;'; put '%if "%substr(&sysver,1,1)"="4" or "%substr(&sysver,1,1)"="5" %then 0;'; put '%else 1;'; put '%end;'; put '%else %if &feature=PROCLUA %then %do;'; put '/* https://blogs.sas.com/content/sasdummy/2015/08/03/using-lua-within-your-sas-programs */'; put '%if &platform=SASVIYA %then 1;'; put '%else %if "&sysver"="9.2" or "&sysver"="9.3" %then 0;'; put '%else %if "&SYSVLONG" < "9.04.01M3" %then 0;'; put '%else 1;'; put '%end;'; put '%else %if &feature=DBMS_MEMTYPE %then %do;'; put '/* does dbms_memtype exist in dictionary.tables? */'; put '%if "%substr(&sysver,1,1)"="4" or "%substr(&sysver,1,1)"="5" %then 0;'; put '%else 1;'; put '%end;'; put '%else %if &feature=EXPORTXLS %then %do;'; put '/* is it possible to PROC EXPORT an excel file? */'; put '%if "%substr(&sysver,1,1)"="4" or "%substr(&sysver,1,1)"="5" %then 1;'; put '%else %if %sysfunc(sysprod(SAS/ACCESS Interface to PC Files)) = 1 %then 1;'; put '%else 0;'; put '%end;'; put '%else %do;'; put '-1'; put '%put &sysmacroname: &feature not found;'; put '%end;'; put '%mend mf_existfeature;'; put '/** @endcond */'; put '%macro mp_getconstraints(lib=WORK'; put ',ds='; put ',outds=mp_getconstraints'; put ',mdebug=0'; put ')/*/STORE SOURCE*/;'; put '%let lib=%upcase(&lib);'; put '%let ds=%upcase(&ds);'; put '/**'; put '* Cater for environments where sashelp.vcncolu is not available'; put '*/'; put '%if %sysfunc(exist(sashelp.vcncolu,view))=0 %then %do;'; put 'proc sql;'; put 'create table &outds('; put 'libref char(8)'; put ',TABLE_NAME char(32)'; put ',constraint_type char(8) label=''Constraint Type'''; put ',constraint_name char(32) label=''Constraint Name'''; put ',column_name char(32) label=''Column'''; put ',constraint_order num'; put ');'; put '%return;'; put '%end;'; put '/**'; put '* Neither dictionary tables nor sashelp provides a constraint order column,'; put '* however they DO arrive in the correct order. So, create the col.'; put '**/'; put '%local vw;'; put '%let vw=%mf_getuniquename(prefix=mp_getconstraints_vw_);'; put 'data &vw /view=&vw;'; put 'set sashelp.vcncolu;'; put 'where table_catalog="&lib";'; put '/* use retain approach to reset the constraint order with each constraint */'; put 'length tmp $1000;'; put 'retain tmp;'; put 'drop tmp;'; put 'if tmp ne catx(''|'',table_catalog,table_name,constraint_name) then do;'; put 'constraint_order=1;'; put 'end;'; put 'else constraint_order+1;'; put 'tmp=catx(''|'',table_catalog, table_name,constraint_name);'; put 'run;'; put '/* must use SQL as proc datasets does not support length changes */'; put 'proc sql noprint;'; put 'create table &outds as'; put 'select upcase(a.TABLE_CATALOG) as libref'; put ',upcase(a.TABLE_NAME) as TABLE_NAME'; put ',a.constraint_type'; put ',a.constraint_name'; put ',b.column_name'; put ',b.constraint_order'; put 'from dictionary.TABLE_CONSTRAINTS a'; put 'left join &vw b'; put 'on upcase(a.TABLE_CATALOG)=upcase(b.TABLE_CATALOG)'; put 'and upcase(a.TABLE_NAME)=upcase(b.TABLE_NAME)'; put 'and a.constraint_name=b.constraint_name'; put '/**'; put '* We cannot apply this clause to the underlying dictionary table. See:'; put '* https://communities.sas.com/t5/SAS-Programming/Unexpected-Where-Clause-behaviour-in-dictionary-TABLE/m-p/771554#M244867'; put '* cannot use`where calculated libref="&lib"` either as it will STILL execute'; put '* all the underlying constraint queries, causing exception errors in some'; put '* cases: https://github.com/sasjs/core/issues/283'; put '*/'; put 'where a.TABLE_CATALOG="&lib"'; put '%if "&ds" ne "" %then %do;'; put 'and upcase(a.TABLE_NAME)="&ds"'; put 'and upcase(b.TABLE_NAME)="&ds"'; put '%end;'; put 'order by libref, table_name, constraint_name, constraint_order'; put ';'; put '/* tidy up */'; put '%mp_dropmembers('; put '&vw,'; put 'iftrue=(&mdebug=0)'; put ')'; put '%mend mp_getconstraints;'; put '%macro mpe_refreshtables(lib,ds=#all);'; put '%let lib=%upcase(&lib);'; put '%let ds=%upcase(&ds);'; put '%local engine; %let engine=%mf_getengine(&lib);'; put '%local schema; %let schema=%mf_getschema(&lib);'; put '%put running &sysmacroname &lib(&engine &schema) for &ds;'; put 'proc sql;'; put 'create table cols as'; put 'select libname as libref'; put ',upcase(memname) as dsn'; put ',memtype'; put ',upcase(name) as name'; put ',type'; put ',length'; put ',varnum'; put ',label'; put ',format'; put ',idxusage'; put ',notnull'; put 'from dictionary.columns'; put 'where upcase(libname)="&lib"'; put '%if &ds ne #ALL %then %do;'; put 'and upcase(memname)="&ds"'; put '%end;'; put ';'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc afer &lib cols extraction)'; put ')'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc afer &lib indexes extraction)'; put ')'; put '%if &engine=SQLSVR %then %do;'; put 'proc sql;'; put 'connect using &lib;'; put 'create table work.indexes as'; put 'select * from connection to &lib('; put 'select'; put 's.name as SchemaName,'; put 't.name as memname,'; put 'tc.name as name,'; put 'ic.key_ordinal as KeyOrderNr'; put 'from'; put 'sys.schemas s'; put 'inner join sys.tables t on s.schema_id=t.schema_id'; put 'inner join sys.indexes i on t.object_id=i.object_id'; put 'inner join sys.index_columns ic on i.object_id=ic.object_id'; put 'and i.index_id=ic.index_id'; put 'inner join sys.columns tc on ic.object_id=tc.object_id'; put 'and ic.column_id=tc.column_id'; put 'where i.is_primary_key=1'; put 'and s.name=%str(%'')&schema%str(%'')'; put 'order by t.name, ic.key_ordinal ;'; put ');disconnect from &lib;'; put 'create table finalcols as'; put 'select a.*'; put ',case when b.name is not null then 1 else 0 end as pk_ind'; put 'from work.cols a'; put 'left join work.indexes b'; put 'on a.dsn=b.memname'; put 'and upcase(a.name)=upcase(b.name)'; put 'order by libref,dsn;'; put '%end;'; put '%else %do;'; put '%local dsn;'; put '%if &ds = #ALL %then %let dsn=;'; put '%mp_getconstraints(lib=&lib.,ds=&dsn,outds=work.constraints)'; put '/* extract cols that are clearly primary keys */'; put 'proc sql;'; put 'create table work.pk4sure as'; put 'select libref'; put ',table_name'; put ',constraint_name'; put ',constraint_order'; put ',column_name as name'; put 'from work.constraints'; put 'where constraint_type=''PRIMARY'''; put 'order by 1,2,3,4;'; put '/* extract unique constraints where every col is also NOT NULL */'; put 'proc sql;'; put 'create table work.sum as'; put 'select a.libref'; put ',a.table_name'; put ',a.constraint_name'; put ',count(a.column_name) as unq_cnt'; put ',count(b.column_name) as nul_cnt'; put 'from work.constraints(where=(constraint_type =''UNIQUE'')) a'; put 'left join work.constraints(where=(constraint_type =''NOT NULL'')) b'; put 'on a.libref=b.libref'; put 'and a.table_name=b.table_name'; put 'and a.column_name=b.column_name'; put 'group by 1,2,3'; put 'having unq_cnt=nul_cnt;'; put '/* extract cols from the relevant unique constraints */'; put 'create table work.pkdefault as'; put 'select a.libref'; put ',a.table_name'; put ',a.constraint_name'; put ',b.constraint_order'; put ',b.column_name as name'; put 'from work.sum a'; put 'left join work.constraints(where=(constraint_type =''UNIQUE'')) b'; put 'on a.libref=b.libref'; put 'and a.table_name=b.table_name'; put 'and a.constraint_name=b.constraint_name'; put 'order by 1,2,3,4;'; put '/* extract cols from the relevant unique INDEXES */'; put 'create table work.pkfromindex as'; put 'select libname as libref'; put ',memname as table_name'; put ',indxname as constraint_name'; put ',indxpos as constraint_order'; put ',name'; put 'from dictionary.indexes'; put 'where nomiss=''yes'' and unique=''yes'' and upcase(libname)="&lib"'; put '%if &ds ne #ALL %then %do;'; put 'and upcase(memname)="&ds"'; put '%end;'; put 'order by 1,2,3,4;'; put '/* create one table */'; put 'data work.finalpks;'; put 'set pkdefault pk4sure pkfromindex;'; put 'pk_ind=1;'; put '/* if there are multiple unique constraints, take the first */'; put 'by libref table_name constraint_name;'; put 'retain keepme;'; put 'if first.table_name then keepme=1;'; put 'if first.constraint_name and not first.table_name then keepme=0;'; put 'if keepme=1;'; put 'run;'; put '/* join back to starting table */'; put 'proc sql;'; put 'create table finalcols as'; put 'select a.*'; put ',b.constraint_order'; put ',case when b.pk_ind=1 then 1 else 0 end as pk_ind'; put 'from work.cols a'; put 'left join work.finalpks b'; put 'on a.libref=b.libref'; put 'and a.dsn=b.table_name'; put 'and upcase(a.name)=upcase(b.name)'; put 'order by libref,dsn,constraint_order;'; put '%end;'; put '/* load columns */'; put '%bitemporal_dataloader(base_lib=&mpelib'; put ',base_dsn=mpe_datacatalog_vars'; put ',append_dsn=finalcols'; put ',PK=LIBREF DSN NAME'; put ',etlsource=&sysmacroname'; put ',loadtype=TXTEMPORAL'; put ',tech_from=TX_FROM'; put ',tech_to=TX_TO'; put '%if &ds ne #ALL %then %do;'; put ',close_vars=LIBREF DSN'; put '%end;'; put ',dclib=&mpelib'; put ')'; put '/* prepare tables */'; put 'proc sql;'; put 'create table work.tabs as select'; put 'libname as libref'; put ',upcase(memname) as dsn'; put ',memtype'; put '%if %mf_existfeature(DBMS_MEMTYPE)=1 %then %do;'; put ',dbms_memtype'; put '%end;'; put '%else %do;'; put ',''n/a'' as dbms_memtype format=$32.'; put '%end;'; put ',typemem'; put ',memlabel'; put ',nvar'; put ',compress'; put 'from dictionary.tables'; put 'where upcase(libname)="&lib"'; put '%if &ds ne #ALL %then %do;'; put 'and upcase(memname)="&ds"'; put '%end;'; put ';'; put 'data tabs2;'; put 'set finalcols;'; put 'length pk_fields $512;'; put 'retain pk_fields;'; put 'by libref dsn;'; put 'if first.dsn then pk_fields='''';'; put 'if pk_ind=1 then pk_fields=catx('' '',pk_fields,name);'; put 'if last.dsn then output;'; put 'run;'; put 'proc sql;'; put 'create table work.finaltabs as'; put 'select a.libref'; put ',a.dsn'; put ',a.memtype'; put ',a.dbms_memtype'; put ',a.typemem'; put ',a.memlabel'; put ',a.nvar'; put ',a.compress'; put ',b.pk_fields'; put 'from work.tabs a'; put 'left join work.tabs2 b'; put 'on a.libref=b.libref'; put 'and a.dsn=b.dsn;'; put '%bitemporal_dataloader(base_lib=&mpelib'; put ',base_dsn=mpe_datacatalog_tabs'; put ',append_dsn=finaltabs'; put ',PK=LIBREF DSN'; put ',etlsource=&sysmacroname'; put ',loadtype=TXTEMPORAL'; put ',tech_from=TX_FROM'; put ',tech_to=TX_TO'; put ',dclib=&mpelib'; put '%if &ds ne #ALL %then %do;'; put ',close_vars=LIBREF'; put '%end;'; put ')'; put '/* prepare table frequently changing attributes */'; put 'proc sql;'; put '%if &engine=SQLSVR %then %do;'; put 'connect using &lib;'; put 'create table work.attrs as select * from connection to &lib('; put 'SELECT SCHEMA_NAME(schema_id) as ''schema'', name, create_date, modify_date'; put 'FROM sys.tables ;'; put ');'; put 'create table work.nobs as select * from connection to &lib('; put 'SELECT SCHEMA_NAME(A.schema_id) AS ''schema'''; put ',A.Name, AVG(B.rows) AS ''RowCount'''; put 'FROM sys.objects A'; put 'INNER JOIN sys.partitions B ON A.object_id = B.object_id'; put 'WHERE A.type = ''U'''; put 'GROUP BY A.schema_id, A.Name'; put ');'; put 'disconnect from &lib;'; put 'create table statustabs as select'; put 'a.libref'; put ',a.dsn'; put ',b.create_date as crdate'; put ',b.modify_date as modate'; put ',. as filesize'; put ',c.RowCount as nobs'; put 'from work.tabs a'; put 'left join work.attrs(where=(schema="&schema")) b'; put 'on upcase(a.dsn)=upcase(b.name)'; put 'left join work.nobs(where=(schema="&schema")) c'; put 'on upcase(a.dsn)=upcase(c.name);'; put '%end;'; put '%else %do;'; put 'create table statustabs as select'; put 'libname as libref'; put ',upcase(memname) as dsn'; put ',crdate'; put ',modate'; put ',filesize'; put ',nobs'; put 'from dictionary.tables'; put 'where upcase(libname)="&lib"'; put '%if &ds ne #ALL %then %do;'; put 'and upcase(memname)="&ds"'; put '%end;'; put ';'; put '%end;'; put '%bitemporal_dataloader(base_lib=&mpelib'; put ',base_dsn=mpe_datastatus_tabs'; put ',append_dsn=statustabs'; put ',PK=LIBREF DSN'; put ',etlsource=&sysmacroname'; put ',loadtype=TXTEMPORAL'; put ',tech_from=TX_FROM'; put ',tech_to=TX_TO'; put ',dclib=&mpelib'; put '%if &ds ne #ALL %then %do;'; put ',close_vars=LIBREF'; put '%end;'; put ')'; put '%if &ds = #ALL %then %do;'; put 'proc sql;'; put 'create table statuslibs as select'; put 'libref'; put ',sum(filesize) as libsize'; put ',count(*) as table_cnt'; put 'from statustabs'; put 'group by 1;'; put '%bitemporal_dataloader(base_lib=&mpelib'; put ',base_dsn=mpe_datastatus_libs'; put ',append_dsn=statuslibs'; put ',PK=LIBREF'; put ',etlsource=&sysmacroname'; put ',loadtype=TXTEMPORAL'; put ',tech_from=TX_FROM'; put ',tech_to=TX_TO'; put ',dclib=&mpelib'; put ')'; put '%end;'; put '%mend mpe_refreshtables;'; put '%macro dc_refreshcatalog(libref);'; put '/* take current repository */'; put '%local repo repocnt xx;'; put '%let repo=%sysfunc(getoption(metarepository));'; put '/* get list of repositories and filter */'; put '%mm_getrepos(outds=repos)'; put '%let repocnt=0;'; put 'data repos;'; put 'set repos;'; put 'put (_all_)(=);'; put 'where repositorytype in(''CUSTOM'',''FOUNDATION'');'; put 'keep id name ;'; put 'call symputx(cats(''repo'',_n_),name,''l'');'; put 'call symputx(''repocnt'',_n_,''l'');'; put 'run;'; put '%put &sysmacroname #&libref#;'; put '%if #&libref# ne ## %then %do;'; put '%put &sysmacroname: assigning specific libref, &libref;'; put '%dc_assignlib(WRITE,&libref) /* write just in order to assign direct lib */'; put '%mpe_refreshlibs(lib=&libref)'; put '%mpe_refreshtables(&libref)'; put '%end;'; put '%else %do xx=1 %to &repocnt;'; put 'options metarepository=&&repo&xx;'; put '%mpe_refreshlibs()'; put '/* get libs to be ignored */'; put '%local ignorelist;'; put 'proc sql noprint;'; put 'select var_value into: ignorelist'; put 'from &mpelib..MPE_CONFIG'; put 'where var_scope=''DC_CATALOG'''; put 'and var_name="DC_IGNORELIBS"'; put 'and &dc_dttmtfmt. < TX_TO'; put 'and var_active=1;'; put '/* get all libs */'; put '%let libcnt=0;'; put 'data libraries;'; put 'set &mpelib..mpe_datacatalog_libs;'; put 'where &dc_dttmtfmt. le TX_TO;'; put 'if index("&ignorelist",''|''!!upcase(trim(libref))!!''|'')=0;'; put 'i+1;'; put 'call symputx(cats(''lib'',i),libref);'; put 'call symputx(''libcnt'',i);'; put 'run;'; put '%local i;'; put '%do i=1 %to &libcnt;'; put '%dc_assignlib(WRITE,&&lib&i)'; put '%mpe_refreshtables(&&lib&i)'; put '%end;'; put '%end;'; put 'options metarepository=&repo;'; put '%mend dc_refreshcatalog;'; put '* SAS Macros end;'; put '* SAS Includes start;'; put '* SAS Includes end;'; put '* Binary Files start;'; put '* Binary Files end;'; put '* ServiceInit start;'; put 'options noquotelenmax ps=max;'; put '/* create dummy macros to prevent stpbegin / stpend from corrupting sessions */'; put '%macro stpbegin();'; put '%put NOTE: the STPBEGIN macro should not be used for web apps!;'; put '%mend stpbegin;'; put '%macro stpend();'; put '%put NOTE: the STPEND macro should not be used for web apps!;'; put '%mend stpend;'; put '* ServiceInit end;'; put '* Service start;'; put '/**'; put '@file refreshcatalog.sas'; put '@brief Refreshes the library data catalog'; put '@details A library may be passed in a LIBREF url param.'; put '

SAS Macros

'; put '@li mpeinit.sas'; put '@li dc_refreshcatalog.sas'; put '@version 9.3'; put '@author 4GL Apps Ltd'; put '@copyright 4GL Apps Ltd. This code may only be used within Data Controller'; put 'and may not be re-distributed or re-sold without the express permission of'; put '4GL Apps Ltd.'; put '**/'; put '%global libref;'; put '%mpeinit()'; put '%dc_refreshcatalog(&libref)'; put 'data _null_;'; put 'file _webout;'; put 'put ''

Catalog Refresh Complete

'';'; put 'run;'; put '* Service end;'; run; %mm_createwebservice(path=&appLoc/&path, name=&service, code=sascode, server=&serverName, replace=yes) filename sascode clear; %let service=refreshlibs; filename sascode temp lrecl=32767; data _null_; file sascode; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '/**'; put '@file mp_jsonout.sas'; put '@brief Writes JSON in SASjs format to a fileref'; put '@details This macro can be used to OPEN a JSON stream and send one or more'; put 'tables as arrays of rows, where each row can be an object or a nested array.'; put 'There are two engines available - DATASTEP or PROCJSON.'; put 'PROC JSON is fast but will produce errs like the ones below if'; put 'special chars are encountered.'; put '> (ERR)OR: Some code points did not transcode.'; put '> An object or array close is not valid at this point in the JSON text.'; put '> Date value out of range'; put 'If this happens, try running with ENGINE=DATASTEP.'; put 'The DATASTEP engine is used to handle special SAS missing numerics, and'; put 'can also convert entire datasets to formatted values. Output JSON is always'; put 'in UTF-8.'; put 'Usage:'; put 'filename tmp temp;'; put 'data class; set sashelp.class;run;'; put '%mp_jsonout(OPEN,jref=tmp)'; put '%mp_jsonout(OBJ,class,jref=tmp)'; put '%mp_jsonout(OBJ,class,dslabel=class2,jref=tmp,showmeta=Y)'; put '%mp_jsonout(CLOSE,jref=tmp)'; put 'data _null_;'; put 'infile tmp;'; put 'input;putlog _infile_;'; put 'run;'; put 'If you are building web apps with SAS then you are strongly encouraged to use'; put 'the mX_createwebservice macros in combination with the'; put '[sasjs adapter](https://github.com/sasjs/adapter).'; put 'For more information see https://sasjs.io'; put '@param [in] action Valid values:'; put '@li OPEN - opens the JSON'; put '@li OBJ - sends a table with each row as an object'; put '@li ARR - sends a table with each row in an array'; put '@li CLOSE - closes the JSON'; put '@param [in] ds The dataset to send. Must be a work table.'; put '@param [out] jref= (_webout) The fileref to which to send the JSON'; put '@param [out] dslabel= The name to give the table in the exported JSON'; put '@param [in] fmt= (Y) Whether to keep (Y) or strip (N) formats from the table'; put '@param [in] engine= (DATASTEP) Which engine to use to send the JSON. Options:'; put '@li PROCJSON (default)'; put '@li DATASTEP (more reliable when data has non standard characters)'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to JSON'; put '

Related Files

'; put '@li mp_ds2fmtds.sas'; put '@version 9.2'; put '@author Allan Bowe'; put '@source https://github.com/sasjs/core'; put '**/'; put '%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y'; put ',engine=DATASTEP'; put ',missing=NULL'; put ',showmeta=N'; put ',maxobs=MAX'; put ')/*/STORE SOURCE*/;'; put '%local tempds colinfo fmtds i numcols numobs stmt_obs lastobs optval'; put 'tmpds1 tmpds2 tmpds3 tmpds4;'; put '%let numcols=0;'; put '%if &maxobs ne MAX %then %let stmt_obs=%str(if _n_>&maxobs then stop;);'; put '%if &action=OPEN %then %do;'; put 'options nobomfile;'; put 'data _null_;file &jref encoding=''utf-8'' lrecl=200;'; put 'put ''{"PROCESSED_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''"'';'; put 'run;'; put '%end;'; put '%else %if (&action=ARR or &action=OBJ) %then %do;'; put '/* force variable names to always be uppercase in the JSON */'; put 'options validvarname=upcase;'; put '/* To avoid issues with _webout on EBI - such as encoding diffs and truncation'; put '(https://support.sas.com/kb/49/325.html) we use temporary files */'; put 'filename _sjs1 temp lrecl=200 ;'; put 'data _null_; file _sjs1 encoding=''utf-8'';'; put 'put ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":";'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjs1 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs1 clear;'; put '/* grab col defs */'; put 'proc contents noprint data=&ds'; put 'out=_data_(keep=name type length format formatl formatd varnum label);'; put 'run;'; put '%let colinfo=%scan(&syslast,2,.);'; put 'proc sort data=&colinfo;'; put 'by varnum;'; put 'run;'; put '/* move meta to mac vars */'; put 'data &colinfo;'; put 'if _n_=1 then call symputx(''numcols'',nobs,''l'');'; put 'set &colinfo end=last nobs=nobs;'; put 'name=upcase(name);'; put '/* fix formats */'; put 'if type=2 or type=6 then do;'; put 'typelong=''char'';'; put 'length fmt $49.;'; put 'if format='''' then fmt=cats(''$'',length,''.'');'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else fmt=cats(format,formatl,''.'');'; put 'end;'; put 'else do;'; put 'typelong=''num'';'; put 'if format='''' then fmt=''best.'';'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else if formatd=0 then fmt=cats(format,formatl,''.'');'; put 'else fmt=cats(format,formatl,''.'',formatd);'; put 'end;'; put '/* 32 char unique name */'; put 'newname=''sasjs''!!substr(cats(put(md5(name),$hex32.)),1,27);'; put 'call symputx(cats(''name'',_n_),name,''l'');'; put 'call symputx(cats(''newname'',_n_),newname,''l'');'; put 'call symputx(cats(''length'',_n_),length,''l'');'; put 'call symputx(cats(''fmt'',_n_),fmt,''l'');'; put 'call symputx(cats(''type'',_n_),type,''l'');'; put 'call symputx(cats(''typelong'',_n_),typelong,''l'');'; put 'call symputx(cats(''label'',_n_),coalescec(label,name),''l'');'; put '/* overwritten when fmt=Y and a custom format exists in catalog */'; put 'if typelong=''num'' then call symputx(cats(''fmtlen'',_n_),200,''l'');'; put 'else call symputx(cats(''fmtlen'',_n_),min(32767,ceil((length+10)*1.5)),''l'');'; put 'run;'; put '%let tempds=%substr(_%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql;'; put 'select count(*) into: lastobs from &ds;'; put '%if &maxobs ne MAX %then %let lastobs=%sysfunc(min(&lastobs,&maxobs));'; put '%if &engine=PROCJSON %then %do;'; put '%if &missing=STRING %then %do;'; put '%put &sysmacroname: Special Missings not supported in proc json.;'; put '%put &sysmacroname: Switching to DATASTEP engine;'; put '%goto datastep;'; put '%end;'; put 'data &tempds;'; put 'set &ds;'; put '&stmt_obs;'; put '%if &fmt=N %then format _numeric_ best32.;;'; put '/* PRETTY is necessary to avoid line truncation in large files */'; put 'filename _sjs2 temp lrecl=131068 encoding=''utf-8'';'; put 'proc json out=_sjs2 pretty'; put '%if &action=ARR %then nokeys ;'; put ';export &tempds / nosastags fmtnumeric;'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs2 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs2 clear;'; put '%end;'; put '%else %if &engine=DATASTEP %then %do;'; put '%datastep:'; put '%if %sysfunc(exist(&ds)) ne 1 & %sysfunc(exist(&ds,VIEW)) ne 1'; put '%then %do;'; put '%put &sysmacroname: &ds NOT FOUND!!!;'; put '%return;'; put '%end;'; put '%if &fmt=Y %then %do;'; put '/**'; put '* Extract format definitions'; put '* First, by getting library locations from dictionary.formats'; put '* Then, by exporting the width using proc format'; put '* Cannot use maxw from sashelp.vformat as not always populated'; put '* Cannot use fmtinfo() as not supported in all flavours'; put '*/'; put '%let tmpds1=%substr(fmtsum%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds2=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds3=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds4=%substr(col%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql noprint;'; put 'create table &tmpds1 as'; put 'select cats(libname,''.'',memname) as FMTCAT,'; put 'FMTNAME'; put 'from dictionary.formats'; put 'where fmttype=''F'' and libname is not null'; put 'and fmtname in (select format from &colinfo where format is not null)'; put 'order by 1;'; put 'create table &tmpds2('; put 'FMTNAME char(32),'; put 'LENGTH num'; put ');'; put '%local catlist cat fmtlist i;'; put 'select distinct fmtcat into: catlist separated by '' '' from &tmpds1;'; put '%do i=1 %to %sysfunc(countw(&catlist,%str( )));'; put '%let cat=%scan(&catlist,&i,%str( ));'; put 'proc sql;'; put 'select distinct fmtname into: fmtlist separated by '' '''; put 'from &tmpds1 where fmtcat="&cat";'; put 'proc format lib=&cat cntlout=&tmpds3(keep=fmtname length);'; put 'select &fmtlist;'; put 'run;'; put 'proc sql;'; put 'insert into &tmpds2 select distinct fmtname,length from &tmpds3;'; put '%end;'; put 'proc sql;'; put 'create table &tmpds4 as'; put 'select a.*, b.length as MAXW'; put 'from &colinfo a'; put 'left join &tmpds2 b'; put 'on cats(a.format)=cats(upcase(b.fmtname))'; put 'order by a.varnum;'; put 'data _null_;'; put 'set &tmpds4;'; put 'if not missing(maxw);'; put 'call symputx('; put 'cats(''fmtlen'',_n_),'; put '/* vars need extra padding due to JSON escaping of special chars */'; put 'min(32767,ceil((max(length,maxw)+10)*1.5))'; put ',''l'''; put ');'; put 'run;'; put '/* configure varlenchk - as we are explicitly shortening the variables */'; put '%let optval=%sysfunc(getoption(varlenchk));'; put 'options varlenchk=NOWARN;'; put 'data _data_(compress=char);'; put '/* shorten the new vars */'; put 'length'; put '%do i=1 %to &numcols;'; put '&&name&i $&&fmtlen&i'; put '%end;'; put ';'; put '/* rename on entry */'; put 'set &ds(rename=('; put '%do i=1 %to &numcols;'; put '&&name&i=&&newname&i'; put '%end;'; put '));'; put '&stmt_obs;'; put 'drop'; put '%do i=1 %to &numcols;'; put '&&newname&i'; put '%end;'; put ';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=num %then %do;'; put '&&name&i=cats(put(&&newname&i,&&fmt&i));'; put '%end;'; put '%else %do;'; put '&&name&i=put(&&newname&i,&&fmt&i);'; put '%end;'; put '%end;'; put 'if _error_ then do;'; put 'call symputx(''syscc'',1012);'; put 'stop;'; put 'end;'; put 'run;'; put '%let fmtds=&syslast;'; put 'options varlenchk=&optval;'; put '%end;'; put 'proc format; /* credit yabwon for special null removal */'; put 'value bart (default=40)'; put '%if &missing=NULL %then %do;'; put '._ - .z = null'; put '%end;'; put '%else %do;'; put '._ = [quote()]'; put '. = null'; put '.a - .z = [quote()]'; put '%end;'; put 'other = [best.];'; put 'data &tempds;'; put 'attrib _all_ label='''';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'length &&name&i $&&fmtlen&i...;'; put 'format &&name&i $&&fmtlen&i...;'; put '%end;'; put '%end;'; put '%if &fmt=Y %then %do;'; put 'set &fmtds;'; put '%end;'; put '%else %do;'; put 'set &ds;'; put '%end;'; put '&stmt_obs;'; put 'format _numeric_ bart.;'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'if findc(&&name&i,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put '&&name&i=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,&&name&i)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else &&name&i=quote(cats(&&name&i));'; put '%end;'; put '%end;'; put 'run;'; put 'filename _sjs3 temp lrecl=131068 ;'; put 'data _null_;'; put 'file _sjs3 encoding=''utf-8'';'; put 'if _n_=1 then put "[";'; put 'set &tempds;'; put 'if _n_>1 then put "," @; put'; put '%if &action=ARR %then "[" ; %else "{" ;'; put '%do i=1 %to &numcols;'; put '%if &i>1 %then "," ;'; put '%if &action=OBJ %then """&&name&i"":" ;'; put '"&&name&i"n /* name literal for reserved variable names */'; put '%end;'; put '%if &action=ARR %then "]" ; %else "}" ; ;'; put '/* close out the table */'; put 'data _null_;'; put 'file _sjs3 mod encoding=''utf-8'';'; put 'put '']'';'; put 'run;'; put 'data _null_;'; put 'infile _sjs3 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs3 clear;'; put '%end;'; put 'proc sql;'; put 'drop table &colinfo, &tempds;'; put '%if %substr(&showmeta,1,1)=Y %then %do;'; put 'filename _sjs4 temp lrecl=131068 encoding=''utf-8'';'; put 'data _null_;'; put 'file _sjs4;'; put 'length label $350;'; put 'put ", ""$%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":{""vars"":{";'; put 'do i=1 to &numcols;'; put 'name=quote(trim(symget(cats(''name'',i))));'; put 'format=quote(trim(symget(cats(''fmt'',i))));'; put 'label=quote(prxchange(''s/\\/\\\\/'',-1,trim(symget(cats(''label'',i)))));'; put 'length=quote(trim(symget(cats(''length'',i))));'; put 'type=quote(trim(symget(cats(''typelong'',i))));'; put 'if i>1 then put "," @@;'; put 'put name '':{"format":'' format '',"label":'' label'; put ''',"length":'' length '',"type":'' type ''}'';'; put 'end;'; put 'put ''}}'';'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs4 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs4 clear;'; put '%end;'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put 'data _null_; file &jref encoding=''utf-8'' mod ;'; put 'put "}";'; put 'run;'; put '%end;'; put '%mend mp_jsonout;'; put '/**'; put '@file mm_webout.sas'; put '@brief Send data to/from SAS Stored Processes'; put '@details This macro should be added to the start of each Stored Process,'; put '**immediately** followed by a call to:'; put '%mm_webout(FETCH)'; put 'This will read all the input data and create same-named SAS datasets in the'; put 'WORK library. You can then insert your code, and send data back using the'; put 'following syntax:'; put 'data some datasets; * make some data ;'; put 'retain some columns;'; put 'run;'; put '%mm_webout(OPEN)'; put '%mm_webout(ARR,some) * Array format, fast, suitable for large tables ;'; put '%mm_webout(OBJ,datasets) * Object format, easier to work with ;'; put 'Finally, wrap everything up send some helpful system variables too'; put '%mm_webout(CLOSE)'; put '@param [in] action Either FETCH, OPEN, ARR, OBJ or CLOSE'; put '@param [in] ds The dataset to send back to the frontend'; put '@param [out] dslabel= Value to use instead of table name for sending to JSON'; put '@param [in] fmt= (N) Setting Y converts all vars to their formatted values'; put '@param [out] fref= (_webout) The fileref to which to write the JSON'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] workobs= (0) When set to a positive integer, will create a new'; put 'output object (WORK) which contains this number of observations from all'; put 'tables in the WORK library.'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to output JSON'; put '

SAS Macros

'; put '@li mp_jsonout.sas'; put '

Related Macros

'; put '@li ms_webout.sas'; put '@li mv_webout.sas'; put '@version 9.3'; put '@author Allan Bowe'; put '**/'; put '%macro mm_webout(action,ds,dslabel=,fref=_webout,fmt=N,missing=NULL'; put ',showmeta=N,maxobs=MAX,workobs=0'; put ');'; put '%global _webin_file_count _webin_fileref1 _webin_name1 _program _debug'; put 'sasjs_tables;'; put '%local i tempds jsonengine;'; put '/* see https://github.com/sasjs/core/issues/41 */'; put '%if "%upcase(&SYSENCODING)" ne "UTF-8" %then %let jsonengine=PROCJSON;'; put '%else %let jsonengine=DATASTEP;'; put '%if &action=FETCH %then %do;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'options mprint notes mprintnest;'; put '%end;'; put '%let _webin_file_count=%eval(&_webin_file_count+0);'; put '/* now read in the data */'; put '%do i=1 %to &_webin_file_count;'; put '%if &_webin_file_count=1 %then %do;'; put '%let _webin_fileref1=&_webin_fileref;'; put '%let _webin_name1=&_webin_name;'; put '%end;'; put 'data _null_;'; put 'infile &&_webin_fileref&i termstr=crlf;'; put 'input;'; put 'call symputx(''input_statement'',_infile_);'; put 'putlog "&&_webin_name&i input statement: " _infile_;'; put 'stop;'; put 'data &&_webin_name&i;'; put 'infile &&_webin_fileref&i firstobs=2 dsd termstr=crlf encoding=''utf-8'';'; put 'input &input_statement;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'if _n_<20 then putlog _infile_;'; put '%end;'; put 'run;'; put '%let sasjs_tables=&sasjs_tables &&_webin_name&i;'; put '%end;'; put '%end;'; put '%else %if &action=OPEN %then %do;'; put '/* fix encoding */'; put 'OPTIONS NOBOMFILE;'; put '/**'; put '* check xengine type to avoid the below err message:'; put '* > Function is only valid for filerefs using the CACHE access method.'; put '*/'; put 'data _null_;'; put 'set sashelp.vextfl(where=(fileref="_WEBOUT"));'; put 'if xengine=''STREAM'' then do;'; put 'rc=stpsrv_header(''Content-type'',"text/html; encoding=utf-8");'; put 'end;'; put 'run;'; put '/* setup json */'; put 'data _null_;file &fref encoding=''utf-8'';'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutBEGIN<<'';'; put '%end;'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'run;'; put '%end;'; put '%else %if &action=ARR or &action=OBJ %then %do;'; put '%mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt,jref=&fref'; put ',engine=&jsonengine,missing=&missing,showmeta=&showmeta,maxobs=&maxobs'; put ')'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put '/* To avoid issues with _webout on EBI we use a temporary file */'; put 'filename _sjsref temp lrecl=131068;'; put '%if %str(&workobs) > 0 %then %do;'; put '/* if debug mode, send back first XX records of each work table also */'; put 'data;run;%let tempds=%scan(&syslast,2,.);'; put 'ods output Members=&tempds;'; put 'proc datasets library=WORK memtype=data;'; put '%local wtcnt;%let wtcnt=0;'; put 'data _null_;'; put 'set &tempds;'; put 'if not (upcase(name) =:"DATA"); /* ignore temp datasets */'; put 'i+1;'; put 'call symputx(cats(''wt'',i),name,''l'');'; put 'call symputx(''wtcnt'',i,''l'');'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put ",""WORK"":{";'; put '%do i=1 %to &wtcnt;'; put '%let wt=&&wt&i;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'dsid=open("WORK.&wt",''is'');'; put 'nlobs=attrn(dsid,''NLOBS'');'; put 'nvars=attrn(dsid,''NVARS'');'; put 'rc=close(dsid);'; put 'if &i>1 then put '',''@;'; put 'put " ""&wt"" : {";'; put 'put ''"nlobs":'' nlobs;'; put 'put '',"nvars":'' nvars;'; put '%mp_jsonout(OBJ,&wt,jref=_sjsref,dslabel=first10rows,showmeta=Y'; put ',maxobs=&workobs'; put ')'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put '%end;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put 'run;'; put '%end;'; put '/* close off json */'; put 'data _null_;file _sjsref mod encoding=''utf-8'';'; put 'length SYSPROCESSNAME syserrortext syswarningtext autoexec $512;'; put 'put ",""_DEBUG"" : ""&_debug"" ";'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'autoexec=quote(urlencode(trim(getoption(''autoexec''))));'; put 'put '',"AUTOEXEC" : '' autoexec;'; put 'put ",""MF_GETUSER"" : ""%mf_getuser()"" ";'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'put ",""SYSENCODING"" : ""&sysencoding"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSPROCESSID"" : ""&SYSPROCESSID"" ";'; put 'put ",""SYSPROCESSMODE"" : ""&SYSPROCESSMODE"" ";'; put 'SYSPROCESSNAME=quote(urlencode(cats(SYSPROCESSNAME)));'; put 'put ",""SYSPROCESSNAME"" : " SYSPROCESSNAME;'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put '',"SYSWARNINGTEXT" : '' syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'length memsize $32;'; put 'memsize="%sysfunc(INPUTN(%sysfunc(getoption(memsize)), best.),sizekmg.)";'; put 'memsize=quote(cats(memsize));'; put 'put '',"MEMSIZE" : '' memsize;'; put 'put "}" @;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutEND<<'';'; put '%end;'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjsref lrecl=1 recfm=n;'; put 'file &fref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjsref clear;'; put '%end;'; put '%mend mm_webout;'; put '%macro webout(action,ds,dslabel=,fmt=,missing=NULL,showmeta=NO,maxobs=MAX);'; put '%mm_webout(&action,ds=&ds,dslabel=&dslabel,fmt=&fmt'; put ',missing=&missing'; put ',showmeta=&showmeta'; put ',maxobs=&maxobs'; put ') %mend;'; put '/* provide additional debug info */'; put '%global _program;'; put '%put &=syscc;'; put '%put user=%mf_getuser();'; put '%put pgm=&_program;'; put '%put timestamp=%sysfunc(datetime(),datetime19.);'; put '* Service Variables start;'; put '* Service Variables end;'; put '* SAS Macros start;'; put '%macro mf_getapploc(pgm);'; put '%if "&pgm"="" %then %do;'; put '%if %symexist(_program) %then %let pgm=&_program;'; put '%else %do;'; put '%put &sysmacroname: No value provided and no _program variable available;'; put '%return;'; put '%end;'; put '%end;'; put '%local root;'; put '/**'; put '* First check we are not in the tests/macros folder (which has no subfolders)'; put '* or specifically in the testsetup or testteardown services'; put '*/'; put '%if %index(&pgm,/tests/macros/)'; put 'or %index(&pgm,/tests/testsetup)'; put 'or %index(&pgm,/tests/testteardown)'; put '%then %do;'; put '%let root=%substr(&pgm,1,%index(&pgm,/tests)-1);'; put '&root'; put '%return;'; put '%end;'; put '/**'; put '* Next, move up two levels to avoid matches on subfolder or service name'; put '*/'; put '%let root=%substr(&pgm,1,%length(&pgm)-%length(%scan(&pgm,-1,/))-1);'; put '%let root=%substr(&root,1,%length(&root)-%length(%scan(&root,-1,/))-1);'; put '%if %index(&root,/tests/) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/tests/)-1);'; put '%end;'; put '%else %if %index(&root,/services) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/services)-1);'; put '%end;'; put '%else %if %index(&root,/jobs) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/jobs)-1);'; put '%end;'; put '%else %put &sysmacroname: Could not find an app location from &pgm;'; put '&root'; put '%mend mf_getapploc ;'; put '%macro mf_getuniquefileref(prefix=_,maxtries=1000,lrecl=32767);'; put '%local rc fname;'; put '%if &prefix=0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%end;'; put '%else %do;'; put '%local x len;'; put '%let len=%eval(8-%length(&prefix));'; put '%let x=0;'; put '%do x=0 %to &maxtries;'; put '%let fname=&prefix%substr(%sysfunc(ranuni(0)),3,&len);'; put '%if %sysfunc(fileref(&fname)) > 0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%return;'; put '%end;'; put '%end;'; put '%put unable to find available fileref after &maxtries attempts;'; put '%end;'; put '%mend mf_getuniquefileref;'; put '%macro mp_abort(mac=mp_abort.sas, type=, msg=, iftrue=%str(1=1)'; put ', errds=work.mp_abort_errds'; put ', mode=REGULAR'; put ')/*/STORE SOURCE*/;'; put '%global sysprocessmode sysprocessname sasjs_stpsrv_header_loc sasjsprocessmode;'; put '%local fref fid i;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%put NOTE: /// mp_abort macro executing //;'; put '%if %length(&mac)>0 %then %put NOTE- called by &mac;'; put '%put NOTE - &msg;'; put '%if %symexist(_SYSINCLUDEFILEDEVICE)'; put '/* abort cancel FILE does not restart outside the INCLUDE on Viya 3.5 */'; put 'and %superq(SYSPROCESSNAME) ne %str(Compute Server)'; put '%then %do;'; put '%if "*&_SYSINCLUDEFILEDEVICE*" ne "**" %then %do;'; put 'data &errds;'; put 'iftrue=''1=1'';'; put 'length mac $100 msg $5000;'; put 'mac=symget(''mac'');'; put 'msg=symget(''msg'');'; put 'run;'; put 'data _null_;'; put 'abort cancel FILE;'; put 'run;'; put '%return;'; put '%end;'; put '%end;'; put '/* Web App Context */'; put '%if %symexist(_PROGRAM)'; put 'or %superq(SYSPROCESSNAME) = %str(Compute Server)'; put 'or &mode=INCLUDE'; put '%then %do;'; put 'options obs=max replace mprint;'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5"'; put '%then %do;'; put 'options nosyntaxcheck;'; put '%end;'; put '%if &mode=INCLUDE %then %do;'; put '%if %sysfunc(exist(&errds))=1 %then %do;'; put 'data _null_;'; put 'set &errds;'; put 'call symputx(''iftrue'',iftrue,''l'');'; put 'call symputx(''mac'',mac,''l'');'; put 'call symputx(''msg'',msg,''l'');'; put 'putlog (_all_)(=);'; put 'run;'; put '%if (&iftrue)=0 %then %return;'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: No include errors found;'; put '%return;'; put '%end;'; put '%end;'; put '/* extract log errs / warns, if exist */'; put '%local logloc logline;'; put '%global logmsg; /* capture global messages */'; put '%if %symexist(SYSPRINTTOLOG) %then %let logloc=&SYSPRINTTOLOG;'; put '%else %let logloc=%qsysfunc(getoption(LOG));'; put 'proc printto log=log;run;'; put '%let logline=0;'; put '%if %length(&logloc)>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input; putlog _infile_;'; put 'i=1;'; put 'retain logonce 0;'; put 'if ('; put '_infile_=:"%str(WARN)ING" or _infile_=:"%str(ERR)OR"'; put ') and logonce=0 then'; put 'do;'; put 'call symputx(''logline'',_n_);'; put 'logonce+1;'; put 'end;'; put 'run;'; put '/* capture log including lines BEFORE the err */'; put '%if &logline>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input;'; put 'i=1;'; put 'stoploop=0;'; put 'if _n_ ge &logline-15 and stoploop=0 then do until (i>22);'; put 'call symputx(''logmsg'',catx(''\n'',symget(''logmsg''),_infile_));'; put 'input;'; put 'i+1;'; put 'stoploop=1;'; put 'end;'; put 'if stoploop=1 then stop;'; put 'run;'; put '%end;'; put '%end;'; put '%if %symexist(SYS_JES_JOB_URI) %then %do;'; put '/* setup webout for Viya */'; put 'options nobomfile;'; put '%if "X&SYS_JES_JOB_URI.X"="XX" %then %do;'; put 'filename _webout temp lrecl=999999 mod;'; put '%end;'; put '%else %do;'; put 'filename _webout filesrvc parenturi="&SYS_JES_JOB_URI"'; put 'name="_webout.json" lrecl=999999 mod;'; put '%end;'; put '%end;'; put '%else %if %sysfunc(filename(fref,&sasjs_stpsrv_header_loc))=0 %then %do;'; put 'options nobomfile;'; put '/* set up http header for SASjs Server */'; put '%let fid=%sysfunc(fopen(&fref,A));'; put '%if &fid=0 %then %do;'; put '%put %str(ERR)OR: %sysfunc(sysmsg());'; put '%return;'; put '%end;'; put '%let rc=%sysfunc(fput(&fid,%str(Content-Type: application/json)));'; put '%let rc=%sysfunc(fwrite(&fid));'; put '%let rc=%sysfunc(fclose(&fid));'; put '%let rc=%sysfunc(filename(&fref));'; put '%end;'; put '/* send response in SASjs JSON format */'; put 'data _null_;'; put 'file _webout mod lrecl=32000 encoding=''utf-8'';'; put 'length msg syswarningtext syserrortext $32767 mode $10 ;'; put 'sasdatetime=datetime();'; put 'msg=symget(''msg'');'; put '%if &logline>0 %then %do;'; put 'msg=cats(msg,''\n\nLog Extract:\n'',symget(''logmsg''));'; put '%end;'; put '/* escape the escapes */'; put 'msg=tranwrd(msg,''\'',''\\'');'; put '/* escape the quotes */'; put 'msg=tranwrd(msg,''"'',''\"'');'; put '/* ditch the CRLFs as chrome complains */'; put 'msg=compress(msg,,''kw'');'; put '/* quote without quoting the quotes (which are escaped instead) */'; put 'msg=cats(''"'',msg,''"'');'; put 'if symexist(''_debug'') then debug=quote(trim(symget(''_debug'')));'; put 'else debug=''""'';'; put 'if symget(''sasjsprocessmode'')=''Stored Program'' then mode=''SASJS'';'; put 'if mode ne ''SASJS'' then put ''>>weboutBEGIN<<'';'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'put '',"sasjsAbort" : [{'';'; put 'put '' "MSG":'' msg ;'; put 'put '' ,"MAC": "'' "&mac" ''"}]'';'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'put '',"_DEBUG":'' debug ;'; put 'if symexist(''_metauser'') then do;'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put 'end;'; put 'if symexist(''SYS_JES_JOB_URI'') then do;'; put 'SYS_JES_JOB_URI=quote(trim(symget(''SYS_JES_JOB_URI'')));'; put 'put '',"SYS_JES_JOB_URI": '' SYS_JES_JOB_URI;'; put 'end;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put ",""SYSWARNINGTEXT"" : " syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'put "}" ;'; put 'if mode ne ''SASJS'' then put ''>>weboutEND<<'';'; put 'run;'; put '%put _all_;'; put '%if "&sysprocessmode " = "SAS Stored Process Server " %then %do;'; put 'data _null_;'; put 'putlog ''stpsrvset program err and syscc'';'; put 'rc=stpsrvset(''program error'', 0);'; put 'call symputx("syscc",0,"g");'; put 'run;'; put '%if &sysscp=WIN'; put 'and 1=0 /* deprecating this logic until we figure out a consistent abort */'; put 'and "%substr(%str(&sysvlong ),1,8)"="9.04.01M"'; put 'and "%substr(%str(&sysvlong ),9,1)">"5" %then %do;'; put '/* skip approach (below) does not work in windows m6+ envs */'; put 'endsas;'; put '%end;'; put '%else %do;'; put '/**'; put '* endsas kills 9.4m3 deployments by orphaning multibridges.'; put '* Abort variants are ungraceful (non zero return code)'; put '* This approach lets SAS run silently until the end :-)'; put '* Caution - fails when called within a %include within a macro'; put '* Use mp_include() to handle this.'; put '*/'; put 'filename skip temp;'; put 'data _null_;'; put 'file skip;'; put 'put ''%macro skip();'';'; put 'comment ''%mend skip; -> fix lint '';'; put 'put ''%macro skippy();'';'; put 'comment ''%mend skippy; -> fix lint '';'; put 'run;'; put '%inc skip;'; put '%end;'; put '%end;'; put '%else %if "&sysprocessmode " = "SAS Compute Server " %then %do;'; put '/* endsas kills the session making it harder to fetch results */'; put 'data _null_;'; put 'syswarningtext=symget(''syswarningtext'');'; put 'syserrortext=symget(''syserrortext'');'; put 'abort_msg=symget(''msg'');'; put 'syscc=symget(''syscc'');'; put 'sysuserid=symget(''sysuserid'');'; put 'iftrue=symget(''iftrue'');'; put 'put (_all_)(/=);'; put 'call symputx(''syscc'',0);'; put 'abort cancel nolist;'; put 'run;'; put '%end;'; put '%else %do;'; put '%abort cancel;'; put '%end;'; put '%end;'; put '%else %do;'; put '%put _all_;'; put '%abort cancel;'; put '%end;'; put '%mend mp_abort;'; put '/** @endcond */'; put '%macro mm_getstpcode('; put 'tree=/User Folders/sasdemo/somestp'; put ',name='; put ',outloc=0'; put ',outref=0'; put ',mDebug=1'; put ',showlog=NO'; put ');'; put '%local mD;'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing &sysmacroname..sas;'; put '%&mD.put _local_;'; put '%if %length(&name)>0 %then %let name=/&name;'; put '/* first, check if STP exists */'; put '%local tsuri;'; put '%let tsuri=stopifempty ;'; put 'data _null_;'; put 'format type uri tsuri value $200.;'; put 'call missing (of _all_);'; put 'path="&tree&name(StoredProcess)";'; put '/* first, find the STP ID */'; put 'if metadata_pathobj("",path,"StoredProcess",type,uri)>0 then do;'; put '/* get sourcecode */'; put 'cnt=1;'; put 'do while (metadata_getnasn(uri,"Notes",cnt,tsuri)>0);'; put 'rc=metadata_getattr(tsuri,"Name",value);'; put '&mD.put tsuri= value=;'; put 'if value="SourceCode" then do;'; put '/* found it! */'; put 'rc=metadata_getattr(tsuri,"Id",value);'; put 'call symputx(''tsuri'',value,''l'');'; put 'stop;'; put 'end;'; put 'cnt+1;'; put 'end;'; put 'end;'; put 'else put (_all_)(=);'; put 'run;'; put '%mp_abort(iftrue= (&tsuri=stopifempty)'; put ',mac=mm_getstpcode'; put ',msg=%str(&tree&name.(StoredProcess) not found!)'; put ')'; put '/**'; put '* Now we can extract the textstore'; put '*/'; put 'filename __getdoc temp lrecl=10000000;'; put 'proc metadata'; put 'in="$METAREPOSITORY'; put ''; put 'SAS1"'; put 'out=__getdoc ;'; put 'run;'; put '/* find the beginning of the text */'; put '%local start;'; put 'data _null_;'; put 'infile __getdoc lrecl=10000;'; put 'input;'; put 'start=index(_infile_,''StoredText="'');'; put 'if start then do;'; put 'call symputx("start",start+11);'; put '*putlog ''"'' _infile_ ''"'';'; put 'end;'; put 'stop;'; put '%local outeng;'; put '%if "&outloc"="0" %then %let outeng=TEMP;'; put '%else %let outeng="&outloc";'; put '%local fref;'; put '%if &outref=0 %then %let fref=%mf_getuniquefileref();'; put '%else %let fref=&outref;'; put '/* read the content, byte by byte, resolving escaped chars */'; put 'filename &fref &outeng lrecl=100000;'; put 'data _null_;'; put 'length filein 8 fileid 8;'; put 'filein = fopen("__getdoc","I",1,"B");'; put 'fileid = fopen("&fref","O",1,"B");'; put 'rec = "20"x;'; put 'length entity $6;'; put 'do while(fread(filein)=0);'; put 'x+1;'; put 'if x>&start then do;'; put 'rc = fget(filein,rec,1);'; put 'if rec=''"'' then leave;'; put 'else if rec="&" then do;'; put 'entity=rec;'; put 'do until (rec=";");'; put 'if fread(filein) ne 0 then goto getout;'; put 'rc = fget(filein,rec,1);'; put 'entity=cats(entity,rec);'; put 'end;'; put 'select (entity);'; put 'when (''&'' ) rec=''&'' ;'; put 'when (''<'' ) rec=''<'' ;'; put 'when (''>'' ) rec=''>'' ;'; put 'when (''''') rec="''" ;'; put 'when (''"'') rec=''"'' ;'; put 'when ('' '') rec=''0A''x;'; put 'when ('' '') rec=''0D''x;'; put 'when (''$'' ) rec=''$'' ;'; put 'when ('' '') rec=''09''x;'; put 'otherwise putlog "%str(WARN)ING: missing value for " entity=;'; put 'end;'; put 'rc =fput(fileid, substr(rec,1,1));'; put 'rc =fwrite(fileid);'; put 'end;'; put 'else do;'; put 'rc =fput(fileid,rec);'; put 'rc =fwrite(fileid);'; put 'end;'; put 'end;'; put 'end;'; put 'getout:'; put 'rc=fclose(filein);'; put 'rc=fclose(fileid);'; put 'run;'; put '%if &showlog=YES %then %do;'; put 'data _null_;'; put 'infile &fref lrecl=32767 end=last;'; put 'input;'; put 'if _n_=1 then putlog ''>>stpcodeBEGIN<<'';'; put 'putlog _infile_;'; put 'if last then putlog ''>>stpcodeEND<<'';'; put 'run;'; put '%end;'; put 'filename __getdoc clear;'; put '%if &outref=0 %then %do;'; put 'filename &fref clear;'; put '%end;'; put '%mend mm_getstpcode;'; put '%macro dc_getsettings();'; put '%global _program;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(syscc=&syscc on entry (&syswarningtext &syserrortext))'; put ')'; put '%if %length(&_PROGRAM)>1 %then %let root=&_program;'; put '%else %do;'; put '%global _metauser;'; put '%let _metauser=&sysuserid;'; put '/* to mimic a "real" _program we need to give a dummy role and stp name */'; put '%let root=/dummyRole/dummyName;'; put '%end;'; put '/* the DC precode is stored in the Admin folder in the root of'; put 'the project. Lets find that root. */'; put '%put &=root;'; put '%let root=%mf_getapploc();'; put '%put &=root;'; put '/* Now we know the root location we can retrieve the params */'; put '%let temploc=%sysfunc(pathname(work))/settings.txt;'; put '%mm_getstpcode(tree=&root/services/public'; put ',name=Data_Controller_Settings'; put ',outloc=&temploc'; put ')'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Unable to run getstpcode)'; put ')'; put 'filename _getsets "&temploc" lrecl=2000;'; put '/*'; put 'Do not use mp_include here - this puts a copy in every service, which creates'; put 'compilation problems when calling services from mp_include'; put '*/'; put '%inc _getsets/source2;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Problem running &sysmacroname (&syswarningtext &syserrortext))'; put ')'; put '%mend dc_getsettings;'; put '%macro mf_fmtdttm('; put ')/*/STORE SOURCE*/;'; put '%if "&sysver"="9.2" or "&sysver"="9.3"'; put 'or ("&sysver"="9.4" and "%substr(&SYSVLONG,9,1)" le "3")'; put 'or "%substr(&sysver,1,1)"="4"'; put 'or "%substr(&sysver,1,1)"="5"'; put '%then %do;DATETIME19.3%end;'; put '%else %do;E8601DT26.6%end;'; put '%mend mf_fmtdttm;'; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '%macro mp_init(prefix=SASJS'; put ')/*/STORE SOURCE*/;'; put '%if %symexist(SASJS_PREFIX) %then %return; /* only run once */'; put '%global'; put 'SASJS_PREFIX /* the ONLY hard-coded global macro variable in SASjs */'; put '&prefix._FUNCTIONS /* used in mcf_init() to track core function compilation */'; put '&prefix._INIT_NUM /* initialisation time as numeric */'; put '&prefix._INIT_DTTM /* initialisation time in E8601DT26.6 format */'; put '&prefix.WORK /* avoid typing %sysfunc(pathname(work)) every time */'; put ';'; put '%let sasjs_prefix=&prefix;'; put 'data _null_;'; put 'dttm=datetime();'; put 'call symputx("&sasjs_prefix._init_num",dttm,''g'');'; put 'call symputx("&sasjs_prefix._init_dttm",put(dttm,E8601DT26.6),''g'');'; put 'call symputx("&sasjs_prefix.work",pathname(''WORK''),''g'');'; put 'run;'; put 'options'; put 'compress=CHAR /* default is none so ensure we have something! */'; put 'datastmtchk=ALLKEYWORDS /* protection from overwriting input datasets */'; put 'errorcheck=STRICT /* catch errs in libname/filename statements */'; put 'fmterr /* ensure err when a format cannot be found */'; put 'mergenoby=%str(ERR)OR /* throw err when a merge has no BY variables */'; put 'missing=. /* changing this can cause hard to detect errs */'; put 'noquotelenmax /* avoid warnings for long strings */'; put 'noreplace /* avoid overwriting permanent datasets */'; put 'ps=max /* reduce log size slightly */'; put 'ls=max /* reduce log even more and avoid word truncation */'; put 'validmemname=COMPATIBLE /* avoid special characters etc in table names */'; put 'validvarname=V7 /* avoid special characters etc in variable names */'; put 'varinitchk=%str(ERR)OR /* avoid data mistakes from variable name typos */'; put 'varlenchk=%str(ERR)OR /* fail hard if truncation (data loss) can result */'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5" %then %do;'; put 'noautocorrect /* disallow misspelled procedure names */'; put 'dsoptions=note2err /* undocumented - convert bad NOTEs to ERRs */'; put '%end;'; put ';'; put '%mend mp_init;'; put '%macro mpeinit(fetch=YES);'; put '%global mpeinit'; put 'mpeadmins /* group with unrestricted Meditor access */'; put 'mpelocapprovals /* location for landing and staging files */'; put 'mpelib /* location of configuration tables for DC */'; put 'dc_repo_users /* location of user / group metadata */'; put 'dc_licence_key /* extracted in dc_getsettings */'; put 'dc_activation_key /* extracted in dc_getsettings */'; put 'dc_locale /* extracted in dc_getsettings */'; put 'dc_dttmtfmt /* can be overridden in dc_getsettings */'; put '_debug'; put ';'; put '%if &mpeinit=1 %then %return;'; put '%else %let mpeinit=1;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(Problem on service startup (&syswarningtext &syserrortext))'; put ')'; put '%mp_init()'; put '%if &fetch=YES %then %do;'; put '%webout(FETCH)'; put '%end;'; put '%global _CLIENTNAME;'; put '%mp_abort(iftrue= (&_CLIENTNAME=SAS Enterprise Guide)'; put ',mac=&_program..sas'; put ',msg=%str(Data Controller is a web app and should not be executed from EG)'; put ')'; put 'options urlencoding=utf8 nobomfile lrecl=32767;'; put '%let perf=%sysfunc(datetime());'; put '%put perfdiff: 0;'; put '%let dc_locale=SYSTEM; /* default if not set */'; put '/**'; put '* E8601DT26.6 has widest database support - but not all SAS flavours can'; put '* handle it. Override in the settings STP if needed.'; put '*/'; put 'data _null_;'; put 'dc_dttmtfmt=''"%sysfunc(datetime(),''!!"%mf_fmtdttm()"!!'')"dt'';'; put 'call symputx(''dc_dttmtfmt'',dc_dttmtfmt);'; put 'put dc_dttmtfmt=;'; put 'run;'; put '%put &=dc_dttmtfmt;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc prior to dc_getsettings)'; put ')'; put '%dc_getsettings()'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc after dc_getsettings)'; put ')'; put 'data _null_;'; put 'set &DC_LIBREF..mpe_config(where=('; put 'var_scope="DC"'; put 'and &dc_dttmtfmt lt tx_to'; put 'and var_active=1'; put '));'; put 'call symputx(var_name,var_value,''G'');'; put 'putlog var_name "=" var_value;'; put 'run;'; put '%let mpelib=&dc_libref;'; put '%let mpeadmins=&dc_admin_group;'; put '%let mpelocapprovals=&dc_staging_area;'; put '%let dc_repo_users=&dc_repo_users;'; put '%if &dc_locale ne SYSTEM %then %do;'; put 'options locale=&dc_locale;'; put '%end;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(Problem during compilation or with STP precode (&syswarningtext))'; put ')'; put '%mend mpeinit;'; put '%macro mf_mval(var);'; put '%if %symexist(&var) %then %do;'; put '%superq(&var)'; put '%end;'; put '%mend mf_mval;'; put '%macro mf_trimstr(basestr,trimstr);'; put '%local baselen trimlen trimval;'; put '/* return if basestr is shorter than trimstr (or 0) */'; put '%let baselen=%length(%superq(basestr));'; put '%let trimlen=%length(%superq(trimstr));'; put '%if &baselen < &trimlen or &baselen=0 %then %return;'; put '/* obtain the characters from the end of basestr */'; put '%let trimval=%qsubstr(%superq(basestr)'; put ',%length(%superq(basestr))-&trimlen+1'; put ',&trimlen);'; put '/* compare and if matching, chop it off! */'; put '%if %superq(basestr)=%superq(trimstr) %then %do;'; put '%return;'; put '%end;'; put '%else %if %superq(trimval)=%superq(trimstr) %then %do;'; put '%qsubstr(%superq(basestr),1,%length(%superq(basestr))-&trimlen)'; put '%end;'; put '%else %do;'; put '&basestr'; put '%end;'; put '%mend mf_trimstr;'; put '%macro mf_getplatform(switch'; put ')/*/STORE SOURCE*/;'; put '%local a b c;'; put '%if &switch.NONE=NONE %then %do;'; put '%if %symexist(sasjsprocessmode) %then %do;'; put '%if &sasjsprocessmode=Stored Program %then %do;'; put 'SASJS'; put '%return;'; put '%end;'; put '%end;'; put '%if %symexist(sysprocessmode) %then %do;'; put '%if "&sysprocessmode"="SAS Object Server"'; put 'or "&sysprocessmode"= "SAS Compute Server" %then %do;'; put 'SASVIYA'; put '%end;'; put '%else %if "&sysprocessmode"="SAS Stored Process Server"'; put 'or "&sysprocessmode"="SAS Workspace Server"'; put '%then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if %symexist(_metaport) or %symexist(_metauser) %then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if &switch=SASSTUDIO %then %do;'; put '/* return the version of SAS Studio else 0 */'; put '%if %mf_mval(_CLIENTAPP)=%str(SAS Studio) %then %do;'; put '%let a=%mf_mval(_CLIENTVERSION);'; put '%let b=%scan(&a,1,.);'; put '%if %eval(&b >2) %then %do;'; put '&b'; put '%end;'; put '%else 0;'; put '%end;'; put '%else 0;'; put '%end;'; put '%else %if &switch=VIYARESTAPI %then %do;'; put '%mf_trimstr(%sysfunc(getoption(servicesbaseurl)),/)'; put '%end;'; put '%mend mf_getplatform;'; put '%macro mpeterm();'; put '%local oldloc;'; put 'data _null_;'; put 'if symexist(''SYSPRINTTOLOG'') then oldloc=symget(''SYSPRINTTOLOG'');'; put 'else oldloc=getoption(''LOG'');'; put 'if subpad(oldloc,1,1) not in (''"'',"''",'' '') then oldloc=quote(cats(oldloc));'; put 'call symputx(''oldloc'',oldloc,''l'');'; put 'run;'; put '%if %length(&oldloc)>0 %then %do;'; put 'proc printto log=log;'; put 'run;'; put 'data _null_;'; put 'infile &oldloc;'; put 'input; putlog _infile_;'; put 'run;'; put '%end;'; put '%if %sysfunc(exist(&dc_libref..mpe_requests)) and %mf_getplatform() ne SASVIYA'; put '%then %do;'; put 'data ;'; put 'if 0 then set &dc_libref..mpe_requests;'; put 'request_dttm=%sysfunc(datetime());'; put 'request_user="%mf_getuser()";'; put 'request_service="%scan(&_program,-2,/)/%scan(&_program,-1,/)";'; put 'request_params='''';'; put 'output;stop;'; put 'proc append base=&dc_libref..mpe_requests data=&syslast force nowarn;'; put 'run;'; put '%end;'; put '%mend mpeterm;'; put '%macro mm_assignlib('; put 'libref'; put ',mAbort=HARD'; put ')/*/STORE SOURCE*/;'; put '%local mp_abort msg;'; put '%let mp_abort=0;'; put '%if %sysfunc(libref(&libref)) %then %do;'; put 'data _null_;'; put 'length liburi LibName msg $200;'; put 'call missing(of _all_);'; put 'nobj=metadata_getnobj("omsobj:SASLibrary?@Libref=''&libref''",1,liburi);'; put 'if nobj=1 then do;'; put 'rc=metadata_getattr(liburi,"Name",LibName);'; put '/* now try and assign it */'; put 'if libname("&libref",,''meta'',cats(''liburi="'',liburi,''";'')) ne 0 then do;'; put 'putlog "&libref could not be assigned";'; put 'putlog liburi=;'; put '/**'; put '* Fetch the system message for display in the abort modal. This is'; put '* not always helpful though. One example, previously received:'; put '* NOTE: Libref XX refers to the same library metadata as libref XX.'; put '*/'; put 'msg=sysmsg();'; put 'if msg=:''ERROR: Libref SAVE is not assigned.'' then do;'; put 'msg=catx(" ",'; put '"Could not assign %upcase(&libref).",'; put '"Please check metadata permissions! Libname:",libname,'; put '"Liburi:",liburi'; put ');'; put 'end;'; put 'else if msg="ERROR: User does not have appropriate authorization "!!'; put '"level for library SAVE."'; put 'then do;'; put 'msg=catx(" ",'; put '"ERROR: User does not have appropriate authorization level",'; put '"for library %upcase(&libref), libname:",libname,'; put '"Liburi:",liburi'; put ');'; put 'end;'; put 'call symputx(''msg'',msg,''l'');'; put 'if "&mabort"=''HARD'' then call symputx(''mp_abort'',1,''l'');'; put 'end;'; put 'else do;'; put 'put (_all_)(=);'; put 'call symputx(''libname'',libname,''L'');'; put 'call symputx(''liburi'',liburi,''L'');'; put 'end;'; put 'end;'; put 'else if nobj>1 then do;'; put 'if "&mabort"=''HARD'' then call symputx(''mp_abort'',1);'; put 'call symputx(''msg'',"More than one library with libref=&libref");'; put 'end;'; put 'else do;'; put 'if "&mabort"=''HARD'' then call symputx(''mp_abort'',1);'; put 'call symputx(''msg'',"Library &libref not found in metadata");'; put 'end;'; put 'run;'; put '%put NOTE: &msg;'; put '%end;'; put '%else %do;'; put '%put NOTE: Library &libref is already assigned;'; put '%end;'; put '%mp_abort(iftrue= (&mp_abort=1)'; put ',mac=mm_assignlib.sas'; put ',msg=%superq(msg)'; put ')'; put '%mend mm_assignlib;'; put '/** @cond */'; put '%macro mf_getengine(libref'; put ')/*/STORE SOURCE*/;'; put '%local dsid engnum rc engine;'; put '/* in case the parameter is a libref.tablename, pull off just the libref */'; put '%let libref = %upcase(%scan(&libref, 1, %str(.)));'; put '%let dsid=%sysfunc('; put 'open(sashelp.vlibnam(where=(libname="%upcase(&libref)")),i)'; put ');'; put '%if (&dsid ^= 0) %then %do;'; put '%let engnum=%sysfunc(varnum(&dsid,ENGINE));'; put '%let rc=%sysfunc(fetch(&dsid));'; put '%let engine=%sysfunc(getvarc(&dsid,&engnum));'; put '%put &libref. ENGINE is &engine.;'; put '%let rc= %sysfunc(close(&dsid));'; put '%end;'; put '%upcase(&engine)'; put '%mend mf_getengine;'; put '/** @endcond */'; put '%macro mm_assigndirectlib('; put 'libref'; put ',open_passthrough='; put ',sql_options='; put ',mDebug=0'; put ',mAbort=0'; put ')/*/STORE SOURCE*/;'; put '%local mD;'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing mm_assigndirectlib.sas;'; put '%&mD.put _local_;'; put '%if &mAbort=1 %then %let mAbort=;'; put '%else %let mAbort=%str(*);'; put '%&mD.put NOTE: Creating direct (non META) connection to &libref library;'; put '%local cur_engine;'; put '%let cur_engine=%mf_getengine(&libref);'; put '%if &cur_engine ne META and &cur_engine ne %then %do;'; put '%put NOTE: &libref already has a direct (&cur_engine) libname connection;'; put '%return;'; put '%end;'; put '%else %if %upcase(&libref)=WORK %then %do;'; put '%put NOTE: We already have a direct connection to WORK :-) ;'; put '%return;'; put '%end;'; put '/* need to determine the library ENGINE first */'; put '%local engine;'; put 'data _null_;'; put 'length lib_uri engine $256;'; put 'call missing (of _all_);'; put '/* get URI for the particular library */'; put 'rc1=metadata_getnobj("omsobj:SASLibrary?@Libref =''&libref''",1,lib_uri);'; put '/* get the Engine attribute of the previous object */'; put 'rc2=metadata_getattr(lib_uri,''Engine'',engine);'; put 'putlog "mm_assigndirectlib for &libref:" rc1= lib_uri= rc2= engine=;'; put 'call symputx("liburi",lib_uri,''l'');'; put 'call symputx("engine",engine,''l'');'; put 'run;'; put '/* now obtain engine specific connection details */'; put '%if &engine=BASE %then %do;'; put '%&mD.put NOTE: Retrieving BASE library path;'; put 'data _null_;'; put 'length up_uri $256 path cat_path $1024;'; put 'retain cat_path;'; put 'call missing (of _all_);'; put '/* get all the filepaths of the UsingPackages association */'; put 'i=1;'; put 'rc3=metadata_getnasn("&liburi",''UsingPackages'',i,up_uri);'; put 'do while (rc3>0);'; put '/* get the DirectoryName attribute of the previous object */'; put 'rc4=metadata_getattr(up_uri,''DirectoryName'',path);'; put 'if i=1 then path = ''("''!!trim(path)!!''" '';'; put 'else path ='' "''!!trim(path)!!''" '';'; put 'cat_path = trim(cat_path) !! " " !! trim(path) ;'; put 'i+1;'; put 'rc3=metadata_getnasn("&liburi",''UsingPackages'',i,up_uri);'; put 'end;'; put 'cat_path = trim(cat_path) !! ")";'; put '&mD.putlog "NOTE: Getting physical path for &libref library";'; put '&mD.putlog rc3= up_uri= rc4= cat_path= path=;'; put '&mD.putlog "NOTE: Libname cmd will be:";'; put '&mD.putlog "libname &libref" cat_path;'; put 'call symputx("filepath",cat_path,''l'');'; put 'run;'; put '%if %sysevalf(&sysver<9.4) %then %do;'; put 'libname &libref &filepath;'; put '%end;'; put '%else %do;'; put '/* apply the new filelocks option to cater for temporary locks */'; put 'libname &libref &filepath filelockwait=5;'; put '%end;'; put '%end;'; put '%else %if &engine=REMOTE %then %do;'; put 'data x;'; put 'length rcCon rcProp rc k 3 uriCon uriProp PropertyValue PropertyName'; put 'Delimiter $256 properties $2048;'; put 'retain properties;'; put 'rcCon = metadata_getnasn("&liburi", "LibraryConnection", 1, uriCon);'; put 'rcProp = metadata_getnasn(uriCon, "Properties", 1, uriProp);'; put 'k = 1;'; put 'rcProp = metadata_getnasn(uriCon, "Properties", k, uriProp);'; put 'do while (rcProp > 0);'; put 'rc = metadata_getattr(uriProp , "DefaultValue",PropertyValue);'; put 'rc = metadata_getattr(uriProp , "PropertyName",PropertyName);'; put 'rc = metadata_getattr(uriProp , "Delimiter",Delimiter);'; put 'properties = trim(properties) !! " " !! trim(PropertyName)'; put '!! trim(Delimiter) !! trim(PropertyValue);'; put 'output;'; put 'k+1;'; put 'rcProp = metadata_getnasn(uriCon, "Properties", k, uriProp);'; put 'end;'; put '%&mD.put NOTE: Getting properties for REMOTE SHARE &libref library;'; put '&mD.put _all_;'; put '%&mD.put NOTE: Libname cmd will be:;'; put '%&mD.put libname &libref &engine &properties slibref=&libref;'; put 'call symputx ("properties",trim(properties),''l'');'; put 'run;'; put 'libname &libref &engine &properties slibref=&libref;'; put '%end;'; put '%else %if &engine=OLEDB %then %do;'; put '%&mD.put NOTE: Retrieving OLEDB connection details;'; put 'data _null_;'; put 'length domain datasource provider properties schema'; put 'connx_uri domain_uri conprop_uri lib_uri schema_uri value $256.;'; put 'call missing (of _all_);'; put '/* get source connection ID */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,connx_uri);'; put '/* get connection domain */'; put 'rc1=metadata_getnasn(connx_uri,''Domain'',1,domain_uri);'; put 'rc2=metadata_getattr(domain_uri,''Name'',domain);'; put '&mD.putlog / ''NOTE: '' // ''NOTE- connection id: '' connx_uri ;'; put '&mD.putlog ''NOTE- domain: '' domain;'; put '/* get DSN and PROVIDER from connection properties */'; put 'i=0;'; put 'do until (rc<0);'; put 'i+1;'; put 'rc=metadata_getnasn(connx_uri,''Properties'',i,conprop_uri);'; put 'rc2=metadata_getattr(conprop_uri,''Name'',value);'; put 'if value=''Connection.OLE.Property.DATASOURCE.Name.xmlKey.txt'' then do;'; put 'rc3=metadata_getattr(conprop_uri,''DefaultValue'',datasource);'; put 'end;'; put 'else if value=''Connection.OLE.Property.PROVIDER.Name.xmlKey.txt'' then do;'; put 'rc4=metadata_getattr(conprop_uri,''DefaultValue'',provider);'; put 'end;'; put 'else if value=''Connection.OLE.Property.PROPERTIES.Name.xmlKey.txt'' then'; put 'do;'; put 'rc5=metadata_getattr(conprop_uri,''DefaultValue'',properties);'; put 'end;'; put 'end;'; put '&mD.putlog ''NOTE- dsn/provider/properties: '' /'; put 'datasource provider properties;'; put '&mD.putlog ''NOTE- schema: '' schema // ''NOTE-'';'; put '/* get SCHEMA */'; put 'rc6=metadata_getnasn("&liburi",''UsingPackages'',1,lib_uri);'; put 'rc7=metadata_getattr(lib_uri,''SchemaName'',schema);'; put 'call symputx(''SQL_domain'',domain,''l'');'; put 'call symputx(''SQL_dsn'',datasource,''l'');'; put 'call symputx(''SQL_provider'',provider,''l'');'; put 'call symputx(''SQL_properties'',properties,''l'');'; put 'call symputx(''SQL_schema'',schema,''l'');'; put 'run;'; put '%if %length(&open_passthrough)>0 %then %do;'; put 'proc sql &sql_options;'; put 'connect to OLEDB as &open_passthrough(INSERT_SQL=YES'; put '/* need additional properties to make this work */'; put 'properties=(''Integrated Security''=SSPI'; put '''Persist Security Info''=True'; put '%sysfunc(compress(%str(&SQL_properties),%str(())))'; put ')'; put 'DATASOURCE=&sql_dsn PROMPT=NO'; put 'PROVIDER=&sql_provider SCHEMA=&sql_schema CONNECTION = GLOBAL);'; put '%end;'; put '%else %do;'; put 'LIBNAME &libref OLEDB PROPERTIES=&sql_properties'; put 'DATASOURCE=&sql_dsn PROVIDER=&sql_provider SCHEMA=&sql_schema'; put '%if %length(&sql_domain)>0 %then %do;'; put 'authdomain="&sql_domain"'; put '%end;'; put 'connection=shared;'; put '%end;'; put '%end;'; put '%else %if &engine=ODBC %then %do;'; put '%&mD.put NOTE: Retrieving ODBC connection details;'; put 'data _null_;'; put 'length connx_uri conprop_uri value datasource up_uri schema domprop_uri authdomain $256.;'; put 'call missing (of _all_);'; put '/* get source connection ID */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,connx_uri);'; put '/* get connection properties */'; put 'i=0;'; put 'do until (rc2<0);'; put 'i+1;'; put 'rc2=metadata_getnasn(connx_uri,''Properties'',i,conprop_uri);'; put 'rc3=metadata_getattr(conprop_uri,''Name'',value);'; put 'if value=''Connection.ODBC.Property.DATASRC.Name.xmlKey.txt'' then do;'; put 'rc4=metadata_getattr(conprop_uri,''DefaultValue'',datasource);'; put 'rc2=-1;'; put 'end;'; put 'end;'; put '/* get auth domain */'; put 'autrc=metadata_getnasn(connx_uri,"Domain",1,domprop_uri);'; put 'arc=metadata_getattr(domprop_uri,"Name",authdomain);'; put 'if not missing(authdomain) then authdomain=cats(''AUTHDOMAIN='',authdomain);'; put 'call symputx(''authdomain'',authdomain,''l'');'; put '/* get SCHEMA */'; put 'rc6=metadata_getnasn("&liburi",''UsingPackages'',1,up_uri);'; put 'rc7=metadata_getattr(up_uri,''SchemaName'',schema);'; put '&mD.put rc= connx_uri= rc2= conprop_uri= rc3= value= rc4= datasource='; put 'rc6= up_uri= rc7= schema=;'; put 'call symputx(''SQL_schema'',schema,''l'');'; put 'call symputx(''SQL_dsn'',datasource,''l'');'; put 'run;'; put '%if %length(&open_passthrough)>0 %then %do;'; put 'proc sql &sql_options;'; put 'connect to ODBC as &open_passthrough'; put '(INSERT_SQL=YES DATASRC=&sql_dsn. CONNECTION=global);'; put '%end;'; put '%else %do;'; put 'libname &libref ODBC DATASRC=&sql_dsn SCHEMA=&sql_schema &authdomain;'; put '%end;'; put '%end;'; put '%else %if &engine=POSTGRES %then %do;'; put '%put NOTE: Obtaining POSTGRES library details;'; put 'data _null_;'; put 'length database ignore_read_only_columns direct_exe preserve_col_names'; put 'preserve_tab_names server schema authdomain user password'; put 'prop name value uri urisrc $256.;'; put 'call missing (of _all_);'; put '/* get database value */'; put 'prop=''Connection.DBMS.Property.DB.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,database,"");'; put 'if database^='''' then database=''database=''!!quote(trim(database));'; put 'call symputx(''database'',database,''l'');'; put '/* get IGNORE_READ_ONLY_COLUMNS value */'; put 'prop=''Library.DBMS.Property.DBIROC.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,ignore_read_only_columns,"");'; put 'if ignore_read_only_columns^='''' then ignore_read_only_columns='; put '''ignore_read_only_columns=''!!ignore_read_only_columns;'; put 'call symputx(''ignore_read_only_columns'',ignore_read_only_columns,''l'');'; put '/* get DIRECT_EXE value */'; put 'prop=''Library.DBMS.Property.DirectExe.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,direct_exe,"");'; put 'if direct_exe^='''' then direct_exe=''direct_exe=''!!direct_exe;'; put 'call symputx(''direct_exe'',direct_exe,''l'');'; put '/* get PRESERVE_COL_NAMES value */'; put 'prop=''Library.DBMS.Property.PreserveColNames.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,preserve_col_names,"");'; put 'if preserve_col_names^='''' then preserve_col_names='; put '''preserve_col_names=''!!preserve_col_names;'; put 'call symputx(''preserve_col_names'',preserve_col_names,''l'');'; put '/* get PRESERVE_TAB_NAMES value */'; put '/* be careful with PRESERVE_TAB_NAMES=YES - it will mean your table will'; put 'become case sensitive!! */'; put 'prop=''Library.DBMS.Property.PreserveTabNames.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,preserve_tab_names,"");'; put 'if preserve_tab_names^='''' then preserve_tab_names='; put '''preserve_tab_names=''!!preserve_tab_names;'; put 'call symputx(''preserve_tab_names'',preserve_tab_names,''l'');'; put '/* get SERVER value */'; put 'if metadata_getnasn("&liburi","LibraryConnection",1,uri)>0 then do;'; put 'prop=''Connection.DBMS.Property.SERVER.Name.xmlKey.txt'';'; put 'rc=metadata_getprop(uri,prop,server,"");'; put 'end;'; put 'if server^='''' then server=''server=''!!quote(cats(server));'; put 'call symputx(''server'',server,''l'');'; put '/* get SCHEMA value */'; put 'if metadata_getnasn("&liburi","UsingPackages",1,uri)>0 then do;'; put 'rc=metadata_getattr(uri,"SchemaName",schema);'; put 'end;'; put 'if schema^='''' then schema=''schema=''!!schema;'; put 'call symputx(''schema'',schema,''l'');'; put '/* get AUTHDOMAIN value */'; put '/* this is only useful if the user account contains that auth domain'; put 'if metadata_getnasn("&liburi","DefaultLogin",1,uri)>0 then do;'; put 'rc=metadata_getnasn(uri,"Domain",1,urisrc);'; put 'rc=metadata_getattr(urisrc,"Name",authdomain);'; put 'end;'; put 'if authdomain^='''' then authdomain=''authdomain=''!!quote(trim(authdomain));'; put '*/'; put 'call symputx(''authdomain'',authdomain,''l'');'; put '/* get user & pass */'; put 'if authdomain='''' & metadata_getnasn("&liburi","DefaultLogin",1,uri)>0 then'; put 'do;'; put 'rc=metadata_getattr(uri,"UserID",user);'; put 'rc=metadata_getattr(uri,"Password",password);'; put 'end;'; put 'if user^='''' then do;'; put 'user=''user=''!!quote(trim(user));'; put 'password=''password=''!!quote(trim(password));'; put 'end;'; put 'call symputx(''user'',user,''l'');'; put 'call symputx(''password'',password,''l'');'; put '&md.put _all_;'; put 'run;'; put '%if %length(&open_passthrough)>0 %then %do;'; put '%put %str(WARN)ING: Passthrough option for postgres not yet supported;'; put '%return;'; put '%end;'; put '%else %do;'; put '%if &mdebug=1 %then %do;'; put '%put NOTE: Executing the following:/;'; put '%put NOTE- libname &libref POSTGRES &database &ignore_read_only_columns;'; put '%put NOTE- &direct_exe &preserve_col_names &preserve_tab_names;'; put '%put NOTE- &server &schema &authdomain &user &password //;'; put '%end;'; put 'libname &libref POSTGRES &database &ignore_read_only_columns &direct_exe'; put '&preserve_col_names &preserve_tab_names &server &schema &authdomain'; put '&user &password;'; put '%end;'; put '%end;'; put '%else %if &engine=ORACLE %then %do;'; put '%put NOTE: Obtaining &engine library details;'; put 'data _null_;'; put 'length assocuri1 assocuri2 assocuri3 authdomain path schema $256;'; put 'call missing (of _all_);'; put '/* get auth domain */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,assocuri1);'; put 'rc=metadata_getnasn(assocuri1,''Domain'',1,assocuri2);'; put 'rc=metadata_getattr(assocuri2,"Name",authdomain);'; put 'call symputx(''authdomain'',authdomain,''l'');'; put '/* path */'; put 'rc=metadata_getprop(assocuri1,'; put '''Connection.Oracle.Property.PATH.Name.xmlKey.txt'',path);'; put 'call symputx(''path'',path,''l'');'; put '/* schema */'; put 'rc=metadata_getnasn("&liburi",''UsingPackages'',1,assocuri3);'; put 'rc=metadata_getattr(assocuri3,''SchemaName'',schema);'; put 'call symputx(''schema'',schema,''l'');'; put 'run;'; put '%put NOTE: Executing the following:/; %put NOTE-;'; put '%put NOTE- libname &libref ORACLE path=&path schema=&schema;'; put '%put NOTE- authdomain=&authdomain;'; put '%put NOTE-;'; put 'libname &libref ORACLE path=&path schema=&schema authdomain=&authdomain;'; put '%end;'; put '%else %if &engine=SQLSVR %then %do;'; put '%put NOTE: Obtaining &engine library details;'; put 'data _null;'; put 'length assocuri1 assocuri2 assocuri3 authdomain path schema userid'; put 'passwd $256;'; put 'call missing (of _all_);'; put 'rc=metadata_getnasn("&liburi",''DefaultLogin'',1,assocuri1);'; put 'rc=metadata_getattr(assocuri1,"UserID",userid);'; put 'rc=metadata_getattr(assocuri1,"Password",passwd);'; put 'call symputx(''user'',userid,''l'');'; put 'call symputx(''pass'',passwd,''l'');'; put '/* path */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,assocuri2);'; put 'rc=metadata_getprop(assocuri2,'; put '''Connection.SQL.Property.Datasrc.Name.xmlKey.txt'',path);'; put 'call symputx(''path'',path,''l'');'; put '/* schema */'; put 'rc=metadata_getnasn("&liburi",''UsingPackages'',1,assocuri3);'; put 'rc=metadata_getattr(assocuri3,''SchemaName'',schema);'; put 'call symputx(''schema'',schema,''l'');'; put 'run;'; put '%put NOTE: Executing the following:/; %put NOTE-;'; put '%put NOTE- libname &libref SQLSVR datasrc=&path schema=&schema ;'; put '%put NOTE- user="&user" pass="XXX";'; put '%put NOTE-;'; put 'libname &libref SQLSVR datasrc=&path schema=&schema user="&user" pass="&pass";'; put '%end;'; put '%else %if &engine=TERADATA %then %do;'; put '%put NOTE: Obtaining &engine library details;'; put 'data _null;'; put 'length assocuri1 assocuri2 assocuri3 authdomain path schema userid'; put 'passwd $256;'; put 'call missing (of _all_);'; put '/* get auth domain */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,assocuri1);'; put 'rc=metadata_getnasn(assocuri1,''Domain'',1,assocuri2);'; put 'rc=metadata_getattr(assocuri2,"Name",authdomain);'; put 'call symputx(''authdomain'',authdomain,''l'');'; put '/*'; put 'rc=metadata_getnasn("&liburi",''DefaultLogin'',1,assocuri1);'; put 'rc=metadata_getattr(assocuri1,"UserID",userid);'; put 'rc=metadata_getattr(assocuri1,"Password",passwd);'; put 'call symputx(''user'',userid,''l'');'; put 'call symputx(''pass'',passwd,''l'');'; put '*/'; put '/* path */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,assocuri2);'; put 'rc=metadata_getprop(assocuri2,'; put '''Connection.Teradata.Property.SERVER.Name.xmlKey.txt'',path);'; put 'call symputx(''path'',path,''l'');'; put '/* schema */'; put 'rc=metadata_getnasn("&liburi",''UsingPackages'',1,assocuri3);'; put 'rc=metadata_getattr(assocuri3,''SchemaName'',schema);'; put 'call symputx(''schema'',schema,''l'');'; put 'run;'; put '%put NOTE: Executing the following:/; %put NOTE-;'; put '%put NOTE- libname &libref TERADATA server="&path" schema=&schema ;'; put '%put NOTe- authdomain=&authdomain;'; put '%put NOTE-;'; put 'libname &libref TERADATA server="&path" schema=&schema authdomain=&authdomain;'; put '%end;'; put '%else %if &engine= %then %do;'; put '%put NOTE: Libref &libref is not registered in metadata;'; put '%&mAbort.mp_abort('; put 'msg=%str(ERR)OR: Libref &libref is not registered in metadata'; put ',mac=mm_assigndirectlib.sas);'; put '%return;'; put '%end;'; put '%else %do;'; put '%put %str(WARN)ING: Engine &engine is currently unsupported;'; put '%put %str(WARN)ING- Please contact your support team.;'; put '%return;'; put '%end;'; put '%mend mm_assigndirectlib;'; put '%macro mm_getrepos('; put 'outds=work.mm_getrepos'; put ')/*/STORE SOURCE*/;'; put '* use a temporary fileref to hold the response;'; put 'filename response temp;'; put '/* get list of libraries */'; put 'proc metadata in='; put '"1"'; put 'out=response;'; put 'run;'; put '/* write the response to the log for debugging */'; put '/*'; put 'data _null_;'; put 'infile response lrecl=1048576;'; put 'input;'; put 'put _infile_;'; put 'run;'; put '*/'; put '/* create an XML map to read the response */'; put 'filename sxlemap temp;'; put 'data _null_;'; put 'file sxlemap;'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository";'; put 'put "";'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Id";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Name";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Desc";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@DefaultNS";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@RepositoryType";'; put 'put "characterstring20";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@RepositoryFormat";'; put 'put "characterstring10";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@Access";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@CurrentAccess";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@PauseState";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Path";'; put 'put "";'; put 'put "characterstring256";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Engine";'; put 'put "";'; put 'put "characterstring8";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Options";'; put 'put "";'; put 'put "characterstring32";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@MetadataCreated";'; put 'put "characterstring24";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@MetadataUpdated";'; put 'put "characterstring24";'; put 'put '''';'; put 'put ''
'';'; put 'run;'; put 'libname _XML_ xml xmlfileref=response xmlmap=sxlemap;'; put 'proc sort data= _XML_.SASRepos out=&outds;'; put 'by name;'; put 'run;'; put '/* clear references */'; put 'filename sxlemap clear;'; put 'filename response clear;'; put 'libname _XML_ clear;'; put '%mend mm_getrepos;'; put '%macro dc_assignlib(type,libref,passthru=);'; put '%put &sysmacroname entry vars:;'; put '%put _local_;'; put '/* take current repository */'; put '%local repo repocnt x;'; put '%let repo=%sysfunc(getoption(metarepository));'; put '/* get list of repositories and filter */'; put '%mm_getrepos(outds=repos)'; put '%let repocnt=0;'; put 'data repos;'; put 'set repos;'; put 'where repositorytype in(''CUSTOM'',''FOUNDATION'')'; put '& upcase(name) ne "%upcase(&repo)";'; put 'keep id name ;'; put 'call symputx(cats(''repo'',_n_),name,''l'');'; put 'call symputx(''repocnt'',_n_,''l'');'; put 'run;'; put '/* find out which of these repositories has the libref we are searching for */'; put '%local lib_uri;'; put '%let lib_uri=NOTFOUND;'; put 'data _null_; /* check default repo first */'; put 'length lib_uri $256;'; put 'call missing (of _all_);'; put 'rc=metadata_getnobj("omsobj:SASLibrary?@Libref =''&libref''",1,lib_uri);'; put 'call symputx(''lib_uri'',coalescec(lib_uri,''NOTFOUND''),''l'');'; put 'run;'; put '%if &lib_uri=NOTFOUND %then %do;'; put '%do x=1 %to &repocnt;'; put 'options metarepository=&&repo&x;'; put 'data _null_;'; put 'length lib_uri $256;'; put 'call missing (of _all_);'; put 'rc=metadata_getnobj("omsobj:SASLibrary?@Libref =''&libref''",1,lib_uri);'; put 'call symputx(''lib_uri'',coalescec(lib_uri,''NOTFOUND''),''l'');'; put 'run;'; put '%if &lib_uri ne NOTFOUND %then %let x=%eval(&repocnt+1);'; put '%end;'; put '%end;'; put '%if &type=READ %then %do;'; put '%mm_assignlib(&libref)'; put '%end;'; put '%else %if &type=WRITE %then %do;'; put '%mm_assigndirectlib(&libref,open_passthrough=&passthru)'; put '%end;'; put 'options metarepository=&repo;'; put '%mend dc_assignlib;'; put '/** @cond */'; put '%macro mf_existvar(libds /* 2 part dataset name */'; put ', var /* variable name */'; put ')/*/STORE SOURCE*/;'; put '%local dsid rc;'; put '%let dsid=%sysfunc(open(&libds,is));'; put '%if &dsid=0 %then %do;'; put '%put %sysfunc(sysmsg());'; put '0'; put '%end;'; put '%else %if %length(&var)=0 %then %do;'; put '0'; put '%let rc=%sysfunc(close(&dsid));'; put '%end;'; put '%else %do;'; put '%sysfunc(varnum(&dsid,&var))'; put '%let rc=%sysfunc(close(&dsid));'; put '%end;'; put '%mend mf_existvar;'; put '/** @endcond */'; put '%macro mf_getattrn('; put 'libds'; put ',attr'; put ')/*/STORE SOURCE*/;'; put '%local dsid rc;'; put '%let dsid=%sysfunc(open(&libds,is));'; put '%if &dsid = 0 %then %do;'; put '%put %str(WARN)ING: Cannot open %trim(&libds), system message below;'; put '%put %sysfunc(sysmsg());'; put '-1'; put '%end;'; put '%else %do;'; put '%sysfunc(attrn(&dsid,&attr))'; put '%let rc=%sysfunc(close(&dsid));'; put '%end;'; put '%mend mf_getattrn;'; put '%macro mf_getvartype(libds /* two level name */'; put ', var /* variable name from which to return the type */'; put ')/*/STORE SOURCE*/;'; put '%local dsid vnum vtype rc;'; put '/* Open dataset */'; put '%let dsid = %sysfunc(open(&libds));'; put '%if &dsid. > 0 %then %do;'; put '/* Get variable number */'; put '%let vnum = %sysfunc(varnum(&dsid, &var));'; put '/* Get variable type (C/N) */'; put '%if(&vnum. > 0) %then %let vtype = %sysfunc(vartype(&dsid, &vnum.));'; put '%else %do;'; put '%put NOTE: Variable &var does not exist in &libds;'; put '%let vtype = %str( );'; put '%end;'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: dataset &libds not opened! (rc=&dsid);'; put '%put &sysmacroname: %sysfunc(sysmsg());'; put '%return;'; put '%end;'; put '/* Close dataset */'; put '%let rc = %sysfunc(close(&dsid));'; put '/* Return variable type */'; put '&vtype'; put '%mend mf_getvartype;'; put '%macro mf_getattrc('; put 'libds'; put ',attr'; put ')/*/STORE SOURCE*/;'; put '%local dsid rc;'; put '%let dsid=%sysfunc(open(&libds,is));'; put '%if &dsid = 0 %then %do;'; put '%put %str(WARN)ING: Cannot open %trim(&libds), system message below;'; put '%put %sysfunc(sysmsg());'; put '-1'; put '%end;'; put '%else %do;'; put '%sysfunc(attrc(&dsid,&attr))'; put '%let rc=%sysfunc(close(&dsid));'; put '%end;'; put '%mend mf_getattrc;'; put '%macro mp_lockfilecheck('; put 'libds'; put ')/*/STORE SOURCE*/;'; put 'data _null_;'; put 'if _n_=1 then putlog "&sysmacroname entry vars:";'; put 'set sashelp.vmacro;'; put 'where scope="&sysmacroname";'; put 'put name ''='' value;'; put 'run;'; put '%mp_abort(iftrue= (&syscc>0)'; put ',mac=checklock.sas'; put ',msg=Aborting with syscc=&syscc on entry.'; put ')'; put '%mp_abort(iftrue= ("&libds"="0")'; put ',mac=&sysmacroname'; put ',msg=%str(libds not provided)'; put ')'; put '%local msg lib ds;'; put '%let lib=%upcase(%scan(&libds,1,.));'; put '%let ds=%upcase(%scan(&libds,2,.));'; put '/* in DC, format catalogs are passed with a -FC suffix. No saslock here! */'; put '%if %scan(&libds,2,-)=FC %then %do;'; put '%put &sysmacroname: Format Catalog detected, no lockfile applied to &libds;'; put '%return;'; put '%end;'; put '/* do not proceed if no observations can be processed */'; put '%let msg=options obs = 0. syserrortext=%superq(syserrortext);'; put '%mp_abort(iftrue= (%sysfunc(getoption(OBS))=0)'; put ',mac=checklock.sas'; put ',msg=%superq(msg)'; put ')'; put 'data _null_;'; put 'putlog "Checking engine & member type";'; put 'run;'; put '%local engine memtype;'; put '%let memtype=%mf_getattrc(&libds,MTYPE);'; put '%let engine=%mf_getattrc(&libds,ENGINE);'; put '%if &engine ne V9 and &engine ne BASE %then %do;'; put 'data _null_;'; put 'putlog "Lib &lib is not assigned using BASE engine - uses &engine instead";'; put 'putlog "SAS lock check will not be performed";'; put 'run;'; put '%return;'; put '%end;'; put '%else %if &memtype ne DATA %then %do;'; put '%put NOTE: Cannot lock a VIEW!! Memtype=&memtype;'; put '%return;'; put '%end;'; put 'data _null_;'; put 'putlog "Engine = &engine, memtype=&memtype";'; put 'putlog "Attempting lock statement";'; put 'run;'; put 'lock &libds;'; put '%local abortme;'; put '%let abortme=0;'; put '%if &syscc>0 or &SYSLCKRC ne 0 %then %do;'; put '%let msg=Unable to apply lock on &libds (SYSLCKRC=&SYSLCKRC syscc=&syscc);'; put '%put %str(ERR)OR: &sysmacroname: &msg;'; put '%let abortme=1;'; put '%end;'; put 'lock &libds clear;'; put '%mp_abort(iftrue= (&abortme=1)'; put ',mac=&sysmacroname'; put ',msg=%superq(msg)'; put ')'; put '%mend mp_lockfilecheck;'; put '%macro mp_lockanytable('; put 'action'; put ',lib= WORK'; put ',ds=0'; put ',ref='; put ',ctl_ds=0'; put ',loops=25'; put ',loop_secs=1'; put ');'; put 'data _null_;'; put 'if _n_=1 then putlog "&sysmacroname entry vars:";'; put 'set sashelp.vmacro;'; put 'where scope="&sysmacroname";'; put 'put name ''='' value;'; put 'run;'; put '%mp_abort(iftrue= ("&ds"="0" and &action ne MAKETABLE)'; put ',mac=&sysmacroname'; put ',msg=%str(dataset was not provided)'; put ')'; put '%mp_abort(iftrue= (&ctl_ds=0)'; put ',mac=&sysmacroname'; put ',msg=%str(Control dataset was not provided)'; put ')'; put '/* set up lib & mac vars */'; put '%let lib=%upcase(&lib);'; put '%let ds=%upcase(&ds);'; put '%let action=%upcase(&action);'; put '%local user x trans msg abortme;'; put '%let user=%mf_getuser();'; put '%let abortme=0;'; put '%mp_abort(iftrue= (&action ne LOCK & &action ne UNLOCK & &action ne MAKETABLE)'; put ',mac=&sysmacroname'; put ',msg=%str(Invalid action (&action) provided)'; put ')'; put '/* if an err condition exists, exit before we even begin */'; put '%mp_abort(iftrue= (&syscc>0 and &action=LOCK)'; put ',mac=&sysmacroname'; put ',msg=%str(aborting due to syscc=&syscc on LOCK entry)'; put ')'; put '/* do not bother locking work tables (else may affect all WORK libraries) */'; put '%if (%upcase(&lib)=WORK or %str(&lib)=%str()) & &action ne MAKETABLE %then %do;'; put '%put NOTE: WORK libraries will not be registered in the locking system.;'; put '%return;'; put '%end;'; put '/* do not proceed if no observations can be processed */'; put '%mp_abort(iftrue= (%sysfunc(getoption(OBS))=0)'; put ',mac=&sysmacroname'; put ',msg=%str(cannot continue when options obs = 0)'; put ')'; put '%if &ACTION=LOCK %then %do;'; put '/* abort if a SAS lock is already in place, or cannot be applied */'; put '%mp_lockfilecheck(&lib..&ds)'; put '/* next, check there is a record for this table */'; put '%local record_exists_check;'; put 'proc sql noprint;'; put 'select count(*) into: record_exists_check from &ctl_ds'; put 'where LOCK_LIB ="&lib" and LOCK_DS="&ds";'; put 'quit;'; put '%if &syscc>0 %then %put syscc=&syscc sqlrc=&sqlrc;'; put '%if &record_exists_check=0 %then %do;'; put 'data _null_;'; put 'putlog "&sysmacroname: adding record to lock table..";'; put 'run;'; put 'data ;'; put 'if 0 then set &ctl_ds;'; put 'LOCK_LIB ="&lib";'; put 'LOCK_DS="&ds";'; put 'LOCK_STATUS_CD=''LOCKED'';'; put 'LOCK_START_DTTM="%sysfunc(datetime(),%mf_fmtdttm())"dt;'; put 'LOCK_USER_NM="&user";'; put 'LOCK_PID="&sysjobid";'; put 'LOCK_REF="&ref";'; put 'output;stop;'; put 'run;'; put '%let trans=&syslast;'; put 'proc append base=&ctl_ds data=&trans;'; put 'run;'; put '%end;'; put '/* if record does exist, perform lock attempts */'; put '%else %do x=1 %to &loops;'; put 'data _null_;'; put 'putlog "&sysmacroname: attempting lock (iteration &x) "@;'; put 'putlog "at %sysfunc(datetime(),datetime19.) ..";'; put 'run;'; put 'proc sql;'; put 'update &ctl_ds'; put 'set LOCK_STATUS_CD=''LOCKED'''; put ', LOCK_START_DTTM="%sysfunc(datetime(),%mf_fmtdttm())"dt'; put ', LOCK_USER_NM="&user"'; put ', LOCK_PID="&sysjobid"'; put ', LOCK_REF="&ref"'; put 'where LOCK_LIB ="&lib" and LOCK_DS="&ds";'; put 'quit;'; put '/**'; put '* NOTE - occasionally SQL server will return an err code (deadlocked'; put '* transaction). If so, ignore it, keep calm, and carry on..'; put '*/'; put '%if &syscc>0 %then %do;'; put 'data _null_;'; put 'putlog ''NOTE-'' / ''NOTE-'';'; put 'putlog "NOTE- &sysmacroname: Update failed. "@;'; put 'putlog "Resetting err conditions and re-attempting.";'; put 'putlog "NOTE- syscc=&syscc syserr=&syserr sqlrc=&sqlrc";'; put 'putlog ''NOTE-'' / ''NOTE-'';'; put 'run;'; put '%let syscc=0;'; put '%let sqlrc=0;'; put '%end;'; put '/* now check if the record was successfully updated */'; put '%local success_check;'; put 'proc sql noprint;'; put 'select count(*) into: success_check from &ctl_ds'; put 'where LOCK_LIB ="&lib" and LOCK_DS="&ds"'; put 'and LOCK_PID="&sysjobid" and LOCK_STATUS_CD=''LOCKED'';'; put 'quit;'; put '%if &success_check=0 %then %do;'; put '%if &x < &loops %then %do;'; put '/* pause before next check */'; put 'data _null_;'; put 'putlog ''NOTE-'' / ''NOTE-'';'; put 'putlog "NOTE- &sysmacroname: table locked, waiting "@;'; put 'putlog "%sysfunc(sleep(&loop_secs)) seconds.. ";'; put 'putlog "NOTE- (iteration &x of &loops)";'; put 'putlog ''NOTE-'' / ''NOTE-'';'; put 'run;'; put '%end;'; put '%else %do;'; put '%let msg=Unable to lock &lib..&ds via &ctl_ds after &loops attempts.\n'; put 'Please ask your administrator to investigate!;'; put '%let abortme=1;'; put '%end;'; put '%end;'; put '%else %do;'; put 'data _null_;'; put 'putlog ''NOTE-'' / ''NOTE-'';'; put 'putlog "NOTE- &sysmacroname: Table &lib..&ds locked at "@;'; put 'putlog " %sysfunc(datetime(),datetime19.) (iteration &x)"@;'; put 'putlog ''NOTE-'' / ''NOTE-'';'; put 'run;'; put '%if &syscc>0 %then %do;'; put '%put setting syscc(&syscc) back to 0;'; put '%let syscc=0;'; put '%end;'; put '%let x=&loops; /* no more iterations needed */'; put '%end;'; put '%end;'; put '%end;'; put '%else %if &ACTION=UNLOCK %then %do;'; put '%local status cnt;'; put '%let cnt=0;'; put 'proc sql noprint;'; put 'select count(*) into: cnt from &ctl_ds where LOCK_LIB ="&lib" & LOCK_DS="&ds";'; put '%if &cnt=0 %then %do;'; put '%put %str(WAR)NING: &lib..&ds was not previously locked in &ctl_ds!;'; put '%end;'; put '%else %do;'; put 'select LOCK_STATUS_CD into: status from &ctl_ds'; put 'where LOCK_LIB ="&lib" and LOCK_DS="&ds";'; put 'quit;'; put '%if &syscc>0 %then %put syscc=&syscc sqlrc=&sqlrc;'; put '%if &status=LOCKED %then %do;'; put 'data _null_;'; put 'putlog "&sysmacroname: unlocking &lib..&ds:";'; put 'run;'; put 'proc sql;'; put 'update &ctl_ds'; put 'set LOCK_STATUS_CD=''UNLOCKED'''; put ', LOCK_END_DTTM="%sysfunc(datetime(),%mf_fmtdttm())"dt'; put ', LOCK_USER_NM="&user"'; put ', LOCK_PID="&sysjobid"'; put ', LOCK_REF="&ref"'; put 'where LOCK_LIB ="&lib" and LOCK_DS="&ds";'; put 'quit;'; put '%end;'; put '%else %if &status=UNLOCKED %then %do;'; put '%put %str(WAR)NING: &lib..&ds is already unlocked!;'; put '%end;'; put '%else %do;'; put '%put NOTE: Unrecognised STATUS_CD (&status) in &ctl_ds;'; put '%let abortme=1;'; put '%end;'; put '%end;'; put '%end;'; put '%else %do;'; put '%let msg=lock_anytable given unsupported action (&action);'; put '%let abortme=1;'; put '%end;'; put '/* catch errs - mp_abort must be called outside of a logic block */'; put '%mp_abort(iftrue=(&abortme=1),'; put 'msg=%superq(msg),'; put 'mac=&sysmacroname'; put ')'; put '%exit_macro:'; put 'data _null_;'; put 'put "&sysmacroname: Exit vars: action=&action lib=&lib ds=&ds";'; put 'put " syscc=&syscc sqlrc=&sqlrc syserr=&syserr";'; put 'run;'; put '%mend mp_lockanytable;'; put '%macro bitemporal_closeouts('; put 'tech_from=tx_from_dttm'; put ',tech_to = tx_to_dttm /* Technical TO datetime variable.'; put 'Req''d on BASE table only. */'; put ',base_lib=WORK /* Libref of the BASE table. */'; put ',base_dsn=BASETABLE /* Name of BASE table. */'; put ',append_lib=WORK /* Libref of the STAGING table. */'; put ',append_dsn=APPENDTABLE /* Name of STAGING table. */'; put ',PK= name sex /* Business key, space separated. */'; put '/* Should INCLUDE BUS_FROM field if relevant. */'; put ',NOW=DEFINE'; put ',FILTER= /* supply a filter to limit the update */'; put ',outdest= /* supply an unquoted filepath/filename.ext to get'; put 'a text file containing the update statements */'; put ',loadtype='; put ',loadtarget=YES /* if <> YES will return without changing anything */'; put ');'; put '%put ENTERING &sysmacroname;'; put '%local x var start;'; put '%let start=%sysfunc(datetime());'; put '%dc_assignlib(WRITE,&base_lib)'; put '%dc_assignlib(WRITE,&append_lib)'; put '%if &now=DEFINE %then %let now=&dc_dttmtfmt.;'; put '%put &=now;'; put '/**'; put '* perform basic checks'; put '*/'; put '/* do tables exist? */'; put '%if not %sysfunc(exist(&base_lib..&base_dsn)) %then %do;'; put '%mp_abort(msg=&base_lib..&base_dsn does not exist)'; put '%end;'; put '%else %if %sysfunc(exist(&append_lib..&append_dsn))=0'; put 'and %sysfunc(exist(&append_lib..&append_dsn,VIEW))=0 %then %do;'; put '%mp_abort(msg=&append_lib..&append_dsn does not exist)'; put '%end;'; put '/* do TX columns exist? */'; put '%if &loadtype ne UPDATE %then %do;'; put '%if not %mf_existvar(&base_lib..&base_dsn,&tech_from) %then %do;'; put '%mp_abort(msg=&tech_from does not exist on &base_lib..&base_dsn)'; put '%end;'; put '%else %if not %mf_existvar(&base_lib..&base_dsn,&tech_to) %then %do;'; put '%mp_abort(msg=&tech_to does not exist on &base_lib..&base_dsn)'; put '%end;'; put '%end;'; put '/* do PK columns exist? */'; put '%do x=1 %to %sysfunc(countw(&PK));'; put '%let var=%scan(&pk,&x,%str( ));'; put '%if not %mf_existvar(&base_lib..&base_dsn,&var) %then %do;'; put '%mp_abort(msg=&var does not exist on &base_lib..&base_dsn)'; put '%end;'; put '%else %if not %mf_existvar(&append_lib..&append_dsn,&var) %then %do;'; put '%mp_abort(msg=&var does not exist on &append_lib..&append_dsn)'; put '%end;'; put '%end;'; put '/* check uniqueness */'; put 'proc sort data=&append_lib..&append_dsn'; put 'out=___closeout1 noduprecs dupout=___closeout1a;'; put 'by &pk;'; put 'run;'; put '%if %mf_getattrn(___closeout1a,NLOBS)>0 %then'; put '%put NOTE: dups on (&PK) in (&append_lib..&append_dsn);'; put '/* is &NOW value within a tolerance? Should not allow renegade closeouts.. */'; put '%local gap;'; put '%let gap=0;'; put 'data _null_;'; put 'now=&now;'; put 'gap=intck(''HOURS'',now,datetime());'; put 'call symputx(''gap'',gap,''l'');'; put 'run;'; put '%mf_abort('; put 'iftrue=(&gap > 24),'; put 'msg=NOW variable (&now) is not within a 24hr tolerance'; put ')'; put '/* have any warnings / errs occurred thus far? If so, abort */'; put '%mf_abort('; put 'iftrue=(&syscc>0),'; put 'msg=Aborted due to SYSCC=&SYSCC status'; put ')'; put '/**'; put '* Create closeout statements. These are sent as individual SQL statements'; put '* to ensure pass-through utilisation. The update_cnt variable monitors'; put '* how many records were actually updated on the target table.'; put '*/'; put '%local update_cnt;'; put '%let update_cnt=0;'; put 'filename tmp temp;'; put 'data _null_;'; put 'set ___closeout1;'; put 'file tmp;'; put 'if _n_=1 then put ''proc sql noprint;'' ;'; put 'length string $32767.;'; put '%if &loadtype=UPDATE %then %do;'; put 'put "delete from &base_lib..&base_dsn where 1";'; put '%end;'; put '%else %do;'; put 'now=symget(''now'');'; put 'put "update &base_lib..&base_dsn set &tech_to= " now @;'; put '%if %mf_existvar(&base_lib..&base_dsn,PROCESSED_DTTM) %then %do;'; put 'put " ,PROCESSED_DTTM=" now @;'; put '%end;'; put 'put " where " now " lt &tech_to ";'; put '%end;'; put '%do x=1 %to %sysfunc(countw(&PK));'; put '%let var=%scan(&pk,&x,%str( ));'; put '%if %mf_getvartype(&base_lib..&base_dsn,&var)=C %then %do;'; put '/* use single quotes to avoid ampersand resolution in data */'; put 'string=" & &var=''"!!trim(prxchange("s/''/''''/",-1,&var))!!"''";'; put '%end;'; put '%else %do;'; put 'string=cats(" & &var=",&var);'; put '%end;'; put 'put string;'; put '%end;'; put 'put "&filter ;";'; put 'put ''%let update_cnt=%eval(&update_cnt+&sqlobs);%put update_cnt=&update_cnt;'';'; put 'run;'; put 'data _null_;'; put 'infile tmp;'; put 'input;'; put 'putlog _infile_;'; put 'run;'; put '%if &loadtarget ne YES %then %return;'; put '/* ensure we have a lock */'; put '%mp_lockanytable(LOCK,'; put 'lib=&base_lib,ds=&base_dsn'; put ',ref=bitemporal_closeouts'; put ',ctl_ds=&mpelib..mpe_lockanytable'; put ')'; put 'options source2;'; put '%inc tmp;'; put 'filename tmp clear;'; put '/**'; put '* Update audit tracker'; put '*/'; put '%local newobs; %let newobs=%mf_getattrn(work.___closeout1,NLOBS);'; put '%local user; %let user=%mf_getuser();'; put 'proc sql;'; put 'insert into &mpelib..mpe_dataloads'; put 'set libref=%upcase("&base_lib")'; put ',DSN=%upcase("&base_dsn")'; put ',ETLSOURCE="&append_lib..&append_dsn contained &newobs records"'; put ',LOADTYPE="CLOSEOUT"'; put ',DELETED_RECORDS=&update_cnt'; put ',NEW_RECORDS=0'; put ',DURATION=%sysfunc(datetime())-&start'; put ',USER_NM="&user"'; put ',PROCESSED_DTTM=&now;'; put 'quit;'; put '%mend bitemporal_closeouts;'; put '%macro mf_existds(libds'; put ')/*/STORE SOURCE*/;'; put '%if %sysfunc(exist(&libds)) ne 1 & %sysfunc(exist(&libds,VIEW)) ne 1 %then 0;'; put '%else 1;'; put '%mend mf_existds;'; put '%macro mf_getschema(libref'; put ')/*/STORE SOURCE*/;'; put '%local dsid vnum rc schema;'; put '/* in case the parameter is a libref.tablename, pull off just the libref */'; put '%let libref = %upcase(%scan(&libref, 1, %str(.)));'; put '%let dsid=%sysfunc(open(sashelp.vlibnam(where=('; put 'libname="%upcase(&libref)" and sysname=''Schema/Owner'''; put ')),i));'; put '%if (&dsid ^= 0) %then %do;'; put '%let vnum=%sysfunc(varnum(&dsid,SYSVALUE));'; put '%let rc=%sysfunc(fetch(&dsid));'; put '%let schema=%sysfunc(getvarc(&dsid,&vnum));'; put '%put &libref. schema is &schema.;'; put '%let rc= %sysfunc(close(&dsid));'; put '%end;'; put '&schema'; put '%mend mf_getschema;'; put '/** @endcond */'; put '%macro mf_getuniquename(prefix=MC);'; put '&prefix.%substr(%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32-%length(&prefix))'; put '%mend mf_getuniquename;'; put '%macro mf_getvarlist(libds'; put ',dlm=%str( )'; put ',quote=no'; put ',typefilter=A'; put ')/*/STORE SOURCE*/;'; put '/* declare local vars */'; put '%local outvar dsid nvars x rc dlm q var vtype;'; put '/* credit Rowland Hale - byte34 is double quote, 39 is single quote */'; put '%if %upcase("e)=DOUBLE %then %let q=%qsysfunc(byte(34));'; put '%else %if %upcase("e)=SINGLE %then %let q=%qsysfunc(byte(39));'; put '/* open dataset in macro */'; put '%let dsid=%sysfunc(open(&libds));'; put '%if &dsid %then %do;'; put '%let nvars=%sysfunc(attrn(&dsid,NVARS));'; put '%if &nvars>0 %then %do;'; put '/* add variables with supplied delimeter */'; put '%do x=1 %to &nvars;'; put '/* get variable type */'; put '%let vtype=%sysfunc(vartype(&dsid,&x));'; put '%if &vtype=&typefilter or &typefilter=A %then %do;'; put '%let var=&q.%sysfunc(varname(&dsid,&x))&q.;'; put '%if &var=&q&q %then %do;'; put '%put &sysmacroname: Empty column found in &libds!;'; put '%let var=&q. &q.;'; put '%end;'; put '%if %quote(&outvar)=%quote() %then %let outvar=&var;'; put '%else %let outvar=&outvar.&dlm.&var.;'; put '%end;'; put '%end;'; put '%end;'; put '%let rc=%sysfunc(close(&dsid));'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: Unable to open &libds (rc=&dsid);'; put '%put &sysmacroname: SYSMSG= %sysfunc(sysmsg());'; put '%let rc=%sysfunc(close(&dsid));'; put '%end;'; put '%do;%unquote(&outvar)%end;'; put '%mend mf_getvarlist;'; put '%macro mf_abort(mac=mf_abort.sas, msg=, iftrue=%str(1=1)'; put ')/des=''ungraceful abort'' /*STORE SOURCE*/;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%put NOTE: /// mf_abort macro executing //;'; put '%if %length(&mac)>0 %then %put NOTE- called by &mac;'; put '%put NOTE - &msg;'; put '%abort;'; put '%mend mf_abort;'; put '/** @endcond */'; put '%macro mf_verifymacvars('; put 'verifyVars /* list of macro variable NAMES */'; put ',makeUpcase=NO /* set to YES to make all the variable VALUES uppercase */'; put ',mAbort=SOFT'; put ')/*/STORE SOURCE*/;'; put '%local verifyIterator verifyVar abortmsg;'; put '%do verifyIterator=1 %to %sysfunc(countw(&verifyVars,%str( )));'; put '%let verifyVar=%qscan(&verifyVars,&verifyIterator,%str( ));'; put '%if not %symexist(&verifyvar) %then %do;'; put '%let abortmsg= Variable &verifyVar is MISSING;'; put '%goto exit_err;'; put '%end;'; put '%if %length(%trim(&&&verifyVar))=0 %then %do;'; put '%let abortmsg= Variable &verifyVar is EMPTY;'; put '%goto exit_err;'; put '%end;'; put '%if &makeupcase=YES %then %do;'; put '%let &verifyVar=%upcase(&&&verifyvar);'; put '%end;'; put '%end;'; put '%goto exit_success;'; put '%exit_err:'; put '%put &abortmsg;'; put '%mf_abort(iftrue=(&mabort ne SOFT),'; put 'mac=mf_verifymacvars,'; put 'msg=%str(&abortmsg)'; put ')'; put '0'; put '%return;'; put '%exit_success:'; put '1'; put '%mend mf_verifymacvars;'; put '%macro mf_wordsInStr1ButNotStr2('; put 'Str1= /* string containing words to extract */'; put ',Str2= /* used to compare with the extract string */'; put ')/*/STORE SOURCE*/;'; put '%local count_base count_extr i i2 extr_word base_word match outvar;'; put '%if %length(&str1)=0 or %length(&str2)=0 %then %do;'; put '%put base string (str1)= &str1;'; put '%put compare string (str2) = &str2;'; put '%return;'; put '%end;'; put '%let count_base=%sysfunc(countw(&Str2));'; put '%let count_extr=%sysfunc(countw(&Str1));'; put '%do i=1 %to &count_extr;'; put '%let extr_word=%scan(&Str1,&i,%str( ));'; put '%let match=0;'; put '%do i2=1 %to &count_base;'; put '%let base_word=%scan(&Str2,&i2,%str( ));'; put '%if &extr_word=&base_word %then %let match=1;'; put '%end;'; put '%if &match=0 %then %let outvar=&outvar &extr_word;'; put '%end;'; put '&outvar'; put '%mend mf_wordsInStr1ButNotStr2;'; put '%macro mf_isblank(param'; put ')/*/STORE SOURCE*/;'; put '%sysevalf(%superq(param)=,boolean)'; put '%mend mf_isblank;'; put '%macro mp_dropmembers('; put 'list /* space separated list of datasets / views */'; put ',libref=WORK /* can only drop from a single library at a time */'; put ',iftrue=%str(1=1)'; put ')/*/STORE SOURCE*/;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%if %mf_isblank(&list) %then %do;'; put '%put NOTE: nothing to drop!;'; put '%return;'; put '%end;'; put 'proc datasets lib=&libref nolist;'; put 'delete &list;'; put 'delete &list /mtype=view;'; put 'run;'; put '%mend mp_dropmembers;'; put '%macro mf_getquotedstr(IN_STR'; put ',DLM=%str(,)'; put ',QUOTE=S'; put ',indlm=%str( )'; put ')/*/STORE SOURCE*/;'; put '/* credit Rowland Hale - byte34 is double quote, 39 is single quote */'; put '%if "e=S %then %let quote=%qsysfunc(byte(39));'; put '%else %if "e=D %then %let quote=%qsysfunc(byte(34));'; put '%else %if "e=N %then %let quote=;'; put '%local i item buffer;'; put '%let i=1;'; put '%do %while (%qscan(&IN_STR,&i,%str(&indlm)) ne %str() ) ;'; put '%let item=%qscan(&IN_STR,&i,%str(&indlm));'; put '%if %bquote("E) ne %then %let item="E%qtrim(&item)"E;'; put '%else %let item=%qtrim(&item);'; put '%if (&i = 1) %then %let buffer =%qtrim(&item);'; put '%else %let buffer =&buffer&DLM%qtrim(&item);'; put '%let i = %eval(&i+1);'; put '%end;'; put '%let buffer=%sysfunc(coalescec(%qtrim(&buffer),"E"E));'; put '&buffer'; put '%mend mf_getquotedstr;'; put '%macro mf_nobs(libds'; put ')/*/STORE SOURCE*/;'; put '%mf_getattrn(&libds,NLOBS)'; put '%mend mf_nobs;'; put '%macro mp_retainedkey('; put 'base_lib=WORK'; put ',base_dsn=BASETABLE'; put ',append_lib=WORK'; put ',append_dsn=APPENDTABLE'; put ',retained_key=DEFAULT_RK'; put ',business_key= PK1 PK2'; put ',check_uniqueness=NO'; put ',maxkeytable=0'; put ',locktable=0'; put ',outds=WORK.APPEND'; put ',filter_str='; put ');'; put '%put &sysmacroname entry vars:;'; put '%put _local_;'; put '%local base_libds app_libds key_field check maxkey idx_pk newkey_cnt iserr'; put 'msg x tempds1 tempds2 comma_pk appnobs checknobs dropvar tempvar idx_val;'; put '%let base_libds=%upcase(&base_lib..&base_dsn);'; put '%let app_libds=%upcase(&append_lib..&append_dsn);'; put '%let tempds1=%mf_getuniquename();'; put '%let tempds2=%mf_getuniquename();'; put '%let comma_pk=%mf_getquotedstr(in_str=%str(&business_key),dlm=%str(,),quote=);'; put '%let outds=%sysfunc(ifc(%index(&outds,.)=0,work.&outds,&outds));'; put '/* validation checks */'; put '%let iserr=0;'; put '%if &syscc>0 %then %do;'; put '%let iserr=1;'; put '%let msg=%str(SYSCC=&syscc on macro entry);'; put '%end;'; put '%else %if %sysfunc(exist(&base_libds))=0 %then %do;'; put '%let iserr=1;'; put '%let msg=%str(Base LIBDS (&base_libds) expected but NOT FOUND);'; put '%end;'; put '%else %if %sysfunc(exist(&app_libds))=0 %then %do;'; put '%let iserr=1;'; put '%let msg=%str(Append LIBDS (&app_libds) expected but NOT FOUND);'; put '%end;'; put '%else %if &maxkeytable ne 0 and %sysfunc(exist(&maxkeytable))=0 %then %do;'; put '%let iserr=1;'; put '%let msg=%str(Maxkeytable (&maxkeytable) expected but NOT FOUND);'; put '%end;'; put '%else %if &maxkeytable ne 0 and %sysfunc(exist(&locktable))=0 %then %do;'; put '%let iserr=1;'; put '%let msg=%str(Locktable (&locktable) expected but NOT FOUND);'; put '%end;'; put '%else %if %length(&business_key)=0 %then %do;'; put '%let iserr=1;'; put '%let msg=%str(Business key (&business_key) expected but NOT FOUND);'; put '%end;'; put '%do x=1 %to %sysfunc(countw(&business_key));'; put '/* check business key values exist */'; put '%let key_field=%scan(&business_key,&x,%str( ));'; put '%if not %mf_existvar(&app_libds,&key_field) %then %do;'; put '%let iserr=1;'; put '%let msg=Business key (&key_field) not found on &app_libds!;'; put '%goto err;'; put '%end;'; put '%else %if not %mf_existvar(&base_libds,&key_field) %then %do;'; put '%let iserr=1;'; put '%let msg=Business key (&key_field) not found on &base_libds!;'; put '%goto err;'; put '%end;'; put '%end;'; put '%err:'; put '%if &iserr=1 %then %do;'; put '/* err case so first perform an unlock of the base table before exiting */'; put '%mp_lockanytable('; put 'UNLOCK,lib=&base_lib,ds=&base_dsn,ref=%superq(msg),ctl_ds=&locktable'; put ')'; put '%end;'; put '%mp_abort(iftrue=(&iserr=1),mac=mp_retainedkey,msg=%superq(msg))'; put 'proc sql noprint;'; put 'select sum(max(&retained_key),0) into: maxkey from &base_libds;'; put '/**'; put '* get base table RK and bus field values for lookup'; put '*/'; put 'proc sql noprint;'; put 'create table &tempds1 as'; put 'select distinct &comma_pk,&retained_key'; put 'from &base_libds &filter_str'; put 'order by &comma_pk,&retained_key;'; put '%if &check_uniqueness=YES %then %do;'; put 'select count(*) into:checknobs'; put 'from (select distinct &comma_pk from &app_libds);'; put 'select count(*) into: appnobs from &app_libds; /* might be view */'; put '%if &checknobs ne &appnobs %then %do;'; put '%let msg=Source table &app_libds is not unique on (&business_key);'; put '%let iserr=1;'; put '%end;'; put '%end;'; put '%if &iserr=1 %then %do;'; put '/* err case so first perform an unlock of the base table before exiting */'; put '%mp_lockanytable('; put 'UNLOCK,lib=&base_lib,ds=&base_dsn,ref=%superq(msg),ctl_ds=&locktable'; put ')'; put '%end;'; put '%mp_abort(iftrue= (&iserr=1),mac=mp_retainedkey,msg=%superq(msg))'; put '%if %mf_existvar(&app_libds,&retained_key)'; put '%then %let dropvar=(drop=&retained_key);'; put '/* prepare interim table with retained key populated for matching keys */'; put 'proc sql noprint;'; put 'create table &tempds2 as'; put 'select b.&retained_key, a.*'; put 'from &app_libds &dropvar a'; put 'left join &tempds1 b'; put 'on 1'; put '%do idx_pk=1 %to %sysfunc(countw(&business_key));'; put '%let idx_val=%scan(&business_key,&idx_pk);'; put 'and a.&idx_val=b.&idx_val'; put '%end;'; put 'order by &retained_key;'; put '/* identify the number of entries without retained keys (new records) */'; put 'select count(*) into: newkey_cnt'; put 'from &tempds2'; put 'where missing(&retained_key);'; put 'quit;'; put '/**'; put '* Update maxkey table if link provided'; put '*/'; put '%if &maxkeytable ne 0 %then %do;'; put 'proc sql noprint;'; put 'select count(*) into: check from &maxkeytable'; put 'where upcase(keytable)="&base_libds";'; put '%mp_lockanytable(LOCK'; put ',lib=%scan(&maxkeytable,1,.)'; put ',ds=%scan(&maxkeytable,2,.)'; put ',ref=Updating maxkeyvalues with mp_retainedkey'; put ',ctl_ds=&locktable'; put ')'; put 'proc sql;'; put '%if &check=0 %then %do;'; put 'insert into &maxkeytable'; put 'set keytable="&base_libds"'; put ',keycolumn="&retained_key"'; put ',max_key=%eval(&maxkey+&newkey_cnt)'; put ',processed_dttm="%sysfunc(datetime(),%mf_fmtdttm())"dt;'; put '%end;'; put '%else %do;'; put 'update &maxkeytable'; put 'set max_key=%eval(&maxkey+&newkey_cnt)'; put ',processed_dttm="%sysfunc(datetime(),%mf_fmtdttm())"dt'; put 'where keytable="&base_libds";'; put '%end;'; put '%mp_lockanytable(UNLOCK'; put ',lib=%scan(&maxkeytable,1,.)'; put ',ds=%scan(&maxkeytable,2,.)'; put ',ref=Updating maxkeyvalues with maxkey=%eval(&maxkey+&newkey_cnt)'; put ',ctl_ds=&locktable'; put ')'; put '%end;'; put '/* fill in the missing retained key values */'; put '%let tempvar=%mf_getuniquename();'; put 'data &outds(drop=&tempvar);'; put 'retain &tempvar %eval(&maxkey+1);'; put 'set &tempds2;'; put 'if &retained_key =. then &retained_key=&tempvar;'; put '&tempvar=&tempvar+1;'; put 'run;'; put '%mend mp_retainedkey;'; put '/** @cond */'; put '%macro mp_storediffs(libds'; put ',origds'; put ',key'; put ',delds=0'; put ',appds=0'; put ',modds=0'; put ',outds=work.mp_storediffs'; put ',loadref=0'; put ',processed_dttm=0'; put ',mdebug=0'; put ')/*/STORE SOURCE*/;'; put '%local dbg;'; put '%if &mdebug=1 %then %do;'; put '%put &sysmacroname entry vars:;'; put '%put _local_;'; put '%end;'; put '%else %let dbg=*;'; put '/* set up unique and temporary vars */'; put '%local ds1 ds2 ds3 ds4 hashkey inds_auto inds_keep dslist vlist;'; put '%let ds1=%upcase(work.%mf_getuniquename(prefix=mpsd_ds1));'; put '%let ds2=%upcase(work.%mf_getuniquename(prefix=mpsd_ds2));'; put '%let ds3=%upcase(work.%mf_getuniquename(prefix=mpsd_ds3));'; put '%let ds4=%upcase(work.%mf_getuniquename(prefix=mpsd_ds4));'; put '%let hashkey=%upcase(%mf_getuniquename(prefix=mpsd_hashkey));'; put '%let inds_auto=%upcase(%mf_getuniquename(prefix=mpsd_inds_auto));'; put '%let inds_keep=%upcase(%mf_getuniquename(prefix=mpsd_inds_keep));'; put '%let dslist=&origds;'; put '%if &delds ne 0 %then %do;'; put '%let delds=%upcase(&delds);'; put '%if %scan(&delds,-1,.)=&delds %then %let delds=WORK.&delds;'; put '%let dslist=&dslist &delds;'; put '%end;'; put '%if &appds ne 0 %then %do;'; put '%let appds=%upcase(&appds);'; put '%if %scan(&appds,-1,.)=&appds %then %let appds=WORK.&appds;'; put '%let dslist=&dslist &appds;'; put '%end;'; put '%if &modds ne 0 %then %do;'; put '%let modds=%upcase(&modds);'; put '%if %scan(&modds,-1,.)=&modds %then %let modds=WORK.&modds;'; put '%let dslist=&dslist &modds;'; put '%end;'; put '%let origds=%upcase(&origds);'; put '%if %scan(&origds,-1,.)=&origds %then %let origds=WORK.&origds;'; put '%let key=%upcase(&key);'; put '/* hash the key and append all the tables (marking the source) */'; put 'data &ds1;'; put 'set &dslist indsname=&inds_auto;'; put '&hashkey=put(md5(catx(''|'',%mf_getquotedstr(&key,quote=N))),$hex32.);'; put '&inds_keep=upcase(&inds_auto);'; put 'proc sort;'; put 'by &inds_keep &hashkey;'; put 'run;'; put '/* transpose numeric & char vars */'; put 'proc transpose data=&ds1'; put 'out=&ds2(rename=(&hashkey=key_hash _name_=tgtvar_nm col1=newval_num));'; put 'by &inds_keep &hashkey;'; put 'var _numeric_;'; put 'run;'; put 'proc transpose data=&ds1'; put 'out=&ds3('; put 'rename=(&hashkey=key_hash _name_=tgtvar_nm col1=newval_char)'; put 'where=(tgtvar_nm not in ("&hashkey","&inds_keep"))'; put ');'; put 'by &inds_keep &hashkey;'; put 'var _character_;'; put 'run;'; put '%if %index(&libds,-)>0 and %scan(&libds,2,-)=FC %then %do;'; put '/* this is a format catalog - cannot query cols directly */'; put '%let vlist="TYPE","FMTNAME","FMTROW","START","END","LABEL","MIN","MAX"'; put ',"DEFAULT","LENGTH","FUZZ","PREFIX","MULT","FILL","NOEDIT","SEXCL"'; put ',"EEXCL","HLO","DECSEP","DIG3SEP","DATATYPE","LANGUAGE";'; put '%end;'; put '%else %let vlist=%mf_getvarlist(&libds,dlm=%str(,),quote=DOUBLE);'; put 'data &ds4;'; put 'length &inds_keep $41 tgtvar_nm $32 _label_ $256;'; put 'if _n_=1 then call missing(_label_);'; put 'drop _label_;'; put 'set &ds2 &ds3 indsname=&inds_auto;'; put 'tgtvar_nm=upcase(tgtvar_nm);'; put 'if tgtvar_nm in (%upcase(&vlist));'; put 'if upcase(&inds_auto)="&ds2" then tgtvar_type=''N'';'; put 'else if upcase(&inds_auto)="&ds3" then tgtvar_type=''C'';'; put 'else do;'; put 'putlog ''ERR'' +(-1) "OR: unidentified vartype input!" &inds_auto;'; put 'call symputx(''syscc'',98);'; put 'end;'; put 'if &inds_keep="&appds" then move_type=''A'';'; put 'else if &inds_keep="&delds" then move_type=''D'';'; put 'else if &inds_keep="&modds" then move_type=''M'';'; put 'else if &inds_keep="&origds" then move_type=''O'';'; put 'else do;'; put 'putlog ''ERR'' +(-1) "OR: unidentified movetype input!" &inds_keep;'; put 'call symputx(''syscc'',99);'; put 'end;'; put 'tgtvar_nm=upcase(tgtvar_nm);'; put 'if tgtvar_nm in (%mf_getquotedstr(&key)) then is_pk=1;'; put 'else is_pk=0;'; put 'drop &inds_keep;'; put 'run;'; put '%if "&loadref"="0" %then %let loadref=%sysfunc(uuidgen());'; put '%if &processed_dttm=0 %then %let processed_dttm=%sysfunc(datetime());'; put '%let libds=%upcase(&libds);'; put '/* join orig vals for modified & deleted */'; put 'proc sql;'; put 'create table &outds as'; put 'select "&loadref" as load_ref length=36'; put ',&processed_dttm as processed_dttm format=E8601DT26.6'; put ',"%scan(&libds,1,.)" as libref length=8'; put ',"%scan(&libds,2,.)" as dsn length=32'; put ',b.key_hash length=32'; put ',b.move_type length=1'; put ',b.tgtvar_nm length=32'; put ',b.is_pk'; put ',case when b.move_type ne ''M'' then -1'; put 'when a.newval_num=b.newval_num and a.newval_char=b.newval_char then 0'; put 'else 1'; put 'end as is_diff'; put ',b.tgtvar_type length=1'; put ',case when b.move_type=''D'' then b.newval_num'; put 'else a.newval_num'; put 'end as oldval_num format=best32.'; put ',case when b.move_type=''D'' then .'; put 'else b.newval_num'; put 'end as newval_num format=best32.'; put ',case when b.move_type=''D'' then b.newval_char'; put 'else a.newval_char'; put 'end as oldval_char length=32765'; put ',case when b.move_type=''D'' then '''''; put 'else b.newval_char'; put 'end as newval_char length=32765'; put 'from &ds4(where=(move_type=''O'')) as a'; put 'right join &ds4(where=(move_type ne ''O'')) as b'; put 'on a.tgtvar_nm=b.tgtvar_nm'; put 'and a.key_hash=b.key_hash'; put 'order by move_type, key_hash,is_pk desc, tgtvar_nm;'; put '%if &mdebug=0 %then %do;'; put 'proc sql;'; put 'drop table &ds1, &ds2, &ds3, &ds4;'; put '%end;'; put '%mend mp_storediffs;'; put '/** @endcond */'; put '%macro bitemporal_dataloader('; put 'bus_from= /* Business FROM datetime variable. Req''d on'; put 'STAGING & BASE tables.*/'; put ',bus_to = /* Business TO datetime variable. Req''d on'; put 'STAGING & BASE tables. */'; put ',bus_from_override= /* Provide a hard coded BUS_FROM datetime value.*/'; put ',bus_to_override= /* provide a hard coded BUS_TO datetime value */'; put ',tech_from= /* Technical FROM datetime variable. Req''d on'; put 'BASE table only. */'; put ',tech_to = /* Technical TO datetime variable. Req''d on BASE'; put 'table only. */'; put ',processed= 0'; put ',base_lib=WORK /* Libref of the BASE table. */'; put ',base_dsn=BASETABLE /* Name of BASE table. */'; put ',append_lib=WORK /* Libref of the STAGING table. */'; put ',append_dsn=APPENDTABLE'; put ',high_date=''01JAN5999:00:00:00''dt /* High date to close out records */'; put ',PK= name sex'; put ',RK_UNDERLYING='; put ',KEEPVARS= /* Provides option for removing unwanted vars from append table */'; put ',RK_UPDATE_MAXKEYTABLE=NO /* If switching (or mix matching) with regular'; put 'SCD2 loader then set this switch to YES to'; put 'ensure the MAXKEYTABLE is updated with the'; put 'current maximum RK value for the target table'; put '*/'; put ',CHECK_UNIQUENESS=YES /* Perform a check of the APPEND table to ensure it is'; put 'unique on its business key */'; put ',ETLSOURCE=demo /* supply a value ($50.) to show as ETLSOURCE in'; put '&dclib..DATALOADS */'; put ',LOADTYPE=BITEMPORAL'; put ',RK_MAXKEYTABLE= mpe_maxkeyvalues'; put ',LOG=1 /* Switch to 0 to prevent records being added to'; put '&mpelib..mpe_DATALOADS (ie when testing)*/'; put ',DELETE_COL= _____DELETE__THIS__RECORD_____'; put '/* If this variable is found in the append dataset'; put 'then records are closed out (or deleted) in the'; put 'append table where that variable= "Yes" */'; put ',LOADTARGET=YES /* set to anything but uppercase YES to switch off'; put 'target table load and generate temp tables only */'; put ',CLOSE_VARS='; put '/*a problem with regular SCD2 or TXTEMPORAL loads is that there is'; put 'no facility to close out removed records (all records are'; put 'assumed new or changed). But how does one determine which'; put 'records are removed? Short of loading the entire table'; put 'each time? This parameter allows a set of variables'; put '(this should be a subset of the PK) to be declared, and'; put 'the macro will determine which records in the base table'; put 'need to be closed out ahead of the load.'; put 'For instance, given the following:'; put 'Base Table Staging Table'; put 'DATE ENTITY AMOUNT DATE ENTITY AMOUNT'; put 'JAN ACME4 66 JAN ACME4 66'; put 'FEB ACME4 99 FEB ACME4 99'; put 'FEB ACME1 22'; put 'By supplying DATE in CLOSE_VARS and DATE ENTITY as the PK,'; put 'the "FEB PAG 22" record would get closed out.'; put '*/'; put ',config_table=&dclib..MPE_CONFIG'; put ',dclib=&dc_libref'; put ',outds_del=work.outds_del'; put ',outds_add=work.outds_add'; put ',outds_mod=work.outds_mod'; put ',outds_audit=0'; put ');'; put '/* when changing this macro, update the version num here */'; put '%local ver;'; put '%let ver=32;'; put '%put &sysmacroname entry vars:;'; put '%put _local_;'; put '%dc_assignlib(WRITE,&base_lib) /* may not already be assigned */'; put '/* return straight away if nothing to load */'; put '%let nobs= %mf_getattrn(&append_lib..&append_dsn,NLOBS);'; put '%if &nobs=-1 %then %do;'; put 'proc sql noprint; select count(*) into: nobs from &append_lib..&append_dsn;'; put '%end;'; put '%if &nobs=0 %then %do;'; put '%put NOTE:; %put NOTE-;%put NOTE-;%put NOTE-;'; put '%put NOTE- Base dataset &append_lib..&append_dsn is empty. Nothing to upload!;'; put '%put NOTE-;%put NOTE-;%put NOTE-;'; put '%return;'; put '%end;'; put '/* hard exit if err condition exists */'; put '%mp_abort(iftrue= (&syscc > 0)'; put ',mac=bitemporal_dataloader'; put ',msg=%str(Bitemporal transform / job aborted due to SYSCC=&SYSCC status;)'; put ')'; put '%local engine_type;'; put '%let engine_type=%mf_getengine(&base_lib);'; put '%if (&engine_type=REDSHIFT or &engine_type=POSTGRES) and %length(&CLOSE_VARS)>0'; put '%then %do;'; put '%put NOTE:; %put NOTE-;%put NOTE-;%put NOTE-;'; put '%put NOTE- CLOSE_VARS functionality not yet supported in &engine_type;'; put '%put NOTE-;%put NOTE-;%put NOTE-;'; put '%return;'; put '%end;'; put '/**'; put '* The metadata functions (eg mf_existvar) will fail if the base table has a'; put '* SAS lock. So, make a snapshot of the base table for further use.'; put '* Also, make output tables (regardless).'; put '*/'; put '%local basecopy;'; put '%let basecopy=%mf_getuniquename(prefix=basecopy);'; put 'data &basecopy &outds_mod &outds_add &outds_del;'; put 'set &base_lib..&base_dsn;'; put 'stop;'; put 'run;'; put '%mp_abort(iftrue= (&syscc > 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc after base table copy - aborting due to table lock)'; put ')'; put '%local cols idx_pk md5_col ;'; put '%let md5_col=___TMP___md5;'; put '%let check_uniqueness=%upcase(&check_uniqueness);'; put '%let RK_UPDATE_MAXKEYTABLE=%upcase(&RK_UPDATE_MAXKEYTABLE);'; put '%let high_date=%unquote(&high_date);'; put '%let loadtype=%upcase(&loadtype);'; put '/* ensure irrelevant variables are cleared */'; put '%if &loadtype=BUSTEMPORAL %then %do;'; put '%let tech_from=;'; put '%let tech_to=;'; put '%end;'; put '%else %if &loadtype=TXTEMPORAL or &loadtype=UPDATE %then %do;'; put '%let bus_from=;'; put '%let bus_to=;'; put '%end;'; put '/* ensure relevant variables are supplied */'; put '%mp_abort(iftrue=(&loadtype=BITEMPORAL & %mf_verifymacvars(bus_from bus_to)=0)'; put ',mac=bitemporal_dataloader'; put ',msg=%str(Missing BUS_FROM / BUS_TO)'; put ')'; put '%mp_abort(iftrue=(&loadtype=TXTEMPORAL & %mf_verifymacvars(tech_from tech_to)=0)'; put ',mac=bitemporal_dataloader'; put ',msg=%str(Missing TECH_FROM / TECH_TO)'; put ')'; put '/**'; put '* drop any tables (may be defined as views or vice versa preventing overwrite)'; put '*/'; put '%mp_dropmembers(append bitemp0_append bitemp_cols)'; put '/* SQL Server requires its own time values */'; put '/* 9.2 will only give picture format down to seconds. 9.3 allows'; put 'milliseconds by using lower S and defining the decimal in the format name..*/'; put 'PROC FORMAT;'; put 'picture MyMSdt other=''%0Y-%0m-%0dT%0H:%0M:%0S'' (datatype=datetime);'; put 'RUN;'; put '%local dbnow;'; put '%let dbnow="%sysfunc(datetime(),%mf_fmtdttm())"dt;'; put 'data _null_;'; put '/* convert space separated macvar to comma separated for SQL processing */'; put 'call symputx(''PK_COMMA'',tranwrd(compbl("&pk"),'' '','',''),''L'');'; put 'call symputx(''PK_CNT'',countw("&pk",'' ''),''L'');'; put 'now=&dbnow;'; put 'call symputx(''NOW'',now,''L'');'; put 'call symputx(''SQLNOW'',cats("''",put(now,MyMSdt.),"''"),''L'');'; put 'length etlsource $100;'; put 'etlsource=subpad(symget(''etlsource''),1,100);'; put 'call symputx(''etlsource'',etlsource,''l'');'; put 'run;'; put '/**'; put '* Even if no PROCESSED var provided, assume that any variable named'; put '* PROCESSED_DTTM should be updated'; put '*/'; put '%if &processed=0 %then %do;'; put '%if %mf_existvar(&basecopy,PROCESSED_DTTM)'; put '%then %let processed=PROCESSED_DTTM;'; put '%else %let processed=;'; put '%end;'; put '/* extract colnames for md5 creation / change tracking */'; put 'proc contents noprint data=&base_lib..&base_dsn'; put 'out=work.bitemp_cols (keep=name type length varnum format:);'; put 'run;'; put 'proc sql noprint;'; put 'select name into: cols separated by '','''; put 'from work.bitemp_cols'; put 'where upcase(name) not in'; put '(%upcase("&bus_from","&bus_to"'; put ',"&tech_from","&tech_to"'; put ',"&processed","&delete_col")) ;'; put 'select case when type in (2,6) then cats(''put(md5(trim('',name,'')),$hex32.)'')'; put '/* multiply by 1 to strip precision errors (eg 0 != 0) */'; put '/* but ONLY if not missing, else will lose any special missing values */'; put 'else cats(''put(md5(trim(put(ifn(missing('''; put ',name,''),'',name,'','',name,''*1),binary64.))),$hex32.)'') end'; put 'into: stripcols separated by ''||'''; put 'from work.bitemp_cols'; put 'where upcase(name) not in'; put '(%upcase("&bus_from","&bus_to"'; put ',"&tech_from","&tech_to"'; put ',"&processed","&delete_col")) ;'; put '/* set default formats*/'; put '%let bus_from_fmt = datetime19.;'; put '%let bus_to_fmt = datetime19.;'; put '%let processed_fmt = datetime19.;'; put '%let tech_from_fmt = format=datetime19.;'; put '%let tech_to_fmt = format=datetime19.;'; put '%put &=stripcols;'; put '%put &=pk;'; put 'data _null_;'; put 'set work.bitemp_cols;'; put 'if type=2 or type=6 then do;'; put 'length fmt $49.;'; put 'if format='''' then fmt=cats(''$'',length,''.'');'; put 'else fmt=cats(format,formatl,''.'');'; put 'end;'; put 'else do;'; put 'if format='''' then fmt=cats(length,''.'');'; put 'else fmt=cats(format,formatl,''.'',formatd);'; put 'end;'; put 'if upcase(name)="%upcase(&bus_from)" then'; put 'call symputx(''bus_from_fmt'',fmt,''L'');'; put 'else if upcase(name)="%upcase(&bus_to)" then'; put 'call symputx(''bus_to_fmt'',fmt,''L'');'; put 'else if upcase(name)="%upcase(&tech_from)" then'; put 'call symputx(''tech_from_fmt'',"format="!!fmt,''L'');'; put 'else if upcase(name)="%upcase(&tech_to)" then'; put 'call symputx(''tech_to_fmt'',"format="!!fmt,''L'');'; put 'else if upcase(name)="%upcase(&processed)" then'; put 'call symputx(''processed_fmt'',fmt,''L'');'; put 'run;'; put '%if %index(%quote(&cols),___TMP___) %then %do;'; put '%let msg=%str(Table contains a variable name containing "___TMP___".%trim('; put ') This may conflict with temp variable generation!!);'; put '%mp_abort(msg=&msg,mac=bitemporal_dataloader);'; put '%let syscc=5;'; put '%return;'; put '%end;'; put '/* if transaction dates appear on the APPEND table, need to remove them */'; put '%local drop_tx_dates /* used in append table */'; put 'drop_tx_dates_noobs /* used to take the base table structure */;'; put '%if %mf_existvar(&append_lib..&append_dsn, &tech_from)'; put '%then %let drop_tx_dates=&tech_from;'; put '%if %mf_existvar(&append_lib..&append_dsn, &tech_to)'; put '%then %let drop_tx_dates=&drop_tx_dates &tech_to;'; put '%if %length(%trim(&drop_tx_dates))>0'; put '%then %let drop_tx_dates=(drop=&drop_tx_dates);'; put '%if %mf_existvar(&basecopy, &tech_from)'; put '%then %let drop_tx_dates_noobs=&tech_from;'; put '%if %mf_existvar(&basecopy, &tech_to)'; put '%then %let drop_tx_dates_noobs=&drop_tx_dates_noobs &tech_to;'; put '%if %length(%trim(&drop_tx_dates_noobs))>0'; put '%then %let drop_tx_dates_noobs=(drop=&drop_tx_dates_noobs obs=0);'; put '%else %let drop_tx_dates_noobs=(obs=0);'; put '/**'; put '* Lock the table. This is necessary as we are doing a two part update (first'; put '* closing records then appending new records). It is theoretically possible'; put '* that an upload may occur whilst preparing the staging tables. And the'; put '* staging tables are about to be prepared..'; put '*/'; put '%if &LOADTARGET = YES %then %do;'; put '%put locking &base_lib..&base_dsn;'; put '%mp_lockanytable(LOCK,'; put 'lib=&base_lib,ds=&base_dsn,ref=&ETLSOURCE,ctl_ds=&dclib..mpe_lockanytable'; put ')'; put '%if "&outds_audit" ne "0" %then %do;'; put '%put locking &outds_audit;'; put '%mp_lockanytable(LOCK'; put ',lib=%scan(&outds_audit,1,.)'; put ',ds=%scan(&outds_audit,2,.)'; put ',ref=&ETLSOURCE'; put ',ctl_ds=&dclib..mpe_lockanytable'; put ')'; put '%end;'; put '%end;'; put '%else %do;'; put '/* not an actual load, so avoid updating the max key table in next step. */'; put '%let rk_update_maxkeytable=NO;'; put '%end;'; put '%if %length(&RK_UNDERLYING)>0 %then %do;'; put '%mp_retainedkey('; put 'base_lib=&base_lib'; put ',base_dsn=&base_dsn'; put ',append_lib=&append_lib'; put ',append_dsn=&append_dsn'; put ',retained_key=&pk'; put ',business_key=&rk_underlying'; put ',check_uniqueness=&CHECK_UNIQUENESS'; put ',outds=work.append'; put '%if &rk_update_maxkeytable=NO %then %do;'; put ',maxkeytable=0'; put '%end;'; put '%else %do;'; put ',maxkeytable=&dclib..&RK_MAXKEYTABLE'; put '%end;'; put ',locktable=&dclib..mpe_lockanytable'; put '%if &loadtype=BITEMPORAL or &loadtype=TXTEMPORAL %then %do;'; put ',filter_str=%str( (where=( &now < &tech_to)) )'; put '%end;'; put ')'; put '%end;'; put '%else %do;'; put 'proc sql;'; put 'create view work.append as select * from &append_lib..&append_dsn;'; put '%end;'; put '/**'; put '* generate md5 for append table'; put '*/'; put '/* it is possible the source dataset has additional (unwanted) columns.'; put 'Drop if specified; */'; put '%if %length(&keepvars)>0 %then %do;'; put '/* remove tech dates from keepvars as they are generated later */'; put '%let keepvars=%sysfunc(tranwrd(%str( &keepvars ),%str( &tech_from ),%str( )));'; put '%let keepvars=%sysfunc(tranwrd(%str( &keepvars ),%str( &tech_to ),%str( )));'; put '%let keepvars=(keep=&keepvars &bus_from &bus_to &processed &md5_col);'; put '%end;'; put '/* CAS varchar types cause append issues here, so perform autoconvert'; put 'by creating empty local table first */'; put 'data;'; put 'set &base_lib..&base_dsn &drop_tx_dates_noobs;'; put 'run;'; put '%local emptybasetable; %let emptybasetable=&syslast;'; put 'data work.bitemp0_append &keepvars &outds_del(drop=&md5_col )'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5" %then %do;'; put '/nonote2err'; put '%end;'; put ';'; put '/* apply formats for bitemporal vars but not tx dates which are added later */'; put '%if %length(&keepvars)>0 and &loadtype=BITEMPORAL %then %do;'; put 'format &bus_from &bus_from_fmt;'; put 'format &bus_to &bus_to_fmt;'; put '%end;'; put 'set &emptybasetable /* base table reqd in case append has fewer cols */'; put 'work.append &drop_tx_dates;'; put '%if %length(%str(&bus_from_override))>0 %then %do;'; put '&bus_from= %unquote(&bus_from_override) ;'; put '%end;'; put '%if %length(%str(&bus_to_override))>0 %then %do;'; put '&bus_to= %unquote(&bus_to_override) ;'; put '%end;'; put 'length &md5_col $32;'; put '&md5_col=put(md5(&stripcols),hex32.);'; put '%if %length(&processed)>0 %then %do;'; put 'format &processed &processed_fmt;'; put '&processed=&now;'; put '%end;'; put '/**'; put '* If a delete column exists then create the delete dataset'; put '*/'; put '%if %mf_existvar(&append_lib..&append_dsn, &delete_col) %then %do;'; put 'drop &delete_col;'; put 'if upcase(&delete_col) = "YES" then output &outds_del ;'; put 'else output work.bitemp0_append ;'; put 'run;'; put '%if %mf_getattrn(&outds_del,NLOBS)>0 %then %do;'; put '%bitemporal_closeouts('; put 'tech_from=&tech_from'; put ',tech_to = &tech_to'; put ',base_lib=&base_lib'; put ',base_dsn=&base_dsn'; put ',append_lib=work'; put ',append_dsn=%scan(&outds_del,-1,.)'; put ',PK=&bus_from &pk'; put ',NOW=&dbnow'; put ',loadtarget=&loadtarget'; put ',loadtype=&loadtype'; put ')'; put '%end;'; put '%end;'; put '%else %do;'; put 'output work.bitemp0_append;'; put 'run;'; put '%end;'; put '%mp_abort(iftrue= (&syscc gt 0 at line 494)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc)'; put ')'; put '%if %length(&close_vars)>0 %then %do;'; put '/**'; put '* need to close out records that are not provided'; put '*/'; put 'proc sql;'; put 'create table bitemp1_closevars1 as'; put 'select distinct a.%mf_getquotedstr(in_str=&pk,dlm=%str(,a.),quote=)'; put 'from &base_lib..&base_dsn a'; put 'inner join work.bitemp0_append b'; put 'on 1=1'; put '/* join on closevars key */'; put '%do idx_pk=1 %to %sysfunc(countw(&close_vars));'; put '%let idx_val=%scan(&close_vars,&idx_pk);'; put 'and a.&idx_val=b.&idx_val'; put '%end;'; put '/* filter base on tech dates if necessary */'; put '%if &loadtype=TXTEMPORAL %then %do;'; put 'where a.&tech_from <=&now and &now < a.&tech_to'; put '%end;'; put ';'; put 'create table bitemp1_closevars2 as'; put 'select distinct a.*'; put 'from bitemp1_closevars1 a'; put 'left join work.bitemp0_append b'; put 'on 1=1'; put '/* join on primary key */'; put '%do idx_pk=1 %to %sysfunc(countw(&pk));'; put '%let idx_val=%scan(&pk,&idx_pk);'; put 'and a.&idx_val=b.&idx_val'; put '%end;'; put '/* identify removed records by null value in a field in PK but not close_vars'; put '*/'; put 'where b.%scan('; put '%mf_wordsInStr1ButNotStr2(Str1=&pk,Str2=&close_vars),1,%str( )'; put ') IS NULL'; put ';'; put '%if %mf_getattrn(bitemp1_closevars2,NLOBS)>0 %then %do;'; put '%bitemporal_closeouts('; put 'tech_from=&tech_from'; put ',tech_to = &tech_to'; put ',base_lib=&base_lib'; put ',base_dsn=&base_dsn'; put ',append_lib=work'; put ',append_dsn=bitemp1_closevars2'; put ',PK=&bus_from &pk'; put ',NOW=&dbnow'; put ',loadtarget=&loadtarget'; put ',loadtype=&loadtype'; put ')'; put '%end;'; put '%end;'; put '/* return if nothing to load (was just deletes) */'; put '%if %mf_getattrn(work.bitemp0_append,NLOBS)=0 %then %do;'; put '%put NOTE:; %put NOTE-;%put NOTE-;%put NOTE-;'; put '%put NOTE- No updates - just deletes!;'; put '%put NOTE-;%put NOTE-;%put NOTE-;'; put '%end;'; put '/**'; put '* If applying manual overrides to business dates, then the input table MUST'; put '* be unique on the PK. Check, and if not - abort.'; put '*/'; put '%local msg;'; put '%if %length(&bus_from_override.&bus_to_override)>0 or &CHECK_UNIQUENESS=YES'; put '%then %do;'; put 'proc sort data=work.bitemp0_append out=work.bitemp0_check nodupkey;'; put 'by &pk;'; put 'run;'; put '%if %mf_getattrn(work.bitemp0_check,NLOBS)'; put 'ne %mf_getattrn(work.bitemp0_append,NLOBS)'; put '%then %do;'; put '%let msg=INPUT table &append_lib..&append_dsn is not unique on PK (&pk);'; put '%mp_lockanytable(UNLOCK,lib=&base_lib,ds=&base_dsn,ref=&ETLSOURCE (&msg),'; put 'ctl_ds=&dclib..mpe_lockanytable'; put ')'; put '%mp_lockanytable(UNLOCK'; put ',lib=%scan(&outds_audit,1,.)'; put ',ds=%scan(&outds_audit,2,.)'; put ',ref=&ETLSOURCE'; put ',ctl_ds=&dclib..mpe_lockanytable'; put ')'; put '%mp_abort(msg=&msg,mac=bitemporal_dataloader.sas);'; put '%end;'; put '%end;'; put '/**'; put '* extract from BASE table. Only want matching records, as could be very BIG.'; put '* New records are subsequently identified via left join and test for nulls.'; put '*/'; put '%local temp_table temp_table2 base_table baselib_schema;'; put '%put DCNOTE: Extracting matching observations from &base_lib..&base_dsn;'; put '%if &engine_type=OLEDB %then %do;'; put '%let temp_table=##BITEMP_&base_dsn;'; put '%if &loadtype=BITEMPORAL or &loadtype=TXTEMPORAL %then'; put '%let base_table=(select * from [dbo].&base_dsn'; put 'where convert(datetime,&SQLNOW) < &tech_to );'; put '%else %let base_table=[dbo].&base_dsn;'; put 'proc sql;'; put 'create table &base_lib.."&temp_table"n as'; put 'select * from work.bitemp0_append;'; put '/* open up a connection for pass through SQL */'; put '%dc_assignlib(WRITE,&base_lib,passthru=myAlias)'; put 'create table work.bitemp0_base as select * from connection to myAlias('; put '%end;'; put '%else %if &engine_type=REDSHIFT or &engine_type=POSTGRES %then %do;'; put '/* grab schema */'; put '%let baselib_schema=%mf_getschema(&base_lib);'; put '%if &baselib_schema.X ne X %then %let baselib_schema=&baselib_schema..;'; put '/* grab redshift config */'; put '%local redcnt; %let redcnt=0;'; put '%if &engine_type=REDSHIFT %then %do;'; put 'data _null_;'; put 'set &config_table(where=(var_scope=''DCBL_REDSH'' and var_active=1));'; put 'x+1;'; put 'call symputx(cats(''rednm'',x),var_value,''l'');'; put 'call symputx(cats(''redval'',x),var_value,''l'');'; put 'call symputx(''redcnt'',x,''l'');'; put 'run;'; put '%end;'; put '/* cannot persist temp tables so must create a temporary permanent table */'; put '%let temp_table=%mf_getuniquename(prefix=XDCTEMP);'; put '%if &loadtype=BITEMPORAL or &loadtype=TXTEMPORAL %then'; put '%let base_table=(select * from &baselib_schema.&base_dsn'; put 'where timestamp &sqlnow < &tech_to );'; put '%else %let base_table=&baselib_schema.&base_dsn;'; put '/* make empty table first - must clone & drop extra cols as autoload is bad */'; put '%dc_assignlib(WRITE,&base_lib,passthru=myAlias)'; put 'exec (create table &temp_table (like &baselib_schema.&base_dsn)) by myAlias;'; put '%if &engine_type=REDSHIFT %then %do;'; put 'exec (alter table &temp_table alter sortkey none) by myAlias;'; put '%end;'; put '%local dropcols;'; put '%let dropcols=%mf_wordsinstr1butnotstr2('; put 'str1=%upcase(%mf_getvarlist(&basecopy))'; put ',str2=%upcase(&pk)'; put ');'; put '%if %length(&dropcols>0) %then %do idx_pk=1 %to %sysfunc(countw(&dropcols));'; put '%put &=dropcols;'; put '%let idx_val=%scan(&dropcols,&idx_pk);'; put 'exec(alter table &temp_table drop column &idx_val;) by myAlias;'; put '%end;'; put 'exec (alter table &temp_table add column &md5_col varchar(32);) by myAlias;'; put '/* create view to strip formats and avoid warns in log */'; put 'data work.vw_bitemp0/view=work.vw_bitemp0;'; put 'set work.bitemp0_append(keep=&pk &md5_col);'; put 'format _all_;'; put 'run;'; put 'proc append base=&base_lib..&temp_table'; put '%if &engine_type=REDSHIFT %then %do;'; put '('; put '%do idx_pk=1 %to &redcnt;'; put '&&rednm&idx_pk = &&redval&idxpk'; put '%end;'; put ')'; put '%end;'; put 'data=work.vw_bitemp0 force nowarn;'; put 'run;'; put '/* open up a connection for pass through SQL */'; put '%dc_assignlib(WRITE,&base_lib,passthru=myAlias)'; put 'create table work.bitemp0_base as select * from connection to myAlias('; put '%end;'; put '%else %if &engine_type=CAS %then %do;'; put '%if &loadtype=BITEMPORAL or &loadtype=TXTEMPORAL %then'; put '%let base_table=&base_lib..&base_dsn'; put '(where=(&tech_from <=&now and &now < &tech_to));'; put '%else %let base_table=&base_lib..&base_dsn;'; put '%let temp_table=CASUSER.%mf_getuniquename(prefix=DC);'; put 'data &temp_table;'; put 'set work.bitemp0_append;'; put 'run;'; put '%let bitemp0base=CASUSER.%mf_getuniquename(prefix=DC);'; put 'proc fedsql sessref=dcsession;'; put 'create table &bitemp0base{options replace=true} as'; put '%end;'; put '%else %do;'; put '%let temp_table=work.bitemp0_append;'; put '%if &loadtype=BITEMPORAL or &loadtype=TXTEMPORAL %then'; put '%let base_table=&base_lib..&base_dsn'; put '(where=(&tech_from <=&now and &now < &tech_to));'; put '%else %let base_table=&base_lib..&base_dsn;'; put 'proc sql;'; put 'create table work.bitemp0_base as'; put '%end;'; put 'select a.&md5_col /* this identifies NEW records */'; put ', b.*'; put '/* assume first PK field cannot be null (if defined in a PK constraint then'; put 'it definitely cannot be null) */'; put ', case when b.%scan(&pk,1) IS NULL then 1 else 0 end as ___TMP___NEW_FLG'; put 'from &baselib_schema.&temp_table a'; put 'left join &base_table b'; put 'on 1=1'; put '%do idx_pk=1 %to &pk_cnt;'; put '%let idx_val=%scan(&pk,&idx_pk);'; put 'and a.&idx_val=b.&idx_val'; put '%end;'; put '%if &engine_type=OLEDB or &engine_type=REDSHIFT or &engine_type=POSTGRES'; put '%then %do;'; put '); proc sql; drop table &base_lib.."&temp_table"n;'; put '%end;'; put '%else %if &engine_type=CAS %then %do;'; put ';'; put 'quit;'; put 'data work.bitemp0_base;'; put 'set &bitemp0base;'; put 'run;'; put 'proc sql;'; put 'drop table &temp_table;'; put 'drop table &bitemp0base;'; put '%end;'; put '%else %do;'; put ';'; put '%end;'; put '/**'; put '* matching & changed records are those without NULL key values'; put '* &idx_val resolves to rightmost PK value (loop above)'; put '*/'; put '%put syscc (line525)=&syscc, sqlrc=&sqlrc;'; put '%mp_abort(iftrue= (&syscc gt 0 or &sqlrc>0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc sqlrc=&sqlrc)'; put ')'; put '%put hashcols2=&stripcols;'; put 'proc sql;'; put 'create table work.bitemp1_current(drop=___TMP___NEW_FLG) as'; put 'select *'; put ', put(md5(&stripcols),$hex32.) as &md5_col'; put 'from work.bitemp0_base (drop=&md5_col)'; put 'where ___TMP___NEW_FLG=0;'; put '/**'; put '* NEW records were identified in ___TMP___NEW_FLG in bitemp0_base'; put '*/'; put 'proc sql;'; put 'create table &outds_add'; put '(drop=&md5_col'; put '%if %mf_existvar(work.bitemp0_base, &delete_col) %then %do;'; put '&delete_col'; put '%end;'; put ')'; put 'as select a.*'; put '%if &loadtype=BITEMPORAL or &loadtype=TXTEMPORAL %then %do;'; put ',&now as &tech_from &tech_from_fmt'; put ',&high_date as &tech_to &tech_to_fmt'; put '%end;'; put 'from work.bitemp0_append a /* STAGING records (mix of existing & new) */'; put ', work.bitemp0_base b /* BASE records (contains null values for new) */'; put 'where a.&md5_col=b.&md5_col /* took staging md5 across in left join */'; put 'and b.___TMP___NEW_FLG=1; /* NEW records also identified in bitemp0_base */'; put '/**'; put '* identify INSERTS. These are records with the same business key but'; put '* the bus_from and bus_to value are higher / lower (respectively)'; put '* such that the existing record needs to be SPLIT to surround the new'; put '* record.'; put '* eg: OLD RECORD from=1 to=10'; put '* NEW RECORD from=5 to=7'; put '*'; put '* APPENDED RECORDS:'; put '* - from=1 to=5'; put '* - from=5 to=7'; put '* - from=7 to=10'; put '*/'; put '/* inserts cannot happen with TXTEMPORAL */'; put '%if &loadtype=BITEMPORAL or &loadtype=BUSTEMPORAL %then %do;'; put '/* IDENTIFY */'; put 'create table work.bitemp3_inserts as'; put 'select b.*'; put ',a.&bus_from as ___TMP___from'; put ',a.&bus_to as ___TMP___to'; put 'from work.bitemp0_append a'; put ',work.bitemp1_current b'; put 'where a.&bus_from > b.&bus_from'; put 'and a.&bus_to < b.&bus_to'; put '%do idx_pk=1 %to &pk_cnt;'; put '%let idx_val=%scan(&pk,&idx_pk);'; put 'and a.&idx_val=b.&idx_val'; put '%end;'; put 'order by'; put '/* compress blanks and then insert commas (as the datetime fields may'; put 'not be in use) */'; put '%sysfunc(tranwrd(%sysfunc(compbl('; put '&pk &bus_from &bus_to &processed'; put ')),%str( ), %str(,)))'; put ';'; put '/* SPLIT */'; put 'data work.bitemp3a_inserts (drop=___TMP___from ___TMP___retain ___TMP___to) ;'; put 'set work.bitemp3_inserts;'; put 'by &pk &bus_from &bus_to &processed;'; put 'if first.&idx_val then do;'; put '___TMP___retain=&bus_to;'; put '&bus_to=___TMP___from;'; put 'output;'; put '&bus_to=___TMP___retain;'; put 'end;'; put 'if last.&idx_val then do;'; put '&bus_from=___TMP___to;'; put 'output;'; put 'end;'; put 'run;'; put '%end;'; put '%else %do;'; put '/* TX temporal load */'; put 'data work.bitemp3a_inserts;'; put 'set work.bitemp1_current;'; put 'stop;'; put 'run;'; put '%end;'; put '/* APPEND */'; put 'proc sql;'; put 'create view work.bitemp3a_view as'; put 'select * from work.bitemp1_current'; put 'where &md5_col not in (select &md5_col from work.bitemp3a_inserts);'; put 'data bitemp3b_newbase;'; put 'set work.bitemp3a_inserts work.bitemp3a_view;'; put 'run;'; put '/** do not use! this converts short numerics into 8 bytes'; put 'proc sql;'; put 'create table work.bitemp3b_newbase as'; put 'select * from work.bitemp3a_inserts'; put 'union corr'; put 'select * from work.bitemp1_current'; put 'where &md5_col not in (select &md5_col from work.bitemp3a_inserts);'; put '*/'; put '/**'; put '* identify CHANGED records from staging.'; put '* Same business key with different temporal dates or md5 value'; put '* This table must be overlayed onto / into existing business history'; put '*/'; put 'proc sql;'; put 'create table work.bitemp4_updated as select distinct a.*'; put 'from work.bitemp0_append a'; put ',work.bitemp3b_newbase b'; put 'where 1=1'; put '%do idx_pk=1 %to &pk_cnt;'; put '%let idx_val=%scan(&pk,&idx_pk);'; put 'and a.&idx_val=b.&idx_val'; put '%end;'; put 'and ( a.&md5_col ne b.&md5_col'; put '%if &loadtype=BITEMPORAL or &loadtype=BUSTEMPORAL %then %do;'; put 'OR (a.&bus_from ne b.&bus_from or a.&bus_to ne b.&bus_to)'; put '%end;'; put ')'; put ';'; put '/**'; put '* This section would have been one simple step with union all'; put '* but that converts short numerics into 8 bytes!'; put '* so, convoluted alternative to retain the same functionality.'; put '*/'; put '/* base records */'; put 'create view work.bitemp4_prep1 as'; put 'select ''BASE'' as ___TMP___'; put ',b.*'; put 'from work.bitemp4_updated a'; put ',work.bitemp3b_newbase b'; put 'where 1'; put '%do idx_pk=1 %to &pk_cnt;'; put '%let idx_val=%scan(&pk,&idx_pk);'; put 'and a.&idx_val=b.&idx_val'; put '%end;'; put ';'; put '/* updated records */'; put 'create view work.bitemp4_prep2 as'; put 'select ''STAG'' as ___TMP___ ,*'; put 'from work.bitemp4_updated;'; put '/* ensure we only keep columns that appear in both */'; put '%local bp1 bp2 bp3 bp4;'; put '%let bp1=%mf_getvarlist(bitemp4_prep1);'; put '%let bp2=%mf_getvarlist(bitemp4_prep2);'; put '%let bp3=%mf_wordsInStr1ButNotStr2(Str1=&bp1,Str2=&bp2);'; put '%let bp4=%mf_wordsInStr1ButNotStr2(Str1=&bp2,Str2=&bp1);'; put 'data work.bitemp4_prep3/view=bitemp4_prep3;'; put 'set bitemp4_prep1 bitemp4_prep2;'; put '%if %length(XX&bp3&bp4)>2 %then %do;'; put 'drop &bp3 &bp4 ;'; put '%end;'; put 'run;'; put '/* remove duplicates */'; put 'proc sql;'; put 'create table work.bitemp4a_allrecs as'; put 'select distinct *'; put 'from work.bitemp4_prep3'; put 'order by'; put '/* compress blanks and then insert commas (as the datetime fields'; put 'may not be in use) */'; put '%sysfunc(tranwrd(%sysfunc(compbl('; put '&pk &bus_from &bus_to &processed'; put ')),%str( ), %str(,)))'; put ';'; put '%if &loadtype=BITEMPORAL or &loadtype=BUSTEMPORAL %then %do;'; put '/* this section aligns the business dates'; put '(eg for inserts or overlaps in the range) */'; put 'data work.bitemp4b_firstpass (drop=___TMP___cond ___TMP___from ___TMP___to );'; put 'set work.bitemp4a_allrecs;'; put 'by &pk &bus_from &bus_to &processed;'; put 'retain ___TMP___cond ''Name of Condition'';'; put 'retain ___TMP___from ___TMP___to 0;'; put '___TMP___md5lag=lag(&md5_col);'; put '/* reset retained variables */'; put 'if first.&idx_val then do;'; put 'call missing (___TMP___cond, ___TMP___from, ___TMP___to,___TMP___md5lag);'; put 'end;'; put 'else do;'; put '/* if record is identical, carry forward bus_from (and bus_to if higher)*/'; put 'if &md5_col=___TMP___md5lag then do;'; put '&bus_from=___TMP___from;'; put 'if &bus_to<___TMP___to then &bus_to=___TMP___to;'; put 'end;'; put 'end;'; put 'if ___TMP___=''STAG'' then do;'; put '/* need to carry forward the closing record */'; put '___TMP___cond=''Condition 1'';'; put 'end;'; put 'else if ___TMP___cond=''Condition 1'' then do;'; put '/* else ensure bus_from starts from prior record bus_to */'; put 'if &md5_col ne ___TMP___md5lag and &bus_from <= ___TMP___to'; put 'then &bus_from= ___TMP___to;'; put '/* new record may replace old record entirely */'; put 'if &bus_to <= &bus_from then delete;'; put 'else call missing (___TMP___cond, ___TMP___from, ___TMP___to);'; put 'end;'; put '___TMP___from=&bus_from;'; put '___TMP___to=&bus_to;'; put 'run;'; put '%end;'; put '%else %do;'; put '/* keep staged records only */'; put 'data work.bitemp4b_firstpass;'; put 'set work.bitemp4a_allrecs;'; put 'if ___TMP___=''STAG'';'; put 'run;'; put '%end;'; put '/* next phase is to pass through in reverse - so set up the sort statement */'; put '%local byvar;'; put '%do idx_pk=1 %to &pk_cnt;'; put '%let byvar=&byvar descending %scan(&pk,&idx_pk);'; put '%end;'; put '%if &loadtype=BITEMPORAL or &loadtype=BUSTEMPORAL'; put '%then %let byvar=&byvar descending &bus_from descending &bus_to;'; put '/* if matching bus dates supplied, need to ensure we also have a sort'; put 'between BASE and STAGING tables */'; put '%let byvar=&byvar descending ___TMP___;'; put 'proc sort data=work.bitemp4b_firstpass out=work.bitemp4c_sort ;'; put 'by &byvar;'; put 'run;'; put '/**'; put '* Now (in reverse) pass back business start dates'; put '*/'; put 'data work.bitemp4d_secondpass;'; put '%if &loadtype=BITEMPORAL or &loadtype=TXTEMPORAL %then %do;'; put '&tech_from=&now;'; put '&tech_to=&high_date;'; put '%end;'; put 'set work.bitemp4c_sort ;'; put 'by &byvar;'; put 'retain ___TMP___cond ''Name of Condition'';'; put 'retain ___TMP___from ___TMP___to 0;'; put '%if &loadtype=BITEMPORAL or &loadtype=BUSTEMPORAL %then %do;'; put '/* put / _all_ /;*/'; put '___TMP___md5lag=lag(&md5_col);'; put 'if first.&idx_val then do;'; put '/* reset retained variables */'; put 'call missing (___TMP___cond,___TMP___from,___TMP___to,___TMP___md5lag);'; put 'end;'; put 'else do;'; put '/* if record is identical, carry back bus_to */'; put 'if &md5_col=___TMP___md5lag then &bus_to=___TMP___to;'; put 'end;'; put 'if ___TMP___=''STAG'' then do;'; put '/* need to carry forward the closing record */'; put '___TMP___cond=''Condition 2'';'; put 'end;'; put 'else if ___TMP___cond=''Condition 2'' then do;'; put '/* else ensure bus_to stops at subsequent record bus_from */'; put 'if &md5_col ne ___TMP___md5lag and &bus_to >= ___TMP___from'; put 'then &bus_to= ___TMP___from;'; put '/* new record may replace old record entirely */'; put 'if &bus_from >= &bus_to then delete;'; put 'if &bus_from=___TMP___from and &bus_to=___TMP___to then delete;'; put 'else call missing (___TMP___cond, ___TMP___from, ___TMP___to);'; put 'end;'; put '___TMP___from=&bus_from;'; put '___TMP___to=&bus_to;'; put '%end;'; put 'run;'; put '%put syscc (line600)=&syscc;'; put '/**'; put 'There may still be some records (eg old business history) which have not'; put 'changed.'; put 'Need to identify these and remove from the append so they are not updated'; put 'unnecessarily. This is done by generating a new md5 (which INCLUDES the'; put 'business key) and any matching / identical records are split out (from those'; put 'that need to be updated).'; put '*/'; put '%if &loadtype=BITEMPORAL %then %do;'; put '%let cat_string=catx(''|'' ,&bus_from,&bus_to);'; put 'data bitemp5a_lkp (keep=&md5_col);'; put 'set bitemp0_base;'; put '/* for BITEMPORAL we need to compare business dates also */'; put '&md5_col=put(md5(&cat_string!!''|''!!&stripcols),$hex32.);'; put 'run;'; put 'data bitemp5b_updates;'; put 'set bitemp4d_secondpass;'; put 'if _n_=1 then do;'; put 'dcl hash md5_lkp(dataset:''bitemp5a_lkp'');'; put 'md5_lkp.definekey("&md5_col");'; put 'md5_lkp.definedone();'; put 'end;'; put '/* drop old md5 col as will rebuild with new business dates */'; put '&md5_col=put(md5(&cat_string!!''|''!!&stripcols),$hex32.) ;'; put 'if md5_lkp.check()=0 then delete;'; put 'run;'; put 'proc sql;'; put '/* get min bus from as will update (close out) all records from this point'; put '(for that PK)*/'; put 'create table work.bitemp5d_subquery as'; put 'select &pk_comma, min(&bus_from)as &bus_from, max(&bus_to) as &bus_to'; put 'from work.bitemp5b_updates'; put 'group by &pk_comma;'; put '/* index has a huge efficiency impact on upcoming nested subquery */'; put 'create index index1 on work.bitemp5d_subquery(&pk_comma,&bus_from, &bus_to);'; put '%let lastds=work.bitemp5b_updates;'; put '%end;'; put '%else %if &loadtype=TXTEMPORAL or &loadtype=UPDATE %then %do;'; put 'proc sql;'; put 'create table work.bitemp5d_subquery as'; put 'select distinct &pk_comma'; put 'from bitemp4d_secondpass;'; put '%let lastds=work.bitemp4d_secondpass;'; put '%end;'; put '%else %let lastds=work.bitemp4d_secondpass;'; put '/* create single append table (an overlapped pre-sert may be classed as'; put 'both an update AND a new record). Also create temp views that may be'; put 'used for pre-load analysis. */'; put 'data &outds_mod;'; put 'set &lastds(drop=___TMP___: &md5_col);'; put 'run;'; put 'data bitemp6_allrecs / view=bitemp6_allrecs;'; put 'set &outds_mod /* UPDATED records */'; put '&outds_add /* NEW records */;'; put 'run;'; put 'proc sort data=work.bitemp6_allrecs'; put 'out=work.bitemp6_unique'; put 'noduprec'; put 'dupout=work.xx_BADBADBAD;'; put 'by _all_;'; put 'run;'; put '/* we have all our temp tables now so exit if this is all that is needed */'; put '%if &LOADTARGET ne YES %then %return;'; put '/* also exit if an err condition exists */'; put '%if &syscc>0 %then %do;'; put '%put syscc=&syscc;'; put '%mp_lockanytable(UNLOCK,lib=&base_lib,ds=&base_dsn,ref=&ETLSOURCE,'; put 'ctl_ds=&dclib..mpe_lockanytable'; put ')'; put '%if "&outds_audit" ne "0" %then %do;'; put '%mp_lockanytable(UNLOCK'; put ',lib=%scan(&outds_audit,1,.)'; put ',ds=%scan(&outds_audit,2,.)'; put ',ref=&ETLSOURCE'; put ',ctl_ds=&dclib..mpe_lockanytable'; put ')'; put '%end;'; put '%end;'; put '%mp_abort(iftrue= (&syscc>0)'; put ',mac=&sysmacroname in &_program'; put ',msg=%str(Bitemporal transform / job aborted due to SYSCC=&SYSCC status)'; put ')'; put '/* final check - abort if a lock has appeared on the target or audit table */'; put '%mp_lockfilecheck(libds=&base_lib..&base_dsn)'; put '%if %mf_existds(&outds_audit) %then %do;'; put '%mp_lockfilecheck(libds=&outds_audit)'; put '%end;'; put '/**'; put '* STAGING TABLES PREPARED, ERR CONDITION TESTED FOR.. NOW TO LOAD!!'; put '*/'; put '/**'; put '* First, CLOSE OUT changed records (if not a REPLACE)'; put '* Note that SAS does not support ANSI standard for UPDATE with a join condition.'; put '* However - this can be worked around using a nested subquery..'; put '*/'; put 'data _null_;'; put 'putlog "&sysmacroname: CLOSEOUTS commencing";'; put 'run;'; put '%if %mf_getattrn(&lastds,NLOBS)=0 %then %do;'; put 'data _null_;'; put 'putlog "&sysmacroname: No closeouts needed";'; put 'run;'; put '%end;'; put '%else %if &engine_type=CAS %then %do;'; put '%mp_abort(iftrue= (&loadtype=BITEMPORAL or &loadtype=TXTEMPORAL)'; put ',mac=&sysmacroname in &_program'; put ',msg=%str(&loadtype not yet supported in CAS engine)'; put ')'; put '/* create temp table for deletions */'; put '%local delds;%let delds=%mf_getuniquename(prefix=DC);'; put 'data casuser.&delds;'; put 'set work.bitemp5d_subquery;'; put 'run;'; put '/* delete the records */'; put 'proc cas ;'; put 'table.deleteRows / table={'; put 'caslib="&base_lib",'; put 'name="&base_dsn",'; put 'where="1=1",'; put 'whereTable={caslib=''CASUSER'',name="&delds"}'; put '};'; put 'quit;'; put '/* drop temp table */'; put 'proc sql;'; put 'drop table CASUSER.&delds;'; put '%end;'; put '%else %if (&loadtype=BITEMPORAL or &loadtype=TXTEMPORAL or &loadtype=UPDATE)'; put '%then %do;'; put 'data _null_;'; put 'putlog "&sysmacroname: &loadtype operation using &engine_type engine";'; put 'run;'; put '%local flexinow;'; put 'proc sql;'; put '/* if OLEDB then create a temp table for efficiency */'; put '%local innertable;'; put '%if &engine_type=OLEDB %then %do;'; put '%let innertable=[##BITEMP_&base_dsn];'; put '%let top_table=[dbo].&base_dsn;'; put '%let flexinow=&SQLNOW;'; put 'create table &base_lib.."##BITEMP_&base_dsn"n as'; put 'select * from work.bitemp5d_subquery;'; put '/* open up a connection for pass through SQL */'; put '%dc_assignlib(WRITE,&base_lib,passthru=myAlias)'; put 'execute('; put '%end;'; put '%else %if &engine_type=REDSHIFT or &engine_type=POSTGRES %then %do;'; put '%let innertable=%mf_getuniquename(prefix=XDCTEMP);'; put '%let top_table=&baselib_schema.&base_dsn;'; put '%let flexinow=timestamp &SQLNOW;'; put '/* make empty table first - must clone & drop extra cols'; put 'as autoload is bad */'; put '%dc_assignlib(WRITE,&base_lib,passthru=myAlias)'; put 'exec (create table &innertable (like &baselib_schema.&base_dsn)) by myAlias;'; put '%if &engine_type=REDSHIFT %then %do;'; put 'exec (alter table &innertable alter sortkey none) by myAlias;'; put '%end;'; put '%let dropcols=%mf_wordsinstr1butnotstr2('; put 'str1=%upcase(%mf_getvarlist(&basecopy))'; put ',str2=%upcase(%mf_getvarlist(work.bitemp5d_subquery))'; put ');'; put '%if %length(&dropcols>0) %then %do idx_pk=1 %to %sysfunc(countw(&dropcols));'; put '%put &=dropcols;'; put '%let idx_val=%scan(&dropcols,&idx_pk);'; put 'exec(alter table &innertable drop column &idx_val;) by myAlias;;'; put '%end;'; put '/* create view to strip formats and avoid warns in log */'; put 'data work.vw_bitemp5d/view=work.vw_bitemp5d;'; put 'set work.bitemp5d_subquery;'; put 'format _all_;'; put 'run;'; put 'proc append base=&base_lib..&innertable ('; put '%do idx_pk=1 %to &redcnt;'; put '&&rednm&idx_pk = &&redval&idxpk'; put '%end;'; put ')'; put 'data=work.vw_bitemp5d force nowarn;'; put 'run;'; put '/* open up a connection for pass through SQL */'; put '%dc_assignlib(WRITE,&base_lib,passthru=myAlias)'; put 'execute('; put '%end;'; put '%else %do;'; put '%let innertable=bitemp5d_subquery;'; put '%let top_table=&base_lib..&base_dsn;'; put '%let flexinow=&now;'; put '%end;'; put '%if &loadtype=BITEMPORAL or &loadtype=TXTEMPORAL %then %do;'; put 'update &top_table set &tech_to=&flexinow'; put '%if %length(&processed)>0 %then %do;'; put ',&processed=&flexinow'; put '%end;'; put 'where &tech_from <= &flexinow and &flexinow < &tech_to and'; put '%end;'; put '%else %if &loadtype=UPDATE %then %do;'; put '/* changed records are deleted then re-appended when doing UPDATEs */'; put 'delete from &top_table where'; put '%end;'; put '%else %do;'; put '%put %str(ERR)OR: BUSTEMPORAL NOT YET SUPPORTED;'; put '%let syscc=5;'; put '%mp_lockanytable(UNLOCK,lib=&base_lib,ds=&base_dsn,ref=&ETLSOURCE,'; put 'ctl_ds=&dclib..mpe_lockanytable'; put ')'; put '%mp_lockanytable(UNLOCK'; put ',lib=%scan(&outds_audit,1,.)'; put ',ds=%scan(&outds_audit,2,.)'; put ',ref=&ETLSOURCE'; put ',ctl_ds=&dclib..mpe_lockanytable'; put ')'; put '%goto end_of_macro;'; put '%end;'; put '/* perform join inside query as per'; put 'http://stackoverflow.com/questions/24629793/update-with-a-proc-sql */'; put 'exists( select 1 from &baselib_schema.&innertable where'; put '/* loop PK join */'; put '%do idx_pk=1 %to &pk_cnt;'; put '%let idx_val=%scan(&pk,&idx_pk);'; put '&base_dsn..&idx_val=&innertable..&idx_val and'; put '%end;'; put '%if &loadtype=BITEMPORAL %then %do;'; put '&base_dsn..&bus_from >= &innertable..&bus_from'; put 'and &base_dsn..&bus_to <= &innertable..&bus_to and'; put '%end;'; put '/* close the statement */'; put '1=1);'; put '%if &engine_type=OLEDB or &engine_type=REDSHIFT or &engine_type=POSTGRES'; put '%then %do;'; put ') by myAlias;'; put 'execute (drop table &baselib_schema.&innertable) by myAlias;'; put '%end;'; put '%end;'; put 'quit;'; put 'data _null_;'; put 'putlog "&sysmacroname: Closeout complete";'; put 'run;'; put '/**'; put '* Append the new / updated records'; put '*/'; put '%if &engine_type=CAS %then %do;'; put '/* get varchar variables ready for casting */'; put '%local vcfmt vcrename vcassign vcdrop;'; put 'data _null_;'; put 'set work.bitemp_cols(where=(type=6)) end=last;'; put 'length vcrename vcassign vcdrop vcfmt $32767 rancol $32;'; put 'retain vcrename vcassign vcdrop vcfmt;'; put 'if _n_=1 then vcrename=''(rename=('';'; put 'rancol=resolve(''%mf_getuniquename()'');'; put 'vcfmt=trim(vcfmt)!!''length ''!!cats(name)!!'' varchar(*);'';'; put 'vcrename=trim(vcrename)!!'' ''!!cats(name,''='',rancol);'; put 'vcassign=cats(vcassign,name,''='',rancol,'';'');'; put 'vcdrop=cats(vcdrop,''drop ''!!rancol,'';'');'; put 'if last then do;'; put 'vcrename=cats(vcrename,''))'');'; put 'call symputx(''vcfmt'',vcfmt);'; put 'call symputx(''vcrename'',vcrename);'; put 'call symputx(''vcassign'',vcassign);'; put 'call symputx(''vcdrop'',vcdrop);'; put 'end;'; put 'run;'; put '/* prepare a temp cas table with varchars casted */'; put '%let tmp=%mf_getuniquename();'; put 'data casuser.&tmp ;'; put '&vcfmt'; put 'set work.bitemp6_unique &vcrename;'; put '&vcassign'; put '&vcdrop'; put 'run;'; put '/* load the table with varchars applied*/'; put 'data &base_lib..&base_dsn (append=yes )/sessref=dcsession ;'; put 'set casuser.&tmp;'; put 'run;'; put '/* drop temp table */'; put 'proc sql;'; put 'drop table CASUSER.&tmp;'; put '/* this code will not work as regular tables do not have varchars */'; put '/*'; put 'proc casutil;'; put 'load data=work.bitemp6_unique'; put 'outcaslib="&base_lib" casout="&base_dsn" append ;'; put 'quit;'; put '*/'; put '%end;'; put '%else %if &engine_type=REDSHIFT or &engine_type=POSTGRES %then %do;'; put 'proc append base=&base_lib..&base_dsn'; put '%if &engine_type=REDSHIFT %then %do;'; put '('; put '%do idx_pk=1 %to &redcnt;'; put '&&rednm&idx_pk = &&redval&idxpk'; put '%end;'; put ')'; put '%end;'; put 'data=bitemp6_unique force nowarn;'; put 'run;'; put '%end;'; put '%else %do;'; put 'proc append base=&base_lib..&base_dsn data=bitemp6_unique force nowarn; run;'; put '%end;'; put '%mp_lockanytable(UNLOCK,lib=&base_lib,ds=&base_dsn,ref=&ETLSOURCE,'; put 'ctl_ds=&dclib..mpe_lockanytable'; put ')'; put '/* final check on syscc */'; put '%mp_abort(iftrue= (&syscc >4)'; put ',mac=&_program'; put ',msg=%str(!!Upload NOT successful!! Failed on actual update / append stage..)'; put ')'; put '%if &outds_audit ne 0 and &LOADTARGET=YES %then %do;'; put 'data work.vw_outds_orig /view=work.vw_outds_orig;'; put 'set work.bitemp0_base (drop=&md5_col);'; put 'where ___TMP___NEW_FLG=0;'; put 'drop ___TMP___NEW_FLG;'; put 'run;'; put '/* update the AUDIT table */'; put '%if %mf_existds(&outds_audit) %then %do;'; put 'options mprint;'; put '%mp_storediffs(&base_lib..&base_dsn'; put ',work.vw_outds_orig'; put ',&pk &bus_from'; put ',delds=&outds_del'; put ',modds=&outds_mod'; put ',appds=&outds_add'; put ',outds=work.mp_storediffs'; put ',processed_dttm=&now'; put ',loadref=%superq(etlsource)'; put ')'; put '/* exclude unchanged values in modified rows */'; put 'data work.mp_storediffs;'; put 'set work.mp_storediffs;'; put 'if MOVE_TYPE="M" and IS_PK=0 and IS_DIFF=0 then delete;'; put '* putlog load_ref= libref= dsn= key_hash= tgtvar_nm=;'; put 'run;'; put 'proc append base=&outds_audit data=work.mp_storediffs;'; put 'run;'; put '%mp_lockanytable(UNLOCK'; put ',lib=%scan(&outds_audit,1,.)'; put ',ds=%scan(&outds_audit,2,.)'; put ',ref=&ETLSOURCE'; put ',ctl_ds=&dclib..mpe_lockanytable'; put ')'; put '%end;'; put '%end;'; put '%mp_abort(iftrue= (&syscc >4)'; put ',mac=bitemporal_dataloader'; put ',msg=%str(Problem in audit stage (&outds_audit))'; put ')'; put '%let user=%mf_getUser();'; put '/**'; put 'Notify as appropriate EMAILS DISABLED'; put '%sumo_alerts(ALERT_EVENT=UPDATE'; put ', ALERT_TARGET=&base_lib..&base_dsn'; put ', from_user= &user);'; put '*/'; put '/* monitor BiTemporal usage */'; put '%if &log=1 %then %do;'; put '%put syscc=&syscc;'; put '/* do not perform duration calc in pass through */'; put '%local dur;'; put 'data _null_;'; put 'now=symget(''now'');'; put 'dur=%sysfunc(datetime())-&now;'; put 'call symputx(''dur'',dur,''l'');'; put 'run;'; put 'proc sql;'; put 'insert into &dclib..mpe_dataloads'; put 'set libref=%upcase("&base_lib")'; put ',DSN=%upcase("&base_dsn")'; put ',ETLSOURCE="&ETLSOURCE"'; put ',LOADTYPE="&loadtype"'; put ',CHANGED_RECORDS=%mf_getattrn(&lastds,NLOBS)'; put ',NEW_RECORDS=%mf_getattrn(&outds_add,NLOBS)'; put ',DELETED_RECORDS=%mf_getattrn(&outds_del,NLOBS)'; put ',DURATION=&dur'; put ',MAC_VER="v&ver"'; put ',user_nm="&user"'; put ',PROCESSED_DTTM=&now;'; put 'quit;'; put '%put syscc=&syscc;'; put '%end;'; put '%end_of_macro:'; put '%mend bitemporal_dataloader;'; put '%macro mm_getlibs('; put 'outds=work.mm_getLibs'; put ')/*/STORE SOURCE*/;'; put '/*'; put 'flags:'; put 'OMI_SUCCINCT (2048) Do not return attributes with null values.'; put 'OMI_GET_METADATA (256) Executes a GetMetadata call for each object that'; put 'is returned by the GetMetadataObjects method.'; put 'OMI_ALL_SIMPLE (8) Gets all of the attributes of the requested object.'; put '*/'; put 'data _null_;'; put 'flags=2048+256+8;'; put 'call symputx(''flags'',flags,''l'');'; put 'run;'; put '* use a temporary fileref to hold the response;'; put 'filename response temp;'; put '/* get list of libraries */'; put 'proc metadata in='; put ''''; put '$METAREPOSITORY'; put 'SASLibrary'; put ''; put 'SAS'; put '&flags'; put ''; put ''''; put 'out=response;'; put 'run;'; put '/* write the response to the log for debugging */'; put 'data _null_;'; put 'infile response lrecl=32767;'; put 'input;'; put 'put _infile_;'; put 'run;'; put '/* create an XML map to read the response */'; put 'filename sxlemap temp;'; put 'data _null_;'; put 'file sxlemap;'; put 'put '''';'; put 'put '''';'; put 'put ''//Objects/SASLibrary'';'; put 'put ''>17'';'; put 'put ''//Objects/SASLibrary/@Id'';'; put 'put ''256>'';'; put 'put ''//Objects/SASLibrary/@Name'';'; put 'put ''8'';'; put 'put ''//Objects/SASLibrary/@Libref'';'; put 'put ''>12'';'; put 'put ''//Objects/SASLibrary/@Engine'';'; put 'put ''
'';'; put 'run;'; put 'libname _XML_ xml xmlfileref=response xmlmap=sxlemap;'; put '/* sort the response by library name */'; put 'proc sort data=_XML_.saslibrary out=&outds;'; put 'by libraryname;'; put 'run;'; put '/* clear references */'; put 'filename sxlemap clear;'; put 'filename response clear;'; put 'libname _XML_ clear;'; put '%mend mm_getlibs;'; put '%macro dc_getlibs(outds=mm_getlibs);'; put '/* take current repository */'; put '%local repo repocnt x;'; put '%let repo=%sysfunc(getoption(metarepository));'; put '/* get list of repositories and filter */'; put '%mm_getrepos(outds=repos)'; put '%let repocnt=0;'; put 'data repos;'; put 'set repos;'; put 'where repositorytype in(''CUSTOM'',''FOUNDATION'')'; put '& upcase(name) ne "%upcase(&repo)";'; put 'keep id name ;'; put 'call symputx(''repo''!!cats(_n_),name,''l'');'; put 'call symputx(''repocnt'',_n_,''l'');'; put 'run;'; put '%put _local_;'; put '%mm_getlibs(outds=&outds)'; put '%do x=1 %to &repocnt;'; put 'options metarepository=&&repo&x;'; put '%mm_getlibs(outds=&outds.a)'; put 'proc append base=&outds data=&outds.a;'; put 'run;'; put '%end;'; put 'options metarepository=&repo;'; put '%mend dc_getlibs;'; put '%macro mpe_refreshlibs(lib=0);'; put '%dc_getlibs(outds=work.mm_getLibs)'; put 'proc sort data=mm_getlibs;'; put 'by libraryref libraryname;'; put 'run;'; put 'data libs0;'; put 'set mm_getlibs;'; put 'by libraryref;'; put '%if &lib ne 0 %then %do;'; put 'where upcase(libraryref)="%upcase(&lib)";'; put '%end;'; put 'if "%mf_getplatform()"="SASMETA" then do;'; put '/* note - invalid libraries can result in exception errors. If this happens,'; put 'configure the dc_viewlib_check variable to NO in Data Controller Settings */'; put 'rc=libname(libraryref,,''meta'',cats(''library="'',libraryname,''";''));'; put 'drop rc;'; put 'if rc ne 0 then do;'; put 'putlog "NOTE: Library " libraryname " does not exist!!";'; put 'putlog (_all_) (=);'; put 'delete;'; put 'end;'; put 'end;'; put 'if not first.libraryref then delete;'; put 'run;'; put 'proc sql;'; put 'create table libs1 as'; put 'select distinct libname'; put ',engine'; put ',path'; put ',level'; put ',sysname'; put ',sysvalue'; put 'from dictionary.libnames'; put 'order by libname, level,engine,path;'; put 'data libs2;'; put 'set libs1;'; put 'length tran $1024;'; put 'if missing(sysname) then sysname=''Missing'';'; put 'select(sysname);'; put 'when(''Access Permission'') tran=''Permissions'';'; put 'when(''Owner Name'') tran=''Owner'';'; put 'when(''Schema/Owner'') tran=''schema'';'; put 'otherwise tran=sysname;'; put 'end;'; put 'run;'; put 'proc transpose data=libs2 out=libs3;'; put 'by libname level engine path;'; put 'var sysvalue;'; put 'id tran;'; put 'run;'; put 'data libs4(rename=(libname=libref));'; put 'length paths $8192 perms owners schemas $500 permissions owner schema $1024;'; put 'if _n_=1 then call missing (of _all_);'; put 'set libs3;'; put 'by libname;'; put 'if engine=''V9'' then engine=''BASE'';'; put 'if first.libname then do;'; put 'retain paths perms owners schemas;'; put 'paths=''(''!!quote(trim(path));'; put 'perms=permissions;'; put 'owners=owner;'; put 'schemas=schema;'; put 'end;'; put 'else do;'; put 'paths=trim(paths)!!'' ''!!quote(trim(path));'; put 'perms=trim(perms)!!'',''!!trim(permissions);'; put 'owners=trim(owners)!!'',''!!trim(owner);'; put 'schemas=trim(schemas)!!'' ''!!trim(schema);'; put 'end;'; put 'if last.libname then do;'; put 'paths=trim(paths)!!'')'';'; put 'schemas=cats(schemas);'; put 'output;'; put 'end;'; put 'keep libname engine paths perms owners schemas;'; put 'run;'; put 'proc sql;'; put 'create table libs5 as'; put 'select a.libref'; put ',coalescec(b.engine,a.engine) as engine length=32'; put ',b.libraryname as libname'; put ',a.paths'; put ',a.perms'; put ',a.owners'; put ',a.schemas'; put ',b.libraryid as libid'; put 'from libs4 a'; put 'left join libs0 b'; put 'on upcase(a.libref)=upcase(b.libraryref)'; put 'where libref not in (''SASWORK'',''WORK'',''SASUSER'',''CASUSER'',''TEMP'',''STPSAMP'''; put ',''MAPSGFK'');'; put '%bitemporal_dataloader(base_lib=&dc_libref'; put ',base_dsn=MPE_DATACATALOG_LIBS'; put ',append_dsn=libs5'; put ',PK=LIBREF'; put ',etlsource=&_program'; put ',loadtype=TXTEMPORAL'; put ',tech_from=TX_FROM'; put ',tech_to=TX_TO'; put ',dclib=&dc_libref'; put ')'; put '%mend mpe_refreshlibs;'; put '* SAS Macros end;'; put '* SAS Includes start;'; put '* SAS Includes end;'; put '* Binary Files start;'; put '* Binary Files end;'; put '* ServiceInit start;'; put 'options noquotelenmax ps=max;'; put '/* create dummy macros to prevent stpbegin / stpend from corrupting sessions */'; put '%macro stpbegin();'; put '%put NOTE: the STPBEGIN macro should not be used for web apps!;'; put '%mend stpbegin;'; put '%macro stpend();'; put '%put NOTE: the STPEND macro should not be used for web apps!;'; put '%mend stpend;'; put '* ServiceInit end;'; put '* Service start;'; put '/**'; put '@file refreshlibs.sas'; put '@brief Refreshes the library data catalog'; put '@details'; put '

SAS Macros

'; put '@li mpeinit.sas'; put '@li mpe_refreshlibs.sas'; put '@version 9.3'; put '@author 4GL Apps Ltd'; put '@copyright 4GL Apps Ltd. This code may only be used within Data Controller'; put 'and may not be re-distributed or re-sold without the express permission of'; put '4GL Apps Ltd.'; put '**/'; put '%mpeinit()'; put '%mpe_refreshlibs()'; put '* Service end;'; run; %mm_createwebservice(path=&appLoc/&path, name=&service, code=sascode, server=&serverName, replace=yes) filename sascode clear; %let service=refreshtablelineage; filename sascode temp lrecl=32767; data _null_; file sascode; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '/**'; put '@file mp_jsonout.sas'; put '@brief Writes JSON in SASjs format to a fileref'; put '@details This macro can be used to OPEN a JSON stream and send one or more'; put 'tables as arrays of rows, where each row can be an object or a nested array.'; put 'There are two engines available - DATASTEP or PROCJSON.'; put 'PROC JSON is fast but will produce errs like the ones below if'; put 'special chars are encountered.'; put '> (ERR)OR: Some code points did not transcode.'; put '> An object or array close is not valid at this point in the JSON text.'; put '> Date value out of range'; put 'If this happens, try running with ENGINE=DATASTEP.'; put 'The DATASTEP engine is used to handle special SAS missing numerics, and'; put 'can also convert entire datasets to formatted values. Output JSON is always'; put 'in UTF-8.'; put 'Usage:'; put 'filename tmp temp;'; put 'data class; set sashelp.class;run;'; put '%mp_jsonout(OPEN,jref=tmp)'; put '%mp_jsonout(OBJ,class,jref=tmp)'; put '%mp_jsonout(OBJ,class,dslabel=class2,jref=tmp,showmeta=Y)'; put '%mp_jsonout(CLOSE,jref=tmp)'; put 'data _null_;'; put 'infile tmp;'; put 'input;putlog _infile_;'; put 'run;'; put 'If you are building web apps with SAS then you are strongly encouraged to use'; put 'the mX_createwebservice macros in combination with the'; put '[sasjs adapter](https://github.com/sasjs/adapter).'; put 'For more information see https://sasjs.io'; put '@param [in] action Valid values:'; put '@li OPEN - opens the JSON'; put '@li OBJ - sends a table with each row as an object'; put '@li ARR - sends a table with each row in an array'; put '@li CLOSE - closes the JSON'; put '@param [in] ds The dataset to send. Must be a work table.'; put '@param [out] jref= (_webout) The fileref to which to send the JSON'; put '@param [out] dslabel= The name to give the table in the exported JSON'; put '@param [in] fmt= (Y) Whether to keep (Y) or strip (N) formats from the table'; put '@param [in] engine= (DATASTEP) Which engine to use to send the JSON. Options:'; put '@li PROCJSON (default)'; put '@li DATASTEP (more reliable when data has non standard characters)'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to JSON'; put '

Related Files

'; put '@li mp_ds2fmtds.sas'; put '@version 9.2'; put '@author Allan Bowe'; put '@source https://github.com/sasjs/core'; put '**/'; put '%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y'; put ',engine=DATASTEP'; put ',missing=NULL'; put ',showmeta=N'; put ',maxobs=MAX'; put ')/*/STORE SOURCE*/;'; put '%local tempds colinfo fmtds i numcols numobs stmt_obs lastobs optval'; put 'tmpds1 tmpds2 tmpds3 tmpds4;'; put '%let numcols=0;'; put '%if &maxobs ne MAX %then %let stmt_obs=%str(if _n_>&maxobs then stop;);'; put '%if &action=OPEN %then %do;'; put 'options nobomfile;'; put 'data _null_;file &jref encoding=''utf-8'' lrecl=200;'; put 'put ''{"PROCESSED_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''"'';'; put 'run;'; put '%end;'; put '%else %if (&action=ARR or &action=OBJ) %then %do;'; put '/* force variable names to always be uppercase in the JSON */'; put 'options validvarname=upcase;'; put '/* To avoid issues with _webout on EBI - such as encoding diffs and truncation'; put '(https://support.sas.com/kb/49/325.html) we use temporary files */'; put 'filename _sjs1 temp lrecl=200 ;'; put 'data _null_; file _sjs1 encoding=''utf-8'';'; put 'put ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":";'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjs1 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs1 clear;'; put '/* grab col defs */'; put 'proc contents noprint data=&ds'; put 'out=_data_(keep=name type length format formatl formatd varnum label);'; put 'run;'; put '%let colinfo=%scan(&syslast,2,.);'; put 'proc sort data=&colinfo;'; put 'by varnum;'; put 'run;'; put '/* move meta to mac vars */'; put 'data &colinfo;'; put 'if _n_=1 then call symputx(''numcols'',nobs,''l'');'; put 'set &colinfo end=last nobs=nobs;'; put 'name=upcase(name);'; put '/* fix formats */'; put 'if type=2 or type=6 then do;'; put 'typelong=''char'';'; put 'length fmt $49.;'; put 'if format='''' then fmt=cats(''$'',length,''.'');'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else fmt=cats(format,formatl,''.'');'; put 'end;'; put 'else do;'; put 'typelong=''num'';'; put 'if format='''' then fmt=''best.'';'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else if formatd=0 then fmt=cats(format,formatl,''.'');'; put 'else fmt=cats(format,formatl,''.'',formatd);'; put 'end;'; put '/* 32 char unique name */'; put 'newname=''sasjs''!!substr(cats(put(md5(name),$hex32.)),1,27);'; put 'call symputx(cats(''name'',_n_),name,''l'');'; put 'call symputx(cats(''newname'',_n_),newname,''l'');'; put 'call symputx(cats(''length'',_n_),length,''l'');'; put 'call symputx(cats(''fmt'',_n_),fmt,''l'');'; put 'call symputx(cats(''type'',_n_),type,''l'');'; put 'call symputx(cats(''typelong'',_n_),typelong,''l'');'; put 'call symputx(cats(''label'',_n_),coalescec(label,name),''l'');'; put '/* overwritten when fmt=Y and a custom format exists in catalog */'; put 'if typelong=''num'' then call symputx(cats(''fmtlen'',_n_),200,''l'');'; put 'else call symputx(cats(''fmtlen'',_n_),min(32767,ceil((length+10)*1.5)),''l'');'; put 'run;'; put '%let tempds=%substr(_%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql;'; put 'select count(*) into: lastobs from &ds;'; put '%if &maxobs ne MAX %then %let lastobs=%sysfunc(min(&lastobs,&maxobs));'; put '%if &engine=PROCJSON %then %do;'; put '%if &missing=STRING %then %do;'; put '%put &sysmacroname: Special Missings not supported in proc json.;'; put '%put &sysmacroname: Switching to DATASTEP engine;'; put '%goto datastep;'; put '%end;'; put 'data &tempds;'; put 'set &ds;'; put '&stmt_obs;'; put '%if &fmt=N %then format _numeric_ best32.;;'; put '/* PRETTY is necessary to avoid line truncation in large files */'; put 'filename _sjs2 temp lrecl=131068 encoding=''utf-8'';'; put 'proc json out=_sjs2 pretty'; put '%if &action=ARR %then nokeys ;'; put ';export &tempds / nosastags fmtnumeric;'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs2 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs2 clear;'; put '%end;'; put '%else %if &engine=DATASTEP %then %do;'; put '%datastep:'; put '%if %sysfunc(exist(&ds)) ne 1 & %sysfunc(exist(&ds,VIEW)) ne 1'; put '%then %do;'; put '%put &sysmacroname: &ds NOT FOUND!!!;'; put '%return;'; put '%end;'; put '%if &fmt=Y %then %do;'; put '/**'; put '* Extract format definitions'; put '* First, by getting library locations from dictionary.formats'; put '* Then, by exporting the width using proc format'; put '* Cannot use maxw from sashelp.vformat as not always populated'; put '* Cannot use fmtinfo() as not supported in all flavours'; put '*/'; put '%let tmpds1=%substr(fmtsum%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds2=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds3=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds4=%substr(col%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql noprint;'; put 'create table &tmpds1 as'; put 'select cats(libname,''.'',memname) as FMTCAT,'; put 'FMTNAME'; put 'from dictionary.formats'; put 'where fmttype=''F'' and libname is not null'; put 'and fmtname in (select format from &colinfo where format is not null)'; put 'order by 1;'; put 'create table &tmpds2('; put 'FMTNAME char(32),'; put 'LENGTH num'; put ');'; put '%local catlist cat fmtlist i;'; put 'select distinct fmtcat into: catlist separated by '' '' from &tmpds1;'; put '%do i=1 %to %sysfunc(countw(&catlist,%str( )));'; put '%let cat=%scan(&catlist,&i,%str( ));'; put 'proc sql;'; put 'select distinct fmtname into: fmtlist separated by '' '''; put 'from &tmpds1 where fmtcat="&cat";'; put 'proc format lib=&cat cntlout=&tmpds3(keep=fmtname length);'; put 'select &fmtlist;'; put 'run;'; put 'proc sql;'; put 'insert into &tmpds2 select distinct fmtname,length from &tmpds3;'; put '%end;'; put 'proc sql;'; put 'create table &tmpds4 as'; put 'select a.*, b.length as MAXW'; put 'from &colinfo a'; put 'left join &tmpds2 b'; put 'on cats(a.format)=cats(upcase(b.fmtname))'; put 'order by a.varnum;'; put 'data _null_;'; put 'set &tmpds4;'; put 'if not missing(maxw);'; put 'call symputx('; put 'cats(''fmtlen'',_n_),'; put '/* vars need extra padding due to JSON escaping of special chars */'; put 'min(32767,ceil((max(length,maxw)+10)*1.5))'; put ',''l'''; put ');'; put 'run;'; put '/* configure varlenchk - as we are explicitly shortening the variables */'; put '%let optval=%sysfunc(getoption(varlenchk));'; put 'options varlenchk=NOWARN;'; put 'data _data_(compress=char);'; put '/* shorten the new vars */'; put 'length'; put '%do i=1 %to &numcols;'; put '&&name&i $&&fmtlen&i'; put '%end;'; put ';'; put '/* rename on entry */'; put 'set &ds(rename=('; put '%do i=1 %to &numcols;'; put '&&name&i=&&newname&i'; put '%end;'; put '));'; put '&stmt_obs;'; put 'drop'; put '%do i=1 %to &numcols;'; put '&&newname&i'; put '%end;'; put ';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=num %then %do;'; put '&&name&i=cats(put(&&newname&i,&&fmt&i));'; put '%end;'; put '%else %do;'; put '&&name&i=put(&&newname&i,&&fmt&i);'; put '%end;'; put '%end;'; put 'if _error_ then do;'; put 'call symputx(''syscc'',1012);'; put 'stop;'; put 'end;'; put 'run;'; put '%let fmtds=&syslast;'; put 'options varlenchk=&optval;'; put '%end;'; put 'proc format; /* credit yabwon for special null removal */'; put 'value bart (default=40)'; put '%if &missing=NULL %then %do;'; put '._ - .z = null'; put '%end;'; put '%else %do;'; put '._ = [quote()]'; put '. = null'; put '.a - .z = [quote()]'; put '%end;'; put 'other = [best.];'; put 'data &tempds;'; put 'attrib _all_ label='''';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'length &&name&i $&&fmtlen&i...;'; put 'format &&name&i $&&fmtlen&i...;'; put '%end;'; put '%end;'; put '%if &fmt=Y %then %do;'; put 'set &fmtds;'; put '%end;'; put '%else %do;'; put 'set &ds;'; put '%end;'; put '&stmt_obs;'; put 'format _numeric_ bart.;'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'if findc(&&name&i,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put '&&name&i=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,&&name&i)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else &&name&i=quote(cats(&&name&i));'; put '%end;'; put '%end;'; put 'run;'; put 'filename _sjs3 temp lrecl=131068 ;'; put 'data _null_;'; put 'file _sjs3 encoding=''utf-8'';'; put 'if _n_=1 then put "[";'; put 'set &tempds;'; put 'if _n_>1 then put "," @; put'; put '%if &action=ARR %then "[" ; %else "{" ;'; put '%do i=1 %to &numcols;'; put '%if &i>1 %then "," ;'; put '%if &action=OBJ %then """&&name&i"":" ;'; put '"&&name&i"n /* name literal for reserved variable names */'; put '%end;'; put '%if &action=ARR %then "]" ; %else "}" ; ;'; put '/* close out the table */'; put 'data _null_;'; put 'file _sjs3 mod encoding=''utf-8'';'; put 'put '']'';'; put 'run;'; put 'data _null_;'; put 'infile _sjs3 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs3 clear;'; put '%end;'; put 'proc sql;'; put 'drop table &colinfo, &tempds;'; put '%if %substr(&showmeta,1,1)=Y %then %do;'; put 'filename _sjs4 temp lrecl=131068 encoding=''utf-8'';'; put 'data _null_;'; put 'file _sjs4;'; put 'length label $350;'; put 'put ", ""$%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":{""vars"":{";'; put 'do i=1 to &numcols;'; put 'name=quote(trim(symget(cats(''name'',i))));'; put 'format=quote(trim(symget(cats(''fmt'',i))));'; put 'label=quote(prxchange(''s/\\/\\\\/'',-1,trim(symget(cats(''label'',i)))));'; put 'length=quote(trim(symget(cats(''length'',i))));'; put 'type=quote(trim(symget(cats(''typelong'',i))));'; put 'if i>1 then put "," @@;'; put 'put name '':{"format":'' format '',"label":'' label'; put ''',"length":'' length '',"type":'' type ''}'';'; put 'end;'; put 'put ''}}'';'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs4 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs4 clear;'; put '%end;'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put 'data _null_; file &jref encoding=''utf-8'' mod ;'; put 'put "}";'; put 'run;'; put '%end;'; put '%mend mp_jsonout;'; put '/**'; put '@file mm_webout.sas'; put '@brief Send data to/from SAS Stored Processes'; put '@details This macro should be added to the start of each Stored Process,'; put '**immediately** followed by a call to:'; put '%mm_webout(FETCH)'; put 'This will read all the input data and create same-named SAS datasets in the'; put 'WORK library. You can then insert your code, and send data back using the'; put 'following syntax:'; put 'data some datasets; * make some data ;'; put 'retain some columns;'; put 'run;'; put '%mm_webout(OPEN)'; put '%mm_webout(ARR,some) * Array format, fast, suitable for large tables ;'; put '%mm_webout(OBJ,datasets) * Object format, easier to work with ;'; put 'Finally, wrap everything up send some helpful system variables too'; put '%mm_webout(CLOSE)'; put '@param [in] action Either FETCH, OPEN, ARR, OBJ or CLOSE'; put '@param [in] ds The dataset to send back to the frontend'; put '@param [out] dslabel= Value to use instead of table name for sending to JSON'; put '@param [in] fmt= (N) Setting Y converts all vars to their formatted values'; put '@param [out] fref= (_webout) The fileref to which to write the JSON'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] workobs= (0) When set to a positive integer, will create a new'; put 'output object (WORK) which contains this number of observations from all'; put 'tables in the WORK library.'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to output JSON'; put '

SAS Macros

'; put '@li mp_jsonout.sas'; put '

Related Macros

'; put '@li ms_webout.sas'; put '@li mv_webout.sas'; put '@version 9.3'; put '@author Allan Bowe'; put '**/'; put '%macro mm_webout(action,ds,dslabel=,fref=_webout,fmt=N,missing=NULL'; put ',showmeta=N,maxobs=MAX,workobs=0'; put ');'; put '%global _webin_file_count _webin_fileref1 _webin_name1 _program _debug'; put 'sasjs_tables;'; put '%local i tempds jsonengine;'; put '/* see https://github.com/sasjs/core/issues/41 */'; put '%if "%upcase(&SYSENCODING)" ne "UTF-8" %then %let jsonengine=PROCJSON;'; put '%else %let jsonengine=DATASTEP;'; put '%if &action=FETCH %then %do;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'options mprint notes mprintnest;'; put '%end;'; put '%let _webin_file_count=%eval(&_webin_file_count+0);'; put '/* now read in the data */'; put '%do i=1 %to &_webin_file_count;'; put '%if &_webin_file_count=1 %then %do;'; put '%let _webin_fileref1=&_webin_fileref;'; put '%let _webin_name1=&_webin_name;'; put '%end;'; put 'data _null_;'; put 'infile &&_webin_fileref&i termstr=crlf;'; put 'input;'; put 'call symputx(''input_statement'',_infile_);'; put 'putlog "&&_webin_name&i input statement: " _infile_;'; put 'stop;'; put 'data &&_webin_name&i;'; put 'infile &&_webin_fileref&i firstobs=2 dsd termstr=crlf encoding=''utf-8'';'; put 'input &input_statement;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'if _n_<20 then putlog _infile_;'; put '%end;'; put 'run;'; put '%let sasjs_tables=&sasjs_tables &&_webin_name&i;'; put '%end;'; put '%end;'; put '%else %if &action=OPEN %then %do;'; put '/* fix encoding */'; put 'OPTIONS NOBOMFILE;'; put '/**'; put '* check xengine type to avoid the below err message:'; put '* > Function is only valid for filerefs using the CACHE access method.'; put '*/'; put 'data _null_;'; put 'set sashelp.vextfl(where=(fileref="_WEBOUT"));'; put 'if xengine=''STREAM'' then do;'; put 'rc=stpsrv_header(''Content-type'',"text/html; encoding=utf-8");'; put 'end;'; put 'run;'; put '/* setup json */'; put 'data _null_;file &fref encoding=''utf-8'';'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutBEGIN<<'';'; put '%end;'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'run;'; put '%end;'; put '%else %if &action=ARR or &action=OBJ %then %do;'; put '%mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt,jref=&fref'; put ',engine=&jsonengine,missing=&missing,showmeta=&showmeta,maxobs=&maxobs'; put ')'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put '/* To avoid issues with _webout on EBI we use a temporary file */'; put 'filename _sjsref temp lrecl=131068;'; put '%if %str(&workobs) > 0 %then %do;'; put '/* if debug mode, send back first XX records of each work table also */'; put 'data;run;%let tempds=%scan(&syslast,2,.);'; put 'ods output Members=&tempds;'; put 'proc datasets library=WORK memtype=data;'; put '%local wtcnt;%let wtcnt=0;'; put 'data _null_;'; put 'set &tempds;'; put 'if not (upcase(name) =:"DATA"); /* ignore temp datasets */'; put 'i+1;'; put 'call symputx(cats(''wt'',i),name,''l'');'; put 'call symputx(''wtcnt'',i,''l'');'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put ",""WORK"":{";'; put '%do i=1 %to &wtcnt;'; put '%let wt=&&wt&i;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'dsid=open("WORK.&wt",''is'');'; put 'nlobs=attrn(dsid,''NLOBS'');'; put 'nvars=attrn(dsid,''NVARS'');'; put 'rc=close(dsid);'; put 'if &i>1 then put '',''@;'; put 'put " ""&wt"" : {";'; put 'put ''"nlobs":'' nlobs;'; put 'put '',"nvars":'' nvars;'; put '%mp_jsonout(OBJ,&wt,jref=_sjsref,dslabel=first10rows,showmeta=Y'; put ',maxobs=&workobs'; put ')'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put '%end;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put 'run;'; put '%end;'; put '/* close off json */'; put 'data _null_;file _sjsref mod encoding=''utf-8'';'; put 'length SYSPROCESSNAME syserrortext syswarningtext autoexec $512;'; put 'put ",""_DEBUG"" : ""&_debug"" ";'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'autoexec=quote(urlencode(trim(getoption(''autoexec''))));'; put 'put '',"AUTOEXEC" : '' autoexec;'; put 'put ",""MF_GETUSER"" : ""%mf_getuser()"" ";'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'put ",""SYSENCODING"" : ""&sysencoding"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSPROCESSID"" : ""&SYSPROCESSID"" ";'; put 'put ",""SYSPROCESSMODE"" : ""&SYSPROCESSMODE"" ";'; put 'SYSPROCESSNAME=quote(urlencode(cats(SYSPROCESSNAME)));'; put 'put ",""SYSPROCESSNAME"" : " SYSPROCESSNAME;'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put '',"SYSWARNINGTEXT" : '' syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'length memsize $32;'; put 'memsize="%sysfunc(INPUTN(%sysfunc(getoption(memsize)), best.),sizekmg.)";'; put 'memsize=quote(cats(memsize));'; put 'put '',"MEMSIZE" : '' memsize;'; put 'put "}" @;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutEND<<'';'; put '%end;'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjsref lrecl=1 recfm=n;'; put 'file &fref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjsref clear;'; put '%end;'; put '%mend mm_webout;'; put '%macro webout(action,ds,dslabel=,fmt=,missing=NULL,showmeta=NO,maxobs=MAX);'; put '%mm_webout(&action,ds=&ds,dslabel=&dslabel,fmt=&fmt'; put ',missing=&missing'; put ',showmeta=&showmeta'; put ',maxobs=&maxobs'; put ') %mend;'; put '/* provide additional debug info */'; put '%global _program;'; put '%put &=syscc;'; put '%put user=%mf_getuser();'; put '%put pgm=&_program;'; put '%put timestamp=%sysfunc(datetime(),datetime19.);'; put '* Service Variables start;'; put '* Service Variables end;'; put '* SAS Macros start;'; put '%macro mf_getapploc(pgm);'; put '%if "&pgm"="" %then %do;'; put '%if %symexist(_program) %then %let pgm=&_program;'; put '%else %do;'; put '%put &sysmacroname: No value provided and no _program variable available;'; put '%return;'; put '%end;'; put '%end;'; put '%local root;'; put '/**'; put '* First check we are not in the tests/macros folder (which has no subfolders)'; put '* or specifically in the testsetup or testteardown services'; put '*/'; put '%if %index(&pgm,/tests/macros/)'; put 'or %index(&pgm,/tests/testsetup)'; put 'or %index(&pgm,/tests/testteardown)'; put '%then %do;'; put '%let root=%substr(&pgm,1,%index(&pgm,/tests)-1);'; put '&root'; put '%return;'; put '%end;'; put '/**'; put '* Next, move up two levels to avoid matches on subfolder or service name'; put '*/'; put '%let root=%substr(&pgm,1,%length(&pgm)-%length(%scan(&pgm,-1,/))-1);'; put '%let root=%substr(&root,1,%length(&root)-%length(%scan(&root,-1,/))-1);'; put '%if %index(&root,/tests/) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/tests/)-1);'; put '%end;'; put '%else %if %index(&root,/services) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/services)-1);'; put '%end;'; put '%else %if %index(&root,/jobs) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/jobs)-1);'; put '%end;'; put '%else %put &sysmacroname: Could not find an app location from &pgm;'; put '&root'; put '%mend mf_getapploc ;'; put '%macro mf_getuniquefileref(prefix=_,maxtries=1000,lrecl=32767);'; put '%local rc fname;'; put '%if &prefix=0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%end;'; put '%else %do;'; put '%local x len;'; put '%let len=%eval(8-%length(&prefix));'; put '%let x=0;'; put '%do x=0 %to &maxtries;'; put '%let fname=&prefix%substr(%sysfunc(ranuni(0)),3,&len);'; put '%if %sysfunc(fileref(&fname)) > 0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%return;'; put '%end;'; put '%end;'; put '%put unable to find available fileref after &maxtries attempts;'; put '%end;'; put '%mend mf_getuniquefileref;'; put '%macro mp_abort(mac=mp_abort.sas, type=, msg=, iftrue=%str(1=1)'; put ', errds=work.mp_abort_errds'; put ', mode=REGULAR'; put ')/*/STORE SOURCE*/;'; put '%global sysprocessmode sysprocessname sasjs_stpsrv_header_loc sasjsprocessmode;'; put '%local fref fid i;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%put NOTE: /// mp_abort macro executing //;'; put '%if %length(&mac)>0 %then %put NOTE- called by &mac;'; put '%put NOTE - &msg;'; put '%if %symexist(_SYSINCLUDEFILEDEVICE)'; put '/* abort cancel FILE does not restart outside the INCLUDE on Viya 3.5 */'; put 'and %superq(SYSPROCESSNAME) ne %str(Compute Server)'; put '%then %do;'; put '%if "*&_SYSINCLUDEFILEDEVICE*" ne "**" %then %do;'; put 'data &errds;'; put 'iftrue=''1=1'';'; put 'length mac $100 msg $5000;'; put 'mac=symget(''mac'');'; put 'msg=symget(''msg'');'; put 'run;'; put 'data _null_;'; put 'abort cancel FILE;'; put 'run;'; put '%return;'; put '%end;'; put '%end;'; put '/* Web App Context */'; put '%if %symexist(_PROGRAM)'; put 'or %superq(SYSPROCESSNAME) = %str(Compute Server)'; put 'or &mode=INCLUDE'; put '%then %do;'; put 'options obs=max replace mprint;'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5"'; put '%then %do;'; put 'options nosyntaxcheck;'; put '%end;'; put '%if &mode=INCLUDE %then %do;'; put '%if %sysfunc(exist(&errds))=1 %then %do;'; put 'data _null_;'; put 'set &errds;'; put 'call symputx(''iftrue'',iftrue,''l'');'; put 'call symputx(''mac'',mac,''l'');'; put 'call symputx(''msg'',msg,''l'');'; put 'putlog (_all_)(=);'; put 'run;'; put '%if (&iftrue)=0 %then %return;'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: No include errors found;'; put '%return;'; put '%end;'; put '%end;'; put '/* extract log errs / warns, if exist */'; put '%local logloc logline;'; put '%global logmsg; /* capture global messages */'; put '%if %symexist(SYSPRINTTOLOG) %then %let logloc=&SYSPRINTTOLOG;'; put '%else %let logloc=%qsysfunc(getoption(LOG));'; put 'proc printto log=log;run;'; put '%let logline=0;'; put '%if %length(&logloc)>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input; putlog _infile_;'; put 'i=1;'; put 'retain logonce 0;'; put 'if ('; put '_infile_=:"%str(WARN)ING" or _infile_=:"%str(ERR)OR"'; put ') and logonce=0 then'; put 'do;'; put 'call symputx(''logline'',_n_);'; put 'logonce+1;'; put 'end;'; put 'run;'; put '/* capture log including lines BEFORE the err */'; put '%if &logline>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input;'; put 'i=1;'; put 'stoploop=0;'; put 'if _n_ ge &logline-15 and stoploop=0 then do until (i>22);'; put 'call symputx(''logmsg'',catx(''\n'',symget(''logmsg''),_infile_));'; put 'input;'; put 'i+1;'; put 'stoploop=1;'; put 'end;'; put 'if stoploop=1 then stop;'; put 'run;'; put '%end;'; put '%end;'; put '%if %symexist(SYS_JES_JOB_URI) %then %do;'; put '/* setup webout for Viya */'; put 'options nobomfile;'; put '%if "X&SYS_JES_JOB_URI.X"="XX" %then %do;'; put 'filename _webout temp lrecl=999999 mod;'; put '%end;'; put '%else %do;'; put 'filename _webout filesrvc parenturi="&SYS_JES_JOB_URI"'; put 'name="_webout.json" lrecl=999999 mod;'; put '%end;'; put '%end;'; put '%else %if %sysfunc(filename(fref,&sasjs_stpsrv_header_loc))=0 %then %do;'; put 'options nobomfile;'; put '/* set up http header for SASjs Server */'; put '%let fid=%sysfunc(fopen(&fref,A));'; put '%if &fid=0 %then %do;'; put '%put %str(ERR)OR: %sysfunc(sysmsg());'; put '%return;'; put '%end;'; put '%let rc=%sysfunc(fput(&fid,%str(Content-Type: application/json)));'; put '%let rc=%sysfunc(fwrite(&fid));'; put '%let rc=%sysfunc(fclose(&fid));'; put '%let rc=%sysfunc(filename(&fref));'; put '%end;'; put '/* send response in SASjs JSON format */'; put 'data _null_;'; put 'file _webout mod lrecl=32000 encoding=''utf-8'';'; put 'length msg syswarningtext syserrortext $32767 mode $10 ;'; put 'sasdatetime=datetime();'; put 'msg=symget(''msg'');'; put '%if &logline>0 %then %do;'; put 'msg=cats(msg,''\n\nLog Extract:\n'',symget(''logmsg''));'; put '%end;'; put '/* escape the escapes */'; put 'msg=tranwrd(msg,''\'',''\\'');'; put '/* escape the quotes */'; put 'msg=tranwrd(msg,''"'',''\"'');'; put '/* ditch the CRLFs as chrome complains */'; put 'msg=compress(msg,,''kw'');'; put '/* quote without quoting the quotes (which are escaped instead) */'; put 'msg=cats(''"'',msg,''"'');'; put 'if symexist(''_debug'') then debug=quote(trim(symget(''_debug'')));'; put 'else debug=''""'';'; put 'if symget(''sasjsprocessmode'')=''Stored Program'' then mode=''SASJS'';'; put 'if mode ne ''SASJS'' then put ''>>weboutBEGIN<<'';'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'put '',"sasjsAbort" : [{'';'; put 'put '' "MSG":'' msg ;'; put 'put '' ,"MAC": "'' "&mac" ''"}]'';'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'put '',"_DEBUG":'' debug ;'; put 'if symexist(''_metauser'') then do;'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put 'end;'; put 'if symexist(''SYS_JES_JOB_URI'') then do;'; put 'SYS_JES_JOB_URI=quote(trim(symget(''SYS_JES_JOB_URI'')));'; put 'put '',"SYS_JES_JOB_URI": '' SYS_JES_JOB_URI;'; put 'end;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put ",""SYSWARNINGTEXT"" : " syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'put "}" ;'; put 'if mode ne ''SASJS'' then put ''>>weboutEND<<'';'; put 'run;'; put '%put _all_;'; put '%if "&sysprocessmode " = "SAS Stored Process Server " %then %do;'; put 'data _null_;'; put 'putlog ''stpsrvset program err and syscc'';'; put 'rc=stpsrvset(''program error'', 0);'; put 'call symputx("syscc",0,"g");'; put 'run;'; put '%if &sysscp=WIN'; put 'and 1=0 /* deprecating this logic until we figure out a consistent abort */'; put 'and "%substr(%str(&sysvlong ),1,8)"="9.04.01M"'; put 'and "%substr(%str(&sysvlong ),9,1)">"5" %then %do;'; put '/* skip approach (below) does not work in windows m6+ envs */'; put 'endsas;'; put '%end;'; put '%else %do;'; put '/**'; put '* endsas kills 9.4m3 deployments by orphaning multibridges.'; put '* Abort variants are ungraceful (non zero return code)'; put '* This approach lets SAS run silently until the end :-)'; put '* Caution - fails when called within a %include within a macro'; put '* Use mp_include() to handle this.'; put '*/'; put 'filename skip temp;'; put 'data _null_;'; put 'file skip;'; put 'put ''%macro skip();'';'; put 'comment ''%mend skip; -> fix lint '';'; put 'put ''%macro skippy();'';'; put 'comment ''%mend skippy; -> fix lint '';'; put 'run;'; put '%inc skip;'; put '%end;'; put '%end;'; put '%else %if "&sysprocessmode " = "SAS Compute Server " %then %do;'; put '/* endsas kills the session making it harder to fetch results */'; put 'data _null_;'; put 'syswarningtext=symget(''syswarningtext'');'; put 'syserrortext=symget(''syserrortext'');'; put 'abort_msg=symget(''msg'');'; put 'syscc=symget(''syscc'');'; put 'sysuserid=symget(''sysuserid'');'; put 'iftrue=symget(''iftrue'');'; put 'put (_all_)(/=);'; put 'call symputx(''syscc'',0);'; put 'abort cancel nolist;'; put 'run;'; put '%end;'; put '%else %do;'; put '%abort cancel;'; put '%end;'; put '%end;'; put '%else %do;'; put '%put _all_;'; put '%abort cancel;'; put '%end;'; put '%mend mp_abort;'; put '/** @endcond */'; put '%macro mm_getstpcode('; put 'tree=/User Folders/sasdemo/somestp'; put ',name='; put ',outloc=0'; put ',outref=0'; put ',mDebug=1'; put ',showlog=NO'; put ');'; put '%local mD;'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing &sysmacroname..sas;'; put '%&mD.put _local_;'; put '%if %length(&name)>0 %then %let name=/&name;'; put '/* first, check if STP exists */'; put '%local tsuri;'; put '%let tsuri=stopifempty ;'; put 'data _null_;'; put 'format type uri tsuri value $200.;'; put 'call missing (of _all_);'; put 'path="&tree&name(StoredProcess)";'; put '/* first, find the STP ID */'; put 'if metadata_pathobj("",path,"StoredProcess",type,uri)>0 then do;'; put '/* get sourcecode */'; put 'cnt=1;'; put 'do while (metadata_getnasn(uri,"Notes",cnt,tsuri)>0);'; put 'rc=metadata_getattr(tsuri,"Name",value);'; put '&mD.put tsuri= value=;'; put 'if value="SourceCode" then do;'; put '/* found it! */'; put 'rc=metadata_getattr(tsuri,"Id",value);'; put 'call symputx(''tsuri'',value,''l'');'; put 'stop;'; put 'end;'; put 'cnt+1;'; put 'end;'; put 'end;'; put 'else put (_all_)(=);'; put 'run;'; put '%mp_abort(iftrue= (&tsuri=stopifempty)'; put ',mac=mm_getstpcode'; put ',msg=%str(&tree&name.(StoredProcess) not found!)'; put ')'; put '/**'; put '* Now we can extract the textstore'; put '*/'; put 'filename __getdoc temp lrecl=10000000;'; put 'proc metadata'; put 'in="$METAREPOSITORY'; put ''; put 'SAS1"'; put 'out=__getdoc ;'; put 'run;'; put '/* find the beginning of the text */'; put '%local start;'; put 'data _null_;'; put 'infile __getdoc lrecl=10000;'; put 'input;'; put 'start=index(_infile_,''StoredText="'');'; put 'if start then do;'; put 'call symputx("start",start+11);'; put '*putlog ''"'' _infile_ ''"'';'; put 'end;'; put 'stop;'; put '%local outeng;'; put '%if "&outloc"="0" %then %let outeng=TEMP;'; put '%else %let outeng="&outloc";'; put '%local fref;'; put '%if &outref=0 %then %let fref=%mf_getuniquefileref();'; put '%else %let fref=&outref;'; put '/* read the content, byte by byte, resolving escaped chars */'; put 'filename &fref &outeng lrecl=100000;'; put 'data _null_;'; put 'length filein 8 fileid 8;'; put 'filein = fopen("__getdoc","I",1,"B");'; put 'fileid = fopen("&fref","O",1,"B");'; put 'rec = "20"x;'; put 'length entity $6;'; put 'do while(fread(filein)=0);'; put 'x+1;'; put 'if x>&start then do;'; put 'rc = fget(filein,rec,1);'; put 'if rec=''"'' then leave;'; put 'else if rec="&" then do;'; put 'entity=rec;'; put 'do until (rec=";");'; put 'if fread(filein) ne 0 then goto getout;'; put 'rc = fget(filein,rec,1);'; put 'entity=cats(entity,rec);'; put 'end;'; put 'select (entity);'; put 'when (''&'' ) rec=''&'' ;'; put 'when (''<'' ) rec=''<'' ;'; put 'when (''>'' ) rec=''>'' ;'; put 'when (''''') rec="''" ;'; put 'when (''"'') rec=''"'' ;'; put 'when ('' '') rec=''0A''x;'; put 'when ('' '') rec=''0D''x;'; put 'when (''$'' ) rec=''$'' ;'; put 'when ('' '') rec=''09''x;'; put 'otherwise putlog "%str(WARN)ING: missing value for " entity=;'; put 'end;'; put 'rc =fput(fileid, substr(rec,1,1));'; put 'rc =fwrite(fileid);'; put 'end;'; put 'else do;'; put 'rc =fput(fileid,rec);'; put 'rc =fwrite(fileid);'; put 'end;'; put 'end;'; put 'end;'; put 'getout:'; put 'rc=fclose(filein);'; put 'rc=fclose(fileid);'; put 'run;'; put '%if &showlog=YES %then %do;'; put 'data _null_;'; put 'infile &fref lrecl=32767 end=last;'; put 'input;'; put 'if _n_=1 then putlog ''>>stpcodeBEGIN<<'';'; put 'putlog _infile_;'; put 'if last then putlog ''>>stpcodeEND<<'';'; put 'run;'; put '%end;'; put 'filename __getdoc clear;'; put '%if &outref=0 %then %do;'; put 'filename &fref clear;'; put '%end;'; put '%mend mm_getstpcode;'; put '%macro dc_getsettings();'; put '%global _program;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(syscc=&syscc on entry (&syswarningtext &syserrortext))'; put ')'; put '%if %length(&_PROGRAM)>1 %then %let root=&_program;'; put '%else %do;'; put '%global _metauser;'; put '%let _metauser=&sysuserid;'; put '/* to mimic a "real" _program we need to give a dummy role and stp name */'; put '%let root=/dummyRole/dummyName;'; put '%end;'; put '/* the DC precode is stored in the Admin folder in the root of'; put 'the project. Lets find that root. */'; put '%put &=root;'; put '%let root=%mf_getapploc();'; put '%put &=root;'; put '/* Now we know the root location we can retrieve the params */'; put '%let temploc=%sysfunc(pathname(work))/settings.txt;'; put '%mm_getstpcode(tree=&root/services/public'; put ',name=Data_Controller_Settings'; put ',outloc=&temploc'; put ')'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Unable to run getstpcode)'; put ')'; put 'filename _getsets "&temploc" lrecl=2000;'; put '/*'; put 'Do not use mp_include here - this puts a copy in every service, which creates'; put 'compilation problems when calling services from mp_include'; put '*/'; put '%inc _getsets/source2;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Problem running &sysmacroname (&syswarningtext &syserrortext))'; put ')'; put '%mend dc_getsettings;'; put '%macro mf_fmtdttm('; put ')/*/STORE SOURCE*/;'; put '%if "&sysver"="9.2" or "&sysver"="9.3"'; put 'or ("&sysver"="9.4" and "%substr(&SYSVLONG,9,1)" le "3")'; put 'or "%substr(&sysver,1,1)"="4"'; put 'or "%substr(&sysver,1,1)"="5"'; put '%then %do;DATETIME19.3%end;'; put '%else %do;E8601DT26.6%end;'; put '%mend mf_fmtdttm;'; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '%macro mp_init(prefix=SASJS'; put ')/*/STORE SOURCE*/;'; put '%if %symexist(SASJS_PREFIX) %then %return; /* only run once */'; put '%global'; put 'SASJS_PREFIX /* the ONLY hard-coded global macro variable in SASjs */'; put '&prefix._FUNCTIONS /* used in mcf_init() to track core function compilation */'; put '&prefix._INIT_NUM /* initialisation time as numeric */'; put '&prefix._INIT_DTTM /* initialisation time in E8601DT26.6 format */'; put '&prefix.WORK /* avoid typing %sysfunc(pathname(work)) every time */'; put ';'; put '%let sasjs_prefix=&prefix;'; put 'data _null_;'; put 'dttm=datetime();'; put 'call symputx("&sasjs_prefix._init_num",dttm,''g'');'; put 'call symputx("&sasjs_prefix._init_dttm",put(dttm,E8601DT26.6),''g'');'; put 'call symputx("&sasjs_prefix.work",pathname(''WORK''),''g'');'; put 'run;'; put 'options'; put 'compress=CHAR /* default is none so ensure we have something! */'; put 'datastmtchk=ALLKEYWORDS /* protection from overwriting input datasets */'; put 'errorcheck=STRICT /* catch errs in libname/filename statements */'; put 'fmterr /* ensure err when a format cannot be found */'; put 'mergenoby=%str(ERR)OR /* throw err when a merge has no BY variables */'; put 'missing=. /* changing this can cause hard to detect errs */'; put 'noquotelenmax /* avoid warnings for long strings */'; put 'noreplace /* avoid overwriting permanent datasets */'; put 'ps=max /* reduce log size slightly */'; put 'ls=max /* reduce log even more and avoid word truncation */'; put 'validmemname=COMPATIBLE /* avoid special characters etc in table names */'; put 'validvarname=V7 /* avoid special characters etc in variable names */'; put 'varinitchk=%str(ERR)OR /* avoid data mistakes from variable name typos */'; put 'varlenchk=%str(ERR)OR /* fail hard if truncation (data loss) can result */'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5" %then %do;'; put 'noautocorrect /* disallow misspelled procedure names */'; put 'dsoptions=note2err /* undocumented - convert bad NOTEs to ERRs */'; put '%end;'; put ';'; put '%mend mp_init;'; put '%macro mpeinit(fetch=YES);'; put '%global mpeinit'; put 'mpeadmins /* group with unrestricted Meditor access */'; put 'mpelocapprovals /* location for landing and staging files */'; put 'mpelib /* location of configuration tables for DC */'; put 'dc_repo_users /* location of user / group metadata */'; put 'dc_licence_key /* extracted in dc_getsettings */'; put 'dc_activation_key /* extracted in dc_getsettings */'; put 'dc_locale /* extracted in dc_getsettings */'; put 'dc_dttmtfmt /* can be overridden in dc_getsettings */'; put '_debug'; put ';'; put '%if &mpeinit=1 %then %return;'; put '%else %let mpeinit=1;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(Problem on service startup (&syswarningtext &syserrortext))'; put ')'; put '%mp_init()'; put '%if &fetch=YES %then %do;'; put '%webout(FETCH)'; put '%end;'; put '%global _CLIENTNAME;'; put '%mp_abort(iftrue= (&_CLIENTNAME=SAS Enterprise Guide)'; put ',mac=&_program..sas'; put ',msg=%str(Data Controller is a web app and should not be executed from EG)'; put ')'; put 'options urlencoding=utf8 nobomfile lrecl=32767;'; put '%let perf=%sysfunc(datetime());'; put '%put perfdiff: 0;'; put '%let dc_locale=SYSTEM; /* default if not set */'; put '/**'; put '* E8601DT26.6 has widest database support - but not all SAS flavours can'; put '* handle it. Override in the settings STP if needed.'; put '*/'; put 'data _null_;'; put 'dc_dttmtfmt=''"%sysfunc(datetime(),''!!"%mf_fmtdttm()"!!'')"dt'';'; put 'call symputx(''dc_dttmtfmt'',dc_dttmtfmt);'; put 'put dc_dttmtfmt=;'; put 'run;'; put '%put &=dc_dttmtfmt;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc prior to dc_getsettings)'; put ')'; put '%dc_getsettings()'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc after dc_getsettings)'; put ')'; put 'data _null_;'; put 'set &DC_LIBREF..mpe_config(where=('; put 'var_scope="DC"'; put 'and &dc_dttmtfmt lt tx_to'; put 'and var_active=1'; put '));'; put 'call symputx(var_name,var_value,''G'');'; put 'putlog var_name "=" var_value;'; put 'run;'; put '%let mpelib=&dc_libref;'; put '%let mpeadmins=&dc_admin_group;'; put '%let mpelocapprovals=&dc_staging_area;'; put '%let dc_repo_users=&dc_repo_users;'; put '%if &dc_locale ne SYSTEM %then %do;'; put 'options locale=&dc_locale;'; put '%end;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(Problem during compilation or with STP precode (&syswarningtext))'; put ')'; put '%mend mpeinit;'; put '%macro mf_mval(var);'; put '%if %symexist(&var) %then %do;'; put '%superq(&var)'; put '%end;'; put '%mend mf_mval;'; put '%macro mf_trimstr(basestr,trimstr);'; put '%local baselen trimlen trimval;'; put '/* return if basestr is shorter than trimstr (or 0) */'; put '%let baselen=%length(%superq(basestr));'; put '%let trimlen=%length(%superq(trimstr));'; put '%if &baselen < &trimlen or &baselen=0 %then %return;'; put '/* obtain the characters from the end of basestr */'; put '%let trimval=%qsubstr(%superq(basestr)'; put ',%length(%superq(basestr))-&trimlen+1'; put ',&trimlen);'; put '/* compare and if matching, chop it off! */'; put '%if %superq(basestr)=%superq(trimstr) %then %do;'; put '%return;'; put '%end;'; put '%else %if %superq(trimval)=%superq(trimstr) %then %do;'; put '%qsubstr(%superq(basestr),1,%length(%superq(basestr))-&trimlen)'; put '%end;'; put '%else %do;'; put '&basestr'; put '%end;'; put '%mend mf_trimstr;'; put '%macro mf_getplatform(switch'; put ')/*/STORE SOURCE*/;'; put '%local a b c;'; put '%if &switch.NONE=NONE %then %do;'; put '%if %symexist(sasjsprocessmode) %then %do;'; put '%if &sasjsprocessmode=Stored Program %then %do;'; put 'SASJS'; put '%return;'; put '%end;'; put '%end;'; put '%if %symexist(sysprocessmode) %then %do;'; put '%if "&sysprocessmode"="SAS Object Server"'; put 'or "&sysprocessmode"= "SAS Compute Server" %then %do;'; put 'SASVIYA'; put '%end;'; put '%else %if "&sysprocessmode"="SAS Stored Process Server"'; put 'or "&sysprocessmode"="SAS Workspace Server"'; put '%then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if %symexist(_metaport) or %symexist(_metauser) %then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if &switch=SASSTUDIO %then %do;'; put '/* return the version of SAS Studio else 0 */'; put '%if %mf_mval(_CLIENTAPP)=%str(SAS Studio) %then %do;'; put '%let a=%mf_mval(_CLIENTVERSION);'; put '%let b=%scan(&a,1,.);'; put '%if %eval(&b >2) %then %do;'; put '&b'; put '%end;'; put '%else 0;'; put '%end;'; put '%else 0;'; put '%end;'; put '%else %if &switch=VIYARESTAPI %then %do;'; put '%mf_trimstr(%sysfunc(getoption(servicesbaseurl)),/)'; put '%end;'; put '%mend mf_getplatform;'; put '%macro mpeterm();'; put '%local oldloc;'; put 'data _null_;'; put 'if symexist(''SYSPRINTTOLOG'') then oldloc=symget(''SYSPRINTTOLOG'');'; put 'else oldloc=getoption(''LOG'');'; put 'if subpad(oldloc,1,1) not in (''"'',"''",'' '') then oldloc=quote(cats(oldloc));'; put 'call symputx(''oldloc'',oldloc,''l'');'; put 'run;'; put '%if %length(&oldloc)>0 %then %do;'; put 'proc printto log=log;'; put 'run;'; put 'data _null_;'; put 'infile &oldloc;'; put 'input; putlog _infile_;'; put 'run;'; put '%end;'; put '%if %sysfunc(exist(&dc_libref..mpe_requests)) and %mf_getplatform() ne SASVIYA'; put '%then %do;'; put 'data ;'; put 'if 0 then set &dc_libref..mpe_requests;'; put 'request_dttm=%sysfunc(datetime());'; put 'request_user="%mf_getuser()";'; put 'request_service="%scan(&_program,-2,/)/%scan(&_program,-1,/)";'; put 'request_params='''';'; put 'output;stop;'; put 'proc append base=&dc_libref..mpe_requests data=&syslast force nowarn;'; put 'run;'; put '%end;'; put '%mend mpeterm;'; put '%macro mm_assignlib('; put 'libref'; put ',mAbort=HARD'; put ')/*/STORE SOURCE*/;'; put '%local mp_abort msg;'; put '%let mp_abort=0;'; put '%if %sysfunc(libref(&libref)) %then %do;'; put 'data _null_;'; put 'length liburi LibName msg $200;'; put 'call missing(of _all_);'; put 'nobj=metadata_getnobj("omsobj:SASLibrary?@Libref=''&libref''",1,liburi);'; put 'if nobj=1 then do;'; put 'rc=metadata_getattr(liburi,"Name",LibName);'; put '/* now try and assign it */'; put 'if libname("&libref",,''meta'',cats(''liburi="'',liburi,''";'')) ne 0 then do;'; put 'putlog "&libref could not be assigned";'; put 'putlog liburi=;'; put '/**'; put '* Fetch the system message for display in the abort modal. This is'; put '* not always helpful though. One example, previously received:'; put '* NOTE: Libref XX refers to the same library metadata as libref XX.'; put '*/'; put 'msg=sysmsg();'; put 'if msg=:''ERROR: Libref SAVE is not assigned.'' then do;'; put 'msg=catx(" ",'; put '"Could not assign %upcase(&libref).",'; put '"Please check metadata permissions! Libname:",libname,'; put '"Liburi:",liburi'; put ');'; put 'end;'; put 'else if msg="ERROR: User does not have appropriate authorization "!!'; put '"level for library SAVE."'; put 'then do;'; put 'msg=catx(" ",'; put '"ERROR: User does not have appropriate authorization level",'; put '"for library %upcase(&libref), libname:",libname,'; put '"Liburi:",liburi'; put ');'; put 'end;'; put 'call symputx(''msg'',msg,''l'');'; put 'if "&mabort"=''HARD'' then call symputx(''mp_abort'',1,''l'');'; put 'end;'; put 'else do;'; put 'put (_all_)(=);'; put 'call symputx(''libname'',libname,''L'');'; put 'call symputx(''liburi'',liburi,''L'');'; put 'end;'; put 'end;'; put 'else if nobj>1 then do;'; put 'if "&mabort"=''HARD'' then call symputx(''mp_abort'',1);'; put 'call symputx(''msg'',"More than one library with libref=&libref");'; put 'end;'; put 'else do;'; put 'if "&mabort"=''HARD'' then call symputx(''mp_abort'',1);'; put 'call symputx(''msg'',"Library &libref not found in metadata");'; put 'end;'; put 'run;'; put '%put NOTE: &msg;'; put '%end;'; put '%else %do;'; put '%put NOTE: Library &libref is already assigned;'; put '%end;'; put '%mp_abort(iftrue= (&mp_abort=1)'; put ',mac=mm_assignlib.sas'; put ',msg=%superq(msg)'; put ')'; put '%mend mm_assignlib;'; put '/** @cond */'; put '%macro mf_getengine(libref'; put ')/*/STORE SOURCE*/;'; put '%local dsid engnum rc engine;'; put '/* in case the parameter is a libref.tablename, pull off just the libref */'; put '%let libref = %upcase(%scan(&libref, 1, %str(.)));'; put '%let dsid=%sysfunc('; put 'open(sashelp.vlibnam(where=(libname="%upcase(&libref)")),i)'; put ');'; put '%if (&dsid ^= 0) %then %do;'; put '%let engnum=%sysfunc(varnum(&dsid,ENGINE));'; put '%let rc=%sysfunc(fetch(&dsid));'; put '%let engine=%sysfunc(getvarc(&dsid,&engnum));'; put '%put &libref. ENGINE is &engine.;'; put '%let rc= %sysfunc(close(&dsid));'; put '%end;'; put '%upcase(&engine)'; put '%mend mf_getengine;'; put '/** @endcond */'; put '%macro mm_assigndirectlib('; put 'libref'; put ',open_passthrough='; put ',sql_options='; put ',mDebug=0'; put ',mAbort=0'; put ')/*/STORE SOURCE*/;'; put '%local mD;'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing mm_assigndirectlib.sas;'; put '%&mD.put _local_;'; put '%if &mAbort=1 %then %let mAbort=;'; put '%else %let mAbort=%str(*);'; put '%&mD.put NOTE: Creating direct (non META) connection to &libref library;'; put '%local cur_engine;'; put '%let cur_engine=%mf_getengine(&libref);'; put '%if &cur_engine ne META and &cur_engine ne %then %do;'; put '%put NOTE: &libref already has a direct (&cur_engine) libname connection;'; put '%return;'; put '%end;'; put '%else %if %upcase(&libref)=WORK %then %do;'; put '%put NOTE: We already have a direct connection to WORK :-) ;'; put '%return;'; put '%end;'; put '/* need to determine the library ENGINE first */'; put '%local engine;'; put 'data _null_;'; put 'length lib_uri engine $256;'; put 'call missing (of _all_);'; put '/* get URI for the particular library */'; put 'rc1=metadata_getnobj("omsobj:SASLibrary?@Libref =''&libref''",1,lib_uri);'; put '/* get the Engine attribute of the previous object */'; put 'rc2=metadata_getattr(lib_uri,''Engine'',engine);'; put 'putlog "mm_assigndirectlib for &libref:" rc1= lib_uri= rc2= engine=;'; put 'call symputx("liburi",lib_uri,''l'');'; put 'call symputx("engine",engine,''l'');'; put 'run;'; put '/* now obtain engine specific connection details */'; put '%if &engine=BASE %then %do;'; put '%&mD.put NOTE: Retrieving BASE library path;'; put 'data _null_;'; put 'length up_uri $256 path cat_path $1024;'; put 'retain cat_path;'; put 'call missing (of _all_);'; put '/* get all the filepaths of the UsingPackages association */'; put 'i=1;'; put 'rc3=metadata_getnasn("&liburi",''UsingPackages'',i,up_uri);'; put 'do while (rc3>0);'; put '/* get the DirectoryName attribute of the previous object */'; put 'rc4=metadata_getattr(up_uri,''DirectoryName'',path);'; put 'if i=1 then path = ''("''!!trim(path)!!''" '';'; put 'else path ='' "''!!trim(path)!!''" '';'; put 'cat_path = trim(cat_path) !! " " !! trim(path) ;'; put 'i+1;'; put 'rc3=metadata_getnasn("&liburi",''UsingPackages'',i,up_uri);'; put 'end;'; put 'cat_path = trim(cat_path) !! ")";'; put '&mD.putlog "NOTE: Getting physical path for &libref library";'; put '&mD.putlog rc3= up_uri= rc4= cat_path= path=;'; put '&mD.putlog "NOTE: Libname cmd will be:";'; put '&mD.putlog "libname &libref" cat_path;'; put 'call symputx("filepath",cat_path,''l'');'; put 'run;'; put '%if %sysevalf(&sysver<9.4) %then %do;'; put 'libname &libref &filepath;'; put '%end;'; put '%else %do;'; put '/* apply the new filelocks option to cater for temporary locks */'; put 'libname &libref &filepath filelockwait=5;'; put '%end;'; put '%end;'; put '%else %if &engine=REMOTE %then %do;'; put 'data x;'; put 'length rcCon rcProp rc k 3 uriCon uriProp PropertyValue PropertyName'; put 'Delimiter $256 properties $2048;'; put 'retain properties;'; put 'rcCon = metadata_getnasn("&liburi", "LibraryConnection", 1, uriCon);'; put 'rcProp = metadata_getnasn(uriCon, "Properties", 1, uriProp);'; put 'k = 1;'; put 'rcProp = metadata_getnasn(uriCon, "Properties", k, uriProp);'; put 'do while (rcProp > 0);'; put 'rc = metadata_getattr(uriProp , "DefaultValue",PropertyValue);'; put 'rc = metadata_getattr(uriProp , "PropertyName",PropertyName);'; put 'rc = metadata_getattr(uriProp , "Delimiter",Delimiter);'; put 'properties = trim(properties) !! " " !! trim(PropertyName)'; put '!! trim(Delimiter) !! trim(PropertyValue);'; put 'output;'; put 'k+1;'; put 'rcProp = metadata_getnasn(uriCon, "Properties", k, uriProp);'; put 'end;'; put '%&mD.put NOTE: Getting properties for REMOTE SHARE &libref library;'; put '&mD.put _all_;'; put '%&mD.put NOTE: Libname cmd will be:;'; put '%&mD.put libname &libref &engine &properties slibref=&libref;'; put 'call symputx ("properties",trim(properties),''l'');'; put 'run;'; put 'libname &libref &engine &properties slibref=&libref;'; put '%end;'; put '%else %if &engine=OLEDB %then %do;'; put '%&mD.put NOTE: Retrieving OLEDB connection details;'; put 'data _null_;'; put 'length domain datasource provider properties schema'; put 'connx_uri domain_uri conprop_uri lib_uri schema_uri value $256.;'; put 'call missing (of _all_);'; put '/* get source connection ID */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,connx_uri);'; put '/* get connection domain */'; put 'rc1=metadata_getnasn(connx_uri,''Domain'',1,domain_uri);'; put 'rc2=metadata_getattr(domain_uri,''Name'',domain);'; put '&mD.putlog / ''NOTE: '' // ''NOTE- connection id: '' connx_uri ;'; put '&mD.putlog ''NOTE- domain: '' domain;'; put '/* get DSN and PROVIDER from connection properties */'; put 'i=0;'; put 'do until (rc<0);'; put 'i+1;'; put 'rc=metadata_getnasn(connx_uri,''Properties'',i,conprop_uri);'; put 'rc2=metadata_getattr(conprop_uri,''Name'',value);'; put 'if value=''Connection.OLE.Property.DATASOURCE.Name.xmlKey.txt'' then do;'; put 'rc3=metadata_getattr(conprop_uri,''DefaultValue'',datasource);'; put 'end;'; put 'else if value=''Connection.OLE.Property.PROVIDER.Name.xmlKey.txt'' then do;'; put 'rc4=metadata_getattr(conprop_uri,''DefaultValue'',provider);'; put 'end;'; put 'else if value=''Connection.OLE.Property.PROPERTIES.Name.xmlKey.txt'' then'; put 'do;'; put 'rc5=metadata_getattr(conprop_uri,''DefaultValue'',properties);'; put 'end;'; put 'end;'; put '&mD.putlog ''NOTE- dsn/provider/properties: '' /'; put 'datasource provider properties;'; put '&mD.putlog ''NOTE- schema: '' schema // ''NOTE-'';'; put '/* get SCHEMA */'; put 'rc6=metadata_getnasn("&liburi",''UsingPackages'',1,lib_uri);'; put 'rc7=metadata_getattr(lib_uri,''SchemaName'',schema);'; put 'call symputx(''SQL_domain'',domain,''l'');'; put 'call symputx(''SQL_dsn'',datasource,''l'');'; put 'call symputx(''SQL_provider'',provider,''l'');'; put 'call symputx(''SQL_properties'',properties,''l'');'; put 'call symputx(''SQL_schema'',schema,''l'');'; put 'run;'; put '%if %length(&open_passthrough)>0 %then %do;'; put 'proc sql &sql_options;'; put 'connect to OLEDB as &open_passthrough(INSERT_SQL=YES'; put '/* need additional properties to make this work */'; put 'properties=(''Integrated Security''=SSPI'; put '''Persist Security Info''=True'; put '%sysfunc(compress(%str(&SQL_properties),%str(())))'; put ')'; put 'DATASOURCE=&sql_dsn PROMPT=NO'; put 'PROVIDER=&sql_provider SCHEMA=&sql_schema CONNECTION = GLOBAL);'; put '%end;'; put '%else %do;'; put 'LIBNAME &libref OLEDB PROPERTIES=&sql_properties'; put 'DATASOURCE=&sql_dsn PROVIDER=&sql_provider SCHEMA=&sql_schema'; put '%if %length(&sql_domain)>0 %then %do;'; put 'authdomain="&sql_domain"'; put '%end;'; put 'connection=shared;'; put '%end;'; put '%end;'; put '%else %if &engine=ODBC %then %do;'; put '%&mD.put NOTE: Retrieving ODBC connection details;'; put 'data _null_;'; put 'length connx_uri conprop_uri value datasource up_uri schema domprop_uri authdomain $256.;'; put 'call missing (of _all_);'; put '/* get source connection ID */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,connx_uri);'; put '/* get connection properties */'; put 'i=0;'; put 'do until (rc2<0);'; put 'i+1;'; put 'rc2=metadata_getnasn(connx_uri,''Properties'',i,conprop_uri);'; put 'rc3=metadata_getattr(conprop_uri,''Name'',value);'; put 'if value=''Connection.ODBC.Property.DATASRC.Name.xmlKey.txt'' then do;'; put 'rc4=metadata_getattr(conprop_uri,''DefaultValue'',datasource);'; put 'rc2=-1;'; put 'end;'; put 'end;'; put '/* get auth domain */'; put 'autrc=metadata_getnasn(connx_uri,"Domain",1,domprop_uri);'; put 'arc=metadata_getattr(domprop_uri,"Name",authdomain);'; put 'if not missing(authdomain) then authdomain=cats(''AUTHDOMAIN='',authdomain);'; put 'call symputx(''authdomain'',authdomain,''l'');'; put '/* get SCHEMA */'; put 'rc6=metadata_getnasn("&liburi",''UsingPackages'',1,up_uri);'; put 'rc7=metadata_getattr(up_uri,''SchemaName'',schema);'; put '&mD.put rc= connx_uri= rc2= conprop_uri= rc3= value= rc4= datasource='; put 'rc6= up_uri= rc7= schema=;'; put 'call symputx(''SQL_schema'',schema,''l'');'; put 'call symputx(''SQL_dsn'',datasource,''l'');'; put 'run;'; put '%if %length(&open_passthrough)>0 %then %do;'; put 'proc sql &sql_options;'; put 'connect to ODBC as &open_passthrough'; put '(INSERT_SQL=YES DATASRC=&sql_dsn. CONNECTION=global);'; put '%end;'; put '%else %do;'; put 'libname &libref ODBC DATASRC=&sql_dsn SCHEMA=&sql_schema &authdomain;'; put '%end;'; put '%end;'; put '%else %if &engine=POSTGRES %then %do;'; put '%put NOTE: Obtaining POSTGRES library details;'; put 'data _null_;'; put 'length database ignore_read_only_columns direct_exe preserve_col_names'; put 'preserve_tab_names server schema authdomain user password'; put 'prop name value uri urisrc $256.;'; put 'call missing (of _all_);'; put '/* get database value */'; put 'prop=''Connection.DBMS.Property.DB.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,database,"");'; put 'if database^='''' then database=''database=''!!quote(trim(database));'; put 'call symputx(''database'',database,''l'');'; put '/* get IGNORE_READ_ONLY_COLUMNS value */'; put 'prop=''Library.DBMS.Property.DBIROC.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,ignore_read_only_columns,"");'; put 'if ignore_read_only_columns^='''' then ignore_read_only_columns='; put '''ignore_read_only_columns=''!!ignore_read_only_columns;'; put 'call symputx(''ignore_read_only_columns'',ignore_read_only_columns,''l'');'; put '/* get DIRECT_EXE value */'; put 'prop=''Library.DBMS.Property.DirectExe.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,direct_exe,"");'; put 'if direct_exe^='''' then direct_exe=''direct_exe=''!!direct_exe;'; put 'call symputx(''direct_exe'',direct_exe,''l'');'; put '/* get PRESERVE_COL_NAMES value */'; put 'prop=''Library.DBMS.Property.PreserveColNames.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,preserve_col_names,"");'; put 'if preserve_col_names^='''' then preserve_col_names='; put '''preserve_col_names=''!!preserve_col_names;'; put 'call symputx(''preserve_col_names'',preserve_col_names,''l'');'; put '/* get PRESERVE_TAB_NAMES value */'; put '/* be careful with PRESERVE_TAB_NAMES=YES - it will mean your table will'; put 'become case sensitive!! */'; put 'prop=''Library.DBMS.Property.PreserveTabNames.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,preserve_tab_names,"");'; put 'if preserve_tab_names^='''' then preserve_tab_names='; put '''preserve_tab_names=''!!preserve_tab_names;'; put 'call symputx(''preserve_tab_names'',preserve_tab_names,''l'');'; put '/* get SERVER value */'; put 'if metadata_getnasn("&liburi","LibraryConnection",1,uri)>0 then do;'; put 'prop=''Connection.DBMS.Property.SERVER.Name.xmlKey.txt'';'; put 'rc=metadata_getprop(uri,prop,server,"");'; put 'end;'; put 'if server^='''' then server=''server=''!!quote(cats(server));'; put 'call symputx(''server'',server,''l'');'; put '/* get SCHEMA value */'; put 'if metadata_getnasn("&liburi","UsingPackages",1,uri)>0 then do;'; put 'rc=metadata_getattr(uri,"SchemaName",schema);'; put 'end;'; put 'if schema^='''' then schema=''schema=''!!schema;'; put 'call symputx(''schema'',schema,''l'');'; put '/* get AUTHDOMAIN value */'; put '/* this is only useful if the user account contains that auth domain'; put 'if metadata_getnasn("&liburi","DefaultLogin",1,uri)>0 then do;'; put 'rc=metadata_getnasn(uri,"Domain",1,urisrc);'; put 'rc=metadata_getattr(urisrc,"Name",authdomain);'; put 'end;'; put 'if authdomain^='''' then authdomain=''authdomain=''!!quote(trim(authdomain));'; put '*/'; put 'call symputx(''authdomain'',authdomain,''l'');'; put '/* get user & pass */'; put 'if authdomain='''' & metadata_getnasn("&liburi","DefaultLogin",1,uri)>0 then'; put 'do;'; put 'rc=metadata_getattr(uri,"UserID",user);'; put 'rc=metadata_getattr(uri,"Password",password);'; put 'end;'; put 'if user^='''' then do;'; put 'user=''user=''!!quote(trim(user));'; put 'password=''password=''!!quote(trim(password));'; put 'end;'; put 'call symputx(''user'',user,''l'');'; put 'call symputx(''password'',password,''l'');'; put '&md.put _all_;'; put 'run;'; put '%if %length(&open_passthrough)>0 %then %do;'; put '%put %str(WARN)ING: Passthrough option for postgres not yet supported;'; put '%return;'; put '%end;'; put '%else %do;'; put '%if &mdebug=1 %then %do;'; put '%put NOTE: Executing the following:/;'; put '%put NOTE- libname &libref POSTGRES &database &ignore_read_only_columns;'; put '%put NOTE- &direct_exe &preserve_col_names &preserve_tab_names;'; put '%put NOTE- &server &schema &authdomain &user &password //;'; put '%end;'; put 'libname &libref POSTGRES &database &ignore_read_only_columns &direct_exe'; put '&preserve_col_names &preserve_tab_names &server &schema &authdomain'; put '&user &password;'; put '%end;'; put '%end;'; put '%else %if &engine=ORACLE %then %do;'; put '%put NOTE: Obtaining &engine library details;'; put 'data _null_;'; put 'length assocuri1 assocuri2 assocuri3 authdomain path schema $256;'; put 'call missing (of _all_);'; put '/* get auth domain */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,assocuri1);'; put 'rc=metadata_getnasn(assocuri1,''Domain'',1,assocuri2);'; put 'rc=metadata_getattr(assocuri2,"Name",authdomain);'; put 'call symputx(''authdomain'',authdomain,''l'');'; put '/* path */'; put 'rc=metadata_getprop(assocuri1,'; put '''Connection.Oracle.Property.PATH.Name.xmlKey.txt'',path);'; put 'call symputx(''path'',path,''l'');'; put '/* schema */'; put 'rc=metadata_getnasn("&liburi",''UsingPackages'',1,assocuri3);'; put 'rc=metadata_getattr(assocuri3,''SchemaName'',schema);'; put 'call symputx(''schema'',schema,''l'');'; put 'run;'; put '%put NOTE: Executing the following:/; %put NOTE-;'; put '%put NOTE- libname &libref ORACLE path=&path schema=&schema;'; put '%put NOTE- authdomain=&authdomain;'; put '%put NOTE-;'; put 'libname &libref ORACLE path=&path schema=&schema authdomain=&authdomain;'; put '%end;'; put '%else %if &engine=SQLSVR %then %do;'; put '%put NOTE: Obtaining &engine library details;'; put 'data _null;'; put 'length assocuri1 assocuri2 assocuri3 authdomain path schema userid'; put 'passwd $256;'; put 'call missing (of _all_);'; put 'rc=metadata_getnasn("&liburi",''DefaultLogin'',1,assocuri1);'; put 'rc=metadata_getattr(assocuri1,"UserID",userid);'; put 'rc=metadata_getattr(assocuri1,"Password",passwd);'; put 'call symputx(''user'',userid,''l'');'; put 'call symputx(''pass'',passwd,''l'');'; put '/* path */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,assocuri2);'; put 'rc=metadata_getprop(assocuri2,'; put '''Connection.SQL.Property.Datasrc.Name.xmlKey.txt'',path);'; put 'call symputx(''path'',path,''l'');'; put '/* schema */'; put 'rc=metadata_getnasn("&liburi",''UsingPackages'',1,assocuri3);'; put 'rc=metadata_getattr(assocuri3,''SchemaName'',schema);'; put 'call symputx(''schema'',schema,''l'');'; put 'run;'; put '%put NOTE: Executing the following:/; %put NOTE-;'; put '%put NOTE- libname &libref SQLSVR datasrc=&path schema=&schema ;'; put '%put NOTE- user="&user" pass="XXX";'; put '%put NOTE-;'; put 'libname &libref SQLSVR datasrc=&path schema=&schema user="&user" pass="&pass";'; put '%end;'; put '%else %if &engine=TERADATA %then %do;'; put '%put NOTE: Obtaining &engine library details;'; put 'data _null;'; put 'length assocuri1 assocuri2 assocuri3 authdomain path schema userid'; put 'passwd $256;'; put 'call missing (of _all_);'; put '/* get auth domain */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,assocuri1);'; put 'rc=metadata_getnasn(assocuri1,''Domain'',1,assocuri2);'; put 'rc=metadata_getattr(assocuri2,"Name",authdomain);'; put 'call symputx(''authdomain'',authdomain,''l'');'; put '/*'; put 'rc=metadata_getnasn("&liburi",''DefaultLogin'',1,assocuri1);'; put 'rc=metadata_getattr(assocuri1,"UserID",userid);'; put 'rc=metadata_getattr(assocuri1,"Password",passwd);'; put 'call symputx(''user'',userid,''l'');'; put 'call symputx(''pass'',passwd,''l'');'; put '*/'; put '/* path */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,assocuri2);'; put 'rc=metadata_getprop(assocuri2,'; put '''Connection.Teradata.Property.SERVER.Name.xmlKey.txt'',path);'; put 'call symputx(''path'',path,''l'');'; put '/* schema */'; put 'rc=metadata_getnasn("&liburi",''UsingPackages'',1,assocuri3);'; put 'rc=metadata_getattr(assocuri3,''SchemaName'',schema);'; put 'call symputx(''schema'',schema,''l'');'; put 'run;'; put '%put NOTE: Executing the following:/; %put NOTE-;'; put '%put NOTE- libname &libref TERADATA server="&path" schema=&schema ;'; put '%put NOTe- authdomain=&authdomain;'; put '%put NOTE-;'; put 'libname &libref TERADATA server="&path" schema=&schema authdomain=&authdomain;'; put '%end;'; put '%else %if &engine= %then %do;'; put '%put NOTE: Libref &libref is not registered in metadata;'; put '%&mAbort.mp_abort('; put 'msg=%str(ERR)OR: Libref &libref is not registered in metadata'; put ',mac=mm_assigndirectlib.sas);'; put '%return;'; put '%end;'; put '%else %do;'; put '%put %str(WARN)ING: Engine &engine is currently unsupported;'; put '%put %str(WARN)ING- Please contact your support team.;'; put '%return;'; put '%end;'; put '%mend mm_assigndirectlib;'; put '%macro mm_getrepos('; put 'outds=work.mm_getrepos'; put ')/*/STORE SOURCE*/;'; put '* use a temporary fileref to hold the response;'; put 'filename response temp;'; put '/* get list of libraries */'; put 'proc metadata in='; put '"1"'; put 'out=response;'; put 'run;'; put '/* write the response to the log for debugging */'; put '/*'; put 'data _null_;'; put 'infile response lrecl=1048576;'; put 'input;'; put 'put _infile_;'; put 'run;'; put '*/'; put '/* create an XML map to read the response */'; put 'filename sxlemap temp;'; put 'data _null_;'; put 'file sxlemap;'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository";'; put 'put "";'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Id";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Name";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Desc";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@DefaultNS";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@RepositoryType";'; put 'put "characterstring20";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@RepositoryFormat";'; put 'put "characterstring10";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@Access";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@CurrentAccess";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@PauseState";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Path";'; put 'put "";'; put 'put "characterstring256";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Engine";'; put 'put "";'; put 'put "characterstring8";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Options";'; put 'put "";'; put 'put "characterstring32";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@MetadataCreated";'; put 'put "characterstring24";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@MetadataUpdated";'; put 'put "characterstring24";'; put 'put '''';'; put 'put ''
'';'; put 'run;'; put 'libname _XML_ xml xmlfileref=response xmlmap=sxlemap;'; put 'proc sort data= _XML_.SASRepos out=&outds;'; put 'by name;'; put 'run;'; put '/* clear references */'; put 'filename sxlemap clear;'; put 'filename response clear;'; put 'libname _XML_ clear;'; put '%mend mm_getrepos;'; put '%macro dc_assignlib(type,libref,passthru=);'; put '%put &sysmacroname entry vars:;'; put '%put _local_;'; put '/* take current repository */'; put '%local repo repocnt x;'; put '%let repo=%sysfunc(getoption(metarepository));'; put '/* get list of repositories and filter */'; put '%mm_getrepos(outds=repos)'; put '%let repocnt=0;'; put 'data repos;'; put 'set repos;'; put 'where repositorytype in(''CUSTOM'',''FOUNDATION'')'; put '& upcase(name) ne "%upcase(&repo)";'; put 'keep id name ;'; put 'call symputx(cats(''repo'',_n_),name,''l'');'; put 'call symputx(''repocnt'',_n_,''l'');'; put 'run;'; put '/* find out which of these repositories has the libref we are searching for */'; put '%local lib_uri;'; put '%let lib_uri=NOTFOUND;'; put 'data _null_; /* check default repo first */'; put 'length lib_uri $256;'; put 'call missing (of _all_);'; put 'rc=metadata_getnobj("omsobj:SASLibrary?@Libref =''&libref''",1,lib_uri);'; put 'call symputx(''lib_uri'',coalescec(lib_uri,''NOTFOUND''),''l'');'; put 'run;'; put '%if &lib_uri=NOTFOUND %then %do;'; put '%do x=1 %to &repocnt;'; put 'options metarepository=&&repo&x;'; put 'data _null_;'; put 'length lib_uri $256;'; put 'call missing (of _all_);'; put 'rc=metadata_getnobj("omsobj:SASLibrary?@Libref =''&libref''",1,lib_uri);'; put 'call symputx(''lib_uri'',coalescec(lib_uri,''NOTFOUND''),''l'');'; put 'run;'; put '%if &lib_uri ne NOTFOUND %then %let x=%eval(&repocnt+1);'; put '%end;'; put '%end;'; put '%if &type=READ %then %do;'; put '%mm_assignlib(&libref)'; put '%end;'; put '%else %if &type=WRITE %then %do;'; put '%mm_assigndirectlib(&libref,open_passthrough=&passthru)'; put '%end;'; put 'options metarepository=&repo;'; put '%mend dc_assignlib;'; put '/** @cond */'; put '%macro mf_existvar(libds /* 2 part dataset name */'; put ', var /* variable name */'; put ')/*/STORE SOURCE*/;'; put '%local dsid rc;'; put '%let dsid=%sysfunc(open(&libds,is));'; put '%if &dsid=0 %then %do;'; put '%put %sysfunc(sysmsg());'; put '0'; put '%end;'; put '%else %if %length(&var)=0 %then %do;'; put '0'; put '%let rc=%sysfunc(close(&dsid));'; put '%end;'; put '%else %do;'; put '%sysfunc(varnum(&dsid,&var))'; put '%let rc=%sysfunc(close(&dsid));'; put '%end;'; put '%mend mf_existvar;'; put '/** @endcond */'; put '%macro mf_getattrn('; put 'libds'; put ',attr'; put ')/*/STORE SOURCE*/;'; put '%local dsid rc;'; put '%let dsid=%sysfunc(open(&libds,is));'; put '%if &dsid = 0 %then %do;'; put '%put %str(WARN)ING: Cannot open %trim(&libds), system message below;'; put '%put %sysfunc(sysmsg());'; put '-1'; put '%end;'; put '%else %do;'; put '%sysfunc(attrn(&dsid,&attr))'; put '%let rc=%sysfunc(close(&dsid));'; put '%end;'; put '%mend mf_getattrn;'; put '%macro mf_getvartype(libds /* two level name */'; put ', var /* variable name from which to return the type */'; put ')/*/STORE SOURCE*/;'; put '%local dsid vnum vtype rc;'; put '/* Open dataset */'; put '%let dsid = %sysfunc(open(&libds));'; put '%if &dsid. > 0 %then %do;'; put '/* Get variable number */'; put '%let vnum = %sysfunc(varnum(&dsid, &var));'; put '/* Get variable type (C/N) */'; put '%if(&vnum. > 0) %then %let vtype = %sysfunc(vartype(&dsid, &vnum.));'; put '%else %do;'; put '%put NOTE: Variable &var does not exist in &libds;'; put '%let vtype = %str( );'; put '%end;'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: dataset &libds not opened! (rc=&dsid);'; put '%put &sysmacroname: %sysfunc(sysmsg());'; put '%return;'; put '%end;'; put '/* Close dataset */'; put '%let rc = %sysfunc(close(&dsid));'; put '/* Return variable type */'; put '&vtype'; put '%mend mf_getvartype;'; put '%macro mf_getattrc('; put 'libds'; put ',attr'; put ')/*/STORE SOURCE*/;'; put '%local dsid rc;'; put '%let dsid=%sysfunc(open(&libds,is));'; put '%if &dsid = 0 %then %do;'; put '%put %str(WARN)ING: Cannot open %trim(&libds), system message below;'; put '%put %sysfunc(sysmsg());'; put '-1'; put '%end;'; put '%else %do;'; put '%sysfunc(attrc(&dsid,&attr))'; put '%let rc=%sysfunc(close(&dsid));'; put '%end;'; put '%mend mf_getattrc;'; put '%macro mp_lockfilecheck('; put 'libds'; put ')/*/STORE SOURCE*/;'; put 'data _null_;'; put 'if _n_=1 then putlog "&sysmacroname entry vars:";'; put 'set sashelp.vmacro;'; put 'where scope="&sysmacroname";'; put 'put name ''='' value;'; put 'run;'; put '%mp_abort(iftrue= (&syscc>0)'; put ',mac=checklock.sas'; put ',msg=Aborting with syscc=&syscc on entry.'; put ')'; put '%mp_abort(iftrue= ("&libds"="0")'; put ',mac=&sysmacroname'; put ',msg=%str(libds not provided)'; put ')'; put '%local msg lib ds;'; put '%let lib=%upcase(%scan(&libds,1,.));'; put '%let ds=%upcase(%scan(&libds,2,.));'; put '/* in DC, format catalogs are passed with a -FC suffix. No saslock here! */'; put '%if %scan(&libds,2,-)=FC %then %do;'; put '%put &sysmacroname: Format Catalog detected, no lockfile applied to &libds;'; put '%return;'; put '%end;'; put '/* do not proceed if no observations can be processed */'; put '%let msg=options obs = 0. syserrortext=%superq(syserrortext);'; put '%mp_abort(iftrue= (%sysfunc(getoption(OBS))=0)'; put ',mac=checklock.sas'; put ',msg=%superq(msg)'; put ')'; put 'data _null_;'; put 'putlog "Checking engine & member type";'; put 'run;'; put '%local engine memtype;'; put '%let memtype=%mf_getattrc(&libds,MTYPE);'; put '%let engine=%mf_getattrc(&libds,ENGINE);'; put '%if &engine ne V9 and &engine ne BASE %then %do;'; put 'data _null_;'; put 'putlog "Lib &lib is not assigned using BASE engine - uses &engine instead";'; put 'putlog "SAS lock check will not be performed";'; put 'run;'; put '%return;'; put '%end;'; put '%else %if &memtype ne DATA %then %do;'; put '%put NOTE: Cannot lock a VIEW!! Memtype=&memtype;'; put '%return;'; put '%end;'; put 'data _null_;'; put 'putlog "Engine = &engine, memtype=&memtype";'; put 'putlog "Attempting lock statement";'; put 'run;'; put 'lock &libds;'; put '%local abortme;'; put '%let abortme=0;'; put '%if &syscc>0 or &SYSLCKRC ne 0 %then %do;'; put '%let msg=Unable to apply lock on &libds (SYSLCKRC=&SYSLCKRC syscc=&syscc);'; put '%put %str(ERR)OR: &sysmacroname: &msg;'; put '%let abortme=1;'; put '%end;'; put 'lock &libds clear;'; put '%mp_abort(iftrue= (&abortme=1)'; put ',mac=&sysmacroname'; put ',msg=%superq(msg)'; put ')'; put '%mend mp_lockfilecheck;'; put '%macro mp_lockanytable('; put 'action'; put ',lib= WORK'; put ',ds=0'; put ',ref='; put ',ctl_ds=0'; put ',loops=25'; put ',loop_secs=1'; put ');'; put 'data _null_;'; put 'if _n_=1 then putlog "&sysmacroname entry vars:";'; put 'set sashelp.vmacro;'; put 'where scope="&sysmacroname";'; put 'put name ''='' value;'; put 'run;'; put '%mp_abort(iftrue= ("&ds"="0" and &action ne MAKETABLE)'; put ',mac=&sysmacroname'; put ',msg=%str(dataset was not provided)'; put ')'; put '%mp_abort(iftrue= (&ctl_ds=0)'; put ',mac=&sysmacroname'; put ',msg=%str(Control dataset was not provided)'; put ')'; put '/* set up lib & mac vars */'; put '%let lib=%upcase(&lib);'; put '%let ds=%upcase(&ds);'; put '%let action=%upcase(&action);'; put '%local user x trans msg abortme;'; put '%let user=%mf_getuser();'; put '%let abortme=0;'; put '%mp_abort(iftrue= (&action ne LOCK & &action ne UNLOCK & &action ne MAKETABLE)'; put ',mac=&sysmacroname'; put ',msg=%str(Invalid action (&action) provided)'; put ')'; put '/* if an err condition exists, exit before we even begin */'; put '%mp_abort(iftrue= (&syscc>0 and &action=LOCK)'; put ',mac=&sysmacroname'; put ',msg=%str(aborting due to syscc=&syscc on LOCK entry)'; put ')'; put '/* do not bother locking work tables (else may affect all WORK libraries) */'; put '%if (%upcase(&lib)=WORK or %str(&lib)=%str()) & &action ne MAKETABLE %then %do;'; put '%put NOTE: WORK libraries will not be registered in the locking system.;'; put '%return;'; put '%end;'; put '/* do not proceed if no observations can be processed */'; put '%mp_abort(iftrue= (%sysfunc(getoption(OBS))=0)'; put ',mac=&sysmacroname'; put ',msg=%str(cannot continue when options obs = 0)'; put ')'; put '%if &ACTION=LOCK %then %do;'; put '/* abort if a SAS lock is already in place, or cannot be applied */'; put '%mp_lockfilecheck(&lib..&ds)'; put '/* next, check there is a record for this table */'; put '%local record_exists_check;'; put 'proc sql noprint;'; put 'select count(*) into: record_exists_check from &ctl_ds'; put 'where LOCK_LIB ="&lib" and LOCK_DS="&ds";'; put 'quit;'; put '%if &syscc>0 %then %put syscc=&syscc sqlrc=&sqlrc;'; put '%if &record_exists_check=0 %then %do;'; put 'data _null_;'; put 'putlog "&sysmacroname: adding record to lock table..";'; put 'run;'; put 'data ;'; put 'if 0 then set &ctl_ds;'; put 'LOCK_LIB ="&lib";'; put 'LOCK_DS="&ds";'; put 'LOCK_STATUS_CD=''LOCKED'';'; put 'LOCK_START_DTTM="%sysfunc(datetime(),%mf_fmtdttm())"dt;'; put 'LOCK_USER_NM="&user";'; put 'LOCK_PID="&sysjobid";'; put 'LOCK_REF="&ref";'; put 'output;stop;'; put 'run;'; put '%let trans=&syslast;'; put 'proc append base=&ctl_ds data=&trans;'; put 'run;'; put '%end;'; put '/* if record does exist, perform lock attempts */'; put '%else %do x=1 %to &loops;'; put 'data _null_;'; put 'putlog "&sysmacroname: attempting lock (iteration &x) "@;'; put 'putlog "at %sysfunc(datetime(),datetime19.) ..";'; put 'run;'; put 'proc sql;'; put 'update &ctl_ds'; put 'set LOCK_STATUS_CD=''LOCKED'''; put ', LOCK_START_DTTM="%sysfunc(datetime(),%mf_fmtdttm())"dt'; put ', LOCK_USER_NM="&user"'; put ', LOCK_PID="&sysjobid"'; put ', LOCK_REF="&ref"'; put 'where LOCK_LIB ="&lib" and LOCK_DS="&ds";'; put 'quit;'; put '/**'; put '* NOTE - occasionally SQL server will return an err code (deadlocked'; put '* transaction). If so, ignore it, keep calm, and carry on..'; put '*/'; put '%if &syscc>0 %then %do;'; put 'data _null_;'; put 'putlog ''NOTE-'' / ''NOTE-'';'; put 'putlog "NOTE- &sysmacroname: Update failed. "@;'; put 'putlog "Resetting err conditions and re-attempting.";'; put 'putlog "NOTE- syscc=&syscc syserr=&syserr sqlrc=&sqlrc";'; put 'putlog ''NOTE-'' / ''NOTE-'';'; put 'run;'; put '%let syscc=0;'; put '%let sqlrc=0;'; put '%end;'; put '/* now check if the record was successfully updated */'; put '%local success_check;'; put 'proc sql noprint;'; put 'select count(*) into: success_check from &ctl_ds'; put 'where LOCK_LIB ="&lib" and LOCK_DS="&ds"'; put 'and LOCK_PID="&sysjobid" and LOCK_STATUS_CD=''LOCKED'';'; put 'quit;'; put '%if &success_check=0 %then %do;'; put '%if &x < &loops %then %do;'; put '/* pause before next check */'; put 'data _null_;'; put 'putlog ''NOTE-'' / ''NOTE-'';'; put 'putlog "NOTE- &sysmacroname: table locked, waiting "@;'; put 'putlog "%sysfunc(sleep(&loop_secs)) seconds.. ";'; put 'putlog "NOTE- (iteration &x of &loops)";'; put 'putlog ''NOTE-'' / ''NOTE-'';'; put 'run;'; put '%end;'; put '%else %do;'; put '%let msg=Unable to lock &lib..&ds via &ctl_ds after &loops attempts.\n'; put 'Please ask your administrator to investigate!;'; put '%let abortme=1;'; put '%end;'; put '%end;'; put '%else %do;'; put 'data _null_;'; put 'putlog ''NOTE-'' / ''NOTE-'';'; put 'putlog "NOTE- &sysmacroname: Table &lib..&ds locked at "@;'; put 'putlog " %sysfunc(datetime(),datetime19.) (iteration &x)"@;'; put 'putlog ''NOTE-'' / ''NOTE-'';'; put 'run;'; put '%if &syscc>0 %then %do;'; put '%put setting syscc(&syscc) back to 0;'; put '%let syscc=0;'; put '%end;'; put '%let x=&loops; /* no more iterations needed */'; put '%end;'; put '%end;'; put '%end;'; put '%else %if &ACTION=UNLOCK %then %do;'; put '%local status cnt;'; put '%let cnt=0;'; put 'proc sql noprint;'; put 'select count(*) into: cnt from &ctl_ds where LOCK_LIB ="&lib" & LOCK_DS="&ds";'; put '%if &cnt=0 %then %do;'; put '%put %str(WAR)NING: &lib..&ds was not previously locked in &ctl_ds!;'; put '%end;'; put '%else %do;'; put 'select LOCK_STATUS_CD into: status from &ctl_ds'; put 'where LOCK_LIB ="&lib" and LOCK_DS="&ds";'; put 'quit;'; put '%if &syscc>0 %then %put syscc=&syscc sqlrc=&sqlrc;'; put '%if &status=LOCKED %then %do;'; put 'data _null_;'; put 'putlog "&sysmacroname: unlocking &lib..&ds:";'; put 'run;'; put 'proc sql;'; put 'update &ctl_ds'; put 'set LOCK_STATUS_CD=''UNLOCKED'''; put ', LOCK_END_DTTM="%sysfunc(datetime(),%mf_fmtdttm())"dt'; put ', LOCK_USER_NM="&user"'; put ', LOCK_PID="&sysjobid"'; put ', LOCK_REF="&ref"'; put 'where LOCK_LIB ="&lib" and LOCK_DS="&ds";'; put 'quit;'; put '%end;'; put '%else %if &status=UNLOCKED %then %do;'; put '%put %str(WAR)NING: &lib..&ds is already unlocked!;'; put '%end;'; put '%else %do;'; put '%put NOTE: Unrecognised STATUS_CD (&status) in &ctl_ds;'; put '%let abortme=1;'; put '%end;'; put '%end;'; put '%end;'; put '%else %do;'; put '%let msg=lock_anytable given unsupported action (&action);'; put '%let abortme=1;'; put '%end;'; put '/* catch errs - mp_abort must be called outside of a logic block */'; put '%mp_abort(iftrue=(&abortme=1),'; put 'msg=%superq(msg),'; put 'mac=&sysmacroname'; put ')'; put '%exit_macro:'; put 'data _null_;'; put 'put "&sysmacroname: Exit vars: action=&action lib=&lib ds=&ds";'; put 'put " syscc=&syscc sqlrc=&sqlrc syserr=&syserr";'; put 'run;'; put '%mend mp_lockanytable;'; put '%macro bitemporal_closeouts('; put 'tech_from=tx_from_dttm'; put ',tech_to = tx_to_dttm /* Technical TO datetime variable.'; put 'Req''d on BASE table only. */'; put ',base_lib=WORK /* Libref of the BASE table. */'; put ',base_dsn=BASETABLE /* Name of BASE table. */'; put ',append_lib=WORK /* Libref of the STAGING table. */'; put ',append_dsn=APPENDTABLE /* Name of STAGING table. */'; put ',PK= name sex /* Business key, space separated. */'; put '/* Should INCLUDE BUS_FROM field if relevant. */'; put ',NOW=DEFINE'; put ',FILTER= /* supply a filter to limit the update */'; put ',outdest= /* supply an unquoted filepath/filename.ext to get'; put 'a text file containing the update statements */'; put ',loadtype='; put ',loadtarget=YES /* if <> YES will return without changing anything */'; put ');'; put '%put ENTERING &sysmacroname;'; put '%local x var start;'; put '%let start=%sysfunc(datetime());'; put '%dc_assignlib(WRITE,&base_lib)'; put '%dc_assignlib(WRITE,&append_lib)'; put '%if &now=DEFINE %then %let now=&dc_dttmtfmt.;'; put '%put &=now;'; put '/**'; put '* perform basic checks'; put '*/'; put '/* do tables exist? */'; put '%if not %sysfunc(exist(&base_lib..&base_dsn)) %then %do;'; put '%mp_abort(msg=&base_lib..&base_dsn does not exist)'; put '%end;'; put '%else %if %sysfunc(exist(&append_lib..&append_dsn))=0'; put 'and %sysfunc(exist(&append_lib..&append_dsn,VIEW))=0 %then %do;'; put '%mp_abort(msg=&append_lib..&append_dsn does not exist)'; put '%end;'; put '/* do TX columns exist? */'; put '%if &loadtype ne UPDATE %then %do;'; put '%if not %mf_existvar(&base_lib..&base_dsn,&tech_from) %then %do;'; put '%mp_abort(msg=&tech_from does not exist on &base_lib..&base_dsn)'; put '%end;'; put '%else %if not %mf_existvar(&base_lib..&base_dsn,&tech_to) %then %do;'; put '%mp_abort(msg=&tech_to does not exist on &base_lib..&base_dsn)'; put '%end;'; put '%end;'; put '/* do PK columns exist? */'; put '%do x=1 %to %sysfunc(countw(&PK));'; put '%let var=%scan(&pk,&x,%str( ));'; put '%if not %mf_existvar(&base_lib..&base_dsn,&var) %then %do;'; put '%mp_abort(msg=&var does not exist on &base_lib..&base_dsn)'; put '%end;'; put '%else %if not %mf_existvar(&append_lib..&append_dsn,&var) %then %do;'; put '%mp_abort(msg=&var does not exist on &append_lib..&append_dsn)'; put '%end;'; put '%end;'; put '/* check uniqueness */'; put 'proc sort data=&append_lib..&append_dsn'; put 'out=___closeout1 noduprecs dupout=___closeout1a;'; put 'by &pk;'; put 'run;'; put '%if %mf_getattrn(___closeout1a,NLOBS)>0 %then'; put '%put NOTE: dups on (&PK) in (&append_lib..&append_dsn);'; put '/* is &NOW value within a tolerance? Should not allow renegade closeouts.. */'; put '%local gap;'; put '%let gap=0;'; put 'data _null_;'; put 'now=&now;'; put 'gap=intck(''HOURS'',now,datetime());'; put 'call symputx(''gap'',gap,''l'');'; put 'run;'; put '%mf_abort('; put 'iftrue=(&gap > 24),'; put 'msg=NOW variable (&now) is not within a 24hr tolerance'; put ')'; put '/* have any warnings / errs occurred thus far? If so, abort */'; put '%mf_abort('; put 'iftrue=(&syscc>0),'; put 'msg=Aborted due to SYSCC=&SYSCC status'; put ')'; put '/**'; put '* Create closeout statements. These are sent as individual SQL statements'; put '* to ensure pass-through utilisation. The update_cnt variable monitors'; put '* how many records were actually updated on the target table.'; put '*/'; put '%local update_cnt;'; put '%let update_cnt=0;'; put 'filename tmp temp;'; put 'data _null_;'; put 'set ___closeout1;'; put 'file tmp;'; put 'if _n_=1 then put ''proc sql noprint;'' ;'; put 'length string $32767.;'; put '%if &loadtype=UPDATE %then %do;'; put 'put "delete from &base_lib..&base_dsn where 1";'; put '%end;'; put '%else %do;'; put 'now=symget(''now'');'; put 'put "update &base_lib..&base_dsn set &tech_to= " now @;'; put '%if %mf_existvar(&base_lib..&base_dsn,PROCESSED_DTTM) %then %do;'; put 'put " ,PROCESSED_DTTM=" now @;'; put '%end;'; put 'put " where " now " lt &tech_to ";'; put '%end;'; put '%do x=1 %to %sysfunc(countw(&PK));'; put '%let var=%scan(&pk,&x,%str( ));'; put '%if %mf_getvartype(&base_lib..&base_dsn,&var)=C %then %do;'; put '/* use single quotes to avoid ampersand resolution in data */'; put 'string=" & &var=''"!!trim(prxchange("s/''/''''/",-1,&var))!!"''";'; put '%end;'; put '%else %do;'; put 'string=cats(" & &var=",&var);'; put '%end;'; put 'put string;'; put '%end;'; put 'put "&filter ;";'; put 'put ''%let update_cnt=%eval(&update_cnt+&sqlobs);%put update_cnt=&update_cnt;'';'; put 'run;'; put 'data _null_;'; put 'infile tmp;'; put 'input;'; put 'putlog _infile_;'; put 'run;'; put '%if &loadtarget ne YES %then %return;'; put '/* ensure we have a lock */'; put '%mp_lockanytable(LOCK,'; put 'lib=&base_lib,ds=&base_dsn'; put ',ref=bitemporal_closeouts'; put ',ctl_ds=&mpelib..mpe_lockanytable'; put ')'; put 'options source2;'; put '%inc tmp;'; put 'filename tmp clear;'; put '/**'; put '* Update audit tracker'; put '*/'; put '%local newobs; %let newobs=%mf_getattrn(work.___closeout1,NLOBS);'; put '%local user; %let user=%mf_getuser();'; put 'proc sql;'; put 'insert into &mpelib..mpe_dataloads'; put 'set libref=%upcase("&base_lib")'; put ',DSN=%upcase("&base_dsn")'; put ',ETLSOURCE="&append_lib..&append_dsn contained &newobs records"'; put ',LOADTYPE="CLOSEOUT"'; put ',DELETED_RECORDS=&update_cnt'; put ',NEW_RECORDS=0'; put ',DURATION=%sysfunc(datetime())-&start'; put ',USER_NM="&user"'; put ',PROCESSED_DTTM=&now;'; put 'quit;'; put '%mend bitemporal_closeouts;'; put '%macro mf_existds(libds'; put ')/*/STORE SOURCE*/;'; put '%if %sysfunc(exist(&libds)) ne 1 & %sysfunc(exist(&libds,VIEW)) ne 1 %then 0;'; put '%else 1;'; put '%mend mf_existds;'; put '%macro mf_getschema(libref'; put ')/*/STORE SOURCE*/;'; put '%local dsid vnum rc schema;'; put '/* in case the parameter is a libref.tablename, pull off just the libref */'; put '%let libref = %upcase(%scan(&libref, 1, %str(.)));'; put '%let dsid=%sysfunc(open(sashelp.vlibnam(where=('; put 'libname="%upcase(&libref)" and sysname=''Schema/Owner'''; put ')),i));'; put '%if (&dsid ^= 0) %then %do;'; put '%let vnum=%sysfunc(varnum(&dsid,SYSVALUE));'; put '%let rc=%sysfunc(fetch(&dsid));'; put '%let schema=%sysfunc(getvarc(&dsid,&vnum));'; put '%put &libref. schema is &schema.;'; put '%let rc= %sysfunc(close(&dsid));'; put '%end;'; put '&schema'; put '%mend mf_getschema;'; put '/** @endcond */'; put '%macro mf_getuniquename(prefix=MC);'; put '&prefix.%substr(%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32-%length(&prefix))'; put '%mend mf_getuniquename;'; put '%macro mf_getvarlist(libds'; put ',dlm=%str( )'; put ',quote=no'; put ',typefilter=A'; put ')/*/STORE SOURCE*/;'; put '/* declare local vars */'; put '%local outvar dsid nvars x rc dlm q var vtype;'; put '/* credit Rowland Hale - byte34 is double quote, 39 is single quote */'; put '%if %upcase("e)=DOUBLE %then %let q=%qsysfunc(byte(34));'; put '%else %if %upcase("e)=SINGLE %then %let q=%qsysfunc(byte(39));'; put '/* open dataset in macro */'; put '%let dsid=%sysfunc(open(&libds));'; put '%if &dsid %then %do;'; put '%let nvars=%sysfunc(attrn(&dsid,NVARS));'; put '%if &nvars>0 %then %do;'; put '/* add variables with supplied delimeter */'; put '%do x=1 %to &nvars;'; put '/* get variable type */'; put '%let vtype=%sysfunc(vartype(&dsid,&x));'; put '%if &vtype=&typefilter or &typefilter=A %then %do;'; put '%let var=&q.%sysfunc(varname(&dsid,&x))&q.;'; put '%if &var=&q&q %then %do;'; put '%put &sysmacroname: Empty column found in &libds!;'; put '%let var=&q. &q.;'; put '%end;'; put '%if %quote(&outvar)=%quote() %then %let outvar=&var;'; put '%else %let outvar=&outvar.&dlm.&var.;'; put '%end;'; put '%end;'; put '%end;'; put '%let rc=%sysfunc(close(&dsid));'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: Unable to open &libds (rc=&dsid);'; put '%put &sysmacroname: SYSMSG= %sysfunc(sysmsg());'; put '%let rc=%sysfunc(close(&dsid));'; put '%end;'; put '%do;%unquote(&outvar)%end;'; put '%mend mf_getvarlist;'; put '%macro mf_abort(mac=mf_abort.sas, msg=, iftrue=%str(1=1)'; put ')/des=''ungraceful abort'' /*STORE SOURCE*/;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%put NOTE: /// mf_abort macro executing //;'; put '%if %length(&mac)>0 %then %put NOTE- called by &mac;'; put '%put NOTE - &msg;'; put '%abort;'; put '%mend mf_abort;'; put '/** @endcond */'; put '%macro mf_verifymacvars('; put 'verifyVars /* list of macro variable NAMES */'; put ',makeUpcase=NO /* set to YES to make all the variable VALUES uppercase */'; put ',mAbort=SOFT'; put ')/*/STORE SOURCE*/;'; put '%local verifyIterator verifyVar abortmsg;'; put '%do verifyIterator=1 %to %sysfunc(countw(&verifyVars,%str( )));'; put '%let verifyVar=%qscan(&verifyVars,&verifyIterator,%str( ));'; put '%if not %symexist(&verifyvar) %then %do;'; put '%let abortmsg= Variable &verifyVar is MISSING;'; put '%goto exit_err;'; put '%end;'; put '%if %length(%trim(&&&verifyVar))=0 %then %do;'; put '%let abortmsg= Variable &verifyVar is EMPTY;'; put '%goto exit_err;'; put '%end;'; put '%if &makeupcase=YES %then %do;'; put '%let &verifyVar=%upcase(&&&verifyvar);'; put '%end;'; put '%end;'; put '%goto exit_success;'; put '%exit_err:'; put '%put &abortmsg;'; put '%mf_abort(iftrue=(&mabort ne SOFT),'; put 'mac=mf_verifymacvars,'; put 'msg=%str(&abortmsg)'; put ')'; put '0'; put '%return;'; put '%exit_success:'; put '1'; put '%mend mf_verifymacvars;'; put '%macro mf_wordsInStr1ButNotStr2('; put 'Str1= /* string containing words to extract */'; put ',Str2= /* used to compare with the extract string */'; put ')/*/STORE SOURCE*/;'; put '%local count_base count_extr i i2 extr_word base_word match outvar;'; put '%if %length(&str1)=0 or %length(&str2)=0 %then %do;'; put '%put base string (str1)= &str1;'; put '%put compare string (str2) = &str2;'; put '%return;'; put '%end;'; put '%let count_base=%sysfunc(countw(&Str2));'; put '%let count_extr=%sysfunc(countw(&Str1));'; put '%do i=1 %to &count_extr;'; put '%let extr_word=%scan(&Str1,&i,%str( ));'; put '%let match=0;'; put '%do i2=1 %to &count_base;'; put '%let base_word=%scan(&Str2,&i2,%str( ));'; put '%if &extr_word=&base_word %then %let match=1;'; put '%end;'; put '%if &match=0 %then %let outvar=&outvar &extr_word;'; put '%end;'; put '&outvar'; put '%mend mf_wordsInStr1ButNotStr2;'; put '%macro mf_isblank(param'; put ')/*/STORE SOURCE*/;'; put '%sysevalf(%superq(param)=,boolean)'; put '%mend mf_isblank;'; put '%macro mp_dropmembers('; put 'list /* space separated list of datasets / views */'; put ',libref=WORK /* can only drop from a single library at a time */'; put ',iftrue=%str(1=1)'; put ')/*/STORE SOURCE*/;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%if %mf_isblank(&list) %then %do;'; put '%put NOTE: nothing to drop!;'; put '%return;'; put '%end;'; put 'proc datasets lib=&libref nolist;'; put 'delete &list;'; put 'delete &list /mtype=view;'; put 'run;'; put '%mend mp_dropmembers;'; put '%macro mf_getquotedstr(IN_STR'; put ',DLM=%str(,)'; put ',QUOTE=S'; put ',indlm=%str( )'; put ')/*/STORE SOURCE*/;'; put '/* credit Rowland Hale - byte34 is double quote, 39 is single quote */'; put '%if "e=S %then %let quote=%qsysfunc(byte(39));'; put '%else %if "e=D %then %let quote=%qsysfunc(byte(34));'; put '%else %if "e=N %then %let quote=;'; put '%local i item buffer;'; put '%let i=1;'; put '%do %while (%qscan(&IN_STR,&i,%str(&indlm)) ne %str() ) ;'; put '%let item=%qscan(&IN_STR,&i,%str(&indlm));'; put '%if %bquote("E) ne %then %let item="E%qtrim(&item)"E;'; put '%else %let item=%qtrim(&item);'; put '%if (&i = 1) %then %let buffer =%qtrim(&item);'; put '%else %let buffer =&buffer&DLM%qtrim(&item);'; put '%let i = %eval(&i+1);'; put '%end;'; put '%let buffer=%sysfunc(coalescec(%qtrim(&buffer),"E"E));'; put '&buffer'; put '%mend mf_getquotedstr;'; put '%macro mf_nobs(libds'; put ')/*/STORE SOURCE*/;'; put '%mf_getattrn(&libds,NLOBS)'; put '%mend mf_nobs;'; put '%macro mp_retainedkey('; put 'base_lib=WORK'; put ',base_dsn=BASETABLE'; put ',append_lib=WORK'; put ',append_dsn=APPENDTABLE'; put ',retained_key=DEFAULT_RK'; put ',business_key= PK1 PK2'; put ',check_uniqueness=NO'; put ',maxkeytable=0'; put ',locktable=0'; put ',outds=WORK.APPEND'; put ',filter_str='; put ');'; put '%put &sysmacroname entry vars:;'; put '%put _local_;'; put '%local base_libds app_libds key_field check maxkey idx_pk newkey_cnt iserr'; put 'msg x tempds1 tempds2 comma_pk appnobs checknobs dropvar tempvar idx_val;'; put '%let base_libds=%upcase(&base_lib..&base_dsn);'; put '%let app_libds=%upcase(&append_lib..&append_dsn);'; put '%let tempds1=%mf_getuniquename();'; put '%let tempds2=%mf_getuniquename();'; put '%let comma_pk=%mf_getquotedstr(in_str=%str(&business_key),dlm=%str(,),quote=);'; put '%let outds=%sysfunc(ifc(%index(&outds,.)=0,work.&outds,&outds));'; put '/* validation checks */'; put '%let iserr=0;'; put '%if &syscc>0 %then %do;'; put '%let iserr=1;'; put '%let msg=%str(SYSCC=&syscc on macro entry);'; put '%end;'; put '%else %if %sysfunc(exist(&base_libds))=0 %then %do;'; put '%let iserr=1;'; put '%let msg=%str(Base LIBDS (&base_libds) expected but NOT FOUND);'; put '%end;'; put '%else %if %sysfunc(exist(&app_libds))=0 %then %do;'; put '%let iserr=1;'; put '%let msg=%str(Append LIBDS (&app_libds) expected but NOT FOUND);'; put '%end;'; put '%else %if &maxkeytable ne 0 and %sysfunc(exist(&maxkeytable))=0 %then %do;'; put '%let iserr=1;'; put '%let msg=%str(Maxkeytable (&maxkeytable) expected but NOT FOUND);'; put '%end;'; put '%else %if &maxkeytable ne 0 and %sysfunc(exist(&locktable))=0 %then %do;'; put '%let iserr=1;'; put '%let msg=%str(Locktable (&locktable) expected but NOT FOUND);'; put '%end;'; put '%else %if %length(&business_key)=0 %then %do;'; put '%let iserr=1;'; put '%let msg=%str(Business key (&business_key) expected but NOT FOUND);'; put '%end;'; put '%do x=1 %to %sysfunc(countw(&business_key));'; put '/* check business key values exist */'; put '%let key_field=%scan(&business_key,&x,%str( ));'; put '%if not %mf_existvar(&app_libds,&key_field) %then %do;'; put '%let iserr=1;'; put '%let msg=Business key (&key_field) not found on &app_libds!;'; put '%goto err;'; put '%end;'; put '%else %if not %mf_existvar(&base_libds,&key_field) %then %do;'; put '%let iserr=1;'; put '%let msg=Business key (&key_field) not found on &base_libds!;'; put '%goto err;'; put '%end;'; put '%end;'; put '%err:'; put '%if &iserr=1 %then %do;'; put '/* err case so first perform an unlock of the base table before exiting */'; put '%mp_lockanytable('; put 'UNLOCK,lib=&base_lib,ds=&base_dsn,ref=%superq(msg),ctl_ds=&locktable'; put ')'; put '%end;'; put '%mp_abort(iftrue=(&iserr=1),mac=mp_retainedkey,msg=%superq(msg))'; put 'proc sql noprint;'; put 'select sum(max(&retained_key),0) into: maxkey from &base_libds;'; put '/**'; put '* get base table RK and bus field values for lookup'; put '*/'; put 'proc sql noprint;'; put 'create table &tempds1 as'; put 'select distinct &comma_pk,&retained_key'; put 'from &base_libds &filter_str'; put 'order by &comma_pk,&retained_key;'; put '%if &check_uniqueness=YES %then %do;'; put 'select count(*) into:checknobs'; put 'from (select distinct &comma_pk from &app_libds);'; put 'select count(*) into: appnobs from &app_libds; /* might be view */'; put '%if &checknobs ne &appnobs %then %do;'; put '%let msg=Source table &app_libds is not unique on (&business_key);'; put '%let iserr=1;'; put '%end;'; put '%end;'; put '%if &iserr=1 %then %do;'; put '/* err case so first perform an unlock of the base table before exiting */'; put '%mp_lockanytable('; put 'UNLOCK,lib=&base_lib,ds=&base_dsn,ref=%superq(msg),ctl_ds=&locktable'; put ')'; put '%end;'; put '%mp_abort(iftrue= (&iserr=1),mac=mp_retainedkey,msg=%superq(msg))'; put '%if %mf_existvar(&app_libds,&retained_key)'; put '%then %let dropvar=(drop=&retained_key);'; put '/* prepare interim table with retained key populated for matching keys */'; put 'proc sql noprint;'; put 'create table &tempds2 as'; put 'select b.&retained_key, a.*'; put 'from &app_libds &dropvar a'; put 'left join &tempds1 b'; put 'on 1'; put '%do idx_pk=1 %to %sysfunc(countw(&business_key));'; put '%let idx_val=%scan(&business_key,&idx_pk);'; put 'and a.&idx_val=b.&idx_val'; put '%end;'; put 'order by &retained_key;'; put '/* identify the number of entries without retained keys (new records) */'; put 'select count(*) into: newkey_cnt'; put 'from &tempds2'; put 'where missing(&retained_key);'; put 'quit;'; put '/**'; put '* Update maxkey table if link provided'; put '*/'; put '%if &maxkeytable ne 0 %then %do;'; put 'proc sql noprint;'; put 'select count(*) into: check from &maxkeytable'; put 'where upcase(keytable)="&base_libds";'; put '%mp_lockanytable(LOCK'; put ',lib=%scan(&maxkeytable,1,.)'; put ',ds=%scan(&maxkeytable,2,.)'; put ',ref=Updating maxkeyvalues with mp_retainedkey'; put ',ctl_ds=&locktable'; put ')'; put 'proc sql;'; put '%if &check=0 %then %do;'; put 'insert into &maxkeytable'; put 'set keytable="&base_libds"'; put ',keycolumn="&retained_key"'; put ',max_key=%eval(&maxkey+&newkey_cnt)'; put ',processed_dttm="%sysfunc(datetime(),%mf_fmtdttm())"dt;'; put '%end;'; put '%else %do;'; put 'update &maxkeytable'; put 'set max_key=%eval(&maxkey+&newkey_cnt)'; put ',processed_dttm="%sysfunc(datetime(),%mf_fmtdttm())"dt'; put 'where keytable="&base_libds";'; put '%end;'; put '%mp_lockanytable(UNLOCK'; put ',lib=%scan(&maxkeytable,1,.)'; put ',ds=%scan(&maxkeytable,2,.)'; put ',ref=Updating maxkeyvalues with maxkey=%eval(&maxkey+&newkey_cnt)'; put ',ctl_ds=&locktable'; put ')'; put '%end;'; put '/* fill in the missing retained key values */'; put '%let tempvar=%mf_getuniquename();'; put 'data &outds(drop=&tempvar);'; put 'retain &tempvar %eval(&maxkey+1);'; put 'set &tempds2;'; put 'if &retained_key =. then &retained_key=&tempvar;'; put '&tempvar=&tempvar+1;'; put 'run;'; put '%mend mp_retainedkey;'; put '/** @cond */'; put '%macro mp_storediffs(libds'; put ',origds'; put ',key'; put ',delds=0'; put ',appds=0'; put ',modds=0'; put ',outds=work.mp_storediffs'; put ',loadref=0'; put ',processed_dttm=0'; put ',mdebug=0'; put ')/*/STORE SOURCE*/;'; put '%local dbg;'; put '%if &mdebug=1 %then %do;'; put '%put &sysmacroname entry vars:;'; put '%put _local_;'; put '%end;'; put '%else %let dbg=*;'; put '/* set up unique and temporary vars */'; put '%local ds1 ds2 ds3 ds4 hashkey inds_auto inds_keep dslist vlist;'; put '%let ds1=%upcase(work.%mf_getuniquename(prefix=mpsd_ds1));'; put '%let ds2=%upcase(work.%mf_getuniquename(prefix=mpsd_ds2));'; put '%let ds3=%upcase(work.%mf_getuniquename(prefix=mpsd_ds3));'; put '%let ds4=%upcase(work.%mf_getuniquename(prefix=mpsd_ds4));'; put '%let hashkey=%upcase(%mf_getuniquename(prefix=mpsd_hashkey));'; put '%let inds_auto=%upcase(%mf_getuniquename(prefix=mpsd_inds_auto));'; put '%let inds_keep=%upcase(%mf_getuniquename(prefix=mpsd_inds_keep));'; put '%let dslist=&origds;'; put '%if &delds ne 0 %then %do;'; put '%let delds=%upcase(&delds);'; put '%if %scan(&delds,-1,.)=&delds %then %let delds=WORK.&delds;'; put '%let dslist=&dslist &delds;'; put '%end;'; put '%if &appds ne 0 %then %do;'; put '%let appds=%upcase(&appds);'; put '%if %scan(&appds,-1,.)=&appds %then %let appds=WORK.&appds;'; put '%let dslist=&dslist &appds;'; put '%end;'; put '%if &modds ne 0 %then %do;'; put '%let modds=%upcase(&modds);'; put '%if %scan(&modds,-1,.)=&modds %then %let modds=WORK.&modds;'; put '%let dslist=&dslist &modds;'; put '%end;'; put '%let origds=%upcase(&origds);'; put '%if %scan(&origds,-1,.)=&origds %then %let origds=WORK.&origds;'; put '%let key=%upcase(&key);'; put '/* hash the key and append all the tables (marking the source) */'; put 'data &ds1;'; put 'set &dslist indsname=&inds_auto;'; put '&hashkey=put(md5(catx(''|'',%mf_getquotedstr(&key,quote=N))),$hex32.);'; put '&inds_keep=upcase(&inds_auto);'; put 'proc sort;'; put 'by &inds_keep &hashkey;'; put 'run;'; put '/* transpose numeric & char vars */'; put 'proc transpose data=&ds1'; put 'out=&ds2(rename=(&hashkey=key_hash _name_=tgtvar_nm col1=newval_num));'; put 'by &inds_keep &hashkey;'; put 'var _numeric_;'; put 'run;'; put 'proc transpose data=&ds1'; put 'out=&ds3('; put 'rename=(&hashkey=key_hash _name_=tgtvar_nm col1=newval_char)'; put 'where=(tgtvar_nm not in ("&hashkey","&inds_keep"))'; put ');'; put 'by &inds_keep &hashkey;'; put 'var _character_;'; put 'run;'; put '%if %index(&libds,-)>0 and %scan(&libds,2,-)=FC %then %do;'; put '/* this is a format catalog - cannot query cols directly */'; put '%let vlist="TYPE","FMTNAME","FMTROW","START","END","LABEL","MIN","MAX"'; put ',"DEFAULT","LENGTH","FUZZ","PREFIX","MULT","FILL","NOEDIT","SEXCL"'; put ',"EEXCL","HLO","DECSEP","DIG3SEP","DATATYPE","LANGUAGE";'; put '%end;'; put '%else %let vlist=%mf_getvarlist(&libds,dlm=%str(,),quote=DOUBLE);'; put 'data &ds4;'; put 'length &inds_keep $41 tgtvar_nm $32 _label_ $256;'; put 'if _n_=1 then call missing(_label_);'; put 'drop _label_;'; put 'set &ds2 &ds3 indsname=&inds_auto;'; put 'tgtvar_nm=upcase(tgtvar_nm);'; put 'if tgtvar_nm in (%upcase(&vlist));'; put 'if upcase(&inds_auto)="&ds2" then tgtvar_type=''N'';'; put 'else if upcase(&inds_auto)="&ds3" then tgtvar_type=''C'';'; put 'else do;'; put 'putlog ''ERR'' +(-1) "OR: unidentified vartype input!" &inds_auto;'; put 'call symputx(''syscc'',98);'; put 'end;'; put 'if &inds_keep="&appds" then move_type=''A'';'; put 'else if &inds_keep="&delds" then move_type=''D'';'; put 'else if &inds_keep="&modds" then move_type=''M'';'; put 'else if &inds_keep="&origds" then move_type=''O'';'; put 'else do;'; put 'putlog ''ERR'' +(-1) "OR: unidentified movetype input!" &inds_keep;'; put 'call symputx(''syscc'',99);'; put 'end;'; put 'tgtvar_nm=upcase(tgtvar_nm);'; put 'if tgtvar_nm in (%mf_getquotedstr(&key)) then is_pk=1;'; put 'else is_pk=0;'; put 'drop &inds_keep;'; put 'run;'; put '%if "&loadref"="0" %then %let loadref=%sysfunc(uuidgen());'; put '%if &processed_dttm=0 %then %let processed_dttm=%sysfunc(datetime());'; put '%let libds=%upcase(&libds);'; put '/* join orig vals for modified & deleted */'; put 'proc sql;'; put 'create table &outds as'; put 'select "&loadref" as load_ref length=36'; put ',&processed_dttm as processed_dttm format=E8601DT26.6'; put ',"%scan(&libds,1,.)" as libref length=8'; put ',"%scan(&libds,2,.)" as dsn length=32'; put ',b.key_hash length=32'; put ',b.move_type length=1'; put ',b.tgtvar_nm length=32'; put ',b.is_pk'; put ',case when b.move_type ne ''M'' then -1'; put 'when a.newval_num=b.newval_num and a.newval_char=b.newval_char then 0'; put 'else 1'; put 'end as is_diff'; put ',b.tgtvar_type length=1'; put ',case when b.move_type=''D'' then b.newval_num'; put 'else a.newval_num'; put 'end as oldval_num format=best32.'; put ',case when b.move_type=''D'' then .'; put 'else b.newval_num'; put 'end as newval_num format=best32.'; put ',case when b.move_type=''D'' then b.newval_char'; put 'else a.newval_char'; put 'end as oldval_char length=32765'; put ',case when b.move_type=''D'' then '''''; put 'else b.newval_char'; put 'end as newval_char length=32765'; put 'from &ds4(where=(move_type=''O'')) as a'; put 'right join &ds4(where=(move_type ne ''O'')) as b'; put 'on a.tgtvar_nm=b.tgtvar_nm'; put 'and a.key_hash=b.key_hash'; put 'order by move_type, key_hash,is_pk desc, tgtvar_nm;'; put '%if &mdebug=0 %then %do;'; put 'proc sql;'; put 'drop table &ds1, &ds2, &ds3, &ds4;'; put '%end;'; put '%mend mp_storediffs;'; put '/** @endcond */'; put '%macro bitemporal_dataloader('; put 'bus_from= /* Business FROM datetime variable. Req''d on'; put 'STAGING & BASE tables.*/'; put ',bus_to = /* Business TO datetime variable. Req''d on'; put 'STAGING & BASE tables. */'; put ',bus_from_override= /* Provide a hard coded BUS_FROM datetime value.*/'; put ',bus_to_override= /* provide a hard coded BUS_TO datetime value */'; put ',tech_from= /* Technical FROM datetime variable. Req''d on'; put 'BASE table only. */'; put ',tech_to = /* Technical TO datetime variable. Req''d on BASE'; put 'table only. */'; put ',processed= 0'; put ',base_lib=WORK /* Libref of the BASE table. */'; put ',base_dsn=BASETABLE /* Name of BASE table. */'; put ',append_lib=WORK /* Libref of the STAGING table. */'; put ',append_dsn=APPENDTABLE'; put ',high_date=''01JAN5999:00:00:00''dt /* High date to close out records */'; put ',PK= name sex'; put ',RK_UNDERLYING='; put ',KEEPVARS= /* Provides option for removing unwanted vars from append table */'; put ',RK_UPDATE_MAXKEYTABLE=NO /* If switching (or mix matching) with regular'; put 'SCD2 loader then set this switch to YES to'; put 'ensure the MAXKEYTABLE is updated with the'; put 'current maximum RK value for the target table'; put '*/'; put ',CHECK_UNIQUENESS=YES /* Perform a check of the APPEND table to ensure it is'; put 'unique on its business key */'; put ',ETLSOURCE=demo /* supply a value ($50.) to show as ETLSOURCE in'; put '&dclib..DATALOADS */'; put ',LOADTYPE=BITEMPORAL'; put ',RK_MAXKEYTABLE= mpe_maxkeyvalues'; put ',LOG=1 /* Switch to 0 to prevent records being added to'; put '&mpelib..mpe_DATALOADS (ie when testing)*/'; put ',DELETE_COL= _____DELETE__THIS__RECORD_____'; put '/* If this variable is found in the append dataset'; put 'then records are closed out (or deleted) in the'; put 'append table where that variable= "Yes" */'; put ',LOADTARGET=YES /* set to anything but uppercase YES to switch off'; put 'target table load and generate temp tables only */'; put ',CLOSE_VARS='; put '/*a problem with regular SCD2 or TXTEMPORAL loads is that there is'; put 'no facility to close out removed records (all records are'; put 'assumed new or changed). But how does one determine which'; put 'records are removed? Short of loading the entire table'; put 'each time? This parameter allows a set of variables'; put '(this should be a subset of the PK) to be declared, and'; put 'the macro will determine which records in the base table'; put 'need to be closed out ahead of the load.'; put 'For instance, given the following:'; put 'Base Table Staging Table'; put 'DATE ENTITY AMOUNT DATE ENTITY AMOUNT'; put 'JAN ACME4 66 JAN ACME4 66'; put 'FEB ACME4 99 FEB ACME4 99'; put 'FEB ACME1 22'; put 'By supplying DATE in CLOSE_VARS and DATE ENTITY as the PK,'; put 'the "FEB PAG 22" record would get closed out.'; put '*/'; put ',config_table=&dclib..MPE_CONFIG'; put ',dclib=&dc_libref'; put ',outds_del=work.outds_del'; put ',outds_add=work.outds_add'; put ',outds_mod=work.outds_mod'; put ',outds_audit=0'; put ');'; put '/* when changing this macro, update the version num here */'; put '%local ver;'; put '%let ver=32;'; put '%put &sysmacroname entry vars:;'; put '%put _local_;'; put '%dc_assignlib(WRITE,&base_lib) /* may not already be assigned */'; put '/* return straight away if nothing to load */'; put '%let nobs= %mf_getattrn(&append_lib..&append_dsn,NLOBS);'; put '%if &nobs=-1 %then %do;'; put 'proc sql noprint; select count(*) into: nobs from &append_lib..&append_dsn;'; put '%end;'; put '%if &nobs=0 %then %do;'; put '%put NOTE:; %put NOTE-;%put NOTE-;%put NOTE-;'; put '%put NOTE- Base dataset &append_lib..&append_dsn is empty. Nothing to upload!;'; put '%put NOTE-;%put NOTE-;%put NOTE-;'; put '%return;'; put '%end;'; put '/* hard exit if err condition exists */'; put '%mp_abort(iftrue= (&syscc > 0)'; put ',mac=bitemporal_dataloader'; put ',msg=%str(Bitemporal transform / job aborted due to SYSCC=&SYSCC status;)'; put ')'; put '%local engine_type;'; put '%let engine_type=%mf_getengine(&base_lib);'; put '%if (&engine_type=REDSHIFT or &engine_type=POSTGRES) and %length(&CLOSE_VARS)>0'; put '%then %do;'; put '%put NOTE:; %put NOTE-;%put NOTE-;%put NOTE-;'; put '%put NOTE- CLOSE_VARS functionality not yet supported in &engine_type;'; put '%put NOTE-;%put NOTE-;%put NOTE-;'; put '%return;'; put '%end;'; put '/**'; put '* The metadata functions (eg mf_existvar) will fail if the base table has a'; put '* SAS lock. So, make a snapshot of the base table for further use.'; put '* Also, make output tables (regardless).'; put '*/'; put '%local basecopy;'; put '%let basecopy=%mf_getuniquename(prefix=basecopy);'; put 'data &basecopy &outds_mod &outds_add &outds_del;'; put 'set &base_lib..&base_dsn;'; put 'stop;'; put 'run;'; put '%mp_abort(iftrue= (&syscc > 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc after base table copy - aborting due to table lock)'; put ')'; put '%local cols idx_pk md5_col ;'; put '%let md5_col=___TMP___md5;'; put '%let check_uniqueness=%upcase(&check_uniqueness);'; put '%let RK_UPDATE_MAXKEYTABLE=%upcase(&RK_UPDATE_MAXKEYTABLE);'; put '%let high_date=%unquote(&high_date);'; put '%let loadtype=%upcase(&loadtype);'; put '/* ensure irrelevant variables are cleared */'; put '%if &loadtype=BUSTEMPORAL %then %do;'; put '%let tech_from=;'; put '%let tech_to=;'; put '%end;'; put '%else %if &loadtype=TXTEMPORAL or &loadtype=UPDATE %then %do;'; put '%let bus_from=;'; put '%let bus_to=;'; put '%end;'; put '/* ensure relevant variables are supplied */'; put '%mp_abort(iftrue=(&loadtype=BITEMPORAL & %mf_verifymacvars(bus_from bus_to)=0)'; put ',mac=bitemporal_dataloader'; put ',msg=%str(Missing BUS_FROM / BUS_TO)'; put ')'; put '%mp_abort(iftrue=(&loadtype=TXTEMPORAL & %mf_verifymacvars(tech_from tech_to)=0)'; put ',mac=bitemporal_dataloader'; put ',msg=%str(Missing TECH_FROM / TECH_TO)'; put ')'; put '/**'; put '* drop any tables (may be defined as views or vice versa preventing overwrite)'; put '*/'; put '%mp_dropmembers(append bitemp0_append bitemp_cols)'; put '/* SQL Server requires its own time values */'; put '/* 9.2 will only give picture format down to seconds. 9.3 allows'; put 'milliseconds by using lower S and defining the decimal in the format name..*/'; put 'PROC FORMAT;'; put 'picture MyMSdt other=''%0Y-%0m-%0dT%0H:%0M:%0S'' (datatype=datetime);'; put 'RUN;'; put '%local dbnow;'; put '%let dbnow="%sysfunc(datetime(),%mf_fmtdttm())"dt;'; put 'data _null_;'; put '/* convert space separated macvar to comma separated for SQL processing */'; put 'call symputx(''PK_COMMA'',tranwrd(compbl("&pk"),'' '','',''),''L'');'; put 'call symputx(''PK_CNT'',countw("&pk",'' ''),''L'');'; put 'now=&dbnow;'; put 'call symputx(''NOW'',now,''L'');'; put 'call symputx(''SQLNOW'',cats("''",put(now,MyMSdt.),"''"),''L'');'; put 'length etlsource $100;'; put 'etlsource=subpad(symget(''etlsource''),1,100);'; put 'call symputx(''etlsource'',etlsource,''l'');'; put 'run;'; put '/**'; put '* Even if no PROCESSED var provided, assume that any variable named'; put '* PROCESSED_DTTM should be updated'; put '*/'; put '%if &processed=0 %then %do;'; put '%if %mf_existvar(&basecopy,PROCESSED_DTTM)'; put '%then %let processed=PROCESSED_DTTM;'; put '%else %let processed=;'; put '%end;'; put '/* extract colnames for md5 creation / change tracking */'; put 'proc contents noprint data=&base_lib..&base_dsn'; put 'out=work.bitemp_cols (keep=name type length varnum format:);'; put 'run;'; put 'proc sql noprint;'; put 'select name into: cols separated by '','''; put 'from work.bitemp_cols'; put 'where upcase(name) not in'; put '(%upcase("&bus_from","&bus_to"'; put ',"&tech_from","&tech_to"'; put ',"&processed","&delete_col")) ;'; put 'select case when type in (2,6) then cats(''put(md5(trim('',name,'')),$hex32.)'')'; put '/* multiply by 1 to strip precision errors (eg 0 != 0) */'; put '/* but ONLY if not missing, else will lose any special missing values */'; put 'else cats(''put(md5(trim(put(ifn(missing('''; put ',name,''),'',name,'','',name,''*1),binary64.))),$hex32.)'') end'; put 'into: stripcols separated by ''||'''; put 'from work.bitemp_cols'; put 'where upcase(name) not in'; put '(%upcase("&bus_from","&bus_to"'; put ',"&tech_from","&tech_to"'; put ',"&processed","&delete_col")) ;'; put '/* set default formats*/'; put '%let bus_from_fmt = datetime19.;'; put '%let bus_to_fmt = datetime19.;'; put '%let processed_fmt = datetime19.;'; put '%let tech_from_fmt = format=datetime19.;'; put '%let tech_to_fmt = format=datetime19.;'; put '%put &=stripcols;'; put '%put &=pk;'; put 'data _null_;'; put 'set work.bitemp_cols;'; put 'if type=2 or type=6 then do;'; put 'length fmt $49.;'; put 'if format='''' then fmt=cats(''$'',length,''.'');'; put 'else fmt=cats(format,formatl,''.'');'; put 'end;'; put 'else do;'; put 'if format='''' then fmt=cats(length,''.'');'; put 'else fmt=cats(format,formatl,''.'',formatd);'; put 'end;'; put 'if upcase(name)="%upcase(&bus_from)" then'; put 'call symputx(''bus_from_fmt'',fmt,''L'');'; put 'else if upcase(name)="%upcase(&bus_to)" then'; put 'call symputx(''bus_to_fmt'',fmt,''L'');'; put 'else if upcase(name)="%upcase(&tech_from)" then'; put 'call symputx(''tech_from_fmt'',"format="!!fmt,''L'');'; put 'else if upcase(name)="%upcase(&tech_to)" then'; put 'call symputx(''tech_to_fmt'',"format="!!fmt,''L'');'; put 'else if upcase(name)="%upcase(&processed)" then'; put 'call symputx(''processed_fmt'',fmt,''L'');'; put 'run;'; put '%if %index(%quote(&cols),___TMP___) %then %do;'; put '%let msg=%str(Table contains a variable name containing "___TMP___".%trim('; put ') This may conflict with temp variable generation!!);'; put '%mp_abort(msg=&msg,mac=bitemporal_dataloader);'; put '%let syscc=5;'; put '%return;'; put '%end;'; put '/* if transaction dates appear on the APPEND table, need to remove them */'; put '%local drop_tx_dates /* used in append table */'; put 'drop_tx_dates_noobs /* used to take the base table structure */;'; put '%if %mf_existvar(&append_lib..&append_dsn, &tech_from)'; put '%then %let drop_tx_dates=&tech_from;'; put '%if %mf_existvar(&append_lib..&append_dsn, &tech_to)'; put '%then %let drop_tx_dates=&drop_tx_dates &tech_to;'; put '%if %length(%trim(&drop_tx_dates))>0'; put '%then %let drop_tx_dates=(drop=&drop_tx_dates);'; put '%if %mf_existvar(&basecopy, &tech_from)'; put '%then %let drop_tx_dates_noobs=&tech_from;'; put '%if %mf_existvar(&basecopy, &tech_to)'; put '%then %let drop_tx_dates_noobs=&drop_tx_dates_noobs &tech_to;'; put '%if %length(%trim(&drop_tx_dates_noobs))>0'; put '%then %let drop_tx_dates_noobs=(drop=&drop_tx_dates_noobs obs=0);'; put '%else %let drop_tx_dates_noobs=(obs=0);'; put '/**'; put '* Lock the table. This is necessary as we are doing a two part update (first'; put '* closing records then appending new records). It is theoretically possible'; put '* that an upload may occur whilst preparing the staging tables. And the'; put '* staging tables are about to be prepared..'; put '*/'; put '%if &LOADTARGET = YES %then %do;'; put '%put locking &base_lib..&base_dsn;'; put '%mp_lockanytable(LOCK,'; put 'lib=&base_lib,ds=&base_dsn,ref=&ETLSOURCE,ctl_ds=&dclib..mpe_lockanytable'; put ')'; put '%if "&outds_audit" ne "0" %then %do;'; put '%put locking &outds_audit;'; put '%mp_lockanytable(LOCK'; put ',lib=%scan(&outds_audit,1,.)'; put ',ds=%scan(&outds_audit,2,.)'; put ',ref=&ETLSOURCE'; put ',ctl_ds=&dclib..mpe_lockanytable'; put ')'; put '%end;'; put '%end;'; put '%else %do;'; put '/* not an actual load, so avoid updating the max key table in next step. */'; put '%let rk_update_maxkeytable=NO;'; put '%end;'; put '%if %length(&RK_UNDERLYING)>0 %then %do;'; put '%mp_retainedkey('; put 'base_lib=&base_lib'; put ',base_dsn=&base_dsn'; put ',append_lib=&append_lib'; put ',append_dsn=&append_dsn'; put ',retained_key=&pk'; put ',business_key=&rk_underlying'; put ',check_uniqueness=&CHECK_UNIQUENESS'; put ',outds=work.append'; put '%if &rk_update_maxkeytable=NO %then %do;'; put ',maxkeytable=0'; put '%end;'; put '%else %do;'; put ',maxkeytable=&dclib..&RK_MAXKEYTABLE'; put '%end;'; put ',locktable=&dclib..mpe_lockanytable'; put '%if &loadtype=BITEMPORAL or &loadtype=TXTEMPORAL %then %do;'; put ',filter_str=%str( (where=( &now < &tech_to)) )'; put '%end;'; put ')'; put '%end;'; put '%else %do;'; put 'proc sql;'; put 'create view work.append as select * from &append_lib..&append_dsn;'; put '%end;'; put '/**'; put '* generate md5 for append table'; put '*/'; put '/* it is possible the source dataset has additional (unwanted) columns.'; put 'Drop if specified; */'; put '%if %length(&keepvars)>0 %then %do;'; put '/* remove tech dates from keepvars as they are generated later */'; put '%let keepvars=%sysfunc(tranwrd(%str( &keepvars ),%str( &tech_from ),%str( )));'; put '%let keepvars=%sysfunc(tranwrd(%str( &keepvars ),%str( &tech_to ),%str( )));'; put '%let keepvars=(keep=&keepvars &bus_from &bus_to &processed &md5_col);'; put '%end;'; put '/* CAS varchar types cause append issues here, so perform autoconvert'; put 'by creating empty local table first */'; put 'data;'; put 'set &base_lib..&base_dsn &drop_tx_dates_noobs;'; put 'run;'; put '%local emptybasetable; %let emptybasetable=&syslast;'; put 'data work.bitemp0_append &keepvars &outds_del(drop=&md5_col )'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5" %then %do;'; put '/nonote2err'; put '%end;'; put ';'; put '/* apply formats for bitemporal vars but not tx dates which are added later */'; put '%if %length(&keepvars)>0 and &loadtype=BITEMPORAL %then %do;'; put 'format &bus_from &bus_from_fmt;'; put 'format &bus_to &bus_to_fmt;'; put '%end;'; put 'set &emptybasetable /* base table reqd in case append has fewer cols */'; put 'work.append &drop_tx_dates;'; put '%if %length(%str(&bus_from_override))>0 %then %do;'; put '&bus_from= %unquote(&bus_from_override) ;'; put '%end;'; put '%if %length(%str(&bus_to_override))>0 %then %do;'; put '&bus_to= %unquote(&bus_to_override) ;'; put '%end;'; put 'length &md5_col $32;'; put '&md5_col=put(md5(&stripcols),hex32.);'; put '%if %length(&processed)>0 %then %do;'; put 'format &processed &processed_fmt;'; put '&processed=&now;'; put '%end;'; put '/**'; put '* If a delete column exists then create the delete dataset'; put '*/'; put '%if %mf_existvar(&append_lib..&append_dsn, &delete_col) %then %do;'; put 'drop &delete_col;'; put 'if upcase(&delete_col) = "YES" then output &outds_del ;'; put 'else output work.bitemp0_append ;'; put 'run;'; put '%if %mf_getattrn(&outds_del,NLOBS)>0 %then %do;'; put '%bitemporal_closeouts('; put 'tech_from=&tech_from'; put ',tech_to = &tech_to'; put ',base_lib=&base_lib'; put ',base_dsn=&base_dsn'; put ',append_lib=work'; put ',append_dsn=%scan(&outds_del,-1,.)'; put ',PK=&bus_from &pk'; put ',NOW=&dbnow'; put ',loadtarget=&loadtarget'; put ',loadtype=&loadtype'; put ')'; put '%end;'; put '%end;'; put '%else %do;'; put 'output work.bitemp0_append;'; put 'run;'; put '%end;'; put '%mp_abort(iftrue= (&syscc gt 0 at line 494)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc)'; put ')'; put '%if %length(&close_vars)>0 %then %do;'; put '/**'; put '* need to close out records that are not provided'; put '*/'; put 'proc sql;'; put 'create table bitemp1_closevars1 as'; put 'select distinct a.%mf_getquotedstr(in_str=&pk,dlm=%str(,a.),quote=)'; put 'from &base_lib..&base_dsn a'; put 'inner join work.bitemp0_append b'; put 'on 1=1'; put '/* join on closevars key */'; put '%do idx_pk=1 %to %sysfunc(countw(&close_vars));'; put '%let idx_val=%scan(&close_vars,&idx_pk);'; put 'and a.&idx_val=b.&idx_val'; put '%end;'; put '/* filter base on tech dates if necessary */'; put '%if &loadtype=TXTEMPORAL %then %do;'; put 'where a.&tech_from <=&now and &now < a.&tech_to'; put '%end;'; put ';'; put 'create table bitemp1_closevars2 as'; put 'select distinct a.*'; put 'from bitemp1_closevars1 a'; put 'left join work.bitemp0_append b'; put 'on 1=1'; put '/* join on primary key */'; put '%do idx_pk=1 %to %sysfunc(countw(&pk));'; put '%let idx_val=%scan(&pk,&idx_pk);'; put 'and a.&idx_val=b.&idx_val'; put '%end;'; put '/* identify removed records by null value in a field in PK but not close_vars'; put '*/'; put 'where b.%scan('; put '%mf_wordsInStr1ButNotStr2(Str1=&pk,Str2=&close_vars),1,%str( )'; put ') IS NULL'; put ';'; put '%if %mf_getattrn(bitemp1_closevars2,NLOBS)>0 %then %do;'; put '%bitemporal_closeouts('; put 'tech_from=&tech_from'; put ',tech_to = &tech_to'; put ',base_lib=&base_lib'; put ',base_dsn=&base_dsn'; put ',append_lib=work'; put ',append_dsn=bitemp1_closevars2'; put ',PK=&bus_from &pk'; put ',NOW=&dbnow'; put ',loadtarget=&loadtarget'; put ',loadtype=&loadtype'; put ')'; put '%end;'; put '%end;'; put '/* return if nothing to load (was just deletes) */'; put '%if %mf_getattrn(work.bitemp0_append,NLOBS)=0 %then %do;'; put '%put NOTE:; %put NOTE-;%put NOTE-;%put NOTE-;'; put '%put NOTE- No updates - just deletes!;'; put '%put NOTE-;%put NOTE-;%put NOTE-;'; put '%end;'; put '/**'; put '* If applying manual overrides to business dates, then the input table MUST'; put '* be unique on the PK. Check, and if not - abort.'; put '*/'; put '%local msg;'; put '%if %length(&bus_from_override.&bus_to_override)>0 or &CHECK_UNIQUENESS=YES'; put '%then %do;'; put 'proc sort data=work.bitemp0_append out=work.bitemp0_check nodupkey;'; put 'by &pk;'; put 'run;'; put '%if %mf_getattrn(work.bitemp0_check,NLOBS)'; put 'ne %mf_getattrn(work.bitemp0_append,NLOBS)'; put '%then %do;'; put '%let msg=INPUT table &append_lib..&append_dsn is not unique on PK (&pk);'; put '%mp_lockanytable(UNLOCK,lib=&base_lib,ds=&base_dsn,ref=&ETLSOURCE (&msg),'; put 'ctl_ds=&dclib..mpe_lockanytable'; put ')'; put '%mp_lockanytable(UNLOCK'; put ',lib=%scan(&outds_audit,1,.)'; put ',ds=%scan(&outds_audit,2,.)'; put ',ref=&ETLSOURCE'; put ',ctl_ds=&dclib..mpe_lockanytable'; put ')'; put '%mp_abort(msg=&msg,mac=bitemporal_dataloader.sas);'; put '%end;'; put '%end;'; put '/**'; put '* extract from BASE table. Only want matching records, as could be very BIG.'; put '* New records are subsequently identified via left join and test for nulls.'; put '*/'; put '%local temp_table temp_table2 base_table baselib_schema;'; put '%put DCNOTE: Extracting matching observations from &base_lib..&base_dsn;'; put '%if &engine_type=OLEDB %then %do;'; put '%let temp_table=##BITEMP_&base_dsn;'; put '%if &loadtype=BITEMPORAL or &loadtype=TXTEMPORAL %then'; put '%let base_table=(select * from [dbo].&base_dsn'; put 'where convert(datetime,&SQLNOW) < &tech_to );'; put '%else %let base_table=[dbo].&base_dsn;'; put 'proc sql;'; put 'create table &base_lib.."&temp_table"n as'; put 'select * from work.bitemp0_append;'; put '/* open up a connection for pass through SQL */'; put '%dc_assignlib(WRITE,&base_lib,passthru=myAlias)'; put 'create table work.bitemp0_base as select * from connection to myAlias('; put '%end;'; put '%else %if &engine_type=REDSHIFT or &engine_type=POSTGRES %then %do;'; put '/* grab schema */'; put '%let baselib_schema=%mf_getschema(&base_lib);'; put '%if &baselib_schema.X ne X %then %let baselib_schema=&baselib_schema..;'; put '/* grab redshift config */'; put '%local redcnt; %let redcnt=0;'; put '%if &engine_type=REDSHIFT %then %do;'; put 'data _null_;'; put 'set &config_table(where=(var_scope=''DCBL_REDSH'' and var_active=1));'; put 'x+1;'; put 'call symputx(cats(''rednm'',x),var_value,''l'');'; put 'call symputx(cats(''redval'',x),var_value,''l'');'; put 'call symputx(''redcnt'',x,''l'');'; put 'run;'; put '%end;'; put '/* cannot persist temp tables so must create a temporary permanent table */'; put '%let temp_table=%mf_getuniquename(prefix=XDCTEMP);'; put '%if &loadtype=BITEMPORAL or &loadtype=TXTEMPORAL %then'; put '%let base_table=(select * from &baselib_schema.&base_dsn'; put 'where timestamp &sqlnow < &tech_to );'; put '%else %let base_table=&baselib_schema.&base_dsn;'; put '/* make empty table first - must clone & drop extra cols as autoload is bad */'; put '%dc_assignlib(WRITE,&base_lib,passthru=myAlias)'; put 'exec (create table &temp_table (like &baselib_schema.&base_dsn)) by myAlias;'; put '%if &engine_type=REDSHIFT %then %do;'; put 'exec (alter table &temp_table alter sortkey none) by myAlias;'; put '%end;'; put '%local dropcols;'; put '%let dropcols=%mf_wordsinstr1butnotstr2('; put 'str1=%upcase(%mf_getvarlist(&basecopy))'; put ',str2=%upcase(&pk)'; put ');'; put '%if %length(&dropcols>0) %then %do idx_pk=1 %to %sysfunc(countw(&dropcols));'; put '%put &=dropcols;'; put '%let idx_val=%scan(&dropcols,&idx_pk);'; put 'exec(alter table &temp_table drop column &idx_val;) by myAlias;'; put '%end;'; put 'exec (alter table &temp_table add column &md5_col varchar(32);) by myAlias;'; put '/* create view to strip formats and avoid warns in log */'; put 'data work.vw_bitemp0/view=work.vw_bitemp0;'; put 'set work.bitemp0_append(keep=&pk &md5_col);'; put 'format _all_;'; put 'run;'; put 'proc append base=&base_lib..&temp_table'; put '%if &engine_type=REDSHIFT %then %do;'; put '('; put '%do idx_pk=1 %to &redcnt;'; put '&&rednm&idx_pk = &&redval&idxpk'; put '%end;'; put ')'; put '%end;'; put 'data=work.vw_bitemp0 force nowarn;'; put 'run;'; put '/* open up a connection for pass through SQL */'; put '%dc_assignlib(WRITE,&base_lib,passthru=myAlias)'; put 'create table work.bitemp0_base as select * from connection to myAlias('; put '%end;'; put '%else %if &engine_type=CAS %then %do;'; put '%if &loadtype=BITEMPORAL or &loadtype=TXTEMPORAL %then'; put '%let base_table=&base_lib..&base_dsn'; put '(where=(&tech_from <=&now and &now < &tech_to));'; put '%else %let base_table=&base_lib..&base_dsn;'; put '%let temp_table=CASUSER.%mf_getuniquename(prefix=DC);'; put 'data &temp_table;'; put 'set work.bitemp0_append;'; put 'run;'; put '%let bitemp0base=CASUSER.%mf_getuniquename(prefix=DC);'; put 'proc fedsql sessref=dcsession;'; put 'create table &bitemp0base{options replace=true} as'; put '%end;'; put '%else %do;'; put '%let temp_table=work.bitemp0_append;'; put '%if &loadtype=BITEMPORAL or &loadtype=TXTEMPORAL %then'; put '%let base_table=&base_lib..&base_dsn'; put '(where=(&tech_from <=&now and &now < &tech_to));'; put '%else %let base_table=&base_lib..&base_dsn;'; put 'proc sql;'; put 'create table work.bitemp0_base as'; put '%end;'; put 'select a.&md5_col /* this identifies NEW records */'; put ', b.*'; put '/* assume first PK field cannot be null (if defined in a PK constraint then'; put 'it definitely cannot be null) */'; put ', case when b.%scan(&pk,1) IS NULL then 1 else 0 end as ___TMP___NEW_FLG'; put 'from &baselib_schema.&temp_table a'; put 'left join &base_table b'; put 'on 1=1'; put '%do idx_pk=1 %to &pk_cnt;'; put '%let idx_val=%scan(&pk,&idx_pk);'; put 'and a.&idx_val=b.&idx_val'; put '%end;'; put '%if &engine_type=OLEDB or &engine_type=REDSHIFT or &engine_type=POSTGRES'; put '%then %do;'; put '); proc sql; drop table &base_lib.."&temp_table"n;'; put '%end;'; put '%else %if &engine_type=CAS %then %do;'; put ';'; put 'quit;'; put 'data work.bitemp0_base;'; put 'set &bitemp0base;'; put 'run;'; put 'proc sql;'; put 'drop table &temp_table;'; put 'drop table &bitemp0base;'; put '%end;'; put '%else %do;'; put ';'; put '%end;'; put '/**'; put '* matching & changed records are those without NULL key values'; put '* &idx_val resolves to rightmost PK value (loop above)'; put '*/'; put '%put syscc (line525)=&syscc, sqlrc=&sqlrc;'; put '%mp_abort(iftrue= (&syscc gt 0 or &sqlrc>0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc sqlrc=&sqlrc)'; put ')'; put '%put hashcols2=&stripcols;'; put 'proc sql;'; put 'create table work.bitemp1_current(drop=___TMP___NEW_FLG) as'; put 'select *'; put ', put(md5(&stripcols),$hex32.) as &md5_col'; put 'from work.bitemp0_base (drop=&md5_col)'; put 'where ___TMP___NEW_FLG=0;'; put '/**'; put '* NEW records were identified in ___TMP___NEW_FLG in bitemp0_base'; put '*/'; put 'proc sql;'; put 'create table &outds_add'; put '(drop=&md5_col'; put '%if %mf_existvar(work.bitemp0_base, &delete_col) %then %do;'; put '&delete_col'; put '%end;'; put ')'; put 'as select a.*'; put '%if &loadtype=BITEMPORAL or &loadtype=TXTEMPORAL %then %do;'; put ',&now as &tech_from &tech_from_fmt'; put ',&high_date as &tech_to &tech_to_fmt'; put '%end;'; put 'from work.bitemp0_append a /* STAGING records (mix of existing & new) */'; put ', work.bitemp0_base b /* BASE records (contains null values for new) */'; put 'where a.&md5_col=b.&md5_col /* took staging md5 across in left join */'; put 'and b.___TMP___NEW_FLG=1; /* NEW records also identified in bitemp0_base */'; put '/**'; put '* identify INSERTS. These are records with the same business key but'; put '* the bus_from and bus_to value are higher / lower (respectively)'; put '* such that the existing record needs to be SPLIT to surround the new'; put '* record.'; put '* eg: OLD RECORD from=1 to=10'; put '* NEW RECORD from=5 to=7'; put '*'; put '* APPENDED RECORDS:'; put '* - from=1 to=5'; put '* - from=5 to=7'; put '* - from=7 to=10'; put '*/'; put '/* inserts cannot happen with TXTEMPORAL */'; put '%if &loadtype=BITEMPORAL or &loadtype=BUSTEMPORAL %then %do;'; put '/* IDENTIFY */'; put 'create table work.bitemp3_inserts as'; put 'select b.*'; put ',a.&bus_from as ___TMP___from'; put ',a.&bus_to as ___TMP___to'; put 'from work.bitemp0_append a'; put ',work.bitemp1_current b'; put 'where a.&bus_from > b.&bus_from'; put 'and a.&bus_to < b.&bus_to'; put '%do idx_pk=1 %to &pk_cnt;'; put '%let idx_val=%scan(&pk,&idx_pk);'; put 'and a.&idx_val=b.&idx_val'; put '%end;'; put 'order by'; put '/* compress blanks and then insert commas (as the datetime fields may'; put 'not be in use) */'; put '%sysfunc(tranwrd(%sysfunc(compbl('; put '&pk &bus_from &bus_to &processed'; put ')),%str( ), %str(,)))'; put ';'; put '/* SPLIT */'; put 'data work.bitemp3a_inserts (drop=___TMP___from ___TMP___retain ___TMP___to) ;'; put 'set work.bitemp3_inserts;'; put 'by &pk &bus_from &bus_to &processed;'; put 'if first.&idx_val then do;'; put '___TMP___retain=&bus_to;'; put '&bus_to=___TMP___from;'; put 'output;'; put '&bus_to=___TMP___retain;'; put 'end;'; put 'if last.&idx_val then do;'; put '&bus_from=___TMP___to;'; put 'output;'; put 'end;'; put 'run;'; put '%end;'; put '%else %do;'; put '/* TX temporal load */'; put 'data work.bitemp3a_inserts;'; put 'set work.bitemp1_current;'; put 'stop;'; put 'run;'; put '%end;'; put '/* APPEND */'; put 'proc sql;'; put 'create view work.bitemp3a_view as'; put 'select * from work.bitemp1_current'; put 'where &md5_col not in (select &md5_col from work.bitemp3a_inserts);'; put 'data bitemp3b_newbase;'; put 'set work.bitemp3a_inserts work.bitemp3a_view;'; put 'run;'; put '/** do not use! this converts short numerics into 8 bytes'; put 'proc sql;'; put 'create table work.bitemp3b_newbase as'; put 'select * from work.bitemp3a_inserts'; put 'union corr'; put 'select * from work.bitemp1_current'; put 'where &md5_col not in (select &md5_col from work.bitemp3a_inserts);'; put '*/'; put '/**'; put '* identify CHANGED records from staging.'; put '* Same business key with different temporal dates or md5 value'; put '* This table must be overlayed onto / into existing business history'; put '*/'; put 'proc sql;'; put 'create table work.bitemp4_updated as select distinct a.*'; put 'from work.bitemp0_append a'; put ',work.bitemp3b_newbase b'; put 'where 1=1'; put '%do idx_pk=1 %to &pk_cnt;'; put '%let idx_val=%scan(&pk,&idx_pk);'; put 'and a.&idx_val=b.&idx_val'; put '%end;'; put 'and ( a.&md5_col ne b.&md5_col'; put '%if &loadtype=BITEMPORAL or &loadtype=BUSTEMPORAL %then %do;'; put 'OR (a.&bus_from ne b.&bus_from or a.&bus_to ne b.&bus_to)'; put '%end;'; put ')'; put ';'; put '/**'; put '* This section would have been one simple step with union all'; put '* but that converts short numerics into 8 bytes!'; put '* so, convoluted alternative to retain the same functionality.'; put '*/'; put '/* base records */'; put 'create view work.bitemp4_prep1 as'; put 'select ''BASE'' as ___TMP___'; put ',b.*'; put 'from work.bitemp4_updated a'; put ',work.bitemp3b_newbase b'; put 'where 1'; put '%do idx_pk=1 %to &pk_cnt;'; put '%let idx_val=%scan(&pk,&idx_pk);'; put 'and a.&idx_val=b.&idx_val'; put '%end;'; put ';'; put '/* updated records */'; put 'create view work.bitemp4_prep2 as'; put 'select ''STAG'' as ___TMP___ ,*'; put 'from work.bitemp4_updated;'; put '/* ensure we only keep columns that appear in both */'; put '%local bp1 bp2 bp3 bp4;'; put '%let bp1=%mf_getvarlist(bitemp4_prep1);'; put '%let bp2=%mf_getvarlist(bitemp4_prep2);'; put '%let bp3=%mf_wordsInStr1ButNotStr2(Str1=&bp1,Str2=&bp2);'; put '%let bp4=%mf_wordsInStr1ButNotStr2(Str1=&bp2,Str2=&bp1);'; put 'data work.bitemp4_prep3/view=bitemp4_prep3;'; put 'set bitemp4_prep1 bitemp4_prep2;'; put '%if %length(XX&bp3&bp4)>2 %then %do;'; put 'drop &bp3 &bp4 ;'; put '%end;'; put 'run;'; put '/* remove duplicates */'; put 'proc sql;'; put 'create table work.bitemp4a_allrecs as'; put 'select distinct *'; put 'from work.bitemp4_prep3'; put 'order by'; put '/* compress blanks and then insert commas (as the datetime fields'; put 'may not be in use) */'; put '%sysfunc(tranwrd(%sysfunc(compbl('; put '&pk &bus_from &bus_to &processed'; put ')),%str( ), %str(,)))'; put ';'; put '%if &loadtype=BITEMPORAL or &loadtype=BUSTEMPORAL %then %do;'; put '/* this section aligns the business dates'; put '(eg for inserts or overlaps in the range) */'; put 'data work.bitemp4b_firstpass (drop=___TMP___cond ___TMP___from ___TMP___to );'; put 'set work.bitemp4a_allrecs;'; put 'by &pk &bus_from &bus_to &processed;'; put 'retain ___TMP___cond ''Name of Condition'';'; put 'retain ___TMP___from ___TMP___to 0;'; put '___TMP___md5lag=lag(&md5_col);'; put '/* reset retained variables */'; put 'if first.&idx_val then do;'; put 'call missing (___TMP___cond, ___TMP___from, ___TMP___to,___TMP___md5lag);'; put 'end;'; put 'else do;'; put '/* if record is identical, carry forward bus_from (and bus_to if higher)*/'; put 'if &md5_col=___TMP___md5lag then do;'; put '&bus_from=___TMP___from;'; put 'if &bus_to<___TMP___to then &bus_to=___TMP___to;'; put 'end;'; put 'end;'; put 'if ___TMP___=''STAG'' then do;'; put '/* need to carry forward the closing record */'; put '___TMP___cond=''Condition 1'';'; put 'end;'; put 'else if ___TMP___cond=''Condition 1'' then do;'; put '/* else ensure bus_from starts from prior record bus_to */'; put 'if &md5_col ne ___TMP___md5lag and &bus_from <= ___TMP___to'; put 'then &bus_from= ___TMP___to;'; put '/* new record may replace old record entirely */'; put 'if &bus_to <= &bus_from then delete;'; put 'else call missing (___TMP___cond, ___TMP___from, ___TMP___to);'; put 'end;'; put '___TMP___from=&bus_from;'; put '___TMP___to=&bus_to;'; put 'run;'; put '%end;'; put '%else %do;'; put '/* keep staged records only */'; put 'data work.bitemp4b_firstpass;'; put 'set work.bitemp4a_allrecs;'; put 'if ___TMP___=''STAG'';'; put 'run;'; put '%end;'; put '/* next phase is to pass through in reverse - so set up the sort statement */'; put '%local byvar;'; put '%do idx_pk=1 %to &pk_cnt;'; put '%let byvar=&byvar descending %scan(&pk,&idx_pk);'; put '%end;'; put '%if &loadtype=BITEMPORAL or &loadtype=BUSTEMPORAL'; put '%then %let byvar=&byvar descending &bus_from descending &bus_to;'; put '/* if matching bus dates supplied, need to ensure we also have a sort'; put 'between BASE and STAGING tables */'; put '%let byvar=&byvar descending ___TMP___;'; put 'proc sort data=work.bitemp4b_firstpass out=work.bitemp4c_sort ;'; put 'by &byvar;'; put 'run;'; put '/**'; put '* Now (in reverse) pass back business start dates'; put '*/'; put 'data work.bitemp4d_secondpass;'; put '%if &loadtype=BITEMPORAL or &loadtype=TXTEMPORAL %then %do;'; put '&tech_from=&now;'; put '&tech_to=&high_date;'; put '%end;'; put 'set work.bitemp4c_sort ;'; put 'by &byvar;'; put 'retain ___TMP___cond ''Name of Condition'';'; put 'retain ___TMP___from ___TMP___to 0;'; put '%if &loadtype=BITEMPORAL or &loadtype=BUSTEMPORAL %then %do;'; put '/* put / _all_ /;*/'; put '___TMP___md5lag=lag(&md5_col);'; put 'if first.&idx_val then do;'; put '/* reset retained variables */'; put 'call missing (___TMP___cond,___TMP___from,___TMP___to,___TMP___md5lag);'; put 'end;'; put 'else do;'; put '/* if record is identical, carry back bus_to */'; put 'if &md5_col=___TMP___md5lag then &bus_to=___TMP___to;'; put 'end;'; put 'if ___TMP___=''STAG'' then do;'; put '/* need to carry forward the closing record */'; put '___TMP___cond=''Condition 2'';'; put 'end;'; put 'else if ___TMP___cond=''Condition 2'' then do;'; put '/* else ensure bus_to stops at subsequent record bus_from */'; put 'if &md5_col ne ___TMP___md5lag and &bus_to >= ___TMP___from'; put 'then &bus_to= ___TMP___from;'; put '/* new record may replace old record entirely */'; put 'if &bus_from >= &bus_to then delete;'; put 'if &bus_from=___TMP___from and &bus_to=___TMP___to then delete;'; put 'else call missing (___TMP___cond, ___TMP___from, ___TMP___to);'; put 'end;'; put '___TMP___from=&bus_from;'; put '___TMP___to=&bus_to;'; put '%end;'; put 'run;'; put '%put syscc (line600)=&syscc;'; put '/**'; put 'There may still be some records (eg old business history) which have not'; put 'changed.'; put 'Need to identify these and remove from the append so they are not updated'; put 'unnecessarily. This is done by generating a new md5 (which INCLUDES the'; put 'business key) and any matching / identical records are split out (from those'; put 'that need to be updated).'; put '*/'; put '%if &loadtype=BITEMPORAL %then %do;'; put '%let cat_string=catx(''|'' ,&bus_from,&bus_to);'; put 'data bitemp5a_lkp (keep=&md5_col);'; put 'set bitemp0_base;'; put '/* for BITEMPORAL we need to compare business dates also */'; put '&md5_col=put(md5(&cat_string!!''|''!!&stripcols),$hex32.);'; put 'run;'; put 'data bitemp5b_updates;'; put 'set bitemp4d_secondpass;'; put 'if _n_=1 then do;'; put 'dcl hash md5_lkp(dataset:''bitemp5a_lkp'');'; put 'md5_lkp.definekey("&md5_col");'; put 'md5_lkp.definedone();'; put 'end;'; put '/* drop old md5 col as will rebuild with new business dates */'; put '&md5_col=put(md5(&cat_string!!''|''!!&stripcols),$hex32.) ;'; put 'if md5_lkp.check()=0 then delete;'; put 'run;'; put 'proc sql;'; put '/* get min bus from as will update (close out) all records from this point'; put '(for that PK)*/'; put 'create table work.bitemp5d_subquery as'; put 'select &pk_comma, min(&bus_from)as &bus_from, max(&bus_to) as &bus_to'; put 'from work.bitemp5b_updates'; put 'group by &pk_comma;'; put '/* index has a huge efficiency impact on upcoming nested subquery */'; put 'create index index1 on work.bitemp5d_subquery(&pk_comma,&bus_from, &bus_to);'; put '%let lastds=work.bitemp5b_updates;'; put '%end;'; put '%else %if &loadtype=TXTEMPORAL or &loadtype=UPDATE %then %do;'; put 'proc sql;'; put 'create table work.bitemp5d_subquery as'; put 'select distinct &pk_comma'; put 'from bitemp4d_secondpass;'; put '%let lastds=work.bitemp4d_secondpass;'; put '%end;'; put '%else %let lastds=work.bitemp4d_secondpass;'; put '/* create single append table (an overlapped pre-sert may be classed as'; put 'both an update AND a new record). Also create temp views that may be'; put 'used for pre-load analysis. */'; put 'data &outds_mod;'; put 'set &lastds(drop=___TMP___: &md5_col);'; put 'run;'; put 'data bitemp6_allrecs / view=bitemp6_allrecs;'; put 'set &outds_mod /* UPDATED records */'; put '&outds_add /* NEW records */;'; put 'run;'; put 'proc sort data=work.bitemp6_allrecs'; put 'out=work.bitemp6_unique'; put 'noduprec'; put 'dupout=work.xx_BADBADBAD;'; put 'by _all_;'; put 'run;'; put '/* we have all our temp tables now so exit if this is all that is needed */'; put '%if &LOADTARGET ne YES %then %return;'; put '/* also exit if an err condition exists */'; put '%if &syscc>0 %then %do;'; put '%put syscc=&syscc;'; put '%mp_lockanytable(UNLOCK,lib=&base_lib,ds=&base_dsn,ref=&ETLSOURCE,'; put 'ctl_ds=&dclib..mpe_lockanytable'; put ')'; put '%if "&outds_audit" ne "0" %then %do;'; put '%mp_lockanytable(UNLOCK'; put ',lib=%scan(&outds_audit,1,.)'; put ',ds=%scan(&outds_audit,2,.)'; put ',ref=&ETLSOURCE'; put ',ctl_ds=&dclib..mpe_lockanytable'; put ')'; put '%end;'; put '%end;'; put '%mp_abort(iftrue= (&syscc>0)'; put ',mac=&sysmacroname in &_program'; put ',msg=%str(Bitemporal transform / job aborted due to SYSCC=&SYSCC status)'; put ')'; put '/* final check - abort if a lock has appeared on the target or audit table */'; put '%mp_lockfilecheck(libds=&base_lib..&base_dsn)'; put '%if %mf_existds(&outds_audit) %then %do;'; put '%mp_lockfilecheck(libds=&outds_audit)'; put '%end;'; put '/**'; put '* STAGING TABLES PREPARED, ERR CONDITION TESTED FOR.. NOW TO LOAD!!'; put '*/'; put '/**'; put '* First, CLOSE OUT changed records (if not a REPLACE)'; put '* Note that SAS does not support ANSI standard for UPDATE with a join condition.'; put '* However - this can be worked around using a nested subquery..'; put '*/'; put 'data _null_;'; put 'putlog "&sysmacroname: CLOSEOUTS commencing";'; put 'run;'; put '%if %mf_getattrn(&lastds,NLOBS)=0 %then %do;'; put 'data _null_;'; put 'putlog "&sysmacroname: No closeouts needed";'; put 'run;'; put '%end;'; put '%else %if &engine_type=CAS %then %do;'; put '%mp_abort(iftrue= (&loadtype=BITEMPORAL or &loadtype=TXTEMPORAL)'; put ',mac=&sysmacroname in &_program'; put ',msg=%str(&loadtype not yet supported in CAS engine)'; put ')'; put '/* create temp table for deletions */'; put '%local delds;%let delds=%mf_getuniquename(prefix=DC);'; put 'data casuser.&delds;'; put 'set work.bitemp5d_subquery;'; put 'run;'; put '/* delete the records */'; put 'proc cas ;'; put 'table.deleteRows / table={'; put 'caslib="&base_lib",'; put 'name="&base_dsn",'; put 'where="1=1",'; put 'whereTable={caslib=''CASUSER'',name="&delds"}'; put '};'; put 'quit;'; put '/* drop temp table */'; put 'proc sql;'; put 'drop table CASUSER.&delds;'; put '%end;'; put '%else %if (&loadtype=BITEMPORAL or &loadtype=TXTEMPORAL or &loadtype=UPDATE)'; put '%then %do;'; put 'data _null_;'; put 'putlog "&sysmacroname: &loadtype operation using &engine_type engine";'; put 'run;'; put '%local flexinow;'; put 'proc sql;'; put '/* if OLEDB then create a temp table for efficiency */'; put '%local innertable;'; put '%if &engine_type=OLEDB %then %do;'; put '%let innertable=[##BITEMP_&base_dsn];'; put '%let top_table=[dbo].&base_dsn;'; put '%let flexinow=&SQLNOW;'; put 'create table &base_lib.."##BITEMP_&base_dsn"n as'; put 'select * from work.bitemp5d_subquery;'; put '/* open up a connection for pass through SQL */'; put '%dc_assignlib(WRITE,&base_lib,passthru=myAlias)'; put 'execute('; put '%end;'; put '%else %if &engine_type=REDSHIFT or &engine_type=POSTGRES %then %do;'; put '%let innertable=%mf_getuniquename(prefix=XDCTEMP);'; put '%let top_table=&baselib_schema.&base_dsn;'; put '%let flexinow=timestamp &SQLNOW;'; put '/* make empty table first - must clone & drop extra cols'; put 'as autoload is bad */'; put '%dc_assignlib(WRITE,&base_lib,passthru=myAlias)'; put 'exec (create table &innertable (like &baselib_schema.&base_dsn)) by myAlias;'; put '%if &engine_type=REDSHIFT %then %do;'; put 'exec (alter table &innertable alter sortkey none) by myAlias;'; put '%end;'; put '%let dropcols=%mf_wordsinstr1butnotstr2('; put 'str1=%upcase(%mf_getvarlist(&basecopy))'; put ',str2=%upcase(%mf_getvarlist(work.bitemp5d_subquery))'; put ');'; put '%if %length(&dropcols>0) %then %do idx_pk=1 %to %sysfunc(countw(&dropcols));'; put '%put &=dropcols;'; put '%let idx_val=%scan(&dropcols,&idx_pk);'; put 'exec(alter table &innertable drop column &idx_val;) by myAlias;;'; put '%end;'; put '/* create view to strip formats and avoid warns in log */'; put 'data work.vw_bitemp5d/view=work.vw_bitemp5d;'; put 'set work.bitemp5d_subquery;'; put 'format _all_;'; put 'run;'; put 'proc append base=&base_lib..&innertable ('; put '%do idx_pk=1 %to &redcnt;'; put '&&rednm&idx_pk = &&redval&idxpk'; put '%end;'; put ')'; put 'data=work.vw_bitemp5d force nowarn;'; put 'run;'; put '/* open up a connection for pass through SQL */'; put '%dc_assignlib(WRITE,&base_lib,passthru=myAlias)'; put 'execute('; put '%end;'; put '%else %do;'; put '%let innertable=bitemp5d_subquery;'; put '%let top_table=&base_lib..&base_dsn;'; put '%let flexinow=&now;'; put '%end;'; put '%if &loadtype=BITEMPORAL or &loadtype=TXTEMPORAL %then %do;'; put 'update &top_table set &tech_to=&flexinow'; put '%if %length(&processed)>0 %then %do;'; put ',&processed=&flexinow'; put '%end;'; put 'where &tech_from <= &flexinow and &flexinow < &tech_to and'; put '%end;'; put '%else %if &loadtype=UPDATE %then %do;'; put '/* changed records are deleted then re-appended when doing UPDATEs */'; put 'delete from &top_table where'; put '%end;'; put '%else %do;'; put '%put %str(ERR)OR: BUSTEMPORAL NOT YET SUPPORTED;'; put '%let syscc=5;'; put '%mp_lockanytable(UNLOCK,lib=&base_lib,ds=&base_dsn,ref=&ETLSOURCE,'; put 'ctl_ds=&dclib..mpe_lockanytable'; put ')'; put '%mp_lockanytable(UNLOCK'; put ',lib=%scan(&outds_audit,1,.)'; put ',ds=%scan(&outds_audit,2,.)'; put ',ref=&ETLSOURCE'; put ',ctl_ds=&dclib..mpe_lockanytable'; put ')'; put '%goto end_of_macro;'; put '%end;'; put '/* perform join inside query as per'; put 'http://stackoverflow.com/questions/24629793/update-with-a-proc-sql */'; put 'exists( select 1 from &baselib_schema.&innertable where'; put '/* loop PK join */'; put '%do idx_pk=1 %to &pk_cnt;'; put '%let idx_val=%scan(&pk,&idx_pk);'; put '&base_dsn..&idx_val=&innertable..&idx_val and'; put '%end;'; put '%if &loadtype=BITEMPORAL %then %do;'; put '&base_dsn..&bus_from >= &innertable..&bus_from'; put 'and &base_dsn..&bus_to <= &innertable..&bus_to and'; put '%end;'; put '/* close the statement */'; put '1=1);'; put '%if &engine_type=OLEDB or &engine_type=REDSHIFT or &engine_type=POSTGRES'; put '%then %do;'; put ') by myAlias;'; put 'execute (drop table &baselib_schema.&innertable) by myAlias;'; put '%end;'; put '%end;'; put 'quit;'; put 'data _null_;'; put 'putlog "&sysmacroname: Closeout complete";'; put 'run;'; put '/**'; put '* Append the new / updated records'; put '*/'; put '%if &engine_type=CAS %then %do;'; put '/* get varchar variables ready for casting */'; put '%local vcfmt vcrename vcassign vcdrop;'; put 'data _null_;'; put 'set work.bitemp_cols(where=(type=6)) end=last;'; put 'length vcrename vcassign vcdrop vcfmt $32767 rancol $32;'; put 'retain vcrename vcassign vcdrop vcfmt;'; put 'if _n_=1 then vcrename=''(rename=('';'; put 'rancol=resolve(''%mf_getuniquename()'');'; put 'vcfmt=trim(vcfmt)!!''length ''!!cats(name)!!'' varchar(*);'';'; put 'vcrename=trim(vcrename)!!'' ''!!cats(name,''='',rancol);'; put 'vcassign=cats(vcassign,name,''='',rancol,'';'');'; put 'vcdrop=cats(vcdrop,''drop ''!!rancol,'';'');'; put 'if last then do;'; put 'vcrename=cats(vcrename,''))'');'; put 'call symputx(''vcfmt'',vcfmt);'; put 'call symputx(''vcrename'',vcrename);'; put 'call symputx(''vcassign'',vcassign);'; put 'call symputx(''vcdrop'',vcdrop);'; put 'end;'; put 'run;'; put '/* prepare a temp cas table with varchars casted */'; put '%let tmp=%mf_getuniquename();'; put 'data casuser.&tmp ;'; put '&vcfmt'; put 'set work.bitemp6_unique &vcrename;'; put '&vcassign'; put '&vcdrop'; put 'run;'; put '/* load the table with varchars applied*/'; put 'data &base_lib..&base_dsn (append=yes )/sessref=dcsession ;'; put 'set casuser.&tmp;'; put 'run;'; put '/* drop temp table */'; put 'proc sql;'; put 'drop table CASUSER.&tmp;'; put '/* this code will not work as regular tables do not have varchars */'; put '/*'; put 'proc casutil;'; put 'load data=work.bitemp6_unique'; put 'outcaslib="&base_lib" casout="&base_dsn" append ;'; put 'quit;'; put '*/'; put '%end;'; put '%else %if &engine_type=REDSHIFT or &engine_type=POSTGRES %then %do;'; put 'proc append base=&base_lib..&base_dsn'; put '%if &engine_type=REDSHIFT %then %do;'; put '('; put '%do idx_pk=1 %to &redcnt;'; put '&&rednm&idx_pk = &&redval&idxpk'; put '%end;'; put ')'; put '%end;'; put 'data=bitemp6_unique force nowarn;'; put 'run;'; put '%end;'; put '%else %do;'; put 'proc append base=&base_lib..&base_dsn data=bitemp6_unique force nowarn; run;'; put '%end;'; put '%mp_lockanytable(UNLOCK,lib=&base_lib,ds=&base_dsn,ref=&ETLSOURCE,'; put 'ctl_ds=&dclib..mpe_lockanytable'; put ')'; put '/* final check on syscc */'; put '%mp_abort(iftrue= (&syscc >4)'; put ',mac=&_program'; put ',msg=%str(!!Upload NOT successful!! Failed on actual update / append stage..)'; put ')'; put '%if &outds_audit ne 0 and &LOADTARGET=YES %then %do;'; put 'data work.vw_outds_orig /view=work.vw_outds_orig;'; put 'set work.bitemp0_base (drop=&md5_col);'; put 'where ___TMP___NEW_FLG=0;'; put 'drop ___TMP___NEW_FLG;'; put 'run;'; put '/* update the AUDIT table */'; put '%if %mf_existds(&outds_audit) %then %do;'; put 'options mprint;'; put '%mp_storediffs(&base_lib..&base_dsn'; put ',work.vw_outds_orig'; put ',&pk &bus_from'; put ',delds=&outds_del'; put ',modds=&outds_mod'; put ',appds=&outds_add'; put ',outds=work.mp_storediffs'; put ',processed_dttm=&now'; put ',loadref=%superq(etlsource)'; put ')'; put '/* exclude unchanged values in modified rows */'; put 'data work.mp_storediffs;'; put 'set work.mp_storediffs;'; put 'if MOVE_TYPE="M" and IS_PK=0 and IS_DIFF=0 then delete;'; put '* putlog load_ref= libref= dsn= key_hash= tgtvar_nm=;'; put 'run;'; put 'proc append base=&outds_audit data=work.mp_storediffs;'; put 'run;'; put '%mp_lockanytable(UNLOCK'; put ',lib=%scan(&outds_audit,1,.)'; put ',ds=%scan(&outds_audit,2,.)'; put ',ref=&ETLSOURCE'; put ',ctl_ds=&dclib..mpe_lockanytable'; put ')'; put '%end;'; put '%end;'; put '%mp_abort(iftrue= (&syscc >4)'; put ',mac=bitemporal_dataloader'; put ',msg=%str(Problem in audit stage (&outds_audit))'; put ')'; put '%let user=%mf_getUser();'; put '/**'; put 'Notify as appropriate EMAILS DISABLED'; put '%sumo_alerts(ALERT_EVENT=UPDATE'; put ', ALERT_TARGET=&base_lib..&base_dsn'; put ', from_user= &user);'; put '*/'; put '/* monitor BiTemporal usage */'; put '%if &log=1 %then %do;'; put '%put syscc=&syscc;'; put '/* do not perform duration calc in pass through */'; put '%local dur;'; put 'data _null_;'; put 'now=symget(''now'');'; put 'dur=%sysfunc(datetime())-&now;'; put 'call symputx(''dur'',dur,''l'');'; put 'run;'; put 'proc sql;'; put 'insert into &dclib..mpe_dataloads'; put 'set libref=%upcase("&base_lib")'; put ',DSN=%upcase("&base_dsn")'; put ',ETLSOURCE="&ETLSOURCE"'; put ',LOADTYPE="&loadtype"'; put ',CHANGED_RECORDS=%mf_getattrn(&lastds,NLOBS)'; put ',NEW_RECORDS=%mf_getattrn(&outds_add,NLOBS)'; put ',DELETED_RECORDS=%mf_getattrn(&outds_del,NLOBS)'; put ',DURATION=&dur'; put ',MAC_VER="v&ver"'; put ',user_nm="&user"'; put ',PROCESSED_DTTM=&now;'; put 'quit;'; put '%put syscc=&syscc;'; put '%end;'; put '%end_of_macro:'; put '%mend bitemporal_dataloader;'; put '%macro mp_binarycopy('; put 'inloc= /* full path and filename of the object to be copied */'; put ',outloc= /* full path and filename of object to be created */'; put ',inref=____in /* override default to use own filerefs */'; put ',outref=____out /* override default to use own filerefs */'; put ',mode=CREATE'; put ',iftrue=%str(1=1)'; put ')/*/STORE SOURCE*/;'; put '%local mod;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%if &mode=APPEND %then %let mod=mod;'; put '/* these IN and OUT filerefs can point to anything */'; put '%if &inref = ____in %then %do;'; put 'filename &inref &inloc lrecl=1048576 ;'; put '%end;'; put '%if &outref=____out %then %do;'; put 'filename &outref &outloc lrecl=1048576 &mod;'; put '%end;'; put '/* copy the file byte-for-byte */'; put 'data _null_;'; put 'infile &inref lrecl=1 recfm=n;'; put 'file &outref &mod recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put '%if &inref = ____in %then %do;'; put 'filename &inref clear;'; put '%end;'; put '%if &outref=____out %then %do;'; put 'filename &outref clear;'; put '%end;'; put '%mend mp_binarycopy;'; put '* SAS Macros end;'; put '* SAS Includes start;'; put '* SAS Includes end;'; put '* Binary Files start;'; put '* Binary Files end;'; put '* ServiceInit start;'; put 'options noquotelenmax ps=max;'; put '/* create dummy macros to prevent stpbegin / stpend from corrupting sessions */'; put '%macro stpbegin();'; put '%put NOTE: the STPBEGIN macro should not be used for web apps!;'; put '%mend stpbegin;'; put '%macro stpend();'; put '%put NOTE: the STPEND macro should not be used for web apps!;'; put '%mend stpend;'; put '* ServiceInit end;'; put '* Service start;'; put '/**'; put '@file refreshtablelineage.sas'; put '@brief updates the table level lineage'; put '@details extracts all sources/targets from every job'; put '

SAS Macros

'; put '@li mpeinit.sas'; put '@li bitemporal_dataloader.sas'; put '@li mp_binarycopy.sas'; put '@li mf_getuniquefileref.sas'; put '@version 9.3'; put '@author 4GL Apps Ltd'; put '@copyright 4GL Apps Ltd. This code may only be used within Data Controller'; put 'and may not be re-distributed or re-sold without the express permission of'; put '4GL Apps Ltd.'; put '**/'; put '%mpeinit()'; put '/* get list of libraries */'; put '%let fileref1=%mf_getuniquefileref();'; put 'filename &fileref1 temp;'; put 'proc metadata in='; put '"$METAREPOSITORY'; put 'JobSAS'; put ''; put '8452'; put ''; put ''; put ''; put ''; put ''; put ''; put ''; put ''; put ''; put ''; put ''; put ''; put ''; put '"'; put 'out=&fileref1'; put ';'; put 'run;'; put '/* create an XML map and extract dependencies from response */'; put '%macro getTables(type=);'; put '%local x;'; put '%do x=1 %to 2;'; put '%local dir;'; put '%if &x=1 %then %let dir=Source;'; put '%else %let dir=Target;'; put '%local y;'; put '%do y=1 %to 3;'; put '%local maptype;'; put '%if &y=1 %then %let maptype=ClassifierMap;'; put '%else %if &y=2 %then %let maptype=Select;'; put '%else %if &y=3 %then %let maptype=Join;'; put 'filename sxlemap temp;'; put 'data _null_;'; put 'file sxlemap;'; put 'put '''';'; put 'put "";'; put 'put "";'; put 'put "/GetMetadataObjects/Objects/Job/JobActivities/TransformationActivi"@;'; put 'put "ty/Steps/TransformationStep/Transformations/&maptype/Classifier"@;'; put 'put "&dir.s/&type";'; put 'put '''';'; put 'put "/GetMetadataObjects/Objects/Job/@Id";'; put 'put "characterstring17";'; put 'put '''';'; put 'put '''';'; put 'put "/GetMetadataObjects/Objects/Job/@Name";'; put 'put "characterstring128";'; put 'put '''';'; put 'put '''';'; put 'put "/GetMetadataObjects/Objects/Job/JobActivities"@;'; put 'put "/TransformationActivity/@Id";'; put 'put "characterstring17";'; put 'put '''';'; put 'put '''';'; put 'put "/GetMetadataObjects/Objects/Job/JobActivities"@;'; put 'put "/TransformationActivity/Steps/TransformationStep/@Id";'; put 'put "characterstring17";'; put 'put '''';'; put 'put '''';'; put 'put "/GetMetadataObjects/Objects/Job/JobActivities"@;'; put 'put "/TransformationActivity/Steps/TransformationStep/Transformations"@;'; put 'put "/&maptype/@Id";'; put 'put "characterstring17";'; put 'put '''';'; put 'put "";'; put 'put "/GetMetadataObjects/Objects/Job/JobActivities"@;'; put 'put "/TransformationActivity/Steps/TransformationStep/Transformations/"@;'; put 'put "&maptype/Classifier&dir.s/&type/@Id";'; put 'put "characterstring17";'; put 'put '''';'; put 'put ''
'';'; put 'run;'; put 'libname _XML_ xml xmlfileref=&fileref1 xmlmap=sxlemap;'; put 'data work.&dir.&type/view=work.&dir.&type;'; put 'set _xml_.&dir.&type;'; put 'length tabletype maptype $16 tabledirection $1;'; put 'jobname=upcase(jobname);'; put 'tabletype="&type";'; put 'maptype="&maptype";'; put 'if "&dir"="Source" then tabledirection="R";'; put 'else tabledirection=''F'';'; put 'run;'; put 'proc append base=work.alldata data=work.&dir.&type;'; put 'run;'; put 'libname _XML_ clear;'; put '%end;'; put '%end;'; put '%mend getTables;'; put '/* list of types:'; put 'https://support.sas.com/documentation/cdl/en/omamodref/63903/HTML/default/viewer.htm#classifiermap.htm'; put '*/'; put '%getTables(type=PhysicalTable)'; put '%getTables(type=ExternalTable)'; put '%getTables(type=QueryTable)'; put '%getTables(type=RelationalTable)'; put '%getTables(type=Report)'; put '%getTables(type=Cube)'; put '%getTables(type=DataTable)'; put '/* remove dups and get tables */'; put 'proc sort data=work.alldata(keep=jobid jobname tableid tabletype tabledirection)'; put 'out=work.sorted nodupkey;'; put 'by _all_;'; put 'run;'; put 'proc sort data=work.sorted(keep=tableid) out=work.tables nodupkey;'; put 'by tableid;'; put 'run;'; put '/* this bit is slow due to lookups but given the different table types'; put 'it was the easiest approach */'; put 'data work.tables2;'; put 'length tablename liburi puri $64 libref schemaname $8 ;'; put 'if _n_=1 then call missing(tablename, liburi, puri, libref, schemaname);'; put 'set work.tables;'; put 'drop rc liburi;'; put 'if metadata_getnasn(tableid,"TablePackage",1,liburi)>0 then do;'; put 'rc= metadata_getattr(liburi, "SchemaName", SchemaName);'; put 'if ^missing(SchemaName) then do;'; put 'if metadata_getnasn(liburi,"UsedByPackages",1,puri)>0 then do;'; put 'rc=metadata_getattr(puri,"Libref",libref);'; put 'end;'; put 'end;'; put 'else rc=metadata_getattr(liburi,"Libref",libref);'; put 'libref=upcase(libref);'; put 'end;'; put 'rc=metadata_getattr(tableid,"Name",tablename);'; put 'tablename=upcase(tablename);'; put 'run;'; put 'proc sql;'; put 'create table work.augmented as'; put 'select a.*'; put ',b.tablename'; put ',b.libref'; put 'from work.sorted a'; put 'left join work.tables2 b'; put 'on a.tableid=b.tableid;'; put 'create table work.mpe_lineage_tabs as'; put 'select distinct'; put 'coalesce(a.jobid,b.jobid) as jobid,'; put 'coalesce(a.jobname,b.jobname) as jobname,'; put 'coalesce(a.tableid,"N/A") as srctableid,'; put 'a.tabletype as srctabletype,'; put 'a.tablename as srctablename,'; put 'coalesce(a.libref,''nolib'') as srclibref,'; put 'coalesce(b.tableid,"N/A") as tgttableid,'; put 'b.tabletype as tgttabletype,'; put 'b.tablename as tgttablename,'; put 'coalesce(b.libref,''nolib'') as tgtlibref'; put 'from work.augmented(where=(tabledirection=''R'')) a'; put 'full join work.augmented(where=(tabledirection=''F'')) b'; put 'on a.jobid=b.jobid'; put ';'; put '%bitemporal_dataloader(base_lib=&mpelib'; put ',base_dsn=MPE_LINEAGE_TABS'; put ',append_dsn=MPE_LINEAGE_TABS'; put ',PK=jobid srctableid tgttableid'; put ',etlsource=&_program'; put ',loadtype=TXTEMPORAL'; put ',tech_from=TX_FROM'; put ',tech_to=TX_TO'; put ',dclib=&mpelib'; put ')'; put '%mp_binarycopy(inref=&fileref1, outref=_webout)'; put '* Service end;'; run; %mm_createwebservice(path=&appLoc/&path, name=&service, code=sascode, server=&serverName, replace=yes) filename sascode clear; %let service=registerkey; filename sascode temp lrecl=32767; data _null_; file sascode; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '/**'; put '@file mp_jsonout.sas'; put '@brief Writes JSON in SASjs format to a fileref'; put '@details This macro can be used to OPEN a JSON stream and send one or more'; put 'tables as arrays of rows, where each row can be an object or a nested array.'; put 'There are two engines available - DATASTEP or PROCJSON.'; put 'PROC JSON is fast but will produce errs like the ones below if'; put 'special chars are encountered.'; put '> (ERR)OR: Some code points did not transcode.'; put '> An object or array close is not valid at this point in the JSON text.'; put '> Date value out of range'; put 'If this happens, try running with ENGINE=DATASTEP.'; put 'The DATASTEP engine is used to handle special SAS missing numerics, and'; put 'can also convert entire datasets to formatted values. Output JSON is always'; put 'in UTF-8.'; put 'Usage:'; put 'filename tmp temp;'; put 'data class; set sashelp.class;run;'; put '%mp_jsonout(OPEN,jref=tmp)'; put '%mp_jsonout(OBJ,class,jref=tmp)'; put '%mp_jsonout(OBJ,class,dslabel=class2,jref=tmp,showmeta=Y)'; put '%mp_jsonout(CLOSE,jref=tmp)'; put 'data _null_;'; put 'infile tmp;'; put 'input;putlog _infile_;'; put 'run;'; put 'If you are building web apps with SAS then you are strongly encouraged to use'; put 'the mX_createwebservice macros in combination with the'; put '[sasjs adapter](https://github.com/sasjs/adapter).'; put 'For more information see https://sasjs.io'; put '@param [in] action Valid values:'; put '@li OPEN - opens the JSON'; put '@li OBJ - sends a table with each row as an object'; put '@li ARR - sends a table with each row in an array'; put '@li CLOSE - closes the JSON'; put '@param [in] ds The dataset to send. Must be a work table.'; put '@param [out] jref= (_webout) The fileref to which to send the JSON'; put '@param [out] dslabel= The name to give the table in the exported JSON'; put '@param [in] fmt= (Y) Whether to keep (Y) or strip (N) formats from the table'; put '@param [in] engine= (DATASTEP) Which engine to use to send the JSON. Options:'; put '@li PROCJSON (default)'; put '@li DATASTEP (more reliable when data has non standard characters)'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to JSON'; put '

Related Files

'; put '@li mp_ds2fmtds.sas'; put '@version 9.2'; put '@author Allan Bowe'; put '@source https://github.com/sasjs/core'; put '**/'; put '%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y'; put ',engine=DATASTEP'; put ',missing=NULL'; put ',showmeta=N'; put ',maxobs=MAX'; put ')/*/STORE SOURCE*/;'; put '%local tempds colinfo fmtds i numcols numobs stmt_obs lastobs optval'; put 'tmpds1 tmpds2 tmpds3 tmpds4;'; put '%let numcols=0;'; put '%if &maxobs ne MAX %then %let stmt_obs=%str(if _n_>&maxobs then stop;);'; put '%if &action=OPEN %then %do;'; put 'options nobomfile;'; put 'data _null_;file &jref encoding=''utf-8'' lrecl=200;'; put 'put ''{"PROCESSED_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''"'';'; put 'run;'; put '%end;'; put '%else %if (&action=ARR or &action=OBJ) %then %do;'; put '/* force variable names to always be uppercase in the JSON */'; put 'options validvarname=upcase;'; put '/* To avoid issues with _webout on EBI - such as encoding diffs and truncation'; put '(https://support.sas.com/kb/49/325.html) we use temporary files */'; put 'filename _sjs1 temp lrecl=200 ;'; put 'data _null_; file _sjs1 encoding=''utf-8'';'; put 'put ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":";'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjs1 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs1 clear;'; put '/* grab col defs */'; put 'proc contents noprint data=&ds'; put 'out=_data_(keep=name type length format formatl formatd varnum label);'; put 'run;'; put '%let colinfo=%scan(&syslast,2,.);'; put 'proc sort data=&colinfo;'; put 'by varnum;'; put 'run;'; put '/* move meta to mac vars */'; put 'data &colinfo;'; put 'if _n_=1 then call symputx(''numcols'',nobs,''l'');'; put 'set &colinfo end=last nobs=nobs;'; put 'name=upcase(name);'; put '/* fix formats */'; put 'if type=2 or type=6 then do;'; put 'typelong=''char'';'; put 'length fmt $49.;'; put 'if format='''' then fmt=cats(''$'',length,''.'');'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else fmt=cats(format,formatl,''.'');'; put 'end;'; put 'else do;'; put 'typelong=''num'';'; put 'if format='''' then fmt=''best.'';'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else if formatd=0 then fmt=cats(format,formatl,''.'');'; put 'else fmt=cats(format,formatl,''.'',formatd);'; put 'end;'; put '/* 32 char unique name */'; put 'newname=''sasjs''!!substr(cats(put(md5(name),$hex32.)),1,27);'; put 'call symputx(cats(''name'',_n_),name,''l'');'; put 'call symputx(cats(''newname'',_n_),newname,''l'');'; put 'call symputx(cats(''length'',_n_),length,''l'');'; put 'call symputx(cats(''fmt'',_n_),fmt,''l'');'; put 'call symputx(cats(''type'',_n_),type,''l'');'; put 'call symputx(cats(''typelong'',_n_),typelong,''l'');'; put 'call symputx(cats(''label'',_n_),coalescec(label,name),''l'');'; put '/* overwritten when fmt=Y and a custom format exists in catalog */'; put 'if typelong=''num'' then call symputx(cats(''fmtlen'',_n_),200,''l'');'; put 'else call symputx(cats(''fmtlen'',_n_),min(32767,ceil((length+10)*1.5)),''l'');'; put 'run;'; put '%let tempds=%substr(_%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql;'; put 'select count(*) into: lastobs from &ds;'; put '%if &maxobs ne MAX %then %let lastobs=%sysfunc(min(&lastobs,&maxobs));'; put '%if &engine=PROCJSON %then %do;'; put '%if &missing=STRING %then %do;'; put '%put &sysmacroname: Special Missings not supported in proc json.;'; put '%put &sysmacroname: Switching to DATASTEP engine;'; put '%goto datastep;'; put '%end;'; put 'data &tempds;'; put 'set &ds;'; put '&stmt_obs;'; put '%if &fmt=N %then format _numeric_ best32.;;'; put '/* PRETTY is necessary to avoid line truncation in large files */'; put 'filename _sjs2 temp lrecl=131068 encoding=''utf-8'';'; put 'proc json out=_sjs2 pretty'; put '%if &action=ARR %then nokeys ;'; put ';export &tempds / nosastags fmtnumeric;'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs2 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs2 clear;'; put '%end;'; put '%else %if &engine=DATASTEP %then %do;'; put '%datastep:'; put '%if %sysfunc(exist(&ds)) ne 1 & %sysfunc(exist(&ds,VIEW)) ne 1'; put '%then %do;'; put '%put &sysmacroname: &ds NOT FOUND!!!;'; put '%return;'; put '%end;'; put '%if &fmt=Y %then %do;'; put '/**'; put '* Extract format definitions'; put '* First, by getting library locations from dictionary.formats'; put '* Then, by exporting the width using proc format'; put '* Cannot use maxw from sashelp.vformat as not always populated'; put '* Cannot use fmtinfo() as not supported in all flavours'; put '*/'; put '%let tmpds1=%substr(fmtsum%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds2=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds3=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds4=%substr(col%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql noprint;'; put 'create table &tmpds1 as'; put 'select cats(libname,''.'',memname) as FMTCAT,'; put 'FMTNAME'; put 'from dictionary.formats'; put 'where fmttype=''F'' and libname is not null'; put 'and fmtname in (select format from &colinfo where format is not null)'; put 'order by 1;'; put 'create table &tmpds2('; put 'FMTNAME char(32),'; put 'LENGTH num'; put ');'; put '%local catlist cat fmtlist i;'; put 'select distinct fmtcat into: catlist separated by '' '' from &tmpds1;'; put '%do i=1 %to %sysfunc(countw(&catlist,%str( )));'; put '%let cat=%scan(&catlist,&i,%str( ));'; put 'proc sql;'; put 'select distinct fmtname into: fmtlist separated by '' '''; put 'from &tmpds1 where fmtcat="&cat";'; put 'proc format lib=&cat cntlout=&tmpds3(keep=fmtname length);'; put 'select &fmtlist;'; put 'run;'; put 'proc sql;'; put 'insert into &tmpds2 select distinct fmtname,length from &tmpds3;'; put '%end;'; put 'proc sql;'; put 'create table &tmpds4 as'; put 'select a.*, b.length as MAXW'; put 'from &colinfo a'; put 'left join &tmpds2 b'; put 'on cats(a.format)=cats(upcase(b.fmtname))'; put 'order by a.varnum;'; put 'data _null_;'; put 'set &tmpds4;'; put 'if not missing(maxw);'; put 'call symputx('; put 'cats(''fmtlen'',_n_),'; put '/* vars need extra padding due to JSON escaping of special chars */'; put 'min(32767,ceil((max(length,maxw)+10)*1.5))'; put ',''l'''; put ');'; put 'run;'; put '/* configure varlenchk - as we are explicitly shortening the variables */'; put '%let optval=%sysfunc(getoption(varlenchk));'; put 'options varlenchk=NOWARN;'; put 'data _data_(compress=char);'; put '/* shorten the new vars */'; put 'length'; put '%do i=1 %to &numcols;'; put '&&name&i $&&fmtlen&i'; put '%end;'; put ';'; put '/* rename on entry */'; put 'set &ds(rename=('; put '%do i=1 %to &numcols;'; put '&&name&i=&&newname&i'; put '%end;'; put '));'; put '&stmt_obs;'; put 'drop'; put '%do i=1 %to &numcols;'; put '&&newname&i'; put '%end;'; put ';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=num %then %do;'; put '&&name&i=cats(put(&&newname&i,&&fmt&i));'; put '%end;'; put '%else %do;'; put '&&name&i=put(&&newname&i,&&fmt&i);'; put '%end;'; put '%end;'; put 'if _error_ then do;'; put 'call symputx(''syscc'',1012);'; put 'stop;'; put 'end;'; put 'run;'; put '%let fmtds=&syslast;'; put 'options varlenchk=&optval;'; put '%end;'; put 'proc format; /* credit yabwon for special null removal */'; put 'value bart (default=40)'; put '%if &missing=NULL %then %do;'; put '._ - .z = null'; put '%end;'; put '%else %do;'; put '._ = [quote()]'; put '. = null'; put '.a - .z = [quote()]'; put '%end;'; put 'other = [best.];'; put 'data &tempds;'; put 'attrib _all_ label='''';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'length &&name&i $&&fmtlen&i...;'; put 'format &&name&i $&&fmtlen&i...;'; put '%end;'; put '%end;'; put '%if &fmt=Y %then %do;'; put 'set &fmtds;'; put '%end;'; put '%else %do;'; put 'set &ds;'; put '%end;'; put '&stmt_obs;'; put 'format _numeric_ bart.;'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'if findc(&&name&i,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put '&&name&i=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,&&name&i)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else &&name&i=quote(cats(&&name&i));'; put '%end;'; put '%end;'; put 'run;'; put 'filename _sjs3 temp lrecl=131068 ;'; put 'data _null_;'; put 'file _sjs3 encoding=''utf-8'';'; put 'if _n_=1 then put "[";'; put 'set &tempds;'; put 'if _n_>1 then put "," @; put'; put '%if &action=ARR %then "[" ; %else "{" ;'; put '%do i=1 %to &numcols;'; put '%if &i>1 %then "," ;'; put '%if &action=OBJ %then """&&name&i"":" ;'; put '"&&name&i"n /* name literal for reserved variable names */'; put '%end;'; put '%if &action=ARR %then "]" ; %else "}" ; ;'; put '/* close out the table */'; put 'data _null_;'; put 'file _sjs3 mod encoding=''utf-8'';'; put 'put '']'';'; put 'run;'; put 'data _null_;'; put 'infile _sjs3 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs3 clear;'; put '%end;'; put 'proc sql;'; put 'drop table &colinfo, &tempds;'; put '%if %substr(&showmeta,1,1)=Y %then %do;'; put 'filename _sjs4 temp lrecl=131068 encoding=''utf-8'';'; put 'data _null_;'; put 'file _sjs4;'; put 'length label $350;'; put 'put ", ""$%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":{""vars"":{";'; put 'do i=1 to &numcols;'; put 'name=quote(trim(symget(cats(''name'',i))));'; put 'format=quote(trim(symget(cats(''fmt'',i))));'; put 'label=quote(prxchange(''s/\\/\\\\/'',-1,trim(symget(cats(''label'',i)))));'; put 'length=quote(trim(symget(cats(''length'',i))));'; put 'type=quote(trim(symget(cats(''typelong'',i))));'; put 'if i>1 then put "," @@;'; put 'put name '':{"format":'' format '',"label":'' label'; put ''',"length":'' length '',"type":'' type ''}'';'; put 'end;'; put 'put ''}}'';'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs4 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs4 clear;'; put '%end;'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put 'data _null_; file &jref encoding=''utf-8'' mod ;'; put 'put "}";'; put 'run;'; put '%end;'; put '%mend mp_jsonout;'; put '/**'; put '@file mm_webout.sas'; put '@brief Send data to/from SAS Stored Processes'; put '@details This macro should be added to the start of each Stored Process,'; put '**immediately** followed by a call to:'; put '%mm_webout(FETCH)'; put 'This will read all the input data and create same-named SAS datasets in the'; put 'WORK library. You can then insert your code, and send data back using the'; put 'following syntax:'; put 'data some datasets; * make some data ;'; put 'retain some columns;'; put 'run;'; put '%mm_webout(OPEN)'; put '%mm_webout(ARR,some) * Array format, fast, suitable for large tables ;'; put '%mm_webout(OBJ,datasets) * Object format, easier to work with ;'; put 'Finally, wrap everything up send some helpful system variables too'; put '%mm_webout(CLOSE)'; put '@param [in] action Either FETCH, OPEN, ARR, OBJ or CLOSE'; put '@param [in] ds The dataset to send back to the frontend'; put '@param [out] dslabel= Value to use instead of table name for sending to JSON'; put '@param [in] fmt= (N) Setting Y converts all vars to their formatted values'; put '@param [out] fref= (_webout) The fileref to which to write the JSON'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] workobs= (0) When set to a positive integer, will create a new'; put 'output object (WORK) which contains this number of observations from all'; put 'tables in the WORK library.'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to output JSON'; put '

SAS Macros

'; put '@li mp_jsonout.sas'; put '

Related Macros

'; put '@li ms_webout.sas'; put '@li mv_webout.sas'; put '@version 9.3'; put '@author Allan Bowe'; put '**/'; put '%macro mm_webout(action,ds,dslabel=,fref=_webout,fmt=N,missing=NULL'; put ',showmeta=N,maxobs=MAX,workobs=0'; put ');'; put '%global _webin_file_count _webin_fileref1 _webin_name1 _program _debug'; put 'sasjs_tables;'; put '%local i tempds jsonengine;'; put '/* see https://github.com/sasjs/core/issues/41 */'; put '%if "%upcase(&SYSENCODING)" ne "UTF-8" %then %let jsonengine=PROCJSON;'; put '%else %let jsonengine=DATASTEP;'; put '%if &action=FETCH %then %do;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'options mprint notes mprintnest;'; put '%end;'; put '%let _webin_file_count=%eval(&_webin_file_count+0);'; put '/* now read in the data */'; put '%do i=1 %to &_webin_file_count;'; put '%if &_webin_file_count=1 %then %do;'; put '%let _webin_fileref1=&_webin_fileref;'; put '%let _webin_name1=&_webin_name;'; put '%end;'; put 'data _null_;'; put 'infile &&_webin_fileref&i termstr=crlf;'; put 'input;'; put 'call symputx(''input_statement'',_infile_);'; put 'putlog "&&_webin_name&i input statement: " _infile_;'; put 'stop;'; put 'data &&_webin_name&i;'; put 'infile &&_webin_fileref&i firstobs=2 dsd termstr=crlf encoding=''utf-8'';'; put 'input &input_statement;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'if _n_<20 then putlog _infile_;'; put '%end;'; put 'run;'; put '%let sasjs_tables=&sasjs_tables &&_webin_name&i;'; put '%end;'; put '%end;'; put '%else %if &action=OPEN %then %do;'; put '/* fix encoding */'; put 'OPTIONS NOBOMFILE;'; put '/**'; put '* check xengine type to avoid the below err message:'; put '* > Function is only valid for filerefs using the CACHE access method.'; put '*/'; put 'data _null_;'; put 'set sashelp.vextfl(where=(fileref="_WEBOUT"));'; put 'if xengine=''STREAM'' then do;'; put 'rc=stpsrv_header(''Content-type'',"text/html; encoding=utf-8");'; put 'end;'; put 'run;'; put '/* setup json */'; put 'data _null_;file &fref encoding=''utf-8'';'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutBEGIN<<'';'; put '%end;'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'run;'; put '%end;'; put '%else %if &action=ARR or &action=OBJ %then %do;'; put '%mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt,jref=&fref'; put ',engine=&jsonengine,missing=&missing,showmeta=&showmeta,maxobs=&maxobs'; put ')'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put '/* To avoid issues with _webout on EBI we use a temporary file */'; put 'filename _sjsref temp lrecl=131068;'; put '%if %str(&workobs) > 0 %then %do;'; put '/* if debug mode, send back first XX records of each work table also */'; put 'data;run;%let tempds=%scan(&syslast,2,.);'; put 'ods output Members=&tempds;'; put 'proc datasets library=WORK memtype=data;'; put '%local wtcnt;%let wtcnt=0;'; put 'data _null_;'; put 'set &tempds;'; put 'if not (upcase(name) =:"DATA"); /* ignore temp datasets */'; put 'i+1;'; put 'call symputx(cats(''wt'',i),name,''l'');'; put 'call symputx(''wtcnt'',i,''l'');'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put ",""WORK"":{";'; put '%do i=1 %to &wtcnt;'; put '%let wt=&&wt&i;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'dsid=open("WORK.&wt",''is'');'; put 'nlobs=attrn(dsid,''NLOBS'');'; put 'nvars=attrn(dsid,''NVARS'');'; put 'rc=close(dsid);'; put 'if &i>1 then put '',''@;'; put 'put " ""&wt"" : {";'; put 'put ''"nlobs":'' nlobs;'; put 'put '',"nvars":'' nvars;'; put '%mp_jsonout(OBJ,&wt,jref=_sjsref,dslabel=first10rows,showmeta=Y'; put ',maxobs=&workobs'; put ')'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put '%end;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put 'run;'; put '%end;'; put '/* close off json */'; put 'data _null_;file _sjsref mod encoding=''utf-8'';'; put 'length SYSPROCESSNAME syserrortext syswarningtext autoexec $512;'; put 'put ",""_DEBUG"" : ""&_debug"" ";'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'autoexec=quote(urlencode(trim(getoption(''autoexec''))));'; put 'put '',"AUTOEXEC" : '' autoexec;'; put 'put ",""MF_GETUSER"" : ""%mf_getuser()"" ";'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'put ",""SYSENCODING"" : ""&sysencoding"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSPROCESSID"" : ""&SYSPROCESSID"" ";'; put 'put ",""SYSPROCESSMODE"" : ""&SYSPROCESSMODE"" ";'; put 'SYSPROCESSNAME=quote(urlencode(cats(SYSPROCESSNAME)));'; put 'put ",""SYSPROCESSNAME"" : " SYSPROCESSNAME;'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put '',"SYSWARNINGTEXT" : '' syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'length memsize $32;'; put 'memsize="%sysfunc(INPUTN(%sysfunc(getoption(memsize)), best.),sizekmg.)";'; put 'memsize=quote(cats(memsize));'; put 'put '',"MEMSIZE" : '' memsize;'; put 'put "}" @;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutEND<<'';'; put '%end;'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjsref lrecl=1 recfm=n;'; put 'file &fref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjsref clear;'; put '%end;'; put '%mend mm_webout;'; put '%macro webout(action,ds,dslabel=,fmt=,missing=NULL,showmeta=NO,maxobs=MAX);'; put '%mm_webout(&action,ds=&ds,dslabel=&dslabel,fmt=&fmt'; put ',missing=&missing'; put ',showmeta=&showmeta'; put ',maxobs=&maxobs'; put ') %mend;'; put '/* provide additional debug info */'; put '%global _program;'; put '%put &=syscc;'; put '%put user=%mf_getuser();'; put '%put pgm=&_program;'; put '%put timestamp=%sysfunc(datetime(),datetime19.);'; put '* Service Variables start;'; put '* Service Variables end;'; put '* SAS Macros start;'; put '%macro mf_getapploc(pgm);'; put '%if "&pgm"="" %then %do;'; put '%if %symexist(_program) %then %let pgm=&_program;'; put '%else %do;'; put '%put &sysmacroname: No value provided and no _program variable available;'; put '%return;'; put '%end;'; put '%end;'; put '%local root;'; put '/**'; put '* First check we are not in the tests/macros folder (which has no subfolders)'; put '* or specifically in the testsetup or testteardown services'; put '*/'; put '%if %index(&pgm,/tests/macros/)'; put 'or %index(&pgm,/tests/testsetup)'; put 'or %index(&pgm,/tests/testteardown)'; put '%then %do;'; put '%let root=%substr(&pgm,1,%index(&pgm,/tests)-1);'; put '&root'; put '%return;'; put '%end;'; put '/**'; put '* Next, move up two levels to avoid matches on subfolder or service name'; put '*/'; put '%let root=%substr(&pgm,1,%length(&pgm)-%length(%scan(&pgm,-1,/))-1);'; put '%let root=%substr(&root,1,%length(&root)-%length(%scan(&root,-1,/))-1);'; put '%if %index(&root,/tests/) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/tests/)-1);'; put '%end;'; put '%else %if %index(&root,/services) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/services)-1);'; put '%end;'; put '%else %if %index(&root,/jobs) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/jobs)-1);'; put '%end;'; put '%else %put &sysmacroname: Could not find an app location from &pgm;'; put '&root'; put '%mend mf_getapploc ;'; put '%macro mf_getuniquefileref(prefix=_,maxtries=1000,lrecl=32767);'; put '%local rc fname;'; put '%if &prefix=0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%end;'; put '%else %do;'; put '%local x len;'; put '%let len=%eval(8-%length(&prefix));'; put '%let x=0;'; put '%do x=0 %to &maxtries;'; put '%let fname=&prefix%substr(%sysfunc(ranuni(0)),3,&len);'; put '%if %sysfunc(fileref(&fname)) > 0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%return;'; put '%end;'; put '%end;'; put '%put unable to find available fileref after &maxtries attempts;'; put '%end;'; put '%mend mf_getuniquefileref;'; put '%macro mp_abort(mac=mp_abort.sas, type=, msg=, iftrue=%str(1=1)'; put ', errds=work.mp_abort_errds'; put ', mode=REGULAR'; put ')/*/STORE SOURCE*/;'; put '%global sysprocessmode sysprocessname sasjs_stpsrv_header_loc sasjsprocessmode;'; put '%local fref fid i;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%put NOTE: /// mp_abort macro executing //;'; put '%if %length(&mac)>0 %then %put NOTE- called by &mac;'; put '%put NOTE - &msg;'; put '%if %symexist(_SYSINCLUDEFILEDEVICE)'; put '/* abort cancel FILE does not restart outside the INCLUDE on Viya 3.5 */'; put 'and %superq(SYSPROCESSNAME) ne %str(Compute Server)'; put '%then %do;'; put '%if "*&_SYSINCLUDEFILEDEVICE*" ne "**" %then %do;'; put 'data &errds;'; put 'iftrue=''1=1'';'; put 'length mac $100 msg $5000;'; put 'mac=symget(''mac'');'; put 'msg=symget(''msg'');'; put 'run;'; put 'data _null_;'; put 'abort cancel FILE;'; put 'run;'; put '%return;'; put '%end;'; put '%end;'; put '/* Web App Context */'; put '%if %symexist(_PROGRAM)'; put 'or %superq(SYSPROCESSNAME) = %str(Compute Server)'; put 'or &mode=INCLUDE'; put '%then %do;'; put 'options obs=max replace mprint;'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5"'; put '%then %do;'; put 'options nosyntaxcheck;'; put '%end;'; put '%if &mode=INCLUDE %then %do;'; put '%if %sysfunc(exist(&errds))=1 %then %do;'; put 'data _null_;'; put 'set &errds;'; put 'call symputx(''iftrue'',iftrue,''l'');'; put 'call symputx(''mac'',mac,''l'');'; put 'call symputx(''msg'',msg,''l'');'; put 'putlog (_all_)(=);'; put 'run;'; put '%if (&iftrue)=0 %then %return;'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: No include errors found;'; put '%return;'; put '%end;'; put '%end;'; put '/* extract log errs / warns, if exist */'; put '%local logloc logline;'; put '%global logmsg; /* capture global messages */'; put '%if %symexist(SYSPRINTTOLOG) %then %let logloc=&SYSPRINTTOLOG;'; put '%else %let logloc=%qsysfunc(getoption(LOG));'; put 'proc printto log=log;run;'; put '%let logline=0;'; put '%if %length(&logloc)>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input; putlog _infile_;'; put 'i=1;'; put 'retain logonce 0;'; put 'if ('; put '_infile_=:"%str(WARN)ING" or _infile_=:"%str(ERR)OR"'; put ') and logonce=0 then'; put 'do;'; put 'call symputx(''logline'',_n_);'; put 'logonce+1;'; put 'end;'; put 'run;'; put '/* capture log including lines BEFORE the err */'; put '%if &logline>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input;'; put 'i=1;'; put 'stoploop=0;'; put 'if _n_ ge &logline-15 and stoploop=0 then do until (i>22);'; put 'call symputx(''logmsg'',catx(''\n'',symget(''logmsg''),_infile_));'; put 'input;'; put 'i+1;'; put 'stoploop=1;'; put 'end;'; put 'if stoploop=1 then stop;'; put 'run;'; put '%end;'; put '%end;'; put '%if %symexist(SYS_JES_JOB_URI) %then %do;'; put '/* setup webout for Viya */'; put 'options nobomfile;'; put '%if "X&SYS_JES_JOB_URI.X"="XX" %then %do;'; put 'filename _webout temp lrecl=999999 mod;'; put '%end;'; put '%else %do;'; put 'filename _webout filesrvc parenturi="&SYS_JES_JOB_URI"'; put 'name="_webout.json" lrecl=999999 mod;'; put '%end;'; put '%end;'; put '%else %if %sysfunc(filename(fref,&sasjs_stpsrv_header_loc))=0 %then %do;'; put 'options nobomfile;'; put '/* set up http header for SASjs Server */'; put '%let fid=%sysfunc(fopen(&fref,A));'; put '%if &fid=0 %then %do;'; put '%put %str(ERR)OR: %sysfunc(sysmsg());'; put '%return;'; put '%end;'; put '%let rc=%sysfunc(fput(&fid,%str(Content-Type: application/json)));'; put '%let rc=%sysfunc(fwrite(&fid));'; put '%let rc=%sysfunc(fclose(&fid));'; put '%let rc=%sysfunc(filename(&fref));'; put '%end;'; put '/* send response in SASjs JSON format */'; put 'data _null_;'; put 'file _webout mod lrecl=32000 encoding=''utf-8'';'; put 'length msg syswarningtext syserrortext $32767 mode $10 ;'; put 'sasdatetime=datetime();'; put 'msg=symget(''msg'');'; put '%if &logline>0 %then %do;'; put 'msg=cats(msg,''\n\nLog Extract:\n'',symget(''logmsg''));'; put '%end;'; put '/* escape the escapes */'; put 'msg=tranwrd(msg,''\'',''\\'');'; put '/* escape the quotes */'; put 'msg=tranwrd(msg,''"'',''\"'');'; put '/* ditch the CRLFs as chrome complains */'; put 'msg=compress(msg,,''kw'');'; put '/* quote without quoting the quotes (which are escaped instead) */'; put 'msg=cats(''"'',msg,''"'');'; put 'if symexist(''_debug'') then debug=quote(trim(symget(''_debug'')));'; put 'else debug=''""'';'; put 'if symget(''sasjsprocessmode'')=''Stored Program'' then mode=''SASJS'';'; put 'if mode ne ''SASJS'' then put ''>>weboutBEGIN<<'';'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'put '',"sasjsAbort" : [{'';'; put 'put '' "MSG":'' msg ;'; put 'put '' ,"MAC": "'' "&mac" ''"}]'';'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'put '',"_DEBUG":'' debug ;'; put 'if symexist(''_metauser'') then do;'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put 'end;'; put 'if symexist(''SYS_JES_JOB_URI'') then do;'; put 'SYS_JES_JOB_URI=quote(trim(symget(''SYS_JES_JOB_URI'')));'; put 'put '',"SYS_JES_JOB_URI": '' SYS_JES_JOB_URI;'; put 'end;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put ",""SYSWARNINGTEXT"" : " syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'put "}" ;'; put 'if mode ne ''SASJS'' then put ''>>weboutEND<<'';'; put 'run;'; put '%put _all_;'; put '%if "&sysprocessmode " = "SAS Stored Process Server " %then %do;'; put 'data _null_;'; put 'putlog ''stpsrvset program err and syscc'';'; put 'rc=stpsrvset(''program error'', 0);'; put 'call symputx("syscc",0,"g");'; put 'run;'; put '%if &sysscp=WIN'; put 'and 1=0 /* deprecating this logic until we figure out a consistent abort */'; put 'and "%substr(%str(&sysvlong ),1,8)"="9.04.01M"'; put 'and "%substr(%str(&sysvlong ),9,1)">"5" %then %do;'; put '/* skip approach (below) does not work in windows m6+ envs */'; put 'endsas;'; put '%end;'; put '%else %do;'; put '/**'; put '* endsas kills 9.4m3 deployments by orphaning multibridges.'; put '* Abort variants are ungraceful (non zero return code)'; put '* This approach lets SAS run silently until the end :-)'; put '* Caution - fails when called within a %include within a macro'; put '* Use mp_include() to handle this.'; put '*/'; put 'filename skip temp;'; put 'data _null_;'; put 'file skip;'; put 'put ''%macro skip();'';'; put 'comment ''%mend skip; -> fix lint '';'; put 'put ''%macro skippy();'';'; put 'comment ''%mend skippy; -> fix lint '';'; put 'run;'; put '%inc skip;'; put '%end;'; put '%end;'; put '%else %if "&sysprocessmode " = "SAS Compute Server " %then %do;'; put '/* endsas kills the session making it harder to fetch results */'; put 'data _null_;'; put 'syswarningtext=symget(''syswarningtext'');'; put 'syserrortext=symget(''syserrortext'');'; put 'abort_msg=symget(''msg'');'; put 'syscc=symget(''syscc'');'; put 'sysuserid=symget(''sysuserid'');'; put 'iftrue=symget(''iftrue'');'; put 'put (_all_)(/=);'; put 'call symputx(''syscc'',0);'; put 'abort cancel nolist;'; put 'run;'; put '%end;'; put '%else %do;'; put '%abort cancel;'; put '%end;'; put '%end;'; put '%else %do;'; put '%put _all_;'; put '%abort cancel;'; put '%end;'; put '%mend mp_abort;'; put '/** @endcond */'; put '%macro mm_getstpcode('; put 'tree=/User Folders/sasdemo/somestp'; put ',name='; put ',outloc=0'; put ',outref=0'; put ',mDebug=1'; put ',showlog=NO'; put ');'; put '%local mD;'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing &sysmacroname..sas;'; put '%&mD.put _local_;'; put '%if %length(&name)>0 %then %let name=/&name;'; put '/* first, check if STP exists */'; put '%local tsuri;'; put '%let tsuri=stopifempty ;'; put 'data _null_;'; put 'format type uri tsuri value $200.;'; put 'call missing (of _all_);'; put 'path="&tree&name(StoredProcess)";'; put '/* first, find the STP ID */'; put 'if metadata_pathobj("",path,"StoredProcess",type,uri)>0 then do;'; put '/* get sourcecode */'; put 'cnt=1;'; put 'do while (metadata_getnasn(uri,"Notes",cnt,tsuri)>0);'; put 'rc=metadata_getattr(tsuri,"Name",value);'; put '&mD.put tsuri= value=;'; put 'if value="SourceCode" then do;'; put '/* found it! */'; put 'rc=metadata_getattr(tsuri,"Id",value);'; put 'call symputx(''tsuri'',value,''l'');'; put 'stop;'; put 'end;'; put 'cnt+1;'; put 'end;'; put 'end;'; put 'else put (_all_)(=);'; put 'run;'; put '%mp_abort(iftrue= (&tsuri=stopifempty)'; put ',mac=mm_getstpcode'; put ',msg=%str(&tree&name.(StoredProcess) not found!)'; put ')'; put '/**'; put '* Now we can extract the textstore'; put '*/'; put 'filename __getdoc temp lrecl=10000000;'; put 'proc metadata'; put 'in="$METAREPOSITORY'; put ''; put 'SAS1"'; put 'out=__getdoc ;'; put 'run;'; put '/* find the beginning of the text */'; put '%local start;'; put 'data _null_;'; put 'infile __getdoc lrecl=10000;'; put 'input;'; put 'start=index(_infile_,''StoredText="'');'; put 'if start then do;'; put 'call symputx("start",start+11);'; put '*putlog ''"'' _infile_ ''"'';'; put 'end;'; put 'stop;'; put '%local outeng;'; put '%if "&outloc"="0" %then %let outeng=TEMP;'; put '%else %let outeng="&outloc";'; put '%local fref;'; put '%if &outref=0 %then %let fref=%mf_getuniquefileref();'; put '%else %let fref=&outref;'; put '/* read the content, byte by byte, resolving escaped chars */'; put 'filename &fref &outeng lrecl=100000;'; put 'data _null_;'; put 'length filein 8 fileid 8;'; put 'filein = fopen("__getdoc","I",1,"B");'; put 'fileid = fopen("&fref","O",1,"B");'; put 'rec = "20"x;'; put 'length entity $6;'; put 'do while(fread(filein)=0);'; put 'x+1;'; put 'if x>&start then do;'; put 'rc = fget(filein,rec,1);'; put 'if rec=''"'' then leave;'; put 'else if rec="&" then do;'; put 'entity=rec;'; put 'do until (rec=";");'; put 'if fread(filein) ne 0 then goto getout;'; put 'rc = fget(filein,rec,1);'; put 'entity=cats(entity,rec);'; put 'end;'; put 'select (entity);'; put 'when (''&'' ) rec=''&'' ;'; put 'when (''<'' ) rec=''<'' ;'; put 'when (''>'' ) rec=''>'' ;'; put 'when (''''') rec="''" ;'; put 'when (''"'') rec=''"'' ;'; put 'when ('' '') rec=''0A''x;'; put 'when ('' '') rec=''0D''x;'; put 'when (''$'' ) rec=''$'' ;'; put 'when ('' '') rec=''09''x;'; put 'otherwise putlog "%str(WARN)ING: missing value for " entity=;'; put 'end;'; put 'rc =fput(fileid, substr(rec,1,1));'; put 'rc =fwrite(fileid);'; put 'end;'; put 'else do;'; put 'rc =fput(fileid,rec);'; put 'rc =fwrite(fileid);'; put 'end;'; put 'end;'; put 'end;'; put 'getout:'; put 'rc=fclose(filein);'; put 'rc=fclose(fileid);'; put 'run;'; put '%if &showlog=YES %then %do;'; put 'data _null_;'; put 'infile &fref lrecl=32767 end=last;'; put 'input;'; put 'if _n_=1 then putlog ''>>stpcodeBEGIN<<'';'; put 'putlog _infile_;'; put 'if last then putlog ''>>stpcodeEND<<'';'; put 'run;'; put '%end;'; put 'filename __getdoc clear;'; put '%if &outref=0 %then %do;'; put 'filename &fref clear;'; put '%end;'; put '%mend mm_getstpcode;'; put '%macro dc_getsettings();'; put '%global _program;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(syscc=&syscc on entry (&syswarningtext &syserrortext))'; put ')'; put '%if %length(&_PROGRAM)>1 %then %let root=&_program;'; put '%else %do;'; put '%global _metauser;'; put '%let _metauser=&sysuserid;'; put '/* to mimic a "real" _program we need to give a dummy role and stp name */'; put '%let root=/dummyRole/dummyName;'; put '%end;'; put '/* the DC precode is stored in the Admin folder in the root of'; put 'the project. Lets find that root. */'; put '%put &=root;'; put '%let root=%mf_getapploc();'; put '%put &=root;'; put '/* Now we know the root location we can retrieve the params */'; put '%let temploc=%sysfunc(pathname(work))/settings.txt;'; put '%mm_getstpcode(tree=&root/services/public'; put ',name=Data_Controller_Settings'; put ',outloc=&temploc'; put ')'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Unable to run getstpcode)'; put ')'; put 'filename _getsets "&temploc" lrecl=2000;'; put '/*'; put 'Do not use mp_include here - this puts a copy in every service, which creates'; put 'compilation problems when calling services from mp_include'; put '*/'; put '%inc _getsets/source2;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Problem running &sysmacroname (&syswarningtext &syserrortext))'; put ')'; put '%mend dc_getsettings;'; put '%macro mf_fmtdttm('; put ')/*/STORE SOURCE*/;'; put '%if "&sysver"="9.2" or "&sysver"="9.3"'; put 'or ("&sysver"="9.4" and "%substr(&SYSVLONG,9,1)" le "3")'; put 'or "%substr(&sysver,1,1)"="4"'; put 'or "%substr(&sysver,1,1)"="5"'; put '%then %do;DATETIME19.3%end;'; put '%else %do;E8601DT26.6%end;'; put '%mend mf_fmtdttm;'; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '%macro mp_init(prefix=SASJS'; put ')/*/STORE SOURCE*/;'; put '%if %symexist(SASJS_PREFIX) %then %return; /* only run once */'; put '%global'; put 'SASJS_PREFIX /* the ONLY hard-coded global macro variable in SASjs */'; put '&prefix._FUNCTIONS /* used in mcf_init() to track core function compilation */'; put '&prefix._INIT_NUM /* initialisation time as numeric */'; put '&prefix._INIT_DTTM /* initialisation time in E8601DT26.6 format */'; put '&prefix.WORK /* avoid typing %sysfunc(pathname(work)) every time */'; put ';'; put '%let sasjs_prefix=&prefix;'; put 'data _null_;'; put 'dttm=datetime();'; put 'call symputx("&sasjs_prefix._init_num",dttm,''g'');'; put 'call symputx("&sasjs_prefix._init_dttm",put(dttm,E8601DT26.6),''g'');'; put 'call symputx("&sasjs_prefix.work",pathname(''WORK''),''g'');'; put 'run;'; put 'options'; put 'compress=CHAR /* default is none so ensure we have something! */'; put 'datastmtchk=ALLKEYWORDS /* protection from overwriting input datasets */'; put 'errorcheck=STRICT /* catch errs in libname/filename statements */'; put 'fmterr /* ensure err when a format cannot be found */'; put 'mergenoby=%str(ERR)OR /* throw err when a merge has no BY variables */'; put 'missing=. /* changing this can cause hard to detect errs */'; put 'noquotelenmax /* avoid warnings for long strings */'; put 'noreplace /* avoid overwriting permanent datasets */'; put 'ps=max /* reduce log size slightly */'; put 'ls=max /* reduce log even more and avoid word truncation */'; put 'validmemname=COMPATIBLE /* avoid special characters etc in table names */'; put 'validvarname=V7 /* avoid special characters etc in variable names */'; put 'varinitchk=%str(ERR)OR /* avoid data mistakes from variable name typos */'; put 'varlenchk=%str(ERR)OR /* fail hard if truncation (data loss) can result */'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5" %then %do;'; put 'noautocorrect /* disallow misspelled procedure names */'; put 'dsoptions=note2err /* undocumented - convert bad NOTEs to ERRs */'; put '%end;'; put ';'; put '%mend mp_init;'; put '%macro mpeinit(fetch=YES);'; put '%global mpeinit'; put 'mpeadmins /* group with unrestricted Meditor access */'; put 'mpelocapprovals /* location for landing and staging files */'; put 'mpelib /* location of configuration tables for DC */'; put 'dc_repo_users /* location of user / group metadata */'; put 'dc_licence_key /* extracted in dc_getsettings */'; put 'dc_activation_key /* extracted in dc_getsettings */'; put 'dc_locale /* extracted in dc_getsettings */'; put 'dc_dttmtfmt /* can be overridden in dc_getsettings */'; put '_debug'; put ';'; put '%if &mpeinit=1 %then %return;'; put '%else %let mpeinit=1;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(Problem on service startup (&syswarningtext &syserrortext))'; put ')'; put '%mp_init()'; put '%if &fetch=YES %then %do;'; put '%webout(FETCH)'; put '%end;'; put '%global _CLIENTNAME;'; put '%mp_abort(iftrue= (&_CLIENTNAME=SAS Enterprise Guide)'; put ',mac=&_program..sas'; put ',msg=%str(Data Controller is a web app and should not be executed from EG)'; put ')'; put 'options urlencoding=utf8 nobomfile lrecl=32767;'; put '%let perf=%sysfunc(datetime());'; put '%put perfdiff: 0;'; put '%let dc_locale=SYSTEM; /* default if not set */'; put '/**'; put '* E8601DT26.6 has widest database support - but not all SAS flavours can'; put '* handle it. Override in the settings STP if needed.'; put '*/'; put 'data _null_;'; put 'dc_dttmtfmt=''"%sysfunc(datetime(),''!!"%mf_fmtdttm()"!!'')"dt'';'; put 'call symputx(''dc_dttmtfmt'',dc_dttmtfmt);'; put 'put dc_dttmtfmt=;'; put 'run;'; put '%put &=dc_dttmtfmt;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc prior to dc_getsettings)'; put ')'; put '%dc_getsettings()'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc after dc_getsettings)'; put ')'; put 'data _null_;'; put 'set &DC_LIBREF..mpe_config(where=('; put 'var_scope="DC"'; put 'and &dc_dttmtfmt lt tx_to'; put 'and var_active=1'; put '));'; put 'call symputx(var_name,var_value,''G'');'; put 'putlog var_name "=" var_value;'; put 'run;'; put '%let mpelib=&dc_libref;'; put '%let mpeadmins=&dc_admin_group;'; put '%let mpelocapprovals=&dc_staging_area;'; put '%let dc_repo_users=&dc_repo_users;'; put '%if &dc_locale ne SYSTEM %then %do;'; put 'options locale=&dc_locale;'; put '%end;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(Problem during compilation or with STP precode (&syswarningtext))'; put ')'; put '%mend mpeinit;'; put '%macro mf_mval(var);'; put '%if %symexist(&var) %then %do;'; put '%superq(&var)'; put '%end;'; put '%mend mf_mval;'; put '%macro mf_trimstr(basestr,trimstr);'; put '%local baselen trimlen trimval;'; put '/* return if basestr is shorter than trimstr (or 0) */'; put '%let baselen=%length(%superq(basestr));'; put '%let trimlen=%length(%superq(trimstr));'; put '%if &baselen < &trimlen or &baselen=0 %then %return;'; put '/* obtain the characters from the end of basestr */'; put '%let trimval=%qsubstr(%superq(basestr)'; put ',%length(%superq(basestr))-&trimlen+1'; put ',&trimlen);'; put '/* compare and if matching, chop it off! */'; put '%if %superq(basestr)=%superq(trimstr) %then %do;'; put '%return;'; put '%end;'; put '%else %if %superq(trimval)=%superq(trimstr) %then %do;'; put '%qsubstr(%superq(basestr),1,%length(%superq(basestr))-&trimlen)'; put '%end;'; put '%else %do;'; put '&basestr'; put '%end;'; put '%mend mf_trimstr;'; put '%macro mf_getplatform(switch'; put ')/*/STORE SOURCE*/;'; put '%local a b c;'; put '%if &switch.NONE=NONE %then %do;'; put '%if %symexist(sasjsprocessmode) %then %do;'; put '%if &sasjsprocessmode=Stored Program %then %do;'; put 'SASJS'; put '%return;'; put '%end;'; put '%end;'; put '%if %symexist(sysprocessmode) %then %do;'; put '%if "&sysprocessmode"="SAS Object Server"'; put 'or "&sysprocessmode"= "SAS Compute Server" %then %do;'; put 'SASVIYA'; put '%end;'; put '%else %if "&sysprocessmode"="SAS Stored Process Server"'; put 'or "&sysprocessmode"="SAS Workspace Server"'; put '%then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if %symexist(_metaport) or %symexist(_metauser) %then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if &switch=SASSTUDIO %then %do;'; put '/* return the version of SAS Studio else 0 */'; put '%if %mf_mval(_CLIENTAPP)=%str(SAS Studio) %then %do;'; put '%let a=%mf_mval(_CLIENTVERSION);'; put '%let b=%scan(&a,1,.);'; put '%if %eval(&b >2) %then %do;'; put '&b'; put '%end;'; put '%else 0;'; put '%end;'; put '%else 0;'; put '%end;'; put '%else %if &switch=VIYARESTAPI %then %do;'; put '%mf_trimstr(%sysfunc(getoption(servicesbaseurl)),/)'; put '%end;'; put '%mend mf_getplatform;'; put '%macro mpeterm();'; put '%local oldloc;'; put 'data _null_;'; put 'if symexist(''SYSPRINTTOLOG'') then oldloc=symget(''SYSPRINTTOLOG'');'; put 'else oldloc=getoption(''LOG'');'; put 'if subpad(oldloc,1,1) not in (''"'',"''",'' '') then oldloc=quote(cats(oldloc));'; put 'call symputx(''oldloc'',oldloc,''l'');'; put 'run;'; put '%if %length(&oldloc)>0 %then %do;'; put 'proc printto log=log;'; put 'run;'; put 'data _null_;'; put 'infile &oldloc;'; put 'input; putlog _infile_;'; put 'run;'; put '%end;'; put '%if %sysfunc(exist(&dc_libref..mpe_requests)) and %mf_getplatform() ne SASVIYA'; put '%then %do;'; put 'data ;'; put 'if 0 then set &dc_libref..mpe_requests;'; put 'request_dttm=%sysfunc(datetime());'; put 'request_user="%mf_getuser()";'; put 'request_service="%scan(&_program,-2,/)/%scan(&_program,-1,/)";'; put 'request_params='''';'; put 'output;stop;'; put 'proc append base=&dc_libref..mpe_requests data=&syslast force nowarn;'; put 'run;'; put '%end;'; put '%mend mpeterm;'; put '%macro mm_assignlib('; put 'libref'; put ',mAbort=HARD'; put ')/*/STORE SOURCE*/;'; put '%local mp_abort msg;'; put '%let mp_abort=0;'; put '%if %sysfunc(libref(&libref)) %then %do;'; put 'data _null_;'; put 'length liburi LibName msg $200;'; put 'call missing(of _all_);'; put 'nobj=metadata_getnobj("omsobj:SASLibrary?@Libref=''&libref''",1,liburi);'; put 'if nobj=1 then do;'; put 'rc=metadata_getattr(liburi,"Name",LibName);'; put '/* now try and assign it */'; put 'if libname("&libref",,''meta'',cats(''liburi="'',liburi,''";'')) ne 0 then do;'; put 'putlog "&libref could not be assigned";'; put 'putlog liburi=;'; put '/**'; put '* Fetch the system message for display in the abort modal. This is'; put '* not always helpful though. One example, previously received:'; put '* NOTE: Libref XX refers to the same library metadata as libref XX.'; put '*/'; put 'msg=sysmsg();'; put 'if msg=:''ERROR: Libref SAVE is not assigned.'' then do;'; put 'msg=catx(" ",'; put '"Could not assign %upcase(&libref).",'; put '"Please check metadata permissions! Libname:",libname,'; put '"Liburi:",liburi'; put ');'; put 'end;'; put 'else if msg="ERROR: User does not have appropriate authorization "!!'; put '"level for library SAVE."'; put 'then do;'; put 'msg=catx(" ",'; put '"ERROR: User does not have appropriate authorization level",'; put '"for library %upcase(&libref), libname:",libname,'; put '"Liburi:",liburi'; put ');'; put 'end;'; put 'call symputx(''msg'',msg,''l'');'; put 'if "&mabort"=''HARD'' then call symputx(''mp_abort'',1,''l'');'; put 'end;'; put 'else do;'; put 'put (_all_)(=);'; put 'call symputx(''libname'',libname,''L'');'; put 'call symputx(''liburi'',liburi,''L'');'; put 'end;'; put 'end;'; put 'else if nobj>1 then do;'; put 'if "&mabort"=''HARD'' then call symputx(''mp_abort'',1);'; put 'call symputx(''msg'',"More than one library with libref=&libref");'; put 'end;'; put 'else do;'; put 'if "&mabort"=''HARD'' then call symputx(''mp_abort'',1);'; put 'call symputx(''msg'',"Library &libref not found in metadata");'; put 'end;'; put 'run;'; put '%put NOTE: &msg;'; put '%end;'; put '%else %do;'; put '%put NOTE: Library &libref is already assigned;'; put '%end;'; put '%mp_abort(iftrue= (&mp_abort=1)'; put ',mac=mm_assignlib.sas'; put ',msg=%superq(msg)'; put ')'; put '%mend mm_assignlib;'; put '/** @cond */'; put '%macro mf_getengine(libref'; put ')/*/STORE SOURCE*/;'; put '%local dsid engnum rc engine;'; put '/* in case the parameter is a libref.tablename, pull off just the libref */'; put '%let libref = %upcase(%scan(&libref, 1, %str(.)));'; put '%let dsid=%sysfunc('; put 'open(sashelp.vlibnam(where=(libname="%upcase(&libref)")),i)'; put ');'; put '%if (&dsid ^= 0) %then %do;'; put '%let engnum=%sysfunc(varnum(&dsid,ENGINE));'; put '%let rc=%sysfunc(fetch(&dsid));'; put '%let engine=%sysfunc(getvarc(&dsid,&engnum));'; put '%put &libref. ENGINE is &engine.;'; put '%let rc= %sysfunc(close(&dsid));'; put '%end;'; put '%upcase(&engine)'; put '%mend mf_getengine;'; put '/** @endcond */'; put '%macro mm_assigndirectlib('; put 'libref'; put ',open_passthrough='; put ',sql_options='; put ',mDebug=0'; put ',mAbort=0'; put ')/*/STORE SOURCE*/;'; put '%local mD;'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing mm_assigndirectlib.sas;'; put '%&mD.put _local_;'; put '%if &mAbort=1 %then %let mAbort=;'; put '%else %let mAbort=%str(*);'; put '%&mD.put NOTE: Creating direct (non META) connection to &libref library;'; put '%local cur_engine;'; put '%let cur_engine=%mf_getengine(&libref);'; put '%if &cur_engine ne META and &cur_engine ne %then %do;'; put '%put NOTE: &libref already has a direct (&cur_engine) libname connection;'; put '%return;'; put '%end;'; put '%else %if %upcase(&libref)=WORK %then %do;'; put '%put NOTE: We already have a direct connection to WORK :-) ;'; put '%return;'; put '%end;'; put '/* need to determine the library ENGINE first */'; put '%local engine;'; put 'data _null_;'; put 'length lib_uri engine $256;'; put 'call missing (of _all_);'; put '/* get URI for the particular library */'; put 'rc1=metadata_getnobj("omsobj:SASLibrary?@Libref =''&libref''",1,lib_uri);'; put '/* get the Engine attribute of the previous object */'; put 'rc2=metadata_getattr(lib_uri,''Engine'',engine);'; put 'putlog "mm_assigndirectlib for &libref:" rc1= lib_uri= rc2= engine=;'; put 'call symputx("liburi",lib_uri,''l'');'; put 'call symputx("engine",engine,''l'');'; put 'run;'; put '/* now obtain engine specific connection details */'; put '%if &engine=BASE %then %do;'; put '%&mD.put NOTE: Retrieving BASE library path;'; put 'data _null_;'; put 'length up_uri $256 path cat_path $1024;'; put 'retain cat_path;'; put 'call missing (of _all_);'; put '/* get all the filepaths of the UsingPackages association */'; put 'i=1;'; put 'rc3=metadata_getnasn("&liburi",''UsingPackages'',i,up_uri);'; put 'do while (rc3>0);'; put '/* get the DirectoryName attribute of the previous object */'; put 'rc4=metadata_getattr(up_uri,''DirectoryName'',path);'; put 'if i=1 then path = ''("''!!trim(path)!!''" '';'; put 'else path ='' "''!!trim(path)!!''" '';'; put 'cat_path = trim(cat_path) !! " " !! trim(path) ;'; put 'i+1;'; put 'rc3=metadata_getnasn("&liburi",''UsingPackages'',i,up_uri);'; put 'end;'; put 'cat_path = trim(cat_path) !! ")";'; put '&mD.putlog "NOTE: Getting physical path for &libref library";'; put '&mD.putlog rc3= up_uri= rc4= cat_path= path=;'; put '&mD.putlog "NOTE: Libname cmd will be:";'; put '&mD.putlog "libname &libref" cat_path;'; put 'call symputx("filepath",cat_path,''l'');'; put 'run;'; put '%if %sysevalf(&sysver<9.4) %then %do;'; put 'libname &libref &filepath;'; put '%end;'; put '%else %do;'; put '/* apply the new filelocks option to cater for temporary locks */'; put 'libname &libref &filepath filelockwait=5;'; put '%end;'; put '%end;'; put '%else %if &engine=REMOTE %then %do;'; put 'data x;'; put 'length rcCon rcProp rc k 3 uriCon uriProp PropertyValue PropertyName'; put 'Delimiter $256 properties $2048;'; put 'retain properties;'; put 'rcCon = metadata_getnasn("&liburi", "LibraryConnection", 1, uriCon);'; put 'rcProp = metadata_getnasn(uriCon, "Properties", 1, uriProp);'; put 'k = 1;'; put 'rcProp = metadata_getnasn(uriCon, "Properties", k, uriProp);'; put 'do while (rcProp > 0);'; put 'rc = metadata_getattr(uriProp , "DefaultValue",PropertyValue);'; put 'rc = metadata_getattr(uriProp , "PropertyName",PropertyName);'; put 'rc = metadata_getattr(uriProp , "Delimiter",Delimiter);'; put 'properties = trim(properties) !! " " !! trim(PropertyName)'; put '!! trim(Delimiter) !! trim(PropertyValue);'; put 'output;'; put 'k+1;'; put 'rcProp = metadata_getnasn(uriCon, "Properties", k, uriProp);'; put 'end;'; put '%&mD.put NOTE: Getting properties for REMOTE SHARE &libref library;'; put '&mD.put _all_;'; put '%&mD.put NOTE: Libname cmd will be:;'; put '%&mD.put libname &libref &engine &properties slibref=&libref;'; put 'call symputx ("properties",trim(properties),''l'');'; put 'run;'; put 'libname &libref &engine &properties slibref=&libref;'; put '%end;'; put '%else %if &engine=OLEDB %then %do;'; put '%&mD.put NOTE: Retrieving OLEDB connection details;'; put 'data _null_;'; put 'length domain datasource provider properties schema'; put 'connx_uri domain_uri conprop_uri lib_uri schema_uri value $256.;'; put 'call missing (of _all_);'; put '/* get source connection ID */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,connx_uri);'; put '/* get connection domain */'; put 'rc1=metadata_getnasn(connx_uri,''Domain'',1,domain_uri);'; put 'rc2=metadata_getattr(domain_uri,''Name'',domain);'; put '&mD.putlog / ''NOTE: '' // ''NOTE- connection id: '' connx_uri ;'; put '&mD.putlog ''NOTE- domain: '' domain;'; put '/* get DSN and PROVIDER from connection properties */'; put 'i=0;'; put 'do until (rc<0);'; put 'i+1;'; put 'rc=metadata_getnasn(connx_uri,''Properties'',i,conprop_uri);'; put 'rc2=metadata_getattr(conprop_uri,''Name'',value);'; put 'if value=''Connection.OLE.Property.DATASOURCE.Name.xmlKey.txt'' then do;'; put 'rc3=metadata_getattr(conprop_uri,''DefaultValue'',datasource);'; put 'end;'; put 'else if value=''Connection.OLE.Property.PROVIDER.Name.xmlKey.txt'' then do;'; put 'rc4=metadata_getattr(conprop_uri,''DefaultValue'',provider);'; put 'end;'; put 'else if value=''Connection.OLE.Property.PROPERTIES.Name.xmlKey.txt'' then'; put 'do;'; put 'rc5=metadata_getattr(conprop_uri,''DefaultValue'',properties);'; put 'end;'; put 'end;'; put '&mD.putlog ''NOTE- dsn/provider/properties: '' /'; put 'datasource provider properties;'; put '&mD.putlog ''NOTE- schema: '' schema // ''NOTE-'';'; put '/* get SCHEMA */'; put 'rc6=metadata_getnasn("&liburi",''UsingPackages'',1,lib_uri);'; put 'rc7=metadata_getattr(lib_uri,''SchemaName'',schema);'; put 'call symputx(''SQL_domain'',domain,''l'');'; put 'call symputx(''SQL_dsn'',datasource,''l'');'; put 'call symputx(''SQL_provider'',provider,''l'');'; put 'call symputx(''SQL_properties'',properties,''l'');'; put 'call symputx(''SQL_schema'',schema,''l'');'; put 'run;'; put '%if %length(&open_passthrough)>0 %then %do;'; put 'proc sql &sql_options;'; put 'connect to OLEDB as &open_passthrough(INSERT_SQL=YES'; put '/* need additional properties to make this work */'; put 'properties=(''Integrated Security''=SSPI'; put '''Persist Security Info''=True'; put '%sysfunc(compress(%str(&SQL_properties),%str(())))'; put ')'; put 'DATASOURCE=&sql_dsn PROMPT=NO'; put 'PROVIDER=&sql_provider SCHEMA=&sql_schema CONNECTION = GLOBAL);'; put '%end;'; put '%else %do;'; put 'LIBNAME &libref OLEDB PROPERTIES=&sql_properties'; put 'DATASOURCE=&sql_dsn PROVIDER=&sql_provider SCHEMA=&sql_schema'; put '%if %length(&sql_domain)>0 %then %do;'; put 'authdomain="&sql_domain"'; put '%end;'; put 'connection=shared;'; put '%end;'; put '%end;'; put '%else %if &engine=ODBC %then %do;'; put '%&mD.put NOTE: Retrieving ODBC connection details;'; put 'data _null_;'; put 'length connx_uri conprop_uri value datasource up_uri schema domprop_uri authdomain $256.;'; put 'call missing (of _all_);'; put '/* get source connection ID */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,connx_uri);'; put '/* get connection properties */'; put 'i=0;'; put 'do until (rc2<0);'; put 'i+1;'; put 'rc2=metadata_getnasn(connx_uri,''Properties'',i,conprop_uri);'; put 'rc3=metadata_getattr(conprop_uri,''Name'',value);'; put 'if value=''Connection.ODBC.Property.DATASRC.Name.xmlKey.txt'' then do;'; put 'rc4=metadata_getattr(conprop_uri,''DefaultValue'',datasource);'; put 'rc2=-1;'; put 'end;'; put 'end;'; put '/* get auth domain */'; put 'autrc=metadata_getnasn(connx_uri,"Domain",1,domprop_uri);'; put 'arc=metadata_getattr(domprop_uri,"Name",authdomain);'; put 'if not missing(authdomain) then authdomain=cats(''AUTHDOMAIN='',authdomain);'; put 'call symputx(''authdomain'',authdomain,''l'');'; put '/* get SCHEMA */'; put 'rc6=metadata_getnasn("&liburi",''UsingPackages'',1,up_uri);'; put 'rc7=metadata_getattr(up_uri,''SchemaName'',schema);'; put '&mD.put rc= connx_uri= rc2= conprop_uri= rc3= value= rc4= datasource='; put 'rc6= up_uri= rc7= schema=;'; put 'call symputx(''SQL_schema'',schema,''l'');'; put 'call symputx(''SQL_dsn'',datasource,''l'');'; put 'run;'; put '%if %length(&open_passthrough)>0 %then %do;'; put 'proc sql &sql_options;'; put 'connect to ODBC as &open_passthrough'; put '(INSERT_SQL=YES DATASRC=&sql_dsn. CONNECTION=global);'; put '%end;'; put '%else %do;'; put 'libname &libref ODBC DATASRC=&sql_dsn SCHEMA=&sql_schema &authdomain;'; put '%end;'; put '%end;'; put '%else %if &engine=POSTGRES %then %do;'; put '%put NOTE: Obtaining POSTGRES library details;'; put 'data _null_;'; put 'length database ignore_read_only_columns direct_exe preserve_col_names'; put 'preserve_tab_names server schema authdomain user password'; put 'prop name value uri urisrc $256.;'; put 'call missing (of _all_);'; put '/* get database value */'; put 'prop=''Connection.DBMS.Property.DB.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,database,"");'; put 'if database^='''' then database=''database=''!!quote(trim(database));'; put 'call symputx(''database'',database,''l'');'; put '/* get IGNORE_READ_ONLY_COLUMNS value */'; put 'prop=''Library.DBMS.Property.DBIROC.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,ignore_read_only_columns,"");'; put 'if ignore_read_only_columns^='''' then ignore_read_only_columns='; put '''ignore_read_only_columns=''!!ignore_read_only_columns;'; put 'call symputx(''ignore_read_only_columns'',ignore_read_only_columns,''l'');'; put '/* get DIRECT_EXE value */'; put 'prop=''Library.DBMS.Property.DirectExe.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,direct_exe,"");'; put 'if direct_exe^='''' then direct_exe=''direct_exe=''!!direct_exe;'; put 'call symputx(''direct_exe'',direct_exe,''l'');'; put '/* get PRESERVE_COL_NAMES value */'; put 'prop=''Library.DBMS.Property.PreserveColNames.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,preserve_col_names,"");'; put 'if preserve_col_names^='''' then preserve_col_names='; put '''preserve_col_names=''!!preserve_col_names;'; put 'call symputx(''preserve_col_names'',preserve_col_names,''l'');'; put '/* get PRESERVE_TAB_NAMES value */'; put '/* be careful with PRESERVE_TAB_NAMES=YES - it will mean your table will'; put 'become case sensitive!! */'; put 'prop=''Library.DBMS.Property.PreserveTabNames.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,preserve_tab_names,"");'; put 'if preserve_tab_names^='''' then preserve_tab_names='; put '''preserve_tab_names=''!!preserve_tab_names;'; put 'call symputx(''preserve_tab_names'',preserve_tab_names,''l'');'; put '/* get SERVER value */'; put 'if metadata_getnasn("&liburi","LibraryConnection",1,uri)>0 then do;'; put 'prop=''Connection.DBMS.Property.SERVER.Name.xmlKey.txt'';'; put 'rc=metadata_getprop(uri,prop,server,"");'; put 'end;'; put 'if server^='''' then server=''server=''!!quote(cats(server));'; put 'call symputx(''server'',server,''l'');'; put '/* get SCHEMA value */'; put 'if metadata_getnasn("&liburi","UsingPackages",1,uri)>0 then do;'; put 'rc=metadata_getattr(uri,"SchemaName",schema);'; put 'end;'; put 'if schema^='''' then schema=''schema=''!!schema;'; put 'call symputx(''schema'',schema,''l'');'; put '/* get AUTHDOMAIN value */'; put '/* this is only useful if the user account contains that auth domain'; put 'if metadata_getnasn("&liburi","DefaultLogin",1,uri)>0 then do;'; put 'rc=metadata_getnasn(uri,"Domain",1,urisrc);'; put 'rc=metadata_getattr(urisrc,"Name",authdomain);'; put 'end;'; put 'if authdomain^='''' then authdomain=''authdomain=''!!quote(trim(authdomain));'; put '*/'; put 'call symputx(''authdomain'',authdomain,''l'');'; put '/* get user & pass */'; put 'if authdomain='''' & metadata_getnasn("&liburi","DefaultLogin",1,uri)>0 then'; put 'do;'; put 'rc=metadata_getattr(uri,"UserID",user);'; put 'rc=metadata_getattr(uri,"Password",password);'; put 'end;'; put 'if user^='''' then do;'; put 'user=''user=''!!quote(trim(user));'; put 'password=''password=''!!quote(trim(password));'; put 'end;'; put 'call symputx(''user'',user,''l'');'; put 'call symputx(''password'',password,''l'');'; put '&md.put _all_;'; put 'run;'; put '%if %length(&open_passthrough)>0 %then %do;'; put '%put %str(WARN)ING: Passthrough option for postgres not yet supported;'; put '%return;'; put '%end;'; put '%else %do;'; put '%if &mdebug=1 %then %do;'; put '%put NOTE: Executing the following:/;'; put '%put NOTE- libname &libref POSTGRES &database &ignore_read_only_columns;'; put '%put NOTE- &direct_exe &preserve_col_names &preserve_tab_names;'; put '%put NOTE- &server &schema &authdomain &user &password //;'; put '%end;'; put 'libname &libref POSTGRES &database &ignore_read_only_columns &direct_exe'; put '&preserve_col_names &preserve_tab_names &server &schema &authdomain'; put '&user &password;'; put '%end;'; put '%end;'; put '%else %if &engine=ORACLE %then %do;'; put '%put NOTE: Obtaining &engine library details;'; put 'data _null_;'; put 'length assocuri1 assocuri2 assocuri3 authdomain path schema $256;'; put 'call missing (of _all_);'; put '/* get auth domain */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,assocuri1);'; put 'rc=metadata_getnasn(assocuri1,''Domain'',1,assocuri2);'; put 'rc=metadata_getattr(assocuri2,"Name",authdomain);'; put 'call symputx(''authdomain'',authdomain,''l'');'; put '/* path */'; put 'rc=metadata_getprop(assocuri1,'; put '''Connection.Oracle.Property.PATH.Name.xmlKey.txt'',path);'; put 'call symputx(''path'',path,''l'');'; put '/* schema */'; put 'rc=metadata_getnasn("&liburi",''UsingPackages'',1,assocuri3);'; put 'rc=metadata_getattr(assocuri3,''SchemaName'',schema);'; put 'call symputx(''schema'',schema,''l'');'; put 'run;'; put '%put NOTE: Executing the following:/; %put NOTE-;'; put '%put NOTE- libname &libref ORACLE path=&path schema=&schema;'; put '%put NOTE- authdomain=&authdomain;'; put '%put NOTE-;'; put 'libname &libref ORACLE path=&path schema=&schema authdomain=&authdomain;'; put '%end;'; put '%else %if &engine=SQLSVR %then %do;'; put '%put NOTE: Obtaining &engine library details;'; put 'data _null;'; put 'length assocuri1 assocuri2 assocuri3 authdomain path schema userid'; put 'passwd $256;'; put 'call missing (of _all_);'; put 'rc=metadata_getnasn("&liburi",''DefaultLogin'',1,assocuri1);'; put 'rc=metadata_getattr(assocuri1,"UserID",userid);'; put 'rc=metadata_getattr(assocuri1,"Password",passwd);'; put 'call symputx(''user'',userid,''l'');'; put 'call symputx(''pass'',passwd,''l'');'; put '/* path */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,assocuri2);'; put 'rc=metadata_getprop(assocuri2,'; put '''Connection.SQL.Property.Datasrc.Name.xmlKey.txt'',path);'; put 'call symputx(''path'',path,''l'');'; put '/* schema */'; put 'rc=metadata_getnasn("&liburi",''UsingPackages'',1,assocuri3);'; put 'rc=metadata_getattr(assocuri3,''SchemaName'',schema);'; put 'call symputx(''schema'',schema,''l'');'; put 'run;'; put '%put NOTE: Executing the following:/; %put NOTE-;'; put '%put NOTE- libname &libref SQLSVR datasrc=&path schema=&schema ;'; put '%put NOTE- user="&user" pass="XXX";'; put '%put NOTE-;'; put 'libname &libref SQLSVR datasrc=&path schema=&schema user="&user" pass="&pass";'; put '%end;'; put '%else %if &engine=TERADATA %then %do;'; put '%put NOTE: Obtaining &engine library details;'; put 'data _null;'; put 'length assocuri1 assocuri2 assocuri3 authdomain path schema userid'; put 'passwd $256;'; put 'call missing (of _all_);'; put '/* get auth domain */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,assocuri1);'; put 'rc=metadata_getnasn(assocuri1,''Domain'',1,assocuri2);'; put 'rc=metadata_getattr(assocuri2,"Name",authdomain);'; put 'call symputx(''authdomain'',authdomain,''l'');'; put '/*'; put 'rc=metadata_getnasn("&liburi",''DefaultLogin'',1,assocuri1);'; put 'rc=metadata_getattr(assocuri1,"UserID",userid);'; put 'rc=metadata_getattr(assocuri1,"Password",passwd);'; put 'call symputx(''user'',userid,''l'');'; put 'call symputx(''pass'',passwd,''l'');'; put '*/'; put '/* path */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,assocuri2);'; put 'rc=metadata_getprop(assocuri2,'; put '''Connection.Teradata.Property.SERVER.Name.xmlKey.txt'',path);'; put 'call symputx(''path'',path,''l'');'; put '/* schema */'; put 'rc=metadata_getnasn("&liburi",''UsingPackages'',1,assocuri3);'; put 'rc=metadata_getattr(assocuri3,''SchemaName'',schema);'; put 'call symputx(''schema'',schema,''l'');'; put 'run;'; put '%put NOTE: Executing the following:/; %put NOTE-;'; put '%put NOTE- libname &libref TERADATA server="&path" schema=&schema ;'; put '%put NOTe- authdomain=&authdomain;'; put '%put NOTE-;'; put 'libname &libref TERADATA server="&path" schema=&schema authdomain=&authdomain;'; put '%end;'; put '%else %if &engine= %then %do;'; put '%put NOTE: Libref &libref is not registered in metadata;'; put '%&mAbort.mp_abort('; put 'msg=%str(ERR)OR: Libref &libref is not registered in metadata'; put ',mac=mm_assigndirectlib.sas);'; put '%return;'; put '%end;'; put '%else %do;'; put '%put %str(WARN)ING: Engine &engine is currently unsupported;'; put '%put %str(WARN)ING- Please contact your support team.;'; put '%return;'; put '%end;'; put '%mend mm_assigndirectlib;'; put '%macro mm_getrepos('; put 'outds=work.mm_getrepos'; put ')/*/STORE SOURCE*/;'; put '* use a temporary fileref to hold the response;'; put 'filename response temp;'; put '/* get list of libraries */'; put 'proc metadata in='; put '"1"'; put 'out=response;'; put 'run;'; put '/* write the response to the log for debugging */'; put '/*'; put 'data _null_;'; put 'infile response lrecl=1048576;'; put 'input;'; put 'put _infile_;'; put 'run;'; put '*/'; put '/* create an XML map to read the response */'; put 'filename sxlemap temp;'; put 'data _null_;'; put 'file sxlemap;'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository";'; put 'put "";'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Id";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Name";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Desc";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@DefaultNS";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@RepositoryType";'; put 'put "characterstring20";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@RepositoryFormat";'; put 'put "characterstring10";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@Access";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@CurrentAccess";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@PauseState";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Path";'; put 'put "";'; put 'put "characterstring256";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Engine";'; put 'put "";'; put 'put "characterstring8";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Options";'; put 'put "";'; put 'put "characterstring32";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@MetadataCreated";'; put 'put "characterstring24";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@MetadataUpdated";'; put 'put "characterstring24";'; put 'put '''';'; put 'put ''
'';'; put 'run;'; put 'libname _XML_ xml xmlfileref=response xmlmap=sxlemap;'; put 'proc sort data= _XML_.SASRepos out=&outds;'; put 'by name;'; put 'run;'; put '/* clear references */'; put 'filename sxlemap clear;'; put 'filename response clear;'; put 'libname _XML_ clear;'; put '%mend mm_getrepos;'; put '%macro dc_assignlib(type,libref,passthru=);'; put '%put &sysmacroname entry vars:;'; put '%put _local_;'; put '/* take current repository */'; put '%local repo repocnt x;'; put '%let repo=%sysfunc(getoption(metarepository));'; put '/* get list of repositories and filter */'; put '%mm_getrepos(outds=repos)'; put '%let repocnt=0;'; put 'data repos;'; put 'set repos;'; put 'where repositorytype in(''CUSTOM'',''FOUNDATION'')'; put '& upcase(name) ne "%upcase(&repo)";'; put 'keep id name ;'; put 'call symputx(cats(''repo'',_n_),name,''l'');'; put 'call symputx(''repocnt'',_n_,''l'');'; put 'run;'; put '/* find out which of these repositories has the libref we are searching for */'; put '%local lib_uri;'; put '%let lib_uri=NOTFOUND;'; put 'data _null_; /* check default repo first */'; put 'length lib_uri $256;'; put 'call missing (of _all_);'; put 'rc=metadata_getnobj("omsobj:SASLibrary?@Libref =''&libref''",1,lib_uri);'; put 'call symputx(''lib_uri'',coalescec(lib_uri,''NOTFOUND''),''l'');'; put 'run;'; put '%if &lib_uri=NOTFOUND %then %do;'; put '%do x=1 %to &repocnt;'; put 'options metarepository=&&repo&x;'; put 'data _null_;'; put 'length lib_uri $256;'; put 'call missing (of _all_);'; put 'rc=metadata_getnobj("omsobj:SASLibrary?@Libref =''&libref''",1,lib_uri);'; put 'call symputx(''lib_uri'',coalescec(lib_uri,''NOTFOUND''),''l'');'; put 'run;'; put '%if &lib_uri ne NOTFOUND %then %let x=%eval(&repocnt+1);'; put '%end;'; put '%end;'; put '%if &type=READ %then %do;'; put '%mm_assignlib(&libref)'; put '%end;'; put '%else %if &type=WRITE %then %do;'; put '%mm_assigndirectlib(&libref,open_passthrough=&passthru)'; put '%end;'; put 'options metarepository=&repo;'; put '%mend dc_assignlib;'; put '/** @cond */'; put '%macro mf_existvar(libds /* 2 part dataset name */'; put ', var /* variable name */'; put ')/*/STORE SOURCE*/;'; put '%local dsid rc;'; put '%let dsid=%sysfunc(open(&libds,is));'; put '%if &dsid=0 %then %do;'; put '%put %sysfunc(sysmsg());'; put '0'; put '%end;'; put '%else %if %length(&var)=0 %then %do;'; put '0'; put '%let rc=%sysfunc(close(&dsid));'; put '%end;'; put '%else %do;'; put '%sysfunc(varnum(&dsid,&var))'; put '%let rc=%sysfunc(close(&dsid));'; put '%end;'; put '%mend mf_existvar;'; put '/** @endcond */'; put '%macro mf_getattrn('; put 'libds'; put ',attr'; put ')/*/STORE SOURCE*/;'; put '%local dsid rc;'; put '%let dsid=%sysfunc(open(&libds,is));'; put '%if &dsid = 0 %then %do;'; put '%put %str(WARN)ING: Cannot open %trim(&libds), system message below;'; put '%put %sysfunc(sysmsg());'; put '-1'; put '%end;'; put '%else %do;'; put '%sysfunc(attrn(&dsid,&attr))'; put '%let rc=%sysfunc(close(&dsid));'; put '%end;'; put '%mend mf_getattrn;'; put '%macro mf_getvartype(libds /* two level name */'; put ', var /* variable name from which to return the type */'; put ')/*/STORE SOURCE*/;'; put '%local dsid vnum vtype rc;'; put '/* Open dataset */'; put '%let dsid = %sysfunc(open(&libds));'; put '%if &dsid. > 0 %then %do;'; put '/* Get variable number */'; put '%let vnum = %sysfunc(varnum(&dsid, &var));'; put '/* Get variable type (C/N) */'; put '%if(&vnum. > 0) %then %let vtype = %sysfunc(vartype(&dsid, &vnum.));'; put '%else %do;'; put '%put NOTE: Variable &var does not exist in &libds;'; put '%let vtype = %str( );'; put '%end;'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: dataset &libds not opened! (rc=&dsid);'; put '%put &sysmacroname: %sysfunc(sysmsg());'; put '%return;'; put '%end;'; put '/* Close dataset */'; put '%let rc = %sysfunc(close(&dsid));'; put '/* Return variable type */'; put '&vtype'; put '%mend mf_getvartype;'; put '%macro mf_getattrc('; put 'libds'; put ',attr'; put ')/*/STORE SOURCE*/;'; put '%local dsid rc;'; put '%let dsid=%sysfunc(open(&libds,is));'; put '%if &dsid = 0 %then %do;'; put '%put %str(WARN)ING: Cannot open %trim(&libds), system message below;'; put '%put %sysfunc(sysmsg());'; put '-1'; put '%end;'; put '%else %do;'; put '%sysfunc(attrc(&dsid,&attr))'; put '%let rc=%sysfunc(close(&dsid));'; put '%end;'; put '%mend mf_getattrc;'; put '%macro mp_lockfilecheck('; put 'libds'; put ')/*/STORE SOURCE*/;'; put 'data _null_;'; put 'if _n_=1 then putlog "&sysmacroname entry vars:";'; put 'set sashelp.vmacro;'; put 'where scope="&sysmacroname";'; put 'put name ''='' value;'; put 'run;'; put '%mp_abort(iftrue= (&syscc>0)'; put ',mac=checklock.sas'; put ',msg=Aborting with syscc=&syscc on entry.'; put ')'; put '%mp_abort(iftrue= ("&libds"="0")'; put ',mac=&sysmacroname'; put ',msg=%str(libds not provided)'; put ')'; put '%local msg lib ds;'; put '%let lib=%upcase(%scan(&libds,1,.));'; put '%let ds=%upcase(%scan(&libds,2,.));'; put '/* in DC, format catalogs are passed with a -FC suffix. No saslock here! */'; put '%if %scan(&libds,2,-)=FC %then %do;'; put '%put &sysmacroname: Format Catalog detected, no lockfile applied to &libds;'; put '%return;'; put '%end;'; put '/* do not proceed if no observations can be processed */'; put '%let msg=options obs = 0. syserrortext=%superq(syserrortext);'; put '%mp_abort(iftrue= (%sysfunc(getoption(OBS))=0)'; put ',mac=checklock.sas'; put ',msg=%superq(msg)'; put ')'; put 'data _null_;'; put 'putlog "Checking engine & member type";'; put 'run;'; put '%local engine memtype;'; put '%let memtype=%mf_getattrc(&libds,MTYPE);'; put '%let engine=%mf_getattrc(&libds,ENGINE);'; put '%if &engine ne V9 and &engine ne BASE %then %do;'; put 'data _null_;'; put 'putlog "Lib &lib is not assigned using BASE engine - uses &engine instead";'; put 'putlog "SAS lock check will not be performed";'; put 'run;'; put '%return;'; put '%end;'; put '%else %if &memtype ne DATA %then %do;'; put '%put NOTE: Cannot lock a VIEW!! Memtype=&memtype;'; put '%return;'; put '%end;'; put 'data _null_;'; put 'putlog "Engine = &engine, memtype=&memtype";'; put 'putlog "Attempting lock statement";'; put 'run;'; put 'lock &libds;'; put '%local abortme;'; put '%let abortme=0;'; put '%if &syscc>0 or &SYSLCKRC ne 0 %then %do;'; put '%let msg=Unable to apply lock on &libds (SYSLCKRC=&SYSLCKRC syscc=&syscc);'; put '%put %str(ERR)OR: &sysmacroname: &msg;'; put '%let abortme=1;'; put '%end;'; put 'lock &libds clear;'; put '%mp_abort(iftrue= (&abortme=1)'; put ',mac=&sysmacroname'; put ',msg=%superq(msg)'; put ')'; put '%mend mp_lockfilecheck;'; put '%macro mp_lockanytable('; put 'action'; put ',lib= WORK'; put ',ds=0'; put ',ref='; put ',ctl_ds=0'; put ',loops=25'; put ',loop_secs=1'; put ');'; put 'data _null_;'; put 'if _n_=1 then putlog "&sysmacroname entry vars:";'; put 'set sashelp.vmacro;'; put 'where scope="&sysmacroname";'; put 'put name ''='' value;'; put 'run;'; put '%mp_abort(iftrue= ("&ds"="0" and &action ne MAKETABLE)'; put ',mac=&sysmacroname'; put ',msg=%str(dataset was not provided)'; put ')'; put '%mp_abort(iftrue= (&ctl_ds=0)'; put ',mac=&sysmacroname'; put ',msg=%str(Control dataset was not provided)'; put ')'; put '/* set up lib & mac vars */'; put '%let lib=%upcase(&lib);'; put '%let ds=%upcase(&ds);'; put '%let action=%upcase(&action);'; put '%local user x trans msg abortme;'; put '%let user=%mf_getuser();'; put '%let abortme=0;'; put '%mp_abort(iftrue= (&action ne LOCK & &action ne UNLOCK & &action ne MAKETABLE)'; put ',mac=&sysmacroname'; put ',msg=%str(Invalid action (&action) provided)'; put ')'; put '/* if an err condition exists, exit before we even begin */'; put '%mp_abort(iftrue= (&syscc>0 and &action=LOCK)'; put ',mac=&sysmacroname'; put ',msg=%str(aborting due to syscc=&syscc on LOCK entry)'; put ')'; put '/* do not bother locking work tables (else may affect all WORK libraries) */'; put '%if (%upcase(&lib)=WORK or %str(&lib)=%str()) & &action ne MAKETABLE %then %do;'; put '%put NOTE: WORK libraries will not be registered in the locking system.;'; put '%return;'; put '%end;'; put '/* do not proceed if no observations can be processed */'; put '%mp_abort(iftrue= (%sysfunc(getoption(OBS))=0)'; put ',mac=&sysmacroname'; put ',msg=%str(cannot continue when options obs = 0)'; put ')'; put '%if &ACTION=LOCK %then %do;'; put '/* abort if a SAS lock is already in place, or cannot be applied */'; put '%mp_lockfilecheck(&lib..&ds)'; put '/* next, check there is a record for this table */'; put '%local record_exists_check;'; put 'proc sql noprint;'; put 'select count(*) into: record_exists_check from &ctl_ds'; put 'where LOCK_LIB ="&lib" and LOCK_DS="&ds";'; put 'quit;'; put '%if &syscc>0 %then %put syscc=&syscc sqlrc=&sqlrc;'; put '%if &record_exists_check=0 %then %do;'; put 'data _null_;'; put 'putlog "&sysmacroname: adding record to lock table..";'; put 'run;'; put 'data ;'; put 'if 0 then set &ctl_ds;'; put 'LOCK_LIB ="&lib";'; put 'LOCK_DS="&ds";'; put 'LOCK_STATUS_CD=''LOCKED'';'; put 'LOCK_START_DTTM="%sysfunc(datetime(),%mf_fmtdttm())"dt;'; put 'LOCK_USER_NM="&user";'; put 'LOCK_PID="&sysjobid";'; put 'LOCK_REF="&ref";'; put 'output;stop;'; put 'run;'; put '%let trans=&syslast;'; put 'proc append base=&ctl_ds data=&trans;'; put 'run;'; put '%end;'; put '/* if record does exist, perform lock attempts */'; put '%else %do x=1 %to &loops;'; put 'data _null_;'; put 'putlog "&sysmacroname: attempting lock (iteration &x) "@;'; put 'putlog "at %sysfunc(datetime(),datetime19.) ..";'; put 'run;'; put 'proc sql;'; put 'update &ctl_ds'; put 'set LOCK_STATUS_CD=''LOCKED'''; put ', LOCK_START_DTTM="%sysfunc(datetime(),%mf_fmtdttm())"dt'; put ', LOCK_USER_NM="&user"'; put ', LOCK_PID="&sysjobid"'; put ', LOCK_REF="&ref"'; put 'where LOCK_LIB ="&lib" and LOCK_DS="&ds";'; put 'quit;'; put '/**'; put '* NOTE - occasionally SQL server will return an err code (deadlocked'; put '* transaction). If so, ignore it, keep calm, and carry on..'; put '*/'; put '%if &syscc>0 %then %do;'; put 'data _null_;'; put 'putlog ''NOTE-'' / ''NOTE-'';'; put 'putlog "NOTE- &sysmacroname: Update failed. "@;'; put 'putlog "Resetting err conditions and re-attempting.";'; put 'putlog "NOTE- syscc=&syscc syserr=&syserr sqlrc=&sqlrc";'; put 'putlog ''NOTE-'' / ''NOTE-'';'; put 'run;'; put '%let syscc=0;'; put '%let sqlrc=0;'; put '%end;'; put '/* now check if the record was successfully updated */'; put '%local success_check;'; put 'proc sql noprint;'; put 'select count(*) into: success_check from &ctl_ds'; put 'where LOCK_LIB ="&lib" and LOCK_DS="&ds"'; put 'and LOCK_PID="&sysjobid" and LOCK_STATUS_CD=''LOCKED'';'; put 'quit;'; put '%if &success_check=0 %then %do;'; put '%if &x < &loops %then %do;'; put '/* pause before next check */'; put 'data _null_;'; put 'putlog ''NOTE-'' / ''NOTE-'';'; put 'putlog "NOTE- &sysmacroname: table locked, waiting "@;'; put 'putlog "%sysfunc(sleep(&loop_secs)) seconds.. ";'; put 'putlog "NOTE- (iteration &x of &loops)";'; put 'putlog ''NOTE-'' / ''NOTE-'';'; put 'run;'; put '%end;'; put '%else %do;'; put '%let msg=Unable to lock &lib..&ds via &ctl_ds after &loops attempts.\n'; put 'Please ask your administrator to investigate!;'; put '%let abortme=1;'; put '%end;'; put '%end;'; put '%else %do;'; put 'data _null_;'; put 'putlog ''NOTE-'' / ''NOTE-'';'; put 'putlog "NOTE- &sysmacroname: Table &lib..&ds locked at "@;'; put 'putlog " %sysfunc(datetime(),datetime19.) (iteration &x)"@;'; put 'putlog ''NOTE-'' / ''NOTE-'';'; put 'run;'; put '%if &syscc>0 %then %do;'; put '%put setting syscc(&syscc) back to 0;'; put '%let syscc=0;'; put '%end;'; put '%let x=&loops; /* no more iterations needed */'; put '%end;'; put '%end;'; put '%end;'; put '%else %if &ACTION=UNLOCK %then %do;'; put '%local status cnt;'; put '%let cnt=0;'; put 'proc sql noprint;'; put 'select count(*) into: cnt from &ctl_ds where LOCK_LIB ="&lib" & LOCK_DS="&ds";'; put '%if &cnt=0 %then %do;'; put '%put %str(WAR)NING: &lib..&ds was not previously locked in &ctl_ds!;'; put '%end;'; put '%else %do;'; put 'select LOCK_STATUS_CD into: status from &ctl_ds'; put 'where LOCK_LIB ="&lib" and LOCK_DS="&ds";'; put 'quit;'; put '%if &syscc>0 %then %put syscc=&syscc sqlrc=&sqlrc;'; put '%if &status=LOCKED %then %do;'; put 'data _null_;'; put 'putlog "&sysmacroname: unlocking &lib..&ds:";'; put 'run;'; put 'proc sql;'; put 'update &ctl_ds'; put 'set LOCK_STATUS_CD=''UNLOCKED'''; put ', LOCK_END_DTTM="%sysfunc(datetime(),%mf_fmtdttm())"dt'; put ', LOCK_USER_NM="&user"'; put ', LOCK_PID="&sysjobid"'; put ', LOCK_REF="&ref"'; put 'where LOCK_LIB ="&lib" and LOCK_DS="&ds";'; put 'quit;'; put '%end;'; put '%else %if &status=UNLOCKED %then %do;'; put '%put %str(WAR)NING: &lib..&ds is already unlocked!;'; put '%end;'; put '%else %do;'; put '%put NOTE: Unrecognised STATUS_CD (&status) in &ctl_ds;'; put '%let abortme=1;'; put '%end;'; put '%end;'; put '%end;'; put '%else %do;'; put '%let msg=lock_anytable given unsupported action (&action);'; put '%let abortme=1;'; put '%end;'; put '/* catch errs - mp_abort must be called outside of a logic block */'; put '%mp_abort(iftrue=(&abortme=1),'; put 'msg=%superq(msg),'; put 'mac=&sysmacroname'; put ')'; put '%exit_macro:'; put 'data _null_;'; put 'put "&sysmacroname: Exit vars: action=&action lib=&lib ds=&ds";'; put 'put " syscc=&syscc sqlrc=&sqlrc syserr=&syserr";'; put 'run;'; put '%mend mp_lockanytable;'; put '%macro bitemporal_closeouts('; put 'tech_from=tx_from_dttm'; put ',tech_to = tx_to_dttm /* Technical TO datetime variable.'; put 'Req''d on BASE table only. */'; put ',base_lib=WORK /* Libref of the BASE table. */'; put ',base_dsn=BASETABLE /* Name of BASE table. */'; put ',append_lib=WORK /* Libref of the STAGING table. */'; put ',append_dsn=APPENDTABLE /* Name of STAGING table. */'; put ',PK= name sex /* Business key, space separated. */'; put '/* Should INCLUDE BUS_FROM field if relevant. */'; put ',NOW=DEFINE'; put ',FILTER= /* supply a filter to limit the update */'; put ',outdest= /* supply an unquoted filepath/filename.ext to get'; put 'a text file containing the update statements */'; put ',loadtype='; put ',loadtarget=YES /* if <> YES will return without changing anything */'; put ');'; put '%put ENTERING &sysmacroname;'; put '%local x var start;'; put '%let start=%sysfunc(datetime());'; put '%dc_assignlib(WRITE,&base_lib)'; put '%dc_assignlib(WRITE,&append_lib)'; put '%if &now=DEFINE %then %let now=&dc_dttmtfmt.;'; put '%put &=now;'; put '/**'; put '* perform basic checks'; put '*/'; put '/* do tables exist? */'; put '%if not %sysfunc(exist(&base_lib..&base_dsn)) %then %do;'; put '%mp_abort(msg=&base_lib..&base_dsn does not exist)'; put '%end;'; put '%else %if %sysfunc(exist(&append_lib..&append_dsn))=0'; put 'and %sysfunc(exist(&append_lib..&append_dsn,VIEW))=0 %then %do;'; put '%mp_abort(msg=&append_lib..&append_dsn does not exist)'; put '%end;'; put '/* do TX columns exist? */'; put '%if &loadtype ne UPDATE %then %do;'; put '%if not %mf_existvar(&base_lib..&base_dsn,&tech_from) %then %do;'; put '%mp_abort(msg=&tech_from does not exist on &base_lib..&base_dsn)'; put '%end;'; put '%else %if not %mf_existvar(&base_lib..&base_dsn,&tech_to) %then %do;'; put '%mp_abort(msg=&tech_to does not exist on &base_lib..&base_dsn)'; put '%end;'; put '%end;'; put '/* do PK columns exist? */'; put '%do x=1 %to %sysfunc(countw(&PK));'; put '%let var=%scan(&pk,&x,%str( ));'; put '%if not %mf_existvar(&base_lib..&base_dsn,&var) %then %do;'; put '%mp_abort(msg=&var does not exist on &base_lib..&base_dsn)'; put '%end;'; put '%else %if not %mf_existvar(&append_lib..&append_dsn,&var) %then %do;'; put '%mp_abort(msg=&var does not exist on &append_lib..&append_dsn)'; put '%end;'; put '%end;'; put '/* check uniqueness */'; put 'proc sort data=&append_lib..&append_dsn'; put 'out=___closeout1 noduprecs dupout=___closeout1a;'; put 'by &pk;'; put 'run;'; put '%if %mf_getattrn(___closeout1a,NLOBS)>0 %then'; put '%put NOTE: dups on (&PK) in (&append_lib..&append_dsn);'; put '/* is &NOW value within a tolerance? Should not allow renegade closeouts.. */'; put '%local gap;'; put '%let gap=0;'; put 'data _null_;'; put 'now=&now;'; put 'gap=intck(''HOURS'',now,datetime());'; put 'call symputx(''gap'',gap,''l'');'; put 'run;'; put '%mf_abort('; put 'iftrue=(&gap > 24),'; put 'msg=NOW variable (&now) is not within a 24hr tolerance'; put ')'; put '/* have any warnings / errs occurred thus far? If so, abort */'; put '%mf_abort('; put 'iftrue=(&syscc>0),'; put 'msg=Aborted due to SYSCC=&SYSCC status'; put ')'; put '/**'; put '* Create closeout statements. These are sent as individual SQL statements'; put '* to ensure pass-through utilisation. The update_cnt variable monitors'; put '* how many records were actually updated on the target table.'; put '*/'; put '%local update_cnt;'; put '%let update_cnt=0;'; put 'filename tmp temp;'; put 'data _null_;'; put 'set ___closeout1;'; put 'file tmp;'; put 'if _n_=1 then put ''proc sql noprint;'' ;'; put 'length string $32767.;'; put '%if &loadtype=UPDATE %then %do;'; put 'put "delete from &base_lib..&base_dsn where 1";'; put '%end;'; put '%else %do;'; put 'now=symget(''now'');'; put 'put "update &base_lib..&base_dsn set &tech_to= " now @;'; put '%if %mf_existvar(&base_lib..&base_dsn,PROCESSED_DTTM) %then %do;'; put 'put " ,PROCESSED_DTTM=" now @;'; put '%end;'; put 'put " where " now " lt &tech_to ";'; put '%end;'; put '%do x=1 %to %sysfunc(countw(&PK));'; put '%let var=%scan(&pk,&x,%str( ));'; put '%if %mf_getvartype(&base_lib..&base_dsn,&var)=C %then %do;'; put '/* use single quotes to avoid ampersand resolution in data */'; put 'string=" & &var=''"!!trim(prxchange("s/''/''''/",-1,&var))!!"''";'; put '%end;'; put '%else %do;'; put 'string=cats(" & &var=",&var);'; put '%end;'; put 'put string;'; put '%end;'; put 'put "&filter ;";'; put 'put ''%let update_cnt=%eval(&update_cnt+&sqlobs);%put update_cnt=&update_cnt;'';'; put 'run;'; put 'data _null_;'; put 'infile tmp;'; put 'input;'; put 'putlog _infile_;'; put 'run;'; put '%if &loadtarget ne YES %then %return;'; put '/* ensure we have a lock */'; put '%mp_lockanytable(LOCK,'; put 'lib=&base_lib,ds=&base_dsn'; put ',ref=bitemporal_closeouts'; put ',ctl_ds=&mpelib..mpe_lockanytable'; put ')'; put 'options source2;'; put '%inc tmp;'; put 'filename tmp clear;'; put '/**'; put '* Update audit tracker'; put '*/'; put '%local newobs; %let newobs=%mf_getattrn(work.___closeout1,NLOBS);'; put '%local user; %let user=%mf_getuser();'; put 'proc sql;'; put 'insert into &mpelib..mpe_dataloads'; put 'set libref=%upcase("&base_lib")'; put ',DSN=%upcase("&base_dsn")'; put ',ETLSOURCE="&append_lib..&append_dsn contained &newobs records"'; put ',LOADTYPE="CLOSEOUT"'; put ',DELETED_RECORDS=&update_cnt'; put ',NEW_RECORDS=0'; put ',DURATION=%sysfunc(datetime())-&start'; put ',USER_NM="&user"'; put ',PROCESSED_DTTM=&now;'; put 'quit;'; put '%mend bitemporal_closeouts;'; put '%macro mf_existds(libds'; put ')/*/STORE SOURCE*/;'; put '%if %sysfunc(exist(&libds)) ne 1 & %sysfunc(exist(&libds,VIEW)) ne 1 %then 0;'; put '%else 1;'; put '%mend mf_existds;'; put '%macro mf_getschema(libref'; put ')/*/STORE SOURCE*/;'; put '%local dsid vnum rc schema;'; put '/* in case the parameter is a libref.tablename, pull off just the libref */'; put '%let libref = %upcase(%scan(&libref, 1, %str(.)));'; put '%let dsid=%sysfunc(open(sashelp.vlibnam(where=('; put 'libname="%upcase(&libref)" and sysname=''Schema/Owner'''; put ')),i));'; put '%if (&dsid ^= 0) %then %do;'; put '%let vnum=%sysfunc(varnum(&dsid,SYSVALUE));'; put '%let rc=%sysfunc(fetch(&dsid));'; put '%let schema=%sysfunc(getvarc(&dsid,&vnum));'; put '%put &libref. schema is &schema.;'; put '%let rc= %sysfunc(close(&dsid));'; put '%end;'; put '&schema'; put '%mend mf_getschema;'; put '/** @endcond */'; put '%macro mf_getuniquename(prefix=MC);'; put '&prefix.%substr(%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32-%length(&prefix))'; put '%mend mf_getuniquename;'; put '%macro mf_getvarlist(libds'; put ',dlm=%str( )'; put ',quote=no'; put ',typefilter=A'; put ')/*/STORE SOURCE*/;'; put '/* declare local vars */'; put '%local outvar dsid nvars x rc dlm q var vtype;'; put '/* credit Rowland Hale - byte34 is double quote, 39 is single quote */'; put '%if %upcase("e)=DOUBLE %then %let q=%qsysfunc(byte(34));'; put '%else %if %upcase("e)=SINGLE %then %let q=%qsysfunc(byte(39));'; put '/* open dataset in macro */'; put '%let dsid=%sysfunc(open(&libds));'; put '%if &dsid %then %do;'; put '%let nvars=%sysfunc(attrn(&dsid,NVARS));'; put '%if &nvars>0 %then %do;'; put '/* add variables with supplied delimeter */'; put '%do x=1 %to &nvars;'; put '/* get variable type */'; put '%let vtype=%sysfunc(vartype(&dsid,&x));'; put '%if &vtype=&typefilter or &typefilter=A %then %do;'; put '%let var=&q.%sysfunc(varname(&dsid,&x))&q.;'; put '%if &var=&q&q %then %do;'; put '%put &sysmacroname: Empty column found in &libds!;'; put '%let var=&q. &q.;'; put '%end;'; put '%if %quote(&outvar)=%quote() %then %let outvar=&var;'; put '%else %let outvar=&outvar.&dlm.&var.;'; put '%end;'; put '%end;'; put '%end;'; put '%let rc=%sysfunc(close(&dsid));'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: Unable to open &libds (rc=&dsid);'; put '%put &sysmacroname: SYSMSG= %sysfunc(sysmsg());'; put '%let rc=%sysfunc(close(&dsid));'; put '%end;'; put '%do;%unquote(&outvar)%end;'; put '%mend mf_getvarlist;'; put '%macro mf_abort(mac=mf_abort.sas, msg=, iftrue=%str(1=1)'; put ')/des=''ungraceful abort'' /*STORE SOURCE*/;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%put NOTE: /// mf_abort macro executing //;'; put '%if %length(&mac)>0 %then %put NOTE- called by &mac;'; put '%put NOTE - &msg;'; put '%abort;'; put '%mend mf_abort;'; put '/** @endcond */'; put '%macro mf_verifymacvars('; put 'verifyVars /* list of macro variable NAMES */'; put ',makeUpcase=NO /* set to YES to make all the variable VALUES uppercase */'; put ',mAbort=SOFT'; put ')/*/STORE SOURCE*/;'; put '%local verifyIterator verifyVar abortmsg;'; put '%do verifyIterator=1 %to %sysfunc(countw(&verifyVars,%str( )));'; put '%let verifyVar=%qscan(&verifyVars,&verifyIterator,%str( ));'; put '%if not %symexist(&verifyvar) %then %do;'; put '%let abortmsg= Variable &verifyVar is MISSING;'; put '%goto exit_err;'; put '%end;'; put '%if %length(%trim(&&&verifyVar))=0 %then %do;'; put '%let abortmsg= Variable &verifyVar is EMPTY;'; put '%goto exit_err;'; put '%end;'; put '%if &makeupcase=YES %then %do;'; put '%let &verifyVar=%upcase(&&&verifyvar);'; put '%end;'; put '%end;'; put '%goto exit_success;'; put '%exit_err:'; put '%put &abortmsg;'; put '%mf_abort(iftrue=(&mabort ne SOFT),'; put 'mac=mf_verifymacvars,'; put 'msg=%str(&abortmsg)'; put ')'; put '0'; put '%return;'; put '%exit_success:'; put '1'; put '%mend mf_verifymacvars;'; put '%macro mf_wordsInStr1ButNotStr2('; put 'Str1= /* string containing words to extract */'; put ',Str2= /* used to compare with the extract string */'; put ')/*/STORE SOURCE*/;'; put '%local count_base count_extr i i2 extr_word base_word match outvar;'; put '%if %length(&str1)=0 or %length(&str2)=0 %then %do;'; put '%put base string (str1)= &str1;'; put '%put compare string (str2) = &str2;'; put '%return;'; put '%end;'; put '%let count_base=%sysfunc(countw(&Str2));'; put '%let count_extr=%sysfunc(countw(&Str1));'; put '%do i=1 %to &count_extr;'; put '%let extr_word=%scan(&Str1,&i,%str( ));'; put '%let match=0;'; put '%do i2=1 %to &count_base;'; put '%let base_word=%scan(&Str2,&i2,%str( ));'; put '%if &extr_word=&base_word %then %let match=1;'; put '%end;'; put '%if &match=0 %then %let outvar=&outvar &extr_word;'; put '%end;'; put '&outvar'; put '%mend mf_wordsInStr1ButNotStr2;'; put '%macro mf_isblank(param'; put ')/*/STORE SOURCE*/;'; put '%sysevalf(%superq(param)=,boolean)'; put '%mend mf_isblank;'; put '%macro mp_dropmembers('; put 'list /* space separated list of datasets / views */'; put ',libref=WORK /* can only drop from a single library at a time */'; put ',iftrue=%str(1=1)'; put ')/*/STORE SOURCE*/;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%if %mf_isblank(&list) %then %do;'; put '%put NOTE: nothing to drop!;'; put '%return;'; put '%end;'; put 'proc datasets lib=&libref nolist;'; put 'delete &list;'; put 'delete &list /mtype=view;'; put 'run;'; put '%mend mp_dropmembers;'; put '%macro mf_getquotedstr(IN_STR'; put ',DLM=%str(,)'; put ',QUOTE=S'; put ',indlm=%str( )'; put ')/*/STORE SOURCE*/;'; put '/* credit Rowland Hale - byte34 is double quote, 39 is single quote */'; put '%if "e=S %then %let quote=%qsysfunc(byte(39));'; put '%else %if "e=D %then %let quote=%qsysfunc(byte(34));'; put '%else %if "e=N %then %let quote=;'; put '%local i item buffer;'; put '%let i=1;'; put '%do %while (%qscan(&IN_STR,&i,%str(&indlm)) ne %str() ) ;'; put '%let item=%qscan(&IN_STR,&i,%str(&indlm));'; put '%if %bquote("E) ne %then %let item="E%qtrim(&item)"E;'; put '%else %let item=%qtrim(&item);'; put '%if (&i = 1) %then %let buffer =%qtrim(&item);'; put '%else %let buffer =&buffer&DLM%qtrim(&item);'; put '%let i = %eval(&i+1);'; put '%end;'; put '%let buffer=%sysfunc(coalescec(%qtrim(&buffer),"E"E));'; put '&buffer'; put '%mend mf_getquotedstr;'; put '%macro mf_nobs(libds'; put ')/*/STORE SOURCE*/;'; put '%mf_getattrn(&libds,NLOBS)'; put '%mend mf_nobs;'; put '%macro mp_retainedkey('; put 'base_lib=WORK'; put ',base_dsn=BASETABLE'; put ',append_lib=WORK'; put ',append_dsn=APPENDTABLE'; put ',retained_key=DEFAULT_RK'; put ',business_key= PK1 PK2'; put ',check_uniqueness=NO'; put ',maxkeytable=0'; put ',locktable=0'; put ',outds=WORK.APPEND'; put ',filter_str='; put ');'; put '%put &sysmacroname entry vars:;'; put '%put _local_;'; put '%local base_libds app_libds key_field check maxkey idx_pk newkey_cnt iserr'; put 'msg x tempds1 tempds2 comma_pk appnobs checknobs dropvar tempvar idx_val;'; put '%let base_libds=%upcase(&base_lib..&base_dsn);'; put '%let app_libds=%upcase(&append_lib..&append_dsn);'; put '%let tempds1=%mf_getuniquename();'; put '%let tempds2=%mf_getuniquename();'; put '%let comma_pk=%mf_getquotedstr(in_str=%str(&business_key),dlm=%str(,),quote=);'; put '%let outds=%sysfunc(ifc(%index(&outds,.)=0,work.&outds,&outds));'; put '/* validation checks */'; put '%let iserr=0;'; put '%if &syscc>0 %then %do;'; put '%let iserr=1;'; put '%let msg=%str(SYSCC=&syscc on macro entry);'; put '%end;'; put '%else %if %sysfunc(exist(&base_libds))=0 %then %do;'; put '%let iserr=1;'; put '%let msg=%str(Base LIBDS (&base_libds) expected but NOT FOUND);'; put '%end;'; put '%else %if %sysfunc(exist(&app_libds))=0 %then %do;'; put '%let iserr=1;'; put '%let msg=%str(Append LIBDS (&app_libds) expected but NOT FOUND);'; put '%end;'; put '%else %if &maxkeytable ne 0 and %sysfunc(exist(&maxkeytable))=0 %then %do;'; put '%let iserr=1;'; put '%let msg=%str(Maxkeytable (&maxkeytable) expected but NOT FOUND);'; put '%end;'; put '%else %if &maxkeytable ne 0 and %sysfunc(exist(&locktable))=0 %then %do;'; put '%let iserr=1;'; put '%let msg=%str(Locktable (&locktable) expected but NOT FOUND);'; put '%end;'; put '%else %if %length(&business_key)=0 %then %do;'; put '%let iserr=1;'; put '%let msg=%str(Business key (&business_key) expected but NOT FOUND);'; put '%end;'; put '%do x=1 %to %sysfunc(countw(&business_key));'; put '/* check business key values exist */'; put '%let key_field=%scan(&business_key,&x,%str( ));'; put '%if not %mf_existvar(&app_libds,&key_field) %then %do;'; put '%let iserr=1;'; put '%let msg=Business key (&key_field) not found on &app_libds!;'; put '%goto err;'; put '%end;'; put '%else %if not %mf_existvar(&base_libds,&key_field) %then %do;'; put '%let iserr=1;'; put '%let msg=Business key (&key_field) not found on &base_libds!;'; put '%goto err;'; put '%end;'; put '%end;'; put '%err:'; put '%if &iserr=1 %then %do;'; put '/* err case so first perform an unlock of the base table before exiting */'; put '%mp_lockanytable('; put 'UNLOCK,lib=&base_lib,ds=&base_dsn,ref=%superq(msg),ctl_ds=&locktable'; put ')'; put '%end;'; put '%mp_abort(iftrue=(&iserr=1),mac=mp_retainedkey,msg=%superq(msg))'; put 'proc sql noprint;'; put 'select sum(max(&retained_key),0) into: maxkey from &base_libds;'; put '/**'; put '* get base table RK and bus field values for lookup'; put '*/'; put 'proc sql noprint;'; put 'create table &tempds1 as'; put 'select distinct &comma_pk,&retained_key'; put 'from &base_libds &filter_str'; put 'order by &comma_pk,&retained_key;'; put '%if &check_uniqueness=YES %then %do;'; put 'select count(*) into:checknobs'; put 'from (select distinct &comma_pk from &app_libds);'; put 'select count(*) into: appnobs from &app_libds; /* might be view */'; put '%if &checknobs ne &appnobs %then %do;'; put '%let msg=Source table &app_libds is not unique on (&business_key);'; put '%let iserr=1;'; put '%end;'; put '%end;'; put '%if &iserr=1 %then %do;'; put '/* err case so first perform an unlock of the base table before exiting */'; put '%mp_lockanytable('; put 'UNLOCK,lib=&base_lib,ds=&base_dsn,ref=%superq(msg),ctl_ds=&locktable'; put ')'; put '%end;'; put '%mp_abort(iftrue= (&iserr=1),mac=mp_retainedkey,msg=%superq(msg))'; put '%if %mf_existvar(&app_libds,&retained_key)'; put '%then %let dropvar=(drop=&retained_key);'; put '/* prepare interim table with retained key populated for matching keys */'; put 'proc sql noprint;'; put 'create table &tempds2 as'; put 'select b.&retained_key, a.*'; put 'from &app_libds &dropvar a'; put 'left join &tempds1 b'; put 'on 1'; put '%do idx_pk=1 %to %sysfunc(countw(&business_key));'; put '%let idx_val=%scan(&business_key,&idx_pk);'; put 'and a.&idx_val=b.&idx_val'; put '%end;'; put 'order by &retained_key;'; put '/* identify the number of entries without retained keys (new records) */'; put 'select count(*) into: newkey_cnt'; put 'from &tempds2'; put 'where missing(&retained_key);'; put 'quit;'; put '/**'; put '* Update maxkey table if link provided'; put '*/'; put '%if &maxkeytable ne 0 %then %do;'; put 'proc sql noprint;'; put 'select count(*) into: check from &maxkeytable'; put 'where upcase(keytable)="&base_libds";'; put '%mp_lockanytable(LOCK'; put ',lib=%scan(&maxkeytable,1,.)'; put ',ds=%scan(&maxkeytable,2,.)'; put ',ref=Updating maxkeyvalues with mp_retainedkey'; put ',ctl_ds=&locktable'; put ')'; put 'proc sql;'; put '%if &check=0 %then %do;'; put 'insert into &maxkeytable'; put 'set keytable="&base_libds"'; put ',keycolumn="&retained_key"'; put ',max_key=%eval(&maxkey+&newkey_cnt)'; put ',processed_dttm="%sysfunc(datetime(),%mf_fmtdttm())"dt;'; put '%end;'; put '%else %do;'; put 'update &maxkeytable'; put 'set max_key=%eval(&maxkey+&newkey_cnt)'; put ',processed_dttm="%sysfunc(datetime(),%mf_fmtdttm())"dt'; put 'where keytable="&base_libds";'; put '%end;'; put '%mp_lockanytable(UNLOCK'; put ',lib=%scan(&maxkeytable,1,.)'; put ',ds=%scan(&maxkeytable,2,.)'; put ',ref=Updating maxkeyvalues with maxkey=%eval(&maxkey+&newkey_cnt)'; put ',ctl_ds=&locktable'; put ')'; put '%end;'; put '/* fill in the missing retained key values */'; put '%let tempvar=%mf_getuniquename();'; put 'data &outds(drop=&tempvar);'; put 'retain &tempvar %eval(&maxkey+1);'; put 'set &tempds2;'; put 'if &retained_key =. then &retained_key=&tempvar;'; put '&tempvar=&tempvar+1;'; put 'run;'; put '%mend mp_retainedkey;'; put '/** @cond */'; put '%macro mp_storediffs(libds'; put ',origds'; put ',key'; put ',delds=0'; put ',appds=0'; put ',modds=0'; put ',outds=work.mp_storediffs'; put ',loadref=0'; put ',processed_dttm=0'; put ',mdebug=0'; put ')/*/STORE SOURCE*/;'; put '%local dbg;'; put '%if &mdebug=1 %then %do;'; put '%put &sysmacroname entry vars:;'; put '%put _local_;'; put '%end;'; put '%else %let dbg=*;'; put '/* set up unique and temporary vars */'; put '%local ds1 ds2 ds3 ds4 hashkey inds_auto inds_keep dslist vlist;'; put '%let ds1=%upcase(work.%mf_getuniquename(prefix=mpsd_ds1));'; put '%let ds2=%upcase(work.%mf_getuniquename(prefix=mpsd_ds2));'; put '%let ds3=%upcase(work.%mf_getuniquename(prefix=mpsd_ds3));'; put '%let ds4=%upcase(work.%mf_getuniquename(prefix=mpsd_ds4));'; put '%let hashkey=%upcase(%mf_getuniquename(prefix=mpsd_hashkey));'; put '%let inds_auto=%upcase(%mf_getuniquename(prefix=mpsd_inds_auto));'; put '%let inds_keep=%upcase(%mf_getuniquename(prefix=mpsd_inds_keep));'; put '%let dslist=&origds;'; put '%if &delds ne 0 %then %do;'; put '%let delds=%upcase(&delds);'; put '%if %scan(&delds,-1,.)=&delds %then %let delds=WORK.&delds;'; put '%let dslist=&dslist &delds;'; put '%end;'; put '%if &appds ne 0 %then %do;'; put '%let appds=%upcase(&appds);'; put '%if %scan(&appds,-1,.)=&appds %then %let appds=WORK.&appds;'; put '%let dslist=&dslist &appds;'; put '%end;'; put '%if &modds ne 0 %then %do;'; put '%let modds=%upcase(&modds);'; put '%if %scan(&modds,-1,.)=&modds %then %let modds=WORK.&modds;'; put '%let dslist=&dslist &modds;'; put '%end;'; put '%let origds=%upcase(&origds);'; put '%if %scan(&origds,-1,.)=&origds %then %let origds=WORK.&origds;'; put '%let key=%upcase(&key);'; put '/* hash the key and append all the tables (marking the source) */'; put 'data &ds1;'; put 'set &dslist indsname=&inds_auto;'; put '&hashkey=put(md5(catx(''|'',%mf_getquotedstr(&key,quote=N))),$hex32.);'; put '&inds_keep=upcase(&inds_auto);'; put 'proc sort;'; put 'by &inds_keep &hashkey;'; put 'run;'; put '/* transpose numeric & char vars */'; put 'proc transpose data=&ds1'; put 'out=&ds2(rename=(&hashkey=key_hash _name_=tgtvar_nm col1=newval_num));'; put 'by &inds_keep &hashkey;'; put 'var _numeric_;'; put 'run;'; put 'proc transpose data=&ds1'; put 'out=&ds3('; put 'rename=(&hashkey=key_hash _name_=tgtvar_nm col1=newval_char)'; put 'where=(tgtvar_nm not in ("&hashkey","&inds_keep"))'; put ');'; put 'by &inds_keep &hashkey;'; put 'var _character_;'; put 'run;'; put '%if %index(&libds,-)>0 and %scan(&libds,2,-)=FC %then %do;'; put '/* this is a format catalog - cannot query cols directly */'; put '%let vlist="TYPE","FMTNAME","FMTROW","START","END","LABEL","MIN","MAX"'; put ',"DEFAULT","LENGTH","FUZZ","PREFIX","MULT","FILL","NOEDIT","SEXCL"'; put ',"EEXCL","HLO","DECSEP","DIG3SEP","DATATYPE","LANGUAGE";'; put '%end;'; put '%else %let vlist=%mf_getvarlist(&libds,dlm=%str(,),quote=DOUBLE);'; put 'data &ds4;'; put 'length &inds_keep $41 tgtvar_nm $32 _label_ $256;'; put 'if _n_=1 then call missing(_label_);'; put 'drop _label_;'; put 'set &ds2 &ds3 indsname=&inds_auto;'; put 'tgtvar_nm=upcase(tgtvar_nm);'; put 'if tgtvar_nm in (%upcase(&vlist));'; put 'if upcase(&inds_auto)="&ds2" then tgtvar_type=''N'';'; put 'else if upcase(&inds_auto)="&ds3" then tgtvar_type=''C'';'; put 'else do;'; put 'putlog ''ERR'' +(-1) "OR: unidentified vartype input!" &inds_auto;'; put 'call symputx(''syscc'',98);'; put 'end;'; put 'if &inds_keep="&appds" then move_type=''A'';'; put 'else if &inds_keep="&delds" then move_type=''D'';'; put 'else if &inds_keep="&modds" then move_type=''M'';'; put 'else if &inds_keep="&origds" then move_type=''O'';'; put 'else do;'; put 'putlog ''ERR'' +(-1) "OR: unidentified movetype input!" &inds_keep;'; put 'call symputx(''syscc'',99);'; put 'end;'; put 'tgtvar_nm=upcase(tgtvar_nm);'; put 'if tgtvar_nm in (%mf_getquotedstr(&key)) then is_pk=1;'; put 'else is_pk=0;'; put 'drop &inds_keep;'; put 'run;'; put '%if "&loadref"="0" %then %let loadref=%sysfunc(uuidgen());'; put '%if &processed_dttm=0 %then %let processed_dttm=%sysfunc(datetime());'; put '%let libds=%upcase(&libds);'; put '/* join orig vals for modified & deleted */'; put 'proc sql;'; put 'create table &outds as'; put 'select "&loadref" as load_ref length=36'; put ',&processed_dttm as processed_dttm format=E8601DT26.6'; put ',"%scan(&libds,1,.)" as libref length=8'; put ',"%scan(&libds,2,.)" as dsn length=32'; put ',b.key_hash length=32'; put ',b.move_type length=1'; put ',b.tgtvar_nm length=32'; put ',b.is_pk'; put ',case when b.move_type ne ''M'' then -1'; put 'when a.newval_num=b.newval_num and a.newval_char=b.newval_char then 0'; put 'else 1'; put 'end as is_diff'; put ',b.tgtvar_type length=1'; put ',case when b.move_type=''D'' then b.newval_num'; put 'else a.newval_num'; put 'end as oldval_num format=best32.'; put ',case when b.move_type=''D'' then .'; put 'else b.newval_num'; put 'end as newval_num format=best32.'; put ',case when b.move_type=''D'' then b.newval_char'; put 'else a.newval_char'; put 'end as oldval_char length=32765'; put ',case when b.move_type=''D'' then '''''; put 'else b.newval_char'; put 'end as newval_char length=32765'; put 'from &ds4(where=(move_type=''O'')) as a'; put 'right join &ds4(where=(move_type ne ''O'')) as b'; put 'on a.tgtvar_nm=b.tgtvar_nm'; put 'and a.key_hash=b.key_hash'; put 'order by move_type, key_hash,is_pk desc, tgtvar_nm;'; put '%if &mdebug=0 %then %do;'; put 'proc sql;'; put 'drop table &ds1, &ds2, &ds3, &ds4;'; put '%end;'; put '%mend mp_storediffs;'; put '/** @endcond */'; put '%macro bitemporal_dataloader('; put 'bus_from= /* Business FROM datetime variable. Req''d on'; put 'STAGING & BASE tables.*/'; put ',bus_to = /* Business TO datetime variable. Req''d on'; put 'STAGING & BASE tables. */'; put ',bus_from_override= /* Provide a hard coded BUS_FROM datetime value.*/'; put ',bus_to_override= /* provide a hard coded BUS_TO datetime value */'; put ',tech_from= /* Technical FROM datetime variable. Req''d on'; put 'BASE table only. */'; put ',tech_to = /* Technical TO datetime variable. Req''d on BASE'; put 'table only. */'; put ',processed= 0'; put ',base_lib=WORK /* Libref of the BASE table. */'; put ',base_dsn=BASETABLE /* Name of BASE table. */'; put ',append_lib=WORK /* Libref of the STAGING table. */'; put ',append_dsn=APPENDTABLE'; put ',high_date=''01JAN5999:00:00:00''dt /* High date to close out records */'; put ',PK= name sex'; put ',RK_UNDERLYING='; put ',KEEPVARS= /* Provides option for removing unwanted vars from append table */'; put ',RK_UPDATE_MAXKEYTABLE=NO /* If switching (or mix matching) with regular'; put 'SCD2 loader then set this switch to YES to'; put 'ensure the MAXKEYTABLE is updated with the'; put 'current maximum RK value for the target table'; put '*/'; put ',CHECK_UNIQUENESS=YES /* Perform a check of the APPEND table to ensure it is'; put 'unique on its business key */'; put ',ETLSOURCE=demo /* supply a value ($50.) to show as ETLSOURCE in'; put '&dclib..DATALOADS */'; put ',LOADTYPE=BITEMPORAL'; put ',RK_MAXKEYTABLE= mpe_maxkeyvalues'; put ',LOG=1 /* Switch to 0 to prevent records being added to'; put '&mpelib..mpe_DATALOADS (ie when testing)*/'; put ',DELETE_COL= _____DELETE__THIS__RECORD_____'; put '/* If this variable is found in the append dataset'; put 'then records are closed out (or deleted) in the'; put 'append table where that variable= "Yes" */'; put ',LOADTARGET=YES /* set to anything but uppercase YES to switch off'; put 'target table load and generate temp tables only */'; put ',CLOSE_VARS='; put '/*a problem with regular SCD2 or TXTEMPORAL loads is that there is'; put 'no facility to close out removed records (all records are'; put 'assumed new or changed). But how does one determine which'; put 'records are removed? Short of loading the entire table'; put 'each time? This parameter allows a set of variables'; put '(this should be a subset of the PK) to be declared, and'; put 'the macro will determine which records in the base table'; put 'need to be closed out ahead of the load.'; put 'For instance, given the following:'; put 'Base Table Staging Table'; put 'DATE ENTITY AMOUNT DATE ENTITY AMOUNT'; put 'JAN ACME4 66 JAN ACME4 66'; put 'FEB ACME4 99 FEB ACME4 99'; put 'FEB ACME1 22'; put 'By supplying DATE in CLOSE_VARS and DATE ENTITY as the PK,'; put 'the "FEB PAG 22" record would get closed out.'; put '*/'; put ',config_table=&dclib..MPE_CONFIG'; put ',dclib=&dc_libref'; put ',outds_del=work.outds_del'; put ',outds_add=work.outds_add'; put ',outds_mod=work.outds_mod'; put ',outds_audit=0'; put ');'; put '/* when changing this macro, update the version num here */'; put '%local ver;'; put '%let ver=32;'; put '%put &sysmacroname entry vars:;'; put '%put _local_;'; put '%dc_assignlib(WRITE,&base_lib) /* may not already be assigned */'; put '/* return straight away if nothing to load */'; put '%let nobs= %mf_getattrn(&append_lib..&append_dsn,NLOBS);'; put '%if &nobs=-1 %then %do;'; put 'proc sql noprint; select count(*) into: nobs from &append_lib..&append_dsn;'; put '%end;'; put '%if &nobs=0 %then %do;'; put '%put NOTE:; %put NOTE-;%put NOTE-;%put NOTE-;'; put '%put NOTE- Base dataset &append_lib..&append_dsn is empty. Nothing to upload!;'; put '%put NOTE-;%put NOTE-;%put NOTE-;'; put '%return;'; put '%end;'; put '/* hard exit if err condition exists */'; put '%mp_abort(iftrue= (&syscc > 0)'; put ',mac=bitemporal_dataloader'; put ',msg=%str(Bitemporal transform / job aborted due to SYSCC=&SYSCC status;)'; put ')'; put '%local engine_type;'; put '%let engine_type=%mf_getengine(&base_lib);'; put '%if (&engine_type=REDSHIFT or &engine_type=POSTGRES) and %length(&CLOSE_VARS)>0'; put '%then %do;'; put '%put NOTE:; %put NOTE-;%put NOTE-;%put NOTE-;'; put '%put NOTE- CLOSE_VARS functionality not yet supported in &engine_type;'; put '%put NOTE-;%put NOTE-;%put NOTE-;'; put '%return;'; put '%end;'; put '/**'; put '* The metadata functions (eg mf_existvar) will fail if the base table has a'; put '* SAS lock. So, make a snapshot of the base table for further use.'; put '* Also, make output tables (regardless).'; put '*/'; put '%local basecopy;'; put '%let basecopy=%mf_getuniquename(prefix=basecopy);'; put 'data &basecopy &outds_mod &outds_add &outds_del;'; put 'set &base_lib..&base_dsn;'; put 'stop;'; put 'run;'; put '%mp_abort(iftrue= (&syscc > 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc after base table copy - aborting due to table lock)'; put ')'; put '%local cols idx_pk md5_col ;'; put '%let md5_col=___TMP___md5;'; put '%let check_uniqueness=%upcase(&check_uniqueness);'; put '%let RK_UPDATE_MAXKEYTABLE=%upcase(&RK_UPDATE_MAXKEYTABLE);'; put '%let high_date=%unquote(&high_date);'; put '%let loadtype=%upcase(&loadtype);'; put '/* ensure irrelevant variables are cleared */'; put '%if &loadtype=BUSTEMPORAL %then %do;'; put '%let tech_from=;'; put '%let tech_to=;'; put '%end;'; put '%else %if &loadtype=TXTEMPORAL or &loadtype=UPDATE %then %do;'; put '%let bus_from=;'; put '%let bus_to=;'; put '%end;'; put '/* ensure relevant variables are supplied */'; put '%mp_abort(iftrue=(&loadtype=BITEMPORAL & %mf_verifymacvars(bus_from bus_to)=0)'; put ',mac=bitemporal_dataloader'; put ',msg=%str(Missing BUS_FROM / BUS_TO)'; put ')'; put '%mp_abort(iftrue=(&loadtype=TXTEMPORAL & %mf_verifymacvars(tech_from tech_to)=0)'; put ',mac=bitemporal_dataloader'; put ',msg=%str(Missing TECH_FROM / TECH_TO)'; put ')'; put '/**'; put '* drop any tables (may be defined as views or vice versa preventing overwrite)'; put '*/'; put '%mp_dropmembers(append bitemp0_append bitemp_cols)'; put '/* SQL Server requires its own time values */'; put '/* 9.2 will only give picture format down to seconds. 9.3 allows'; put 'milliseconds by using lower S and defining the decimal in the format name..*/'; put 'PROC FORMAT;'; put 'picture MyMSdt other=''%0Y-%0m-%0dT%0H:%0M:%0S'' (datatype=datetime);'; put 'RUN;'; put '%local dbnow;'; put '%let dbnow="%sysfunc(datetime(),%mf_fmtdttm())"dt;'; put 'data _null_;'; put '/* convert space separated macvar to comma separated for SQL processing */'; put 'call symputx(''PK_COMMA'',tranwrd(compbl("&pk"),'' '','',''),''L'');'; put 'call symputx(''PK_CNT'',countw("&pk",'' ''),''L'');'; put 'now=&dbnow;'; put 'call symputx(''NOW'',now,''L'');'; put 'call symputx(''SQLNOW'',cats("''",put(now,MyMSdt.),"''"),''L'');'; put 'length etlsource $100;'; put 'etlsource=subpad(symget(''etlsource''),1,100);'; put 'call symputx(''etlsource'',etlsource,''l'');'; put 'run;'; put '/**'; put '* Even if no PROCESSED var provided, assume that any variable named'; put '* PROCESSED_DTTM should be updated'; put '*/'; put '%if &processed=0 %then %do;'; put '%if %mf_existvar(&basecopy,PROCESSED_DTTM)'; put '%then %let processed=PROCESSED_DTTM;'; put '%else %let processed=;'; put '%end;'; put '/* extract colnames for md5 creation / change tracking */'; put 'proc contents noprint data=&base_lib..&base_dsn'; put 'out=work.bitemp_cols (keep=name type length varnum format:);'; put 'run;'; put 'proc sql noprint;'; put 'select name into: cols separated by '','''; put 'from work.bitemp_cols'; put 'where upcase(name) not in'; put '(%upcase("&bus_from","&bus_to"'; put ',"&tech_from","&tech_to"'; put ',"&processed","&delete_col")) ;'; put 'select case when type in (2,6) then cats(''put(md5(trim('',name,'')),$hex32.)'')'; put '/* multiply by 1 to strip precision errors (eg 0 != 0) */'; put '/* but ONLY if not missing, else will lose any special missing values */'; put 'else cats(''put(md5(trim(put(ifn(missing('''; put ',name,''),'',name,'','',name,''*1),binary64.))),$hex32.)'') end'; put 'into: stripcols separated by ''||'''; put 'from work.bitemp_cols'; put 'where upcase(name) not in'; put '(%upcase("&bus_from","&bus_to"'; put ',"&tech_from","&tech_to"'; put ',"&processed","&delete_col")) ;'; put '/* set default formats*/'; put '%let bus_from_fmt = datetime19.;'; put '%let bus_to_fmt = datetime19.;'; put '%let processed_fmt = datetime19.;'; put '%let tech_from_fmt = format=datetime19.;'; put '%let tech_to_fmt = format=datetime19.;'; put '%put &=stripcols;'; put '%put &=pk;'; put 'data _null_;'; put 'set work.bitemp_cols;'; put 'if type=2 or type=6 then do;'; put 'length fmt $49.;'; put 'if format='''' then fmt=cats(''$'',length,''.'');'; put 'else fmt=cats(format,formatl,''.'');'; put 'end;'; put 'else do;'; put 'if format='''' then fmt=cats(length,''.'');'; put 'else fmt=cats(format,formatl,''.'',formatd);'; put 'end;'; put 'if upcase(name)="%upcase(&bus_from)" then'; put 'call symputx(''bus_from_fmt'',fmt,''L'');'; put 'else if upcase(name)="%upcase(&bus_to)" then'; put 'call symputx(''bus_to_fmt'',fmt,''L'');'; put 'else if upcase(name)="%upcase(&tech_from)" then'; put 'call symputx(''tech_from_fmt'',"format="!!fmt,''L'');'; put 'else if upcase(name)="%upcase(&tech_to)" then'; put 'call symputx(''tech_to_fmt'',"format="!!fmt,''L'');'; put 'else if upcase(name)="%upcase(&processed)" then'; put 'call symputx(''processed_fmt'',fmt,''L'');'; put 'run;'; put '%if %index(%quote(&cols),___TMP___) %then %do;'; put '%let msg=%str(Table contains a variable name containing "___TMP___".%trim('; put ') This may conflict with temp variable generation!!);'; put '%mp_abort(msg=&msg,mac=bitemporal_dataloader);'; put '%let syscc=5;'; put '%return;'; put '%end;'; put '/* if transaction dates appear on the APPEND table, need to remove them */'; put '%local drop_tx_dates /* used in append table */'; put 'drop_tx_dates_noobs /* used to take the base table structure */;'; put '%if %mf_existvar(&append_lib..&append_dsn, &tech_from)'; put '%then %let drop_tx_dates=&tech_from;'; put '%if %mf_existvar(&append_lib..&append_dsn, &tech_to)'; put '%then %let drop_tx_dates=&drop_tx_dates &tech_to;'; put '%if %length(%trim(&drop_tx_dates))>0'; put '%then %let drop_tx_dates=(drop=&drop_tx_dates);'; put '%if %mf_existvar(&basecopy, &tech_from)'; put '%then %let drop_tx_dates_noobs=&tech_from;'; put '%if %mf_existvar(&basecopy, &tech_to)'; put '%then %let drop_tx_dates_noobs=&drop_tx_dates_noobs &tech_to;'; put '%if %length(%trim(&drop_tx_dates_noobs))>0'; put '%then %let drop_tx_dates_noobs=(drop=&drop_tx_dates_noobs obs=0);'; put '%else %let drop_tx_dates_noobs=(obs=0);'; put '/**'; put '* Lock the table. This is necessary as we are doing a two part update (first'; put '* closing records then appending new records). It is theoretically possible'; put '* that an upload may occur whilst preparing the staging tables. And the'; put '* staging tables are about to be prepared..'; put '*/'; put '%if &LOADTARGET = YES %then %do;'; put '%put locking &base_lib..&base_dsn;'; put '%mp_lockanytable(LOCK,'; put 'lib=&base_lib,ds=&base_dsn,ref=&ETLSOURCE,ctl_ds=&dclib..mpe_lockanytable'; put ')'; put '%if "&outds_audit" ne "0" %then %do;'; put '%put locking &outds_audit;'; put '%mp_lockanytable(LOCK'; put ',lib=%scan(&outds_audit,1,.)'; put ',ds=%scan(&outds_audit,2,.)'; put ',ref=&ETLSOURCE'; put ',ctl_ds=&dclib..mpe_lockanytable'; put ')'; put '%end;'; put '%end;'; put '%else %do;'; put '/* not an actual load, so avoid updating the max key table in next step. */'; put '%let rk_update_maxkeytable=NO;'; put '%end;'; put '%if %length(&RK_UNDERLYING)>0 %then %do;'; put '%mp_retainedkey('; put 'base_lib=&base_lib'; put ',base_dsn=&base_dsn'; put ',append_lib=&append_lib'; put ',append_dsn=&append_dsn'; put ',retained_key=&pk'; put ',business_key=&rk_underlying'; put ',check_uniqueness=&CHECK_UNIQUENESS'; put ',outds=work.append'; put '%if &rk_update_maxkeytable=NO %then %do;'; put ',maxkeytable=0'; put '%end;'; put '%else %do;'; put ',maxkeytable=&dclib..&RK_MAXKEYTABLE'; put '%end;'; put ',locktable=&dclib..mpe_lockanytable'; put '%if &loadtype=BITEMPORAL or &loadtype=TXTEMPORAL %then %do;'; put ',filter_str=%str( (where=( &now < &tech_to)) )'; put '%end;'; put ')'; put '%end;'; put '%else %do;'; put 'proc sql;'; put 'create view work.append as select * from &append_lib..&append_dsn;'; put '%end;'; put '/**'; put '* generate md5 for append table'; put '*/'; put '/* it is possible the source dataset has additional (unwanted) columns.'; put 'Drop if specified; */'; put '%if %length(&keepvars)>0 %then %do;'; put '/* remove tech dates from keepvars as they are generated later */'; put '%let keepvars=%sysfunc(tranwrd(%str( &keepvars ),%str( &tech_from ),%str( )));'; put '%let keepvars=%sysfunc(tranwrd(%str( &keepvars ),%str( &tech_to ),%str( )));'; put '%let keepvars=(keep=&keepvars &bus_from &bus_to &processed &md5_col);'; put '%end;'; put '/* CAS varchar types cause append issues here, so perform autoconvert'; put 'by creating empty local table first */'; put 'data;'; put 'set &base_lib..&base_dsn &drop_tx_dates_noobs;'; put 'run;'; put '%local emptybasetable; %let emptybasetable=&syslast;'; put 'data work.bitemp0_append &keepvars &outds_del(drop=&md5_col )'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5" %then %do;'; put '/nonote2err'; put '%end;'; put ';'; put '/* apply formats for bitemporal vars but not tx dates which are added later */'; put '%if %length(&keepvars)>0 and &loadtype=BITEMPORAL %then %do;'; put 'format &bus_from &bus_from_fmt;'; put 'format &bus_to &bus_to_fmt;'; put '%end;'; put 'set &emptybasetable /* base table reqd in case append has fewer cols */'; put 'work.append &drop_tx_dates;'; put '%if %length(%str(&bus_from_override))>0 %then %do;'; put '&bus_from= %unquote(&bus_from_override) ;'; put '%end;'; put '%if %length(%str(&bus_to_override))>0 %then %do;'; put '&bus_to= %unquote(&bus_to_override) ;'; put '%end;'; put 'length &md5_col $32;'; put '&md5_col=put(md5(&stripcols),hex32.);'; put '%if %length(&processed)>0 %then %do;'; put 'format &processed &processed_fmt;'; put '&processed=&now;'; put '%end;'; put '/**'; put '* If a delete column exists then create the delete dataset'; put '*/'; put '%if %mf_existvar(&append_lib..&append_dsn, &delete_col) %then %do;'; put 'drop &delete_col;'; put 'if upcase(&delete_col) = "YES" then output &outds_del ;'; put 'else output work.bitemp0_append ;'; put 'run;'; put '%if %mf_getattrn(&outds_del,NLOBS)>0 %then %do;'; put '%bitemporal_closeouts('; put 'tech_from=&tech_from'; put ',tech_to = &tech_to'; put ',base_lib=&base_lib'; put ',base_dsn=&base_dsn'; put ',append_lib=work'; put ',append_dsn=%scan(&outds_del,-1,.)'; put ',PK=&bus_from &pk'; put ',NOW=&dbnow'; put ',loadtarget=&loadtarget'; put ',loadtype=&loadtype'; put ')'; put '%end;'; put '%end;'; put '%else %do;'; put 'output work.bitemp0_append;'; put 'run;'; put '%end;'; put '%mp_abort(iftrue= (&syscc gt 0 at line 494)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc)'; put ')'; put '%if %length(&close_vars)>0 %then %do;'; put '/**'; put '* need to close out records that are not provided'; put '*/'; put 'proc sql;'; put 'create table bitemp1_closevars1 as'; put 'select distinct a.%mf_getquotedstr(in_str=&pk,dlm=%str(,a.),quote=)'; put 'from &base_lib..&base_dsn a'; put 'inner join work.bitemp0_append b'; put 'on 1=1'; put '/* join on closevars key */'; put '%do idx_pk=1 %to %sysfunc(countw(&close_vars));'; put '%let idx_val=%scan(&close_vars,&idx_pk);'; put 'and a.&idx_val=b.&idx_val'; put '%end;'; put '/* filter base on tech dates if necessary */'; put '%if &loadtype=TXTEMPORAL %then %do;'; put 'where a.&tech_from <=&now and &now < a.&tech_to'; put '%end;'; put ';'; put 'create table bitemp1_closevars2 as'; put 'select distinct a.*'; put 'from bitemp1_closevars1 a'; put 'left join work.bitemp0_append b'; put 'on 1=1'; put '/* join on primary key */'; put '%do idx_pk=1 %to %sysfunc(countw(&pk));'; put '%let idx_val=%scan(&pk,&idx_pk);'; put 'and a.&idx_val=b.&idx_val'; put '%end;'; put '/* identify removed records by null value in a field in PK but not close_vars'; put '*/'; put 'where b.%scan('; put '%mf_wordsInStr1ButNotStr2(Str1=&pk,Str2=&close_vars),1,%str( )'; put ') IS NULL'; put ';'; put '%if %mf_getattrn(bitemp1_closevars2,NLOBS)>0 %then %do;'; put '%bitemporal_closeouts('; put 'tech_from=&tech_from'; put ',tech_to = &tech_to'; put ',base_lib=&base_lib'; put ',base_dsn=&base_dsn'; put ',append_lib=work'; put ',append_dsn=bitemp1_closevars2'; put ',PK=&bus_from &pk'; put ',NOW=&dbnow'; put ',loadtarget=&loadtarget'; put ',loadtype=&loadtype'; put ')'; put '%end;'; put '%end;'; put '/* return if nothing to load (was just deletes) */'; put '%if %mf_getattrn(work.bitemp0_append,NLOBS)=0 %then %do;'; put '%put NOTE:; %put NOTE-;%put NOTE-;%put NOTE-;'; put '%put NOTE- No updates - just deletes!;'; put '%put NOTE-;%put NOTE-;%put NOTE-;'; put '%end;'; put '/**'; put '* If applying manual overrides to business dates, then the input table MUST'; put '* be unique on the PK. Check, and if not - abort.'; put '*/'; put '%local msg;'; put '%if %length(&bus_from_override.&bus_to_override)>0 or &CHECK_UNIQUENESS=YES'; put '%then %do;'; put 'proc sort data=work.bitemp0_append out=work.bitemp0_check nodupkey;'; put 'by &pk;'; put 'run;'; put '%if %mf_getattrn(work.bitemp0_check,NLOBS)'; put 'ne %mf_getattrn(work.bitemp0_append,NLOBS)'; put '%then %do;'; put '%let msg=INPUT table &append_lib..&append_dsn is not unique on PK (&pk);'; put '%mp_lockanytable(UNLOCK,lib=&base_lib,ds=&base_dsn,ref=&ETLSOURCE (&msg),'; put 'ctl_ds=&dclib..mpe_lockanytable'; put ')'; put '%mp_lockanytable(UNLOCK'; put ',lib=%scan(&outds_audit,1,.)'; put ',ds=%scan(&outds_audit,2,.)'; put ',ref=&ETLSOURCE'; put ',ctl_ds=&dclib..mpe_lockanytable'; put ')'; put '%mp_abort(msg=&msg,mac=bitemporal_dataloader.sas);'; put '%end;'; put '%end;'; put '/**'; put '* extract from BASE table. Only want matching records, as could be very BIG.'; put '* New records are subsequently identified via left join and test for nulls.'; put '*/'; put '%local temp_table temp_table2 base_table baselib_schema;'; put '%put DCNOTE: Extracting matching observations from &base_lib..&base_dsn;'; put '%if &engine_type=OLEDB %then %do;'; put '%let temp_table=##BITEMP_&base_dsn;'; put '%if &loadtype=BITEMPORAL or &loadtype=TXTEMPORAL %then'; put '%let base_table=(select * from [dbo].&base_dsn'; put 'where convert(datetime,&SQLNOW) < &tech_to );'; put '%else %let base_table=[dbo].&base_dsn;'; put 'proc sql;'; put 'create table &base_lib.."&temp_table"n as'; put 'select * from work.bitemp0_append;'; put '/* open up a connection for pass through SQL */'; put '%dc_assignlib(WRITE,&base_lib,passthru=myAlias)'; put 'create table work.bitemp0_base as select * from connection to myAlias('; put '%end;'; put '%else %if &engine_type=REDSHIFT or &engine_type=POSTGRES %then %do;'; put '/* grab schema */'; put '%let baselib_schema=%mf_getschema(&base_lib);'; put '%if &baselib_schema.X ne X %then %let baselib_schema=&baselib_schema..;'; put '/* grab redshift config */'; put '%local redcnt; %let redcnt=0;'; put '%if &engine_type=REDSHIFT %then %do;'; put 'data _null_;'; put 'set &config_table(where=(var_scope=''DCBL_REDSH'' and var_active=1));'; put 'x+1;'; put 'call symputx(cats(''rednm'',x),var_value,''l'');'; put 'call symputx(cats(''redval'',x),var_value,''l'');'; put 'call symputx(''redcnt'',x,''l'');'; put 'run;'; put '%end;'; put '/* cannot persist temp tables so must create a temporary permanent table */'; put '%let temp_table=%mf_getuniquename(prefix=XDCTEMP);'; put '%if &loadtype=BITEMPORAL or &loadtype=TXTEMPORAL %then'; put '%let base_table=(select * from &baselib_schema.&base_dsn'; put 'where timestamp &sqlnow < &tech_to );'; put '%else %let base_table=&baselib_schema.&base_dsn;'; put '/* make empty table first - must clone & drop extra cols as autoload is bad */'; put '%dc_assignlib(WRITE,&base_lib,passthru=myAlias)'; put 'exec (create table &temp_table (like &baselib_schema.&base_dsn)) by myAlias;'; put '%if &engine_type=REDSHIFT %then %do;'; put 'exec (alter table &temp_table alter sortkey none) by myAlias;'; put '%end;'; put '%local dropcols;'; put '%let dropcols=%mf_wordsinstr1butnotstr2('; put 'str1=%upcase(%mf_getvarlist(&basecopy))'; put ',str2=%upcase(&pk)'; put ');'; put '%if %length(&dropcols>0) %then %do idx_pk=1 %to %sysfunc(countw(&dropcols));'; put '%put &=dropcols;'; put '%let idx_val=%scan(&dropcols,&idx_pk);'; put 'exec(alter table &temp_table drop column &idx_val;) by myAlias;'; put '%end;'; put 'exec (alter table &temp_table add column &md5_col varchar(32);) by myAlias;'; put '/* create view to strip formats and avoid warns in log */'; put 'data work.vw_bitemp0/view=work.vw_bitemp0;'; put 'set work.bitemp0_append(keep=&pk &md5_col);'; put 'format _all_;'; put 'run;'; put 'proc append base=&base_lib..&temp_table'; put '%if &engine_type=REDSHIFT %then %do;'; put '('; put '%do idx_pk=1 %to &redcnt;'; put '&&rednm&idx_pk = &&redval&idxpk'; put '%end;'; put ')'; put '%end;'; put 'data=work.vw_bitemp0 force nowarn;'; put 'run;'; put '/* open up a connection for pass through SQL */'; put '%dc_assignlib(WRITE,&base_lib,passthru=myAlias)'; put 'create table work.bitemp0_base as select * from connection to myAlias('; put '%end;'; put '%else %if &engine_type=CAS %then %do;'; put '%if &loadtype=BITEMPORAL or &loadtype=TXTEMPORAL %then'; put '%let base_table=&base_lib..&base_dsn'; put '(where=(&tech_from <=&now and &now < &tech_to));'; put '%else %let base_table=&base_lib..&base_dsn;'; put '%let temp_table=CASUSER.%mf_getuniquename(prefix=DC);'; put 'data &temp_table;'; put 'set work.bitemp0_append;'; put 'run;'; put '%let bitemp0base=CASUSER.%mf_getuniquename(prefix=DC);'; put 'proc fedsql sessref=dcsession;'; put 'create table &bitemp0base{options replace=true} as'; put '%end;'; put '%else %do;'; put '%let temp_table=work.bitemp0_append;'; put '%if &loadtype=BITEMPORAL or &loadtype=TXTEMPORAL %then'; put '%let base_table=&base_lib..&base_dsn'; put '(where=(&tech_from <=&now and &now < &tech_to));'; put '%else %let base_table=&base_lib..&base_dsn;'; put 'proc sql;'; put 'create table work.bitemp0_base as'; put '%end;'; put 'select a.&md5_col /* this identifies NEW records */'; put ', b.*'; put '/* assume first PK field cannot be null (if defined in a PK constraint then'; put 'it definitely cannot be null) */'; put ', case when b.%scan(&pk,1) IS NULL then 1 else 0 end as ___TMP___NEW_FLG'; put 'from &baselib_schema.&temp_table a'; put 'left join &base_table b'; put 'on 1=1'; put '%do idx_pk=1 %to &pk_cnt;'; put '%let idx_val=%scan(&pk,&idx_pk);'; put 'and a.&idx_val=b.&idx_val'; put '%end;'; put '%if &engine_type=OLEDB or &engine_type=REDSHIFT or &engine_type=POSTGRES'; put '%then %do;'; put '); proc sql; drop table &base_lib.."&temp_table"n;'; put '%end;'; put '%else %if &engine_type=CAS %then %do;'; put ';'; put 'quit;'; put 'data work.bitemp0_base;'; put 'set &bitemp0base;'; put 'run;'; put 'proc sql;'; put 'drop table &temp_table;'; put 'drop table &bitemp0base;'; put '%end;'; put '%else %do;'; put ';'; put '%end;'; put '/**'; put '* matching & changed records are those without NULL key values'; put '* &idx_val resolves to rightmost PK value (loop above)'; put '*/'; put '%put syscc (line525)=&syscc, sqlrc=&sqlrc;'; put '%mp_abort(iftrue= (&syscc gt 0 or &sqlrc>0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc sqlrc=&sqlrc)'; put ')'; put '%put hashcols2=&stripcols;'; put 'proc sql;'; put 'create table work.bitemp1_current(drop=___TMP___NEW_FLG) as'; put 'select *'; put ', put(md5(&stripcols),$hex32.) as &md5_col'; put 'from work.bitemp0_base (drop=&md5_col)'; put 'where ___TMP___NEW_FLG=0;'; put '/**'; put '* NEW records were identified in ___TMP___NEW_FLG in bitemp0_base'; put '*/'; put 'proc sql;'; put 'create table &outds_add'; put '(drop=&md5_col'; put '%if %mf_existvar(work.bitemp0_base, &delete_col) %then %do;'; put '&delete_col'; put '%end;'; put ')'; put 'as select a.*'; put '%if &loadtype=BITEMPORAL or &loadtype=TXTEMPORAL %then %do;'; put ',&now as &tech_from &tech_from_fmt'; put ',&high_date as &tech_to &tech_to_fmt'; put '%end;'; put 'from work.bitemp0_append a /* STAGING records (mix of existing & new) */'; put ', work.bitemp0_base b /* BASE records (contains null values for new) */'; put 'where a.&md5_col=b.&md5_col /* took staging md5 across in left join */'; put 'and b.___TMP___NEW_FLG=1; /* NEW records also identified in bitemp0_base */'; put '/**'; put '* identify INSERTS. These are records with the same business key but'; put '* the bus_from and bus_to value are higher / lower (respectively)'; put '* such that the existing record needs to be SPLIT to surround the new'; put '* record.'; put '* eg: OLD RECORD from=1 to=10'; put '* NEW RECORD from=5 to=7'; put '*'; put '* APPENDED RECORDS:'; put '* - from=1 to=5'; put '* - from=5 to=7'; put '* - from=7 to=10'; put '*/'; put '/* inserts cannot happen with TXTEMPORAL */'; put '%if &loadtype=BITEMPORAL or &loadtype=BUSTEMPORAL %then %do;'; put '/* IDENTIFY */'; put 'create table work.bitemp3_inserts as'; put 'select b.*'; put ',a.&bus_from as ___TMP___from'; put ',a.&bus_to as ___TMP___to'; put 'from work.bitemp0_append a'; put ',work.bitemp1_current b'; put 'where a.&bus_from > b.&bus_from'; put 'and a.&bus_to < b.&bus_to'; put '%do idx_pk=1 %to &pk_cnt;'; put '%let idx_val=%scan(&pk,&idx_pk);'; put 'and a.&idx_val=b.&idx_val'; put '%end;'; put 'order by'; put '/* compress blanks and then insert commas (as the datetime fields may'; put 'not be in use) */'; put '%sysfunc(tranwrd(%sysfunc(compbl('; put '&pk &bus_from &bus_to &processed'; put ')),%str( ), %str(,)))'; put ';'; put '/* SPLIT */'; put 'data work.bitemp3a_inserts (drop=___TMP___from ___TMP___retain ___TMP___to) ;'; put 'set work.bitemp3_inserts;'; put 'by &pk &bus_from &bus_to &processed;'; put 'if first.&idx_val then do;'; put '___TMP___retain=&bus_to;'; put '&bus_to=___TMP___from;'; put 'output;'; put '&bus_to=___TMP___retain;'; put 'end;'; put 'if last.&idx_val then do;'; put '&bus_from=___TMP___to;'; put 'output;'; put 'end;'; put 'run;'; put '%end;'; put '%else %do;'; put '/* TX temporal load */'; put 'data work.bitemp3a_inserts;'; put 'set work.bitemp1_current;'; put 'stop;'; put 'run;'; put '%end;'; put '/* APPEND */'; put 'proc sql;'; put 'create view work.bitemp3a_view as'; put 'select * from work.bitemp1_current'; put 'where &md5_col not in (select &md5_col from work.bitemp3a_inserts);'; put 'data bitemp3b_newbase;'; put 'set work.bitemp3a_inserts work.bitemp3a_view;'; put 'run;'; put '/** do not use! this converts short numerics into 8 bytes'; put 'proc sql;'; put 'create table work.bitemp3b_newbase as'; put 'select * from work.bitemp3a_inserts'; put 'union corr'; put 'select * from work.bitemp1_current'; put 'where &md5_col not in (select &md5_col from work.bitemp3a_inserts);'; put '*/'; put '/**'; put '* identify CHANGED records from staging.'; put '* Same business key with different temporal dates or md5 value'; put '* This table must be overlayed onto / into existing business history'; put '*/'; put 'proc sql;'; put 'create table work.bitemp4_updated as select distinct a.*'; put 'from work.bitemp0_append a'; put ',work.bitemp3b_newbase b'; put 'where 1=1'; put '%do idx_pk=1 %to &pk_cnt;'; put '%let idx_val=%scan(&pk,&idx_pk);'; put 'and a.&idx_val=b.&idx_val'; put '%end;'; put 'and ( a.&md5_col ne b.&md5_col'; put '%if &loadtype=BITEMPORAL or &loadtype=BUSTEMPORAL %then %do;'; put 'OR (a.&bus_from ne b.&bus_from or a.&bus_to ne b.&bus_to)'; put '%end;'; put ')'; put ';'; put '/**'; put '* This section would have been one simple step with union all'; put '* but that converts short numerics into 8 bytes!'; put '* so, convoluted alternative to retain the same functionality.'; put '*/'; put '/* base records */'; put 'create view work.bitemp4_prep1 as'; put 'select ''BASE'' as ___TMP___'; put ',b.*'; put 'from work.bitemp4_updated a'; put ',work.bitemp3b_newbase b'; put 'where 1'; put '%do idx_pk=1 %to &pk_cnt;'; put '%let idx_val=%scan(&pk,&idx_pk);'; put 'and a.&idx_val=b.&idx_val'; put '%end;'; put ';'; put '/* updated records */'; put 'create view work.bitemp4_prep2 as'; put 'select ''STAG'' as ___TMP___ ,*'; put 'from work.bitemp4_updated;'; put '/* ensure we only keep columns that appear in both */'; put '%local bp1 bp2 bp3 bp4;'; put '%let bp1=%mf_getvarlist(bitemp4_prep1);'; put '%let bp2=%mf_getvarlist(bitemp4_prep2);'; put '%let bp3=%mf_wordsInStr1ButNotStr2(Str1=&bp1,Str2=&bp2);'; put '%let bp4=%mf_wordsInStr1ButNotStr2(Str1=&bp2,Str2=&bp1);'; put 'data work.bitemp4_prep3/view=bitemp4_prep3;'; put 'set bitemp4_prep1 bitemp4_prep2;'; put '%if %length(XX&bp3&bp4)>2 %then %do;'; put 'drop &bp3 &bp4 ;'; put '%end;'; put 'run;'; put '/* remove duplicates */'; put 'proc sql;'; put 'create table work.bitemp4a_allrecs as'; put 'select distinct *'; put 'from work.bitemp4_prep3'; put 'order by'; put '/* compress blanks and then insert commas (as the datetime fields'; put 'may not be in use) */'; put '%sysfunc(tranwrd(%sysfunc(compbl('; put '&pk &bus_from &bus_to &processed'; put ')),%str( ), %str(,)))'; put ';'; put '%if &loadtype=BITEMPORAL or &loadtype=BUSTEMPORAL %then %do;'; put '/* this section aligns the business dates'; put '(eg for inserts or overlaps in the range) */'; put 'data work.bitemp4b_firstpass (drop=___TMP___cond ___TMP___from ___TMP___to );'; put 'set work.bitemp4a_allrecs;'; put 'by &pk &bus_from &bus_to &processed;'; put 'retain ___TMP___cond ''Name of Condition'';'; put 'retain ___TMP___from ___TMP___to 0;'; put '___TMP___md5lag=lag(&md5_col);'; put '/* reset retained variables */'; put 'if first.&idx_val then do;'; put 'call missing (___TMP___cond, ___TMP___from, ___TMP___to,___TMP___md5lag);'; put 'end;'; put 'else do;'; put '/* if record is identical, carry forward bus_from (and bus_to if higher)*/'; put 'if &md5_col=___TMP___md5lag then do;'; put '&bus_from=___TMP___from;'; put 'if &bus_to<___TMP___to then &bus_to=___TMP___to;'; put 'end;'; put 'end;'; put 'if ___TMP___=''STAG'' then do;'; put '/* need to carry forward the closing record */'; put '___TMP___cond=''Condition 1'';'; put 'end;'; put 'else if ___TMP___cond=''Condition 1'' then do;'; put '/* else ensure bus_from starts from prior record bus_to */'; put 'if &md5_col ne ___TMP___md5lag and &bus_from <= ___TMP___to'; put 'then &bus_from= ___TMP___to;'; put '/* new record may replace old record entirely */'; put 'if &bus_to <= &bus_from then delete;'; put 'else call missing (___TMP___cond, ___TMP___from, ___TMP___to);'; put 'end;'; put '___TMP___from=&bus_from;'; put '___TMP___to=&bus_to;'; put 'run;'; put '%end;'; put '%else %do;'; put '/* keep staged records only */'; put 'data work.bitemp4b_firstpass;'; put 'set work.bitemp4a_allrecs;'; put 'if ___TMP___=''STAG'';'; put 'run;'; put '%end;'; put '/* next phase is to pass through in reverse - so set up the sort statement */'; put '%local byvar;'; put '%do idx_pk=1 %to &pk_cnt;'; put '%let byvar=&byvar descending %scan(&pk,&idx_pk);'; put '%end;'; put '%if &loadtype=BITEMPORAL or &loadtype=BUSTEMPORAL'; put '%then %let byvar=&byvar descending &bus_from descending &bus_to;'; put '/* if matching bus dates supplied, need to ensure we also have a sort'; put 'between BASE and STAGING tables */'; put '%let byvar=&byvar descending ___TMP___;'; put 'proc sort data=work.bitemp4b_firstpass out=work.bitemp4c_sort ;'; put 'by &byvar;'; put 'run;'; put '/**'; put '* Now (in reverse) pass back business start dates'; put '*/'; put 'data work.bitemp4d_secondpass;'; put '%if &loadtype=BITEMPORAL or &loadtype=TXTEMPORAL %then %do;'; put '&tech_from=&now;'; put '&tech_to=&high_date;'; put '%end;'; put 'set work.bitemp4c_sort ;'; put 'by &byvar;'; put 'retain ___TMP___cond ''Name of Condition'';'; put 'retain ___TMP___from ___TMP___to 0;'; put '%if &loadtype=BITEMPORAL or &loadtype=BUSTEMPORAL %then %do;'; put '/* put / _all_ /;*/'; put '___TMP___md5lag=lag(&md5_col);'; put 'if first.&idx_val then do;'; put '/* reset retained variables */'; put 'call missing (___TMP___cond,___TMP___from,___TMP___to,___TMP___md5lag);'; put 'end;'; put 'else do;'; put '/* if record is identical, carry back bus_to */'; put 'if &md5_col=___TMP___md5lag then &bus_to=___TMP___to;'; put 'end;'; put 'if ___TMP___=''STAG'' then do;'; put '/* need to carry forward the closing record */'; put '___TMP___cond=''Condition 2'';'; put 'end;'; put 'else if ___TMP___cond=''Condition 2'' then do;'; put '/* else ensure bus_to stops at subsequent record bus_from */'; put 'if &md5_col ne ___TMP___md5lag and &bus_to >= ___TMP___from'; put 'then &bus_to= ___TMP___from;'; put '/* new record may replace old record entirely */'; put 'if &bus_from >= &bus_to then delete;'; put 'if &bus_from=___TMP___from and &bus_to=___TMP___to then delete;'; put 'else call missing (___TMP___cond, ___TMP___from, ___TMP___to);'; put 'end;'; put '___TMP___from=&bus_from;'; put '___TMP___to=&bus_to;'; put '%end;'; put 'run;'; put '%put syscc (line600)=&syscc;'; put '/**'; put 'There may still be some records (eg old business history) which have not'; put 'changed.'; put 'Need to identify these and remove from the append so they are not updated'; put 'unnecessarily. This is done by generating a new md5 (which INCLUDES the'; put 'business key) and any matching / identical records are split out (from those'; put 'that need to be updated).'; put '*/'; put '%if &loadtype=BITEMPORAL %then %do;'; put '%let cat_string=catx(''|'' ,&bus_from,&bus_to);'; put 'data bitemp5a_lkp (keep=&md5_col);'; put 'set bitemp0_base;'; put '/* for BITEMPORAL we need to compare business dates also */'; put '&md5_col=put(md5(&cat_string!!''|''!!&stripcols),$hex32.);'; put 'run;'; put 'data bitemp5b_updates;'; put 'set bitemp4d_secondpass;'; put 'if _n_=1 then do;'; put 'dcl hash md5_lkp(dataset:''bitemp5a_lkp'');'; put 'md5_lkp.definekey("&md5_col");'; put 'md5_lkp.definedone();'; put 'end;'; put '/* drop old md5 col as will rebuild with new business dates */'; put '&md5_col=put(md5(&cat_string!!''|''!!&stripcols),$hex32.) ;'; put 'if md5_lkp.check()=0 then delete;'; put 'run;'; put 'proc sql;'; put '/* get min bus from as will update (close out) all records from this point'; put '(for that PK)*/'; put 'create table work.bitemp5d_subquery as'; put 'select &pk_comma, min(&bus_from)as &bus_from, max(&bus_to) as &bus_to'; put 'from work.bitemp5b_updates'; put 'group by &pk_comma;'; put '/* index has a huge efficiency impact on upcoming nested subquery */'; put 'create index index1 on work.bitemp5d_subquery(&pk_comma,&bus_from, &bus_to);'; put '%let lastds=work.bitemp5b_updates;'; put '%end;'; put '%else %if &loadtype=TXTEMPORAL or &loadtype=UPDATE %then %do;'; put 'proc sql;'; put 'create table work.bitemp5d_subquery as'; put 'select distinct &pk_comma'; put 'from bitemp4d_secondpass;'; put '%let lastds=work.bitemp4d_secondpass;'; put '%end;'; put '%else %let lastds=work.bitemp4d_secondpass;'; put '/* create single append table (an overlapped pre-sert may be classed as'; put 'both an update AND a new record). Also create temp views that may be'; put 'used for pre-load analysis. */'; put 'data &outds_mod;'; put 'set &lastds(drop=___TMP___: &md5_col);'; put 'run;'; put 'data bitemp6_allrecs / view=bitemp6_allrecs;'; put 'set &outds_mod /* UPDATED records */'; put '&outds_add /* NEW records */;'; put 'run;'; put 'proc sort data=work.bitemp6_allrecs'; put 'out=work.bitemp6_unique'; put 'noduprec'; put 'dupout=work.xx_BADBADBAD;'; put 'by _all_;'; put 'run;'; put '/* we have all our temp tables now so exit if this is all that is needed */'; put '%if &LOADTARGET ne YES %then %return;'; put '/* also exit if an err condition exists */'; put '%if &syscc>0 %then %do;'; put '%put syscc=&syscc;'; put '%mp_lockanytable(UNLOCK,lib=&base_lib,ds=&base_dsn,ref=&ETLSOURCE,'; put 'ctl_ds=&dclib..mpe_lockanytable'; put ')'; put '%if "&outds_audit" ne "0" %then %do;'; put '%mp_lockanytable(UNLOCK'; put ',lib=%scan(&outds_audit,1,.)'; put ',ds=%scan(&outds_audit,2,.)'; put ',ref=&ETLSOURCE'; put ',ctl_ds=&dclib..mpe_lockanytable'; put ')'; put '%end;'; put '%end;'; put '%mp_abort(iftrue= (&syscc>0)'; put ',mac=&sysmacroname in &_program'; put ',msg=%str(Bitemporal transform / job aborted due to SYSCC=&SYSCC status)'; put ')'; put '/* final check - abort if a lock has appeared on the target or audit table */'; put '%mp_lockfilecheck(libds=&base_lib..&base_dsn)'; put '%if %mf_existds(&outds_audit) %then %do;'; put '%mp_lockfilecheck(libds=&outds_audit)'; put '%end;'; put '/**'; put '* STAGING TABLES PREPARED, ERR CONDITION TESTED FOR.. NOW TO LOAD!!'; put '*/'; put '/**'; put '* First, CLOSE OUT changed records (if not a REPLACE)'; put '* Note that SAS does not support ANSI standard for UPDATE with a join condition.'; put '* However - this can be worked around using a nested subquery..'; put '*/'; put 'data _null_;'; put 'putlog "&sysmacroname: CLOSEOUTS commencing";'; put 'run;'; put '%if %mf_getattrn(&lastds,NLOBS)=0 %then %do;'; put 'data _null_;'; put 'putlog "&sysmacroname: No closeouts needed";'; put 'run;'; put '%end;'; put '%else %if &engine_type=CAS %then %do;'; put '%mp_abort(iftrue= (&loadtype=BITEMPORAL or &loadtype=TXTEMPORAL)'; put ',mac=&sysmacroname in &_program'; put ',msg=%str(&loadtype not yet supported in CAS engine)'; put ')'; put '/* create temp table for deletions */'; put '%local delds;%let delds=%mf_getuniquename(prefix=DC);'; put 'data casuser.&delds;'; put 'set work.bitemp5d_subquery;'; put 'run;'; put '/* delete the records */'; put 'proc cas ;'; put 'table.deleteRows / table={'; put 'caslib="&base_lib",'; put 'name="&base_dsn",'; put 'where="1=1",'; put 'whereTable={caslib=''CASUSER'',name="&delds"}'; put '};'; put 'quit;'; put '/* drop temp table */'; put 'proc sql;'; put 'drop table CASUSER.&delds;'; put '%end;'; put '%else %if (&loadtype=BITEMPORAL or &loadtype=TXTEMPORAL or &loadtype=UPDATE)'; put '%then %do;'; put 'data _null_;'; put 'putlog "&sysmacroname: &loadtype operation using &engine_type engine";'; put 'run;'; put '%local flexinow;'; put 'proc sql;'; put '/* if OLEDB then create a temp table for efficiency */'; put '%local innertable;'; put '%if &engine_type=OLEDB %then %do;'; put '%let innertable=[##BITEMP_&base_dsn];'; put '%let top_table=[dbo].&base_dsn;'; put '%let flexinow=&SQLNOW;'; put 'create table &base_lib.."##BITEMP_&base_dsn"n as'; put 'select * from work.bitemp5d_subquery;'; put '/* open up a connection for pass through SQL */'; put '%dc_assignlib(WRITE,&base_lib,passthru=myAlias)'; put 'execute('; put '%end;'; put '%else %if &engine_type=REDSHIFT or &engine_type=POSTGRES %then %do;'; put '%let innertable=%mf_getuniquename(prefix=XDCTEMP);'; put '%let top_table=&baselib_schema.&base_dsn;'; put '%let flexinow=timestamp &SQLNOW;'; put '/* make empty table first - must clone & drop extra cols'; put 'as autoload is bad */'; put '%dc_assignlib(WRITE,&base_lib,passthru=myAlias)'; put 'exec (create table &innertable (like &baselib_schema.&base_dsn)) by myAlias;'; put '%if &engine_type=REDSHIFT %then %do;'; put 'exec (alter table &innertable alter sortkey none) by myAlias;'; put '%end;'; put '%let dropcols=%mf_wordsinstr1butnotstr2('; put 'str1=%upcase(%mf_getvarlist(&basecopy))'; put ',str2=%upcase(%mf_getvarlist(work.bitemp5d_subquery))'; put ');'; put '%if %length(&dropcols>0) %then %do idx_pk=1 %to %sysfunc(countw(&dropcols));'; put '%put &=dropcols;'; put '%let idx_val=%scan(&dropcols,&idx_pk);'; put 'exec(alter table &innertable drop column &idx_val;) by myAlias;;'; put '%end;'; put '/* create view to strip formats and avoid warns in log */'; put 'data work.vw_bitemp5d/view=work.vw_bitemp5d;'; put 'set work.bitemp5d_subquery;'; put 'format _all_;'; put 'run;'; put 'proc append base=&base_lib..&innertable ('; put '%do idx_pk=1 %to &redcnt;'; put '&&rednm&idx_pk = &&redval&idxpk'; put '%end;'; put ')'; put 'data=work.vw_bitemp5d force nowarn;'; put 'run;'; put '/* open up a connection for pass through SQL */'; put '%dc_assignlib(WRITE,&base_lib,passthru=myAlias)'; put 'execute('; put '%end;'; put '%else %do;'; put '%let innertable=bitemp5d_subquery;'; put '%let top_table=&base_lib..&base_dsn;'; put '%let flexinow=&now;'; put '%end;'; put '%if &loadtype=BITEMPORAL or &loadtype=TXTEMPORAL %then %do;'; put 'update &top_table set &tech_to=&flexinow'; put '%if %length(&processed)>0 %then %do;'; put ',&processed=&flexinow'; put '%end;'; put 'where &tech_from <= &flexinow and &flexinow < &tech_to and'; put '%end;'; put '%else %if &loadtype=UPDATE %then %do;'; put '/* changed records are deleted then re-appended when doing UPDATEs */'; put 'delete from &top_table where'; put '%end;'; put '%else %do;'; put '%put %str(ERR)OR: BUSTEMPORAL NOT YET SUPPORTED;'; put '%let syscc=5;'; put '%mp_lockanytable(UNLOCK,lib=&base_lib,ds=&base_dsn,ref=&ETLSOURCE,'; put 'ctl_ds=&dclib..mpe_lockanytable'; put ')'; put '%mp_lockanytable(UNLOCK'; put ',lib=%scan(&outds_audit,1,.)'; put ',ds=%scan(&outds_audit,2,.)'; put ',ref=&ETLSOURCE'; put ',ctl_ds=&dclib..mpe_lockanytable'; put ')'; put '%goto end_of_macro;'; put '%end;'; put '/* perform join inside query as per'; put 'http://stackoverflow.com/questions/24629793/update-with-a-proc-sql */'; put 'exists( select 1 from &baselib_schema.&innertable where'; put '/* loop PK join */'; put '%do idx_pk=1 %to &pk_cnt;'; put '%let idx_val=%scan(&pk,&idx_pk);'; put '&base_dsn..&idx_val=&innertable..&idx_val and'; put '%end;'; put '%if &loadtype=BITEMPORAL %then %do;'; put '&base_dsn..&bus_from >= &innertable..&bus_from'; put 'and &base_dsn..&bus_to <= &innertable..&bus_to and'; put '%end;'; put '/* close the statement */'; put '1=1);'; put '%if &engine_type=OLEDB or &engine_type=REDSHIFT or &engine_type=POSTGRES'; put '%then %do;'; put ') by myAlias;'; put 'execute (drop table &baselib_schema.&innertable) by myAlias;'; put '%end;'; put '%end;'; put 'quit;'; put 'data _null_;'; put 'putlog "&sysmacroname: Closeout complete";'; put 'run;'; put '/**'; put '* Append the new / updated records'; put '*/'; put '%if &engine_type=CAS %then %do;'; put '/* get varchar variables ready for casting */'; put '%local vcfmt vcrename vcassign vcdrop;'; put 'data _null_;'; put 'set work.bitemp_cols(where=(type=6)) end=last;'; put 'length vcrename vcassign vcdrop vcfmt $32767 rancol $32;'; put 'retain vcrename vcassign vcdrop vcfmt;'; put 'if _n_=1 then vcrename=''(rename=('';'; put 'rancol=resolve(''%mf_getuniquename()'');'; put 'vcfmt=trim(vcfmt)!!''length ''!!cats(name)!!'' varchar(*);'';'; put 'vcrename=trim(vcrename)!!'' ''!!cats(name,''='',rancol);'; put 'vcassign=cats(vcassign,name,''='',rancol,'';'');'; put 'vcdrop=cats(vcdrop,''drop ''!!rancol,'';'');'; put 'if last then do;'; put 'vcrename=cats(vcrename,''))'');'; put 'call symputx(''vcfmt'',vcfmt);'; put 'call symputx(''vcrename'',vcrename);'; put 'call symputx(''vcassign'',vcassign);'; put 'call symputx(''vcdrop'',vcdrop);'; put 'end;'; put 'run;'; put '/* prepare a temp cas table with varchars casted */'; put '%let tmp=%mf_getuniquename();'; put 'data casuser.&tmp ;'; put '&vcfmt'; put 'set work.bitemp6_unique &vcrename;'; put '&vcassign'; put '&vcdrop'; put 'run;'; put '/* load the table with varchars applied*/'; put 'data &base_lib..&base_dsn (append=yes )/sessref=dcsession ;'; put 'set casuser.&tmp;'; put 'run;'; put '/* drop temp table */'; put 'proc sql;'; put 'drop table CASUSER.&tmp;'; put '/* this code will not work as regular tables do not have varchars */'; put '/*'; put 'proc casutil;'; put 'load data=work.bitemp6_unique'; put 'outcaslib="&base_lib" casout="&base_dsn" append ;'; put 'quit;'; put '*/'; put '%end;'; put '%else %if &engine_type=REDSHIFT or &engine_type=POSTGRES %then %do;'; put 'proc append base=&base_lib..&base_dsn'; put '%if &engine_type=REDSHIFT %then %do;'; put '('; put '%do idx_pk=1 %to &redcnt;'; put '&&rednm&idx_pk = &&redval&idxpk'; put '%end;'; put ')'; put '%end;'; put 'data=bitemp6_unique force nowarn;'; put 'run;'; put '%end;'; put '%else %do;'; put 'proc append base=&base_lib..&base_dsn data=bitemp6_unique force nowarn; run;'; put '%end;'; put '%mp_lockanytable(UNLOCK,lib=&base_lib,ds=&base_dsn,ref=&ETLSOURCE,'; put 'ctl_ds=&dclib..mpe_lockanytable'; put ')'; put '/* final check on syscc */'; put '%mp_abort(iftrue= (&syscc >4)'; put ',mac=&_program'; put ',msg=%str(!!Upload NOT successful!! Failed on actual update / append stage..)'; put ')'; put '%if &outds_audit ne 0 and &LOADTARGET=YES %then %do;'; put 'data work.vw_outds_orig /view=work.vw_outds_orig;'; put 'set work.bitemp0_base (drop=&md5_col);'; put 'where ___TMP___NEW_FLG=0;'; put 'drop ___TMP___NEW_FLG;'; put 'run;'; put '/* update the AUDIT table */'; put '%if %mf_existds(&outds_audit) %then %do;'; put 'options mprint;'; put '%mp_storediffs(&base_lib..&base_dsn'; put ',work.vw_outds_orig'; put ',&pk &bus_from'; put ',delds=&outds_del'; put ',modds=&outds_mod'; put ',appds=&outds_add'; put ',outds=work.mp_storediffs'; put ',processed_dttm=&now'; put ',loadref=%superq(etlsource)'; put ')'; put '/* exclude unchanged values in modified rows */'; put 'data work.mp_storediffs;'; put 'set work.mp_storediffs;'; put 'if MOVE_TYPE="M" and IS_PK=0 and IS_DIFF=0 then delete;'; put '* putlog load_ref= libref= dsn= key_hash= tgtvar_nm=;'; put 'run;'; put 'proc append base=&outds_audit data=work.mp_storediffs;'; put 'run;'; put '%mp_lockanytable(UNLOCK'; put ',lib=%scan(&outds_audit,1,.)'; put ',ds=%scan(&outds_audit,2,.)'; put ',ref=&ETLSOURCE'; put ',ctl_ds=&dclib..mpe_lockanytable'; put ')'; put '%end;'; put '%end;'; put '%mp_abort(iftrue= (&syscc >4)'; put ',mac=bitemporal_dataloader'; put ',msg=%str(Problem in audit stage (&outds_audit))'; put ')'; put '%let user=%mf_getUser();'; put '/**'; put 'Notify as appropriate EMAILS DISABLED'; put '%sumo_alerts(ALERT_EVENT=UPDATE'; put ', ALERT_TARGET=&base_lib..&base_dsn'; put ', from_user= &user);'; put '*/'; put '/* monitor BiTemporal usage */'; put '%if &log=1 %then %do;'; put '%put syscc=&syscc;'; put '/* do not perform duration calc in pass through */'; put '%local dur;'; put 'data _null_;'; put 'now=symget(''now'');'; put 'dur=%sysfunc(datetime())-&now;'; put 'call symputx(''dur'',dur,''l'');'; put 'run;'; put 'proc sql;'; put 'insert into &dclib..mpe_dataloads'; put 'set libref=%upcase("&base_lib")'; put ',DSN=%upcase("&base_dsn")'; put ',ETLSOURCE="&ETLSOURCE"'; put ',LOADTYPE="&loadtype"'; put ',CHANGED_RECORDS=%mf_getattrn(&lastds,NLOBS)'; put ',NEW_RECORDS=%mf_getattrn(&outds_add,NLOBS)'; put ',DELETED_RECORDS=%mf_getattrn(&outds_del,NLOBS)'; put ',DURATION=&dur'; put ',MAC_VER="v&ver"'; put ',user_nm="&user"'; put ',PROCESSED_DTTM=&now;'; put 'quit;'; put '%put syscc=&syscc;'; put '%end;'; put '%end_of_macro:'; put '%mend bitemporal_dataloader;'; put '%macro mm_getGroups('; put 'user='; put ',outds=work.mm_getGroups'; put ',repo=foundation'; put ',mDebug=0'; put ')/*/STORE SOURCE*/;'; put '%local mD oldrepo;'; put '%let oldrepo=%sysfunc(getoption(metarepository));'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing mm_getGroups.sas;'; put '%&mD.put _local_;'; put '/* on some sites, user / group info is in a different metadata repo to the'; put 'default */'; put '%if &oldrepo ne &repo %then %do;'; put 'options metarepository=&repo;'; put '%end;'; put '%if %length(&user)=0 %then %do;'; put 'data &outds (keep=groupuri groupname groupdesc);'; put 'length groupuri groupname groupdesc group_or_role $256;'; put 'call missing(of _all_);'; put 'i+1;'; put 'do while'; put '(metadata_getnobj("omsobj:IdentityGroup?@Id contains ''.''",i,groupuri)>0);'; put 'rc=metadata_getattr(groupuri, "Name", groupname);'; put 'rc=metadata_getattr(groupuri, "Desc", groupdesc);'; put 'rc=metadata_getattr(groupuri,"PublicType",group_or_role);'; put 'if Group_or_Role = ''UserGroup'' then output;'; put 'i+1;'; put 'end;'; put 'run;'; put '%end;'; put '%else %do;'; put 'data &outds (keep=groupuri groupname groupdesc);'; put 'length uri groupuri groupname groupdesc group_or_role $256;'; put 'call missing(of _all_);'; put 'rc=metadata_getnobj("omsobj:Person?@Name=''&user''",1,uri);'; put 'if rc<=0 then do;'; put 'putlog "%str(WARN)ING: rc=" rc "&user not found "'; put '", or there was an issue reading the repository.";'; put 'stop;'; put 'end;'; put 'a=1;'; put 'grpassn=metadata_getnasn(uri,"IdentityGroups",a,groupuri);'; put 'if grpassn in (-3,-4) then do;'; put 'putlog "%str(WARN)ING: No metadata groups found for &user";'; put 'output;'; put 'end;'; put 'else do while (grpassn > 0);'; put 'rc=metadata_getattr(groupuri, "Name", groupname);'; put 'rc=metadata_getattr(groupuri, "Desc", groupdesc);'; put 'a+1;'; put 'rc=metadata_getattr(groupuri,"PublicType",group_or_role);'; put 'if Group_or_Role = ''UserGroup'' then output;'; put 'grpassn=metadata_getnasn(uri,"IdentityGroups",a,groupuri);'; put 'end;'; put 'run;'; put '%end;'; put '%if &oldrepo ne &repo %then %do;'; put 'options metarepository=&oldrepo;'; put '%end;'; put '%mend mm_getGroups;'; put '%macro dc_getusergroups(user=,outds=mm_getgroups);'; put '%global dc_repo_users;'; put '%if &dc_repo_users= %then %let dc_repo_users=foundation;'; put '%mm_getgroups(user=&user,outds=&outds,repo=&dc_repo_users)'; put '%mend dc_getusergroups;'; put '%macro mpe_getgroups(user=,outds=);'; put '%if not %symexist(dc_repo_users) %then %let dc_repo_users=foundation;'; put '%dc_getusergroups(user=&user,outds=&outds)'; put 'data;'; put 'length groupname groupdesc $256;'; put 'set &dc_libref..mpe_groups;'; put 'where &dc_dttmtfmt. lt tx_to;'; put 'where also upcase(user_name)="%upcase(&user)";'; put 'groupname=group_name;'; put 'groupdesc=group_desc;'; put 'keep groupname groupdesc;'; put 'run;'; put 'data &outds;'; put 'set &syslast &outds(keep=groupname groupdesc);'; put 'run;'; put '%mend mpe_getgroups;'; put '* SAS Macros end;'; put '* SAS Includes start;'; put '* SAS Includes end;'; put '* Binary Files start;'; put '* Binary Files end;'; put '* ServiceInit start;'; put 'options noquotelenmax ps=max;'; put '/* create dummy macros to prevent stpbegin / stpend from corrupting sessions */'; put '%macro stpbegin();'; put '%put NOTE: the STPBEGIN macro should not be used for web apps!;'; put '%mend stpbegin;'; put '%macro stpend();'; put '%put NOTE: the STPEND macro should not be used for web apps!;'; put '%mend stpend;'; put '* ServiceInit end;'; put '* Service start;'; put '/**'; put '@file'; put '@brief Register a new licence key'; put '@details'; put '

SAS Macros

'; put '@li mpeinit.sas'; put '@li bitemporal_dataloader.sas'; put '@li mp_abort.sas'; put '@li mf_getuser.sas'; put '@li mpe_getgroups.sas'; put '@version 9.3'; put '@author 4GL Apps Ltd'; put '@copyright 4GL Apps Ltd. This code may only be used within Data Controller'; put 'and may not be re-distributed or re-sold without the express permission of'; put '4GL Apps Ltd.'; put '@test'; put 'echo ''{"keyupload":[{"activation_key":"slfdjasfda;dslf","licence_key":"asdfasdlfkajsfdas"}]}''>in.json'; put 'sasjs request admin/registerkey -d in.json'; put '**/'; put '%mpeinit()'; put '/* determine users group membership */'; put '%mpe_getgroups(user=%mf_getuser(),outds=work.groups)'; put '%global admin_check;'; put 'proc sql;'; put 'select count(*) into: admin_check'; put 'from groups where groupname="&mpeadmins";'; put '%mp_abort(iftrue= (&admin_check = 0)'; put ',mac=%str(&_program)'; put ',msg=%str(Only members of &mpeadmins may register a key)'; put ')'; put '%global licencekey activation_key;'; put 'data _null_;'; put 'set work.keyupload;'; put 'call symputx(''activation_key'',activation_key);'; put 'call symputx(''licencekey'',licence_key);'; put 'call symputx(''activlen'',length(activation_key));'; put 'call symputx(''liclen'',length(licence_key));'; put 'run;'; put '%mp_abort(iftrue= (&activlen< 10)'; put ',mac=%str(&_program)'; put ',msg=%str(Invalid activation_key)'; put ')'; put '%mp_abort(iftrue= (&liclen < 10)'; put ',mac=%str(&_program)'; put ',msg=%str(Invalid licencekey)'; put ')'; put 'data work.loadme;'; put 'if 0 then set &mpelib..mpe_config;'; put 'VAR_SCOPE=''DC'';'; put 'VAR_NAME=''DC_ACTIVATION_KEY'';'; put 'VAR_VALUE=symget(''activation_key'');'; put 'VAR_ACTIVE=1;'; put 'output;'; put 'VAR_NAME=''DC_LICENCE_KEY'';'; put 'VAR_VALUE=symget(''licencekey'');'; put 'VAR_ACTIVE=1;'; put 'output;'; put 'keep VAR_: ;'; put 'run;'; put '%bitemporal_dataloader('; put 'tech_from=tx_from'; put ',tech_to = tx_to'; put ',base_lib=&mpelib'; put ',base_dsn=mpe_config'; put ',append_lib=WORK'; put ',append_dsn=loadme'; put ',PK= VAR_SCOPE VAR_NAME'; put ',ETLSOURCE=%str(&_program STP)'; put ',LOADTYPE=TXTEMPORAL'; put ',dclib=&mpelib'; put ')'; put 'data work.return;'; put 'msg=''SUCCESS'';'; put 'run;'; put '%webout(OPEN)'; put '%webout(OBJ,return)'; put '%webout(CLOSE)'; put '* Service end;'; run; %mm_createwebservice(path=&appLoc/&path, name=&service, code=sascode, server=&serverName, replace=yes) filename sascode clear; %let path=services/approvers; %let service=getapprovals; filename sascode temp lrecl=32767; data _null_; file sascode; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '/**'; put '@file mp_jsonout.sas'; put '@brief Writes JSON in SASjs format to a fileref'; put '@details This macro can be used to OPEN a JSON stream and send one or more'; put 'tables as arrays of rows, where each row can be an object or a nested array.'; put 'There are two engines available - DATASTEP or PROCJSON.'; put 'PROC JSON is fast but will produce errs like the ones below if'; put 'special chars are encountered.'; put '> (ERR)OR: Some code points did not transcode.'; put '> An object or array close is not valid at this point in the JSON text.'; put '> Date value out of range'; put 'If this happens, try running with ENGINE=DATASTEP.'; put 'The DATASTEP engine is used to handle special SAS missing numerics, and'; put 'can also convert entire datasets to formatted values. Output JSON is always'; put 'in UTF-8.'; put 'Usage:'; put 'filename tmp temp;'; put 'data class; set sashelp.class;run;'; put '%mp_jsonout(OPEN,jref=tmp)'; put '%mp_jsonout(OBJ,class,jref=tmp)'; put '%mp_jsonout(OBJ,class,dslabel=class2,jref=tmp,showmeta=Y)'; put '%mp_jsonout(CLOSE,jref=tmp)'; put 'data _null_;'; put 'infile tmp;'; put 'input;putlog _infile_;'; put 'run;'; put 'If you are building web apps with SAS then you are strongly encouraged to use'; put 'the mX_createwebservice macros in combination with the'; put '[sasjs adapter](https://github.com/sasjs/adapter).'; put 'For more information see https://sasjs.io'; put '@param [in] action Valid values:'; put '@li OPEN - opens the JSON'; put '@li OBJ - sends a table with each row as an object'; put '@li ARR - sends a table with each row in an array'; put '@li CLOSE - closes the JSON'; put '@param [in] ds The dataset to send. Must be a work table.'; put '@param [out] jref= (_webout) The fileref to which to send the JSON'; put '@param [out] dslabel= The name to give the table in the exported JSON'; put '@param [in] fmt= (Y) Whether to keep (Y) or strip (N) formats from the table'; put '@param [in] engine= (DATASTEP) Which engine to use to send the JSON. Options:'; put '@li PROCJSON (default)'; put '@li DATASTEP (more reliable when data has non standard characters)'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to JSON'; put '

Related Files

'; put '@li mp_ds2fmtds.sas'; put '@version 9.2'; put '@author Allan Bowe'; put '@source https://github.com/sasjs/core'; put '**/'; put '%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y'; put ',engine=DATASTEP'; put ',missing=NULL'; put ',showmeta=N'; put ',maxobs=MAX'; put ')/*/STORE SOURCE*/;'; put '%local tempds colinfo fmtds i numcols numobs stmt_obs lastobs optval'; put 'tmpds1 tmpds2 tmpds3 tmpds4;'; put '%let numcols=0;'; put '%if &maxobs ne MAX %then %let stmt_obs=%str(if _n_>&maxobs then stop;);'; put '%if &action=OPEN %then %do;'; put 'options nobomfile;'; put 'data _null_;file &jref encoding=''utf-8'' lrecl=200;'; put 'put ''{"PROCESSED_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''"'';'; put 'run;'; put '%end;'; put '%else %if (&action=ARR or &action=OBJ) %then %do;'; put '/* force variable names to always be uppercase in the JSON */'; put 'options validvarname=upcase;'; put '/* To avoid issues with _webout on EBI - such as encoding diffs and truncation'; put '(https://support.sas.com/kb/49/325.html) we use temporary files */'; put 'filename _sjs1 temp lrecl=200 ;'; put 'data _null_; file _sjs1 encoding=''utf-8'';'; put 'put ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":";'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjs1 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs1 clear;'; put '/* grab col defs */'; put 'proc contents noprint data=&ds'; put 'out=_data_(keep=name type length format formatl formatd varnum label);'; put 'run;'; put '%let colinfo=%scan(&syslast,2,.);'; put 'proc sort data=&colinfo;'; put 'by varnum;'; put 'run;'; put '/* move meta to mac vars */'; put 'data &colinfo;'; put 'if _n_=1 then call symputx(''numcols'',nobs,''l'');'; put 'set &colinfo end=last nobs=nobs;'; put 'name=upcase(name);'; put '/* fix formats */'; put 'if type=2 or type=6 then do;'; put 'typelong=''char'';'; put 'length fmt $49.;'; put 'if format='''' then fmt=cats(''$'',length,''.'');'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else fmt=cats(format,formatl,''.'');'; put 'end;'; put 'else do;'; put 'typelong=''num'';'; put 'if format='''' then fmt=''best.'';'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else if formatd=0 then fmt=cats(format,formatl,''.'');'; put 'else fmt=cats(format,formatl,''.'',formatd);'; put 'end;'; put '/* 32 char unique name */'; put 'newname=''sasjs''!!substr(cats(put(md5(name),$hex32.)),1,27);'; put 'call symputx(cats(''name'',_n_),name,''l'');'; put 'call symputx(cats(''newname'',_n_),newname,''l'');'; put 'call symputx(cats(''length'',_n_),length,''l'');'; put 'call symputx(cats(''fmt'',_n_),fmt,''l'');'; put 'call symputx(cats(''type'',_n_),type,''l'');'; put 'call symputx(cats(''typelong'',_n_),typelong,''l'');'; put 'call symputx(cats(''label'',_n_),coalescec(label,name),''l'');'; put '/* overwritten when fmt=Y and a custom format exists in catalog */'; put 'if typelong=''num'' then call symputx(cats(''fmtlen'',_n_),200,''l'');'; put 'else call symputx(cats(''fmtlen'',_n_),min(32767,ceil((length+10)*1.5)),''l'');'; put 'run;'; put '%let tempds=%substr(_%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql;'; put 'select count(*) into: lastobs from &ds;'; put '%if &maxobs ne MAX %then %let lastobs=%sysfunc(min(&lastobs,&maxobs));'; put '%if &engine=PROCJSON %then %do;'; put '%if &missing=STRING %then %do;'; put '%put &sysmacroname: Special Missings not supported in proc json.;'; put '%put &sysmacroname: Switching to DATASTEP engine;'; put '%goto datastep;'; put '%end;'; put 'data &tempds;'; put 'set &ds;'; put '&stmt_obs;'; put '%if &fmt=N %then format _numeric_ best32.;;'; put '/* PRETTY is necessary to avoid line truncation in large files */'; put 'filename _sjs2 temp lrecl=131068 encoding=''utf-8'';'; put 'proc json out=_sjs2 pretty'; put '%if &action=ARR %then nokeys ;'; put ';export &tempds / nosastags fmtnumeric;'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs2 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs2 clear;'; put '%end;'; put '%else %if &engine=DATASTEP %then %do;'; put '%datastep:'; put '%if %sysfunc(exist(&ds)) ne 1 & %sysfunc(exist(&ds,VIEW)) ne 1'; put '%then %do;'; put '%put &sysmacroname: &ds NOT FOUND!!!;'; put '%return;'; put '%end;'; put '%if &fmt=Y %then %do;'; put '/**'; put '* Extract format definitions'; put '* First, by getting library locations from dictionary.formats'; put '* Then, by exporting the width using proc format'; put '* Cannot use maxw from sashelp.vformat as not always populated'; put '* Cannot use fmtinfo() as not supported in all flavours'; put '*/'; put '%let tmpds1=%substr(fmtsum%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds2=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds3=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds4=%substr(col%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql noprint;'; put 'create table &tmpds1 as'; put 'select cats(libname,''.'',memname) as FMTCAT,'; put 'FMTNAME'; put 'from dictionary.formats'; put 'where fmttype=''F'' and libname is not null'; put 'and fmtname in (select format from &colinfo where format is not null)'; put 'order by 1;'; put 'create table &tmpds2('; put 'FMTNAME char(32),'; put 'LENGTH num'; put ');'; put '%local catlist cat fmtlist i;'; put 'select distinct fmtcat into: catlist separated by '' '' from &tmpds1;'; put '%do i=1 %to %sysfunc(countw(&catlist,%str( )));'; put '%let cat=%scan(&catlist,&i,%str( ));'; put 'proc sql;'; put 'select distinct fmtname into: fmtlist separated by '' '''; put 'from &tmpds1 where fmtcat="&cat";'; put 'proc format lib=&cat cntlout=&tmpds3(keep=fmtname length);'; put 'select &fmtlist;'; put 'run;'; put 'proc sql;'; put 'insert into &tmpds2 select distinct fmtname,length from &tmpds3;'; put '%end;'; put 'proc sql;'; put 'create table &tmpds4 as'; put 'select a.*, b.length as MAXW'; put 'from &colinfo a'; put 'left join &tmpds2 b'; put 'on cats(a.format)=cats(upcase(b.fmtname))'; put 'order by a.varnum;'; put 'data _null_;'; put 'set &tmpds4;'; put 'if not missing(maxw);'; put 'call symputx('; put 'cats(''fmtlen'',_n_),'; put '/* vars need extra padding due to JSON escaping of special chars */'; put 'min(32767,ceil((max(length,maxw)+10)*1.5))'; put ',''l'''; put ');'; put 'run;'; put '/* configure varlenchk - as we are explicitly shortening the variables */'; put '%let optval=%sysfunc(getoption(varlenchk));'; put 'options varlenchk=NOWARN;'; put 'data _data_(compress=char);'; put '/* shorten the new vars */'; put 'length'; put '%do i=1 %to &numcols;'; put '&&name&i $&&fmtlen&i'; put '%end;'; put ';'; put '/* rename on entry */'; put 'set &ds(rename=('; put '%do i=1 %to &numcols;'; put '&&name&i=&&newname&i'; put '%end;'; put '));'; put '&stmt_obs;'; put 'drop'; put '%do i=1 %to &numcols;'; put '&&newname&i'; put '%end;'; put ';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=num %then %do;'; put '&&name&i=cats(put(&&newname&i,&&fmt&i));'; put '%end;'; put '%else %do;'; put '&&name&i=put(&&newname&i,&&fmt&i);'; put '%end;'; put '%end;'; put 'if _error_ then do;'; put 'call symputx(''syscc'',1012);'; put 'stop;'; put 'end;'; put 'run;'; put '%let fmtds=&syslast;'; put 'options varlenchk=&optval;'; put '%end;'; put 'proc format; /* credit yabwon for special null removal */'; put 'value bart (default=40)'; put '%if &missing=NULL %then %do;'; put '._ - .z = null'; put '%end;'; put '%else %do;'; put '._ = [quote()]'; put '. = null'; put '.a - .z = [quote()]'; put '%end;'; put 'other = [best.];'; put 'data &tempds;'; put 'attrib _all_ label='''';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'length &&name&i $&&fmtlen&i...;'; put 'format &&name&i $&&fmtlen&i...;'; put '%end;'; put '%end;'; put '%if &fmt=Y %then %do;'; put 'set &fmtds;'; put '%end;'; put '%else %do;'; put 'set &ds;'; put '%end;'; put '&stmt_obs;'; put 'format _numeric_ bart.;'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'if findc(&&name&i,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put '&&name&i=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,&&name&i)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else &&name&i=quote(cats(&&name&i));'; put '%end;'; put '%end;'; put 'run;'; put 'filename _sjs3 temp lrecl=131068 ;'; put 'data _null_;'; put 'file _sjs3 encoding=''utf-8'';'; put 'if _n_=1 then put "[";'; put 'set &tempds;'; put 'if _n_>1 then put "," @; put'; put '%if &action=ARR %then "[" ; %else "{" ;'; put '%do i=1 %to &numcols;'; put '%if &i>1 %then "," ;'; put '%if &action=OBJ %then """&&name&i"":" ;'; put '"&&name&i"n /* name literal for reserved variable names */'; put '%end;'; put '%if &action=ARR %then "]" ; %else "}" ; ;'; put '/* close out the table */'; put 'data _null_;'; put 'file _sjs3 mod encoding=''utf-8'';'; put 'put '']'';'; put 'run;'; put 'data _null_;'; put 'infile _sjs3 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs3 clear;'; put '%end;'; put 'proc sql;'; put 'drop table &colinfo, &tempds;'; put '%if %substr(&showmeta,1,1)=Y %then %do;'; put 'filename _sjs4 temp lrecl=131068 encoding=''utf-8'';'; put 'data _null_;'; put 'file _sjs4;'; put 'length label $350;'; put 'put ", ""$%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":{""vars"":{";'; put 'do i=1 to &numcols;'; put 'name=quote(trim(symget(cats(''name'',i))));'; put 'format=quote(trim(symget(cats(''fmt'',i))));'; put 'label=quote(prxchange(''s/\\/\\\\/'',-1,trim(symget(cats(''label'',i)))));'; put 'length=quote(trim(symget(cats(''length'',i))));'; put 'type=quote(trim(symget(cats(''typelong'',i))));'; put 'if i>1 then put "," @@;'; put 'put name '':{"format":'' format '',"label":'' label'; put ''',"length":'' length '',"type":'' type ''}'';'; put 'end;'; put 'put ''}}'';'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs4 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs4 clear;'; put '%end;'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put 'data _null_; file &jref encoding=''utf-8'' mod ;'; put 'put "}";'; put 'run;'; put '%end;'; put '%mend mp_jsonout;'; put '/**'; put '@file mm_webout.sas'; put '@brief Send data to/from SAS Stored Processes'; put '@details This macro should be added to the start of each Stored Process,'; put '**immediately** followed by a call to:'; put '%mm_webout(FETCH)'; put 'This will read all the input data and create same-named SAS datasets in the'; put 'WORK library. You can then insert your code, and send data back using the'; put 'following syntax:'; put 'data some datasets; * make some data ;'; put 'retain some columns;'; put 'run;'; put '%mm_webout(OPEN)'; put '%mm_webout(ARR,some) * Array format, fast, suitable for large tables ;'; put '%mm_webout(OBJ,datasets) * Object format, easier to work with ;'; put 'Finally, wrap everything up send some helpful system variables too'; put '%mm_webout(CLOSE)'; put '@param [in] action Either FETCH, OPEN, ARR, OBJ or CLOSE'; put '@param [in] ds The dataset to send back to the frontend'; put '@param [out] dslabel= Value to use instead of table name for sending to JSON'; put '@param [in] fmt= (N) Setting Y converts all vars to their formatted values'; put '@param [out] fref= (_webout) The fileref to which to write the JSON'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] workobs= (0) When set to a positive integer, will create a new'; put 'output object (WORK) which contains this number of observations from all'; put 'tables in the WORK library.'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to output JSON'; put '

SAS Macros

'; put '@li mp_jsonout.sas'; put '

Related Macros

'; put '@li ms_webout.sas'; put '@li mv_webout.sas'; put '@version 9.3'; put '@author Allan Bowe'; put '**/'; put '%macro mm_webout(action,ds,dslabel=,fref=_webout,fmt=N,missing=NULL'; put ',showmeta=N,maxobs=MAX,workobs=0'; put ');'; put '%global _webin_file_count _webin_fileref1 _webin_name1 _program _debug'; put 'sasjs_tables;'; put '%local i tempds jsonengine;'; put '/* see https://github.com/sasjs/core/issues/41 */'; put '%if "%upcase(&SYSENCODING)" ne "UTF-8" %then %let jsonengine=PROCJSON;'; put '%else %let jsonengine=DATASTEP;'; put '%if &action=FETCH %then %do;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'options mprint notes mprintnest;'; put '%end;'; put '%let _webin_file_count=%eval(&_webin_file_count+0);'; put '/* now read in the data */'; put '%do i=1 %to &_webin_file_count;'; put '%if &_webin_file_count=1 %then %do;'; put '%let _webin_fileref1=&_webin_fileref;'; put '%let _webin_name1=&_webin_name;'; put '%end;'; put 'data _null_;'; put 'infile &&_webin_fileref&i termstr=crlf;'; put 'input;'; put 'call symputx(''input_statement'',_infile_);'; put 'putlog "&&_webin_name&i input statement: " _infile_;'; put 'stop;'; put 'data &&_webin_name&i;'; put 'infile &&_webin_fileref&i firstobs=2 dsd termstr=crlf encoding=''utf-8'';'; put 'input &input_statement;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'if _n_<20 then putlog _infile_;'; put '%end;'; put 'run;'; put '%let sasjs_tables=&sasjs_tables &&_webin_name&i;'; put '%end;'; put '%end;'; put '%else %if &action=OPEN %then %do;'; put '/* fix encoding */'; put 'OPTIONS NOBOMFILE;'; put '/**'; put '* check xengine type to avoid the below err message:'; put '* > Function is only valid for filerefs using the CACHE access method.'; put '*/'; put 'data _null_;'; put 'set sashelp.vextfl(where=(fileref="_WEBOUT"));'; put 'if xengine=''STREAM'' then do;'; put 'rc=stpsrv_header(''Content-type'',"text/html; encoding=utf-8");'; put 'end;'; put 'run;'; put '/* setup json */'; put 'data _null_;file &fref encoding=''utf-8'';'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutBEGIN<<'';'; put '%end;'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'run;'; put '%end;'; put '%else %if &action=ARR or &action=OBJ %then %do;'; put '%mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt,jref=&fref'; put ',engine=&jsonengine,missing=&missing,showmeta=&showmeta,maxobs=&maxobs'; put ')'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put '/* To avoid issues with _webout on EBI we use a temporary file */'; put 'filename _sjsref temp lrecl=131068;'; put '%if %str(&workobs) > 0 %then %do;'; put '/* if debug mode, send back first XX records of each work table also */'; put 'data;run;%let tempds=%scan(&syslast,2,.);'; put 'ods output Members=&tempds;'; put 'proc datasets library=WORK memtype=data;'; put '%local wtcnt;%let wtcnt=0;'; put 'data _null_;'; put 'set &tempds;'; put 'if not (upcase(name) =:"DATA"); /* ignore temp datasets */'; put 'i+1;'; put 'call symputx(cats(''wt'',i),name,''l'');'; put 'call symputx(''wtcnt'',i,''l'');'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put ",""WORK"":{";'; put '%do i=1 %to &wtcnt;'; put '%let wt=&&wt&i;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'dsid=open("WORK.&wt",''is'');'; put 'nlobs=attrn(dsid,''NLOBS'');'; put 'nvars=attrn(dsid,''NVARS'');'; put 'rc=close(dsid);'; put 'if &i>1 then put '',''@;'; put 'put " ""&wt"" : {";'; put 'put ''"nlobs":'' nlobs;'; put 'put '',"nvars":'' nvars;'; put '%mp_jsonout(OBJ,&wt,jref=_sjsref,dslabel=first10rows,showmeta=Y'; put ',maxobs=&workobs'; put ')'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put '%end;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put 'run;'; put '%end;'; put '/* close off json */'; put 'data _null_;file _sjsref mod encoding=''utf-8'';'; put 'length SYSPROCESSNAME syserrortext syswarningtext autoexec $512;'; put 'put ",""_DEBUG"" : ""&_debug"" ";'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'autoexec=quote(urlencode(trim(getoption(''autoexec''))));'; put 'put '',"AUTOEXEC" : '' autoexec;'; put 'put ",""MF_GETUSER"" : ""%mf_getuser()"" ";'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'put ",""SYSENCODING"" : ""&sysencoding"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSPROCESSID"" : ""&SYSPROCESSID"" ";'; put 'put ",""SYSPROCESSMODE"" : ""&SYSPROCESSMODE"" ";'; put 'SYSPROCESSNAME=quote(urlencode(cats(SYSPROCESSNAME)));'; put 'put ",""SYSPROCESSNAME"" : " SYSPROCESSNAME;'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put '',"SYSWARNINGTEXT" : '' syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'length memsize $32;'; put 'memsize="%sysfunc(INPUTN(%sysfunc(getoption(memsize)), best.),sizekmg.)";'; put 'memsize=quote(cats(memsize));'; put 'put '',"MEMSIZE" : '' memsize;'; put 'put "}" @;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutEND<<'';'; put '%end;'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjsref lrecl=1 recfm=n;'; put 'file &fref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjsref clear;'; put '%end;'; put '%mend mm_webout;'; put '%macro webout(action,ds,dslabel=,fmt=,missing=NULL,showmeta=NO,maxobs=MAX);'; put '%mm_webout(&action,ds=&ds,dslabel=&dslabel,fmt=&fmt'; put ',missing=&missing'; put ',showmeta=&showmeta'; put ',maxobs=&maxobs'; put ') %mend;'; put '/* provide additional debug info */'; put '%global _program;'; put '%put &=syscc;'; put '%put user=%mf_getuser();'; put '%put pgm=&_program;'; put '%put timestamp=%sysfunc(datetime(),datetime19.);'; put '* Service Variables start;'; put '* Service Variables end;'; put '* SAS Macros start;'; put '%macro mf_getapploc(pgm);'; put '%if "&pgm"="" %then %do;'; put '%if %symexist(_program) %then %let pgm=&_program;'; put '%else %do;'; put '%put &sysmacroname: No value provided and no _program variable available;'; put '%return;'; put '%end;'; put '%end;'; put '%local root;'; put '/**'; put '* First check we are not in the tests/macros folder (which has no subfolders)'; put '* or specifically in the testsetup or testteardown services'; put '*/'; put '%if %index(&pgm,/tests/macros/)'; put 'or %index(&pgm,/tests/testsetup)'; put 'or %index(&pgm,/tests/testteardown)'; put '%then %do;'; put '%let root=%substr(&pgm,1,%index(&pgm,/tests)-1);'; put '&root'; put '%return;'; put '%end;'; put '/**'; put '* Next, move up two levels to avoid matches on subfolder or service name'; put '*/'; put '%let root=%substr(&pgm,1,%length(&pgm)-%length(%scan(&pgm,-1,/))-1);'; put '%let root=%substr(&root,1,%length(&root)-%length(%scan(&root,-1,/))-1);'; put '%if %index(&root,/tests/) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/tests/)-1);'; put '%end;'; put '%else %if %index(&root,/services) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/services)-1);'; put '%end;'; put '%else %if %index(&root,/jobs) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/jobs)-1);'; put '%end;'; put '%else %put &sysmacroname: Could not find an app location from &pgm;'; put '&root'; put '%mend mf_getapploc ;'; put '%macro mf_getuniquefileref(prefix=_,maxtries=1000,lrecl=32767);'; put '%local rc fname;'; put '%if &prefix=0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%end;'; put '%else %do;'; put '%local x len;'; put '%let len=%eval(8-%length(&prefix));'; put '%let x=0;'; put '%do x=0 %to &maxtries;'; put '%let fname=&prefix%substr(%sysfunc(ranuni(0)),3,&len);'; put '%if %sysfunc(fileref(&fname)) > 0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%return;'; put '%end;'; put '%end;'; put '%put unable to find available fileref after &maxtries attempts;'; put '%end;'; put '%mend mf_getuniquefileref;'; put '%macro mp_abort(mac=mp_abort.sas, type=, msg=, iftrue=%str(1=1)'; put ', errds=work.mp_abort_errds'; put ', mode=REGULAR'; put ')/*/STORE SOURCE*/;'; put '%global sysprocessmode sysprocessname sasjs_stpsrv_header_loc sasjsprocessmode;'; put '%local fref fid i;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%put NOTE: /// mp_abort macro executing //;'; put '%if %length(&mac)>0 %then %put NOTE- called by &mac;'; put '%put NOTE - &msg;'; put '%if %symexist(_SYSINCLUDEFILEDEVICE)'; put '/* abort cancel FILE does not restart outside the INCLUDE on Viya 3.5 */'; put 'and %superq(SYSPROCESSNAME) ne %str(Compute Server)'; put '%then %do;'; put '%if "*&_SYSINCLUDEFILEDEVICE*" ne "**" %then %do;'; put 'data &errds;'; put 'iftrue=''1=1'';'; put 'length mac $100 msg $5000;'; put 'mac=symget(''mac'');'; put 'msg=symget(''msg'');'; put 'run;'; put 'data _null_;'; put 'abort cancel FILE;'; put 'run;'; put '%return;'; put '%end;'; put '%end;'; put '/* Web App Context */'; put '%if %symexist(_PROGRAM)'; put 'or %superq(SYSPROCESSNAME) = %str(Compute Server)'; put 'or &mode=INCLUDE'; put '%then %do;'; put 'options obs=max replace mprint;'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5"'; put '%then %do;'; put 'options nosyntaxcheck;'; put '%end;'; put '%if &mode=INCLUDE %then %do;'; put '%if %sysfunc(exist(&errds))=1 %then %do;'; put 'data _null_;'; put 'set &errds;'; put 'call symputx(''iftrue'',iftrue,''l'');'; put 'call symputx(''mac'',mac,''l'');'; put 'call symputx(''msg'',msg,''l'');'; put 'putlog (_all_)(=);'; put 'run;'; put '%if (&iftrue)=0 %then %return;'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: No include errors found;'; put '%return;'; put '%end;'; put '%end;'; put '/* extract log errs / warns, if exist */'; put '%local logloc logline;'; put '%global logmsg; /* capture global messages */'; put '%if %symexist(SYSPRINTTOLOG) %then %let logloc=&SYSPRINTTOLOG;'; put '%else %let logloc=%qsysfunc(getoption(LOG));'; put 'proc printto log=log;run;'; put '%let logline=0;'; put '%if %length(&logloc)>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input; putlog _infile_;'; put 'i=1;'; put 'retain logonce 0;'; put 'if ('; put '_infile_=:"%str(WARN)ING" or _infile_=:"%str(ERR)OR"'; put ') and logonce=0 then'; put 'do;'; put 'call symputx(''logline'',_n_);'; put 'logonce+1;'; put 'end;'; put 'run;'; put '/* capture log including lines BEFORE the err */'; put '%if &logline>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input;'; put 'i=1;'; put 'stoploop=0;'; put 'if _n_ ge &logline-15 and stoploop=0 then do until (i>22);'; put 'call symputx(''logmsg'',catx(''\n'',symget(''logmsg''),_infile_));'; put 'input;'; put 'i+1;'; put 'stoploop=1;'; put 'end;'; put 'if stoploop=1 then stop;'; put 'run;'; put '%end;'; put '%end;'; put '%if %symexist(SYS_JES_JOB_URI) %then %do;'; put '/* setup webout for Viya */'; put 'options nobomfile;'; put '%if "X&SYS_JES_JOB_URI.X"="XX" %then %do;'; put 'filename _webout temp lrecl=999999 mod;'; put '%end;'; put '%else %do;'; put 'filename _webout filesrvc parenturi="&SYS_JES_JOB_URI"'; put 'name="_webout.json" lrecl=999999 mod;'; put '%end;'; put '%end;'; put '%else %if %sysfunc(filename(fref,&sasjs_stpsrv_header_loc))=0 %then %do;'; put 'options nobomfile;'; put '/* set up http header for SASjs Server */'; put '%let fid=%sysfunc(fopen(&fref,A));'; put '%if &fid=0 %then %do;'; put '%put %str(ERR)OR: %sysfunc(sysmsg());'; put '%return;'; put '%end;'; put '%let rc=%sysfunc(fput(&fid,%str(Content-Type: application/json)));'; put '%let rc=%sysfunc(fwrite(&fid));'; put '%let rc=%sysfunc(fclose(&fid));'; put '%let rc=%sysfunc(filename(&fref));'; put '%end;'; put '/* send response in SASjs JSON format */'; put 'data _null_;'; put 'file _webout mod lrecl=32000 encoding=''utf-8'';'; put 'length msg syswarningtext syserrortext $32767 mode $10 ;'; put 'sasdatetime=datetime();'; put 'msg=symget(''msg'');'; put '%if &logline>0 %then %do;'; put 'msg=cats(msg,''\n\nLog Extract:\n'',symget(''logmsg''));'; put '%end;'; put '/* escape the escapes */'; put 'msg=tranwrd(msg,''\'',''\\'');'; put '/* escape the quotes */'; put 'msg=tranwrd(msg,''"'',''\"'');'; put '/* ditch the CRLFs as chrome complains */'; put 'msg=compress(msg,,''kw'');'; put '/* quote without quoting the quotes (which are escaped instead) */'; put 'msg=cats(''"'',msg,''"'');'; put 'if symexist(''_debug'') then debug=quote(trim(symget(''_debug'')));'; put 'else debug=''""'';'; put 'if symget(''sasjsprocessmode'')=''Stored Program'' then mode=''SASJS'';'; put 'if mode ne ''SASJS'' then put ''>>weboutBEGIN<<'';'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'put '',"sasjsAbort" : [{'';'; put 'put '' "MSG":'' msg ;'; put 'put '' ,"MAC": "'' "&mac" ''"}]'';'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'put '',"_DEBUG":'' debug ;'; put 'if symexist(''_metauser'') then do;'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put 'end;'; put 'if symexist(''SYS_JES_JOB_URI'') then do;'; put 'SYS_JES_JOB_URI=quote(trim(symget(''SYS_JES_JOB_URI'')));'; put 'put '',"SYS_JES_JOB_URI": '' SYS_JES_JOB_URI;'; put 'end;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put ",""SYSWARNINGTEXT"" : " syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'put "}" ;'; put 'if mode ne ''SASJS'' then put ''>>weboutEND<<'';'; put 'run;'; put '%put _all_;'; put '%if "&sysprocessmode " = "SAS Stored Process Server " %then %do;'; put 'data _null_;'; put 'putlog ''stpsrvset program err and syscc'';'; put 'rc=stpsrvset(''program error'', 0);'; put 'call symputx("syscc",0,"g");'; put 'run;'; put '%if &sysscp=WIN'; put 'and 1=0 /* deprecating this logic until we figure out a consistent abort */'; put 'and "%substr(%str(&sysvlong ),1,8)"="9.04.01M"'; put 'and "%substr(%str(&sysvlong ),9,1)">"5" %then %do;'; put '/* skip approach (below) does not work in windows m6+ envs */'; put 'endsas;'; put '%end;'; put '%else %do;'; put '/**'; put '* endsas kills 9.4m3 deployments by orphaning multibridges.'; put '* Abort variants are ungraceful (non zero return code)'; put '* This approach lets SAS run silently until the end :-)'; put '* Caution - fails when called within a %include within a macro'; put '* Use mp_include() to handle this.'; put '*/'; put 'filename skip temp;'; put 'data _null_;'; put 'file skip;'; put 'put ''%macro skip();'';'; put 'comment ''%mend skip; -> fix lint '';'; put 'put ''%macro skippy();'';'; put 'comment ''%mend skippy; -> fix lint '';'; put 'run;'; put '%inc skip;'; put '%end;'; put '%end;'; put '%else %if "&sysprocessmode " = "SAS Compute Server " %then %do;'; put '/* endsas kills the session making it harder to fetch results */'; put 'data _null_;'; put 'syswarningtext=symget(''syswarningtext'');'; put 'syserrortext=symget(''syserrortext'');'; put 'abort_msg=symget(''msg'');'; put 'syscc=symget(''syscc'');'; put 'sysuserid=symget(''sysuserid'');'; put 'iftrue=symget(''iftrue'');'; put 'put (_all_)(/=);'; put 'call symputx(''syscc'',0);'; put 'abort cancel nolist;'; put 'run;'; put '%end;'; put '%else %do;'; put '%abort cancel;'; put '%end;'; put '%end;'; put '%else %do;'; put '%put _all_;'; put '%abort cancel;'; put '%end;'; put '%mend mp_abort;'; put '/** @endcond */'; put '%macro mm_getstpcode('; put 'tree=/User Folders/sasdemo/somestp'; put ',name='; put ',outloc=0'; put ',outref=0'; put ',mDebug=1'; put ',showlog=NO'; put ');'; put '%local mD;'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing &sysmacroname..sas;'; put '%&mD.put _local_;'; put '%if %length(&name)>0 %then %let name=/&name;'; put '/* first, check if STP exists */'; put '%local tsuri;'; put '%let tsuri=stopifempty ;'; put 'data _null_;'; put 'format type uri tsuri value $200.;'; put 'call missing (of _all_);'; put 'path="&tree&name(StoredProcess)";'; put '/* first, find the STP ID */'; put 'if metadata_pathobj("",path,"StoredProcess",type,uri)>0 then do;'; put '/* get sourcecode */'; put 'cnt=1;'; put 'do while (metadata_getnasn(uri,"Notes",cnt,tsuri)>0);'; put 'rc=metadata_getattr(tsuri,"Name",value);'; put '&mD.put tsuri= value=;'; put 'if value="SourceCode" then do;'; put '/* found it! */'; put 'rc=metadata_getattr(tsuri,"Id",value);'; put 'call symputx(''tsuri'',value,''l'');'; put 'stop;'; put 'end;'; put 'cnt+1;'; put 'end;'; put 'end;'; put 'else put (_all_)(=);'; put 'run;'; put '%mp_abort(iftrue= (&tsuri=stopifempty)'; put ',mac=mm_getstpcode'; put ',msg=%str(&tree&name.(StoredProcess) not found!)'; put ')'; put '/**'; put '* Now we can extract the textstore'; put '*/'; put 'filename __getdoc temp lrecl=10000000;'; put 'proc metadata'; put 'in="$METAREPOSITORY'; put ''; put 'SAS1"'; put 'out=__getdoc ;'; put 'run;'; put '/* find the beginning of the text */'; put '%local start;'; put 'data _null_;'; put 'infile __getdoc lrecl=10000;'; put 'input;'; put 'start=index(_infile_,''StoredText="'');'; put 'if start then do;'; put 'call symputx("start",start+11);'; put '*putlog ''"'' _infile_ ''"'';'; put 'end;'; put 'stop;'; put '%local outeng;'; put '%if "&outloc"="0" %then %let outeng=TEMP;'; put '%else %let outeng="&outloc";'; put '%local fref;'; put '%if &outref=0 %then %let fref=%mf_getuniquefileref();'; put '%else %let fref=&outref;'; put '/* read the content, byte by byte, resolving escaped chars */'; put 'filename &fref &outeng lrecl=100000;'; put 'data _null_;'; put 'length filein 8 fileid 8;'; put 'filein = fopen("__getdoc","I",1,"B");'; put 'fileid = fopen("&fref","O",1,"B");'; put 'rec = "20"x;'; put 'length entity $6;'; put 'do while(fread(filein)=0);'; put 'x+1;'; put 'if x>&start then do;'; put 'rc = fget(filein,rec,1);'; put 'if rec=''"'' then leave;'; put 'else if rec="&" then do;'; put 'entity=rec;'; put 'do until (rec=";");'; put 'if fread(filein) ne 0 then goto getout;'; put 'rc = fget(filein,rec,1);'; put 'entity=cats(entity,rec);'; put 'end;'; put 'select (entity);'; put 'when (''&'' ) rec=''&'' ;'; put 'when (''<'' ) rec=''<'' ;'; put 'when (''>'' ) rec=''>'' ;'; put 'when (''''') rec="''" ;'; put 'when (''"'') rec=''"'' ;'; put 'when ('' '') rec=''0A''x;'; put 'when ('' '') rec=''0D''x;'; put 'when (''$'' ) rec=''$'' ;'; put 'when ('' '') rec=''09''x;'; put 'otherwise putlog "%str(WARN)ING: missing value for " entity=;'; put 'end;'; put 'rc =fput(fileid, substr(rec,1,1));'; put 'rc =fwrite(fileid);'; put 'end;'; put 'else do;'; put 'rc =fput(fileid,rec);'; put 'rc =fwrite(fileid);'; put 'end;'; put 'end;'; put 'end;'; put 'getout:'; put 'rc=fclose(filein);'; put 'rc=fclose(fileid);'; put 'run;'; put '%if &showlog=YES %then %do;'; put 'data _null_;'; put 'infile &fref lrecl=32767 end=last;'; put 'input;'; put 'if _n_=1 then putlog ''>>stpcodeBEGIN<<'';'; put 'putlog _infile_;'; put 'if last then putlog ''>>stpcodeEND<<'';'; put 'run;'; put '%end;'; put 'filename __getdoc clear;'; put '%if &outref=0 %then %do;'; put 'filename &fref clear;'; put '%end;'; put '%mend mm_getstpcode;'; put '%macro dc_getsettings();'; put '%global _program;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(syscc=&syscc on entry (&syswarningtext &syserrortext))'; put ')'; put '%if %length(&_PROGRAM)>1 %then %let root=&_program;'; put '%else %do;'; put '%global _metauser;'; put '%let _metauser=&sysuserid;'; put '/* to mimic a "real" _program we need to give a dummy role and stp name */'; put '%let root=/dummyRole/dummyName;'; put '%end;'; put '/* the DC precode is stored in the Admin folder in the root of'; put 'the project. Lets find that root. */'; put '%put &=root;'; put '%let root=%mf_getapploc();'; put '%put &=root;'; put '/* Now we know the root location we can retrieve the params */'; put '%let temploc=%sysfunc(pathname(work))/settings.txt;'; put '%mm_getstpcode(tree=&root/services/public'; put ',name=Data_Controller_Settings'; put ',outloc=&temploc'; put ')'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Unable to run getstpcode)'; put ')'; put 'filename _getsets "&temploc" lrecl=2000;'; put '/*'; put 'Do not use mp_include here - this puts a copy in every service, which creates'; put 'compilation problems when calling services from mp_include'; put '*/'; put '%inc _getsets/source2;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Problem running &sysmacroname (&syswarningtext &syserrortext))'; put ')'; put '%mend dc_getsettings;'; put '%macro mf_fmtdttm('; put ')/*/STORE SOURCE*/;'; put '%if "&sysver"="9.2" or "&sysver"="9.3"'; put 'or ("&sysver"="9.4" and "%substr(&SYSVLONG,9,1)" le "3")'; put 'or "%substr(&sysver,1,1)"="4"'; put 'or "%substr(&sysver,1,1)"="5"'; put '%then %do;DATETIME19.3%end;'; put '%else %do;E8601DT26.6%end;'; put '%mend mf_fmtdttm;'; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '%macro mp_init(prefix=SASJS'; put ')/*/STORE SOURCE*/;'; put '%if %symexist(SASJS_PREFIX) %then %return; /* only run once */'; put '%global'; put 'SASJS_PREFIX /* the ONLY hard-coded global macro variable in SASjs */'; put '&prefix._FUNCTIONS /* used in mcf_init() to track core function compilation */'; put '&prefix._INIT_NUM /* initialisation time as numeric */'; put '&prefix._INIT_DTTM /* initialisation time in E8601DT26.6 format */'; put '&prefix.WORK /* avoid typing %sysfunc(pathname(work)) every time */'; put ';'; put '%let sasjs_prefix=&prefix;'; put 'data _null_;'; put 'dttm=datetime();'; put 'call symputx("&sasjs_prefix._init_num",dttm,''g'');'; put 'call symputx("&sasjs_prefix._init_dttm",put(dttm,E8601DT26.6),''g'');'; put 'call symputx("&sasjs_prefix.work",pathname(''WORK''),''g'');'; put 'run;'; put 'options'; put 'compress=CHAR /* default is none so ensure we have something! */'; put 'datastmtchk=ALLKEYWORDS /* protection from overwriting input datasets */'; put 'errorcheck=STRICT /* catch errs in libname/filename statements */'; put 'fmterr /* ensure err when a format cannot be found */'; put 'mergenoby=%str(ERR)OR /* throw err when a merge has no BY variables */'; put 'missing=. /* changing this can cause hard to detect errs */'; put 'noquotelenmax /* avoid warnings for long strings */'; put 'noreplace /* avoid overwriting permanent datasets */'; put 'ps=max /* reduce log size slightly */'; put 'ls=max /* reduce log even more and avoid word truncation */'; put 'validmemname=COMPATIBLE /* avoid special characters etc in table names */'; put 'validvarname=V7 /* avoid special characters etc in variable names */'; put 'varinitchk=%str(ERR)OR /* avoid data mistakes from variable name typos */'; put 'varlenchk=%str(ERR)OR /* fail hard if truncation (data loss) can result */'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5" %then %do;'; put 'noautocorrect /* disallow misspelled procedure names */'; put 'dsoptions=note2err /* undocumented - convert bad NOTEs to ERRs */'; put '%end;'; put ';'; put '%mend mp_init;'; put '%macro mpeinit(fetch=YES);'; put '%global mpeinit'; put 'mpeadmins /* group with unrestricted Meditor access */'; put 'mpelocapprovals /* location for landing and staging files */'; put 'mpelib /* location of configuration tables for DC */'; put 'dc_repo_users /* location of user / group metadata */'; put 'dc_licence_key /* extracted in dc_getsettings */'; put 'dc_activation_key /* extracted in dc_getsettings */'; put 'dc_locale /* extracted in dc_getsettings */'; put 'dc_dttmtfmt /* can be overridden in dc_getsettings */'; put '_debug'; put ';'; put '%if &mpeinit=1 %then %return;'; put '%else %let mpeinit=1;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(Problem on service startup (&syswarningtext &syserrortext))'; put ')'; put '%mp_init()'; put '%if &fetch=YES %then %do;'; put '%webout(FETCH)'; put '%end;'; put '%global _CLIENTNAME;'; put '%mp_abort(iftrue= (&_CLIENTNAME=SAS Enterprise Guide)'; put ',mac=&_program..sas'; put ',msg=%str(Data Controller is a web app and should not be executed from EG)'; put ')'; put 'options urlencoding=utf8 nobomfile lrecl=32767;'; put '%let perf=%sysfunc(datetime());'; put '%put perfdiff: 0;'; put '%let dc_locale=SYSTEM; /* default if not set */'; put '/**'; put '* E8601DT26.6 has widest database support - but not all SAS flavours can'; put '* handle it. Override in the settings STP if needed.'; put '*/'; put 'data _null_;'; put 'dc_dttmtfmt=''"%sysfunc(datetime(),''!!"%mf_fmtdttm()"!!'')"dt'';'; put 'call symputx(''dc_dttmtfmt'',dc_dttmtfmt);'; put 'put dc_dttmtfmt=;'; put 'run;'; put '%put &=dc_dttmtfmt;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc prior to dc_getsettings)'; put ')'; put '%dc_getsettings()'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc after dc_getsettings)'; put ')'; put 'data _null_;'; put 'set &DC_LIBREF..mpe_config(where=('; put 'var_scope="DC"'; put 'and &dc_dttmtfmt lt tx_to'; put 'and var_active=1'; put '));'; put 'call symputx(var_name,var_value,''G'');'; put 'putlog var_name "=" var_value;'; put 'run;'; put '%let mpelib=&dc_libref;'; put '%let mpeadmins=&dc_admin_group;'; put '%let mpelocapprovals=&dc_staging_area;'; put '%let dc_repo_users=&dc_repo_users;'; put '%if &dc_locale ne SYSTEM %then %do;'; put 'options locale=&dc_locale;'; put '%end;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(Problem during compilation or with STP precode (&syswarningtext))'; put ')'; put '%mend mpeinit;'; put '%macro mf_mval(var);'; put '%if %symexist(&var) %then %do;'; put '%superq(&var)'; put '%end;'; put '%mend mf_mval;'; put '%macro mf_trimstr(basestr,trimstr);'; put '%local baselen trimlen trimval;'; put '/* return if basestr is shorter than trimstr (or 0) */'; put '%let baselen=%length(%superq(basestr));'; put '%let trimlen=%length(%superq(trimstr));'; put '%if &baselen < &trimlen or &baselen=0 %then %return;'; put '/* obtain the characters from the end of basestr */'; put '%let trimval=%qsubstr(%superq(basestr)'; put ',%length(%superq(basestr))-&trimlen+1'; put ',&trimlen);'; put '/* compare and if matching, chop it off! */'; put '%if %superq(basestr)=%superq(trimstr) %then %do;'; put '%return;'; put '%end;'; put '%else %if %superq(trimval)=%superq(trimstr) %then %do;'; put '%qsubstr(%superq(basestr),1,%length(%superq(basestr))-&trimlen)'; put '%end;'; put '%else %do;'; put '&basestr'; put '%end;'; put '%mend mf_trimstr;'; put '%macro mf_getplatform(switch'; put ')/*/STORE SOURCE*/;'; put '%local a b c;'; put '%if &switch.NONE=NONE %then %do;'; put '%if %symexist(sasjsprocessmode) %then %do;'; put '%if &sasjsprocessmode=Stored Program %then %do;'; put 'SASJS'; put '%return;'; put '%end;'; put '%end;'; put '%if %symexist(sysprocessmode) %then %do;'; put '%if "&sysprocessmode"="SAS Object Server"'; put 'or "&sysprocessmode"= "SAS Compute Server" %then %do;'; put 'SASVIYA'; put '%end;'; put '%else %if "&sysprocessmode"="SAS Stored Process Server"'; put 'or "&sysprocessmode"="SAS Workspace Server"'; put '%then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if %symexist(_metaport) or %symexist(_metauser) %then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if &switch=SASSTUDIO %then %do;'; put '/* return the version of SAS Studio else 0 */'; put '%if %mf_mval(_CLIENTAPP)=%str(SAS Studio) %then %do;'; put '%let a=%mf_mval(_CLIENTVERSION);'; put '%let b=%scan(&a,1,.);'; put '%if %eval(&b >2) %then %do;'; put '&b'; put '%end;'; put '%else 0;'; put '%end;'; put '%else 0;'; put '%end;'; put '%else %if &switch=VIYARESTAPI %then %do;'; put '%mf_trimstr(%sysfunc(getoption(servicesbaseurl)),/)'; put '%end;'; put '%mend mf_getplatform;'; put '%macro mpeterm();'; put '%local oldloc;'; put 'data _null_;'; put 'if symexist(''SYSPRINTTOLOG'') then oldloc=symget(''SYSPRINTTOLOG'');'; put 'else oldloc=getoption(''LOG'');'; put 'if subpad(oldloc,1,1) not in (''"'',"''",'' '') then oldloc=quote(cats(oldloc));'; put 'call symputx(''oldloc'',oldloc,''l'');'; put 'run;'; put '%if %length(&oldloc)>0 %then %do;'; put 'proc printto log=log;'; put 'run;'; put 'data _null_;'; put 'infile &oldloc;'; put 'input; putlog _infile_;'; put 'run;'; put '%end;'; put '%if %sysfunc(exist(&dc_libref..mpe_requests)) and %mf_getplatform() ne SASVIYA'; put '%then %do;'; put 'data ;'; put 'if 0 then set &dc_libref..mpe_requests;'; put 'request_dttm=%sysfunc(datetime());'; put 'request_user="%mf_getuser()";'; put 'request_service="%scan(&_program,-2,/)/%scan(&_program,-1,/)";'; put 'request_params='''';'; put 'output;stop;'; put 'proc append base=&dc_libref..mpe_requests data=&syslast force nowarn;'; put 'run;'; put '%end;'; put '%mend mpeterm;'; put '%macro mm_getGroups('; put 'user='; put ',outds=work.mm_getGroups'; put ',repo=foundation'; put ',mDebug=0'; put ')/*/STORE SOURCE*/;'; put '%local mD oldrepo;'; put '%let oldrepo=%sysfunc(getoption(metarepository));'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing mm_getGroups.sas;'; put '%&mD.put _local_;'; put '/* on some sites, user / group info is in a different metadata repo to the'; put 'default */'; put '%if &oldrepo ne &repo %then %do;'; put 'options metarepository=&repo;'; put '%end;'; put '%if %length(&user)=0 %then %do;'; put 'data &outds (keep=groupuri groupname groupdesc);'; put 'length groupuri groupname groupdesc group_or_role $256;'; put 'call missing(of _all_);'; put 'i+1;'; put 'do while'; put '(metadata_getnobj("omsobj:IdentityGroup?@Id contains ''.''",i,groupuri)>0);'; put 'rc=metadata_getattr(groupuri, "Name", groupname);'; put 'rc=metadata_getattr(groupuri, "Desc", groupdesc);'; put 'rc=metadata_getattr(groupuri,"PublicType",group_or_role);'; put 'if Group_or_Role = ''UserGroup'' then output;'; put 'i+1;'; put 'end;'; put 'run;'; put '%end;'; put '%else %do;'; put 'data &outds (keep=groupuri groupname groupdesc);'; put 'length uri groupuri groupname groupdesc group_or_role $256;'; put 'call missing(of _all_);'; put 'rc=metadata_getnobj("omsobj:Person?@Name=''&user''",1,uri);'; put 'if rc<=0 then do;'; put 'putlog "%str(WARN)ING: rc=" rc "&user not found "'; put '", or there was an issue reading the repository.";'; put 'stop;'; put 'end;'; put 'a=1;'; put 'grpassn=metadata_getnasn(uri,"IdentityGroups",a,groupuri);'; put 'if grpassn in (-3,-4) then do;'; put 'putlog "%str(WARN)ING: No metadata groups found for &user";'; put 'output;'; put 'end;'; put 'else do while (grpassn > 0);'; put 'rc=metadata_getattr(groupuri, "Name", groupname);'; put 'rc=metadata_getattr(groupuri, "Desc", groupdesc);'; put 'a+1;'; put 'rc=metadata_getattr(groupuri,"PublicType",group_or_role);'; put 'if Group_or_Role = ''UserGroup'' then output;'; put 'grpassn=metadata_getnasn(uri,"IdentityGroups",a,groupuri);'; put 'end;'; put 'run;'; put '%end;'; put '%if &oldrepo ne &repo %then %do;'; put 'options metarepository=&oldrepo;'; put '%end;'; put '%mend mm_getGroups;'; put '%macro dc_getusergroups(user=,outds=mm_getgroups);'; put '%global dc_repo_users;'; put '%if &dc_repo_users= %then %let dc_repo_users=foundation;'; put '%mm_getgroups(user=&user,outds=&outds,repo=&dc_repo_users)'; put '%mend dc_getusergroups;'; put '%macro mpe_getgroups(user=,outds=);'; put '%if not %symexist(dc_repo_users) %then %let dc_repo_users=foundation;'; put '%dc_getusergroups(user=&user,outds=&outds)'; put 'data;'; put 'length groupname groupdesc $256;'; put 'set &dc_libref..mpe_groups;'; put 'where &dc_dttmtfmt. lt tx_to;'; put 'where also upcase(user_name)="%upcase(&user)";'; put 'groupname=group_name;'; put 'groupdesc=group_desc;'; put 'keep groupname groupdesc;'; put 'run;'; put 'data &outds;'; put 'set &syslast &outds(keep=groupname groupdesc);'; put 'run;'; put '%mend mpe_getgroups;'; put '* SAS Macros end;'; put '* SAS Includes start;'; put '* SAS Includes end;'; put '* Binary Files start;'; put '* Binary Files end;'; put '* ServiceInit start;'; put 'options noquotelenmax ps=max;'; put '/* create dummy macros to prevent stpbegin / stpend from corrupting sessions */'; put '%macro stpbegin();'; put '%put NOTE: the STPBEGIN macro should not be used for web apps!;'; put '%mend stpbegin;'; put '%macro stpend();'; put '%put NOTE: the STPEND macro should not be used for web apps!;'; put '%mend stpend;'; put '* ServiceInit end;'; put '* Service start;'; put '/**'; put '@file getapprovals.sas'; put '@brief Returns a list of staged data items that need to be approved'; put '@details'; put '

SAS Macros

'; put '@li mpe_getgroups.sas'; put '@li mp_abort.sas'; put '@li mf_getuser.sas'; put '@version 9.2'; put '@author 4GL Apps Ltd'; put '@copyright 4GL Apps Ltd. This code may only be used within Data Controller'; put 'and may not be re-distributed or re-sold without the express permission of'; put '4GL Apps Ltd.'; put '**/'; put '%mpeinit()'; put '/* determine users group membership */'; put '%let user=%mf_getuser();'; put '%mpe_getgroups(user=&user,outds=work.groups)'; put 'PROC FORMAT;'; put 'picture yymmddhhmmss other=''%0Y-%0m-%0d %0H:%0M:%0S'' (datatype=datetime);'; put 'RUN;'; put 'proc sql noprint;'; put 'create table out1 (rename=(SUBMITTED_ON_DTTM1=SUBMITTED_ON_DTTM)) as'; put 'select table_id'; put ',submit_status_cd as REVIEW_STATUS_ID'; put ',SUBMITTED_BY_NM'; put ',cats(base_lib,''.'',base_ds) as base_table'; put ',put(submitted_on_dttm,yymmddhhmmss.) as SUBMITTED_ON_DTTM1'; put ',submitted_on_dttm as SUBMITTED_ON_DTTM2'; put ',submitted_reason_txt'; put ',num_of_approvals_required'; put ',num_of_approvals_remaining'; put ',base_lib as libref'; put ',base_ds as dsn'; put 'from &mpelib..mpe_submit (where=(submit_status_cd=''SUBMITTED''))'; put '/* filter out any submits for which approval is already made */'; put 'where table_id not in ('; put 'select table_id from &mpelib..mpe_review where submitted_by_nm="&user"'; put ');'; put '%macro getapprovals();'; put '%local admin_check;'; put 'select count(*) into: admin_check'; put 'from groups'; put 'where groupname="&mpeadmins"'; put 'or groupname in ('; put 'select sas_group from &mpelib..mpe_security'; put 'where libref=''*ALL*'''; put 'and &dc_dttmtfmt. lt tx_to'; put 'and access_level in (''APPROVE'')'; put ');'; put '%if &admin_check >0 %then %do;'; put 'create table fromSAS as'; put 'select distinct * from out1'; put 'order by SUBMITTED_ON_DTTM2 desc;'; put '%end;'; put '%else %do;'; put 'create table fromSAS as'; put 'select distinct a.*'; put 'from out1 a'; put 'inner join &mpelib..mpe_security b'; put 'on a.libref=b.libref'; put 'and (a.dsn=b.dsn or b.dsn=''*ALL*'')'; put 'and &dc_dttmtfmt. lt b.tx_to'; put 'and b.ACCESS_LEVEL =''APPROVE'''; put 'and b.SAS_GROUP in (select groupname from work.groups)'; put 'order by SUBMITTED_ON_DTTM2 desc;'; put '%end;'; put '%mend getapprovals;'; put '%getapprovals()'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(syscc=&syscc)'; put ')'; put '%webout(OPEN)'; put '%webout(OBJ,fromSAS)'; put '%webout(CLOSE)'; put '%mpeterm()'; put '* Service end;'; run; %mm_createwebservice(path=&appLoc/&path, name=&service, code=sascode, server=&serverName, replace=yes) filename sascode clear; %let service=gethistory; filename sascode temp lrecl=32767; data _null_; file sascode; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '/**'; put '@file mp_jsonout.sas'; put '@brief Writes JSON in SASjs format to a fileref'; put '@details This macro can be used to OPEN a JSON stream and send one or more'; put 'tables as arrays of rows, where each row can be an object or a nested array.'; put 'There are two engines available - DATASTEP or PROCJSON.'; put 'PROC JSON is fast but will produce errs like the ones below if'; put 'special chars are encountered.'; put '> (ERR)OR: Some code points did not transcode.'; put '> An object or array close is not valid at this point in the JSON text.'; put '> Date value out of range'; put 'If this happens, try running with ENGINE=DATASTEP.'; put 'The DATASTEP engine is used to handle special SAS missing numerics, and'; put 'can also convert entire datasets to formatted values. Output JSON is always'; put 'in UTF-8.'; put 'Usage:'; put 'filename tmp temp;'; put 'data class; set sashelp.class;run;'; put '%mp_jsonout(OPEN,jref=tmp)'; put '%mp_jsonout(OBJ,class,jref=tmp)'; put '%mp_jsonout(OBJ,class,dslabel=class2,jref=tmp,showmeta=Y)'; put '%mp_jsonout(CLOSE,jref=tmp)'; put 'data _null_;'; put 'infile tmp;'; put 'input;putlog _infile_;'; put 'run;'; put 'If you are building web apps with SAS then you are strongly encouraged to use'; put 'the mX_createwebservice macros in combination with the'; put '[sasjs adapter](https://github.com/sasjs/adapter).'; put 'For more information see https://sasjs.io'; put '@param [in] action Valid values:'; put '@li OPEN - opens the JSON'; put '@li OBJ - sends a table with each row as an object'; put '@li ARR - sends a table with each row in an array'; put '@li CLOSE - closes the JSON'; put '@param [in] ds The dataset to send. Must be a work table.'; put '@param [out] jref= (_webout) The fileref to which to send the JSON'; put '@param [out] dslabel= The name to give the table in the exported JSON'; put '@param [in] fmt= (Y) Whether to keep (Y) or strip (N) formats from the table'; put '@param [in] engine= (DATASTEP) Which engine to use to send the JSON. Options:'; put '@li PROCJSON (default)'; put '@li DATASTEP (more reliable when data has non standard characters)'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to JSON'; put '

Related Files

'; put '@li mp_ds2fmtds.sas'; put '@version 9.2'; put '@author Allan Bowe'; put '@source https://github.com/sasjs/core'; put '**/'; put '%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y'; put ',engine=DATASTEP'; put ',missing=NULL'; put ',showmeta=N'; put ',maxobs=MAX'; put ')/*/STORE SOURCE*/;'; put '%local tempds colinfo fmtds i numcols numobs stmt_obs lastobs optval'; put 'tmpds1 tmpds2 tmpds3 tmpds4;'; put '%let numcols=0;'; put '%if &maxobs ne MAX %then %let stmt_obs=%str(if _n_>&maxobs then stop;);'; put '%if &action=OPEN %then %do;'; put 'options nobomfile;'; put 'data _null_;file &jref encoding=''utf-8'' lrecl=200;'; put 'put ''{"PROCESSED_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''"'';'; put 'run;'; put '%end;'; put '%else %if (&action=ARR or &action=OBJ) %then %do;'; put '/* force variable names to always be uppercase in the JSON */'; put 'options validvarname=upcase;'; put '/* To avoid issues with _webout on EBI - such as encoding diffs and truncation'; put '(https://support.sas.com/kb/49/325.html) we use temporary files */'; put 'filename _sjs1 temp lrecl=200 ;'; put 'data _null_; file _sjs1 encoding=''utf-8'';'; put 'put ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":";'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjs1 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs1 clear;'; put '/* grab col defs */'; put 'proc contents noprint data=&ds'; put 'out=_data_(keep=name type length format formatl formatd varnum label);'; put 'run;'; put '%let colinfo=%scan(&syslast,2,.);'; put 'proc sort data=&colinfo;'; put 'by varnum;'; put 'run;'; put '/* move meta to mac vars */'; put 'data &colinfo;'; put 'if _n_=1 then call symputx(''numcols'',nobs,''l'');'; put 'set &colinfo end=last nobs=nobs;'; put 'name=upcase(name);'; put '/* fix formats */'; put 'if type=2 or type=6 then do;'; put 'typelong=''char'';'; put 'length fmt $49.;'; put 'if format='''' then fmt=cats(''$'',length,''.'');'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else fmt=cats(format,formatl,''.'');'; put 'end;'; put 'else do;'; put 'typelong=''num'';'; put 'if format='''' then fmt=''best.'';'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else if formatd=0 then fmt=cats(format,formatl,''.'');'; put 'else fmt=cats(format,formatl,''.'',formatd);'; put 'end;'; put '/* 32 char unique name */'; put 'newname=''sasjs''!!substr(cats(put(md5(name),$hex32.)),1,27);'; put 'call symputx(cats(''name'',_n_),name,''l'');'; put 'call symputx(cats(''newname'',_n_),newname,''l'');'; put 'call symputx(cats(''length'',_n_),length,''l'');'; put 'call symputx(cats(''fmt'',_n_),fmt,''l'');'; put 'call symputx(cats(''type'',_n_),type,''l'');'; put 'call symputx(cats(''typelong'',_n_),typelong,''l'');'; put 'call symputx(cats(''label'',_n_),coalescec(label,name),''l'');'; put '/* overwritten when fmt=Y and a custom format exists in catalog */'; put 'if typelong=''num'' then call symputx(cats(''fmtlen'',_n_),200,''l'');'; put 'else call symputx(cats(''fmtlen'',_n_),min(32767,ceil((length+10)*1.5)),''l'');'; put 'run;'; put '%let tempds=%substr(_%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql;'; put 'select count(*) into: lastobs from &ds;'; put '%if &maxobs ne MAX %then %let lastobs=%sysfunc(min(&lastobs,&maxobs));'; put '%if &engine=PROCJSON %then %do;'; put '%if &missing=STRING %then %do;'; put '%put &sysmacroname: Special Missings not supported in proc json.;'; put '%put &sysmacroname: Switching to DATASTEP engine;'; put '%goto datastep;'; put '%end;'; put 'data &tempds;'; put 'set &ds;'; put '&stmt_obs;'; put '%if &fmt=N %then format _numeric_ best32.;;'; put '/* PRETTY is necessary to avoid line truncation in large files */'; put 'filename _sjs2 temp lrecl=131068 encoding=''utf-8'';'; put 'proc json out=_sjs2 pretty'; put '%if &action=ARR %then nokeys ;'; put ';export &tempds / nosastags fmtnumeric;'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs2 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs2 clear;'; put '%end;'; put '%else %if &engine=DATASTEP %then %do;'; put '%datastep:'; put '%if %sysfunc(exist(&ds)) ne 1 & %sysfunc(exist(&ds,VIEW)) ne 1'; put '%then %do;'; put '%put &sysmacroname: &ds NOT FOUND!!!;'; put '%return;'; put '%end;'; put '%if &fmt=Y %then %do;'; put '/**'; put '* Extract format definitions'; put '* First, by getting library locations from dictionary.formats'; put '* Then, by exporting the width using proc format'; put '* Cannot use maxw from sashelp.vformat as not always populated'; put '* Cannot use fmtinfo() as not supported in all flavours'; put '*/'; put '%let tmpds1=%substr(fmtsum%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds2=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds3=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds4=%substr(col%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql noprint;'; put 'create table &tmpds1 as'; put 'select cats(libname,''.'',memname) as FMTCAT,'; put 'FMTNAME'; put 'from dictionary.formats'; put 'where fmttype=''F'' and libname is not null'; put 'and fmtname in (select format from &colinfo where format is not null)'; put 'order by 1;'; put 'create table &tmpds2('; put 'FMTNAME char(32),'; put 'LENGTH num'; put ');'; put '%local catlist cat fmtlist i;'; put 'select distinct fmtcat into: catlist separated by '' '' from &tmpds1;'; put '%do i=1 %to %sysfunc(countw(&catlist,%str( )));'; put '%let cat=%scan(&catlist,&i,%str( ));'; put 'proc sql;'; put 'select distinct fmtname into: fmtlist separated by '' '''; put 'from &tmpds1 where fmtcat="&cat";'; put 'proc format lib=&cat cntlout=&tmpds3(keep=fmtname length);'; put 'select &fmtlist;'; put 'run;'; put 'proc sql;'; put 'insert into &tmpds2 select distinct fmtname,length from &tmpds3;'; put '%end;'; put 'proc sql;'; put 'create table &tmpds4 as'; put 'select a.*, b.length as MAXW'; put 'from &colinfo a'; put 'left join &tmpds2 b'; put 'on cats(a.format)=cats(upcase(b.fmtname))'; put 'order by a.varnum;'; put 'data _null_;'; put 'set &tmpds4;'; put 'if not missing(maxw);'; put 'call symputx('; put 'cats(''fmtlen'',_n_),'; put '/* vars need extra padding due to JSON escaping of special chars */'; put 'min(32767,ceil((max(length,maxw)+10)*1.5))'; put ',''l'''; put ');'; put 'run;'; put '/* configure varlenchk - as we are explicitly shortening the variables */'; put '%let optval=%sysfunc(getoption(varlenchk));'; put 'options varlenchk=NOWARN;'; put 'data _data_(compress=char);'; put '/* shorten the new vars */'; put 'length'; put '%do i=1 %to &numcols;'; put '&&name&i $&&fmtlen&i'; put '%end;'; put ';'; put '/* rename on entry */'; put 'set &ds(rename=('; put '%do i=1 %to &numcols;'; put '&&name&i=&&newname&i'; put '%end;'; put '));'; put '&stmt_obs;'; put 'drop'; put '%do i=1 %to &numcols;'; put '&&newname&i'; put '%end;'; put ';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=num %then %do;'; put '&&name&i=cats(put(&&newname&i,&&fmt&i));'; put '%end;'; put '%else %do;'; put '&&name&i=put(&&newname&i,&&fmt&i);'; put '%end;'; put '%end;'; put 'if _error_ then do;'; put 'call symputx(''syscc'',1012);'; put 'stop;'; put 'end;'; put 'run;'; put '%let fmtds=&syslast;'; put 'options varlenchk=&optval;'; put '%end;'; put 'proc format; /* credit yabwon for special null removal */'; put 'value bart (default=40)'; put '%if &missing=NULL %then %do;'; put '._ - .z = null'; put '%end;'; put '%else %do;'; put '._ = [quote()]'; put '. = null'; put '.a - .z = [quote()]'; put '%end;'; put 'other = [best.];'; put 'data &tempds;'; put 'attrib _all_ label='''';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'length &&name&i $&&fmtlen&i...;'; put 'format &&name&i $&&fmtlen&i...;'; put '%end;'; put '%end;'; put '%if &fmt=Y %then %do;'; put 'set &fmtds;'; put '%end;'; put '%else %do;'; put 'set &ds;'; put '%end;'; put '&stmt_obs;'; put 'format _numeric_ bart.;'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'if findc(&&name&i,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put '&&name&i=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,&&name&i)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else &&name&i=quote(cats(&&name&i));'; put '%end;'; put '%end;'; put 'run;'; put 'filename _sjs3 temp lrecl=131068 ;'; put 'data _null_;'; put 'file _sjs3 encoding=''utf-8'';'; put 'if _n_=1 then put "[";'; put 'set &tempds;'; put 'if _n_>1 then put "," @; put'; put '%if &action=ARR %then "[" ; %else "{" ;'; put '%do i=1 %to &numcols;'; put '%if &i>1 %then "," ;'; put '%if &action=OBJ %then """&&name&i"":" ;'; put '"&&name&i"n /* name literal for reserved variable names */'; put '%end;'; put '%if &action=ARR %then "]" ; %else "}" ; ;'; put '/* close out the table */'; put 'data _null_;'; put 'file _sjs3 mod encoding=''utf-8'';'; put 'put '']'';'; put 'run;'; put 'data _null_;'; put 'infile _sjs3 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs3 clear;'; put '%end;'; put 'proc sql;'; put 'drop table &colinfo, &tempds;'; put '%if %substr(&showmeta,1,1)=Y %then %do;'; put 'filename _sjs4 temp lrecl=131068 encoding=''utf-8'';'; put 'data _null_;'; put 'file _sjs4;'; put 'length label $350;'; put 'put ", ""$%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":{""vars"":{";'; put 'do i=1 to &numcols;'; put 'name=quote(trim(symget(cats(''name'',i))));'; put 'format=quote(trim(symget(cats(''fmt'',i))));'; put 'label=quote(prxchange(''s/\\/\\\\/'',-1,trim(symget(cats(''label'',i)))));'; put 'length=quote(trim(symget(cats(''length'',i))));'; put 'type=quote(trim(symget(cats(''typelong'',i))));'; put 'if i>1 then put "," @@;'; put 'put name '':{"format":'' format '',"label":'' label'; put ''',"length":'' length '',"type":'' type ''}'';'; put 'end;'; put 'put ''}}'';'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs4 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs4 clear;'; put '%end;'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put 'data _null_; file &jref encoding=''utf-8'' mod ;'; put 'put "}";'; put 'run;'; put '%end;'; put '%mend mp_jsonout;'; put '/**'; put '@file mm_webout.sas'; put '@brief Send data to/from SAS Stored Processes'; put '@details This macro should be added to the start of each Stored Process,'; put '**immediately** followed by a call to:'; put '%mm_webout(FETCH)'; put 'This will read all the input data and create same-named SAS datasets in the'; put 'WORK library. You can then insert your code, and send data back using the'; put 'following syntax:'; put 'data some datasets; * make some data ;'; put 'retain some columns;'; put 'run;'; put '%mm_webout(OPEN)'; put '%mm_webout(ARR,some) * Array format, fast, suitable for large tables ;'; put '%mm_webout(OBJ,datasets) * Object format, easier to work with ;'; put 'Finally, wrap everything up send some helpful system variables too'; put '%mm_webout(CLOSE)'; put '@param [in] action Either FETCH, OPEN, ARR, OBJ or CLOSE'; put '@param [in] ds The dataset to send back to the frontend'; put '@param [out] dslabel= Value to use instead of table name for sending to JSON'; put '@param [in] fmt= (N) Setting Y converts all vars to their formatted values'; put '@param [out] fref= (_webout) The fileref to which to write the JSON'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] workobs= (0) When set to a positive integer, will create a new'; put 'output object (WORK) which contains this number of observations from all'; put 'tables in the WORK library.'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to output JSON'; put '

SAS Macros

'; put '@li mp_jsonout.sas'; put '

Related Macros

'; put '@li ms_webout.sas'; put '@li mv_webout.sas'; put '@version 9.3'; put '@author Allan Bowe'; put '**/'; put '%macro mm_webout(action,ds,dslabel=,fref=_webout,fmt=N,missing=NULL'; put ',showmeta=N,maxobs=MAX,workobs=0'; put ');'; put '%global _webin_file_count _webin_fileref1 _webin_name1 _program _debug'; put 'sasjs_tables;'; put '%local i tempds jsonengine;'; put '/* see https://github.com/sasjs/core/issues/41 */'; put '%if "%upcase(&SYSENCODING)" ne "UTF-8" %then %let jsonengine=PROCJSON;'; put '%else %let jsonengine=DATASTEP;'; put '%if &action=FETCH %then %do;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'options mprint notes mprintnest;'; put '%end;'; put '%let _webin_file_count=%eval(&_webin_file_count+0);'; put '/* now read in the data */'; put '%do i=1 %to &_webin_file_count;'; put '%if &_webin_file_count=1 %then %do;'; put '%let _webin_fileref1=&_webin_fileref;'; put '%let _webin_name1=&_webin_name;'; put '%end;'; put 'data _null_;'; put 'infile &&_webin_fileref&i termstr=crlf;'; put 'input;'; put 'call symputx(''input_statement'',_infile_);'; put 'putlog "&&_webin_name&i input statement: " _infile_;'; put 'stop;'; put 'data &&_webin_name&i;'; put 'infile &&_webin_fileref&i firstobs=2 dsd termstr=crlf encoding=''utf-8'';'; put 'input &input_statement;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'if _n_<20 then putlog _infile_;'; put '%end;'; put 'run;'; put '%let sasjs_tables=&sasjs_tables &&_webin_name&i;'; put '%end;'; put '%end;'; put '%else %if &action=OPEN %then %do;'; put '/* fix encoding */'; put 'OPTIONS NOBOMFILE;'; put '/**'; put '* check xengine type to avoid the below err message:'; put '* > Function is only valid for filerefs using the CACHE access method.'; put '*/'; put 'data _null_;'; put 'set sashelp.vextfl(where=(fileref="_WEBOUT"));'; put 'if xengine=''STREAM'' then do;'; put 'rc=stpsrv_header(''Content-type'',"text/html; encoding=utf-8");'; put 'end;'; put 'run;'; put '/* setup json */'; put 'data _null_;file &fref encoding=''utf-8'';'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutBEGIN<<'';'; put '%end;'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'run;'; put '%end;'; put '%else %if &action=ARR or &action=OBJ %then %do;'; put '%mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt,jref=&fref'; put ',engine=&jsonengine,missing=&missing,showmeta=&showmeta,maxobs=&maxobs'; put ')'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put '/* To avoid issues with _webout on EBI we use a temporary file */'; put 'filename _sjsref temp lrecl=131068;'; put '%if %str(&workobs) > 0 %then %do;'; put '/* if debug mode, send back first XX records of each work table also */'; put 'data;run;%let tempds=%scan(&syslast,2,.);'; put 'ods output Members=&tempds;'; put 'proc datasets library=WORK memtype=data;'; put '%local wtcnt;%let wtcnt=0;'; put 'data _null_;'; put 'set &tempds;'; put 'if not (upcase(name) =:"DATA"); /* ignore temp datasets */'; put 'i+1;'; put 'call symputx(cats(''wt'',i),name,''l'');'; put 'call symputx(''wtcnt'',i,''l'');'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put ",""WORK"":{";'; put '%do i=1 %to &wtcnt;'; put '%let wt=&&wt&i;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'dsid=open("WORK.&wt",''is'');'; put 'nlobs=attrn(dsid,''NLOBS'');'; put 'nvars=attrn(dsid,''NVARS'');'; put 'rc=close(dsid);'; put 'if &i>1 then put '',''@;'; put 'put " ""&wt"" : {";'; put 'put ''"nlobs":'' nlobs;'; put 'put '',"nvars":'' nvars;'; put '%mp_jsonout(OBJ,&wt,jref=_sjsref,dslabel=first10rows,showmeta=Y'; put ',maxobs=&workobs'; put ')'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put '%end;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put 'run;'; put '%end;'; put '/* close off json */'; put 'data _null_;file _sjsref mod encoding=''utf-8'';'; put 'length SYSPROCESSNAME syserrortext syswarningtext autoexec $512;'; put 'put ",""_DEBUG"" : ""&_debug"" ";'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'autoexec=quote(urlencode(trim(getoption(''autoexec''))));'; put 'put '',"AUTOEXEC" : '' autoexec;'; put 'put ",""MF_GETUSER"" : ""%mf_getuser()"" ";'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'put ",""SYSENCODING"" : ""&sysencoding"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSPROCESSID"" : ""&SYSPROCESSID"" ";'; put 'put ",""SYSPROCESSMODE"" : ""&SYSPROCESSMODE"" ";'; put 'SYSPROCESSNAME=quote(urlencode(cats(SYSPROCESSNAME)));'; put 'put ",""SYSPROCESSNAME"" : " SYSPROCESSNAME;'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put '',"SYSWARNINGTEXT" : '' syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'length memsize $32;'; put 'memsize="%sysfunc(INPUTN(%sysfunc(getoption(memsize)), best.),sizekmg.)";'; put 'memsize=quote(cats(memsize));'; put 'put '',"MEMSIZE" : '' memsize;'; put 'put "}" @;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutEND<<'';'; put '%end;'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjsref lrecl=1 recfm=n;'; put 'file &fref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjsref clear;'; put '%end;'; put '%mend mm_webout;'; put '%macro webout(action,ds,dslabel=,fmt=,missing=NULL,showmeta=NO,maxobs=MAX);'; put '%mm_webout(&action,ds=&ds,dslabel=&dslabel,fmt=&fmt'; put ',missing=&missing'; put ',showmeta=&showmeta'; put ',maxobs=&maxobs'; put ') %mend;'; put '/* provide additional debug info */'; put '%global _program;'; put '%put &=syscc;'; put '%put user=%mf_getuser();'; put '%put pgm=&_program;'; put '%put timestamp=%sysfunc(datetime(),datetime19.);'; put '* Service Variables start;'; put '* Service Variables end;'; put '* SAS Macros start;'; put '%macro mf_getapploc(pgm);'; put '%if "&pgm"="" %then %do;'; put '%if %symexist(_program) %then %let pgm=&_program;'; put '%else %do;'; put '%put &sysmacroname: No value provided and no _program variable available;'; put '%return;'; put '%end;'; put '%end;'; put '%local root;'; put '/**'; put '* First check we are not in the tests/macros folder (which has no subfolders)'; put '* or specifically in the testsetup or testteardown services'; put '*/'; put '%if %index(&pgm,/tests/macros/)'; put 'or %index(&pgm,/tests/testsetup)'; put 'or %index(&pgm,/tests/testteardown)'; put '%then %do;'; put '%let root=%substr(&pgm,1,%index(&pgm,/tests)-1);'; put '&root'; put '%return;'; put '%end;'; put '/**'; put '* Next, move up two levels to avoid matches on subfolder or service name'; put '*/'; put '%let root=%substr(&pgm,1,%length(&pgm)-%length(%scan(&pgm,-1,/))-1);'; put '%let root=%substr(&root,1,%length(&root)-%length(%scan(&root,-1,/))-1);'; put '%if %index(&root,/tests/) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/tests/)-1);'; put '%end;'; put '%else %if %index(&root,/services) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/services)-1);'; put '%end;'; put '%else %if %index(&root,/jobs) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/jobs)-1);'; put '%end;'; put '%else %put &sysmacroname: Could not find an app location from &pgm;'; put '&root'; put '%mend mf_getapploc ;'; put '%macro mf_getuniquefileref(prefix=_,maxtries=1000,lrecl=32767);'; put '%local rc fname;'; put '%if &prefix=0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%end;'; put '%else %do;'; put '%local x len;'; put '%let len=%eval(8-%length(&prefix));'; put '%let x=0;'; put '%do x=0 %to &maxtries;'; put '%let fname=&prefix%substr(%sysfunc(ranuni(0)),3,&len);'; put '%if %sysfunc(fileref(&fname)) > 0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%return;'; put '%end;'; put '%end;'; put '%put unable to find available fileref after &maxtries attempts;'; put '%end;'; put '%mend mf_getuniquefileref;'; put '%macro mp_abort(mac=mp_abort.sas, type=, msg=, iftrue=%str(1=1)'; put ', errds=work.mp_abort_errds'; put ', mode=REGULAR'; put ')/*/STORE SOURCE*/;'; put '%global sysprocessmode sysprocessname sasjs_stpsrv_header_loc sasjsprocessmode;'; put '%local fref fid i;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%put NOTE: /// mp_abort macro executing //;'; put '%if %length(&mac)>0 %then %put NOTE- called by &mac;'; put '%put NOTE - &msg;'; put '%if %symexist(_SYSINCLUDEFILEDEVICE)'; put '/* abort cancel FILE does not restart outside the INCLUDE on Viya 3.5 */'; put 'and %superq(SYSPROCESSNAME) ne %str(Compute Server)'; put '%then %do;'; put '%if "*&_SYSINCLUDEFILEDEVICE*" ne "**" %then %do;'; put 'data &errds;'; put 'iftrue=''1=1'';'; put 'length mac $100 msg $5000;'; put 'mac=symget(''mac'');'; put 'msg=symget(''msg'');'; put 'run;'; put 'data _null_;'; put 'abort cancel FILE;'; put 'run;'; put '%return;'; put '%end;'; put '%end;'; put '/* Web App Context */'; put '%if %symexist(_PROGRAM)'; put 'or %superq(SYSPROCESSNAME) = %str(Compute Server)'; put 'or &mode=INCLUDE'; put '%then %do;'; put 'options obs=max replace mprint;'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5"'; put '%then %do;'; put 'options nosyntaxcheck;'; put '%end;'; put '%if &mode=INCLUDE %then %do;'; put '%if %sysfunc(exist(&errds))=1 %then %do;'; put 'data _null_;'; put 'set &errds;'; put 'call symputx(''iftrue'',iftrue,''l'');'; put 'call symputx(''mac'',mac,''l'');'; put 'call symputx(''msg'',msg,''l'');'; put 'putlog (_all_)(=);'; put 'run;'; put '%if (&iftrue)=0 %then %return;'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: No include errors found;'; put '%return;'; put '%end;'; put '%end;'; put '/* extract log errs / warns, if exist */'; put '%local logloc logline;'; put '%global logmsg; /* capture global messages */'; put '%if %symexist(SYSPRINTTOLOG) %then %let logloc=&SYSPRINTTOLOG;'; put '%else %let logloc=%qsysfunc(getoption(LOG));'; put 'proc printto log=log;run;'; put '%let logline=0;'; put '%if %length(&logloc)>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input; putlog _infile_;'; put 'i=1;'; put 'retain logonce 0;'; put 'if ('; put '_infile_=:"%str(WARN)ING" or _infile_=:"%str(ERR)OR"'; put ') and logonce=0 then'; put 'do;'; put 'call symputx(''logline'',_n_);'; put 'logonce+1;'; put 'end;'; put 'run;'; put '/* capture log including lines BEFORE the err */'; put '%if &logline>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input;'; put 'i=1;'; put 'stoploop=0;'; put 'if _n_ ge &logline-15 and stoploop=0 then do until (i>22);'; put 'call symputx(''logmsg'',catx(''\n'',symget(''logmsg''),_infile_));'; put 'input;'; put 'i+1;'; put 'stoploop=1;'; put 'end;'; put 'if stoploop=1 then stop;'; put 'run;'; put '%end;'; put '%end;'; put '%if %symexist(SYS_JES_JOB_URI) %then %do;'; put '/* setup webout for Viya */'; put 'options nobomfile;'; put '%if "X&SYS_JES_JOB_URI.X"="XX" %then %do;'; put 'filename _webout temp lrecl=999999 mod;'; put '%end;'; put '%else %do;'; put 'filename _webout filesrvc parenturi="&SYS_JES_JOB_URI"'; put 'name="_webout.json" lrecl=999999 mod;'; put '%end;'; put '%end;'; put '%else %if %sysfunc(filename(fref,&sasjs_stpsrv_header_loc))=0 %then %do;'; put 'options nobomfile;'; put '/* set up http header for SASjs Server */'; put '%let fid=%sysfunc(fopen(&fref,A));'; put '%if &fid=0 %then %do;'; put '%put %str(ERR)OR: %sysfunc(sysmsg());'; put '%return;'; put '%end;'; put '%let rc=%sysfunc(fput(&fid,%str(Content-Type: application/json)));'; put '%let rc=%sysfunc(fwrite(&fid));'; put '%let rc=%sysfunc(fclose(&fid));'; put '%let rc=%sysfunc(filename(&fref));'; put '%end;'; put '/* send response in SASjs JSON format */'; put 'data _null_;'; put 'file _webout mod lrecl=32000 encoding=''utf-8'';'; put 'length msg syswarningtext syserrortext $32767 mode $10 ;'; put 'sasdatetime=datetime();'; put 'msg=symget(''msg'');'; put '%if &logline>0 %then %do;'; put 'msg=cats(msg,''\n\nLog Extract:\n'',symget(''logmsg''));'; put '%end;'; put '/* escape the escapes */'; put 'msg=tranwrd(msg,''\'',''\\'');'; put '/* escape the quotes */'; put 'msg=tranwrd(msg,''"'',''\"'');'; put '/* ditch the CRLFs as chrome complains */'; put 'msg=compress(msg,,''kw'');'; put '/* quote without quoting the quotes (which are escaped instead) */'; put 'msg=cats(''"'',msg,''"'');'; put 'if symexist(''_debug'') then debug=quote(trim(symget(''_debug'')));'; put 'else debug=''""'';'; put 'if symget(''sasjsprocessmode'')=''Stored Program'' then mode=''SASJS'';'; put 'if mode ne ''SASJS'' then put ''>>weboutBEGIN<<'';'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'put '',"sasjsAbort" : [{'';'; put 'put '' "MSG":'' msg ;'; put 'put '' ,"MAC": "'' "&mac" ''"}]'';'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'put '',"_DEBUG":'' debug ;'; put 'if symexist(''_metauser'') then do;'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put 'end;'; put 'if symexist(''SYS_JES_JOB_URI'') then do;'; put 'SYS_JES_JOB_URI=quote(trim(symget(''SYS_JES_JOB_URI'')));'; put 'put '',"SYS_JES_JOB_URI": '' SYS_JES_JOB_URI;'; put 'end;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put ",""SYSWARNINGTEXT"" : " syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'put "}" ;'; put 'if mode ne ''SASJS'' then put ''>>weboutEND<<'';'; put 'run;'; put '%put _all_;'; put '%if "&sysprocessmode " = "SAS Stored Process Server " %then %do;'; put 'data _null_;'; put 'putlog ''stpsrvset program err and syscc'';'; put 'rc=stpsrvset(''program error'', 0);'; put 'call symputx("syscc",0,"g");'; put 'run;'; put '%if &sysscp=WIN'; put 'and 1=0 /* deprecating this logic until we figure out a consistent abort */'; put 'and "%substr(%str(&sysvlong ),1,8)"="9.04.01M"'; put 'and "%substr(%str(&sysvlong ),9,1)">"5" %then %do;'; put '/* skip approach (below) does not work in windows m6+ envs */'; put 'endsas;'; put '%end;'; put '%else %do;'; put '/**'; put '* endsas kills 9.4m3 deployments by orphaning multibridges.'; put '* Abort variants are ungraceful (non zero return code)'; put '* This approach lets SAS run silently until the end :-)'; put '* Caution - fails when called within a %include within a macro'; put '* Use mp_include() to handle this.'; put '*/'; put 'filename skip temp;'; put 'data _null_;'; put 'file skip;'; put 'put ''%macro skip();'';'; put 'comment ''%mend skip; -> fix lint '';'; put 'put ''%macro skippy();'';'; put 'comment ''%mend skippy; -> fix lint '';'; put 'run;'; put '%inc skip;'; put '%end;'; put '%end;'; put '%else %if "&sysprocessmode " = "SAS Compute Server " %then %do;'; put '/* endsas kills the session making it harder to fetch results */'; put 'data _null_;'; put 'syswarningtext=symget(''syswarningtext'');'; put 'syserrortext=symget(''syserrortext'');'; put 'abort_msg=symget(''msg'');'; put 'syscc=symget(''syscc'');'; put 'sysuserid=symget(''sysuserid'');'; put 'iftrue=symget(''iftrue'');'; put 'put (_all_)(/=);'; put 'call symputx(''syscc'',0);'; put 'abort cancel nolist;'; put 'run;'; put '%end;'; put '%else %do;'; put '%abort cancel;'; put '%end;'; put '%end;'; put '%else %do;'; put '%put _all_;'; put '%abort cancel;'; put '%end;'; put '%mend mp_abort;'; put '/** @endcond */'; put '%macro mm_getstpcode('; put 'tree=/User Folders/sasdemo/somestp'; put ',name='; put ',outloc=0'; put ',outref=0'; put ',mDebug=1'; put ',showlog=NO'; put ');'; put '%local mD;'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing &sysmacroname..sas;'; put '%&mD.put _local_;'; put '%if %length(&name)>0 %then %let name=/&name;'; put '/* first, check if STP exists */'; put '%local tsuri;'; put '%let tsuri=stopifempty ;'; put 'data _null_;'; put 'format type uri tsuri value $200.;'; put 'call missing (of _all_);'; put 'path="&tree&name(StoredProcess)";'; put '/* first, find the STP ID */'; put 'if metadata_pathobj("",path,"StoredProcess",type,uri)>0 then do;'; put '/* get sourcecode */'; put 'cnt=1;'; put 'do while (metadata_getnasn(uri,"Notes",cnt,tsuri)>0);'; put 'rc=metadata_getattr(tsuri,"Name",value);'; put '&mD.put tsuri= value=;'; put 'if value="SourceCode" then do;'; put '/* found it! */'; put 'rc=metadata_getattr(tsuri,"Id",value);'; put 'call symputx(''tsuri'',value,''l'');'; put 'stop;'; put 'end;'; put 'cnt+1;'; put 'end;'; put 'end;'; put 'else put (_all_)(=);'; put 'run;'; put '%mp_abort(iftrue= (&tsuri=stopifempty)'; put ',mac=mm_getstpcode'; put ',msg=%str(&tree&name.(StoredProcess) not found!)'; put ')'; put '/**'; put '* Now we can extract the textstore'; put '*/'; put 'filename __getdoc temp lrecl=10000000;'; put 'proc metadata'; put 'in="$METAREPOSITORY'; put ''; put 'SAS1"'; put 'out=__getdoc ;'; put 'run;'; put '/* find the beginning of the text */'; put '%local start;'; put 'data _null_;'; put 'infile __getdoc lrecl=10000;'; put 'input;'; put 'start=index(_infile_,''StoredText="'');'; put 'if start then do;'; put 'call symputx("start",start+11);'; put '*putlog ''"'' _infile_ ''"'';'; put 'end;'; put 'stop;'; put '%local outeng;'; put '%if "&outloc"="0" %then %let outeng=TEMP;'; put '%else %let outeng="&outloc";'; put '%local fref;'; put '%if &outref=0 %then %let fref=%mf_getuniquefileref();'; put '%else %let fref=&outref;'; put '/* read the content, byte by byte, resolving escaped chars */'; put 'filename &fref &outeng lrecl=100000;'; put 'data _null_;'; put 'length filein 8 fileid 8;'; put 'filein = fopen("__getdoc","I",1,"B");'; put 'fileid = fopen("&fref","O",1,"B");'; put 'rec = "20"x;'; put 'length entity $6;'; put 'do while(fread(filein)=0);'; put 'x+1;'; put 'if x>&start then do;'; put 'rc = fget(filein,rec,1);'; put 'if rec=''"'' then leave;'; put 'else if rec="&" then do;'; put 'entity=rec;'; put 'do until (rec=";");'; put 'if fread(filein) ne 0 then goto getout;'; put 'rc = fget(filein,rec,1);'; put 'entity=cats(entity,rec);'; put 'end;'; put 'select (entity);'; put 'when (''&'' ) rec=''&'' ;'; put 'when (''<'' ) rec=''<'' ;'; put 'when (''>'' ) rec=''>'' ;'; put 'when (''''') rec="''" ;'; put 'when (''"'') rec=''"'' ;'; put 'when ('' '') rec=''0A''x;'; put 'when ('' '') rec=''0D''x;'; put 'when (''$'' ) rec=''$'' ;'; put 'when ('' '') rec=''09''x;'; put 'otherwise putlog "%str(WARN)ING: missing value for " entity=;'; put 'end;'; put 'rc =fput(fileid, substr(rec,1,1));'; put 'rc =fwrite(fileid);'; put 'end;'; put 'else do;'; put 'rc =fput(fileid,rec);'; put 'rc =fwrite(fileid);'; put 'end;'; put 'end;'; put 'end;'; put 'getout:'; put 'rc=fclose(filein);'; put 'rc=fclose(fileid);'; put 'run;'; put '%if &showlog=YES %then %do;'; put 'data _null_;'; put 'infile &fref lrecl=32767 end=last;'; put 'input;'; put 'if _n_=1 then putlog ''>>stpcodeBEGIN<<'';'; put 'putlog _infile_;'; put 'if last then putlog ''>>stpcodeEND<<'';'; put 'run;'; put '%end;'; put 'filename __getdoc clear;'; put '%if &outref=0 %then %do;'; put 'filename &fref clear;'; put '%end;'; put '%mend mm_getstpcode;'; put '%macro dc_getsettings();'; put '%global _program;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(syscc=&syscc on entry (&syswarningtext &syserrortext))'; put ')'; put '%if %length(&_PROGRAM)>1 %then %let root=&_program;'; put '%else %do;'; put '%global _metauser;'; put '%let _metauser=&sysuserid;'; put '/* to mimic a "real" _program we need to give a dummy role and stp name */'; put '%let root=/dummyRole/dummyName;'; put '%end;'; put '/* the DC precode is stored in the Admin folder in the root of'; put 'the project. Lets find that root. */'; put '%put &=root;'; put '%let root=%mf_getapploc();'; put '%put &=root;'; put '/* Now we know the root location we can retrieve the params */'; put '%let temploc=%sysfunc(pathname(work))/settings.txt;'; put '%mm_getstpcode(tree=&root/services/public'; put ',name=Data_Controller_Settings'; put ',outloc=&temploc'; put ')'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Unable to run getstpcode)'; put ')'; put 'filename _getsets "&temploc" lrecl=2000;'; put '/*'; put 'Do not use mp_include here - this puts a copy in every service, which creates'; put 'compilation problems when calling services from mp_include'; put '*/'; put '%inc _getsets/source2;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Problem running &sysmacroname (&syswarningtext &syserrortext))'; put ')'; put '%mend dc_getsettings;'; put '%macro mf_fmtdttm('; put ')/*/STORE SOURCE*/;'; put '%if "&sysver"="9.2" or "&sysver"="9.3"'; put 'or ("&sysver"="9.4" and "%substr(&SYSVLONG,9,1)" le "3")'; put 'or "%substr(&sysver,1,1)"="4"'; put 'or "%substr(&sysver,1,1)"="5"'; put '%then %do;DATETIME19.3%end;'; put '%else %do;E8601DT26.6%end;'; put '%mend mf_fmtdttm;'; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '%macro mp_init(prefix=SASJS'; put ')/*/STORE SOURCE*/;'; put '%if %symexist(SASJS_PREFIX) %then %return; /* only run once */'; put '%global'; put 'SASJS_PREFIX /* the ONLY hard-coded global macro variable in SASjs */'; put '&prefix._FUNCTIONS /* used in mcf_init() to track core function compilation */'; put '&prefix._INIT_NUM /* initialisation time as numeric */'; put '&prefix._INIT_DTTM /* initialisation time in E8601DT26.6 format */'; put '&prefix.WORK /* avoid typing %sysfunc(pathname(work)) every time */'; put ';'; put '%let sasjs_prefix=&prefix;'; put 'data _null_;'; put 'dttm=datetime();'; put 'call symputx("&sasjs_prefix._init_num",dttm,''g'');'; put 'call symputx("&sasjs_prefix._init_dttm",put(dttm,E8601DT26.6),''g'');'; put 'call symputx("&sasjs_prefix.work",pathname(''WORK''),''g'');'; put 'run;'; put 'options'; put 'compress=CHAR /* default is none so ensure we have something! */'; put 'datastmtchk=ALLKEYWORDS /* protection from overwriting input datasets */'; put 'errorcheck=STRICT /* catch errs in libname/filename statements */'; put 'fmterr /* ensure err when a format cannot be found */'; put 'mergenoby=%str(ERR)OR /* throw err when a merge has no BY variables */'; put 'missing=. /* changing this can cause hard to detect errs */'; put 'noquotelenmax /* avoid warnings for long strings */'; put 'noreplace /* avoid overwriting permanent datasets */'; put 'ps=max /* reduce log size slightly */'; put 'ls=max /* reduce log even more and avoid word truncation */'; put 'validmemname=COMPATIBLE /* avoid special characters etc in table names */'; put 'validvarname=V7 /* avoid special characters etc in variable names */'; put 'varinitchk=%str(ERR)OR /* avoid data mistakes from variable name typos */'; put 'varlenchk=%str(ERR)OR /* fail hard if truncation (data loss) can result */'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5" %then %do;'; put 'noautocorrect /* disallow misspelled procedure names */'; put 'dsoptions=note2err /* undocumented - convert bad NOTEs to ERRs */'; put '%end;'; put ';'; put '%mend mp_init;'; put '%macro mpeinit(fetch=YES);'; put '%global mpeinit'; put 'mpeadmins /* group with unrestricted Meditor access */'; put 'mpelocapprovals /* location for landing and staging files */'; put 'mpelib /* location of configuration tables for DC */'; put 'dc_repo_users /* location of user / group metadata */'; put 'dc_licence_key /* extracted in dc_getsettings */'; put 'dc_activation_key /* extracted in dc_getsettings */'; put 'dc_locale /* extracted in dc_getsettings */'; put 'dc_dttmtfmt /* can be overridden in dc_getsettings */'; put '_debug'; put ';'; put '%if &mpeinit=1 %then %return;'; put '%else %let mpeinit=1;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(Problem on service startup (&syswarningtext &syserrortext))'; put ')'; put '%mp_init()'; put '%if &fetch=YES %then %do;'; put '%webout(FETCH)'; put '%end;'; put '%global _CLIENTNAME;'; put '%mp_abort(iftrue= (&_CLIENTNAME=SAS Enterprise Guide)'; put ',mac=&_program..sas'; put ',msg=%str(Data Controller is a web app and should not be executed from EG)'; put ')'; put 'options urlencoding=utf8 nobomfile lrecl=32767;'; put '%let perf=%sysfunc(datetime());'; put '%put perfdiff: 0;'; put '%let dc_locale=SYSTEM; /* default if not set */'; put '/**'; put '* E8601DT26.6 has widest database support - but not all SAS flavours can'; put '* handle it. Override in the settings STP if needed.'; put '*/'; put 'data _null_;'; put 'dc_dttmtfmt=''"%sysfunc(datetime(),''!!"%mf_fmtdttm()"!!'')"dt'';'; put 'call symputx(''dc_dttmtfmt'',dc_dttmtfmt);'; put 'put dc_dttmtfmt=;'; put 'run;'; put '%put &=dc_dttmtfmt;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc prior to dc_getsettings)'; put ')'; put '%dc_getsettings()'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc after dc_getsettings)'; put ')'; put 'data _null_;'; put 'set &DC_LIBREF..mpe_config(where=('; put 'var_scope="DC"'; put 'and &dc_dttmtfmt lt tx_to'; put 'and var_active=1'; put '));'; put 'call symputx(var_name,var_value,''G'');'; put 'putlog var_name "=" var_value;'; put 'run;'; put '%let mpelib=&dc_libref;'; put '%let mpeadmins=&dc_admin_group;'; put '%let mpelocapprovals=&dc_staging_area;'; put '%let dc_repo_users=&dc_repo_users;'; put '%if &dc_locale ne SYSTEM %then %do;'; put 'options locale=&dc_locale;'; put '%end;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(Problem during compilation or with STP precode (&syswarningtext))'; put ')'; put '%mend mpeinit;'; put '%macro mf_mval(var);'; put '%if %symexist(&var) %then %do;'; put '%superq(&var)'; put '%end;'; put '%mend mf_mval;'; put '%macro mf_trimstr(basestr,trimstr);'; put '%local baselen trimlen trimval;'; put '/* return if basestr is shorter than trimstr (or 0) */'; put '%let baselen=%length(%superq(basestr));'; put '%let trimlen=%length(%superq(trimstr));'; put '%if &baselen < &trimlen or &baselen=0 %then %return;'; put '/* obtain the characters from the end of basestr */'; put '%let trimval=%qsubstr(%superq(basestr)'; put ',%length(%superq(basestr))-&trimlen+1'; put ',&trimlen);'; put '/* compare and if matching, chop it off! */'; put '%if %superq(basestr)=%superq(trimstr) %then %do;'; put '%return;'; put '%end;'; put '%else %if %superq(trimval)=%superq(trimstr) %then %do;'; put '%qsubstr(%superq(basestr),1,%length(%superq(basestr))-&trimlen)'; put '%end;'; put '%else %do;'; put '&basestr'; put '%end;'; put '%mend mf_trimstr;'; put '%macro mf_getplatform(switch'; put ')/*/STORE SOURCE*/;'; put '%local a b c;'; put '%if &switch.NONE=NONE %then %do;'; put '%if %symexist(sasjsprocessmode) %then %do;'; put '%if &sasjsprocessmode=Stored Program %then %do;'; put 'SASJS'; put '%return;'; put '%end;'; put '%end;'; put '%if %symexist(sysprocessmode) %then %do;'; put '%if "&sysprocessmode"="SAS Object Server"'; put 'or "&sysprocessmode"= "SAS Compute Server" %then %do;'; put 'SASVIYA'; put '%end;'; put '%else %if "&sysprocessmode"="SAS Stored Process Server"'; put 'or "&sysprocessmode"="SAS Workspace Server"'; put '%then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if %symexist(_metaport) or %symexist(_metauser) %then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if &switch=SASSTUDIO %then %do;'; put '/* return the version of SAS Studio else 0 */'; put '%if %mf_mval(_CLIENTAPP)=%str(SAS Studio) %then %do;'; put '%let a=%mf_mval(_CLIENTVERSION);'; put '%let b=%scan(&a,1,.);'; put '%if %eval(&b >2) %then %do;'; put '&b'; put '%end;'; put '%else 0;'; put '%end;'; put '%else 0;'; put '%end;'; put '%else %if &switch=VIYARESTAPI %then %do;'; put '%mf_trimstr(%sysfunc(getoption(servicesbaseurl)),/)'; put '%end;'; put '%mend mf_getplatform;'; put '%macro mpeterm();'; put '%local oldloc;'; put 'data _null_;'; put 'if symexist(''SYSPRINTTOLOG'') then oldloc=symget(''SYSPRINTTOLOG'');'; put 'else oldloc=getoption(''LOG'');'; put 'if subpad(oldloc,1,1) not in (''"'',"''",'' '') then oldloc=quote(cats(oldloc));'; put 'call symputx(''oldloc'',oldloc,''l'');'; put 'run;'; put '%if %length(&oldloc)>0 %then %do;'; put 'proc printto log=log;'; put 'run;'; put 'data _null_;'; put 'infile &oldloc;'; put 'input; putlog _infile_;'; put 'run;'; put '%end;'; put '%if %sysfunc(exist(&dc_libref..mpe_requests)) and %mf_getplatform() ne SASVIYA'; put '%then %do;'; put 'data ;'; put 'if 0 then set &dc_libref..mpe_requests;'; put 'request_dttm=%sysfunc(datetime());'; put 'request_user="%mf_getuser()";'; put 'request_service="%scan(&_program,-2,/)/%scan(&_program,-1,/)";'; put 'request_params='''';'; put 'output;stop;'; put 'proc append base=&dc_libref..mpe_requests data=&syslast force nowarn;'; put 'run;'; put '%end;'; put '%mend mpeterm;'; put '%macro mpe_getvars(injs,outds);'; put '/* load parameters */'; put 'data _null_;'; put '__dummychar='''';__dummynum=0;'; put 'set &outds;'; put 'array __charvals _character_;'; put 'do over __charvals;'; put 'call symputx(vname(__charvals),__charvals,''g'');'; put 'end;'; put 'array __numvals _numeric_;'; put 'do over __numvals;'; put 'call symputx(vname(__numvals),__numvals,''g'');'; put 'end;'; put 'run;'; put '%mend mpe_getvars;'; put '%macro mm_getGroups('; put 'user='; put ',outds=work.mm_getGroups'; put ',repo=foundation'; put ',mDebug=0'; put ')/*/STORE SOURCE*/;'; put '%local mD oldrepo;'; put '%let oldrepo=%sysfunc(getoption(metarepository));'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing mm_getGroups.sas;'; put '%&mD.put _local_;'; put '/* on some sites, user / group info is in a different metadata repo to the'; put 'default */'; put '%if &oldrepo ne &repo %then %do;'; put 'options metarepository=&repo;'; put '%end;'; put '%if %length(&user)=0 %then %do;'; put 'data &outds (keep=groupuri groupname groupdesc);'; put 'length groupuri groupname groupdesc group_or_role $256;'; put 'call missing(of _all_);'; put 'i+1;'; put 'do while'; put '(metadata_getnobj("omsobj:IdentityGroup?@Id contains ''.''",i,groupuri)>0);'; put 'rc=metadata_getattr(groupuri, "Name", groupname);'; put 'rc=metadata_getattr(groupuri, "Desc", groupdesc);'; put 'rc=metadata_getattr(groupuri,"PublicType",group_or_role);'; put 'if Group_or_Role = ''UserGroup'' then output;'; put 'i+1;'; put 'end;'; put 'run;'; put '%end;'; put '%else %do;'; put 'data &outds (keep=groupuri groupname groupdesc);'; put 'length uri groupuri groupname groupdesc group_or_role $256;'; put 'call missing(of _all_);'; put 'rc=metadata_getnobj("omsobj:Person?@Name=''&user''",1,uri);'; put 'if rc<=0 then do;'; put 'putlog "%str(WARN)ING: rc=" rc "&user not found "'; put '", or there was an issue reading the repository.";'; put 'stop;'; put 'end;'; put 'a=1;'; put 'grpassn=metadata_getnasn(uri,"IdentityGroups",a,groupuri);'; put 'if grpassn in (-3,-4) then do;'; put 'putlog "%str(WARN)ING: No metadata groups found for &user";'; put 'output;'; put 'end;'; put 'else do while (grpassn > 0);'; put 'rc=metadata_getattr(groupuri, "Name", groupname);'; put 'rc=metadata_getattr(groupuri, "Desc", groupdesc);'; put 'a+1;'; put 'rc=metadata_getattr(groupuri,"PublicType",group_or_role);'; put 'if Group_or_Role = ''UserGroup'' then output;'; put 'grpassn=metadata_getnasn(uri,"IdentityGroups",a,groupuri);'; put 'end;'; put 'run;'; put '%end;'; put '%if &oldrepo ne &repo %then %do;'; put 'options metarepository=&oldrepo;'; put '%end;'; put '%mend mm_getGroups;'; put '%macro dc_getusergroups(user=,outds=mm_getgroups);'; put '%global dc_repo_users;'; put '%if &dc_repo_users= %then %let dc_repo_users=foundation;'; put '%mm_getgroups(user=&user,outds=&outds,repo=&dc_repo_users)'; put '%mend dc_getusergroups;'; put '%macro mpe_getgroups(user=,outds=);'; put '%if not %symexist(dc_repo_users) %then %let dc_repo_users=foundation;'; put '%dc_getusergroups(user=&user,outds=&outds)'; put 'data;'; put 'length groupname groupdesc $256;'; put 'set &dc_libref..mpe_groups;'; put 'where &dc_dttmtfmt. lt tx_to;'; put 'where also upcase(user_name)="%upcase(&user)";'; put 'groupname=group_name;'; put 'groupdesc=group_desc;'; put 'keep groupname groupdesc;'; put 'run;'; put 'data &outds;'; put 'set &syslast &outds(keep=groupname groupdesc);'; put 'run;'; put '%mend mpe_getgroups;'; put '* SAS Macros end;'; put '* SAS Includes start;'; put '* SAS Includes end;'; put '* Binary Files start;'; put '* Binary Files end;'; put '* ServiceInit start;'; put 'options noquotelenmax ps=max;'; put '/* create dummy macros to prevent stpbegin / stpend from corrupting sessions */'; put '%macro stpbegin();'; put '%put NOTE: the STPBEGIN macro should not be used for web apps!;'; put '%mend stpbegin;'; put '%macro stpend();'; put '%put NOTE: the STPEND macro should not be used for web apps!;'; put '%mend stpend;'; put '* ServiceInit end;'; put '* Service start;'; put '/**'; put '@file'; put '@brief Returns the list of previously approved / rejected items.'; put '@details History is taken from MPE_SUBMIT (where status_cd ne ''SUBMITTED'') and'; put 'filtered according to the groups in MPE_SECURITY (unless the user is in the'; put 'DC admin group).'; put '

SAS Macros

'; put '@li mpe_getvars.sas'; put '@li mpe_getgroups.sas'; put '@li mp_abort.sas'; put '@li mf_getuser.sas'; put '

Service Inputs

'; put '
BROWSERPARAMS
'; put 'The following variables MAY be provided from frontend (HIST can also be set'; put 'in MPE_CONFIG):'; put '@li HIST - number of records to return'; put '@li STARTROW - the starting row (default is 1)'; put '

Service Outputs

'; put '
FROMSAS
'; put 'This table is returned, starting from &STARTROW for &HIST rows (ordered'; put 'descending on SUBMITTED datetime)'; put '@li TABLE_ID'; put '@li BASE_TABLE'; put '@li SUBMITTED'; put '@li SUBMITTED_REASON_TXT'; put '@li SUBMITTER'; put '@li REVIEWED'; put '@li STATUS'; put '@li REVIEWED_ON_DTTM'; put '@li APPROVER'; put '
HISTPARAMS
'; put '@li HIST - rows returned'; put '@li STARTROW - starting row used'; put '@li NOBS - Number of observations (rows) available'; put '@version 9.3'; put '@author 4GL Apps Ltd'; put '@copyright 4GL Apps Ltd. This code may only be used within Data Controller'; put 'and may not be re-distributed or re-sold without the express permission of'; put '4GL Apps Ltd.'; put '**/'; put '%mpeinit()'; put '/* hard coded HIST value */'; put '%let hist=40;'; put '%let startrow=1;'; put '/* load parameters from frontend (HIST and STARTROW) */'; put 'data _null_;'; put 'set &DC_LIBREF..mpe_config(where=('; put 'var_scope="DC_REVIEW"'; put 'and var_name=''HISTORY_ROWS'''; put 'and &dc_dttmtfmt. lt tx_to'; put 'and var_active=1'; put '));'; put 'call symputx(''hist'',var_value,''G'');'; put 'putlog ''mpe_config: '' var_name "=" var_value;'; put 'run;'; put '/* load parameters (override HIST again if provided) */'; put '%mpe_getvars(BrowserParams, BrowserParams)'; put '/* determine users group membership */'; put '%mpe_getgroups(user=%mf_getuser(),outds=work.usergroups)'; put 'PROC FORMAT;'; put 'picture yymmddhhmmss other=''%0Y-%0m-%0d %0H:%0M:%0S'' (datatype=datetime);'; put 'RUN;'; put '/* check to see if the user is an admin, or has *ALL* access rights */'; put '%let authcheck=0;'; put 'proc sql noprint;'; put 'create table work.authcheck'; put 'as select *'; put 'from usergroups'; put 'where upcase(groupname)="%upcase(&mpeadmins)"'; put 'or upcase(groupname) in ('; put 'select upcase(sas_group) from &mpelib..mpe_security'; put 'where libref=''*ALL*'' and &dc_dttmtfmt. lt tx_to'; put ');'; put 'select count(*) into: authcheck from &syslast;'; put '%mp_abort(iftrue= (&syscc > 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc after auth check)'; put ')'; put '/* now get the previous &hist records from mpe_submit */'; put 'proc sql;'; put 'create view work.submits as'; put 'select distinct a.TABLE_ID'; put ',cats(a.base_lib,''.'',a.base_ds) as base_table'; put ',put(a.SUBMITTED_ON_DTTM,yymmddhhmmss.) as submitted'; put ',a.submitted_reason_txt'; put ',a.submitted_by_nm as submitter'; put ',put(a.REVIEWED_ON_DTTM,yymmddhhmmss.) as REVIEWED'; put ',a.submit_status_cd as status'; put ',a.reviewed_on_dttm'; put ',a.reviewed_by_nm as approver'; put 'from &mpelib..mpe_submit(where=(submit_status_cd ne ''SUBMITTED'')) a'; put '%macro gethistory();'; put '%if &authcheck=0 %then %do;'; put '/* filter for allowed items */'; put 'left join &mpelib..mpe_security(where=(&dc_dttmtfmt. lt tx_to)) b'; put 'on a.base_lib=b.libref'; put 'and (a.base_ds=b.dsn or b.dsn=''*ALL*'')'; put 'where upcase(b.SAS_GROUP) in (select upcase(groupname) from work.usergroups)'; put 'and b.access_level in (''VIEW'',''AUDIT'',''EDIT'',''APPROVE'')'; put '%end;'; put '%mend gethistory;'; put '%gethistory()'; put 'order by a.submitted_on_dttm desc;'; put '%mp_abort(iftrue= (&syscc > 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc after fetching submits)'; put ')'; put 'data work.fromsas;'; put 'set work.submits;'; put 'if _n_ ge &startrow;'; put 'n+1;'; put 'if n>&hist then stop;'; put 'drop n;'; put 'run;'; put 'proc sql noprint;'; put 'select count(*) into: nobs from work.submits;'; put 'data work.histparams;'; put 'hist=&hist;'; put 'startrow=&startrow;'; put 'nobs=&nobs;'; put 'run;'; put '%mp_abort(iftrue= (&syscc > 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc)'; put ')'; put '%webout(OPEN)'; put '%webout(OBJ,fromSAS)'; put '%webout(OBJ,histparams)'; put '%webout(CLOSE)'; put '%mpeterm()'; put '* Service end;'; run; %mm_createwebservice(path=&appLoc/&path, name=&service, code=sascode, server=&serverName, replace=yes) filename sascode clear; %let service=rejection; filename sascode temp lrecl=32767; data _null_; file sascode; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '/**'; put '@file mp_jsonout.sas'; put '@brief Writes JSON in SASjs format to a fileref'; put '@details This macro can be used to OPEN a JSON stream and send one or more'; put 'tables as arrays of rows, where each row can be an object or a nested array.'; put 'There are two engines available - DATASTEP or PROCJSON.'; put 'PROC JSON is fast but will produce errs like the ones below if'; put 'special chars are encountered.'; put '> (ERR)OR: Some code points did not transcode.'; put '> An object or array close is not valid at this point in the JSON text.'; put '> Date value out of range'; put 'If this happens, try running with ENGINE=DATASTEP.'; put 'The DATASTEP engine is used to handle special SAS missing numerics, and'; put 'can also convert entire datasets to formatted values. Output JSON is always'; put 'in UTF-8.'; put 'Usage:'; put 'filename tmp temp;'; put 'data class; set sashelp.class;run;'; put '%mp_jsonout(OPEN,jref=tmp)'; put '%mp_jsonout(OBJ,class,jref=tmp)'; put '%mp_jsonout(OBJ,class,dslabel=class2,jref=tmp,showmeta=Y)'; put '%mp_jsonout(CLOSE,jref=tmp)'; put 'data _null_;'; put 'infile tmp;'; put 'input;putlog _infile_;'; put 'run;'; put 'If you are building web apps with SAS then you are strongly encouraged to use'; put 'the mX_createwebservice macros in combination with the'; put '[sasjs adapter](https://github.com/sasjs/adapter).'; put 'For more information see https://sasjs.io'; put '@param [in] action Valid values:'; put '@li OPEN - opens the JSON'; put '@li OBJ - sends a table with each row as an object'; put '@li ARR - sends a table with each row in an array'; put '@li CLOSE - closes the JSON'; put '@param [in] ds The dataset to send. Must be a work table.'; put '@param [out] jref= (_webout) The fileref to which to send the JSON'; put '@param [out] dslabel= The name to give the table in the exported JSON'; put '@param [in] fmt= (Y) Whether to keep (Y) or strip (N) formats from the table'; put '@param [in] engine= (DATASTEP) Which engine to use to send the JSON. Options:'; put '@li PROCJSON (default)'; put '@li DATASTEP (more reliable when data has non standard characters)'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to JSON'; put '

Related Files

'; put '@li mp_ds2fmtds.sas'; put '@version 9.2'; put '@author Allan Bowe'; put '@source https://github.com/sasjs/core'; put '**/'; put '%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y'; put ',engine=DATASTEP'; put ',missing=NULL'; put ',showmeta=N'; put ',maxobs=MAX'; put ')/*/STORE SOURCE*/;'; put '%local tempds colinfo fmtds i numcols numobs stmt_obs lastobs optval'; put 'tmpds1 tmpds2 tmpds3 tmpds4;'; put '%let numcols=0;'; put '%if &maxobs ne MAX %then %let stmt_obs=%str(if _n_>&maxobs then stop;);'; put '%if &action=OPEN %then %do;'; put 'options nobomfile;'; put 'data _null_;file &jref encoding=''utf-8'' lrecl=200;'; put 'put ''{"PROCESSED_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''"'';'; put 'run;'; put '%end;'; put '%else %if (&action=ARR or &action=OBJ) %then %do;'; put '/* force variable names to always be uppercase in the JSON */'; put 'options validvarname=upcase;'; put '/* To avoid issues with _webout on EBI - such as encoding diffs and truncation'; put '(https://support.sas.com/kb/49/325.html) we use temporary files */'; put 'filename _sjs1 temp lrecl=200 ;'; put 'data _null_; file _sjs1 encoding=''utf-8'';'; put 'put ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":";'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjs1 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs1 clear;'; put '/* grab col defs */'; put 'proc contents noprint data=&ds'; put 'out=_data_(keep=name type length format formatl formatd varnum label);'; put 'run;'; put '%let colinfo=%scan(&syslast,2,.);'; put 'proc sort data=&colinfo;'; put 'by varnum;'; put 'run;'; put '/* move meta to mac vars */'; put 'data &colinfo;'; put 'if _n_=1 then call symputx(''numcols'',nobs,''l'');'; put 'set &colinfo end=last nobs=nobs;'; put 'name=upcase(name);'; put '/* fix formats */'; put 'if type=2 or type=6 then do;'; put 'typelong=''char'';'; put 'length fmt $49.;'; put 'if format='''' then fmt=cats(''$'',length,''.'');'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else fmt=cats(format,formatl,''.'');'; put 'end;'; put 'else do;'; put 'typelong=''num'';'; put 'if format='''' then fmt=''best.'';'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else if formatd=0 then fmt=cats(format,formatl,''.'');'; put 'else fmt=cats(format,formatl,''.'',formatd);'; put 'end;'; put '/* 32 char unique name */'; put 'newname=''sasjs''!!substr(cats(put(md5(name),$hex32.)),1,27);'; put 'call symputx(cats(''name'',_n_),name,''l'');'; put 'call symputx(cats(''newname'',_n_),newname,''l'');'; put 'call symputx(cats(''length'',_n_),length,''l'');'; put 'call symputx(cats(''fmt'',_n_),fmt,''l'');'; put 'call symputx(cats(''type'',_n_),type,''l'');'; put 'call symputx(cats(''typelong'',_n_),typelong,''l'');'; put 'call symputx(cats(''label'',_n_),coalescec(label,name),''l'');'; put '/* overwritten when fmt=Y and a custom format exists in catalog */'; put 'if typelong=''num'' then call symputx(cats(''fmtlen'',_n_),200,''l'');'; put 'else call symputx(cats(''fmtlen'',_n_),min(32767,ceil((length+10)*1.5)),''l'');'; put 'run;'; put '%let tempds=%substr(_%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql;'; put 'select count(*) into: lastobs from &ds;'; put '%if &maxobs ne MAX %then %let lastobs=%sysfunc(min(&lastobs,&maxobs));'; put '%if &engine=PROCJSON %then %do;'; put '%if &missing=STRING %then %do;'; put '%put &sysmacroname: Special Missings not supported in proc json.;'; put '%put &sysmacroname: Switching to DATASTEP engine;'; put '%goto datastep;'; put '%end;'; put 'data &tempds;'; put 'set &ds;'; put '&stmt_obs;'; put '%if &fmt=N %then format _numeric_ best32.;;'; put '/* PRETTY is necessary to avoid line truncation in large files */'; put 'filename _sjs2 temp lrecl=131068 encoding=''utf-8'';'; put 'proc json out=_sjs2 pretty'; put '%if &action=ARR %then nokeys ;'; put ';export &tempds / nosastags fmtnumeric;'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs2 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs2 clear;'; put '%end;'; put '%else %if &engine=DATASTEP %then %do;'; put '%datastep:'; put '%if %sysfunc(exist(&ds)) ne 1 & %sysfunc(exist(&ds,VIEW)) ne 1'; put '%then %do;'; put '%put &sysmacroname: &ds NOT FOUND!!!;'; put '%return;'; put '%end;'; put '%if &fmt=Y %then %do;'; put '/**'; put '* Extract format definitions'; put '* First, by getting library locations from dictionary.formats'; put '* Then, by exporting the width using proc format'; put '* Cannot use maxw from sashelp.vformat as not always populated'; put '* Cannot use fmtinfo() as not supported in all flavours'; put '*/'; put '%let tmpds1=%substr(fmtsum%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds2=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds3=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds4=%substr(col%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql noprint;'; put 'create table &tmpds1 as'; put 'select cats(libname,''.'',memname) as FMTCAT,'; put 'FMTNAME'; put 'from dictionary.formats'; put 'where fmttype=''F'' and libname is not null'; put 'and fmtname in (select format from &colinfo where format is not null)'; put 'order by 1;'; put 'create table &tmpds2('; put 'FMTNAME char(32),'; put 'LENGTH num'; put ');'; put '%local catlist cat fmtlist i;'; put 'select distinct fmtcat into: catlist separated by '' '' from &tmpds1;'; put '%do i=1 %to %sysfunc(countw(&catlist,%str( )));'; put '%let cat=%scan(&catlist,&i,%str( ));'; put 'proc sql;'; put 'select distinct fmtname into: fmtlist separated by '' '''; put 'from &tmpds1 where fmtcat="&cat";'; put 'proc format lib=&cat cntlout=&tmpds3(keep=fmtname length);'; put 'select &fmtlist;'; put 'run;'; put 'proc sql;'; put 'insert into &tmpds2 select distinct fmtname,length from &tmpds3;'; put '%end;'; put 'proc sql;'; put 'create table &tmpds4 as'; put 'select a.*, b.length as MAXW'; put 'from &colinfo a'; put 'left join &tmpds2 b'; put 'on cats(a.format)=cats(upcase(b.fmtname))'; put 'order by a.varnum;'; put 'data _null_;'; put 'set &tmpds4;'; put 'if not missing(maxw);'; put 'call symputx('; put 'cats(''fmtlen'',_n_),'; put '/* vars need extra padding due to JSON escaping of special chars */'; put 'min(32767,ceil((max(length,maxw)+10)*1.5))'; put ',''l'''; put ');'; put 'run;'; put '/* configure varlenchk - as we are explicitly shortening the variables */'; put '%let optval=%sysfunc(getoption(varlenchk));'; put 'options varlenchk=NOWARN;'; put 'data _data_(compress=char);'; put '/* shorten the new vars */'; put 'length'; put '%do i=1 %to &numcols;'; put '&&name&i $&&fmtlen&i'; put '%end;'; put ';'; put '/* rename on entry */'; put 'set &ds(rename=('; put '%do i=1 %to &numcols;'; put '&&name&i=&&newname&i'; put '%end;'; put '));'; put '&stmt_obs;'; put 'drop'; put '%do i=1 %to &numcols;'; put '&&newname&i'; put '%end;'; put ';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=num %then %do;'; put '&&name&i=cats(put(&&newname&i,&&fmt&i));'; put '%end;'; put '%else %do;'; put '&&name&i=put(&&newname&i,&&fmt&i);'; put '%end;'; put '%end;'; put 'if _error_ then do;'; put 'call symputx(''syscc'',1012);'; put 'stop;'; put 'end;'; put 'run;'; put '%let fmtds=&syslast;'; put 'options varlenchk=&optval;'; put '%end;'; put 'proc format; /* credit yabwon for special null removal */'; put 'value bart (default=40)'; put '%if &missing=NULL %then %do;'; put '._ - .z = null'; put '%end;'; put '%else %do;'; put '._ = [quote()]'; put '. = null'; put '.a - .z = [quote()]'; put '%end;'; put 'other = [best.];'; put 'data &tempds;'; put 'attrib _all_ label='''';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'length &&name&i $&&fmtlen&i...;'; put 'format &&name&i $&&fmtlen&i...;'; put '%end;'; put '%end;'; put '%if &fmt=Y %then %do;'; put 'set &fmtds;'; put '%end;'; put '%else %do;'; put 'set &ds;'; put '%end;'; put '&stmt_obs;'; put 'format _numeric_ bart.;'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'if findc(&&name&i,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put '&&name&i=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,&&name&i)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else &&name&i=quote(cats(&&name&i));'; put '%end;'; put '%end;'; put 'run;'; put 'filename _sjs3 temp lrecl=131068 ;'; put 'data _null_;'; put 'file _sjs3 encoding=''utf-8'';'; put 'if _n_=1 then put "[";'; put 'set &tempds;'; put 'if _n_>1 then put "," @; put'; put '%if &action=ARR %then "[" ; %else "{" ;'; put '%do i=1 %to &numcols;'; put '%if &i>1 %then "," ;'; put '%if &action=OBJ %then """&&name&i"":" ;'; put '"&&name&i"n /* name literal for reserved variable names */'; put '%end;'; put '%if &action=ARR %then "]" ; %else "}" ; ;'; put '/* close out the table */'; put 'data _null_;'; put 'file _sjs3 mod encoding=''utf-8'';'; put 'put '']'';'; put 'run;'; put 'data _null_;'; put 'infile _sjs3 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs3 clear;'; put '%end;'; put 'proc sql;'; put 'drop table &colinfo, &tempds;'; put '%if %substr(&showmeta,1,1)=Y %then %do;'; put 'filename _sjs4 temp lrecl=131068 encoding=''utf-8'';'; put 'data _null_;'; put 'file _sjs4;'; put 'length label $350;'; put 'put ", ""$%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":{""vars"":{";'; put 'do i=1 to &numcols;'; put 'name=quote(trim(symget(cats(''name'',i))));'; put 'format=quote(trim(symget(cats(''fmt'',i))));'; put 'label=quote(prxchange(''s/\\/\\\\/'',-1,trim(symget(cats(''label'',i)))));'; put 'length=quote(trim(symget(cats(''length'',i))));'; put 'type=quote(trim(symget(cats(''typelong'',i))));'; put 'if i>1 then put "," @@;'; put 'put name '':{"format":'' format '',"label":'' label'; put ''',"length":'' length '',"type":'' type ''}'';'; put 'end;'; put 'put ''}}'';'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs4 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs4 clear;'; put '%end;'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put 'data _null_; file &jref encoding=''utf-8'' mod ;'; put 'put "}";'; put 'run;'; put '%end;'; put '%mend mp_jsonout;'; put '/**'; put '@file mm_webout.sas'; put '@brief Send data to/from SAS Stored Processes'; put '@details This macro should be added to the start of each Stored Process,'; put '**immediately** followed by a call to:'; put '%mm_webout(FETCH)'; put 'This will read all the input data and create same-named SAS datasets in the'; put 'WORK library. You can then insert your code, and send data back using the'; put 'following syntax:'; put 'data some datasets; * make some data ;'; put 'retain some columns;'; put 'run;'; put '%mm_webout(OPEN)'; put '%mm_webout(ARR,some) * Array format, fast, suitable for large tables ;'; put '%mm_webout(OBJ,datasets) * Object format, easier to work with ;'; put 'Finally, wrap everything up send some helpful system variables too'; put '%mm_webout(CLOSE)'; put '@param [in] action Either FETCH, OPEN, ARR, OBJ or CLOSE'; put '@param [in] ds The dataset to send back to the frontend'; put '@param [out] dslabel= Value to use instead of table name for sending to JSON'; put '@param [in] fmt= (N) Setting Y converts all vars to their formatted values'; put '@param [out] fref= (_webout) The fileref to which to write the JSON'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] workobs= (0) When set to a positive integer, will create a new'; put 'output object (WORK) which contains this number of observations from all'; put 'tables in the WORK library.'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to output JSON'; put '

SAS Macros

'; put '@li mp_jsonout.sas'; put '

Related Macros

'; put '@li ms_webout.sas'; put '@li mv_webout.sas'; put '@version 9.3'; put '@author Allan Bowe'; put '**/'; put '%macro mm_webout(action,ds,dslabel=,fref=_webout,fmt=N,missing=NULL'; put ',showmeta=N,maxobs=MAX,workobs=0'; put ');'; put '%global _webin_file_count _webin_fileref1 _webin_name1 _program _debug'; put 'sasjs_tables;'; put '%local i tempds jsonengine;'; put '/* see https://github.com/sasjs/core/issues/41 */'; put '%if "%upcase(&SYSENCODING)" ne "UTF-8" %then %let jsonengine=PROCJSON;'; put '%else %let jsonengine=DATASTEP;'; put '%if &action=FETCH %then %do;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'options mprint notes mprintnest;'; put '%end;'; put '%let _webin_file_count=%eval(&_webin_file_count+0);'; put '/* now read in the data */'; put '%do i=1 %to &_webin_file_count;'; put '%if &_webin_file_count=1 %then %do;'; put '%let _webin_fileref1=&_webin_fileref;'; put '%let _webin_name1=&_webin_name;'; put '%end;'; put 'data _null_;'; put 'infile &&_webin_fileref&i termstr=crlf;'; put 'input;'; put 'call symputx(''input_statement'',_infile_);'; put 'putlog "&&_webin_name&i input statement: " _infile_;'; put 'stop;'; put 'data &&_webin_name&i;'; put 'infile &&_webin_fileref&i firstobs=2 dsd termstr=crlf encoding=''utf-8'';'; put 'input &input_statement;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'if _n_<20 then putlog _infile_;'; put '%end;'; put 'run;'; put '%let sasjs_tables=&sasjs_tables &&_webin_name&i;'; put '%end;'; put '%end;'; put '%else %if &action=OPEN %then %do;'; put '/* fix encoding */'; put 'OPTIONS NOBOMFILE;'; put '/**'; put '* check xengine type to avoid the below err message:'; put '* > Function is only valid for filerefs using the CACHE access method.'; put '*/'; put 'data _null_;'; put 'set sashelp.vextfl(where=(fileref="_WEBOUT"));'; put 'if xengine=''STREAM'' then do;'; put 'rc=stpsrv_header(''Content-type'',"text/html; encoding=utf-8");'; put 'end;'; put 'run;'; put '/* setup json */'; put 'data _null_;file &fref encoding=''utf-8'';'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutBEGIN<<'';'; put '%end;'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'run;'; put '%end;'; put '%else %if &action=ARR or &action=OBJ %then %do;'; put '%mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt,jref=&fref'; put ',engine=&jsonengine,missing=&missing,showmeta=&showmeta,maxobs=&maxobs'; put ')'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put '/* To avoid issues with _webout on EBI we use a temporary file */'; put 'filename _sjsref temp lrecl=131068;'; put '%if %str(&workobs) > 0 %then %do;'; put '/* if debug mode, send back first XX records of each work table also */'; put 'data;run;%let tempds=%scan(&syslast,2,.);'; put 'ods output Members=&tempds;'; put 'proc datasets library=WORK memtype=data;'; put '%local wtcnt;%let wtcnt=0;'; put 'data _null_;'; put 'set &tempds;'; put 'if not (upcase(name) =:"DATA"); /* ignore temp datasets */'; put 'i+1;'; put 'call symputx(cats(''wt'',i),name,''l'');'; put 'call symputx(''wtcnt'',i,''l'');'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put ",""WORK"":{";'; put '%do i=1 %to &wtcnt;'; put '%let wt=&&wt&i;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'dsid=open("WORK.&wt",''is'');'; put 'nlobs=attrn(dsid,''NLOBS'');'; put 'nvars=attrn(dsid,''NVARS'');'; put 'rc=close(dsid);'; put 'if &i>1 then put '',''@;'; put 'put " ""&wt"" : {";'; put 'put ''"nlobs":'' nlobs;'; put 'put '',"nvars":'' nvars;'; put '%mp_jsonout(OBJ,&wt,jref=_sjsref,dslabel=first10rows,showmeta=Y'; put ',maxobs=&workobs'; put ')'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put '%end;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put 'run;'; put '%end;'; put '/* close off json */'; put 'data _null_;file _sjsref mod encoding=''utf-8'';'; put 'length SYSPROCESSNAME syserrortext syswarningtext autoexec $512;'; put 'put ",""_DEBUG"" : ""&_debug"" ";'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'autoexec=quote(urlencode(trim(getoption(''autoexec''))));'; put 'put '',"AUTOEXEC" : '' autoexec;'; put 'put ",""MF_GETUSER"" : ""%mf_getuser()"" ";'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'put ",""SYSENCODING"" : ""&sysencoding"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSPROCESSID"" : ""&SYSPROCESSID"" ";'; put 'put ",""SYSPROCESSMODE"" : ""&SYSPROCESSMODE"" ";'; put 'SYSPROCESSNAME=quote(urlencode(cats(SYSPROCESSNAME)));'; put 'put ",""SYSPROCESSNAME"" : " SYSPROCESSNAME;'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put '',"SYSWARNINGTEXT" : '' syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'length memsize $32;'; put 'memsize="%sysfunc(INPUTN(%sysfunc(getoption(memsize)), best.),sizekmg.)";'; put 'memsize=quote(cats(memsize));'; put 'put '',"MEMSIZE" : '' memsize;'; put 'put "}" @;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutEND<<'';'; put '%end;'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjsref lrecl=1 recfm=n;'; put 'file &fref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjsref clear;'; put '%end;'; put '%mend mm_webout;'; put '%macro webout(action,ds,dslabel=,fmt=,missing=NULL,showmeta=NO,maxobs=MAX);'; put '%mm_webout(&action,ds=&ds,dslabel=&dslabel,fmt=&fmt'; put ',missing=&missing'; put ',showmeta=&showmeta'; put ',maxobs=&maxobs'; put ') %mend;'; put '/* provide additional debug info */'; put '%global _program;'; put '%put &=syscc;'; put '%put user=%mf_getuser();'; put '%put pgm=&_program;'; put '%put timestamp=%sysfunc(datetime(),datetime19.);'; put '* Service Variables start;'; put '* Service Variables end;'; put '* SAS Macros start;'; put '%macro mf_getapploc(pgm);'; put '%if "&pgm"="" %then %do;'; put '%if %symexist(_program) %then %let pgm=&_program;'; put '%else %do;'; put '%put &sysmacroname: No value provided and no _program variable available;'; put '%return;'; put '%end;'; put '%end;'; put '%local root;'; put '/**'; put '* First check we are not in the tests/macros folder (which has no subfolders)'; put '* or specifically in the testsetup or testteardown services'; put '*/'; put '%if %index(&pgm,/tests/macros/)'; put 'or %index(&pgm,/tests/testsetup)'; put 'or %index(&pgm,/tests/testteardown)'; put '%then %do;'; put '%let root=%substr(&pgm,1,%index(&pgm,/tests)-1);'; put '&root'; put '%return;'; put '%end;'; put '/**'; put '* Next, move up two levels to avoid matches on subfolder or service name'; put '*/'; put '%let root=%substr(&pgm,1,%length(&pgm)-%length(%scan(&pgm,-1,/))-1);'; put '%let root=%substr(&root,1,%length(&root)-%length(%scan(&root,-1,/))-1);'; put '%if %index(&root,/tests/) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/tests/)-1);'; put '%end;'; put '%else %if %index(&root,/services) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/services)-1);'; put '%end;'; put '%else %if %index(&root,/jobs) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/jobs)-1);'; put '%end;'; put '%else %put &sysmacroname: Could not find an app location from &pgm;'; put '&root'; put '%mend mf_getapploc ;'; put '%macro mf_getuniquefileref(prefix=_,maxtries=1000,lrecl=32767);'; put '%local rc fname;'; put '%if &prefix=0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%end;'; put '%else %do;'; put '%local x len;'; put '%let len=%eval(8-%length(&prefix));'; put '%let x=0;'; put '%do x=0 %to &maxtries;'; put '%let fname=&prefix%substr(%sysfunc(ranuni(0)),3,&len);'; put '%if %sysfunc(fileref(&fname)) > 0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%return;'; put '%end;'; put '%end;'; put '%put unable to find available fileref after &maxtries attempts;'; put '%end;'; put '%mend mf_getuniquefileref;'; put '%macro mp_abort(mac=mp_abort.sas, type=, msg=, iftrue=%str(1=1)'; put ', errds=work.mp_abort_errds'; put ', mode=REGULAR'; put ')/*/STORE SOURCE*/;'; put '%global sysprocessmode sysprocessname sasjs_stpsrv_header_loc sasjsprocessmode;'; put '%local fref fid i;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%put NOTE: /// mp_abort macro executing //;'; put '%if %length(&mac)>0 %then %put NOTE- called by &mac;'; put '%put NOTE - &msg;'; put '%if %symexist(_SYSINCLUDEFILEDEVICE)'; put '/* abort cancel FILE does not restart outside the INCLUDE on Viya 3.5 */'; put 'and %superq(SYSPROCESSNAME) ne %str(Compute Server)'; put '%then %do;'; put '%if "*&_SYSINCLUDEFILEDEVICE*" ne "**" %then %do;'; put 'data &errds;'; put 'iftrue=''1=1'';'; put 'length mac $100 msg $5000;'; put 'mac=symget(''mac'');'; put 'msg=symget(''msg'');'; put 'run;'; put 'data _null_;'; put 'abort cancel FILE;'; put 'run;'; put '%return;'; put '%end;'; put '%end;'; put '/* Web App Context */'; put '%if %symexist(_PROGRAM)'; put 'or %superq(SYSPROCESSNAME) = %str(Compute Server)'; put 'or &mode=INCLUDE'; put '%then %do;'; put 'options obs=max replace mprint;'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5"'; put '%then %do;'; put 'options nosyntaxcheck;'; put '%end;'; put '%if &mode=INCLUDE %then %do;'; put '%if %sysfunc(exist(&errds))=1 %then %do;'; put 'data _null_;'; put 'set &errds;'; put 'call symputx(''iftrue'',iftrue,''l'');'; put 'call symputx(''mac'',mac,''l'');'; put 'call symputx(''msg'',msg,''l'');'; put 'putlog (_all_)(=);'; put 'run;'; put '%if (&iftrue)=0 %then %return;'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: No include errors found;'; put '%return;'; put '%end;'; put '%end;'; put '/* extract log errs / warns, if exist */'; put '%local logloc logline;'; put '%global logmsg; /* capture global messages */'; put '%if %symexist(SYSPRINTTOLOG) %then %let logloc=&SYSPRINTTOLOG;'; put '%else %let logloc=%qsysfunc(getoption(LOG));'; put 'proc printto log=log;run;'; put '%let logline=0;'; put '%if %length(&logloc)>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input; putlog _infile_;'; put 'i=1;'; put 'retain logonce 0;'; put 'if ('; put '_infile_=:"%str(WARN)ING" or _infile_=:"%str(ERR)OR"'; put ') and logonce=0 then'; put 'do;'; put 'call symputx(''logline'',_n_);'; put 'logonce+1;'; put 'end;'; put 'run;'; put '/* capture log including lines BEFORE the err */'; put '%if &logline>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input;'; put 'i=1;'; put 'stoploop=0;'; put 'if _n_ ge &logline-15 and stoploop=0 then do until (i>22);'; put 'call symputx(''logmsg'',catx(''\n'',symget(''logmsg''),_infile_));'; put 'input;'; put 'i+1;'; put 'stoploop=1;'; put 'end;'; put 'if stoploop=1 then stop;'; put 'run;'; put '%end;'; put '%end;'; put '%if %symexist(SYS_JES_JOB_URI) %then %do;'; put '/* setup webout for Viya */'; put 'options nobomfile;'; put '%if "X&SYS_JES_JOB_URI.X"="XX" %then %do;'; put 'filename _webout temp lrecl=999999 mod;'; put '%end;'; put '%else %do;'; put 'filename _webout filesrvc parenturi="&SYS_JES_JOB_URI"'; put 'name="_webout.json" lrecl=999999 mod;'; put '%end;'; put '%end;'; put '%else %if %sysfunc(filename(fref,&sasjs_stpsrv_header_loc))=0 %then %do;'; put 'options nobomfile;'; put '/* set up http header for SASjs Server */'; put '%let fid=%sysfunc(fopen(&fref,A));'; put '%if &fid=0 %then %do;'; put '%put %str(ERR)OR: %sysfunc(sysmsg());'; put '%return;'; put '%end;'; put '%let rc=%sysfunc(fput(&fid,%str(Content-Type: application/json)));'; put '%let rc=%sysfunc(fwrite(&fid));'; put '%let rc=%sysfunc(fclose(&fid));'; put '%let rc=%sysfunc(filename(&fref));'; put '%end;'; put '/* send response in SASjs JSON format */'; put 'data _null_;'; put 'file _webout mod lrecl=32000 encoding=''utf-8'';'; put 'length msg syswarningtext syserrortext $32767 mode $10 ;'; put 'sasdatetime=datetime();'; put 'msg=symget(''msg'');'; put '%if &logline>0 %then %do;'; put 'msg=cats(msg,''\n\nLog Extract:\n'',symget(''logmsg''));'; put '%end;'; put '/* escape the escapes */'; put 'msg=tranwrd(msg,''\'',''\\'');'; put '/* escape the quotes */'; put 'msg=tranwrd(msg,''"'',''\"'');'; put '/* ditch the CRLFs as chrome complains */'; put 'msg=compress(msg,,''kw'');'; put '/* quote without quoting the quotes (which are escaped instead) */'; put 'msg=cats(''"'',msg,''"'');'; put 'if symexist(''_debug'') then debug=quote(trim(symget(''_debug'')));'; put 'else debug=''""'';'; put 'if symget(''sasjsprocessmode'')=''Stored Program'' then mode=''SASJS'';'; put 'if mode ne ''SASJS'' then put ''>>weboutBEGIN<<'';'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'put '',"sasjsAbort" : [{'';'; put 'put '' "MSG":'' msg ;'; put 'put '' ,"MAC": "'' "&mac" ''"}]'';'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'put '',"_DEBUG":'' debug ;'; put 'if symexist(''_metauser'') then do;'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put 'end;'; put 'if symexist(''SYS_JES_JOB_URI'') then do;'; put 'SYS_JES_JOB_URI=quote(trim(symget(''SYS_JES_JOB_URI'')));'; put 'put '',"SYS_JES_JOB_URI": '' SYS_JES_JOB_URI;'; put 'end;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put ",""SYSWARNINGTEXT"" : " syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'put "}" ;'; put 'if mode ne ''SASJS'' then put ''>>weboutEND<<'';'; put 'run;'; put '%put _all_;'; put '%if "&sysprocessmode " = "SAS Stored Process Server " %then %do;'; put 'data _null_;'; put 'putlog ''stpsrvset program err and syscc'';'; put 'rc=stpsrvset(''program error'', 0);'; put 'call symputx("syscc",0,"g");'; put 'run;'; put '%if &sysscp=WIN'; put 'and 1=0 /* deprecating this logic until we figure out a consistent abort */'; put 'and "%substr(%str(&sysvlong ),1,8)"="9.04.01M"'; put 'and "%substr(%str(&sysvlong ),9,1)">"5" %then %do;'; put '/* skip approach (below) does not work in windows m6+ envs */'; put 'endsas;'; put '%end;'; put '%else %do;'; put '/**'; put '* endsas kills 9.4m3 deployments by orphaning multibridges.'; put '* Abort variants are ungraceful (non zero return code)'; put '* This approach lets SAS run silently until the end :-)'; put '* Caution - fails when called within a %include within a macro'; put '* Use mp_include() to handle this.'; put '*/'; put 'filename skip temp;'; put 'data _null_;'; put 'file skip;'; put 'put ''%macro skip();'';'; put 'comment ''%mend skip; -> fix lint '';'; put 'put ''%macro skippy();'';'; put 'comment ''%mend skippy; -> fix lint '';'; put 'run;'; put '%inc skip;'; put '%end;'; put '%end;'; put '%else %if "&sysprocessmode " = "SAS Compute Server " %then %do;'; put '/* endsas kills the session making it harder to fetch results */'; put 'data _null_;'; put 'syswarningtext=symget(''syswarningtext'');'; put 'syserrortext=symget(''syserrortext'');'; put 'abort_msg=symget(''msg'');'; put 'syscc=symget(''syscc'');'; put 'sysuserid=symget(''sysuserid'');'; put 'iftrue=symget(''iftrue'');'; put 'put (_all_)(/=);'; put 'call symputx(''syscc'',0);'; put 'abort cancel nolist;'; put 'run;'; put '%end;'; put '%else %do;'; put '%abort cancel;'; put '%end;'; put '%end;'; put '%else %do;'; put '%put _all_;'; put '%abort cancel;'; put '%end;'; put '%mend mp_abort;'; put '/** @endcond */'; put '%macro mm_getstpcode('; put 'tree=/User Folders/sasdemo/somestp'; put ',name='; put ',outloc=0'; put ',outref=0'; put ',mDebug=1'; put ',showlog=NO'; put ');'; put '%local mD;'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing &sysmacroname..sas;'; put '%&mD.put _local_;'; put '%if %length(&name)>0 %then %let name=/&name;'; put '/* first, check if STP exists */'; put '%local tsuri;'; put '%let tsuri=stopifempty ;'; put 'data _null_;'; put 'format type uri tsuri value $200.;'; put 'call missing (of _all_);'; put 'path="&tree&name(StoredProcess)";'; put '/* first, find the STP ID */'; put 'if metadata_pathobj("",path,"StoredProcess",type,uri)>0 then do;'; put '/* get sourcecode */'; put 'cnt=1;'; put 'do while (metadata_getnasn(uri,"Notes",cnt,tsuri)>0);'; put 'rc=metadata_getattr(tsuri,"Name",value);'; put '&mD.put tsuri= value=;'; put 'if value="SourceCode" then do;'; put '/* found it! */'; put 'rc=metadata_getattr(tsuri,"Id",value);'; put 'call symputx(''tsuri'',value,''l'');'; put 'stop;'; put 'end;'; put 'cnt+1;'; put 'end;'; put 'end;'; put 'else put (_all_)(=);'; put 'run;'; put '%mp_abort(iftrue= (&tsuri=stopifempty)'; put ',mac=mm_getstpcode'; put ',msg=%str(&tree&name.(StoredProcess) not found!)'; put ')'; put '/**'; put '* Now we can extract the textstore'; put '*/'; put 'filename __getdoc temp lrecl=10000000;'; put 'proc metadata'; put 'in="$METAREPOSITORY'; put ''; put 'SAS1"'; put 'out=__getdoc ;'; put 'run;'; put '/* find the beginning of the text */'; put '%local start;'; put 'data _null_;'; put 'infile __getdoc lrecl=10000;'; put 'input;'; put 'start=index(_infile_,''StoredText="'');'; put 'if start then do;'; put 'call symputx("start",start+11);'; put '*putlog ''"'' _infile_ ''"'';'; put 'end;'; put 'stop;'; put '%local outeng;'; put '%if "&outloc"="0" %then %let outeng=TEMP;'; put '%else %let outeng="&outloc";'; put '%local fref;'; put '%if &outref=0 %then %let fref=%mf_getuniquefileref();'; put '%else %let fref=&outref;'; put '/* read the content, byte by byte, resolving escaped chars */'; put 'filename &fref &outeng lrecl=100000;'; put 'data _null_;'; put 'length filein 8 fileid 8;'; put 'filein = fopen("__getdoc","I",1,"B");'; put 'fileid = fopen("&fref","O",1,"B");'; put 'rec = "20"x;'; put 'length entity $6;'; put 'do while(fread(filein)=0);'; put 'x+1;'; put 'if x>&start then do;'; put 'rc = fget(filein,rec,1);'; put 'if rec=''"'' then leave;'; put 'else if rec="&" then do;'; put 'entity=rec;'; put 'do until (rec=";");'; put 'if fread(filein) ne 0 then goto getout;'; put 'rc = fget(filein,rec,1);'; put 'entity=cats(entity,rec);'; put 'end;'; put 'select (entity);'; put 'when (''&'' ) rec=''&'' ;'; put 'when (''<'' ) rec=''<'' ;'; put 'when (''>'' ) rec=''>'' ;'; put 'when (''''') rec="''" ;'; put 'when (''"'') rec=''"'' ;'; put 'when ('' '') rec=''0A''x;'; put 'when ('' '') rec=''0D''x;'; put 'when (''$'' ) rec=''$'' ;'; put 'when ('' '') rec=''09''x;'; put 'otherwise putlog "%str(WARN)ING: missing value for " entity=;'; put 'end;'; put 'rc =fput(fileid, substr(rec,1,1));'; put 'rc =fwrite(fileid);'; put 'end;'; put 'else do;'; put 'rc =fput(fileid,rec);'; put 'rc =fwrite(fileid);'; put 'end;'; put 'end;'; put 'end;'; put 'getout:'; put 'rc=fclose(filein);'; put 'rc=fclose(fileid);'; put 'run;'; put '%if &showlog=YES %then %do;'; put 'data _null_;'; put 'infile &fref lrecl=32767 end=last;'; put 'input;'; put 'if _n_=1 then putlog ''>>stpcodeBEGIN<<'';'; put 'putlog _infile_;'; put 'if last then putlog ''>>stpcodeEND<<'';'; put 'run;'; put '%end;'; put 'filename __getdoc clear;'; put '%if &outref=0 %then %do;'; put 'filename &fref clear;'; put '%end;'; put '%mend mm_getstpcode;'; put '%macro dc_getsettings();'; put '%global _program;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(syscc=&syscc on entry (&syswarningtext &syserrortext))'; put ')'; put '%if %length(&_PROGRAM)>1 %then %let root=&_program;'; put '%else %do;'; put '%global _metauser;'; put '%let _metauser=&sysuserid;'; put '/* to mimic a "real" _program we need to give a dummy role and stp name */'; put '%let root=/dummyRole/dummyName;'; put '%end;'; put '/* the DC precode is stored in the Admin folder in the root of'; put 'the project. Lets find that root. */'; put '%put &=root;'; put '%let root=%mf_getapploc();'; put '%put &=root;'; put '/* Now we know the root location we can retrieve the params */'; put '%let temploc=%sysfunc(pathname(work))/settings.txt;'; put '%mm_getstpcode(tree=&root/services/public'; put ',name=Data_Controller_Settings'; put ',outloc=&temploc'; put ')'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Unable to run getstpcode)'; put ')'; put 'filename _getsets "&temploc" lrecl=2000;'; put '/*'; put 'Do not use mp_include here - this puts a copy in every service, which creates'; put 'compilation problems when calling services from mp_include'; put '*/'; put '%inc _getsets/source2;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Problem running &sysmacroname (&syswarningtext &syserrortext))'; put ')'; put '%mend dc_getsettings;'; put '%macro mf_fmtdttm('; put ')/*/STORE SOURCE*/;'; put '%if "&sysver"="9.2" or "&sysver"="9.3"'; put 'or ("&sysver"="9.4" and "%substr(&SYSVLONG,9,1)" le "3")'; put 'or "%substr(&sysver,1,1)"="4"'; put 'or "%substr(&sysver,1,1)"="5"'; put '%then %do;DATETIME19.3%end;'; put '%else %do;E8601DT26.6%end;'; put '%mend mf_fmtdttm;'; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '%macro mp_init(prefix=SASJS'; put ')/*/STORE SOURCE*/;'; put '%if %symexist(SASJS_PREFIX) %then %return; /* only run once */'; put '%global'; put 'SASJS_PREFIX /* the ONLY hard-coded global macro variable in SASjs */'; put '&prefix._FUNCTIONS /* used in mcf_init() to track core function compilation */'; put '&prefix._INIT_NUM /* initialisation time as numeric */'; put '&prefix._INIT_DTTM /* initialisation time in E8601DT26.6 format */'; put '&prefix.WORK /* avoid typing %sysfunc(pathname(work)) every time */'; put ';'; put '%let sasjs_prefix=&prefix;'; put 'data _null_;'; put 'dttm=datetime();'; put 'call symputx("&sasjs_prefix._init_num",dttm,''g'');'; put 'call symputx("&sasjs_prefix._init_dttm",put(dttm,E8601DT26.6),''g'');'; put 'call symputx("&sasjs_prefix.work",pathname(''WORK''),''g'');'; put 'run;'; put 'options'; put 'compress=CHAR /* default is none so ensure we have something! */'; put 'datastmtchk=ALLKEYWORDS /* protection from overwriting input datasets */'; put 'errorcheck=STRICT /* catch errs in libname/filename statements */'; put 'fmterr /* ensure err when a format cannot be found */'; put 'mergenoby=%str(ERR)OR /* throw err when a merge has no BY variables */'; put 'missing=. /* changing this can cause hard to detect errs */'; put 'noquotelenmax /* avoid warnings for long strings */'; put 'noreplace /* avoid overwriting permanent datasets */'; put 'ps=max /* reduce log size slightly */'; put 'ls=max /* reduce log even more and avoid word truncation */'; put 'validmemname=COMPATIBLE /* avoid special characters etc in table names */'; put 'validvarname=V7 /* avoid special characters etc in variable names */'; put 'varinitchk=%str(ERR)OR /* avoid data mistakes from variable name typos */'; put 'varlenchk=%str(ERR)OR /* fail hard if truncation (data loss) can result */'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5" %then %do;'; put 'noautocorrect /* disallow misspelled procedure names */'; put 'dsoptions=note2err /* undocumented - convert bad NOTEs to ERRs */'; put '%end;'; put ';'; put '%mend mp_init;'; put '%macro mpeinit(fetch=YES);'; put '%global mpeinit'; put 'mpeadmins /* group with unrestricted Meditor access */'; put 'mpelocapprovals /* location for landing and staging files */'; put 'mpelib /* location of configuration tables for DC */'; put 'dc_repo_users /* location of user / group metadata */'; put 'dc_licence_key /* extracted in dc_getsettings */'; put 'dc_activation_key /* extracted in dc_getsettings */'; put 'dc_locale /* extracted in dc_getsettings */'; put 'dc_dttmtfmt /* can be overridden in dc_getsettings */'; put '_debug'; put ';'; put '%if &mpeinit=1 %then %return;'; put '%else %let mpeinit=1;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(Problem on service startup (&syswarningtext &syserrortext))'; put ')'; put '%mp_init()'; put '%if &fetch=YES %then %do;'; put '%webout(FETCH)'; put '%end;'; put '%global _CLIENTNAME;'; put '%mp_abort(iftrue= (&_CLIENTNAME=SAS Enterprise Guide)'; put ',mac=&_program..sas'; put ',msg=%str(Data Controller is a web app and should not be executed from EG)'; put ')'; put 'options urlencoding=utf8 nobomfile lrecl=32767;'; put '%let perf=%sysfunc(datetime());'; put '%put perfdiff: 0;'; put '%let dc_locale=SYSTEM; /* default if not set */'; put '/**'; put '* E8601DT26.6 has widest database support - but not all SAS flavours can'; put '* handle it. Override in the settings STP if needed.'; put '*/'; put 'data _null_;'; put 'dc_dttmtfmt=''"%sysfunc(datetime(),''!!"%mf_fmtdttm()"!!'')"dt'';'; put 'call symputx(''dc_dttmtfmt'',dc_dttmtfmt);'; put 'put dc_dttmtfmt=;'; put 'run;'; put '%put &=dc_dttmtfmt;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc prior to dc_getsettings)'; put ')'; put '%dc_getsettings()'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc after dc_getsettings)'; put ')'; put 'data _null_;'; put 'set &DC_LIBREF..mpe_config(where=('; put 'var_scope="DC"'; put 'and &dc_dttmtfmt lt tx_to'; put 'and var_active=1'; put '));'; put 'call symputx(var_name,var_value,''G'');'; put 'putlog var_name "=" var_value;'; put 'run;'; put '%let mpelib=&dc_libref;'; put '%let mpeadmins=&dc_admin_group;'; put '%let mpelocapprovals=&dc_staging_area;'; put '%let dc_repo_users=&dc_repo_users;'; put '%if &dc_locale ne SYSTEM %then %do;'; put 'options locale=&dc_locale;'; put '%end;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(Problem during compilation or with STP precode (&syswarningtext))'; put ')'; put '%mend mpeinit;'; put '%macro mf_mval(var);'; put '%if %symexist(&var) %then %do;'; put '%superq(&var)'; put '%end;'; put '%mend mf_mval;'; put '%macro mf_trimstr(basestr,trimstr);'; put '%local baselen trimlen trimval;'; put '/* return if basestr is shorter than trimstr (or 0) */'; put '%let baselen=%length(%superq(basestr));'; put '%let trimlen=%length(%superq(trimstr));'; put '%if &baselen < &trimlen or &baselen=0 %then %return;'; put '/* obtain the characters from the end of basestr */'; put '%let trimval=%qsubstr(%superq(basestr)'; put ',%length(%superq(basestr))-&trimlen+1'; put ',&trimlen);'; put '/* compare and if matching, chop it off! */'; put '%if %superq(basestr)=%superq(trimstr) %then %do;'; put '%return;'; put '%end;'; put '%else %if %superq(trimval)=%superq(trimstr) %then %do;'; put '%qsubstr(%superq(basestr),1,%length(%superq(basestr))-&trimlen)'; put '%end;'; put '%else %do;'; put '&basestr'; put '%end;'; put '%mend mf_trimstr;'; put '%macro mf_getplatform(switch'; put ')/*/STORE SOURCE*/;'; put '%local a b c;'; put '%if &switch.NONE=NONE %then %do;'; put '%if %symexist(sasjsprocessmode) %then %do;'; put '%if &sasjsprocessmode=Stored Program %then %do;'; put 'SASJS'; put '%return;'; put '%end;'; put '%end;'; put '%if %symexist(sysprocessmode) %then %do;'; put '%if "&sysprocessmode"="SAS Object Server"'; put 'or "&sysprocessmode"= "SAS Compute Server" %then %do;'; put 'SASVIYA'; put '%end;'; put '%else %if "&sysprocessmode"="SAS Stored Process Server"'; put 'or "&sysprocessmode"="SAS Workspace Server"'; put '%then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if %symexist(_metaport) or %symexist(_metauser) %then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if &switch=SASSTUDIO %then %do;'; put '/* return the version of SAS Studio else 0 */'; put '%if %mf_mval(_CLIENTAPP)=%str(SAS Studio) %then %do;'; put '%let a=%mf_mval(_CLIENTVERSION);'; put '%let b=%scan(&a,1,.);'; put '%if %eval(&b >2) %then %do;'; put '&b'; put '%end;'; put '%else 0;'; put '%end;'; put '%else 0;'; put '%end;'; put '%else %if &switch=VIYARESTAPI %then %do;'; put '%mf_trimstr(%sysfunc(getoption(servicesbaseurl)),/)'; put '%end;'; put '%mend mf_getplatform;'; put '%macro mpeterm();'; put '%local oldloc;'; put 'data _null_;'; put 'if symexist(''SYSPRINTTOLOG'') then oldloc=symget(''SYSPRINTTOLOG'');'; put 'else oldloc=getoption(''LOG'');'; put 'if subpad(oldloc,1,1) not in (''"'',"''",'' '') then oldloc=quote(cats(oldloc));'; put 'call symputx(''oldloc'',oldloc,''l'');'; put 'run;'; put '%if %length(&oldloc)>0 %then %do;'; put 'proc printto log=log;'; put 'run;'; put 'data _null_;'; put 'infile &oldloc;'; put 'input; putlog _infile_;'; put 'run;'; put '%end;'; put '%if %sysfunc(exist(&dc_libref..mpe_requests)) and %mf_getplatform() ne SASVIYA'; put '%then %do;'; put 'data ;'; put 'if 0 then set &dc_libref..mpe_requests;'; put 'request_dttm=%sysfunc(datetime());'; put 'request_user="%mf_getuser()";'; put 'request_service="%scan(&_program,-2,/)/%scan(&_program,-1,/)";'; put 'request_params='''';'; put 'output;stop;'; put 'proc append base=&dc_libref..mpe_requests data=&syslast force nowarn;'; put 'run;'; put '%end;'; put '%mend mpeterm;'; put '%macro mm_getGroups('; put 'user='; put ',outds=work.mm_getGroups'; put ',repo=foundation'; put ',mDebug=0'; put ')/*/STORE SOURCE*/;'; put '%local mD oldrepo;'; put '%let oldrepo=%sysfunc(getoption(metarepository));'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing mm_getGroups.sas;'; put '%&mD.put _local_;'; put '/* on some sites, user / group info is in a different metadata repo to the'; put 'default */'; put '%if &oldrepo ne &repo %then %do;'; put 'options metarepository=&repo;'; put '%end;'; put '%if %length(&user)=0 %then %do;'; put 'data &outds (keep=groupuri groupname groupdesc);'; put 'length groupuri groupname groupdesc group_or_role $256;'; put 'call missing(of _all_);'; put 'i+1;'; put 'do while'; put '(metadata_getnobj("omsobj:IdentityGroup?@Id contains ''.''",i,groupuri)>0);'; put 'rc=metadata_getattr(groupuri, "Name", groupname);'; put 'rc=metadata_getattr(groupuri, "Desc", groupdesc);'; put 'rc=metadata_getattr(groupuri,"PublicType",group_or_role);'; put 'if Group_or_Role = ''UserGroup'' then output;'; put 'i+1;'; put 'end;'; put 'run;'; put '%end;'; put '%else %do;'; put 'data &outds (keep=groupuri groupname groupdesc);'; put 'length uri groupuri groupname groupdesc group_or_role $256;'; put 'call missing(of _all_);'; put 'rc=metadata_getnobj("omsobj:Person?@Name=''&user''",1,uri);'; put 'if rc<=0 then do;'; put 'putlog "%str(WARN)ING: rc=" rc "&user not found "'; put '", or there was an issue reading the repository.";'; put 'stop;'; put 'end;'; put 'a=1;'; put 'grpassn=metadata_getnasn(uri,"IdentityGroups",a,groupuri);'; put 'if grpassn in (-3,-4) then do;'; put 'putlog "%str(WARN)ING: No metadata groups found for &user";'; put 'output;'; put 'end;'; put 'else do while (grpassn > 0);'; put 'rc=metadata_getattr(groupuri, "Name", groupname);'; put 'rc=metadata_getattr(groupuri, "Desc", groupdesc);'; put 'a+1;'; put 'rc=metadata_getattr(groupuri,"PublicType",group_or_role);'; put 'if Group_or_Role = ''UserGroup'' then output;'; put 'grpassn=metadata_getnasn(uri,"IdentityGroups",a,groupuri);'; put 'end;'; put 'run;'; put '%end;'; put '%if &oldrepo ne &repo %then %do;'; put 'options metarepository=&oldrepo;'; put '%end;'; put '%mend mm_getGroups;'; put '%macro dc_getusergroups(user=,outds=mm_getgroups);'; put '%global dc_repo_users;'; put '%if &dc_repo_users= %then %let dc_repo_users=foundation;'; put '%mm_getgroups(user=&user,outds=&outds,repo=&dc_repo_users)'; put '%mend dc_getusergroups;'; put '%macro mpe_getgroups(user=,outds=);'; put '%if not %symexist(dc_repo_users) %then %let dc_repo_users=foundation;'; put '%dc_getusergroups(user=&user,outds=&outds)'; put 'data;'; put 'length groupname groupdesc $256;'; put 'set &dc_libref..mpe_groups;'; put 'where &dc_dttmtfmt. lt tx_to;'; put 'where also upcase(user_name)="%upcase(&user)";'; put 'groupname=group_name;'; put 'groupdesc=group_desc;'; put 'keep groupname groupdesc;'; put 'run;'; put 'data &outds;'; put 'set &syslast &outds(keep=groupname groupdesc);'; put 'run;'; put '%mend mpe_getgroups;'; put '%macro mf_getuniquename(prefix=MC);'; put '&prefix.%substr(%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32-%length(&prefix))'; put '%mend mf_getuniquename;'; put '%macro mf_abort(mac=mf_abort.sas, msg=, iftrue=%str(1=1)'; put ')/des=''ungraceful abort'' /*STORE SOURCE*/;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%put NOTE: /// mf_abort macro executing //;'; put '%if %length(&mac)>0 %then %put NOTE- called by &mac;'; put '%put NOTE - &msg;'; put '%abort;'; put '%mend mf_abort;'; put '/** @endcond */'; put '%macro mf_verifymacvars('; put 'verifyVars /* list of macro variable NAMES */'; put ',makeUpcase=NO /* set to YES to make all the variable VALUES uppercase */'; put ',mAbort=SOFT'; put ')/*/STORE SOURCE*/;'; put '%local verifyIterator verifyVar abortmsg;'; put '%do verifyIterator=1 %to %sysfunc(countw(&verifyVars,%str( )));'; put '%let verifyVar=%qscan(&verifyVars,&verifyIterator,%str( ));'; put '%if not %symexist(&verifyvar) %then %do;'; put '%let abortmsg= Variable &verifyVar is MISSING;'; put '%goto exit_err;'; put '%end;'; put '%if %length(%trim(&&&verifyVar))=0 %then %do;'; put '%let abortmsg= Variable &verifyVar is EMPTY;'; put '%goto exit_err;'; put '%end;'; put '%if &makeupcase=YES %then %do;'; put '%let &verifyVar=%upcase(&&&verifyvar);'; put '%end;'; put '%end;'; put '%goto exit_success;'; put '%exit_err:'; put '%put &abortmsg;'; put '%mf_abort(iftrue=(&mabort ne SOFT),'; put 'mac=mf_verifymacvars,'; put 'msg=%str(&abortmsg)'; put ')'; put '0'; put '%return;'; put '%exit_success:'; put '1'; put '%mend mf_verifymacvars;'; put '%macro mpe_accesscheck('; put 'base_table'; put ',outds=med_accesscheck /* WORK table to contain access details */'; put ',user= /* metadata user to check for */'; put ',access_level=APPROVE'; put ',cntl_lib_var=MPELIB'; put ');'; put '%if &user= %then %let user=%mf_getuser();'; put '%mp_abort('; put 'iftrue=(%index(&outds,.)>0 and %upcase(%scan(&outds,1,.)) ne WORK)'; put ',mac=mpe_accesscheck'; put ',msg=%str(outds should be a WORK table)'; put ')'; put '%mp_abort('; put 'iftrue=(%mf_verifymacvars(base_table user access_level)=0)'; put ',mac=mpe_accesscheck'; put ',msg=%str(Missing base_table/user access_level variables)'; put ')'; put '/* make unique temp table vars */'; put '%local tempds1 tempds2;'; put '%let tempds1=%mf_getuniquename(prefix=usergroups);'; put '%let tempds2=%mf_getuniquename(prefix=tablegroups);'; put '/* get list of user groups */'; put '%mpe_getgroups(user=&user,outds=&tempds1)'; put '/* get list of groups with access for that table */'; put 'proc sql;'; put 'create table &tempds2 as'; put 'select distinct sas_group'; put 'from &&&cntl_lib_var...mpe_security'; put 'where &dc_dttmtfmt. lt tx_to'; put 'and access_level="&access_level"'; put 'and ('; put '(libref="%scan(&base_table,1,.)" and upcase(dsn)="%scan(&base_table,2,.)")'; put 'or (libref="%scan(&base_table,1,.)" and dsn="*ALL*")'; put 'or (libref="*ALL*")'; put ');'; put '%if &_debug ge 131 %then %do;'; put 'data _null_;'; put 'set &tempds1;'; put 'putlog (_all_)(=);'; put 'run;'; put 'data _null_;'; put 'set &tempds2;'; put 'putlog (_all_)(=);'; put 'run;'; put '%end;'; put 'proc sql;'; put 'create table &outds as'; put 'select * from &tempds1'; put 'where groupname="&mpeadmins"'; put 'or groupname in (select * from &tempds2);'; put '%put &sysmacroname: base_table=&base_table;'; put '%put &sysmacroname: access_level=&access_level;'; put '%mend mpe_accesscheck;'; put '%macro mf_getattrn('; put 'libds'; put ',attr'; put ')/*/STORE SOURCE*/;'; put '%local dsid rc;'; put '%let dsid=%sysfunc(open(&libds,is));'; put '%if &dsid = 0 %then %do;'; put '%put %str(WARN)ING: Cannot open %trim(&libds), system message below;'; put '%put %sysfunc(sysmsg());'; put '-1'; put '%end;'; put '%else %do;'; put '%sysfunc(attrn(&dsid,&attr))'; put '%let rc=%sysfunc(close(&dsid));'; put '%end;'; put '%mend mf_getattrn;'; put '%macro mf_existds(libds'; put ')/*/STORE SOURCE*/;'; put '%if %sysfunc(exist(&libds)) ne 1 & %sysfunc(exist(&libds,VIEW)) ne 1 %then 0;'; put '%else 1;'; put '%mend mf_existds;'; put '%macro mpe_alerts(alert_event='; put ', alert_lib='; put ', alert_ds='; put ', dsid='; put ');'; put '/* exit if not configured */'; put '%global DC_EMAIL_ALERTS;'; put '%if &DC_EMAIL_ALERTS ne YES %then %do;'; put '%put DCNOTE: Email alerts are not configured;'; put '%put DCNOTE: (dc_email_alerts=&dc_email_alerts in &mpelib..mpe_config);'; put '%return;'; put '%end;'; put '%let alert_event=%upcase(&alert_event);'; put '%let alert_lib=%upcase(&alert_lib);'; put '%let alert_ds=%upcase(&alert_ds);'; put '%let from_user=%mf_getuser();'; put '/* get users TO which the email should be sent */'; put 'proc sql noprint;'; put 'create table work.users as select distinct a.alert_user,'; put 'b.user_displayname,'; put 'b.user_email'; put 'from &mpelib..mpe_alerts'; put '(where=(&dc_dttmtfmt. lt tx_to)) a'; put 'left join &mpelib..mpe_emails'; put '(where=(&dc_dttmtfmt. lt tx_to)) b'; put 'on upcase(trim(a.alert_user))=upcase(trim(b.user_name))'; put 'where a.alert_event in ("&alert_event","*ALL*")'; put 'and a.alert_lib in ("&alert_lib","*ALL*")'; put 'and a.alert_ds in ("&alert_ds","*ALL*");'; put '/* ensure the submitter is included on the email */'; put '%local isThere userdisp user_eml;'; put '%let isThere=0;'; put 'select count(*) into: isThere from &syslast where alert_user="&from_user";'; put '%if &isThere=0 %then %do;'; put 'select user_displayname, user_email'; put 'into: userdisp trimmed, :user_eml trimmed'; put 'from &mpelib..mpe_emails'; put 'where &dc_dttmtfmt. lt tx_to'; put 'and user_name="&from_user";'; put 'insert into work.users'; put 'set alert_user="&from_user"'; put ',user_displayname="&userdisp"'; put ',user_email="&user_eml";'; put '%end;'; put '/* if no email / displayname is provided, then extract from metadata */'; put 'data work.emails;'; put 'set work.users;'; put 'length emailuri uri text $256; call missing(emailuri,uri); drop emailuri uri;'; put '/* get displayname */'; put 'text=cats("omsobj:Person?@Name=''",alert_user,"''");'; put 'if metadata_getnobj(text,1,uri)<=0 then do;'; put 'putlog "DCWARN: &from_user not found";'; put 'return;'; put 'end;'; put 'else if user_displayname = '''' then do;'; put 'if metadata_getattr(uri,''DisplayName'',user_displayname)<0 then do;'; put 'putlog ''DCWARN: strange err, no displayname attribute of user URI'';'; put 'end;'; put 'end;'; put 'if index(user_email,''@'') then return;'; put '/* get email from metadata if not in input table */'; put 'if metadata_getnasn(uri,"EmailAddresses",1,emailuri)<=0 then do;'; put 'putlog "DCWARN: " alert_user " has no emails in MPE_EMAILS or metadata!";'; put 'if metadata_getattr(emailuri,"Address",user_email)<0 then do;'; put 'putlog ''DCWARN: Unexpected error! Valid emailURI but no email. Weird.'';'; put 'end;'; put 'end;'; put '/* only keep valid emails */'; put 'if index(user_email,''@'') ;'; put '/* dump contents for debugging */'; put 'if _n_<21 then putlog (_all_)(=);'; put 'run;'; put '%local emails;'; put 'proc sql noprint;'; put 'select quote(trim(user_email)) into: emails separated by '' '' from work.emails;'; put '/* exit if nobody to email */'; put '%if %mf_getattrn(emails,NLOBS)=0 %then %do;'; put '%put NOTE: No alerts configured (mpe_alerts.sas);'; put '%return;'; put '%end;'; put '/* display email options */'; put 'data _null_;'; put 'set sashelp.voption(where=(group=''EMAIL''));'; put 'put optname ''='' setting;'; put 'run;'; put 'filename __out email (&emails)'; put 'subject="Table &alert_lib..&alert_ds has been &alert_event";'; put '%local SUBMITTED_TXT;'; put '%if &alert_event=SUBMITTED %then %do;'; put 'data _null_;'; put 'set &mpelib..mpe_submit;'; put 'where table_id="&dsid" and submit_status_cd=''SUBMITTED'';'; put 'call symputx(''SUBMITTED_TXT'',submitted_reason_txt,''l'');'; put 'run;'; put 'data _null_;'; put 'File __out lrecl=32000;'; put 'put ''Dear user,'';'; put 'put '' '';'; put 'put "Please be advised that a change to table &alert_lib..&alert_ds has "'; put '"been proposed by &from_user on the ''&syshostname'' SAS server.";'; put 'put " ";'; put 'length txt $2048;'; put 'txt=symget(''SUBMITTED_TXT'');'; put 'put "Reason provided: " txt;'; put 'put " ";'; put 'put "This is an automated email by Data Controller for SAS. For "'; put '"documentation, please visit https://docs.datacontroller.io";'; put 'run;'; put '%end;'; put '%else %if &alert_event=APPROVED %then %do;'; put '/* there is no approval message */'; put 'data _null_;'; put 'File __out lrecl=32000;'; put 'put ''Dear user,'';'; put 'put '' '';'; put 'put "Please be advised that a change to table &alert_lib..&alert_ds has "'; put '"been approved by &from_user on the ''&syshostname'' SAS server.";'; put 'put " ";'; put 'put "This is an automated email by Data Controller for SAS. For "'; put '"documentation, please visit https://docs.datacontroller.io";'; put 'run;'; put '%end;'; put '%else %if &alert_event=REJECTED %then %do;'; put 'data _null_;'; put 'set &mpelib..mpe_review;'; put 'where table_id="&dsid" and review_status_id=''REJECTED'';'; put 'call symputx(''REVIEW_REASON_TXT'',REVIEW_REASON_TXT,''l'');'; put 'run;'; put 'data _null_;'; put 'File __out lrecl=32000;'; put 'put ''Dear user,'';'; put 'put '' '';'; put 'put "Please be advised that a change to table &alert_lib..&alert_ds has "'; put '"been rejected by &from_user on the ''&syshostname'' SAS server.";'; put 'put " ";'; put 'length txt $2048;'; put 'txt=symget(''REVIEW_REASON_TXT'');'; put 'put "Reason provided: " txt;'; put 'put " ";'; put 'put "This is an automated email by Data Controller for SAS. For "'; put '"documentation, please visit https://docs.datacontroller.io";'; put 'run;'; put '%end;'; put 'filename __out clear;'; put '%mend mpe_alerts ;'; put '%macro mpe_getvars(injs,outds);'; put '/* load parameters */'; put 'data _null_;'; put '__dummychar='''';__dummynum=0;'; put 'set &outds;'; put 'array __charvals _character_;'; put 'do over __charvals;'; put 'call symputx(vname(__charvals),__charvals,''g'');'; put 'end;'; put 'array __numvals _numeric_;'; put 'do over __numvals;'; put 'call symputx(vname(__numvals),__numvals,''g'');'; put 'end;'; put 'run;'; put '%mend mpe_getvars;'; put '%macro mf_isblank(param'; put ')/*/STORE SOURCE*/;'; put '%sysevalf(%superq(param)=,boolean)'; put '%mend mf_isblank;'; put '%macro mp_dropmembers('; put 'list /* space separated list of datasets / views */'; put ',libref=WORK /* can only drop from a single library at a time */'; put ',iftrue=%str(1=1)'; put ')/*/STORE SOURCE*/;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%if %mf_isblank(&list) %then %do;'; put '%put NOTE: nothing to drop!;'; put '%return;'; put '%end;'; put 'proc datasets lib=&libref nolist;'; put 'delete &list;'; put 'delete &list /mtype=view;'; put 'run;'; put '%mend mp_dropmembers;'; put '%macro removecolsfromwork(col);'; put '/* only an issue if debug mode enabled */'; put '%global _debug;'; put '%if &_debug ge 131 %then %do;'; put '%let col=%upcase(&col);'; put '%local memlist;'; put 'proc sql noprint;'; put 'select distinct memname into: memlist'; put 'separated by '' '''; put 'from dictionary.columns'; put 'where libname=''WORK'' and upcase(name)="&col";'; put '%if %mf_isblank(&memlist) %then %return;'; put '%mp_dropmembers(list=&memlist)'; put '%end;'; put '%mend removecolsfromwork;'; put '%macro mf_getvarlist(libds'; put ',dlm=%str( )'; put ',quote=no'; put ',typefilter=A'; put ')/*/STORE SOURCE*/;'; put '/* declare local vars */'; put '%local outvar dsid nvars x rc dlm q var vtype;'; put '/* credit Rowland Hale - byte34 is double quote, 39 is single quote */'; put '%if %upcase("e)=DOUBLE %then %let q=%qsysfunc(byte(34));'; put '%else %if %upcase("e)=SINGLE %then %let q=%qsysfunc(byte(39));'; put '/* open dataset in macro */'; put '%let dsid=%sysfunc(open(&libds));'; put '%if &dsid %then %do;'; put '%let nvars=%sysfunc(attrn(&dsid,NVARS));'; put '%if &nvars>0 %then %do;'; put '/* add variables with supplied delimeter */'; put '%do x=1 %to &nvars;'; put '/* get variable type */'; put '%let vtype=%sysfunc(vartype(&dsid,&x));'; put '%if &vtype=&typefilter or &typefilter=A %then %do;'; put '%let var=&q.%sysfunc(varname(&dsid,&x))&q.;'; put '%if &var=&q&q %then %do;'; put '%put &sysmacroname: Empty column found in &libds!;'; put '%let var=&q. &q.;'; put '%end;'; put '%if %quote(&outvar)=%quote() %then %let outvar=&var;'; put '%else %let outvar=&outvar.&dlm.&var.;'; put '%end;'; put '%end;'; put '%end;'; put '%let rc=%sysfunc(close(&dsid));'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: Unable to open &libds (rc=&dsid);'; put '%put &sysmacroname: SYSMSG= %sysfunc(sysmsg());'; put '%let rc=%sysfunc(close(&dsid));'; put '%end;'; put '%do;%unquote(&outvar)%end;'; put '%mend mf_getvarlist;'; put '%macro mf_getattrc('; put 'libds'; put ',attr'; put ')/*/STORE SOURCE*/;'; put '%local dsid rc;'; put '%let dsid=%sysfunc(open(&libds,is));'; put '%if &dsid = 0 %then %do;'; put '%put %str(WARN)ING: Cannot open %trim(&libds), system message below;'; put '%put %sysfunc(sysmsg());'; put '-1'; put '%end;'; put '%else %do;'; put '%sysfunc(attrc(&dsid,&attr))'; put '%let rc=%sysfunc(close(&dsid));'; put '%end;'; put '%mend mf_getattrc;'; put '%macro mp_lockfilecheck('; put 'libds'; put ')/*/STORE SOURCE*/;'; put 'data _null_;'; put 'if _n_=1 then putlog "&sysmacroname entry vars:";'; put 'set sashelp.vmacro;'; put 'where scope="&sysmacroname";'; put 'put name ''='' value;'; put 'run;'; put '%mp_abort(iftrue= (&syscc>0)'; put ',mac=checklock.sas'; put ',msg=Aborting with syscc=&syscc on entry.'; put ')'; put '%mp_abort(iftrue= ("&libds"="0")'; put ',mac=&sysmacroname'; put ',msg=%str(libds not provided)'; put ')'; put '%local msg lib ds;'; put '%let lib=%upcase(%scan(&libds,1,.));'; put '%let ds=%upcase(%scan(&libds,2,.));'; put '/* in DC, format catalogs are passed with a -FC suffix. No saslock here! */'; put '%if %scan(&libds,2,-)=FC %then %do;'; put '%put &sysmacroname: Format Catalog detected, no lockfile applied to &libds;'; put '%return;'; put '%end;'; put '/* do not proceed if no observations can be processed */'; put '%let msg=options obs = 0. syserrortext=%superq(syserrortext);'; put '%mp_abort(iftrue= (%sysfunc(getoption(OBS))=0)'; put ',mac=checklock.sas'; put ',msg=%superq(msg)'; put ')'; put 'data _null_;'; put 'putlog "Checking engine & member type";'; put 'run;'; put '%local engine memtype;'; put '%let memtype=%mf_getattrc(&libds,MTYPE);'; put '%let engine=%mf_getattrc(&libds,ENGINE);'; put '%if &engine ne V9 and &engine ne BASE %then %do;'; put 'data _null_;'; put 'putlog "Lib &lib is not assigned using BASE engine - uses &engine instead";'; put 'putlog "SAS lock check will not be performed";'; put 'run;'; put '%return;'; put '%end;'; put '%else %if &memtype ne DATA %then %do;'; put '%put NOTE: Cannot lock a VIEW!! Memtype=&memtype;'; put '%return;'; put '%end;'; put 'data _null_;'; put 'putlog "Engine = &engine, memtype=&memtype";'; put 'putlog "Attempting lock statement";'; put 'run;'; put 'lock &libds;'; put '%local abortme;'; put '%let abortme=0;'; put '%if &syscc>0 or &SYSLCKRC ne 0 %then %do;'; put '%let msg=Unable to apply lock on &libds (SYSLCKRC=&SYSLCKRC syscc=&syscc);'; put '%put %str(ERR)OR: &sysmacroname: &msg;'; put '%let abortme=1;'; put '%end;'; put 'lock &libds clear;'; put '%mp_abort(iftrue= (&abortme=1)'; put ',mac=&sysmacroname'; put ',msg=%superq(msg)'; put ')'; put '%mend mp_lockfilecheck;'; put '%macro mp_lockanytable('; put 'action'; put ',lib= WORK'; put ',ds=0'; put ',ref='; put ',ctl_ds=0'; put ',loops=25'; put ',loop_secs=1'; put ');'; put 'data _null_;'; put 'if _n_=1 then putlog "&sysmacroname entry vars:";'; put 'set sashelp.vmacro;'; put 'where scope="&sysmacroname";'; put 'put name ''='' value;'; put 'run;'; put '%mp_abort(iftrue= ("&ds"="0" and &action ne MAKETABLE)'; put ',mac=&sysmacroname'; put ',msg=%str(dataset was not provided)'; put ')'; put '%mp_abort(iftrue= (&ctl_ds=0)'; put ',mac=&sysmacroname'; put ',msg=%str(Control dataset was not provided)'; put ')'; put '/* set up lib & mac vars */'; put '%let lib=%upcase(&lib);'; put '%let ds=%upcase(&ds);'; put '%let action=%upcase(&action);'; put '%local user x trans msg abortme;'; put '%let user=%mf_getuser();'; put '%let abortme=0;'; put '%mp_abort(iftrue= (&action ne LOCK & &action ne UNLOCK & &action ne MAKETABLE)'; put ',mac=&sysmacroname'; put ',msg=%str(Invalid action (&action) provided)'; put ')'; put '/* if an err condition exists, exit before we even begin */'; put '%mp_abort(iftrue= (&syscc>0 and &action=LOCK)'; put ',mac=&sysmacroname'; put ',msg=%str(aborting due to syscc=&syscc on LOCK entry)'; put ')'; put '/* do not bother locking work tables (else may affect all WORK libraries) */'; put '%if (%upcase(&lib)=WORK or %str(&lib)=%str()) & &action ne MAKETABLE %then %do;'; put '%put NOTE: WORK libraries will not be registered in the locking system.;'; put '%return;'; put '%end;'; put '/* do not proceed if no observations can be processed */'; put '%mp_abort(iftrue= (%sysfunc(getoption(OBS))=0)'; put ',mac=&sysmacroname'; put ',msg=%str(cannot continue when options obs = 0)'; put ')'; put '%if &ACTION=LOCK %then %do;'; put '/* abort if a SAS lock is already in place, or cannot be applied */'; put '%mp_lockfilecheck(&lib..&ds)'; put '/* next, check there is a record for this table */'; put '%local record_exists_check;'; put 'proc sql noprint;'; put 'select count(*) into: record_exists_check from &ctl_ds'; put 'where LOCK_LIB ="&lib" and LOCK_DS="&ds";'; put 'quit;'; put '%if &syscc>0 %then %put syscc=&syscc sqlrc=&sqlrc;'; put '%if &record_exists_check=0 %then %do;'; put 'data _null_;'; put 'putlog "&sysmacroname: adding record to lock table..";'; put 'run;'; put 'data ;'; put 'if 0 then set &ctl_ds;'; put 'LOCK_LIB ="&lib";'; put 'LOCK_DS="&ds";'; put 'LOCK_STATUS_CD=''LOCKED'';'; put 'LOCK_START_DTTM="%sysfunc(datetime(),%mf_fmtdttm())"dt;'; put 'LOCK_USER_NM="&user";'; put 'LOCK_PID="&sysjobid";'; put 'LOCK_REF="&ref";'; put 'output;stop;'; put 'run;'; put '%let trans=&syslast;'; put 'proc append base=&ctl_ds data=&trans;'; put 'run;'; put '%end;'; put '/* if record does exist, perform lock attempts */'; put '%else %do x=1 %to &loops;'; put 'data _null_;'; put 'putlog "&sysmacroname: attempting lock (iteration &x) "@;'; put 'putlog "at %sysfunc(datetime(),datetime19.) ..";'; put 'run;'; put 'proc sql;'; put 'update &ctl_ds'; put 'set LOCK_STATUS_CD=''LOCKED'''; put ', LOCK_START_DTTM="%sysfunc(datetime(),%mf_fmtdttm())"dt'; put ', LOCK_USER_NM="&user"'; put ', LOCK_PID="&sysjobid"'; put ', LOCK_REF="&ref"'; put 'where LOCK_LIB ="&lib" and LOCK_DS="&ds";'; put 'quit;'; put '/**'; put '* NOTE - occasionally SQL server will return an err code (deadlocked'; put '* transaction). If so, ignore it, keep calm, and carry on..'; put '*/'; put '%if &syscc>0 %then %do;'; put 'data _null_;'; put 'putlog ''NOTE-'' / ''NOTE-'';'; put 'putlog "NOTE- &sysmacroname: Update failed. "@;'; put 'putlog "Resetting err conditions and re-attempting.";'; put 'putlog "NOTE- syscc=&syscc syserr=&syserr sqlrc=&sqlrc";'; put 'putlog ''NOTE-'' / ''NOTE-'';'; put 'run;'; put '%let syscc=0;'; put '%let sqlrc=0;'; put '%end;'; put '/* now check if the record was successfully updated */'; put '%local success_check;'; put 'proc sql noprint;'; put 'select count(*) into: success_check from &ctl_ds'; put 'where LOCK_LIB ="&lib" and LOCK_DS="&ds"'; put 'and LOCK_PID="&sysjobid" and LOCK_STATUS_CD=''LOCKED'';'; put 'quit;'; put '%if &success_check=0 %then %do;'; put '%if &x < &loops %then %do;'; put '/* pause before next check */'; put 'data _null_;'; put 'putlog ''NOTE-'' / ''NOTE-'';'; put 'putlog "NOTE- &sysmacroname: table locked, waiting "@;'; put 'putlog "%sysfunc(sleep(&loop_secs)) seconds.. ";'; put 'putlog "NOTE- (iteration &x of &loops)";'; put 'putlog ''NOTE-'' / ''NOTE-'';'; put 'run;'; put '%end;'; put '%else %do;'; put '%let msg=Unable to lock &lib..&ds via &ctl_ds after &loops attempts.\n'; put 'Please ask your administrator to investigate!;'; put '%let abortme=1;'; put '%end;'; put '%end;'; put '%else %do;'; put 'data _null_;'; put 'putlog ''NOTE-'' / ''NOTE-'';'; put 'putlog "NOTE- &sysmacroname: Table &lib..&ds locked at "@;'; put 'putlog " %sysfunc(datetime(),datetime19.) (iteration &x)"@;'; put 'putlog ''NOTE-'' / ''NOTE-'';'; put 'run;'; put '%if &syscc>0 %then %do;'; put '%put setting syscc(&syscc) back to 0;'; put '%let syscc=0;'; put '%end;'; put '%let x=&loops; /* no more iterations needed */'; put '%end;'; put '%end;'; put '%end;'; put '%else %if &ACTION=UNLOCK %then %do;'; put '%local status cnt;'; put '%let cnt=0;'; put 'proc sql noprint;'; put 'select count(*) into: cnt from &ctl_ds where LOCK_LIB ="&lib" & LOCK_DS="&ds";'; put '%if &cnt=0 %then %do;'; put '%put %str(WAR)NING: &lib..&ds was not previously locked in &ctl_ds!;'; put '%end;'; put '%else %do;'; put 'select LOCK_STATUS_CD into: status from &ctl_ds'; put 'where LOCK_LIB ="&lib" and LOCK_DS="&ds";'; put 'quit;'; put '%if &syscc>0 %then %put syscc=&syscc sqlrc=&sqlrc;'; put '%if &status=LOCKED %then %do;'; put 'data _null_;'; put 'putlog "&sysmacroname: unlocking &lib..&ds:";'; put 'run;'; put 'proc sql;'; put 'update &ctl_ds'; put 'set LOCK_STATUS_CD=''UNLOCKED'''; put ', LOCK_END_DTTM="%sysfunc(datetime(),%mf_fmtdttm())"dt'; put ', LOCK_USER_NM="&user"'; put ', LOCK_PID="&sysjobid"'; put ', LOCK_REF="&ref"'; put 'where LOCK_LIB ="&lib" and LOCK_DS="&ds";'; put 'quit;'; put '%end;'; put '%else %if &status=UNLOCKED %then %do;'; put '%put %str(WAR)NING: &lib..&ds is already unlocked!;'; put '%end;'; put '%else %do;'; put '%put NOTE: Unrecognised STATUS_CD (&status) in &ctl_ds;'; put '%let abortme=1;'; put '%end;'; put '%end;'; put '%end;'; put '%else %do;'; put '%let msg=lock_anytable given unsupported action (&action);'; put '%let abortme=1;'; put '%end;'; put '/* catch errs - mp_abort must be called outside of a logic block */'; put '%mp_abort(iftrue=(&abortme=1),'; put 'msg=%superq(msg),'; put 'mac=&sysmacroname'; put ')'; put '%exit_macro:'; put 'data _null_;'; put 'put "&sysmacroname: Exit vars: action=&action lib=&lib ds=&ds";'; put 'put " syscc=&syscc sqlrc=&sqlrc syserr=&syserr";'; put 'run;'; put '%mend mp_lockanytable;'; put '* SAS Macros end;'; put '* SAS Includes start;'; put '* SAS Includes end;'; put '* Binary Files start;'; put '* Binary Files end;'; put '* ServiceInit start;'; put 'options noquotelenmax ps=max;'; put '/* create dummy macros to prevent stpbegin / stpend from corrupting sessions */'; put '%macro stpbegin();'; put '%put NOTE: the STPBEGIN macro should not be used for web apps!;'; put '%mend stpbegin;'; put '%macro stpend();'; put '%put NOTE: the STPEND macro should not be used for web apps!;'; put '%mend stpend;'; put '* ServiceInit end;'; put '* Service start;'; put '/**'; put '@file'; put '@brief Removes a staged data package from approval screen'; put '@details'; put '

SAS Macros

'; put '@li mf_getuser.sas'; put '@li mf_getvarlist.sas'; put '@li mf_verifymacvars.sas'; put '@li mp_abort.sas'; put '@li mp_lockanytable.sas'; put '@li mpe_accesscheck.sas'; put '@li mpe_alerts.sas'; put '@li mpe_getvars.sas'; put '@li removecolsfromwork.sas'; put '

Service Outputs

'; put '
fromsas
'; put '@li TABLE_ID'; put '@li SUBMITTED_REASON_TXT'; put '@li RESPONSE'; put '@version 9.2'; put '@author 4GL Apps Ltd'; put '@copyright 4GL Apps Ltd. This code may only be used within Data Controller'; put 'and may not be re-distributed or re-sold without the express permission of'; put '4GL Apps Ltd.'; put '**/'; put '%global STP_ACTION TABLE STP_REASON;'; put '%mpeinit()'; put '%mpe_getvars(BrowserParams, BrowserParams)'; put 'PROC FORMAT;'; put 'picture yymmddhhmmss other=''%0Y-%0m-%0d %0H:%0M:%0S'' (datatype=datetime);'; put 'RUN;'; put '/* get current status and base table */'; put 'data _null_;'; put 'set &mpelib..mpe_submit(where=(TABLE_ID="&TABLE"));'; put 'call symputx(''BASE_TABLE'',cats(base_lib,''.'',base_ds));'; put 'call symputx(''submit_status_cd'',submit_status_cd);'; put 'run;'; put '%mp_abort('; put 'iftrue=(%mf_verifymacvars(base_table)=0)'; put ',mac=&_program'; put ',msg=%str(Missing: base_table)'; put ')'; put '%mp_abort('; put 'iftrue=(%quote(&submit_status_cd)=%quote(REJECTED))'; put ',mac=&_program'; put ',msg=%str(&table is already rejected!)'; put ')'; put '%mp_abort(iftrue= (&syscc ge 4)'; put ',mac=&_program'; put ',msg=%str(Issue on setup)'; put ')'; put '/**'; put '* determine if user is authorised to reject table'; put '*/'; put '%let user=%mf_getuser();'; put '%global authcheck; %let authcheck=0;'; put '%mpe_accesscheck(&base_table,outds=authAPP,user=&user,access_level=APPROVE)'; put '%let authcheck=%mf_getattrn(work.authAPP,NLOBS);'; put '%mp_abort(iftrue= (&authcheck=0)'; put ',mac=&_program..sas'; put ',msg=%str(User &user does not have APPROVE rights on &base_table and is not'; put 'in the &mpeadmins group)'; put ')'; put '/* update the control table to show table as rejected (and why) */'; put '%let now=%sysfunc(datetime());'; put 'data work.reject;'; put 'if 0 then set &mpelib..mpe_review;'; put 'TABLE_ID="&table";'; put 'BASE_TABLE="&base_table";'; put 'REVIEW_STATUS_ID="REJECTED";'; put 'REVIEWED_BY_NM="&user";'; put 'REVIEWED_ON_DTTM=&now;'; put 'REVIEW_REASON_TXT=symget(''STP_REASON'');'; put 'run;'; put '%mp_lockanytable(LOCK,'; put 'lib=&mpelib,ds=mpe_review,ref=%str(&table rejection),'; put 'ctl_ds=&mpelib..mpe_lockanytable'; put ')'; put 'proc append base=&mpelib..mpe_review data=work.reject;'; put 'run;'; put '%mp_lockanytable(UNLOCK,'; put 'lib=&mpelib,ds=mpe_review,'; put 'ctl_ds=&mpelib..mpe_lockanytable'; put ')'; put '%mp_lockanytable(LOCK,'; put 'lib=&mpelib,ds=mpe_submit,ref=%str(&table rejection),'; put 'ctl_ds=&mpelib..mpe_lockanytable'; put ')'; put 'proc sql;'; put 'update &mpelib..mpe_submit'; put 'set submit_status_cd=''REJECTED'','; put 'num_of_approvals_remaining=0,'; put 'reviewed_by_nm="&user",'; put 'reviewed_on_dttm=&now'; put 'where table_id="&table";'; put '%mp_lockanytable(UNLOCK,'; put 'lib=&mpelib,ds=mpe_submit,'; put 'ctl_ds=&mpelib..mpe_lockanytable'; put ')'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(syscc=&syscc AFTER update...)'; put ')'; put '%mpe_alerts(alert_event=REJECTED'; put ', alert_lib=%scan(&BASE_TABLE,1,.)'; put ', alert_ds=%scan(&BASE_TABLE,2,.)'; put ', dsid=&TABLE'; put ')'; put 'data fromSAS;'; put 'RESPONSE=''SUCCESS!'';'; put 'set REJECT;'; put 'run;'; put '%removecolsfromwork(___TMP___MD5)'; put '%webout(OPEN)'; put '%webout(OBJ,fromSAS)'; put '%webout(CLOSE)'; put '%mpeterm()'; put '* Service end;'; run; %mm_createwebservice(path=&appLoc/&path, name=&service, code=sascode, server=&serverName, replace=yes) filename sascode clear; %let path=services/auditors; %let service=getauditfile; filename sascode temp lrecl=32767; data _null_; file sascode; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '/**'; put '@file mp_jsonout.sas'; put '@brief Writes JSON in SASjs format to a fileref'; put '@details This macro can be used to OPEN a JSON stream and send one or more'; put 'tables as arrays of rows, where each row can be an object or a nested array.'; put 'There are two engines available - DATASTEP or PROCJSON.'; put 'PROC JSON is fast but will produce errs like the ones below if'; put 'special chars are encountered.'; put '> (ERR)OR: Some code points did not transcode.'; put '> An object or array close is not valid at this point in the JSON text.'; put '> Date value out of range'; put 'If this happens, try running with ENGINE=DATASTEP.'; put 'The DATASTEP engine is used to handle special SAS missing numerics, and'; put 'can also convert entire datasets to formatted values. Output JSON is always'; put 'in UTF-8.'; put 'Usage:'; put 'filename tmp temp;'; put 'data class; set sashelp.class;run;'; put '%mp_jsonout(OPEN,jref=tmp)'; put '%mp_jsonout(OBJ,class,jref=tmp)'; put '%mp_jsonout(OBJ,class,dslabel=class2,jref=tmp,showmeta=Y)'; put '%mp_jsonout(CLOSE,jref=tmp)'; put 'data _null_;'; put 'infile tmp;'; put 'input;putlog _infile_;'; put 'run;'; put 'If you are building web apps with SAS then you are strongly encouraged to use'; put 'the mX_createwebservice macros in combination with the'; put '[sasjs adapter](https://github.com/sasjs/adapter).'; put 'For more information see https://sasjs.io'; put '@param [in] action Valid values:'; put '@li OPEN - opens the JSON'; put '@li OBJ - sends a table with each row as an object'; put '@li ARR - sends a table with each row in an array'; put '@li CLOSE - closes the JSON'; put '@param [in] ds The dataset to send. Must be a work table.'; put '@param [out] jref= (_webout) The fileref to which to send the JSON'; put '@param [out] dslabel= The name to give the table in the exported JSON'; put '@param [in] fmt= (Y) Whether to keep (Y) or strip (N) formats from the table'; put '@param [in] engine= (DATASTEP) Which engine to use to send the JSON. Options:'; put '@li PROCJSON (default)'; put '@li DATASTEP (more reliable when data has non standard characters)'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to JSON'; put '

Related Files

'; put '@li mp_ds2fmtds.sas'; put '@version 9.2'; put '@author Allan Bowe'; put '@source https://github.com/sasjs/core'; put '**/'; put '%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y'; put ',engine=DATASTEP'; put ',missing=NULL'; put ',showmeta=N'; put ',maxobs=MAX'; put ')/*/STORE SOURCE*/;'; put '%local tempds colinfo fmtds i numcols numobs stmt_obs lastobs optval'; put 'tmpds1 tmpds2 tmpds3 tmpds4;'; put '%let numcols=0;'; put '%if &maxobs ne MAX %then %let stmt_obs=%str(if _n_>&maxobs then stop;);'; put '%if &action=OPEN %then %do;'; put 'options nobomfile;'; put 'data _null_;file &jref encoding=''utf-8'' lrecl=200;'; put 'put ''{"PROCESSED_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''"'';'; put 'run;'; put '%end;'; put '%else %if (&action=ARR or &action=OBJ) %then %do;'; put '/* force variable names to always be uppercase in the JSON */'; put 'options validvarname=upcase;'; put '/* To avoid issues with _webout on EBI - such as encoding diffs and truncation'; put '(https://support.sas.com/kb/49/325.html) we use temporary files */'; put 'filename _sjs1 temp lrecl=200 ;'; put 'data _null_; file _sjs1 encoding=''utf-8'';'; put 'put ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":";'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjs1 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs1 clear;'; put '/* grab col defs */'; put 'proc contents noprint data=&ds'; put 'out=_data_(keep=name type length format formatl formatd varnum label);'; put 'run;'; put '%let colinfo=%scan(&syslast,2,.);'; put 'proc sort data=&colinfo;'; put 'by varnum;'; put 'run;'; put '/* move meta to mac vars */'; put 'data &colinfo;'; put 'if _n_=1 then call symputx(''numcols'',nobs,''l'');'; put 'set &colinfo end=last nobs=nobs;'; put 'name=upcase(name);'; put '/* fix formats */'; put 'if type=2 or type=6 then do;'; put 'typelong=''char'';'; put 'length fmt $49.;'; put 'if format='''' then fmt=cats(''$'',length,''.'');'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else fmt=cats(format,formatl,''.'');'; put 'end;'; put 'else do;'; put 'typelong=''num'';'; put 'if format='''' then fmt=''best.'';'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else if formatd=0 then fmt=cats(format,formatl,''.'');'; put 'else fmt=cats(format,formatl,''.'',formatd);'; put 'end;'; put '/* 32 char unique name */'; put 'newname=''sasjs''!!substr(cats(put(md5(name),$hex32.)),1,27);'; put 'call symputx(cats(''name'',_n_),name,''l'');'; put 'call symputx(cats(''newname'',_n_),newname,''l'');'; put 'call symputx(cats(''length'',_n_),length,''l'');'; put 'call symputx(cats(''fmt'',_n_),fmt,''l'');'; put 'call symputx(cats(''type'',_n_),type,''l'');'; put 'call symputx(cats(''typelong'',_n_),typelong,''l'');'; put 'call symputx(cats(''label'',_n_),coalescec(label,name),''l'');'; put '/* overwritten when fmt=Y and a custom format exists in catalog */'; put 'if typelong=''num'' then call symputx(cats(''fmtlen'',_n_),200,''l'');'; put 'else call symputx(cats(''fmtlen'',_n_),min(32767,ceil((length+10)*1.5)),''l'');'; put 'run;'; put '%let tempds=%substr(_%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql;'; put 'select count(*) into: lastobs from &ds;'; put '%if &maxobs ne MAX %then %let lastobs=%sysfunc(min(&lastobs,&maxobs));'; put '%if &engine=PROCJSON %then %do;'; put '%if &missing=STRING %then %do;'; put '%put &sysmacroname: Special Missings not supported in proc json.;'; put '%put &sysmacroname: Switching to DATASTEP engine;'; put '%goto datastep;'; put '%end;'; put 'data &tempds;'; put 'set &ds;'; put '&stmt_obs;'; put '%if &fmt=N %then format _numeric_ best32.;;'; put '/* PRETTY is necessary to avoid line truncation in large files */'; put 'filename _sjs2 temp lrecl=131068 encoding=''utf-8'';'; put 'proc json out=_sjs2 pretty'; put '%if &action=ARR %then nokeys ;'; put ';export &tempds / nosastags fmtnumeric;'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs2 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs2 clear;'; put '%end;'; put '%else %if &engine=DATASTEP %then %do;'; put '%datastep:'; put '%if %sysfunc(exist(&ds)) ne 1 & %sysfunc(exist(&ds,VIEW)) ne 1'; put '%then %do;'; put '%put &sysmacroname: &ds NOT FOUND!!!;'; put '%return;'; put '%end;'; put '%if &fmt=Y %then %do;'; put '/**'; put '* Extract format definitions'; put '* First, by getting library locations from dictionary.formats'; put '* Then, by exporting the width using proc format'; put '* Cannot use maxw from sashelp.vformat as not always populated'; put '* Cannot use fmtinfo() as not supported in all flavours'; put '*/'; put '%let tmpds1=%substr(fmtsum%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds2=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds3=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds4=%substr(col%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql noprint;'; put 'create table &tmpds1 as'; put 'select cats(libname,''.'',memname) as FMTCAT,'; put 'FMTNAME'; put 'from dictionary.formats'; put 'where fmttype=''F'' and libname is not null'; put 'and fmtname in (select format from &colinfo where format is not null)'; put 'order by 1;'; put 'create table &tmpds2('; put 'FMTNAME char(32),'; put 'LENGTH num'; put ');'; put '%local catlist cat fmtlist i;'; put 'select distinct fmtcat into: catlist separated by '' '' from &tmpds1;'; put '%do i=1 %to %sysfunc(countw(&catlist,%str( )));'; put '%let cat=%scan(&catlist,&i,%str( ));'; put 'proc sql;'; put 'select distinct fmtname into: fmtlist separated by '' '''; put 'from &tmpds1 where fmtcat="&cat";'; put 'proc format lib=&cat cntlout=&tmpds3(keep=fmtname length);'; put 'select &fmtlist;'; put 'run;'; put 'proc sql;'; put 'insert into &tmpds2 select distinct fmtname,length from &tmpds3;'; put '%end;'; put 'proc sql;'; put 'create table &tmpds4 as'; put 'select a.*, b.length as MAXW'; put 'from &colinfo a'; put 'left join &tmpds2 b'; put 'on cats(a.format)=cats(upcase(b.fmtname))'; put 'order by a.varnum;'; put 'data _null_;'; put 'set &tmpds4;'; put 'if not missing(maxw);'; put 'call symputx('; put 'cats(''fmtlen'',_n_),'; put '/* vars need extra padding due to JSON escaping of special chars */'; put 'min(32767,ceil((max(length,maxw)+10)*1.5))'; put ',''l'''; put ');'; put 'run;'; put '/* configure varlenchk - as we are explicitly shortening the variables */'; put '%let optval=%sysfunc(getoption(varlenchk));'; put 'options varlenchk=NOWARN;'; put 'data _data_(compress=char);'; put '/* shorten the new vars */'; put 'length'; put '%do i=1 %to &numcols;'; put '&&name&i $&&fmtlen&i'; put '%end;'; put ';'; put '/* rename on entry */'; put 'set &ds(rename=('; put '%do i=1 %to &numcols;'; put '&&name&i=&&newname&i'; put '%end;'; put '));'; put '&stmt_obs;'; put 'drop'; put '%do i=1 %to &numcols;'; put '&&newname&i'; put '%end;'; put ';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=num %then %do;'; put '&&name&i=cats(put(&&newname&i,&&fmt&i));'; put '%end;'; put '%else %do;'; put '&&name&i=put(&&newname&i,&&fmt&i);'; put '%end;'; put '%end;'; put 'if _error_ then do;'; put 'call symputx(''syscc'',1012);'; put 'stop;'; put 'end;'; put 'run;'; put '%let fmtds=&syslast;'; put 'options varlenchk=&optval;'; put '%end;'; put 'proc format; /* credit yabwon for special null removal */'; put 'value bart (default=40)'; put '%if &missing=NULL %then %do;'; put '._ - .z = null'; put '%end;'; put '%else %do;'; put '._ = [quote()]'; put '. = null'; put '.a - .z = [quote()]'; put '%end;'; put 'other = [best.];'; put 'data &tempds;'; put 'attrib _all_ label='''';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'length &&name&i $&&fmtlen&i...;'; put 'format &&name&i $&&fmtlen&i...;'; put '%end;'; put '%end;'; put '%if &fmt=Y %then %do;'; put 'set &fmtds;'; put '%end;'; put '%else %do;'; put 'set &ds;'; put '%end;'; put '&stmt_obs;'; put 'format _numeric_ bart.;'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'if findc(&&name&i,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put '&&name&i=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,&&name&i)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else &&name&i=quote(cats(&&name&i));'; put '%end;'; put '%end;'; put 'run;'; put 'filename _sjs3 temp lrecl=131068 ;'; put 'data _null_;'; put 'file _sjs3 encoding=''utf-8'';'; put 'if _n_=1 then put "[";'; put 'set &tempds;'; put 'if _n_>1 then put "," @; put'; put '%if &action=ARR %then "[" ; %else "{" ;'; put '%do i=1 %to &numcols;'; put '%if &i>1 %then "," ;'; put '%if &action=OBJ %then """&&name&i"":" ;'; put '"&&name&i"n /* name literal for reserved variable names */'; put '%end;'; put '%if &action=ARR %then "]" ; %else "}" ; ;'; put '/* close out the table */'; put 'data _null_;'; put 'file _sjs3 mod encoding=''utf-8'';'; put 'put '']'';'; put 'run;'; put 'data _null_;'; put 'infile _sjs3 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs3 clear;'; put '%end;'; put 'proc sql;'; put 'drop table &colinfo, &tempds;'; put '%if %substr(&showmeta,1,1)=Y %then %do;'; put 'filename _sjs4 temp lrecl=131068 encoding=''utf-8'';'; put 'data _null_;'; put 'file _sjs4;'; put 'length label $350;'; put 'put ", ""$%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":{""vars"":{";'; put 'do i=1 to &numcols;'; put 'name=quote(trim(symget(cats(''name'',i))));'; put 'format=quote(trim(symget(cats(''fmt'',i))));'; put 'label=quote(prxchange(''s/\\/\\\\/'',-1,trim(symget(cats(''label'',i)))));'; put 'length=quote(trim(symget(cats(''length'',i))));'; put 'type=quote(trim(symget(cats(''typelong'',i))));'; put 'if i>1 then put "," @@;'; put 'put name '':{"format":'' format '',"label":'' label'; put ''',"length":'' length '',"type":'' type ''}'';'; put 'end;'; put 'put ''}}'';'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs4 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs4 clear;'; put '%end;'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put 'data _null_; file &jref encoding=''utf-8'' mod ;'; put 'put "}";'; put 'run;'; put '%end;'; put '%mend mp_jsonout;'; put '/**'; put '@file mm_webout.sas'; put '@brief Send data to/from SAS Stored Processes'; put '@details This macro should be added to the start of each Stored Process,'; put '**immediately** followed by a call to:'; put '%mm_webout(FETCH)'; put 'This will read all the input data and create same-named SAS datasets in the'; put 'WORK library. You can then insert your code, and send data back using the'; put 'following syntax:'; put 'data some datasets; * make some data ;'; put 'retain some columns;'; put 'run;'; put '%mm_webout(OPEN)'; put '%mm_webout(ARR,some) * Array format, fast, suitable for large tables ;'; put '%mm_webout(OBJ,datasets) * Object format, easier to work with ;'; put 'Finally, wrap everything up send some helpful system variables too'; put '%mm_webout(CLOSE)'; put '@param [in] action Either FETCH, OPEN, ARR, OBJ or CLOSE'; put '@param [in] ds The dataset to send back to the frontend'; put '@param [out] dslabel= Value to use instead of table name for sending to JSON'; put '@param [in] fmt= (N) Setting Y converts all vars to their formatted values'; put '@param [out] fref= (_webout) The fileref to which to write the JSON'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] workobs= (0) When set to a positive integer, will create a new'; put 'output object (WORK) which contains this number of observations from all'; put 'tables in the WORK library.'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to output JSON'; put '

SAS Macros

'; put '@li mp_jsonout.sas'; put '

Related Macros

'; put '@li ms_webout.sas'; put '@li mv_webout.sas'; put '@version 9.3'; put '@author Allan Bowe'; put '**/'; put '%macro mm_webout(action,ds,dslabel=,fref=_webout,fmt=N,missing=NULL'; put ',showmeta=N,maxobs=MAX,workobs=0'; put ');'; put '%global _webin_file_count _webin_fileref1 _webin_name1 _program _debug'; put 'sasjs_tables;'; put '%local i tempds jsonengine;'; put '/* see https://github.com/sasjs/core/issues/41 */'; put '%if "%upcase(&SYSENCODING)" ne "UTF-8" %then %let jsonengine=PROCJSON;'; put '%else %let jsonengine=DATASTEP;'; put '%if &action=FETCH %then %do;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'options mprint notes mprintnest;'; put '%end;'; put '%let _webin_file_count=%eval(&_webin_file_count+0);'; put '/* now read in the data */'; put '%do i=1 %to &_webin_file_count;'; put '%if &_webin_file_count=1 %then %do;'; put '%let _webin_fileref1=&_webin_fileref;'; put '%let _webin_name1=&_webin_name;'; put '%end;'; put 'data _null_;'; put 'infile &&_webin_fileref&i termstr=crlf;'; put 'input;'; put 'call symputx(''input_statement'',_infile_);'; put 'putlog "&&_webin_name&i input statement: " _infile_;'; put 'stop;'; put 'data &&_webin_name&i;'; put 'infile &&_webin_fileref&i firstobs=2 dsd termstr=crlf encoding=''utf-8'';'; put 'input &input_statement;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'if _n_<20 then putlog _infile_;'; put '%end;'; put 'run;'; put '%let sasjs_tables=&sasjs_tables &&_webin_name&i;'; put '%end;'; put '%end;'; put '%else %if &action=OPEN %then %do;'; put '/* fix encoding */'; put 'OPTIONS NOBOMFILE;'; put '/**'; put '* check xengine type to avoid the below err message:'; put '* > Function is only valid for filerefs using the CACHE access method.'; put '*/'; put 'data _null_;'; put 'set sashelp.vextfl(where=(fileref="_WEBOUT"));'; put 'if xengine=''STREAM'' then do;'; put 'rc=stpsrv_header(''Content-type'',"text/html; encoding=utf-8");'; put 'end;'; put 'run;'; put '/* setup json */'; put 'data _null_;file &fref encoding=''utf-8'';'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutBEGIN<<'';'; put '%end;'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'run;'; put '%end;'; put '%else %if &action=ARR or &action=OBJ %then %do;'; put '%mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt,jref=&fref'; put ',engine=&jsonengine,missing=&missing,showmeta=&showmeta,maxobs=&maxobs'; put ')'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put '/* To avoid issues with _webout on EBI we use a temporary file */'; put 'filename _sjsref temp lrecl=131068;'; put '%if %str(&workobs) > 0 %then %do;'; put '/* if debug mode, send back first XX records of each work table also */'; put 'data;run;%let tempds=%scan(&syslast,2,.);'; put 'ods output Members=&tempds;'; put 'proc datasets library=WORK memtype=data;'; put '%local wtcnt;%let wtcnt=0;'; put 'data _null_;'; put 'set &tempds;'; put 'if not (upcase(name) =:"DATA"); /* ignore temp datasets */'; put 'i+1;'; put 'call symputx(cats(''wt'',i),name,''l'');'; put 'call symputx(''wtcnt'',i,''l'');'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put ",""WORK"":{";'; put '%do i=1 %to &wtcnt;'; put '%let wt=&&wt&i;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'dsid=open("WORK.&wt",''is'');'; put 'nlobs=attrn(dsid,''NLOBS'');'; put 'nvars=attrn(dsid,''NVARS'');'; put 'rc=close(dsid);'; put 'if &i>1 then put '',''@;'; put 'put " ""&wt"" : {";'; put 'put ''"nlobs":'' nlobs;'; put 'put '',"nvars":'' nvars;'; put '%mp_jsonout(OBJ,&wt,jref=_sjsref,dslabel=first10rows,showmeta=Y'; put ',maxobs=&workobs'; put ')'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put '%end;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put 'run;'; put '%end;'; put '/* close off json */'; put 'data _null_;file _sjsref mod encoding=''utf-8'';'; put 'length SYSPROCESSNAME syserrortext syswarningtext autoexec $512;'; put 'put ",""_DEBUG"" : ""&_debug"" ";'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'autoexec=quote(urlencode(trim(getoption(''autoexec''))));'; put 'put '',"AUTOEXEC" : '' autoexec;'; put 'put ",""MF_GETUSER"" : ""%mf_getuser()"" ";'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'put ",""SYSENCODING"" : ""&sysencoding"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSPROCESSID"" : ""&SYSPROCESSID"" ";'; put 'put ",""SYSPROCESSMODE"" : ""&SYSPROCESSMODE"" ";'; put 'SYSPROCESSNAME=quote(urlencode(cats(SYSPROCESSNAME)));'; put 'put ",""SYSPROCESSNAME"" : " SYSPROCESSNAME;'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put '',"SYSWARNINGTEXT" : '' syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'length memsize $32;'; put 'memsize="%sysfunc(INPUTN(%sysfunc(getoption(memsize)), best.),sizekmg.)";'; put 'memsize=quote(cats(memsize));'; put 'put '',"MEMSIZE" : '' memsize;'; put 'put "}" @;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutEND<<'';'; put '%end;'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjsref lrecl=1 recfm=n;'; put 'file &fref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjsref clear;'; put '%end;'; put '%mend mm_webout;'; put '%macro webout(action,ds,dslabel=,fmt=,missing=NULL,showmeta=NO,maxobs=MAX);'; put '%mm_webout(&action,ds=&ds,dslabel=&dslabel,fmt=&fmt'; put ',missing=&missing'; put ',showmeta=&showmeta'; put ',maxobs=&maxobs'; put ') %mend;'; put '/* provide additional debug info */'; put '%global _program;'; put '%put &=syscc;'; put '%put user=%mf_getuser();'; put '%put pgm=&_program;'; put '%put timestamp=%sysfunc(datetime(),datetime19.);'; put '* Service Variables start;'; put '* Service Variables end;'; put '* SAS Macros start;'; put '%macro mf_getapploc(pgm);'; put '%if "&pgm"="" %then %do;'; put '%if %symexist(_program) %then %let pgm=&_program;'; put '%else %do;'; put '%put &sysmacroname: No value provided and no _program variable available;'; put '%return;'; put '%end;'; put '%end;'; put '%local root;'; put '/**'; put '* First check we are not in the tests/macros folder (which has no subfolders)'; put '* or specifically in the testsetup or testteardown services'; put '*/'; put '%if %index(&pgm,/tests/macros/)'; put 'or %index(&pgm,/tests/testsetup)'; put 'or %index(&pgm,/tests/testteardown)'; put '%then %do;'; put '%let root=%substr(&pgm,1,%index(&pgm,/tests)-1);'; put '&root'; put '%return;'; put '%end;'; put '/**'; put '* Next, move up two levels to avoid matches on subfolder or service name'; put '*/'; put '%let root=%substr(&pgm,1,%length(&pgm)-%length(%scan(&pgm,-1,/))-1);'; put '%let root=%substr(&root,1,%length(&root)-%length(%scan(&root,-1,/))-1);'; put '%if %index(&root,/tests/) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/tests/)-1);'; put '%end;'; put '%else %if %index(&root,/services) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/services)-1);'; put '%end;'; put '%else %if %index(&root,/jobs) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/jobs)-1);'; put '%end;'; put '%else %put &sysmacroname: Could not find an app location from &pgm;'; put '&root'; put '%mend mf_getapploc ;'; put '%macro mf_getuniquefileref(prefix=_,maxtries=1000,lrecl=32767);'; put '%local rc fname;'; put '%if &prefix=0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%end;'; put '%else %do;'; put '%local x len;'; put '%let len=%eval(8-%length(&prefix));'; put '%let x=0;'; put '%do x=0 %to &maxtries;'; put '%let fname=&prefix%substr(%sysfunc(ranuni(0)),3,&len);'; put '%if %sysfunc(fileref(&fname)) > 0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%return;'; put '%end;'; put '%end;'; put '%put unable to find available fileref after &maxtries attempts;'; put '%end;'; put '%mend mf_getuniquefileref;'; put '%macro mp_abort(mac=mp_abort.sas, type=, msg=, iftrue=%str(1=1)'; put ', errds=work.mp_abort_errds'; put ', mode=REGULAR'; put ')/*/STORE SOURCE*/;'; put '%global sysprocessmode sysprocessname sasjs_stpsrv_header_loc sasjsprocessmode;'; put '%local fref fid i;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%put NOTE: /// mp_abort macro executing //;'; put '%if %length(&mac)>0 %then %put NOTE- called by &mac;'; put '%put NOTE - &msg;'; put '%if %symexist(_SYSINCLUDEFILEDEVICE)'; put '/* abort cancel FILE does not restart outside the INCLUDE on Viya 3.5 */'; put 'and %superq(SYSPROCESSNAME) ne %str(Compute Server)'; put '%then %do;'; put '%if "*&_SYSINCLUDEFILEDEVICE*" ne "**" %then %do;'; put 'data &errds;'; put 'iftrue=''1=1'';'; put 'length mac $100 msg $5000;'; put 'mac=symget(''mac'');'; put 'msg=symget(''msg'');'; put 'run;'; put 'data _null_;'; put 'abort cancel FILE;'; put 'run;'; put '%return;'; put '%end;'; put '%end;'; put '/* Web App Context */'; put '%if %symexist(_PROGRAM)'; put 'or %superq(SYSPROCESSNAME) = %str(Compute Server)'; put 'or &mode=INCLUDE'; put '%then %do;'; put 'options obs=max replace mprint;'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5"'; put '%then %do;'; put 'options nosyntaxcheck;'; put '%end;'; put '%if &mode=INCLUDE %then %do;'; put '%if %sysfunc(exist(&errds))=1 %then %do;'; put 'data _null_;'; put 'set &errds;'; put 'call symputx(''iftrue'',iftrue,''l'');'; put 'call symputx(''mac'',mac,''l'');'; put 'call symputx(''msg'',msg,''l'');'; put 'putlog (_all_)(=);'; put 'run;'; put '%if (&iftrue)=0 %then %return;'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: No include errors found;'; put '%return;'; put '%end;'; put '%end;'; put '/* extract log errs / warns, if exist */'; put '%local logloc logline;'; put '%global logmsg; /* capture global messages */'; put '%if %symexist(SYSPRINTTOLOG) %then %let logloc=&SYSPRINTTOLOG;'; put '%else %let logloc=%qsysfunc(getoption(LOG));'; put 'proc printto log=log;run;'; put '%let logline=0;'; put '%if %length(&logloc)>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input; putlog _infile_;'; put 'i=1;'; put 'retain logonce 0;'; put 'if ('; put '_infile_=:"%str(WARN)ING" or _infile_=:"%str(ERR)OR"'; put ') and logonce=0 then'; put 'do;'; put 'call symputx(''logline'',_n_);'; put 'logonce+1;'; put 'end;'; put 'run;'; put '/* capture log including lines BEFORE the err */'; put '%if &logline>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input;'; put 'i=1;'; put 'stoploop=0;'; put 'if _n_ ge &logline-15 and stoploop=0 then do until (i>22);'; put 'call symputx(''logmsg'',catx(''\n'',symget(''logmsg''),_infile_));'; put 'input;'; put 'i+1;'; put 'stoploop=1;'; put 'end;'; put 'if stoploop=1 then stop;'; put 'run;'; put '%end;'; put '%end;'; put '%if %symexist(SYS_JES_JOB_URI) %then %do;'; put '/* setup webout for Viya */'; put 'options nobomfile;'; put '%if "X&SYS_JES_JOB_URI.X"="XX" %then %do;'; put 'filename _webout temp lrecl=999999 mod;'; put '%end;'; put '%else %do;'; put 'filename _webout filesrvc parenturi="&SYS_JES_JOB_URI"'; put 'name="_webout.json" lrecl=999999 mod;'; put '%end;'; put '%end;'; put '%else %if %sysfunc(filename(fref,&sasjs_stpsrv_header_loc))=0 %then %do;'; put 'options nobomfile;'; put '/* set up http header for SASjs Server */'; put '%let fid=%sysfunc(fopen(&fref,A));'; put '%if &fid=0 %then %do;'; put '%put %str(ERR)OR: %sysfunc(sysmsg());'; put '%return;'; put '%end;'; put '%let rc=%sysfunc(fput(&fid,%str(Content-Type: application/json)));'; put '%let rc=%sysfunc(fwrite(&fid));'; put '%let rc=%sysfunc(fclose(&fid));'; put '%let rc=%sysfunc(filename(&fref));'; put '%end;'; put '/* send response in SASjs JSON format */'; put 'data _null_;'; put 'file _webout mod lrecl=32000 encoding=''utf-8'';'; put 'length msg syswarningtext syserrortext $32767 mode $10 ;'; put 'sasdatetime=datetime();'; put 'msg=symget(''msg'');'; put '%if &logline>0 %then %do;'; put 'msg=cats(msg,''\n\nLog Extract:\n'',symget(''logmsg''));'; put '%end;'; put '/* escape the escapes */'; put 'msg=tranwrd(msg,''\'',''\\'');'; put '/* escape the quotes */'; put 'msg=tranwrd(msg,''"'',''\"'');'; put '/* ditch the CRLFs as chrome complains */'; put 'msg=compress(msg,,''kw'');'; put '/* quote without quoting the quotes (which are escaped instead) */'; put 'msg=cats(''"'',msg,''"'');'; put 'if symexist(''_debug'') then debug=quote(trim(symget(''_debug'')));'; put 'else debug=''""'';'; put 'if symget(''sasjsprocessmode'')=''Stored Program'' then mode=''SASJS'';'; put 'if mode ne ''SASJS'' then put ''>>weboutBEGIN<<'';'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'put '',"sasjsAbort" : [{'';'; put 'put '' "MSG":'' msg ;'; put 'put '' ,"MAC": "'' "&mac" ''"}]'';'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'put '',"_DEBUG":'' debug ;'; put 'if symexist(''_metauser'') then do;'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put 'end;'; put 'if symexist(''SYS_JES_JOB_URI'') then do;'; put 'SYS_JES_JOB_URI=quote(trim(symget(''SYS_JES_JOB_URI'')));'; put 'put '',"SYS_JES_JOB_URI": '' SYS_JES_JOB_URI;'; put 'end;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put ",""SYSWARNINGTEXT"" : " syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'put "}" ;'; put 'if mode ne ''SASJS'' then put ''>>weboutEND<<'';'; put 'run;'; put '%put _all_;'; put '%if "&sysprocessmode " = "SAS Stored Process Server " %then %do;'; put 'data _null_;'; put 'putlog ''stpsrvset program err and syscc'';'; put 'rc=stpsrvset(''program error'', 0);'; put 'call symputx("syscc",0,"g");'; put 'run;'; put '%if &sysscp=WIN'; put 'and 1=0 /* deprecating this logic until we figure out a consistent abort */'; put 'and "%substr(%str(&sysvlong ),1,8)"="9.04.01M"'; put 'and "%substr(%str(&sysvlong ),9,1)">"5" %then %do;'; put '/* skip approach (below) does not work in windows m6+ envs */'; put 'endsas;'; put '%end;'; put '%else %do;'; put '/**'; put '* endsas kills 9.4m3 deployments by orphaning multibridges.'; put '* Abort variants are ungraceful (non zero return code)'; put '* This approach lets SAS run silently until the end :-)'; put '* Caution - fails when called within a %include within a macro'; put '* Use mp_include() to handle this.'; put '*/'; put 'filename skip temp;'; put 'data _null_;'; put 'file skip;'; put 'put ''%macro skip();'';'; put 'comment ''%mend skip; -> fix lint '';'; put 'put ''%macro skippy();'';'; put 'comment ''%mend skippy; -> fix lint '';'; put 'run;'; put '%inc skip;'; put '%end;'; put '%end;'; put '%else %if "&sysprocessmode " = "SAS Compute Server " %then %do;'; put '/* endsas kills the session making it harder to fetch results */'; put 'data _null_;'; put 'syswarningtext=symget(''syswarningtext'');'; put 'syserrortext=symget(''syserrortext'');'; put 'abort_msg=symget(''msg'');'; put 'syscc=symget(''syscc'');'; put 'sysuserid=symget(''sysuserid'');'; put 'iftrue=symget(''iftrue'');'; put 'put (_all_)(/=);'; put 'call symputx(''syscc'',0);'; put 'abort cancel nolist;'; put 'run;'; put '%end;'; put '%else %do;'; put '%abort cancel;'; put '%end;'; put '%end;'; put '%else %do;'; put '%put _all_;'; put '%abort cancel;'; put '%end;'; put '%mend mp_abort;'; put '/** @endcond */'; put '%macro mm_getstpcode('; put 'tree=/User Folders/sasdemo/somestp'; put ',name='; put ',outloc=0'; put ',outref=0'; put ',mDebug=1'; put ',showlog=NO'; put ');'; put '%local mD;'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing &sysmacroname..sas;'; put '%&mD.put _local_;'; put '%if %length(&name)>0 %then %let name=/&name;'; put '/* first, check if STP exists */'; put '%local tsuri;'; put '%let tsuri=stopifempty ;'; put 'data _null_;'; put 'format type uri tsuri value $200.;'; put 'call missing (of _all_);'; put 'path="&tree&name(StoredProcess)";'; put '/* first, find the STP ID */'; put 'if metadata_pathobj("",path,"StoredProcess",type,uri)>0 then do;'; put '/* get sourcecode */'; put 'cnt=1;'; put 'do while (metadata_getnasn(uri,"Notes",cnt,tsuri)>0);'; put 'rc=metadata_getattr(tsuri,"Name",value);'; put '&mD.put tsuri= value=;'; put 'if value="SourceCode" then do;'; put '/* found it! */'; put 'rc=metadata_getattr(tsuri,"Id",value);'; put 'call symputx(''tsuri'',value,''l'');'; put 'stop;'; put 'end;'; put 'cnt+1;'; put 'end;'; put 'end;'; put 'else put (_all_)(=);'; put 'run;'; put '%mp_abort(iftrue= (&tsuri=stopifempty)'; put ',mac=mm_getstpcode'; put ',msg=%str(&tree&name.(StoredProcess) not found!)'; put ')'; put '/**'; put '* Now we can extract the textstore'; put '*/'; put 'filename __getdoc temp lrecl=10000000;'; put 'proc metadata'; put 'in="$METAREPOSITORY'; put ''; put 'SAS1"'; put 'out=__getdoc ;'; put 'run;'; put '/* find the beginning of the text */'; put '%local start;'; put 'data _null_;'; put 'infile __getdoc lrecl=10000;'; put 'input;'; put 'start=index(_infile_,''StoredText="'');'; put 'if start then do;'; put 'call symputx("start",start+11);'; put '*putlog ''"'' _infile_ ''"'';'; put 'end;'; put 'stop;'; put '%local outeng;'; put '%if "&outloc"="0" %then %let outeng=TEMP;'; put '%else %let outeng="&outloc";'; put '%local fref;'; put '%if &outref=0 %then %let fref=%mf_getuniquefileref();'; put '%else %let fref=&outref;'; put '/* read the content, byte by byte, resolving escaped chars */'; put 'filename &fref &outeng lrecl=100000;'; put 'data _null_;'; put 'length filein 8 fileid 8;'; put 'filein = fopen("__getdoc","I",1,"B");'; put 'fileid = fopen("&fref","O",1,"B");'; put 'rec = "20"x;'; put 'length entity $6;'; put 'do while(fread(filein)=0);'; put 'x+1;'; put 'if x>&start then do;'; put 'rc = fget(filein,rec,1);'; put 'if rec=''"'' then leave;'; put 'else if rec="&" then do;'; put 'entity=rec;'; put 'do until (rec=";");'; put 'if fread(filein) ne 0 then goto getout;'; put 'rc = fget(filein,rec,1);'; put 'entity=cats(entity,rec);'; put 'end;'; put 'select (entity);'; put 'when (''&'' ) rec=''&'' ;'; put 'when (''<'' ) rec=''<'' ;'; put 'when (''>'' ) rec=''>'' ;'; put 'when (''''') rec="''" ;'; put 'when (''"'') rec=''"'' ;'; put 'when ('' '') rec=''0A''x;'; put 'when ('' '') rec=''0D''x;'; put 'when (''$'' ) rec=''$'' ;'; put 'when ('' '') rec=''09''x;'; put 'otherwise putlog "%str(WARN)ING: missing value for " entity=;'; put 'end;'; put 'rc =fput(fileid, substr(rec,1,1));'; put 'rc =fwrite(fileid);'; put 'end;'; put 'else do;'; put 'rc =fput(fileid,rec);'; put 'rc =fwrite(fileid);'; put 'end;'; put 'end;'; put 'end;'; put 'getout:'; put 'rc=fclose(filein);'; put 'rc=fclose(fileid);'; put 'run;'; put '%if &showlog=YES %then %do;'; put 'data _null_;'; put 'infile &fref lrecl=32767 end=last;'; put 'input;'; put 'if _n_=1 then putlog ''>>stpcodeBEGIN<<'';'; put 'putlog _infile_;'; put 'if last then putlog ''>>stpcodeEND<<'';'; put 'run;'; put '%end;'; put 'filename __getdoc clear;'; put '%if &outref=0 %then %do;'; put 'filename &fref clear;'; put '%end;'; put '%mend mm_getstpcode;'; put '%macro dc_getsettings();'; put '%global _program;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(syscc=&syscc on entry (&syswarningtext &syserrortext))'; put ')'; put '%if %length(&_PROGRAM)>1 %then %let root=&_program;'; put '%else %do;'; put '%global _metauser;'; put '%let _metauser=&sysuserid;'; put '/* to mimic a "real" _program we need to give a dummy role and stp name */'; put '%let root=/dummyRole/dummyName;'; put '%end;'; put '/* the DC precode is stored in the Admin folder in the root of'; put 'the project. Lets find that root. */'; put '%put &=root;'; put '%let root=%mf_getapploc();'; put '%put &=root;'; put '/* Now we know the root location we can retrieve the params */'; put '%let temploc=%sysfunc(pathname(work))/settings.txt;'; put '%mm_getstpcode(tree=&root/services/public'; put ',name=Data_Controller_Settings'; put ',outloc=&temploc'; put ')'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Unable to run getstpcode)'; put ')'; put 'filename _getsets "&temploc" lrecl=2000;'; put '/*'; put 'Do not use mp_include here - this puts a copy in every service, which creates'; put 'compilation problems when calling services from mp_include'; put '*/'; put '%inc _getsets/source2;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Problem running &sysmacroname (&syswarningtext &syserrortext))'; put ')'; put '%mend dc_getsettings;'; put '%macro mf_fmtdttm('; put ')/*/STORE SOURCE*/;'; put '%if "&sysver"="9.2" or "&sysver"="9.3"'; put 'or ("&sysver"="9.4" and "%substr(&SYSVLONG,9,1)" le "3")'; put 'or "%substr(&sysver,1,1)"="4"'; put 'or "%substr(&sysver,1,1)"="5"'; put '%then %do;DATETIME19.3%end;'; put '%else %do;E8601DT26.6%end;'; put '%mend mf_fmtdttm;'; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '%macro mp_init(prefix=SASJS'; put ')/*/STORE SOURCE*/;'; put '%if %symexist(SASJS_PREFIX) %then %return; /* only run once */'; put '%global'; put 'SASJS_PREFIX /* the ONLY hard-coded global macro variable in SASjs */'; put '&prefix._FUNCTIONS /* used in mcf_init() to track core function compilation */'; put '&prefix._INIT_NUM /* initialisation time as numeric */'; put '&prefix._INIT_DTTM /* initialisation time in E8601DT26.6 format */'; put '&prefix.WORK /* avoid typing %sysfunc(pathname(work)) every time */'; put ';'; put '%let sasjs_prefix=&prefix;'; put 'data _null_;'; put 'dttm=datetime();'; put 'call symputx("&sasjs_prefix._init_num",dttm,''g'');'; put 'call symputx("&sasjs_prefix._init_dttm",put(dttm,E8601DT26.6),''g'');'; put 'call symputx("&sasjs_prefix.work",pathname(''WORK''),''g'');'; put 'run;'; put 'options'; put 'compress=CHAR /* default is none so ensure we have something! */'; put 'datastmtchk=ALLKEYWORDS /* protection from overwriting input datasets */'; put 'errorcheck=STRICT /* catch errs in libname/filename statements */'; put 'fmterr /* ensure err when a format cannot be found */'; put 'mergenoby=%str(ERR)OR /* throw err when a merge has no BY variables */'; put 'missing=. /* changing this can cause hard to detect errs */'; put 'noquotelenmax /* avoid warnings for long strings */'; put 'noreplace /* avoid overwriting permanent datasets */'; put 'ps=max /* reduce log size slightly */'; put 'ls=max /* reduce log even more and avoid word truncation */'; put 'validmemname=COMPATIBLE /* avoid special characters etc in table names */'; put 'validvarname=V7 /* avoid special characters etc in variable names */'; put 'varinitchk=%str(ERR)OR /* avoid data mistakes from variable name typos */'; put 'varlenchk=%str(ERR)OR /* fail hard if truncation (data loss) can result */'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5" %then %do;'; put 'noautocorrect /* disallow misspelled procedure names */'; put 'dsoptions=note2err /* undocumented - convert bad NOTEs to ERRs */'; put '%end;'; put ';'; put '%mend mp_init;'; put '%macro mpeinit(fetch=YES);'; put '%global mpeinit'; put 'mpeadmins /* group with unrestricted Meditor access */'; put 'mpelocapprovals /* location for landing and staging files */'; put 'mpelib /* location of configuration tables for DC */'; put 'dc_repo_users /* location of user / group metadata */'; put 'dc_licence_key /* extracted in dc_getsettings */'; put 'dc_activation_key /* extracted in dc_getsettings */'; put 'dc_locale /* extracted in dc_getsettings */'; put 'dc_dttmtfmt /* can be overridden in dc_getsettings */'; put '_debug'; put ';'; put '%if &mpeinit=1 %then %return;'; put '%else %let mpeinit=1;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(Problem on service startup (&syswarningtext &syserrortext))'; put ')'; put '%mp_init()'; put '%if &fetch=YES %then %do;'; put '%webout(FETCH)'; put '%end;'; put '%global _CLIENTNAME;'; put '%mp_abort(iftrue= (&_CLIENTNAME=SAS Enterprise Guide)'; put ',mac=&_program..sas'; put ',msg=%str(Data Controller is a web app and should not be executed from EG)'; put ')'; put 'options urlencoding=utf8 nobomfile lrecl=32767;'; put '%let perf=%sysfunc(datetime());'; put '%put perfdiff: 0;'; put '%let dc_locale=SYSTEM; /* default if not set */'; put '/**'; put '* E8601DT26.6 has widest database support - but not all SAS flavours can'; put '* handle it. Override in the settings STP if needed.'; put '*/'; put 'data _null_;'; put 'dc_dttmtfmt=''"%sysfunc(datetime(),''!!"%mf_fmtdttm()"!!'')"dt'';'; put 'call symputx(''dc_dttmtfmt'',dc_dttmtfmt);'; put 'put dc_dttmtfmt=;'; put 'run;'; put '%put &=dc_dttmtfmt;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc prior to dc_getsettings)'; put ')'; put '%dc_getsettings()'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc after dc_getsettings)'; put ')'; put 'data _null_;'; put 'set &DC_LIBREF..mpe_config(where=('; put 'var_scope="DC"'; put 'and &dc_dttmtfmt lt tx_to'; put 'and var_active=1'; put '));'; put 'call symputx(var_name,var_value,''G'');'; put 'putlog var_name "=" var_value;'; put 'run;'; put '%let mpelib=&dc_libref;'; put '%let mpeadmins=&dc_admin_group;'; put '%let mpelocapprovals=&dc_staging_area;'; put '%let dc_repo_users=&dc_repo_users;'; put '%if &dc_locale ne SYSTEM %then %do;'; put 'options locale=&dc_locale;'; put '%end;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(Problem during compilation or with STP precode (&syswarningtext))'; put ')'; put '%mend mpeinit;'; put '%macro mf_mval(var);'; put '%if %symexist(&var) %then %do;'; put '%superq(&var)'; put '%end;'; put '%mend mf_mval;'; put '%macro mf_trimstr(basestr,trimstr);'; put '%local baselen trimlen trimval;'; put '/* return if basestr is shorter than trimstr (or 0) */'; put '%let baselen=%length(%superq(basestr));'; put '%let trimlen=%length(%superq(trimstr));'; put '%if &baselen < &trimlen or &baselen=0 %then %return;'; put '/* obtain the characters from the end of basestr */'; put '%let trimval=%qsubstr(%superq(basestr)'; put ',%length(%superq(basestr))-&trimlen+1'; put ',&trimlen);'; put '/* compare and if matching, chop it off! */'; put '%if %superq(basestr)=%superq(trimstr) %then %do;'; put '%return;'; put '%end;'; put '%else %if %superq(trimval)=%superq(trimstr) %then %do;'; put '%qsubstr(%superq(basestr),1,%length(%superq(basestr))-&trimlen)'; put '%end;'; put '%else %do;'; put '&basestr'; put '%end;'; put '%mend mf_trimstr;'; put '%macro mf_getplatform(switch'; put ')/*/STORE SOURCE*/;'; put '%local a b c;'; put '%if &switch.NONE=NONE %then %do;'; put '%if %symexist(sasjsprocessmode) %then %do;'; put '%if &sasjsprocessmode=Stored Program %then %do;'; put 'SASJS'; put '%return;'; put '%end;'; put '%end;'; put '%if %symexist(sysprocessmode) %then %do;'; put '%if "&sysprocessmode"="SAS Object Server"'; put 'or "&sysprocessmode"= "SAS Compute Server" %then %do;'; put 'SASVIYA'; put '%end;'; put '%else %if "&sysprocessmode"="SAS Stored Process Server"'; put 'or "&sysprocessmode"="SAS Workspace Server"'; put '%then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if %symexist(_metaport) or %symexist(_metauser) %then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if &switch=SASSTUDIO %then %do;'; put '/* return the version of SAS Studio else 0 */'; put '%if %mf_mval(_CLIENTAPP)=%str(SAS Studio) %then %do;'; put '%let a=%mf_mval(_CLIENTVERSION);'; put '%let b=%scan(&a,1,.);'; put '%if %eval(&b >2) %then %do;'; put '&b'; put '%end;'; put '%else 0;'; put '%end;'; put '%else 0;'; put '%end;'; put '%else %if &switch=VIYARESTAPI %then %do;'; put '%mf_trimstr(%sysfunc(getoption(servicesbaseurl)),/)'; put '%end;'; put '%mend mf_getplatform;'; put '%macro mpeterm();'; put '%local oldloc;'; put 'data _null_;'; put 'if symexist(''SYSPRINTTOLOG'') then oldloc=symget(''SYSPRINTTOLOG'');'; put 'else oldloc=getoption(''LOG'');'; put 'if subpad(oldloc,1,1) not in (''"'',"''",'' '') then oldloc=quote(cats(oldloc));'; put 'call symputx(''oldloc'',oldloc,''l'');'; put 'run;'; put '%if %length(&oldloc)>0 %then %do;'; put 'proc printto log=log;'; put 'run;'; put 'data _null_;'; put 'infile &oldloc;'; put 'input; putlog _infile_;'; put 'run;'; put '%end;'; put '%if %sysfunc(exist(&dc_libref..mpe_requests)) and %mf_getplatform() ne SASVIYA'; put '%then %do;'; put 'data ;'; put 'if 0 then set &dc_libref..mpe_requests;'; put 'request_dttm=%sysfunc(datetime());'; put 'request_user="%mf_getuser()";'; put 'request_service="%scan(&_program,-2,/)/%scan(&_program,-1,/)";'; put 'request_params='''';'; put 'output;stop;'; put 'proc append base=&dc_libref..mpe_requests data=&syslast force nowarn;'; put 'run;'; put '%end;'; put '%mend mpeterm;'; put '%macro mm_getGroups('; put 'user='; put ',outds=work.mm_getGroups'; put ',repo=foundation'; put ',mDebug=0'; put ')/*/STORE SOURCE*/;'; put '%local mD oldrepo;'; put '%let oldrepo=%sysfunc(getoption(metarepository));'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing mm_getGroups.sas;'; put '%&mD.put _local_;'; put '/* on some sites, user / group info is in a different metadata repo to the'; put 'default */'; put '%if &oldrepo ne &repo %then %do;'; put 'options metarepository=&repo;'; put '%end;'; put '%if %length(&user)=0 %then %do;'; put 'data &outds (keep=groupuri groupname groupdesc);'; put 'length groupuri groupname groupdesc group_or_role $256;'; put 'call missing(of _all_);'; put 'i+1;'; put 'do while'; put '(metadata_getnobj("omsobj:IdentityGroup?@Id contains ''.''",i,groupuri)>0);'; put 'rc=metadata_getattr(groupuri, "Name", groupname);'; put 'rc=metadata_getattr(groupuri, "Desc", groupdesc);'; put 'rc=metadata_getattr(groupuri,"PublicType",group_or_role);'; put 'if Group_or_Role = ''UserGroup'' then output;'; put 'i+1;'; put 'end;'; put 'run;'; put '%end;'; put '%else %do;'; put 'data &outds (keep=groupuri groupname groupdesc);'; put 'length uri groupuri groupname groupdesc group_or_role $256;'; put 'call missing(of _all_);'; put 'rc=metadata_getnobj("omsobj:Person?@Name=''&user''",1,uri);'; put 'if rc<=0 then do;'; put 'putlog "%str(WARN)ING: rc=" rc "&user not found "'; put '", or there was an issue reading the repository.";'; put 'stop;'; put 'end;'; put 'a=1;'; put 'grpassn=metadata_getnasn(uri,"IdentityGroups",a,groupuri);'; put 'if grpassn in (-3,-4) then do;'; put 'putlog "%str(WARN)ING: No metadata groups found for &user";'; put 'output;'; put 'end;'; put 'else do while (grpassn > 0);'; put 'rc=metadata_getattr(groupuri, "Name", groupname);'; put 'rc=metadata_getattr(groupuri, "Desc", groupdesc);'; put 'a+1;'; put 'rc=metadata_getattr(groupuri,"PublicType",group_or_role);'; put 'if Group_or_Role = ''UserGroup'' then output;'; put 'grpassn=metadata_getnasn(uri,"IdentityGroups",a,groupuri);'; put 'end;'; put 'run;'; put '%end;'; put '%if &oldrepo ne &repo %then %do;'; put 'options metarepository=&oldrepo;'; put '%end;'; put '%mend mm_getGroups;'; put '%macro dc_getusergroups(user=,outds=mm_getgroups);'; put '%global dc_repo_users;'; put '%if &dc_repo_users= %then %let dc_repo_users=foundation;'; put '%mm_getgroups(user=&user,outds=&outds,repo=&dc_repo_users)'; put '%mend dc_getusergroups;'; put '%macro mpe_getgroups(user=,outds=);'; put '%if not %symexist(dc_repo_users) %then %let dc_repo_users=foundation;'; put '%dc_getusergroups(user=&user,outds=&outds)'; put 'data;'; put 'length groupname groupdesc $256;'; put 'set &dc_libref..mpe_groups;'; put 'where &dc_dttmtfmt. lt tx_to;'; put 'where also upcase(user_name)="%upcase(&user)";'; put 'groupname=group_name;'; put 'groupdesc=group_desc;'; put 'keep groupname groupdesc;'; put 'run;'; put 'data &outds;'; put 'set &syslast &outds(keep=groupname groupdesc);'; put 'run;'; put '%mend mpe_getgroups;'; put '%macro mf_getuniquename(prefix=MC);'; put '&prefix.%substr(%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32-%length(&prefix))'; put '%mend mf_getuniquename;'; put '%macro mf_abort(mac=mf_abort.sas, msg=, iftrue=%str(1=1)'; put ')/des=''ungraceful abort'' /*STORE SOURCE*/;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%put NOTE: /// mf_abort macro executing //;'; put '%if %length(&mac)>0 %then %put NOTE- called by &mac;'; put '%put NOTE - &msg;'; put '%abort;'; put '%mend mf_abort;'; put '/** @endcond */'; put '%macro mf_verifymacvars('; put 'verifyVars /* list of macro variable NAMES */'; put ',makeUpcase=NO /* set to YES to make all the variable VALUES uppercase */'; put ',mAbort=SOFT'; put ')/*/STORE SOURCE*/;'; put '%local verifyIterator verifyVar abortmsg;'; put '%do verifyIterator=1 %to %sysfunc(countw(&verifyVars,%str( )));'; put '%let verifyVar=%qscan(&verifyVars,&verifyIterator,%str( ));'; put '%if not %symexist(&verifyvar) %then %do;'; put '%let abortmsg= Variable &verifyVar is MISSING;'; put '%goto exit_err;'; put '%end;'; put '%if %length(%trim(&&&verifyVar))=0 %then %do;'; put '%let abortmsg= Variable &verifyVar is EMPTY;'; put '%goto exit_err;'; put '%end;'; put '%if &makeupcase=YES %then %do;'; put '%let &verifyVar=%upcase(&&&verifyvar);'; put '%end;'; put '%end;'; put '%goto exit_success;'; put '%exit_err:'; put '%put &abortmsg;'; put '%mf_abort(iftrue=(&mabort ne SOFT),'; put 'mac=mf_verifymacvars,'; put 'msg=%str(&abortmsg)'; put ')'; put '0'; put '%return;'; put '%exit_success:'; put '1'; put '%mend mf_verifymacvars;'; put '%macro mpe_accesscheck('; put 'base_table'; put ',outds=med_accesscheck /* WORK table to contain access details */'; put ',user= /* metadata user to check for */'; put ',access_level=APPROVE'; put ',cntl_lib_var=MPELIB'; put ');'; put '%if &user= %then %let user=%mf_getuser();'; put '%mp_abort('; put 'iftrue=(%index(&outds,.)>0 and %upcase(%scan(&outds,1,.)) ne WORK)'; put ',mac=mpe_accesscheck'; put ',msg=%str(outds should be a WORK table)'; put ')'; put '%mp_abort('; put 'iftrue=(%mf_verifymacvars(base_table user access_level)=0)'; put ',mac=mpe_accesscheck'; put ',msg=%str(Missing base_table/user access_level variables)'; put ')'; put '/* make unique temp table vars */'; put '%local tempds1 tempds2;'; put '%let tempds1=%mf_getuniquename(prefix=usergroups);'; put '%let tempds2=%mf_getuniquename(prefix=tablegroups);'; put '/* get list of user groups */'; put '%mpe_getgroups(user=&user,outds=&tempds1)'; put '/* get list of groups with access for that table */'; put 'proc sql;'; put 'create table &tempds2 as'; put 'select distinct sas_group'; put 'from &&&cntl_lib_var...mpe_security'; put 'where &dc_dttmtfmt. lt tx_to'; put 'and access_level="&access_level"'; put 'and ('; put '(libref="%scan(&base_table,1,.)" and upcase(dsn)="%scan(&base_table,2,.)")'; put 'or (libref="%scan(&base_table,1,.)" and dsn="*ALL*")'; put 'or (libref="*ALL*")'; put ');'; put '%if &_debug ge 131 %then %do;'; put 'data _null_;'; put 'set &tempds1;'; put 'putlog (_all_)(=);'; put 'run;'; put 'data _null_;'; put 'set &tempds2;'; put 'putlog (_all_)(=);'; put 'run;'; put '%end;'; put 'proc sql;'; put 'create table &outds as'; put 'select * from &tempds1'; put 'where groupname="&mpeadmins"'; put 'or groupname in (select * from &tempds2);'; put '%put &sysmacroname: base_table=&base_table;'; put '%put &sysmacroname: access_level=&access_level;'; put '%mend mpe_accesscheck;'; put '%macro mf_existds(libds'; put ')/*/STORE SOURCE*/;'; put '%if %sysfunc(exist(&libds)) ne 1 & %sysfunc(exist(&libds,VIEW)) ne 1 %then 0;'; put '%else 1;'; put '%mend mf_existds;'; put '%macro mf_getvarlist(libds'; put ',dlm=%str( )'; put ',quote=no'; put ',typefilter=A'; put ')/*/STORE SOURCE*/;'; put '/* declare local vars */'; put '%local outvar dsid nvars x rc dlm q var vtype;'; put '/* credit Rowland Hale - byte34 is double quote, 39 is single quote */'; put '%if %upcase("e)=DOUBLE %then %let q=%qsysfunc(byte(34));'; put '%else %if %upcase("e)=SINGLE %then %let q=%qsysfunc(byte(39));'; put '/* open dataset in macro */'; put '%let dsid=%sysfunc(open(&libds));'; put '%if &dsid %then %do;'; put '%let nvars=%sysfunc(attrn(&dsid,NVARS));'; put '%if &nvars>0 %then %do;'; put '/* add variables with supplied delimeter */'; put '%do x=1 %to &nvars;'; put '/* get variable type */'; put '%let vtype=%sysfunc(vartype(&dsid,&x));'; put '%if &vtype=&typefilter or &typefilter=A %then %do;'; put '%let var=&q.%sysfunc(varname(&dsid,&x))&q.;'; put '%if &var=&q&q %then %do;'; put '%put &sysmacroname: Empty column found in &libds!;'; put '%let var=&q. &q.;'; put '%end;'; put '%if %quote(&outvar)=%quote() %then %let outvar=&var;'; put '%else %let outvar=&outvar.&dlm.&var.;'; put '%end;'; put '%end;'; put '%end;'; put '%let rc=%sysfunc(close(&dsid));'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: Unable to open &libds (rc=&dsid);'; put '%put &sysmacroname: SYSMSG= %sysfunc(sysmsg());'; put '%let rc=%sysfunc(close(&dsid));'; put '%end;'; put '%do;%unquote(&outvar)%end;'; put '%mend mf_getvarlist;'; put '%macro mf_wordsInStr1ButNotStr2('; put 'Str1= /* string containing words to extract */'; put ',Str2= /* used to compare with the extract string */'; put ')/*/STORE SOURCE*/;'; put '%local count_base count_extr i i2 extr_word base_word match outvar;'; put '%if %length(&str1)=0 or %length(&str2)=0 %then %do;'; put '%put base string (str1)= &str1;'; put '%put compare string (str2) = &str2;'; put '%return;'; put '%end;'; put '%let count_base=%sysfunc(countw(&Str2));'; put '%let count_extr=%sysfunc(countw(&Str1));'; put '%do i=1 %to &count_extr;'; put '%let extr_word=%scan(&Str1,&i,%str( ));'; put '%let match=0;'; put '%do i2=1 %to &count_base;'; put '%let base_word=%scan(&Str2,&i2,%str( ));'; put '%if &extr_word=&base_word %then %let match=1;'; put '%end;'; put '%if &match=0 %then %let outvar=&outvar &extr_word;'; put '%end;'; put '&outvar'; put '%mend mf_wordsInStr1ButNotStr2;'; put '%macro mf_isblank(param'; put ')/*/STORE SOURCE*/;'; put '%sysevalf(%superq(param)=,boolean)'; put '%mend mf_isblank;'; put '%macro mp_dropmembers('; put 'list /* space separated list of datasets / views */'; put ',libref=WORK /* can only drop from a single library at a time */'; put ',iftrue=%str(1=1)'; put ')/*/STORE SOURCE*/;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%if %mf_isblank(&list) %then %do;'; put '%put NOTE: nothing to drop!;'; put '%return;'; put '%end;'; put 'proc datasets lib=&libref nolist;'; put 'delete &list;'; put 'delete &list /mtype=view;'; put 'run;'; put '%mend mp_dropmembers;'; put '%macro mp_dirlist(path=%sysfunc(pathname(work))'; put ', fref=0'; put ', outds=work.mp_dirlist'; put ', getattrs=NO'; put ', showparent=NO'; put ', maxdepth=0'; put ', level=0 /* The level of recursion to perform. For internal use only. */'; put ')/*/STORE SOURCE*/;'; put '%let getattrs=%upcase(&getattrs)XX;'; put '/* temp table */'; put '%local out_ds;'; put 'data;run;'; put '%let out_ds=%str(&syslast);'; put '/* drop main (top) table if it exists */'; put '%if &level=0 %then %do;'; put '%mp_dropmembers(%scan(&outds,-1,.), libref=WORK)'; put '%end;'; put 'data &out_ds(compress=no'; put 'keep=file_or_folder filepath filename ext msg directory level'; put ');'; put 'length directory filepath $500 fref fref2 $8 file_or_folder $6 filename $80'; put 'ext $20 msg $200 foption $16;'; put 'if _n_=1 then call missing(of _all_);'; put 'retain level &level;'; put '%if &fref=0 %then %do;'; put 'rc = filename(fref, "&path");'; put '%end;'; put '%else %do;'; put 'fref="&fref";'; put 'rc=0;'; put '%end;'; put 'if rc = 0 then do;'; put 'did = dopen(fref);'; put 'if did=0 then do;'; put 'putlog "NOTE: This directory is empty, or does not exist - &path";'; put 'msg=sysmsg();'; put 'put (_all_)(=);'; put 'stop;'; put 'end;'; put '/* attribute is OS-dependent - could be "Directory" or "Directory Name" */'; put 'numopts=doptnum(did);'; put 'do i=1 to numopts;'; put 'foption=doptname(did,i);'; put 'if foption=:''Directory'' then i=numopts;'; put 'end;'; put 'directory=dinfo(did,foption);'; put 'rc = filename(fref);'; put 'end;'; put 'else do;'; put 'msg=sysmsg();'; put 'put _all_;'; put 'stop;'; put 'end;'; put 'dnum = dnum(did);'; put 'do i = 1 to dnum;'; put 'filename = dread(did, i);'; put 'filepath=cats(directory,''/'',filename);'; put 'rc = filename(fref2,filepath);'; put 'midd=dopen(fref2);'; put 'dmsg=sysmsg();'; put 'if did > 0 then file_or_folder=''folder'';'; put 'rc=dclose(midd);'; put 'midf=fopen(fref2);'; put 'fmsg=sysmsg();'; put 'if midf > 0 then file_or_folder=''file'';'; put 'rc=fclose(midf);'; put 'if index(fmsg,''File is in use'') or index(dmsg,''is not a directory'')'; put 'then file_or_folder=''file'';'; put 'else if index(fmsg,''Insufficient authorization'') then file_or_folder=''file'';'; put 'else if file_or_folder='''' then file_or_folder=''locked'';'; put 'if file_or_folder=''file'' then do;'; put 'ext = prxchange(''s/.*\.{1,1}(.*)/$1/'', 1, filename);'; put 'if filename = ext then ext = '' '';'; put 'end;'; put 'else do;'; put 'ext='''';'; put 'file_or_folder=''folder'';'; put 'end;'; put 'output;'; put 'end;'; put 'rc = dclose(did);'; put '%if &showparent=YES and &level=0 %then %do;'; put 'filepath=directory;'; put 'file_or_folder=''folder'';'; put 'ext='''';'; put 'filename=scan(directory,-1,''/\'');'; put 'msg='''';'; put 'level=&level;'; put 'output;'; put '%end;'; put 'stop;'; put 'run;'; put '%if %substr(&getattrs,1,1)=Y %then %do;'; put 'data &out_ds;'; put 'set &out_ds;'; put 'length infoname infoval $60 fref $8;'; put 'if _n_=1 then call missing(fref);'; put 'rc=filename(fref,filepath);'; put 'drop rc infoname fid i close fref;'; put 'if file_or_folder=''file'' then do;'; put 'fid=fopen(fref);'; put 'if fid le 0 then do;'; put 'msg=sysmsg();'; put 'putlog "Could not open file:" filepath fid= ;'; put 'sasname=''_MCNOTVALID_'';'; put 'output;'; put 'end;'; put 'else do i=1 to foptnum(fid);'; put 'infoname=foptname(fid,i);'; put 'infoval=finfo(fid,infoname);'; put 'sasname=compress(infoname, ''_'', ''adik'');'; put 'if anydigit(sasname)=1 then sasname=substr(sasname,anyalpha(sasname));'; put 'if upcase(sasname) ne ''FILENAME'' then output;'; put 'end;'; put 'close=fclose(fid);'; put 'end;'; put 'else do;'; put 'fid=dopen(fref);'; put 'if fid le 0 then do;'; put 'msg=sysmsg();'; put 'putlog "Could not open folder:" filepath fid= ;'; put 'sasname=''_MCNOTVALID_'';'; put 'output;'; put 'end;'; put 'else do i=1 to doptnum(fid);'; put 'infoname=doptname(fid,i);'; put 'infoval=dinfo(fid,infoname);'; put 'sasname=compress(infoname, ''_'', ''adik'');'; put 'if anydigit(sasname)=1 then sasname=substr(sasname,anyalpha(sasname));'; put 'if upcase(sasname) ne ''FILENAME'' then output;'; put 'end;'; put 'close=dclose(fid);'; put 'end;'; put 'run;'; put 'proc sort;'; put 'by filepath sasname;'; put 'proc transpose data=&out_ds out=&out_ds(drop=_:);'; put 'id sasname;'; put 'var infoval;'; put 'by filepath file_or_folder filename ext ;'; put 'run;'; put '%end;'; put 'data &out_ds;'; put 'set &out_ds(where=(filepath ne ''''));'; put 'run;'; put '/**'; put '* The above transpose can mean that some updates create additional columns.'; put '* This necessitates the occasional use of datastep over proc append.'; put '*/'; put '%if %mf_existds(&outds) %then %do;'; put '%local basevars appvars newvars;'; put '%let basevars=%mf_getvarlist(&outds);'; put '%let appvars=%mf_getvarlist(&out_ds);'; put '%let newvars=%length(%mf_wordsinstr1butnotstr2(Str1=&appvars,Str2=&basevars));'; put '%if &newvars>0 %then %do;'; put 'data &outds;'; put 'set &outds &out_ds;'; put 'run;'; put '%end;'; put '%else %do;'; put 'proc append base=&outds data=&out_ds force nowarn;'; put 'run;'; put '%end;'; put '%end;'; put '%else %do;'; put 'proc append base=&outds data=&out_ds;'; put 'run;'; put '%end;'; put '/* recursive call */'; put '%if &maxdepth>&level or &maxdepth=MAX %then %do;'; put 'data _null_;'; put 'set &out_ds;'; put 'where file_or_folder=''folder'';'; put '%if &showparent=YES and &level=0 %then %do;'; put 'if filepath ne directory;'; put '%end;'; put 'length code $10000;'; put 'code=cats(''%nrstr(%mp_dirlist(path='',filepath,",outds=&outds"'; put ',",getattrs=&getattrs,level=%eval(&level+1),maxdepth=&maxdepth))");'; put 'put code=;'; put 'call execute(code);'; put 'run;'; put '%end;'; put '/* tidy up */'; put 'proc sql;'; put 'drop table &out_ds;'; put '%mend mp_dirlist;'; put '%macro mp_binarycopy('; put 'inloc= /* full path and filename of the object to be copied */'; put ',outloc= /* full path and filename of object to be created */'; put ',inref=____in /* override default to use own filerefs */'; put ',outref=____out /* override default to use own filerefs */'; put ',mode=CREATE'; put ',iftrue=%str(1=1)'; put ')/*/STORE SOURCE*/;'; put '%local mod;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%if &mode=APPEND %then %let mod=mod;'; put '/* these IN and OUT filerefs can point to anything */'; put '%if &inref = ____in %then %do;'; put 'filename &inref &inloc lrecl=1048576 ;'; put '%end;'; put '%if &outref=____out %then %do;'; put 'filename &outref &outloc lrecl=1048576 &mod;'; put '%end;'; put '/* copy the file byte-for-byte */'; put 'data _null_;'; put 'infile &inref lrecl=1 recfm=n;'; put 'file &outref &mod recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put '%if &inref = ____in %then %do;'; put 'filename &inref clear;'; put '%end;'; put '%if &outref=____out %then %do;'; put 'filename &outref clear;'; put '%end;'; put '%mend mp_binarycopy;'; put '%macro mf_getattrn('; put 'libds'; put ',attr'; put ')/*/STORE SOURCE*/;'; put '%local dsid rc;'; put '%let dsid=%sysfunc(open(&libds,is));'; put '%if &dsid = 0 %then %do;'; put '%put %str(WARN)ING: Cannot open %trim(&libds), system message below;'; put '%put %sysfunc(sysmsg());'; put '-1'; put '%end;'; put '%else %do;'; put '%sysfunc(attrn(&dsid,&attr))'; put '%let rc=%sysfunc(close(&dsid));'; put '%end;'; put '%mend mf_getattrn;'; put '%macro mfs_httpheader(header_name'; put ',header_value'; put ')/*/STORE SOURCE*/;'; put '%global sasjs_stpsrv_header_loc;'; put '%local fref fid i;'; put '%if %sysfunc(filename(fref,&sasjs_stpsrv_header_loc)) ne 0 %then %do;'; put '%put &=fref &=sasjs_stpsrv_header_loc;'; put '%put %str(ERR)OR: %sysfunc(sysmsg());'; put '%return;'; put '%end;'; put '%let fid=%sysfunc(fopen(&fref,A));'; put '%if &fid=0 %then %do;'; put '%put %str(ERR)OR: %sysfunc(sysmsg());'; put '%return;'; put '%end;'; put '%let rc=%sysfunc(fput(&fid,%str(&header_name): %str(&header_value)));'; put '%let rc=%sysfunc(fwrite(&fid));'; put '%let rc=%sysfunc(fclose(&fid));'; put '%let rc=%sysfunc(filename(&fref));'; put '%mend mfs_httpheader;'; put '%macro mp_streamfile('; put 'contenttype=TEXT'; put ',inloc='; put ',inref=0'; put ',iftrue=%str(1=1)'; put ',outname='; put ',outref=_webout'; put ')/*/STORE SOURCE*/;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%let contentype=%upcase(&contenttype);'; put '%let outref=%upcase(&outref);'; put '%local platform; %let platform=%mf_getplatform();'; put '/**'; put '* check engine type to avoid the below err message:'; put '* > Function is only valid for filerefs using the CACHE access method.'; put '*/'; put '%local streamweb;'; put '%let streamweb=0;'; put 'data _null_;'; put 'set sashelp.vextfl(where=(upcase(fileref)="&outref"));'; put 'if xengine=''STREAM'' then call symputx(''streamweb'',1,''l'');'; put 'run;'; put '%if &contentype=CSV %then %do;'; put '%if (&platform=SASMETA and &streamweb=1) %then %do;'; put 'data _null_;'; put 'rc=stpsrv_header(''Content-Type'',''application/csv'');'; put 'rc=stpsrv_header(''Content-disposition'',"attachment; filename=&outname");'; put 'run;'; put '%end;'; put '%else %if &platform=SASVIYA %then %do;'; put 'filename &outref filesrvc parenturi="&SYS_JES_JOB_URI" name=''_webout.txt'''; put 'contenttype=''application/csv'''; put 'contentdisp="attachment; filename=&outname";'; put '%end;'; put '%else %if &platform=SASJS %then %do;'; put '%mfs_httpheader(Content-Type,application/csv)'; put '%mfs_httpheader(Content-disposition,%str(attachment; filename=&outname))'; put '%end;'; put '%end;'; put '%else %if &contentype=EXCEL %then %do;'; put '/* suitable for XLS format */'; put '%if (&platform=SASMETA and &streamweb=1) %then %do;'; put 'data _null_;'; put 'rc=stpsrv_header(''Content-Type'',''application/vnd.ms-excel'');'; put 'rc=stpsrv_header(''Content-disposition'',"attachment; filename=&outname");'; put 'run;'; put '%end;'; put '%else %if &platform=SASVIYA %then %do;'; put 'filename &outref filesrvc parenturi="&SYS_JES_JOB_URI" name=''_webout.xls'''; put 'contenttype=''application/vnd.ms-excel'''; put 'contentdisp="attachment; filename=&outname";'; put '%end;'; put '%else %if &platform=SASJS %then %do;'; put '%mfs_httpheader(Content-Type,application/vnd.ms-excel)'; put '%mfs_httpheader(Content-disposition,%str(attachment; filename=&outname))'; put '%end;'; put '%end;'; put '%else %if &contentype=GIF or &contentype=JPEG or &contentype=PNG %then %do;'; put '%if (&platform=SASMETA and &streamweb=1) %then %do;'; put 'data _null_;'; put 'rc=stpsrv_header(''Content-Type'',"image/%lowcase(&contenttype)");'; put 'run;'; put '%end;'; put '%else %if &platform=SASVIYA %then %do;'; put 'filename &outref filesrvc parenturi="&SYS_JES_JOB_URI"'; put 'contenttype="image/%lowcase(&contenttype)";'; put '%end;'; put '%else %if &platform=SASJS %then %do;'; put '%mfs_httpheader(Content-Type,image/%lowcase(&contenttype))'; put '%end;'; put '%end;'; put '%else %if &contentype=HTML or &contenttype=MARKDOWN %then %do;'; put '%if (&platform=SASMETA and &streamweb=1) %then %do;'; put 'data _null_;'; put 'rc=stpsrv_header(''Content-Type'',"text/%lowcase(&contenttype)");'; put 'rc=stpsrv_header(''Content-disposition'',"attachment; filename=&outname");'; put 'run;'; put '%end;'; put '%else %if &platform=SASVIYA %then %do;'; put 'filename &outref filesrvc parenturi="&SYS_JES_JOB_URI" name="_webout.json"'; put 'contenttype="text/%lowcase(&contenttype)"'; put 'contentdisp="attachment; filename=&outname";'; put '%end;'; put '%else %if &platform=SASJS %then %do;'; put '%mfs_httpheader(Content-Type,text/%lowcase(&contenttype))'; put '%mfs_httpheader(Content-disposition,%str(attachment; filename=&outname))'; put '%end;'; put '%end;'; put '%else %if &contentype=TEXT %then %do;'; put '%if (&platform=SASMETA and &streamweb=1) %then %do;'; put 'data _null_;'; put 'rc=stpsrv_header(''Content-Type'',''application/text'');'; put 'rc=stpsrv_header(''Content-disposition'',"attachment; filename=&outname");'; put 'run;'; put '%end;'; put '%else %if &platform=SASVIYA %then %do;'; put 'filename &outref filesrvc parenturi="&SYS_JES_JOB_URI" name=''_webout.txt'''; put 'contenttype=''application/text'''; put 'contentdisp="attachment; filename=&outname";'; put '%end;'; put '%else %if &platform=SASJS %then %do;'; put '%mfs_httpheader(Content-Type,application/text)'; put '%mfs_httpheader(Content-disposition,%str(attachment; filename=&outname))'; put '%end;'; put '%end;'; put '%else %if &contentype=WOFF or &contentype=WOFF2 or &contentype=TTF %then %do;'; put '%if (&platform=SASMETA and &streamweb=1) %then %do;'; put 'data _null_;'; put 'rc=stpsrv_header(''Content-Type'',"font/%lowcase(&contenttype)");'; put 'run;'; put '%end;'; put '%else %if &platform=SASVIYA %then %do;'; put 'filename &outref filesrvc parenturi="&SYS_JES_JOB_URI"'; put 'contenttype="font/%lowcase(&contenttype)";'; put '%end;'; put '%else %if &platform=SASJS %then %do;'; put '%mfs_httpheader(Content-Type,font/%lowcase(&contenttype))'; put '%end;'; put '%end;'; put '%else %if &contentype=XLSX %then %do;'; put '%if (&platform=SASMETA and &streamweb=1) %then %do;'; put 'data _null_;'; put 'rc=stpsrv_header(''Content-Type'','; put '''application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'');'; put 'rc=stpsrv_header(''Content-disposition'',"attachment; filename=&outname");'; put 'run;'; put '%end;'; put '%else %if &platform=SASVIYA %then %do;'; put 'filename &outref filesrvc parenturi="&SYS_JES_JOB_URI" name=''_webout.xls'''; put 'contenttype='; put '''application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'''; put 'contentdisp="attachment; filename=&outname";'; put '%end;'; put '%else %if &platform=SASJS %then %do;'; put '%mfs_httpheader(Content-Type'; put ',application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'; put ')'; put '%mfs_httpheader(Content-disposition,%str(attachment; filename=&outname))'; put '%end;'; put '%end;'; put '%else %if &contentype=ZIP %then %do;'; put '%if (&platform=SASMETA and &streamweb=1) %then %do;'; put 'data _null_;'; put 'rc=stpsrv_header(''Content-Type'',''application/zip'');'; put 'rc=stpsrv_header(''Content-disposition'',"attachment; filename=&outname");'; put 'run;'; put '%end;'; put '%else %if &platform=SASVIYA %then %do;'; put 'filename &outref filesrvc parenturi="&SYS_JES_JOB_URI" name=''_webout.zip'''; put 'contenttype=''application/zip'''; put 'contentdisp="attachment; filename=&outname";'; put '%end;'; put '%else %if &platform=SASJS %then %do;'; put '%mfs_httpheader(Content-Type,application/zip)'; put '%mfs_httpheader(Content-disposition,%str(attachment; filename=&outname))'; put '%end;'; put '%end;'; put '%else %do;'; put '%put %str(ERR)OR: Content Type &contenttype NOT SUPPORTED by &sysmacroname!;'; put '%end;'; put '%if &inref ne 0 %then %do;'; put '%mp_binarycopy(inref=&inref,outref=&outref)'; put '%end;'; put '%else %do;'; put '%mp_binarycopy(inloc="&inloc",outref=&outref)'; put '%end;'; put '%mend mp_streamfile;'; put '* SAS Macros end;'; put '* SAS Includes start;'; put '* SAS Includes end;'; put '* Binary Files start;'; put '* Binary Files end;'; put '* ServiceInit start;'; put 'options noquotelenmax ps=max;'; put '/* create dummy macros to prevent stpbegin / stpend from corrupting sessions */'; put '%macro stpbegin();'; put '%put NOTE: the STPBEGIN macro should not be used for web apps!;'; put '%mend stpbegin;'; put '%macro stpend();'; put '%put NOTE: the STPEND macro should not be used for web apps!;'; put '%mend stpend;'; put '* ServiceInit end;'; put '* Service start;'; put '/**'; put '@file getauditfile.sas'; put '@brief Downloads a zip file containing audit info.'; put '@details The staging location from the &mpelocapprovals location'; put 'is zipped and returned as a file download. A user can only request the'; put 'audit pack if they have EDIT or APPROVE rights on the target table.'; put '

SAS Macros

'; put '@li mf_getuser.sas'; put '@li mf_verifymacvars.sas'; put '@li mpe_accesscheck.sas'; put '@li mp_abort.sas'; put '@li mp_dirlist.sas'; put '@li mp_binarycopy.sas'; put '@li mf_getattrn.sas'; put '@li mp_streamfile.sas'; put '@version 9.2'; put '@author 4GL Apps Ltd'; put '@copyright 4GL Apps Ltd. This code may only be used within Data Controller'; put 'and may not be re-distributed or re-sold without the express permission of'; put '4GL Apps Ltd.'; put '**/'; put '%mpeinit()'; put 'options mprint;'; put '/* security checks */'; put '%let user=%mf_getuser();'; put 'proc sql noprint;'; put 'select cats(base_lib,''.'',base_ds) into: libds'; put 'from &mpelib..mpe_submit'; put 'where table_id="&table";'; put '%mp_abort('; put 'iftrue=(%mf_verifymacvars(libds table)=0)'; put ',mac=&_program'; put ',msg=%str(Missing: libds table)'; put ')'; put '%mpe_accesscheck(&libds,outds=authEDIT,user=&user,access_level=EDIT);'; put '%mpe_accesscheck(&libds,outds=authAPP,user=&user,access_level=APPROVE);'; put '%mp_abort('; put 'iftrue=('; put '%mf_getattrn(work.authEDIT,NLOBS)=0 & %mf_getattrn(work.authAPP,NLOBS)=0'; put ')'; put ',mac=mpestp_audit'; put ',msg=%str(&user not authorised to download audit data for &table)'; put ')'; put 'ods package(ProdOutput) open nopf;'; put 'options notes source2 mprint;'; put '%let table=%unquote(&table);'; put '%mp_dirlist(outds=dirs, path=&mpelocapprovals/&TABLE);'; put 'data _null_;'; put 'set dirs;'; put 'retain str1'; put '"ods package(ProdOutput) add file=''&mpelocapprovals/&TABLE/";'; put 'retain str2 "'' mimetype=''text/plain'' path=''contents/'';";'; put 'call execute(cats(str1,filename,str2));'; put 'run;'; put '%let archive_path=%sysfunc(pathname(work));'; put 'ods package(ProdOutput) publish archive properties'; put '(archive_name= "&table..zip" archive_path="&archive_path");'; put 'ods package(ProdOutput) close;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%nrstr(syscc=&syscc)'; put ')'; put '/* now serve zip file to client */'; put '%mp_streamfile(contenttype=ZIP'; put ',inloc=%str(&archive_path/&table..zip)'; put ',outname=&table..zip'; put ')'; put '%mpeterm()'; put '* Service end;'; run; %mm_createwebservice(path=&appLoc/&path, name=&service, code=sascode, server=&serverName, replace=yes) filename sascode clear; %let service=getdiffs; filename sascode temp lrecl=32767; data _null_; file sascode; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '/**'; put '@file mp_jsonout.sas'; put '@brief Writes JSON in SASjs format to a fileref'; put '@details This macro can be used to OPEN a JSON stream and send one or more'; put 'tables as arrays of rows, where each row can be an object or a nested array.'; put 'There are two engines available - DATASTEP or PROCJSON.'; put 'PROC JSON is fast but will produce errs like the ones below if'; put 'special chars are encountered.'; put '> (ERR)OR: Some code points did not transcode.'; put '> An object or array close is not valid at this point in the JSON text.'; put '> Date value out of range'; put 'If this happens, try running with ENGINE=DATASTEP.'; put 'The DATASTEP engine is used to handle special SAS missing numerics, and'; put 'can also convert entire datasets to formatted values. Output JSON is always'; put 'in UTF-8.'; put 'Usage:'; put 'filename tmp temp;'; put 'data class; set sashelp.class;run;'; put '%mp_jsonout(OPEN,jref=tmp)'; put '%mp_jsonout(OBJ,class,jref=tmp)'; put '%mp_jsonout(OBJ,class,dslabel=class2,jref=tmp,showmeta=Y)'; put '%mp_jsonout(CLOSE,jref=tmp)'; put 'data _null_;'; put 'infile tmp;'; put 'input;putlog _infile_;'; put 'run;'; put 'If you are building web apps with SAS then you are strongly encouraged to use'; put 'the mX_createwebservice macros in combination with the'; put '[sasjs adapter](https://github.com/sasjs/adapter).'; put 'For more information see https://sasjs.io'; put '@param [in] action Valid values:'; put '@li OPEN - opens the JSON'; put '@li OBJ - sends a table with each row as an object'; put '@li ARR - sends a table with each row in an array'; put '@li CLOSE - closes the JSON'; put '@param [in] ds The dataset to send. Must be a work table.'; put '@param [out] jref= (_webout) The fileref to which to send the JSON'; put '@param [out] dslabel= The name to give the table in the exported JSON'; put '@param [in] fmt= (Y) Whether to keep (Y) or strip (N) formats from the table'; put '@param [in] engine= (DATASTEP) Which engine to use to send the JSON. Options:'; put '@li PROCJSON (default)'; put '@li DATASTEP (more reliable when data has non standard characters)'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to JSON'; put '

Related Files

'; put '@li mp_ds2fmtds.sas'; put '@version 9.2'; put '@author Allan Bowe'; put '@source https://github.com/sasjs/core'; put '**/'; put '%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y'; put ',engine=DATASTEP'; put ',missing=NULL'; put ',showmeta=N'; put ',maxobs=MAX'; put ')/*/STORE SOURCE*/;'; put '%local tempds colinfo fmtds i numcols numobs stmt_obs lastobs optval'; put 'tmpds1 tmpds2 tmpds3 tmpds4;'; put '%let numcols=0;'; put '%if &maxobs ne MAX %then %let stmt_obs=%str(if _n_>&maxobs then stop;);'; put '%if &action=OPEN %then %do;'; put 'options nobomfile;'; put 'data _null_;file &jref encoding=''utf-8'' lrecl=200;'; put 'put ''{"PROCESSED_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''"'';'; put 'run;'; put '%end;'; put '%else %if (&action=ARR or &action=OBJ) %then %do;'; put '/* force variable names to always be uppercase in the JSON */'; put 'options validvarname=upcase;'; put '/* To avoid issues with _webout on EBI - such as encoding diffs and truncation'; put '(https://support.sas.com/kb/49/325.html) we use temporary files */'; put 'filename _sjs1 temp lrecl=200 ;'; put 'data _null_; file _sjs1 encoding=''utf-8'';'; put 'put ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":";'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjs1 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs1 clear;'; put '/* grab col defs */'; put 'proc contents noprint data=&ds'; put 'out=_data_(keep=name type length format formatl formatd varnum label);'; put 'run;'; put '%let colinfo=%scan(&syslast,2,.);'; put 'proc sort data=&colinfo;'; put 'by varnum;'; put 'run;'; put '/* move meta to mac vars */'; put 'data &colinfo;'; put 'if _n_=1 then call symputx(''numcols'',nobs,''l'');'; put 'set &colinfo end=last nobs=nobs;'; put 'name=upcase(name);'; put '/* fix formats */'; put 'if type=2 or type=6 then do;'; put 'typelong=''char'';'; put 'length fmt $49.;'; put 'if format='''' then fmt=cats(''$'',length,''.'');'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else fmt=cats(format,formatl,''.'');'; put 'end;'; put 'else do;'; put 'typelong=''num'';'; put 'if format='''' then fmt=''best.'';'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else if formatd=0 then fmt=cats(format,formatl,''.'');'; put 'else fmt=cats(format,formatl,''.'',formatd);'; put 'end;'; put '/* 32 char unique name */'; put 'newname=''sasjs''!!substr(cats(put(md5(name),$hex32.)),1,27);'; put 'call symputx(cats(''name'',_n_),name,''l'');'; put 'call symputx(cats(''newname'',_n_),newname,''l'');'; put 'call symputx(cats(''length'',_n_),length,''l'');'; put 'call symputx(cats(''fmt'',_n_),fmt,''l'');'; put 'call symputx(cats(''type'',_n_),type,''l'');'; put 'call symputx(cats(''typelong'',_n_),typelong,''l'');'; put 'call symputx(cats(''label'',_n_),coalescec(label,name),''l'');'; put '/* overwritten when fmt=Y and a custom format exists in catalog */'; put 'if typelong=''num'' then call symputx(cats(''fmtlen'',_n_),200,''l'');'; put 'else call symputx(cats(''fmtlen'',_n_),min(32767,ceil((length+10)*1.5)),''l'');'; put 'run;'; put '%let tempds=%substr(_%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql;'; put 'select count(*) into: lastobs from &ds;'; put '%if &maxobs ne MAX %then %let lastobs=%sysfunc(min(&lastobs,&maxobs));'; put '%if &engine=PROCJSON %then %do;'; put '%if &missing=STRING %then %do;'; put '%put &sysmacroname: Special Missings not supported in proc json.;'; put '%put &sysmacroname: Switching to DATASTEP engine;'; put '%goto datastep;'; put '%end;'; put 'data &tempds;'; put 'set &ds;'; put '&stmt_obs;'; put '%if &fmt=N %then format _numeric_ best32.;;'; put '/* PRETTY is necessary to avoid line truncation in large files */'; put 'filename _sjs2 temp lrecl=131068 encoding=''utf-8'';'; put 'proc json out=_sjs2 pretty'; put '%if &action=ARR %then nokeys ;'; put ';export &tempds / nosastags fmtnumeric;'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs2 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs2 clear;'; put '%end;'; put '%else %if &engine=DATASTEP %then %do;'; put '%datastep:'; put '%if %sysfunc(exist(&ds)) ne 1 & %sysfunc(exist(&ds,VIEW)) ne 1'; put '%then %do;'; put '%put &sysmacroname: &ds NOT FOUND!!!;'; put '%return;'; put '%end;'; put '%if &fmt=Y %then %do;'; put '/**'; put '* Extract format definitions'; put '* First, by getting library locations from dictionary.formats'; put '* Then, by exporting the width using proc format'; put '* Cannot use maxw from sashelp.vformat as not always populated'; put '* Cannot use fmtinfo() as not supported in all flavours'; put '*/'; put '%let tmpds1=%substr(fmtsum%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds2=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds3=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds4=%substr(col%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql noprint;'; put 'create table &tmpds1 as'; put 'select cats(libname,''.'',memname) as FMTCAT,'; put 'FMTNAME'; put 'from dictionary.formats'; put 'where fmttype=''F'' and libname is not null'; put 'and fmtname in (select format from &colinfo where format is not null)'; put 'order by 1;'; put 'create table &tmpds2('; put 'FMTNAME char(32),'; put 'LENGTH num'; put ');'; put '%local catlist cat fmtlist i;'; put 'select distinct fmtcat into: catlist separated by '' '' from &tmpds1;'; put '%do i=1 %to %sysfunc(countw(&catlist,%str( )));'; put '%let cat=%scan(&catlist,&i,%str( ));'; put 'proc sql;'; put 'select distinct fmtname into: fmtlist separated by '' '''; put 'from &tmpds1 where fmtcat="&cat";'; put 'proc format lib=&cat cntlout=&tmpds3(keep=fmtname length);'; put 'select &fmtlist;'; put 'run;'; put 'proc sql;'; put 'insert into &tmpds2 select distinct fmtname,length from &tmpds3;'; put '%end;'; put 'proc sql;'; put 'create table &tmpds4 as'; put 'select a.*, b.length as MAXW'; put 'from &colinfo a'; put 'left join &tmpds2 b'; put 'on cats(a.format)=cats(upcase(b.fmtname))'; put 'order by a.varnum;'; put 'data _null_;'; put 'set &tmpds4;'; put 'if not missing(maxw);'; put 'call symputx('; put 'cats(''fmtlen'',_n_),'; put '/* vars need extra padding due to JSON escaping of special chars */'; put 'min(32767,ceil((max(length,maxw)+10)*1.5))'; put ',''l'''; put ');'; put 'run;'; put '/* configure varlenchk - as we are explicitly shortening the variables */'; put '%let optval=%sysfunc(getoption(varlenchk));'; put 'options varlenchk=NOWARN;'; put 'data _data_(compress=char);'; put '/* shorten the new vars */'; put 'length'; put '%do i=1 %to &numcols;'; put '&&name&i $&&fmtlen&i'; put '%end;'; put ';'; put '/* rename on entry */'; put 'set &ds(rename=('; put '%do i=1 %to &numcols;'; put '&&name&i=&&newname&i'; put '%end;'; put '));'; put '&stmt_obs;'; put 'drop'; put '%do i=1 %to &numcols;'; put '&&newname&i'; put '%end;'; put ';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=num %then %do;'; put '&&name&i=cats(put(&&newname&i,&&fmt&i));'; put '%end;'; put '%else %do;'; put '&&name&i=put(&&newname&i,&&fmt&i);'; put '%end;'; put '%end;'; put 'if _error_ then do;'; put 'call symputx(''syscc'',1012);'; put 'stop;'; put 'end;'; put 'run;'; put '%let fmtds=&syslast;'; put 'options varlenchk=&optval;'; put '%end;'; put 'proc format; /* credit yabwon for special null removal */'; put 'value bart (default=40)'; put '%if &missing=NULL %then %do;'; put '._ - .z = null'; put '%end;'; put '%else %do;'; put '._ = [quote()]'; put '. = null'; put '.a - .z = [quote()]'; put '%end;'; put 'other = [best.];'; put 'data &tempds;'; put 'attrib _all_ label='''';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'length &&name&i $&&fmtlen&i...;'; put 'format &&name&i $&&fmtlen&i...;'; put '%end;'; put '%end;'; put '%if &fmt=Y %then %do;'; put 'set &fmtds;'; put '%end;'; put '%else %do;'; put 'set &ds;'; put '%end;'; put '&stmt_obs;'; put 'format _numeric_ bart.;'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'if findc(&&name&i,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put '&&name&i=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,&&name&i)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else &&name&i=quote(cats(&&name&i));'; put '%end;'; put '%end;'; put 'run;'; put 'filename _sjs3 temp lrecl=131068 ;'; put 'data _null_;'; put 'file _sjs3 encoding=''utf-8'';'; put 'if _n_=1 then put "[";'; put 'set &tempds;'; put 'if _n_>1 then put "," @; put'; put '%if &action=ARR %then "[" ; %else "{" ;'; put '%do i=1 %to &numcols;'; put '%if &i>1 %then "," ;'; put '%if &action=OBJ %then """&&name&i"":" ;'; put '"&&name&i"n /* name literal for reserved variable names */'; put '%end;'; put '%if &action=ARR %then "]" ; %else "}" ; ;'; put '/* close out the table */'; put 'data _null_;'; put 'file _sjs3 mod encoding=''utf-8'';'; put 'put '']'';'; put 'run;'; put 'data _null_;'; put 'infile _sjs3 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs3 clear;'; put '%end;'; put 'proc sql;'; put 'drop table &colinfo, &tempds;'; put '%if %substr(&showmeta,1,1)=Y %then %do;'; put 'filename _sjs4 temp lrecl=131068 encoding=''utf-8'';'; put 'data _null_;'; put 'file _sjs4;'; put 'length label $350;'; put 'put ", ""$%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":{""vars"":{";'; put 'do i=1 to &numcols;'; put 'name=quote(trim(symget(cats(''name'',i))));'; put 'format=quote(trim(symget(cats(''fmt'',i))));'; put 'label=quote(prxchange(''s/\\/\\\\/'',-1,trim(symget(cats(''label'',i)))));'; put 'length=quote(trim(symget(cats(''length'',i))));'; put 'type=quote(trim(symget(cats(''typelong'',i))));'; put 'if i>1 then put "," @@;'; put 'put name '':{"format":'' format '',"label":'' label'; put ''',"length":'' length '',"type":'' type ''}'';'; put 'end;'; put 'put ''}}'';'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs4 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs4 clear;'; put '%end;'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put 'data _null_; file &jref encoding=''utf-8'' mod ;'; put 'put "}";'; put 'run;'; put '%end;'; put '%mend mp_jsonout;'; put '/**'; put '@file mm_webout.sas'; put '@brief Send data to/from SAS Stored Processes'; put '@details This macro should be added to the start of each Stored Process,'; put '**immediately** followed by a call to:'; put '%mm_webout(FETCH)'; put 'This will read all the input data and create same-named SAS datasets in the'; put 'WORK library. You can then insert your code, and send data back using the'; put 'following syntax:'; put 'data some datasets; * make some data ;'; put 'retain some columns;'; put 'run;'; put '%mm_webout(OPEN)'; put '%mm_webout(ARR,some) * Array format, fast, suitable for large tables ;'; put '%mm_webout(OBJ,datasets) * Object format, easier to work with ;'; put 'Finally, wrap everything up send some helpful system variables too'; put '%mm_webout(CLOSE)'; put '@param [in] action Either FETCH, OPEN, ARR, OBJ or CLOSE'; put '@param [in] ds The dataset to send back to the frontend'; put '@param [out] dslabel= Value to use instead of table name for sending to JSON'; put '@param [in] fmt= (N) Setting Y converts all vars to their formatted values'; put '@param [out] fref= (_webout) The fileref to which to write the JSON'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] workobs= (0) When set to a positive integer, will create a new'; put 'output object (WORK) which contains this number of observations from all'; put 'tables in the WORK library.'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to output JSON'; put '

SAS Macros

'; put '@li mp_jsonout.sas'; put '

Related Macros

'; put '@li ms_webout.sas'; put '@li mv_webout.sas'; put '@version 9.3'; put '@author Allan Bowe'; put '**/'; put '%macro mm_webout(action,ds,dslabel=,fref=_webout,fmt=N,missing=NULL'; put ',showmeta=N,maxobs=MAX,workobs=0'; put ');'; put '%global _webin_file_count _webin_fileref1 _webin_name1 _program _debug'; put 'sasjs_tables;'; put '%local i tempds jsonengine;'; put '/* see https://github.com/sasjs/core/issues/41 */'; put '%if "%upcase(&SYSENCODING)" ne "UTF-8" %then %let jsonengine=PROCJSON;'; put '%else %let jsonengine=DATASTEP;'; put '%if &action=FETCH %then %do;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'options mprint notes mprintnest;'; put '%end;'; put '%let _webin_file_count=%eval(&_webin_file_count+0);'; put '/* now read in the data */'; put '%do i=1 %to &_webin_file_count;'; put '%if &_webin_file_count=1 %then %do;'; put '%let _webin_fileref1=&_webin_fileref;'; put '%let _webin_name1=&_webin_name;'; put '%end;'; put 'data _null_;'; put 'infile &&_webin_fileref&i termstr=crlf;'; put 'input;'; put 'call symputx(''input_statement'',_infile_);'; put 'putlog "&&_webin_name&i input statement: " _infile_;'; put 'stop;'; put 'data &&_webin_name&i;'; put 'infile &&_webin_fileref&i firstobs=2 dsd termstr=crlf encoding=''utf-8'';'; put 'input &input_statement;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'if _n_<20 then putlog _infile_;'; put '%end;'; put 'run;'; put '%let sasjs_tables=&sasjs_tables &&_webin_name&i;'; put '%end;'; put '%end;'; put '%else %if &action=OPEN %then %do;'; put '/* fix encoding */'; put 'OPTIONS NOBOMFILE;'; put '/**'; put '* check xengine type to avoid the below err message:'; put '* > Function is only valid for filerefs using the CACHE access method.'; put '*/'; put 'data _null_;'; put 'set sashelp.vextfl(where=(fileref="_WEBOUT"));'; put 'if xengine=''STREAM'' then do;'; put 'rc=stpsrv_header(''Content-type'',"text/html; encoding=utf-8");'; put 'end;'; put 'run;'; put '/* setup json */'; put 'data _null_;file &fref encoding=''utf-8'';'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutBEGIN<<'';'; put '%end;'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'run;'; put '%end;'; put '%else %if &action=ARR or &action=OBJ %then %do;'; put '%mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt,jref=&fref'; put ',engine=&jsonengine,missing=&missing,showmeta=&showmeta,maxobs=&maxobs'; put ')'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put '/* To avoid issues with _webout on EBI we use a temporary file */'; put 'filename _sjsref temp lrecl=131068;'; put '%if %str(&workobs) > 0 %then %do;'; put '/* if debug mode, send back first XX records of each work table also */'; put 'data;run;%let tempds=%scan(&syslast,2,.);'; put 'ods output Members=&tempds;'; put 'proc datasets library=WORK memtype=data;'; put '%local wtcnt;%let wtcnt=0;'; put 'data _null_;'; put 'set &tempds;'; put 'if not (upcase(name) =:"DATA"); /* ignore temp datasets */'; put 'i+1;'; put 'call symputx(cats(''wt'',i),name,''l'');'; put 'call symputx(''wtcnt'',i,''l'');'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put ",""WORK"":{";'; put '%do i=1 %to &wtcnt;'; put '%let wt=&&wt&i;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'dsid=open("WORK.&wt",''is'');'; put 'nlobs=attrn(dsid,''NLOBS'');'; put 'nvars=attrn(dsid,''NVARS'');'; put 'rc=close(dsid);'; put 'if &i>1 then put '',''@;'; put 'put " ""&wt"" : {";'; put 'put ''"nlobs":'' nlobs;'; put 'put '',"nvars":'' nvars;'; put '%mp_jsonout(OBJ,&wt,jref=_sjsref,dslabel=first10rows,showmeta=Y'; put ',maxobs=&workobs'; put ')'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put '%end;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put 'run;'; put '%end;'; put '/* close off json */'; put 'data _null_;file _sjsref mod encoding=''utf-8'';'; put 'length SYSPROCESSNAME syserrortext syswarningtext autoexec $512;'; put 'put ",""_DEBUG"" : ""&_debug"" ";'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'autoexec=quote(urlencode(trim(getoption(''autoexec''))));'; put 'put '',"AUTOEXEC" : '' autoexec;'; put 'put ",""MF_GETUSER"" : ""%mf_getuser()"" ";'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'put ",""SYSENCODING"" : ""&sysencoding"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSPROCESSID"" : ""&SYSPROCESSID"" ";'; put 'put ",""SYSPROCESSMODE"" : ""&SYSPROCESSMODE"" ";'; put 'SYSPROCESSNAME=quote(urlencode(cats(SYSPROCESSNAME)));'; put 'put ",""SYSPROCESSNAME"" : " SYSPROCESSNAME;'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put '',"SYSWARNINGTEXT" : '' syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'length memsize $32;'; put 'memsize="%sysfunc(INPUTN(%sysfunc(getoption(memsize)), best.),sizekmg.)";'; put 'memsize=quote(cats(memsize));'; put 'put '',"MEMSIZE" : '' memsize;'; put 'put "}" @;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutEND<<'';'; put '%end;'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjsref lrecl=1 recfm=n;'; put 'file &fref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjsref clear;'; put '%end;'; put '%mend mm_webout;'; put '%macro webout(action,ds,dslabel=,fmt=,missing=NULL,showmeta=NO,maxobs=MAX);'; put '%mm_webout(&action,ds=&ds,dslabel=&dslabel,fmt=&fmt'; put ',missing=&missing'; put ',showmeta=&showmeta'; put ',maxobs=&maxobs'; put ') %mend;'; put '/* provide additional debug info */'; put '%global _program;'; put '%put &=syscc;'; put '%put user=%mf_getuser();'; put '%put pgm=&_program;'; put '%put timestamp=%sysfunc(datetime(),datetime19.);'; put '* Service Variables start;'; put '* Service Variables end;'; put '* SAS Macros start;'; put '%macro mf_getapploc(pgm);'; put '%if "&pgm"="" %then %do;'; put '%if %symexist(_program) %then %let pgm=&_program;'; put '%else %do;'; put '%put &sysmacroname: No value provided and no _program variable available;'; put '%return;'; put '%end;'; put '%end;'; put '%local root;'; put '/**'; put '* First check we are not in the tests/macros folder (which has no subfolders)'; put '* or specifically in the testsetup or testteardown services'; put '*/'; put '%if %index(&pgm,/tests/macros/)'; put 'or %index(&pgm,/tests/testsetup)'; put 'or %index(&pgm,/tests/testteardown)'; put '%then %do;'; put '%let root=%substr(&pgm,1,%index(&pgm,/tests)-1);'; put '&root'; put '%return;'; put '%end;'; put '/**'; put '* Next, move up two levels to avoid matches on subfolder or service name'; put '*/'; put '%let root=%substr(&pgm,1,%length(&pgm)-%length(%scan(&pgm,-1,/))-1);'; put '%let root=%substr(&root,1,%length(&root)-%length(%scan(&root,-1,/))-1);'; put '%if %index(&root,/tests/) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/tests/)-1);'; put '%end;'; put '%else %if %index(&root,/services) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/services)-1);'; put '%end;'; put '%else %if %index(&root,/jobs) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/jobs)-1);'; put '%end;'; put '%else %put &sysmacroname: Could not find an app location from &pgm;'; put '&root'; put '%mend mf_getapploc ;'; put '%macro mf_getuniquefileref(prefix=_,maxtries=1000,lrecl=32767);'; put '%local rc fname;'; put '%if &prefix=0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%end;'; put '%else %do;'; put '%local x len;'; put '%let len=%eval(8-%length(&prefix));'; put '%let x=0;'; put '%do x=0 %to &maxtries;'; put '%let fname=&prefix%substr(%sysfunc(ranuni(0)),3,&len);'; put '%if %sysfunc(fileref(&fname)) > 0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%return;'; put '%end;'; put '%end;'; put '%put unable to find available fileref after &maxtries attempts;'; put '%end;'; put '%mend mf_getuniquefileref;'; put '%macro mp_abort(mac=mp_abort.sas, type=, msg=, iftrue=%str(1=1)'; put ', errds=work.mp_abort_errds'; put ', mode=REGULAR'; put ')/*/STORE SOURCE*/;'; put '%global sysprocessmode sysprocessname sasjs_stpsrv_header_loc sasjsprocessmode;'; put '%local fref fid i;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%put NOTE: /// mp_abort macro executing //;'; put '%if %length(&mac)>0 %then %put NOTE- called by &mac;'; put '%put NOTE - &msg;'; put '%if %symexist(_SYSINCLUDEFILEDEVICE)'; put '/* abort cancel FILE does not restart outside the INCLUDE on Viya 3.5 */'; put 'and %superq(SYSPROCESSNAME) ne %str(Compute Server)'; put '%then %do;'; put '%if "*&_SYSINCLUDEFILEDEVICE*" ne "**" %then %do;'; put 'data &errds;'; put 'iftrue=''1=1'';'; put 'length mac $100 msg $5000;'; put 'mac=symget(''mac'');'; put 'msg=symget(''msg'');'; put 'run;'; put 'data _null_;'; put 'abort cancel FILE;'; put 'run;'; put '%return;'; put '%end;'; put '%end;'; put '/* Web App Context */'; put '%if %symexist(_PROGRAM)'; put 'or %superq(SYSPROCESSNAME) = %str(Compute Server)'; put 'or &mode=INCLUDE'; put '%then %do;'; put 'options obs=max replace mprint;'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5"'; put '%then %do;'; put 'options nosyntaxcheck;'; put '%end;'; put '%if &mode=INCLUDE %then %do;'; put '%if %sysfunc(exist(&errds))=1 %then %do;'; put 'data _null_;'; put 'set &errds;'; put 'call symputx(''iftrue'',iftrue,''l'');'; put 'call symputx(''mac'',mac,''l'');'; put 'call symputx(''msg'',msg,''l'');'; put 'putlog (_all_)(=);'; put 'run;'; put '%if (&iftrue)=0 %then %return;'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: No include errors found;'; put '%return;'; put '%end;'; put '%end;'; put '/* extract log errs / warns, if exist */'; put '%local logloc logline;'; put '%global logmsg; /* capture global messages */'; put '%if %symexist(SYSPRINTTOLOG) %then %let logloc=&SYSPRINTTOLOG;'; put '%else %let logloc=%qsysfunc(getoption(LOG));'; put 'proc printto log=log;run;'; put '%let logline=0;'; put '%if %length(&logloc)>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input; putlog _infile_;'; put 'i=1;'; put 'retain logonce 0;'; put 'if ('; put '_infile_=:"%str(WARN)ING" or _infile_=:"%str(ERR)OR"'; put ') and logonce=0 then'; put 'do;'; put 'call symputx(''logline'',_n_);'; put 'logonce+1;'; put 'end;'; put 'run;'; put '/* capture log including lines BEFORE the err */'; put '%if &logline>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input;'; put 'i=1;'; put 'stoploop=0;'; put 'if _n_ ge &logline-15 and stoploop=0 then do until (i>22);'; put 'call symputx(''logmsg'',catx(''\n'',symget(''logmsg''),_infile_));'; put 'input;'; put 'i+1;'; put 'stoploop=1;'; put 'end;'; put 'if stoploop=1 then stop;'; put 'run;'; put '%end;'; put '%end;'; put '%if %symexist(SYS_JES_JOB_URI) %then %do;'; put '/* setup webout for Viya */'; put 'options nobomfile;'; put '%if "X&SYS_JES_JOB_URI.X"="XX" %then %do;'; put 'filename _webout temp lrecl=999999 mod;'; put '%end;'; put '%else %do;'; put 'filename _webout filesrvc parenturi="&SYS_JES_JOB_URI"'; put 'name="_webout.json" lrecl=999999 mod;'; put '%end;'; put '%end;'; put '%else %if %sysfunc(filename(fref,&sasjs_stpsrv_header_loc))=0 %then %do;'; put 'options nobomfile;'; put '/* set up http header for SASjs Server */'; put '%let fid=%sysfunc(fopen(&fref,A));'; put '%if &fid=0 %then %do;'; put '%put %str(ERR)OR: %sysfunc(sysmsg());'; put '%return;'; put '%end;'; put '%let rc=%sysfunc(fput(&fid,%str(Content-Type: application/json)));'; put '%let rc=%sysfunc(fwrite(&fid));'; put '%let rc=%sysfunc(fclose(&fid));'; put '%let rc=%sysfunc(filename(&fref));'; put '%end;'; put '/* send response in SASjs JSON format */'; put 'data _null_;'; put 'file _webout mod lrecl=32000 encoding=''utf-8'';'; put 'length msg syswarningtext syserrortext $32767 mode $10 ;'; put 'sasdatetime=datetime();'; put 'msg=symget(''msg'');'; put '%if &logline>0 %then %do;'; put 'msg=cats(msg,''\n\nLog Extract:\n'',symget(''logmsg''));'; put '%end;'; put '/* escape the escapes */'; put 'msg=tranwrd(msg,''\'',''\\'');'; put '/* escape the quotes */'; put 'msg=tranwrd(msg,''"'',''\"'');'; put '/* ditch the CRLFs as chrome complains */'; put 'msg=compress(msg,,''kw'');'; put '/* quote without quoting the quotes (which are escaped instead) */'; put 'msg=cats(''"'',msg,''"'');'; put 'if symexist(''_debug'') then debug=quote(trim(symget(''_debug'')));'; put 'else debug=''""'';'; put 'if symget(''sasjsprocessmode'')=''Stored Program'' then mode=''SASJS'';'; put 'if mode ne ''SASJS'' then put ''>>weboutBEGIN<<'';'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'put '',"sasjsAbort" : [{'';'; put 'put '' "MSG":'' msg ;'; put 'put '' ,"MAC": "'' "&mac" ''"}]'';'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'put '',"_DEBUG":'' debug ;'; put 'if symexist(''_metauser'') then do;'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put 'end;'; put 'if symexist(''SYS_JES_JOB_URI'') then do;'; put 'SYS_JES_JOB_URI=quote(trim(symget(''SYS_JES_JOB_URI'')));'; put 'put '',"SYS_JES_JOB_URI": '' SYS_JES_JOB_URI;'; put 'end;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put ",""SYSWARNINGTEXT"" : " syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'put "}" ;'; put 'if mode ne ''SASJS'' then put ''>>weboutEND<<'';'; put 'run;'; put '%put _all_;'; put '%if "&sysprocessmode " = "SAS Stored Process Server " %then %do;'; put 'data _null_;'; put 'putlog ''stpsrvset program err and syscc'';'; put 'rc=stpsrvset(''program error'', 0);'; put 'call symputx("syscc",0,"g");'; put 'run;'; put '%if &sysscp=WIN'; put 'and 1=0 /* deprecating this logic until we figure out a consistent abort */'; put 'and "%substr(%str(&sysvlong ),1,8)"="9.04.01M"'; put 'and "%substr(%str(&sysvlong ),9,1)">"5" %then %do;'; put '/* skip approach (below) does not work in windows m6+ envs */'; put 'endsas;'; put '%end;'; put '%else %do;'; put '/**'; put '* endsas kills 9.4m3 deployments by orphaning multibridges.'; put '* Abort variants are ungraceful (non zero return code)'; put '* This approach lets SAS run silently until the end :-)'; put '* Caution - fails when called within a %include within a macro'; put '* Use mp_include() to handle this.'; put '*/'; put 'filename skip temp;'; put 'data _null_;'; put 'file skip;'; put 'put ''%macro skip();'';'; put 'comment ''%mend skip; -> fix lint '';'; put 'put ''%macro skippy();'';'; put 'comment ''%mend skippy; -> fix lint '';'; put 'run;'; put '%inc skip;'; put '%end;'; put '%end;'; put '%else %if "&sysprocessmode " = "SAS Compute Server " %then %do;'; put '/* endsas kills the session making it harder to fetch results */'; put 'data _null_;'; put 'syswarningtext=symget(''syswarningtext'');'; put 'syserrortext=symget(''syserrortext'');'; put 'abort_msg=symget(''msg'');'; put 'syscc=symget(''syscc'');'; put 'sysuserid=symget(''sysuserid'');'; put 'iftrue=symget(''iftrue'');'; put 'put (_all_)(/=);'; put 'call symputx(''syscc'',0);'; put 'abort cancel nolist;'; put 'run;'; put '%end;'; put '%else %do;'; put '%abort cancel;'; put '%end;'; put '%end;'; put '%else %do;'; put '%put _all_;'; put '%abort cancel;'; put '%end;'; put '%mend mp_abort;'; put '/** @endcond */'; put '%macro mm_getstpcode('; put 'tree=/User Folders/sasdemo/somestp'; put ',name='; put ',outloc=0'; put ',outref=0'; put ',mDebug=1'; put ',showlog=NO'; put ');'; put '%local mD;'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing &sysmacroname..sas;'; put '%&mD.put _local_;'; put '%if %length(&name)>0 %then %let name=/&name;'; put '/* first, check if STP exists */'; put '%local tsuri;'; put '%let tsuri=stopifempty ;'; put 'data _null_;'; put 'format type uri tsuri value $200.;'; put 'call missing (of _all_);'; put 'path="&tree&name(StoredProcess)";'; put '/* first, find the STP ID */'; put 'if metadata_pathobj("",path,"StoredProcess",type,uri)>0 then do;'; put '/* get sourcecode */'; put 'cnt=1;'; put 'do while (metadata_getnasn(uri,"Notes",cnt,tsuri)>0);'; put 'rc=metadata_getattr(tsuri,"Name",value);'; put '&mD.put tsuri= value=;'; put 'if value="SourceCode" then do;'; put '/* found it! */'; put 'rc=metadata_getattr(tsuri,"Id",value);'; put 'call symputx(''tsuri'',value,''l'');'; put 'stop;'; put 'end;'; put 'cnt+1;'; put 'end;'; put 'end;'; put 'else put (_all_)(=);'; put 'run;'; put '%mp_abort(iftrue= (&tsuri=stopifempty)'; put ',mac=mm_getstpcode'; put ',msg=%str(&tree&name.(StoredProcess) not found!)'; put ')'; put '/**'; put '* Now we can extract the textstore'; put '*/'; put 'filename __getdoc temp lrecl=10000000;'; put 'proc metadata'; put 'in="$METAREPOSITORY'; put ''; put 'SAS1"'; put 'out=__getdoc ;'; put 'run;'; put '/* find the beginning of the text */'; put '%local start;'; put 'data _null_;'; put 'infile __getdoc lrecl=10000;'; put 'input;'; put 'start=index(_infile_,''StoredText="'');'; put 'if start then do;'; put 'call symputx("start",start+11);'; put '*putlog ''"'' _infile_ ''"'';'; put 'end;'; put 'stop;'; put '%local outeng;'; put '%if "&outloc"="0" %then %let outeng=TEMP;'; put '%else %let outeng="&outloc";'; put '%local fref;'; put '%if &outref=0 %then %let fref=%mf_getuniquefileref();'; put '%else %let fref=&outref;'; put '/* read the content, byte by byte, resolving escaped chars */'; put 'filename &fref &outeng lrecl=100000;'; put 'data _null_;'; put 'length filein 8 fileid 8;'; put 'filein = fopen("__getdoc","I",1,"B");'; put 'fileid = fopen("&fref","O",1,"B");'; put 'rec = "20"x;'; put 'length entity $6;'; put 'do while(fread(filein)=0);'; put 'x+1;'; put 'if x>&start then do;'; put 'rc = fget(filein,rec,1);'; put 'if rec=''"'' then leave;'; put 'else if rec="&" then do;'; put 'entity=rec;'; put 'do until (rec=";");'; put 'if fread(filein) ne 0 then goto getout;'; put 'rc = fget(filein,rec,1);'; put 'entity=cats(entity,rec);'; put 'end;'; put 'select (entity);'; put 'when (''&'' ) rec=''&'' ;'; put 'when (''<'' ) rec=''<'' ;'; put 'when (''>'' ) rec=''>'' ;'; put 'when (''''') rec="''" ;'; put 'when (''"'') rec=''"'' ;'; put 'when ('' '') rec=''0A''x;'; put 'when ('' '') rec=''0D''x;'; put 'when (''$'' ) rec=''$'' ;'; put 'when ('' '') rec=''09''x;'; put 'otherwise putlog "%str(WARN)ING: missing value for " entity=;'; put 'end;'; put 'rc =fput(fileid, substr(rec,1,1));'; put 'rc =fwrite(fileid);'; put 'end;'; put 'else do;'; put 'rc =fput(fileid,rec);'; put 'rc =fwrite(fileid);'; put 'end;'; put 'end;'; put 'end;'; put 'getout:'; put 'rc=fclose(filein);'; put 'rc=fclose(fileid);'; put 'run;'; put '%if &showlog=YES %then %do;'; put 'data _null_;'; put 'infile &fref lrecl=32767 end=last;'; put 'input;'; put 'if _n_=1 then putlog ''>>stpcodeBEGIN<<'';'; put 'putlog _infile_;'; put 'if last then putlog ''>>stpcodeEND<<'';'; put 'run;'; put '%end;'; put 'filename __getdoc clear;'; put '%if &outref=0 %then %do;'; put 'filename &fref clear;'; put '%end;'; put '%mend mm_getstpcode;'; put '%macro dc_getsettings();'; put '%global _program;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(syscc=&syscc on entry (&syswarningtext &syserrortext))'; put ')'; put '%if %length(&_PROGRAM)>1 %then %let root=&_program;'; put '%else %do;'; put '%global _metauser;'; put '%let _metauser=&sysuserid;'; put '/* to mimic a "real" _program we need to give a dummy role and stp name */'; put '%let root=/dummyRole/dummyName;'; put '%end;'; put '/* the DC precode is stored in the Admin folder in the root of'; put 'the project. Lets find that root. */'; put '%put &=root;'; put '%let root=%mf_getapploc();'; put '%put &=root;'; put '/* Now we know the root location we can retrieve the params */'; put '%let temploc=%sysfunc(pathname(work))/settings.txt;'; put '%mm_getstpcode(tree=&root/services/public'; put ',name=Data_Controller_Settings'; put ',outloc=&temploc'; put ')'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Unable to run getstpcode)'; put ')'; put 'filename _getsets "&temploc" lrecl=2000;'; put '/*'; put 'Do not use mp_include here - this puts a copy in every service, which creates'; put 'compilation problems when calling services from mp_include'; put '*/'; put '%inc _getsets/source2;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Problem running &sysmacroname (&syswarningtext &syserrortext))'; put ')'; put '%mend dc_getsettings;'; put '%macro mf_fmtdttm('; put ')/*/STORE SOURCE*/;'; put '%if "&sysver"="9.2" or "&sysver"="9.3"'; put 'or ("&sysver"="9.4" and "%substr(&SYSVLONG,9,1)" le "3")'; put 'or "%substr(&sysver,1,1)"="4"'; put 'or "%substr(&sysver,1,1)"="5"'; put '%then %do;DATETIME19.3%end;'; put '%else %do;E8601DT26.6%end;'; put '%mend mf_fmtdttm;'; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '%macro mp_init(prefix=SASJS'; put ')/*/STORE SOURCE*/;'; put '%if %symexist(SASJS_PREFIX) %then %return; /* only run once */'; put '%global'; put 'SASJS_PREFIX /* the ONLY hard-coded global macro variable in SASjs */'; put '&prefix._FUNCTIONS /* used in mcf_init() to track core function compilation */'; put '&prefix._INIT_NUM /* initialisation time as numeric */'; put '&prefix._INIT_DTTM /* initialisation time in E8601DT26.6 format */'; put '&prefix.WORK /* avoid typing %sysfunc(pathname(work)) every time */'; put ';'; put '%let sasjs_prefix=&prefix;'; put 'data _null_;'; put 'dttm=datetime();'; put 'call symputx("&sasjs_prefix._init_num",dttm,''g'');'; put 'call symputx("&sasjs_prefix._init_dttm",put(dttm,E8601DT26.6),''g'');'; put 'call symputx("&sasjs_prefix.work",pathname(''WORK''),''g'');'; put 'run;'; put 'options'; put 'compress=CHAR /* default is none so ensure we have something! */'; put 'datastmtchk=ALLKEYWORDS /* protection from overwriting input datasets */'; put 'errorcheck=STRICT /* catch errs in libname/filename statements */'; put 'fmterr /* ensure err when a format cannot be found */'; put 'mergenoby=%str(ERR)OR /* throw err when a merge has no BY variables */'; put 'missing=. /* changing this can cause hard to detect errs */'; put 'noquotelenmax /* avoid warnings for long strings */'; put 'noreplace /* avoid overwriting permanent datasets */'; put 'ps=max /* reduce log size slightly */'; put 'ls=max /* reduce log even more and avoid word truncation */'; put 'validmemname=COMPATIBLE /* avoid special characters etc in table names */'; put 'validvarname=V7 /* avoid special characters etc in variable names */'; put 'varinitchk=%str(ERR)OR /* avoid data mistakes from variable name typos */'; put 'varlenchk=%str(ERR)OR /* fail hard if truncation (data loss) can result */'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5" %then %do;'; put 'noautocorrect /* disallow misspelled procedure names */'; put 'dsoptions=note2err /* undocumented - convert bad NOTEs to ERRs */'; put '%end;'; put ';'; put '%mend mp_init;'; put '%macro mpeinit(fetch=YES);'; put '%global mpeinit'; put 'mpeadmins /* group with unrestricted Meditor access */'; put 'mpelocapprovals /* location for landing and staging files */'; put 'mpelib /* location of configuration tables for DC */'; put 'dc_repo_users /* location of user / group metadata */'; put 'dc_licence_key /* extracted in dc_getsettings */'; put 'dc_activation_key /* extracted in dc_getsettings */'; put 'dc_locale /* extracted in dc_getsettings */'; put 'dc_dttmtfmt /* can be overridden in dc_getsettings */'; put '_debug'; put ';'; put '%if &mpeinit=1 %then %return;'; put '%else %let mpeinit=1;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(Problem on service startup (&syswarningtext &syserrortext))'; put ')'; put '%mp_init()'; put '%if &fetch=YES %then %do;'; put '%webout(FETCH)'; put '%end;'; put '%global _CLIENTNAME;'; put '%mp_abort(iftrue= (&_CLIENTNAME=SAS Enterprise Guide)'; put ',mac=&_program..sas'; put ',msg=%str(Data Controller is a web app and should not be executed from EG)'; put ')'; put 'options urlencoding=utf8 nobomfile lrecl=32767;'; put '%let perf=%sysfunc(datetime());'; put '%put perfdiff: 0;'; put '%let dc_locale=SYSTEM; /* default if not set */'; put '/**'; put '* E8601DT26.6 has widest database support - but not all SAS flavours can'; put '* handle it. Override in the settings STP if needed.'; put '*/'; put 'data _null_;'; put 'dc_dttmtfmt=''"%sysfunc(datetime(),''!!"%mf_fmtdttm()"!!'')"dt'';'; put 'call symputx(''dc_dttmtfmt'',dc_dttmtfmt);'; put 'put dc_dttmtfmt=;'; put 'run;'; put '%put &=dc_dttmtfmt;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc prior to dc_getsettings)'; put ')'; put '%dc_getsettings()'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc after dc_getsettings)'; put ')'; put 'data _null_;'; put 'set &DC_LIBREF..mpe_config(where=('; put 'var_scope="DC"'; put 'and &dc_dttmtfmt lt tx_to'; put 'and var_active=1'; put '));'; put 'call symputx(var_name,var_value,''G'');'; put 'putlog var_name "=" var_value;'; put 'run;'; put '%let mpelib=&dc_libref;'; put '%let mpeadmins=&dc_admin_group;'; put '%let mpelocapprovals=&dc_staging_area;'; put '%let dc_repo_users=&dc_repo_users;'; put '%if &dc_locale ne SYSTEM %then %do;'; put 'options locale=&dc_locale;'; put '%end;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(Problem during compilation or with STP precode (&syswarningtext))'; put ')'; put '%mend mpeinit;'; put '%macro mf_mval(var);'; put '%if %symexist(&var) %then %do;'; put '%superq(&var)'; put '%end;'; put '%mend mf_mval;'; put '%macro mf_trimstr(basestr,trimstr);'; put '%local baselen trimlen trimval;'; put '/* return if basestr is shorter than trimstr (or 0) */'; put '%let baselen=%length(%superq(basestr));'; put '%let trimlen=%length(%superq(trimstr));'; put '%if &baselen < &trimlen or &baselen=0 %then %return;'; put '/* obtain the characters from the end of basestr */'; put '%let trimval=%qsubstr(%superq(basestr)'; put ',%length(%superq(basestr))-&trimlen+1'; put ',&trimlen);'; put '/* compare and if matching, chop it off! */'; put '%if %superq(basestr)=%superq(trimstr) %then %do;'; put '%return;'; put '%end;'; put '%else %if %superq(trimval)=%superq(trimstr) %then %do;'; put '%qsubstr(%superq(basestr),1,%length(%superq(basestr))-&trimlen)'; put '%end;'; put '%else %do;'; put '&basestr'; put '%end;'; put '%mend mf_trimstr;'; put '%macro mf_getplatform(switch'; put ')/*/STORE SOURCE*/;'; put '%local a b c;'; put '%if &switch.NONE=NONE %then %do;'; put '%if %symexist(sasjsprocessmode) %then %do;'; put '%if &sasjsprocessmode=Stored Program %then %do;'; put 'SASJS'; put '%return;'; put '%end;'; put '%end;'; put '%if %symexist(sysprocessmode) %then %do;'; put '%if "&sysprocessmode"="SAS Object Server"'; put 'or "&sysprocessmode"= "SAS Compute Server" %then %do;'; put 'SASVIYA'; put '%end;'; put '%else %if "&sysprocessmode"="SAS Stored Process Server"'; put 'or "&sysprocessmode"="SAS Workspace Server"'; put '%then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if %symexist(_metaport) or %symexist(_metauser) %then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if &switch=SASSTUDIO %then %do;'; put '/* return the version of SAS Studio else 0 */'; put '%if %mf_mval(_CLIENTAPP)=%str(SAS Studio) %then %do;'; put '%let a=%mf_mval(_CLIENTVERSION);'; put '%let b=%scan(&a,1,.);'; put '%if %eval(&b >2) %then %do;'; put '&b'; put '%end;'; put '%else 0;'; put '%end;'; put '%else 0;'; put '%end;'; put '%else %if &switch=VIYARESTAPI %then %do;'; put '%mf_trimstr(%sysfunc(getoption(servicesbaseurl)),/)'; put '%end;'; put '%mend mf_getplatform;'; put '%macro mpeterm();'; put '%local oldloc;'; put 'data _null_;'; put 'if symexist(''SYSPRINTTOLOG'') then oldloc=symget(''SYSPRINTTOLOG'');'; put 'else oldloc=getoption(''LOG'');'; put 'if subpad(oldloc,1,1) not in (''"'',"''",'' '') then oldloc=quote(cats(oldloc));'; put 'call symputx(''oldloc'',oldloc,''l'');'; put 'run;'; put '%if %length(&oldloc)>0 %then %do;'; put 'proc printto log=log;'; put 'run;'; put 'data _null_;'; put 'infile &oldloc;'; put 'input; putlog _infile_;'; put 'run;'; put '%end;'; put '%if %sysfunc(exist(&dc_libref..mpe_requests)) and %mf_getplatform() ne SASVIYA'; put '%then %do;'; put 'data ;'; put 'if 0 then set &dc_libref..mpe_requests;'; put 'request_dttm=%sysfunc(datetime());'; put 'request_user="%mf_getuser()";'; put 'request_service="%scan(&_program,-2,/)/%scan(&_program,-1,/)";'; put 'request_params='''';'; put 'output;stop;'; put 'proc append base=&dc_libref..mpe_requests data=&syslast force nowarn;'; put 'run;'; put '%end;'; put '%mend mpeterm;'; put '%macro mpe_getvars(injs,outds);'; put '/* load parameters */'; put 'data _null_;'; put '__dummychar='''';__dummynum=0;'; put 'set &outds;'; put 'array __charvals _character_;'; put 'do over __charvals;'; put 'call symputx(vname(__charvals),__charvals,''g'');'; put 'end;'; put 'array __numvals _numeric_;'; put 'do over __numvals;'; put 'call symputx(vname(__numvals),__numvals,''g'');'; put 'end;'; put 'run;'; put '%mend mpe_getvars;'; put '%macro mm_getGroups('; put 'user='; put ',outds=work.mm_getGroups'; put ',repo=foundation'; put ',mDebug=0'; put ')/*/STORE SOURCE*/;'; put '%local mD oldrepo;'; put '%let oldrepo=%sysfunc(getoption(metarepository));'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing mm_getGroups.sas;'; put '%&mD.put _local_;'; put '/* on some sites, user / group info is in a different metadata repo to the'; put 'default */'; put '%if &oldrepo ne &repo %then %do;'; put 'options metarepository=&repo;'; put '%end;'; put '%if %length(&user)=0 %then %do;'; put 'data &outds (keep=groupuri groupname groupdesc);'; put 'length groupuri groupname groupdesc group_or_role $256;'; put 'call missing(of _all_);'; put 'i+1;'; put 'do while'; put '(metadata_getnobj("omsobj:IdentityGroup?@Id contains ''.''",i,groupuri)>0);'; put 'rc=metadata_getattr(groupuri, "Name", groupname);'; put 'rc=metadata_getattr(groupuri, "Desc", groupdesc);'; put 'rc=metadata_getattr(groupuri,"PublicType",group_or_role);'; put 'if Group_or_Role = ''UserGroup'' then output;'; put 'i+1;'; put 'end;'; put 'run;'; put '%end;'; put '%else %do;'; put 'data &outds (keep=groupuri groupname groupdesc);'; put 'length uri groupuri groupname groupdesc group_or_role $256;'; put 'call missing(of _all_);'; put 'rc=metadata_getnobj("omsobj:Person?@Name=''&user''",1,uri);'; put 'if rc<=0 then do;'; put 'putlog "%str(WARN)ING: rc=" rc "&user not found "'; put '", or there was an issue reading the repository.";'; put 'stop;'; put 'end;'; put 'a=1;'; put 'grpassn=metadata_getnasn(uri,"IdentityGroups",a,groupuri);'; put 'if grpassn in (-3,-4) then do;'; put 'putlog "%str(WARN)ING: No metadata groups found for &user";'; put 'output;'; put 'end;'; put 'else do while (grpassn > 0);'; put 'rc=metadata_getattr(groupuri, "Name", groupname);'; put 'rc=metadata_getattr(groupuri, "Desc", groupdesc);'; put 'a+1;'; put 'rc=metadata_getattr(groupuri,"PublicType",group_or_role);'; put 'if Group_or_Role = ''UserGroup'' then output;'; put 'grpassn=metadata_getnasn(uri,"IdentityGroups",a,groupuri);'; put 'end;'; put 'run;'; put '%end;'; put '%if &oldrepo ne &repo %then %do;'; put 'options metarepository=&oldrepo;'; put '%end;'; put '%mend mm_getGroups;'; put '%macro dc_getusergroups(user=,outds=mm_getgroups);'; put '%global dc_repo_users;'; put '%if &dc_repo_users= %then %let dc_repo_users=foundation;'; put '%mm_getgroups(user=&user,outds=&outds,repo=&dc_repo_users)'; put '%mend dc_getusergroups;'; put '%macro mpe_getgroups(user=,outds=);'; put '%if not %symexist(dc_repo_users) %then %let dc_repo_users=foundation;'; put '%dc_getusergroups(user=&user,outds=&outds)'; put 'data;'; put 'length groupname groupdesc $256;'; put 'set &dc_libref..mpe_groups;'; put 'where &dc_dttmtfmt. lt tx_to;'; put 'where also upcase(user_name)="%upcase(&user)";'; put 'groupname=group_name;'; put 'groupdesc=group_desc;'; put 'keep groupname groupdesc;'; put 'run;'; put 'data &outds;'; put 'set &syslast &outds(keep=groupname groupdesc);'; put 'run;'; put '%mend mpe_getgroups;'; put '%macro mf_getuniquename(prefix=MC);'; put '&prefix.%substr(%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32-%length(&prefix))'; put '%mend mf_getuniquename;'; put '%macro mf_abort(mac=mf_abort.sas, msg=, iftrue=%str(1=1)'; put ')/des=''ungraceful abort'' /*STORE SOURCE*/;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%put NOTE: /// mf_abort macro executing //;'; put '%if %length(&mac)>0 %then %put NOTE- called by &mac;'; put '%put NOTE - &msg;'; put '%abort;'; put '%mend mf_abort;'; put '/** @endcond */'; put '%macro mf_verifymacvars('; put 'verifyVars /* list of macro variable NAMES */'; put ',makeUpcase=NO /* set to YES to make all the variable VALUES uppercase */'; put ',mAbort=SOFT'; put ')/*/STORE SOURCE*/;'; put '%local verifyIterator verifyVar abortmsg;'; put '%do verifyIterator=1 %to %sysfunc(countw(&verifyVars,%str( )));'; put '%let verifyVar=%qscan(&verifyVars,&verifyIterator,%str( ));'; put '%if not %symexist(&verifyvar) %then %do;'; put '%let abortmsg= Variable &verifyVar is MISSING;'; put '%goto exit_err;'; put '%end;'; put '%if %length(%trim(&&&verifyVar))=0 %then %do;'; put '%let abortmsg= Variable &verifyVar is EMPTY;'; put '%goto exit_err;'; put '%end;'; put '%if &makeupcase=YES %then %do;'; put '%let &verifyVar=%upcase(&&&verifyvar);'; put '%end;'; put '%end;'; put '%goto exit_success;'; put '%exit_err:'; put '%put &abortmsg;'; put '%mf_abort(iftrue=(&mabort ne SOFT),'; put 'mac=mf_verifymacvars,'; put 'msg=%str(&abortmsg)'; put ')'; put '0'; put '%return;'; put '%exit_success:'; put '1'; put '%mend mf_verifymacvars;'; put '%macro mpe_accesscheck('; put 'base_table'; put ',outds=med_accesscheck /* WORK table to contain access details */'; put ',user= /* metadata user to check for */'; put ',access_level=APPROVE'; put ',cntl_lib_var=MPELIB'; put ');'; put '%if &user= %then %let user=%mf_getuser();'; put '%mp_abort('; put 'iftrue=(%index(&outds,.)>0 and %upcase(%scan(&outds,1,.)) ne WORK)'; put ',mac=mpe_accesscheck'; put ',msg=%str(outds should be a WORK table)'; put ')'; put '%mp_abort('; put 'iftrue=(%mf_verifymacvars(base_table user access_level)=0)'; put ',mac=mpe_accesscheck'; put ',msg=%str(Missing base_table/user access_level variables)'; put ')'; put '/* make unique temp table vars */'; put '%local tempds1 tempds2;'; put '%let tempds1=%mf_getuniquename(prefix=usergroups);'; put '%let tempds2=%mf_getuniquename(prefix=tablegroups);'; put '/* get list of user groups */'; put '%mpe_getgroups(user=&user,outds=&tempds1)'; put '/* get list of groups with access for that table */'; put 'proc sql;'; put 'create table &tempds2 as'; put 'select distinct sas_group'; put 'from &&&cntl_lib_var...mpe_security'; put 'where &dc_dttmtfmt. lt tx_to'; put 'and access_level="&access_level"'; put 'and ('; put '(libref="%scan(&base_table,1,.)" and upcase(dsn)="%scan(&base_table,2,.)")'; put 'or (libref="%scan(&base_table,1,.)" and dsn="*ALL*")'; put 'or (libref="*ALL*")'; put ');'; put '%if &_debug ge 131 %then %do;'; put 'data _null_;'; put 'set &tempds1;'; put 'putlog (_all_)(=);'; put 'run;'; put 'data _null_;'; put 'set &tempds2;'; put 'putlog (_all_)(=);'; put 'run;'; put '%end;'; put 'proc sql;'; put 'create table &outds as'; put 'select * from &tempds1'; put 'where groupname="&mpeadmins"'; put 'or groupname in (select * from &tempds2);'; put '%put &sysmacroname: base_table=&base_table;'; put '%put &sysmacroname: access_level=&access_level;'; put '%mend mpe_accesscheck;'; put '%macro mf_getattrn('; put 'libds'; put ',attr'; put ')/*/STORE SOURCE*/;'; put '%local dsid rc;'; put '%let dsid=%sysfunc(open(&libds,is));'; put '%if &dsid = 0 %then %do;'; put '%put %str(WARN)ING: Cannot open %trim(&libds), system message below;'; put '%put %sysfunc(sysmsg());'; put '-1'; put '%end;'; put '%else %do;'; put '%sysfunc(attrn(&dsid,&attr))'; put '%let rc=%sysfunc(close(&dsid));'; put '%end;'; put '%mend mf_getattrn;'; put '%macro mp_binarycopy('; put 'inloc= /* full path and filename of the object to be copied */'; put ',outloc= /* full path and filename of object to be created */'; put ',inref=____in /* override default to use own filerefs */'; put ',outref=____out /* override default to use own filerefs */'; put ',mode=CREATE'; put ',iftrue=%str(1=1)'; put ')/*/STORE SOURCE*/;'; put '%local mod;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%if &mode=APPEND %then %let mod=mod;'; put '/* these IN and OUT filerefs can point to anything */'; put '%if &inref = ____in %then %do;'; put 'filename &inref &inloc lrecl=1048576 ;'; put '%end;'; put '%if &outref=____out %then %do;'; put 'filename &outref &outloc lrecl=1048576 &mod;'; put '%end;'; put '/* copy the file byte-for-byte */'; put 'data _null_;'; put 'infile &inref lrecl=1 recfm=n;'; put 'file &outref &mod recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put '%if &inref = ____in %then %do;'; put 'filename &inref clear;'; put '%end;'; put '%if &outref=____out %then %do;'; put 'filename &outref clear;'; put '%end;'; put '%mend mp_binarycopy;'; put '%macro mfs_httpheader(header_name'; put ',header_value'; put ')/*/STORE SOURCE*/;'; put '%global sasjs_stpsrv_header_loc;'; put '%local fref fid i;'; put '%if %sysfunc(filename(fref,&sasjs_stpsrv_header_loc)) ne 0 %then %do;'; put '%put &=fref &=sasjs_stpsrv_header_loc;'; put '%put %str(ERR)OR: %sysfunc(sysmsg());'; put '%return;'; put '%end;'; put '%let fid=%sysfunc(fopen(&fref,A));'; put '%if &fid=0 %then %do;'; put '%put %str(ERR)OR: %sysfunc(sysmsg());'; put '%return;'; put '%end;'; put '%let rc=%sysfunc(fput(&fid,%str(&header_name): %str(&header_value)));'; put '%let rc=%sysfunc(fwrite(&fid));'; put '%let rc=%sysfunc(fclose(&fid));'; put '%let rc=%sysfunc(filename(&fref));'; put '%mend mfs_httpheader;'; put '%macro mp_streamfile('; put 'contenttype=TEXT'; put ',inloc='; put ',inref=0'; put ',iftrue=%str(1=1)'; put ',outname='; put ',outref=_webout'; put ')/*/STORE SOURCE*/;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%let contentype=%upcase(&contenttype);'; put '%let outref=%upcase(&outref);'; put '%local platform; %let platform=%mf_getplatform();'; put '/**'; put '* check engine type to avoid the below err message:'; put '* > Function is only valid for filerefs using the CACHE access method.'; put '*/'; put '%local streamweb;'; put '%let streamweb=0;'; put 'data _null_;'; put 'set sashelp.vextfl(where=(upcase(fileref)="&outref"));'; put 'if xengine=''STREAM'' then call symputx(''streamweb'',1,''l'');'; put 'run;'; put '%if &contentype=CSV %then %do;'; put '%if (&platform=SASMETA and &streamweb=1) %then %do;'; put 'data _null_;'; put 'rc=stpsrv_header(''Content-Type'',''application/csv'');'; put 'rc=stpsrv_header(''Content-disposition'',"attachment; filename=&outname");'; put 'run;'; put '%end;'; put '%else %if &platform=SASVIYA %then %do;'; put 'filename &outref filesrvc parenturi="&SYS_JES_JOB_URI" name=''_webout.txt'''; put 'contenttype=''application/csv'''; put 'contentdisp="attachment; filename=&outname";'; put '%end;'; put '%else %if &platform=SASJS %then %do;'; put '%mfs_httpheader(Content-Type,application/csv)'; put '%mfs_httpheader(Content-disposition,%str(attachment; filename=&outname))'; put '%end;'; put '%end;'; put '%else %if &contentype=EXCEL %then %do;'; put '/* suitable for XLS format */'; put '%if (&platform=SASMETA and &streamweb=1) %then %do;'; put 'data _null_;'; put 'rc=stpsrv_header(''Content-Type'',''application/vnd.ms-excel'');'; put 'rc=stpsrv_header(''Content-disposition'',"attachment; filename=&outname");'; put 'run;'; put '%end;'; put '%else %if &platform=SASVIYA %then %do;'; put 'filename &outref filesrvc parenturi="&SYS_JES_JOB_URI" name=''_webout.xls'''; put 'contenttype=''application/vnd.ms-excel'''; put 'contentdisp="attachment; filename=&outname";'; put '%end;'; put '%else %if &platform=SASJS %then %do;'; put '%mfs_httpheader(Content-Type,application/vnd.ms-excel)'; put '%mfs_httpheader(Content-disposition,%str(attachment; filename=&outname))'; put '%end;'; put '%end;'; put '%else %if &contentype=GIF or &contentype=JPEG or &contentype=PNG %then %do;'; put '%if (&platform=SASMETA and &streamweb=1) %then %do;'; put 'data _null_;'; put 'rc=stpsrv_header(''Content-Type'',"image/%lowcase(&contenttype)");'; put 'run;'; put '%end;'; put '%else %if &platform=SASVIYA %then %do;'; put 'filename &outref filesrvc parenturi="&SYS_JES_JOB_URI"'; put 'contenttype="image/%lowcase(&contenttype)";'; put '%end;'; put '%else %if &platform=SASJS %then %do;'; put '%mfs_httpheader(Content-Type,image/%lowcase(&contenttype))'; put '%end;'; put '%end;'; put '%else %if &contentype=HTML or &contenttype=MARKDOWN %then %do;'; put '%if (&platform=SASMETA and &streamweb=1) %then %do;'; put 'data _null_;'; put 'rc=stpsrv_header(''Content-Type'',"text/%lowcase(&contenttype)");'; put 'rc=stpsrv_header(''Content-disposition'',"attachment; filename=&outname");'; put 'run;'; put '%end;'; put '%else %if &platform=SASVIYA %then %do;'; put 'filename &outref filesrvc parenturi="&SYS_JES_JOB_URI" name="_webout.json"'; put 'contenttype="text/%lowcase(&contenttype)"'; put 'contentdisp="attachment; filename=&outname";'; put '%end;'; put '%else %if &platform=SASJS %then %do;'; put '%mfs_httpheader(Content-Type,text/%lowcase(&contenttype))'; put '%mfs_httpheader(Content-disposition,%str(attachment; filename=&outname))'; put '%end;'; put '%end;'; put '%else %if &contentype=TEXT %then %do;'; put '%if (&platform=SASMETA and &streamweb=1) %then %do;'; put 'data _null_;'; put 'rc=stpsrv_header(''Content-Type'',''application/text'');'; put 'rc=stpsrv_header(''Content-disposition'',"attachment; filename=&outname");'; put 'run;'; put '%end;'; put '%else %if &platform=SASVIYA %then %do;'; put 'filename &outref filesrvc parenturi="&SYS_JES_JOB_URI" name=''_webout.txt'''; put 'contenttype=''application/text'''; put 'contentdisp="attachment; filename=&outname";'; put '%end;'; put '%else %if &platform=SASJS %then %do;'; put '%mfs_httpheader(Content-Type,application/text)'; put '%mfs_httpheader(Content-disposition,%str(attachment; filename=&outname))'; put '%end;'; put '%end;'; put '%else %if &contentype=WOFF or &contentype=WOFF2 or &contentype=TTF %then %do;'; put '%if (&platform=SASMETA and &streamweb=1) %then %do;'; put 'data _null_;'; put 'rc=stpsrv_header(''Content-Type'',"font/%lowcase(&contenttype)");'; put 'run;'; put '%end;'; put '%else %if &platform=SASVIYA %then %do;'; put 'filename &outref filesrvc parenturi="&SYS_JES_JOB_URI"'; put 'contenttype="font/%lowcase(&contenttype)";'; put '%end;'; put '%else %if &platform=SASJS %then %do;'; put '%mfs_httpheader(Content-Type,font/%lowcase(&contenttype))'; put '%end;'; put '%end;'; put '%else %if &contentype=XLSX %then %do;'; put '%if (&platform=SASMETA and &streamweb=1) %then %do;'; put 'data _null_;'; put 'rc=stpsrv_header(''Content-Type'','; put '''application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'');'; put 'rc=stpsrv_header(''Content-disposition'',"attachment; filename=&outname");'; put 'run;'; put '%end;'; put '%else %if &platform=SASVIYA %then %do;'; put 'filename &outref filesrvc parenturi="&SYS_JES_JOB_URI" name=''_webout.xls'''; put 'contenttype='; put '''application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'''; put 'contentdisp="attachment; filename=&outname";'; put '%end;'; put '%else %if &platform=SASJS %then %do;'; put '%mfs_httpheader(Content-Type'; put ',application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'; put ')'; put '%mfs_httpheader(Content-disposition,%str(attachment; filename=&outname))'; put '%end;'; put '%end;'; put '%else %if &contentype=ZIP %then %do;'; put '%if (&platform=SASMETA and &streamweb=1) %then %do;'; put 'data _null_;'; put 'rc=stpsrv_header(''Content-Type'',''application/zip'');'; put 'rc=stpsrv_header(''Content-disposition'',"attachment; filename=&outname");'; put 'run;'; put '%end;'; put '%else %if &platform=SASVIYA %then %do;'; put 'filename &outref filesrvc parenturi="&SYS_JES_JOB_URI" name=''_webout.zip'''; put 'contenttype=''application/zip'''; put 'contentdisp="attachment; filename=&outname";'; put '%end;'; put '%else %if &platform=SASJS %then %do;'; put '%mfs_httpheader(Content-Type,application/zip)'; put '%mfs_httpheader(Content-disposition,%str(attachment; filename=&outname))'; put '%end;'; put '%end;'; put '%else %do;'; put '%put %str(ERR)OR: Content Type &contenttype NOT SUPPORTED by &sysmacroname!;'; put '%end;'; put '%if &inref ne 0 %then %do;'; put '%mp_binarycopy(inref=&inref,outref=&outref)'; put '%end;'; put '%else %do;'; put '%mp_binarycopy(inloc="&inloc",outref=&outref)'; put '%end;'; put '%mend mp_streamfile;'; put '* SAS Macros end;'; put '* SAS Includes start;'; put '* SAS Includes end;'; put '* Binary Files start;'; put '* Binary Files end;'; put '* ServiceInit start;'; put 'options noquotelenmax ps=max;'; put '/* create dummy macros to prevent stpbegin / stpend from corrupting sessions */'; put '%macro stpbegin();'; put '%put NOTE: the STPBEGIN macro should not be used for web apps!;'; put '%mend stpbegin;'; put '%macro stpend();'; put '%put NOTE: the STPEND macro should not be used for web apps!;'; put '%mend stpend;'; put '* ServiceInit end;'; put '* Service start;'; put '/**'; put '@file getdiffs.sas'; put '@brief Retrieves the diff file for viewing'; put '@details'; put '

SAS Macros

'; put '@li mpe_getvars.sas'; put '@li mpe_accesscheck.sas'; put '@li mf_getattrn.sas'; put '@li mp_abort.sas'; put '@li mp_binarycopy.sas'; put '@li mp_streamfile.sas'; put '@version 9.2'; put '@author 4GL Apps Ltd'; put '@copyright 4GL Apps Ltd. This code may only be used within Data Controller'; put 'and may not be re-distributed or re-sold without the express permission of'; put '4GL Apps Ltd.'; put '**/'; put '%mpeinit()'; put '%mpe_getvars(BrowserParams, BrowserParams);'; put '/* security checks */'; put '%let user=%mf_getuser();'; put '%mpe_accesscheck(&libds,outds=authEDIT,user=&user,access_level=EDIT)'; put '%mpe_accesscheck(&libds,outds=authAPP,user=&user,access_level=APPROVE)'; put '%macro mpestp_diffs();'; put '%if %mf_getattrn(work.authEDIT,NLOBS)=0 & %mf_getattrn(work.authAPP,NLOBS)=0'; put '%then %do;'; put '%mp_abort(msg=%str('; put '&user not authorised to download diffs data for &stp_table)'; put ',mac=mpestp_diffs.sas);'; put '%return;'; put '%end;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(syscc=&syscc)'; put ')'; put '/* stream diffs csv to client */'; put '%mp_streamfile(contenttype=EXCEL'; put ',inloc=%str(&mpelocapprovals/&TABLE/&STP_DIFFS_CSV)'; put ',outname=&STP_DIFFS_CSV'; put ')'; put '%mend mpestp_diffs;'; put '%mpestp_diffs()'; put '%mpeterm()'; put '* Service end;'; run; %mm_createwebservice(path=&appLoc/&path, name=&service, code=sascode, server=&serverName, replace=yes) filename sascode clear; %let service=getstagetable; filename sascode temp lrecl=32767; data _null_; file sascode; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '/**'; put '@file mp_jsonout.sas'; put '@brief Writes JSON in SASjs format to a fileref'; put '@details This macro can be used to OPEN a JSON stream and send one or more'; put 'tables as arrays of rows, where each row can be an object or a nested array.'; put 'There are two engines available - DATASTEP or PROCJSON.'; put 'PROC JSON is fast but will produce errs like the ones below if'; put 'special chars are encountered.'; put '> (ERR)OR: Some code points did not transcode.'; put '> An object or array close is not valid at this point in the JSON text.'; put '> Date value out of range'; put 'If this happens, try running with ENGINE=DATASTEP.'; put 'The DATASTEP engine is used to handle special SAS missing numerics, and'; put 'can also convert entire datasets to formatted values. Output JSON is always'; put 'in UTF-8.'; put 'Usage:'; put 'filename tmp temp;'; put 'data class; set sashelp.class;run;'; put '%mp_jsonout(OPEN,jref=tmp)'; put '%mp_jsonout(OBJ,class,jref=tmp)'; put '%mp_jsonout(OBJ,class,dslabel=class2,jref=tmp,showmeta=Y)'; put '%mp_jsonout(CLOSE,jref=tmp)'; put 'data _null_;'; put 'infile tmp;'; put 'input;putlog _infile_;'; put 'run;'; put 'If you are building web apps with SAS then you are strongly encouraged to use'; put 'the mX_createwebservice macros in combination with the'; put '[sasjs adapter](https://github.com/sasjs/adapter).'; put 'For more information see https://sasjs.io'; put '@param [in] action Valid values:'; put '@li OPEN - opens the JSON'; put '@li OBJ - sends a table with each row as an object'; put '@li ARR - sends a table with each row in an array'; put '@li CLOSE - closes the JSON'; put '@param [in] ds The dataset to send. Must be a work table.'; put '@param [out] jref= (_webout) The fileref to which to send the JSON'; put '@param [out] dslabel= The name to give the table in the exported JSON'; put '@param [in] fmt= (Y) Whether to keep (Y) or strip (N) formats from the table'; put '@param [in] engine= (DATASTEP) Which engine to use to send the JSON. Options:'; put '@li PROCJSON (default)'; put '@li DATASTEP (more reliable when data has non standard characters)'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to JSON'; put '

Related Files

'; put '@li mp_ds2fmtds.sas'; put '@version 9.2'; put '@author Allan Bowe'; put '@source https://github.com/sasjs/core'; put '**/'; put '%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y'; put ',engine=DATASTEP'; put ',missing=NULL'; put ',showmeta=N'; put ',maxobs=MAX'; put ')/*/STORE SOURCE*/;'; put '%local tempds colinfo fmtds i numcols numobs stmt_obs lastobs optval'; put 'tmpds1 tmpds2 tmpds3 tmpds4;'; put '%let numcols=0;'; put '%if &maxobs ne MAX %then %let stmt_obs=%str(if _n_>&maxobs then stop;);'; put '%if &action=OPEN %then %do;'; put 'options nobomfile;'; put 'data _null_;file &jref encoding=''utf-8'' lrecl=200;'; put 'put ''{"PROCESSED_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''"'';'; put 'run;'; put '%end;'; put '%else %if (&action=ARR or &action=OBJ) %then %do;'; put '/* force variable names to always be uppercase in the JSON */'; put 'options validvarname=upcase;'; put '/* To avoid issues with _webout on EBI - such as encoding diffs and truncation'; put '(https://support.sas.com/kb/49/325.html) we use temporary files */'; put 'filename _sjs1 temp lrecl=200 ;'; put 'data _null_; file _sjs1 encoding=''utf-8'';'; put 'put ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":";'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjs1 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs1 clear;'; put '/* grab col defs */'; put 'proc contents noprint data=&ds'; put 'out=_data_(keep=name type length format formatl formatd varnum label);'; put 'run;'; put '%let colinfo=%scan(&syslast,2,.);'; put 'proc sort data=&colinfo;'; put 'by varnum;'; put 'run;'; put '/* move meta to mac vars */'; put 'data &colinfo;'; put 'if _n_=1 then call symputx(''numcols'',nobs,''l'');'; put 'set &colinfo end=last nobs=nobs;'; put 'name=upcase(name);'; put '/* fix formats */'; put 'if type=2 or type=6 then do;'; put 'typelong=''char'';'; put 'length fmt $49.;'; put 'if format='''' then fmt=cats(''$'',length,''.'');'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else fmt=cats(format,formatl,''.'');'; put 'end;'; put 'else do;'; put 'typelong=''num'';'; put 'if format='''' then fmt=''best.'';'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else if formatd=0 then fmt=cats(format,formatl,''.'');'; put 'else fmt=cats(format,formatl,''.'',formatd);'; put 'end;'; put '/* 32 char unique name */'; put 'newname=''sasjs''!!substr(cats(put(md5(name),$hex32.)),1,27);'; put 'call symputx(cats(''name'',_n_),name,''l'');'; put 'call symputx(cats(''newname'',_n_),newname,''l'');'; put 'call symputx(cats(''length'',_n_),length,''l'');'; put 'call symputx(cats(''fmt'',_n_),fmt,''l'');'; put 'call symputx(cats(''type'',_n_),type,''l'');'; put 'call symputx(cats(''typelong'',_n_),typelong,''l'');'; put 'call symputx(cats(''label'',_n_),coalescec(label,name),''l'');'; put '/* overwritten when fmt=Y and a custom format exists in catalog */'; put 'if typelong=''num'' then call symputx(cats(''fmtlen'',_n_),200,''l'');'; put 'else call symputx(cats(''fmtlen'',_n_),min(32767,ceil((length+10)*1.5)),''l'');'; put 'run;'; put '%let tempds=%substr(_%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql;'; put 'select count(*) into: lastobs from &ds;'; put '%if &maxobs ne MAX %then %let lastobs=%sysfunc(min(&lastobs,&maxobs));'; put '%if &engine=PROCJSON %then %do;'; put '%if &missing=STRING %then %do;'; put '%put &sysmacroname: Special Missings not supported in proc json.;'; put '%put &sysmacroname: Switching to DATASTEP engine;'; put '%goto datastep;'; put '%end;'; put 'data &tempds;'; put 'set &ds;'; put '&stmt_obs;'; put '%if &fmt=N %then format _numeric_ best32.;;'; put '/* PRETTY is necessary to avoid line truncation in large files */'; put 'filename _sjs2 temp lrecl=131068 encoding=''utf-8'';'; put 'proc json out=_sjs2 pretty'; put '%if &action=ARR %then nokeys ;'; put ';export &tempds / nosastags fmtnumeric;'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs2 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs2 clear;'; put '%end;'; put '%else %if &engine=DATASTEP %then %do;'; put '%datastep:'; put '%if %sysfunc(exist(&ds)) ne 1 & %sysfunc(exist(&ds,VIEW)) ne 1'; put '%then %do;'; put '%put &sysmacroname: &ds NOT FOUND!!!;'; put '%return;'; put '%end;'; put '%if &fmt=Y %then %do;'; put '/**'; put '* Extract format definitions'; put '* First, by getting library locations from dictionary.formats'; put '* Then, by exporting the width using proc format'; put '* Cannot use maxw from sashelp.vformat as not always populated'; put '* Cannot use fmtinfo() as not supported in all flavours'; put '*/'; put '%let tmpds1=%substr(fmtsum%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds2=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds3=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds4=%substr(col%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql noprint;'; put 'create table &tmpds1 as'; put 'select cats(libname,''.'',memname) as FMTCAT,'; put 'FMTNAME'; put 'from dictionary.formats'; put 'where fmttype=''F'' and libname is not null'; put 'and fmtname in (select format from &colinfo where format is not null)'; put 'order by 1;'; put 'create table &tmpds2('; put 'FMTNAME char(32),'; put 'LENGTH num'; put ');'; put '%local catlist cat fmtlist i;'; put 'select distinct fmtcat into: catlist separated by '' '' from &tmpds1;'; put '%do i=1 %to %sysfunc(countw(&catlist,%str( )));'; put '%let cat=%scan(&catlist,&i,%str( ));'; put 'proc sql;'; put 'select distinct fmtname into: fmtlist separated by '' '''; put 'from &tmpds1 where fmtcat="&cat";'; put 'proc format lib=&cat cntlout=&tmpds3(keep=fmtname length);'; put 'select &fmtlist;'; put 'run;'; put 'proc sql;'; put 'insert into &tmpds2 select distinct fmtname,length from &tmpds3;'; put '%end;'; put 'proc sql;'; put 'create table &tmpds4 as'; put 'select a.*, b.length as MAXW'; put 'from &colinfo a'; put 'left join &tmpds2 b'; put 'on cats(a.format)=cats(upcase(b.fmtname))'; put 'order by a.varnum;'; put 'data _null_;'; put 'set &tmpds4;'; put 'if not missing(maxw);'; put 'call symputx('; put 'cats(''fmtlen'',_n_),'; put '/* vars need extra padding due to JSON escaping of special chars */'; put 'min(32767,ceil((max(length,maxw)+10)*1.5))'; put ',''l'''; put ');'; put 'run;'; put '/* configure varlenchk - as we are explicitly shortening the variables */'; put '%let optval=%sysfunc(getoption(varlenchk));'; put 'options varlenchk=NOWARN;'; put 'data _data_(compress=char);'; put '/* shorten the new vars */'; put 'length'; put '%do i=1 %to &numcols;'; put '&&name&i $&&fmtlen&i'; put '%end;'; put ';'; put '/* rename on entry */'; put 'set &ds(rename=('; put '%do i=1 %to &numcols;'; put '&&name&i=&&newname&i'; put '%end;'; put '));'; put '&stmt_obs;'; put 'drop'; put '%do i=1 %to &numcols;'; put '&&newname&i'; put '%end;'; put ';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=num %then %do;'; put '&&name&i=cats(put(&&newname&i,&&fmt&i));'; put '%end;'; put '%else %do;'; put '&&name&i=put(&&newname&i,&&fmt&i);'; put '%end;'; put '%end;'; put 'if _error_ then do;'; put 'call symputx(''syscc'',1012);'; put 'stop;'; put 'end;'; put 'run;'; put '%let fmtds=&syslast;'; put 'options varlenchk=&optval;'; put '%end;'; put 'proc format; /* credit yabwon for special null removal */'; put 'value bart (default=40)'; put '%if &missing=NULL %then %do;'; put '._ - .z = null'; put '%end;'; put '%else %do;'; put '._ = [quote()]'; put '. = null'; put '.a - .z = [quote()]'; put '%end;'; put 'other = [best.];'; put 'data &tempds;'; put 'attrib _all_ label='''';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'length &&name&i $&&fmtlen&i...;'; put 'format &&name&i $&&fmtlen&i...;'; put '%end;'; put '%end;'; put '%if &fmt=Y %then %do;'; put 'set &fmtds;'; put '%end;'; put '%else %do;'; put 'set &ds;'; put '%end;'; put '&stmt_obs;'; put 'format _numeric_ bart.;'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'if findc(&&name&i,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put '&&name&i=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,&&name&i)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else &&name&i=quote(cats(&&name&i));'; put '%end;'; put '%end;'; put 'run;'; put 'filename _sjs3 temp lrecl=131068 ;'; put 'data _null_;'; put 'file _sjs3 encoding=''utf-8'';'; put 'if _n_=1 then put "[";'; put 'set &tempds;'; put 'if _n_>1 then put "," @; put'; put '%if &action=ARR %then "[" ; %else "{" ;'; put '%do i=1 %to &numcols;'; put '%if &i>1 %then "," ;'; put '%if &action=OBJ %then """&&name&i"":" ;'; put '"&&name&i"n /* name literal for reserved variable names */'; put '%end;'; put '%if &action=ARR %then "]" ; %else "}" ; ;'; put '/* close out the table */'; put 'data _null_;'; put 'file _sjs3 mod encoding=''utf-8'';'; put 'put '']'';'; put 'run;'; put 'data _null_;'; put 'infile _sjs3 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs3 clear;'; put '%end;'; put 'proc sql;'; put 'drop table &colinfo, &tempds;'; put '%if %substr(&showmeta,1,1)=Y %then %do;'; put 'filename _sjs4 temp lrecl=131068 encoding=''utf-8'';'; put 'data _null_;'; put 'file _sjs4;'; put 'length label $350;'; put 'put ", ""$%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":{""vars"":{";'; put 'do i=1 to &numcols;'; put 'name=quote(trim(symget(cats(''name'',i))));'; put 'format=quote(trim(symget(cats(''fmt'',i))));'; put 'label=quote(prxchange(''s/\\/\\\\/'',-1,trim(symget(cats(''label'',i)))));'; put 'length=quote(trim(symget(cats(''length'',i))));'; put 'type=quote(trim(symget(cats(''typelong'',i))));'; put 'if i>1 then put "," @@;'; put 'put name '':{"format":'' format '',"label":'' label'; put ''',"length":'' length '',"type":'' type ''}'';'; put 'end;'; put 'put ''}}'';'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs4 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs4 clear;'; put '%end;'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put 'data _null_; file &jref encoding=''utf-8'' mod ;'; put 'put "}";'; put 'run;'; put '%end;'; put '%mend mp_jsonout;'; put '/**'; put '@file mm_webout.sas'; put '@brief Send data to/from SAS Stored Processes'; put '@details This macro should be added to the start of each Stored Process,'; put '**immediately** followed by a call to:'; put '%mm_webout(FETCH)'; put 'This will read all the input data and create same-named SAS datasets in the'; put 'WORK library. You can then insert your code, and send data back using the'; put 'following syntax:'; put 'data some datasets; * make some data ;'; put 'retain some columns;'; put 'run;'; put '%mm_webout(OPEN)'; put '%mm_webout(ARR,some) * Array format, fast, suitable for large tables ;'; put '%mm_webout(OBJ,datasets) * Object format, easier to work with ;'; put 'Finally, wrap everything up send some helpful system variables too'; put '%mm_webout(CLOSE)'; put '@param [in] action Either FETCH, OPEN, ARR, OBJ or CLOSE'; put '@param [in] ds The dataset to send back to the frontend'; put '@param [out] dslabel= Value to use instead of table name for sending to JSON'; put '@param [in] fmt= (N) Setting Y converts all vars to their formatted values'; put '@param [out] fref= (_webout) The fileref to which to write the JSON'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] workobs= (0) When set to a positive integer, will create a new'; put 'output object (WORK) which contains this number of observations from all'; put 'tables in the WORK library.'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to output JSON'; put '

SAS Macros

'; put '@li mp_jsonout.sas'; put '

Related Macros

'; put '@li ms_webout.sas'; put '@li mv_webout.sas'; put '@version 9.3'; put '@author Allan Bowe'; put '**/'; put '%macro mm_webout(action,ds,dslabel=,fref=_webout,fmt=N,missing=NULL'; put ',showmeta=N,maxobs=MAX,workobs=0'; put ');'; put '%global _webin_file_count _webin_fileref1 _webin_name1 _program _debug'; put 'sasjs_tables;'; put '%local i tempds jsonengine;'; put '/* see https://github.com/sasjs/core/issues/41 */'; put '%if "%upcase(&SYSENCODING)" ne "UTF-8" %then %let jsonengine=PROCJSON;'; put '%else %let jsonengine=DATASTEP;'; put '%if &action=FETCH %then %do;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'options mprint notes mprintnest;'; put '%end;'; put '%let _webin_file_count=%eval(&_webin_file_count+0);'; put '/* now read in the data */'; put '%do i=1 %to &_webin_file_count;'; put '%if &_webin_file_count=1 %then %do;'; put '%let _webin_fileref1=&_webin_fileref;'; put '%let _webin_name1=&_webin_name;'; put '%end;'; put 'data _null_;'; put 'infile &&_webin_fileref&i termstr=crlf;'; put 'input;'; put 'call symputx(''input_statement'',_infile_);'; put 'putlog "&&_webin_name&i input statement: " _infile_;'; put 'stop;'; put 'data &&_webin_name&i;'; put 'infile &&_webin_fileref&i firstobs=2 dsd termstr=crlf encoding=''utf-8'';'; put 'input &input_statement;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'if _n_<20 then putlog _infile_;'; put '%end;'; put 'run;'; put '%let sasjs_tables=&sasjs_tables &&_webin_name&i;'; put '%end;'; put '%end;'; put '%else %if &action=OPEN %then %do;'; put '/* fix encoding */'; put 'OPTIONS NOBOMFILE;'; put '/**'; put '* check xengine type to avoid the below err message:'; put '* > Function is only valid for filerefs using the CACHE access method.'; put '*/'; put 'data _null_;'; put 'set sashelp.vextfl(where=(fileref="_WEBOUT"));'; put 'if xengine=''STREAM'' then do;'; put 'rc=stpsrv_header(''Content-type'',"text/html; encoding=utf-8");'; put 'end;'; put 'run;'; put '/* setup json */'; put 'data _null_;file &fref encoding=''utf-8'';'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutBEGIN<<'';'; put '%end;'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'run;'; put '%end;'; put '%else %if &action=ARR or &action=OBJ %then %do;'; put '%mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt,jref=&fref'; put ',engine=&jsonengine,missing=&missing,showmeta=&showmeta,maxobs=&maxobs'; put ')'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put '/* To avoid issues with _webout on EBI we use a temporary file */'; put 'filename _sjsref temp lrecl=131068;'; put '%if %str(&workobs) > 0 %then %do;'; put '/* if debug mode, send back first XX records of each work table also */'; put 'data;run;%let tempds=%scan(&syslast,2,.);'; put 'ods output Members=&tempds;'; put 'proc datasets library=WORK memtype=data;'; put '%local wtcnt;%let wtcnt=0;'; put 'data _null_;'; put 'set &tempds;'; put 'if not (upcase(name) =:"DATA"); /* ignore temp datasets */'; put 'i+1;'; put 'call symputx(cats(''wt'',i),name,''l'');'; put 'call symputx(''wtcnt'',i,''l'');'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put ",""WORK"":{";'; put '%do i=1 %to &wtcnt;'; put '%let wt=&&wt&i;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'dsid=open("WORK.&wt",''is'');'; put 'nlobs=attrn(dsid,''NLOBS'');'; put 'nvars=attrn(dsid,''NVARS'');'; put 'rc=close(dsid);'; put 'if &i>1 then put '',''@;'; put 'put " ""&wt"" : {";'; put 'put ''"nlobs":'' nlobs;'; put 'put '',"nvars":'' nvars;'; put '%mp_jsonout(OBJ,&wt,jref=_sjsref,dslabel=first10rows,showmeta=Y'; put ',maxobs=&workobs'; put ')'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put '%end;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put 'run;'; put '%end;'; put '/* close off json */'; put 'data _null_;file _sjsref mod encoding=''utf-8'';'; put 'length SYSPROCESSNAME syserrortext syswarningtext autoexec $512;'; put 'put ",""_DEBUG"" : ""&_debug"" ";'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'autoexec=quote(urlencode(trim(getoption(''autoexec''))));'; put 'put '',"AUTOEXEC" : '' autoexec;'; put 'put ",""MF_GETUSER"" : ""%mf_getuser()"" ";'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'put ",""SYSENCODING"" : ""&sysencoding"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSPROCESSID"" : ""&SYSPROCESSID"" ";'; put 'put ",""SYSPROCESSMODE"" : ""&SYSPROCESSMODE"" ";'; put 'SYSPROCESSNAME=quote(urlencode(cats(SYSPROCESSNAME)));'; put 'put ",""SYSPROCESSNAME"" : " SYSPROCESSNAME;'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put '',"SYSWARNINGTEXT" : '' syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'length memsize $32;'; put 'memsize="%sysfunc(INPUTN(%sysfunc(getoption(memsize)), best.),sizekmg.)";'; put 'memsize=quote(cats(memsize));'; put 'put '',"MEMSIZE" : '' memsize;'; put 'put "}" @;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutEND<<'';'; put '%end;'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjsref lrecl=1 recfm=n;'; put 'file &fref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjsref clear;'; put '%end;'; put '%mend mm_webout;'; put '%macro webout(action,ds,dslabel=,fmt=,missing=NULL,showmeta=NO,maxobs=MAX);'; put '%mm_webout(&action,ds=&ds,dslabel=&dslabel,fmt=&fmt'; put ',missing=&missing'; put ',showmeta=&showmeta'; put ',maxobs=&maxobs'; put ') %mend;'; put '/* provide additional debug info */'; put '%global _program;'; put '%put &=syscc;'; put '%put user=%mf_getuser();'; put '%put pgm=&_program;'; put '%put timestamp=%sysfunc(datetime(),datetime19.);'; put '* Service Variables start;'; put '* Service Variables end;'; put '* SAS Macros start;'; put '%macro mf_getapploc(pgm);'; put '%if "&pgm"="" %then %do;'; put '%if %symexist(_program) %then %let pgm=&_program;'; put '%else %do;'; put '%put &sysmacroname: No value provided and no _program variable available;'; put '%return;'; put '%end;'; put '%end;'; put '%local root;'; put '/**'; put '* First check we are not in the tests/macros folder (which has no subfolders)'; put '* or specifically in the testsetup or testteardown services'; put '*/'; put '%if %index(&pgm,/tests/macros/)'; put 'or %index(&pgm,/tests/testsetup)'; put 'or %index(&pgm,/tests/testteardown)'; put '%then %do;'; put '%let root=%substr(&pgm,1,%index(&pgm,/tests)-1);'; put '&root'; put '%return;'; put '%end;'; put '/**'; put '* Next, move up two levels to avoid matches on subfolder or service name'; put '*/'; put '%let root=%substr(&pgm,1,%length(&pgm)-%length(%scan(&pgm,-1,/))-1);'; put '%let root=%substr(&root,1,%length(&root)-%length(%scan(&root,-1,/))-1);'; put '%if %index(&root,/tests/) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/tests/)-1);'; put '%end;'; put '%else %if %index(&root,/services) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/services)-1);'; put '%end;'; put '%else %if %index(&root,/jobs) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/jobs)-1);'; put '%end;'; put '%else %put &sysmacroname: Could not find an app location from &pgm;'; put '&root'; put '%mend mf_getapploc ;'; put '%macro mf_getuniquefileref(prefix=_,maxtries=1000,lrecl=32767);'; put '%local rc fname;'; put '%if &prefix=0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%end;'; put '%else %do;'; put '%local x len;'; put '%let len=%eval(8-%length(&prefix));'; put '%let x=0;'; put '%do x=0 %to &maxtries;'; put '%let fname=&prefix%substr(%sysfunc(ranuni(0)),3,&len);'; put '%if %sysfunc(fileref(&fname)) > 0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%return;'; put '%end;'; put '%end;'; put '%put unable to find available fileref after &maxtries attempts;'; put '%end;'; put '%mend mf_getuniquefileref;'; put '%macro mp_abort(mac=mp_abort.sas, type=, msg=, iftrue=%str(1=1)'; put ', errds=work.mp_abort_errds'; put ', mode=REGULAR'; put ')/*/STORE SOURCE*/;'; put '%global sysprocessmode sysprocessname sasjs_stpsrv_header_loc sasjsprocessmode;'; put '%local fref fid i;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%put NOTE: /// mp_abort macro executing //;'; put '%if %length(&mac)>0 %then %put NOTE- called by &mac;'; put '%put NOTE - &msg;'; put '%if %symexist(_SYSINCLUDEFILEDEVICE)'; put '/* abort cancel FILE does not restart outside the INCLUDE on Viya 3.5 */'; put 'and %superq(SYSPROCESSNAME) ne %str(Compute Server)'; put '%then %do;'; put '%if "*&_SYSINCLUDEFILEDEVICE*" ne "**" %then %do;'; put 'data &errds;'; put 'iftrue=''1=1'';'; put 'length mac $100 msg $5000;'; put 'mac=symget(''mac'');'; put 'msg=symget(''msg'');'; put 'run;'; put 'data _null_;'; put 'abort cancel FILE;'; put 'run;'; put '%return;'; put '%end;'; put '%end;'; put '/* Web App Context */'; put '%if %symexist(_PROGRAM)'; put 'or %superq(SYSPROCESSNAME) = %str(Compute Server)'; put 'or &mode=INCLUDE'; put '%then %do;'; put 'options obs=max replace mprint;'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5"'; put '%then %do;'; put 'options nosyntaxcheck;'; put '%end;'; put '%if &mode=INCLUDE %then %do;'; put '%if %sysfunc(exist(&errds))=1 %then %do;'; put 'data _null_;'; put 'set &errds;'; put 'call symputx(''iftrue'',iftrue,''l'');'; put 'call symputx(''mac'',mac,''l'');'; put 'call symputx(''msg'',msg,''l'');'; put 'putlog (_all_)(=);'; put 'run;'; put '%if (&iftrue)=0 %then %return;'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: No include errors found;'; put '%return;'; put '%end;'; put '%end;'; put '/* extract log errs / warns, if exist */'; put '%local logloc logline;'; put '%global logmsg; /* capture global messages */'; put '%if %symexist(SYSPRINTTOLOG) %then %let logloc=&SYSPRINTTOLOG;'; put '%else %let logloc=%qsysfunc(getoption(LOG));'; put 'proc printto log=log;run;'; put '%let logline=0;'; put '%if %length(&logloc)>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input; putlog _infile_;'; put 'i=1;'; put 'retain logonce 0;'; put 'if ('; put '_infile_=:"%str(WARN)ING" or _infile_=:"%str(ERR)OR"'; put ') and logonce=0 then'; put 'do;'; put 'call symputx(''logline'',_n_);'; put 'logonce+1;'; put 'end;'; put 'run;'; put '/* capture log including lines BEFORE the err */'; put '%if &logline>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input;'; put 'i=1;'; put 'stoploop=0;'; put 'if _n_ ge &logline-15 and stoploop=0 then do until (i>22);'; put 'call symputx(''logmsg'',catx(''\n'',symget(''logmsg''),_infile_));'; put 'input;'; put 'i+1;'; put 'stoploop=1;'; put 'end;'; put 'if stoploop=1 then stop;'; put 'run;'; put '%end;'; put '%end;'; put '%if %symexist(SYS_JES_JOB_URI) %then %do;'; put '/* setup webout for Viya */'; put 'options nobomfile;'; put '%if "X&SYS_JES_JOB_URI.X"="XX" %then %do;'; put 'filename _webout temp lrecl=999999 mod;'; put '%end;'; put '%else %do;'; put 'filename _webout filesrvc parenturi="&SYS_JES_JOB_URI"'; put 'name="_webout.json" lrecl=999999 mod;'; put '%end;'; put '%end;'; put '%else %if %sysfunc(filename(fref,&sasjs_stpsrv_header_loc))=0 %then %do;'; put 'options nobomfile;'; put '/* set up http header for SASjs Server */'; put '%let fid=%sysfunc(fopen(&fref,A));'; put '%if &fid=0 %then %do;'; put '%put %str(ERR)OR: %sysfunc(sysmsg());'; put '%return;'; put '%end;'; put '%let rc=%sysfunc(fput(&fid,%str(Content-Type: application/json)));'; put '%let rc=%sysfunc(fwrite(&fid));'; put '%let rc=%sysfunc(fclose(&fid));'; put '%let rc=%sysfunc(filename(&fref));'; put '%end;'; put '/* send response in SASjs JSON format */'; put 'data _null_;'; put 'file _webout mod lrecl=32000 encoding=''utf-8'';'; put 'length msg syswarningtext syserrortext $32767 mode $10 ;'; put 'sasdatetime=datetime();'; put 'msg=symget(''msg'');'; put '%if &logline>0 %then %do;'; put 'msg=cats(msg,''\n\nLog Extract:\n'',symget(''logmsg''));'; put '%end;'; put '/* escape the escapes */'; put 'msg=tranwrd(msg,''\'',''\\'');'; put '/* escape the quotes */'; put 'msg=tranwrd(msg,''"'',''\"'');'; put '/* ditch the CRLFs as chrome complains */'; put 'msg=compress(msg,,''kw'');'; put '/* quote without quoting the quotes (which are escaped instead) */'; put 'msg=cats(''"'',msg,''"'');'; put 'if symexist(''_debug'') then debug=quote(trim(symget(''_debug'')));'; put 'else debug=''""'';'; put 'if symget(''sasjsprocessmode'')=''Stored Program'' then mode=''SASJS'';'; put 'if mode ne ''SASJS'' then put ''>>weboutBEGIN<<'';'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'put '',"sasjsAbort" : [{'';'; put 'put '' "MSG":'' msg ;'; put 'put '' ,"MAC": "'' "&mac" ''"}]'';'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'put '',"_DEBUG":'' debug ;'; put 'if symexist(''_metauser'') then do;'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put 'end;'; put 'if symexist(''SYS_JES_JOB_URI'') then do;'; put 'SYS_JES_JOB_URI=quote(trim(symget(''SYS_JES_JOB_URI'')));'; put 'put '',"SYS_JES_JOB_URI": '' SYS_JES_JOB_URI;'; put 'end;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put ",""SYSWARNINGTEXT"" : " syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'put "}" ;'; put 'if mode ne ''SASJS'' then put ''>>weboutEND<<'';'; put 'run;'; put '%put _all_;'; put '%if "&sysprocessmode " = "SAS Stored Process Server " %then %do;'; put 'data _null_;'; put 'putlog ''stpsrvset program err and syscc'';'; put 'rc=stpsrvset(''program error'', 0);'; put 'call symputx("syscc",0,"g");'; put 'run;'; put '%if &sysscp=WIN'; put 'and 1=0 /* deprecating this logic until we figure out a consistent abort */'; put 'and "%substr(%str(&sysvlong ),1,8)"="9.04.01M"'; put 'and "%substr(%str(&sysvlong ),9,1)">"5" %then %do;'; put '/* skip approach (below) does not work in windows m6+ envs */'; put 'endsas;'; put '%end;'; put '%else %do;'; put '/**'; put '* endsas kills 9.4m3 deployments by orphaning multibridges.'; put '* Abort variants are ungraceful (non zero return code)'; put '* This approach lets SAS run silently until the end :-)'; put '* Caution - fails when called within a %include within a macro'; put '* Use mp_include() to handle this.'; put '*/'; put 'filename skip temp;'; put 'data _null_;'; put 'file skip;'; put 'put ''%macro skip();'';'; put 'comment ''%mend skip; -> fix lint '';'; put 'put ''%macro skippy();'';'; put 'comment ''%mend skippy; -> fix lint '';'; put 'run;'; put '%inc skip;'; put '%end;'; put '%end;'; put '%else %if "&sysprocessmode " = "SAS Compute Server " %then %do;'; put '/* endsas kills the session making it harder to fetch results */'; put 'data _null_;'; put 'syswarningtext=symget(''syswarningtext'');'; put 'syserrortext=symget(''syserrortext'');'; put 'abort_msg=symget(''msg'');'; put 'syscc=symget(''syscc'');'; put 'sysuserid=symget(''sysuserid'');'; put 'iftrue=symget(''iftrue'');'; put 'put (_all_)(/=);'; put 'call symputx(''syscc'',0);'; put 'abort cancel nolist;'; put 'run;'; put '%end;'; put '%else %do;'; put '%abort cancel;'; put '%end;'; put '%end;'; put '%else %do;'; put '%put _all_;'; put '%abort cancel;'; put '%end;'; put '%mend mp_abort;'; put '/** @endcond */'; put '%macro mm_getstpcode('; put 'tree=/User Folders/sasdemo/somestp'; put ',name='; put ',outloc=0'; put ',outref=0'; put ',mDebug=1'; put ',showlog=NO'; put ');'; put '%local mD;'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing &sysmacroname..sas;'; put '%&mD.put _local_;'; put '%if %length(&name)>0 %then %let name=/&name;'; put '/* first, check if STP exists */'; put '%local tsuri;'; put '%let tsuri=stopifempty ;'; put 'data _null_;'; put 'format type uri tsuri value $200.;'; put 'call missing (of _all_);'; put 'path="&tree&name(StoredProcess)";'; put '/* first, find the STP ID */'; put 'if metadata_pathobj("",path,"StoredProcess",type,uri)>0 then do;'; put '/* get sourcecode */'; put 'cnt=1;'; put 'do while (metadata_getnasn(uri,"Notes",cnt,tsuri)>0);'; put 'rc=metadata_getattr(tsuri,"Name",value);'; put '&mD.put tsuri= value=;'; put 'if value="SourceCode" then do;'; put '/* found it! */'; put 'rc=metadata_getattr(tsuri,"Id",value);'; put 'call symputx(''tsuri'',value,''l'');'; put 'stop;'; put 'end;'; put 'cnt+1;'; put 'end;'; put 'end;'; put 'else put (_all_)(=);'; put 'run;'; put '%mp_abort(iftrue= (&tsuri=stopifempty)'; put ',mac=mm_getstpcode'; put ',msg=%str(&tree&name.(StoredProcess) not found!)'; put ')'; put '/**'; put '* Now we can extract the textstore'; put '*/'; put 'filename __getdoc temp lrecl=10000000;'; put 'proc metadata'; put 'in="$METAREPOSITORY'; put ''; put 'SAS1"'; put 'out=__getdoc ;'; put 'run;'; put '/* find the beginning of the text */'; put '%local start;'; put 'data _null_;'; put 'infile __getdoc lrecl=10000;'; put 'input;'; put 'start=index(_infile_,''StoredText="'');'; put 'if start then do;'; put 'call symputx("start",start+11);'; put '*putlog ''"'' _infile_ ''"'';'; put 'end;'; put 'stop;'; put '%local outeng;'; put '%if "&outloc"="0" %then %let outeng=TEMP;'; put '%else %let outeng="&outloc";'; put '%local fref;'; put '%if &outref=0 %then %let fref=%mf_getuniquefileref();'; put '%else %let fref=&outref;'; put '/* read the content, byte by byte, resolving escaped chars */'; put 'filename &fref &outeng lrecl=100000;'; put 'data _null_;'; put 'length filein 8 fileid 8;'; put 'filein = fopen("__getdoc","I",1,"B");'; put 'fileid = fopen("&fref","O",1,"B");'; put 'rec = "20"x;'; put 'length entity $6;'; put 'do while(fread(filein)=0);'; put 'x+1;'; put 'if x>&start then do;'; put 'rc = fget(filein,rec,1);'; put 'if rec=''"'' then leave;'; put 'else if rec="&" then do;'; put 'entity=rec;'; put 'do until (rec=";");'; put 'if fread(filein) ne 0 then goto getout;'; put 'rc = fget(filein,rec,1);'; put 'entity=cats(entity,rec);'; put 'end;'; put 'select (entity);'; put 'when (''&'' ) rec=''&'' ;'; put 'when (''<'' ) rec=''<'' ;'; put 'when (''>'' ) rec=''>'' ;'; put 'when (''''') rec="''" ;'; put 'when (''"'') rec=''"'' ;'; put 'when ('' '') rec=''0A''x;'; put 'when ('' '') rec=''0D''x;'; put 'when (''$'' ) rec=''$'' ;'; put 'when ('' '') rec=''09''x;'; put 'otherwise putlog "%str(WARN)ING: missing value for " entity=;'; put 'end;'; put 'rc =fput(fileid, substr(rec,1,1));'; put 'rc =fwrite(fileid);'; put 'end;'; put 'else do;'; put 'rc =fput(fileid,rec);'; put 'rc =fwrite(fileid);'; put 'end;'; put 'end;'; put 'end;'; put 'getout:'; put 'rc=fclose(filein);'; put 'rc=fclose(fileid);'; put 'run;'; put '%if &showlog=YES %then %do;'; put 'data _null_;'; put 'infile &fref lrecl=32767 end=last;'; put 'input;'; put 'if _n_=1 then putlog ''>>stpcodeBEGIN<<'';'; put 'putlog _infile_;'; put 'if last then putlog ''>>stpcodeEND<<'';'; put 'run;'; put '%end;'; put 'filename __getdoc clear;'; put '%if &outref=0 %then %do;'; put 'filename &fref clear;'; put '%end;'; put '%mend mm_getstpcode;'; put '%macro dc_getsettings();'; put '%global _program;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(syscc=&syscc on entry (&syswarningtext &syserrortext))'; put ')'; put '%if %length(&_PROGRAM)>1 %then %let root=&_program;'; put '%else %do;'; put '%global _metauser;'; put '%let _metauser=&sysuserid;'; put '/* to mimic a "real" _program we need to give a dummy role and stp name */'; put '%let root=/dummyRole/dummyName;'; put '%end;'; put '/* the DC precode is stored in the Admin folder in the root of'; put 'the project. Lets find that root. */'; put '%put &=root;'; put '%let root=%mf_getapploc();'; put '%put &=root;'; put '/* Now we know the root location we can retrieve the params */'; put '%let temploc=%sysfunc(pathname(work))/settings.txt;'; put '%mm_getstpcode(tree=&root/services/public'; put ',name=Data_Controller_Settings'; put ',outloc=&temploc'; put ')'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Unable to run getstpcode)'; put ')'; put 'filename _getsets "&temploc" lrecl=2000;'; put '/*'; put 'Do not use mp_include here - this puts a copy in every service, which creates'; put 'compilation problems when calling services from mp_include'; put '*/'; put '%inc _getsets/source2;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Problem running &sysmacroname (&syswarningtext &syserrortext))'; put ')'; put '%mend dc_getsettings;'; put '%macro mf_fmtdttm('; put ')/*/STORE SOURCE*/;'; put '%if "&sysver"="9.2" or "&sysver"="9.3"'; put 'or ("&sysver"="9.4" and "%substr(&SYSVLONG,9,1)" le "3")'; put 'or "%substr(&sysver,1,1)"="4"'; put 'or "%substr(&sysver,1,1)"="5"'; put '%then %do;DATETIME19.3%end;'; put '%else %do;E8601DT26.6%end;'; put '%mend mf_fmtdttm;'; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '%macro mp_init(prefix=SASJS'; put ')/*/STORE SOURCE*/;'; put '%if %symexist(SASJS_PREFIX) %then %return; /* only run once */'; put '%global'; put 'SASJS_PREFIX /* the ONLY hard-coded global macro variable in SASjs */'; put '&prefix._FUNCTIONS /* used in mcf_init() to track core function compilation */'; put '&prefix._INIT_NUM /* initialisation time as numeric */'; put '&prefix._INIT_DTTM /* initialisation time in E8601DT26.6 format */'; put '&prefix.WORK /* avoid typing %sysfunc(pathname(work)) every time */'; put ';'; put '%let sasjs_prefix=&prefix;'; put 'data _null_;'; put 'dttm=datetime();'; put 'call symputx("&sasjs_prefix._init_num",dttm,''g'');'; put 'call symputx("&sasjs_prefix._init_dttm",put(dttm,E8601DT26.6),''g'');'; put 'call symputx("&sasjs_prefix.work",pathname(''WORK''),''g'');'; put 'run;'; put 'options'; put 'compress=CHAR /* default is none so ensure we have something! */'; put 'datastmtchk=ALLKEYWORDS /* protection from overwriting input datasets */'; put 'errorcheck=STRICT /* catch errs in libname/filename statements */'; put 'fmterr /* ensure err when a format cannot be found */'; put 'mergenoby=%str(ERR)OR /* throw err when a merge has no BY variables */'; put 'missing=. /* changing this can cause hard to detect errs */'; put 'noquotelenmax /* avoid warnings for long strings */'; put 'noreplace /* avoid overwriting permanent datasets */'; put 'ps=max /* reduce log size slightly */'; put 'ls=max /* reduce log even more and avoid word truncation */'; put 'validmemname=COMPATIBLE /* avoid special characters etc in table names */'; put 'validvarname=V7 /* avoid special characters etc in variable names */'; put 'varinitchk=%str(ERR)OR /* avoid data mistakes from variable name typos */'; put 'varlenchk=%str(ERR)OR /* fail hard if truncation (data loss) can result */'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5" %then %do;'; put 'noautocorrect /* disallow misspelled procedure names */'; put 'dsoptions=note2err /* undocumented - convert bad NOTEs to ERRs */'; put '%end;'; put ';'; put '%mend mp_init;'; put '%macro mpeinit(fetch=YES);'; put '%global mpeinit'; put 'mpeadmins /* group with unrestricted Meditor access */'; put 'mpelocapprovals /* location for landing and staging files */'; put 'mpelib /* location of configuration tables for DC */'; put 'dc_repo_users /* location of user / group metadata */'; put 'dc_licence_key /* extracted in dc_getsettings */'; put 'dc_activation_key /* extracted in dc_getsettings */'; put 'dc_locale /* extracted in dc_getsettings */'; put 'dc_dttmtfmt /* can be overridden in dc_getsettings */'; put '_debug'; put ';'; put '%if &mpeinit=1 %then %return;'; put '%else %let mpeinit=1;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(Problem on service startup (&syswarningtext &syserrortext))'; put ')'; put '%mp_init()'; put '%if &fetch=YES %then %do;'; put '%webout(FETCH)'; put '%end;'; put '%global _CLIENTNAME;'; put '%mp_abort(iftrue= (&_CLIENTNAME=SAS Enterprise Guide)'; put ',mac=&_program..sas'; put ',msg=%str(Data Controller is a web app and should not be executed from EG)'; put ')'; put 'options urlencoding=utf8 nobomfile lrecl=32767;'; put '%let perf=%sysfunc(datetime());'; put '%put perfdiff: 0;'; put '%let dc_locale=SYSTEM; /* default if not set */'; put '/**'; put '* E8601DT26.6 has widest database support - but not all SAS flavours can'; put '* handle it. Override in the settings STP if needed.'; put '*/'; put 'data _null_;'; put 'dc_dttmtfmt=''"%sysfunc(datetime(),''!!"%mf_fmtdttm()"!!'')"dt'';'; put 'call symputx(''dc_dttmtfmt'',dc_dttmtfmt);'; put 'put dc_dttmtfmt=;'; put 'run;'; put '%put &=dc_dttmtfmt;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc prior to dc_getsettings)'; put ')'; put '%dc_getsettings()'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc after dc_getsettings)'; put ')'; put 'data _null_;'; put 'set &DC_LIBREF..mpe_config(where=('; put 'var_scope="DC"'; put 'and &dc_dttmtfmt lt tx_to'; put 'and var_active=1'; put '));'; put 'call symputx(var_name,var_value,''G'');'; put 'putlog var_name "=" var_value;'; put 'run;'; put '%let mpelib=&dc_libref;'; put '%let mpeadmins=&dc_admin_group;'; put '%let mpelocapprovals=&dc_staging_area;'; put '%let dc_repo_users=&dc_repo_users;'; put '%if &dc_locale ne SYSTEM %then %do;'; put 'options locale=&dc_locale;'; put '%end;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(Problem during compilation or with STP precode (&syswarningtext))'; put ')'; put '%mend mpeinit;'; put '%macro mf_mval(var);'; put '%if %symexist(&var) %then %do;'; put '%superq(&var)'; put '%end;'; put '%mend mf_mval;'; put '%macro mf_trimstr(basestr,trimstr);'; put '%local baselen trimlen trimval;'; put '/* return if basestr is shorter than trimstr (or 0) */'; put '%let baselen=%length(%superq(basestr));'; put '%let trimlen=%length(%superq(trimstr));'; put '%if &baselen < &trimlen or &baselen=0 %then %return;'; put '/* obtain the characters from the end of basestr */'; put '%let trimval=%qsubstr(%superq(basestr)'; put ',%length(%superq(basestr))-&trimlen+1'; put ',&trimlen);'; put '/* compare and if matching, chop it off! */'; put '%if %superq(basestr)=%superq(trimstr) %then %do;'; put '%return;'; put '%end;'; put '%else %if %superq(trimval)=%superq(trimstr) %then %do;'; put '%qsubstr(%superq(basestr),1,%length(%superq(basestr))-&trimlen)'; put '%end;'; put '%else %do;'; put '&basestr'; put '%end;'; put '%mend mf_trimstr;'; put '%macro mf_getplatform(switch'; put ')/*/STORE SOURCE*/;'; put '%local a b c;'; put '%if &switch.NONE=NONE %then %do;'; put '%if %symexist(sasjsprocessmode) %then %do;'; put '%if &sasjsprocessmode=Stored Program %then %do;'; put 'SASJS'; put '%return;'; put '%end;'; put '%end;'; put '%if %symexist(sysprocessmode) %then %do;'; put '%if "&sysprocessmode"="SAS Object Server"'; put 'or "&sysprocessmode"= "SAS Compute Server" %then %do;'; put 'SASVIYA'; put '%end;'; put '%else %if "&sysprocessmode"="SAS Stored Process Server"'; put 'or "&sysprocessmode"="SAS Workspace Server"'; put '%then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if %symexist(_metaport) or %symexist(_metauser) %then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if &switch=SASSTUDIO %then %do;'; put '/* return the version of SAS Studio else 0 */'; put '%if %mf_mval(_CLIENTAPP)=%str(SAS Studio) %then %do;'; put '%let a=%mf_mval(_CLIENTVERSION);'; put '%let b=%scan(&a,1,.);'; put '%if %eval(&b >2) %then %do;'; put '&b'; put '%end;'; put '%else 0;'; put '%end;'; put '%else 0;'; put '%end;'; put '%else %if &switch=VIYARESTAPI %then %do;'; put '%mf_trimstr(%sysfunc(getoption(servicesbaseurl)),/)'; put '%end;'; put '%mend mf_getplatform;'; put '%macro mpeterm();'; put '%local oldloc;'; put 'data _null_;'; put 'if symexist(''SYSPRINTTOLOG'') then oldloc=symget(''SYSPRINTTOLOG'');'; put 'else oldloc=getoption(''LOG'');'; put 'if subpad(oldloc,1,1) not in (''"'',"''",'' '') then oldloc=quote(cats(oldloc));'; put 'call symputx(''oldloc'',oldloc,''l'');'; put 'run;'; put '%if %length(&oldloc)>0 %then %do;'; put 'proc printto log=log;'; put 'run;'; put 'data _null_;'; put 'infile &oldloc;'; put 'input; putlog _infile_;'; put 'run;'; put '%end;'; put '%if %sysfunc(exist(&dc_libref..mpe_requests)) and %mf_getplatform() ne SASVIYA'; put '%then %do;'; put 'data ;'; put 'if 0 then set &dc_libref..mpe_requests;'; put 'request_dttm=%sysfunc(datetime());'; put 'request_user="%mf_getuser()";'; put 'request_service="%scan(&_program,-2,/)/%scan(&_program,-1,/)";'; put 'request_params='''';'; put 'output;stop;'; put 'proc append base=&dc_libref..mpe_requests data=&syslast force nowarn;'; put 'run;'; put '%end;'; put '%mend mpeterm;'; put '%macro mf_getattrn('; put 'libds'; put ',attr'; put ')/*/STORE SOURCE*/;'; put '%local dsid rc;'; put '%let dsid=%sysfunc(open(&libds,is));'; put '%if &dsid = 0 %then %do;'; put '%put %str(WARN)ING: Cannot open %trim(&libds), system message below;'; put '%put %sysfunc(sysmsg());'; put '-1'; put '%end;'; put '%else %do;'; put '%sysfunc(attrn(&dsid,&attr))'; put '%let rc=%sysfunc(close(&dsid));'; put '%end;'; put '%mend mf_getattrn;'; put '%macro mf_getvalue(libds,variable,filter=1'; put ')/*/STORE SOURCE*/;'; put '%if %mf_getattrn(&libds,NLOBS)>0 %then %do;'; put '%local dsid rc &variable;'; put '%let dsid=%sysfunc(open(&libds(where=(&filter))));'; put '%syscall set(dsid);'; put '%let rc = %sysfunc(fetch(&dsid));'; put '%let rc = %sysfunc(close(&dsid));'; put '%trim(&&&variable)'; put '%end;'; put '%mend mf_getvalue;'; put '* SAS Macros end;'; put '* SAS Includes start;'; put '* SAS Includes end;'; put '* Binary Files start;'; put '* Binary Files end;'; put '* ServiceInit start;'; put 'options noquotelenmax ps=max;'; put '/* create dummy macros to prevent stpbegin / stpend from corrupting sessions */'; put '%macro stpbegin();'; put '%put NOTE: the STPBEGIN macro should not be used for web apps!;'; put '%mend stpbegin;'; put '%macro stpend();'; put '%put NOTE: the STPEND macro should not be used for web apps!;'; put '%mend stpend;'; put '* ServiceInit end;'; put '* Service start;'; put '/**'; put '@file getstagetable.sas'; put '@brief Retrieves the actual table that is being sent for update'; put '@details'; put '

SAS Macros

'; put '@li mf_getvalue.sas'; put '@li mp_abort.sas'; put '@version 9.2'; put '@author 4GL Apps Ltd'; put '@copyright 4GL Apps Ltd. This code may only be used within Data Controller'; put 'and may not be re-distributed or re-sold without the express permission of'; put '4GL Apps Ltd.'; put '**/'; put '%mpeinit()'; put '%let table_id=%mf_getvalue(work.iwant,table_id);'; put 'libname loc "&mpelocapprovals/&table_id";'; put 'data stagetable;'; put 'set loc.&table_id;'; put 'run;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(syscc=&syscc)'; put ')'; put '%webout(OPEN)'; put '%webout(OBJ,stagetable,missing=STRING)'; put '%webout(CLOSE)'; put '%mpeterm()'; put '* Service end;'; run; %mm_createwebservice(path=&appLoc/&path, name=&service, code=sascode, server=&serverName, replace=yes) filename sascode clear; %let service=postdata; filename sascode temp lrecl=32767; data _null_; file sascode; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '/**'; put '@file mp_jsonout.sas'; put '@brief Writes JSON in SASjs format to a fileref'; put '@details This macro can be used to OPEN a JSON stream and send one or more'; put 'tables as arrays of rows, where each row can be an object or a nested array.'; put 'There are two engines available - DATASTEP or PROCJSON.'; put 'PROC JSON is fast but will produce errs like the ones below if'; put 'special chars are encountered.'; put '> (ERR)OR: Some code points did not transcode.'; put '> An object or array close is not valid at this point in the JSON text.'; put '> Date value out of range'; put 'If this happens, try running with ENGINE=DATASTEP.'; put 'The DATASTEP engine is used to handle special SAS missing numerics, and'; put 'can also convert entire datasets to formatted values. Output JSON is always'; put 'in UTF-8.'; put 'Usage:'; put 'filename tmp temp;'; put 'data class; set sashelp.class;run;'; put '%mp_jsonout(OPEN,jref=tmp)'; put '%mp_jsonout(OBJ,class,jref=tmp)'; put '%mp_jsonout(OBJ,class,dslabel=class2,jref=tmp,showmeta=Y)'; put '%mp_jsonout(CLOSE,jref=tmp)'; put 'data _null_;'; put 'infile tmp;'; put 'input;putlog _infile_;'; put 'run;'; put 'If you are building web apps with SAS then you are strongly encouraged to use'; put 'the mX_createwebservice macros in combination with the'; put '[sasjs adapter](https://github.com/sasjs/adapter).'; put 'For more information see https://sasjs.io'; put '@param [in] action Valid values:'; put '@li OPEN - opens the JSON'; put '@li OBJ - sends a table with each row as an object'; put '@li ARR - sends a table with each row in an array'; put '@li CLOSE - closes the JSON'; put '@param [in] ds The dataset to send. Must be a work table.'; put '@param [out] jref= (_webout) The fileref to which to send the JSON'; put '@param [out] dslabel= The name to give the table in the exported JSON'; put '@param [in] fmt= (Y) Whether to keep (Y) or strip (N) formats from the table'; put '@param [in] engine= (DATASTEP) Which engine to use to send the JSON. Options:'; put '@li PROCJSON (default)'; put '@li DATASTEP (more reliable when data has non standard characters)'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to JSON'; put '

Related Files

'; put '@li mp_ds2fmtds.sas'; put '@version 9.2'; put '@author Allan Bowe'; put '@source https://github.com/sasjs/core'; put '**/'; put '%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y'; put ',engine=DATASTEP'; put ',missing=NULL'; put ',showmeta=N'; put ',maxobs=MAX'; put ')/*/STORE SOURCE*/;'; put '%local tempds colinfo fmtds i numcols numobs stmt_obs lastobs optval'; put 'tmpds1 tmpds2 tmpds3 tmpds4;'; put '%let numcols=0;'; put '%if &maxobs ne MAX %then %let stmt_obs=%str(if _n_>&maxobs then stop;);'; put '%if &action=OPEN %then %do;'; put 'options nobomfile;'; put 'data _null_;file &jref encoding=''utf-8'' lrecl=200;'; put 'put ''{"PROCESSED_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''"'';'; put 'run;'; put '%end;'; put '%else %if (&action=ARR or &action=OBJ) %then %do;'; put '/* force variable names to always be uppercase in the JSON */'; put 'options validvarname=upcase;'; put '/* To avoid issues with _webout on EBI - such as encoding diffs and truncation'; put '(https://support.sas.com/kb/49/325.html) we use temporary files */'; put 'filename _sjs1 temp lrecl=200 ;'; put 'data _null_; file _sjs1 encoding=''utf-8'';'; put 'put ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":";'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjs1 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs1 clear;'; put '/* grab col defs */'; put 'proc contents noprint data=&ds'; put 'out=_data_(keep=name type length format formatl formatd varnum label);'; put 'run;'; put '%let colinfo=%scan(&syslast,2,.);'; put 'proc sort data=&colinfo;'; put 'by varnum;'; put 'run;'; put '/* move meta to mac vars */'; put 'data &colinfo;'; put 'if _n_=1 then call symputx(''numcols'',nobs,''l'');'; put 'set &colinfo end=last nobs=nobs;'; put 'name=upcase(name);'; put '/* fix formats */'; put 'if type=2 or type=6 then do;'; put 'typelong=''char'';'; put 'length fmt $49.;'; put 'if format='''' then fmt=cats(''$'',length,''.'');'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else fmt=cats(format,formatl,''.'');'; put 'end;'; put 'else do;'; put 'typelong=''num'';'; put 'if format='''' then fmt=''best.'';'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else if formatd=0 then fmt=cats(format,formatl,''.'');'; put 'else fmt=cats(format,formatl,''.'',formatd);'; put 'end;'; put '/* 32 char unique name */'; put 'newname=''sasjs''!!substr(cats(put(md5(name),$hex32.)),1,27);'; put 'call symputx(cats(''name'',_n_),name,''l'');'; put 'call symputx(cats(''newname'',_n_),newname,''l'');'; put 'call symputx(cats(''length'',_n_),length,''l'');'; put 'call symputx(cats(''fmt'',_n_),fmt,''l'');'; put 'call symputx(cats(''type'',_n_),type,''l'');'; put 'call symputx(cats(''typelong'',_n_),typelong,''l'');'; put 'call symputx(cats(''label'',_n_),coalescec(label,name),''l'');'; put '/* overwritten when fmt=Y and a custom format exists in catalog */'; put 'if typelong=''num'' then call symputx(cats(''fmtlen'',_n_),200,''l'');'; put 'else call symputx(cats(''fmtlen'',_n_),min(32767,ceil((length+10)*1.5)),''l'');'; put 'run;'; put '%let tempds=%substr(_%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql;'; put 'select count(*) into: lastobs from &ds;'; put '%if &maxobs ne MAX %then %let lastobs=%sysfunc(min(&lastobs,&maxobs));'; put '%if &engine=PROCJSON %then %do;'; put '%if &missing=STRING %then %do;'; put '%put &sysmacroname: Special Missings not supported in proc json.;'; put '%put &sysmacroname: Switching to DATASTEP engine;'; put '%goto datastep;'; put '%end;'; put 'data &tempds;'; put 'set &ds;'; put '&stmt_obs;'; put '%if &fmt=N %then format _numeric_ best32.;;'; put '/* PRETTY is necessary to avoid line truncation in large files */'; put 'filename _sjs2 temp lrecl=131068 encoding=''utf-8'';'; put 'proc json out=_sjs2 pretty'; put '%if &action=ARR %then nokeys ;'; put ';export &tempds / nosastags fmtnumeric;'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs2 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs2 clear;'; put '%end;'; put '%else %if &engine=DATASTEP %then %do;'; put '%datastep:'; put '%if %sysfunc(exist(&ds)) ne 1 & %sysfunc(exist(&ds,VIEW)) ne 1'; put '%then %do;'; put '%put &sysmacroname: &ds NOT FOUND!!!;'; put '%return;'; put '%end;'; put '%if &fmt=Y %then %do;'; put '/**'; put '* Extract format definitions'; put '* First, by getting library locations from dictionary.formats'; put '* Then, by exporting the width using proc format'; put '* Cannot use maxw from sashelp.vformat as not always populated'; put '* Cannot use fmtinfo() as not supported in all flavours'; put '*/'; put '%let tmpds1=%substr(fmtsum%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds2=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds3=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds4=%substr(col%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql noprint;'; put 'create table &tmpds1 as'; put 'select cats(libname,''.'',memname) as FMTCAT,'; put 'FMTNAME'; put 'from dictionary.formats'; put 'where fmttype=''F'' and libname is not null'; put 'and fmtname in (select format from &colinfo where format is not null)'; put 'order by 1;'; put 'create table &tmpds2('; put 'FMTNAME char(32),'; put 'LENGTH num'; put ');'; put '%local catlist cat fmtlist i;'; put 'select distinct fmtcat into: catlist separated by '' '' from &tmpds1;'; put '%do i=1 %to %sysfunc(countw(&catlist,%str( )));'; put '%let cat=%scan(&catlist,&i,%str( ));'; put 'proc sql;'; put 'select distinct fmtname into: fmtlist separated by '' '''; put 'from &tmpds1 where fmtcat="&cat";'; put 'proc format lib=&cat cntlout=&tmpds3(keep=fmtname length);'; put 'select &fmtlist;'; put 'run;'; put 'proc sql;'; put 'insert into &tmpds2 select distinct fmtname,length from &tmpds3;'; put '%end;'; put 'proc sql;'; put 'create table &tmpds4 as'; put 'select a.*, b.length as MAXW'; put 'from &colinfo a'; put 'left join &tmpds2 b'; put 'on cats(a.format)=cats(upcase(b.fmtname))'; put 'order by a.varnum;'; put 'data _null_;'; put 'set &tmpds4;'; put 'if not missing(maxw);'; put 'call symputx('; put 'cats(''fmtlen'',_n_),'; put '/* vars need extra padding due to JSON escaping of special chars */'; put 'min(32767,ceil((max(length,maxw)+10)*1.5))'; put ',''l'''; put ');'; put 'run;'; put '/* configure varlenchk - as we are explicitly shortening the variables */'; put '%let optval=%sysfunc(getoption(varlenchk));'; put 'options varlenchk=NOWARN;'; put 'data _data_(compress=char);'; put '/* shorten the new vars */'; put 'length'; put '%do i=1 %to &numcols;'; put '&&name&i $&&fmtlen&i'; put '%end;'; put ';'; put '/* rename on entry */'; put 'set &ds(rename=('; put '%do i=1 %to &numcols;'; put '&&name&i=&&newname&i'; put '%end;'; put '));'; put '&stmt_obs;'; put 'drop'; put '%do i=1 %to &numcols;'; put '&&newname&i'; put '%end;'; put ';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=num %then %do;'; put '&&name&i=cats(put(&&newname&i,&&fmt&i));'; put '%end;'; put '%else %do;'; put '&&name&i=put(&&newname&i,&&fmt&i);'; put '%end;'; put '%end;'; put 'if _error_ then do;'; put 'call symputx(''syscc'',1012);'; put 'stop;'; put 'end;'; put 'run;'; put '%let fmtds=&syslast;'; put 'options varlenchk=&optval;'; put '%end;'; put 'proc format; /* credit yabwon for special null removal */'; put 'value bart (default=40)'; put '%if &missing=NULL %then %do;'; put '._ - .z = null'; put '%end;'; put '%else %do;'; put '._ = [quote()]'; put '. = null'; put '.a - .z = [quote()]'; put '%end;'; put 'other = [best.];'; put 'data &tempds;'; put 'attrib _all_ label='''';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'length &&name&i $&&fmtlen&i...;'; put 'format &&name&i $&&fmtlen&i...;'; put '%end;'; put '%end;'; put '%if &fmt=Y %then %do;'; put 'set &fmtds;'; put '%end;'; put '%else %do;'; put 'set &ds;'; put '%end;'; put '&stmt_obs;'; put 'format _numeric_ bart.;'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'if findc(&&name&i,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put '&&name&i=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,&&name&i)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else &&name&i=quote(cats(&&name&i));'; put '%end;'; put '%end;'; put 'run;'; put 'filename _sjs3 temp lrecl=131068 ;'; put 'data _null_;'; put 'file _sjs3 encoding=''utf-8'';'; put 'if _n_=1 then put "[";'; put 'set &tempds;'; put 'if _n_>1 then put "," @; put'; put '%if &action=ARR %then "[" ; %else "{" ;'; put '%do i=1 %to &numcols;'; put '%if &i>1 %then "," ;'; put '%if &action=OBJ %then """&&name&i"":" ;'; put '"&&name&i"n /* name literal for reserved variable names */'; put '%end;'; put '%if &action=ARR %then "]" ; %else "}" ; ;'; put '/* close out the table */'; put 'data _null_;'; put 'file _sjs3 mod encoding=''utf-8'';'; put 'put '']'';'; put 'run;'; put 'data _null_;'; put 'infile _sjs3 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs3 clear;'; put '%end;'; put 'proc sql;'; put 'drop table &colinfo, &tempds;'; put '%if %substr(&showmeta,1,1)=Y %then %do;'; put 'filename _sjs4 temp lrecl=131068 encoding=''utf-8'';'; put 'data _null_;'; put 'file _sjs4;'; put 'length label $350;'; put 'put ", ""$%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":{""vars"":{";'; put 'do i=1 to &numcols;'; put 'name=quote(trim(symget(cats(''name'',i))));'; put 'format=quote(trim(symget(cats(''fmt'',i))));'; put 'label=quote(prxchange(''s/\\/\\\\/'',-1,trim(symget(cats(''label'',i)))));'; put 'length=quote(trim(symget(cats(''length'',i))));'; put 'type=quote(trim(symget(cats(''typelong'',i))));'; put 'if i>1 then put "," @@;'; put 'put name '':{"format":'' format '',"label":'' label'; put ''',"length":'' length '',"type":'' type ''}'';'; put 'end;'; put 'put ''}}'';'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs4 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs4 clear;'; put '%end;'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put 'data _null_; file &jref encoding=''utf-8'' mod ;'; put 'put "}";'; put 'run;'; put '%end;'; put '%mend mp_jsonout;'; put '/**'; put '@file mm_webout.sas'; put '@brief Send data to/from SAS Stored Processes'; put '@details This macro should be added to the start of each Stored Process,'; put '**immediately** followed by a call to:'; put '%mm_webout(FETCH)'; put 'This will read all the input data and create same-named SAS datasets in the'; put 'WORK library. You can then insert your code, and send data back using the'; put 'following syntax:'; put 'data some datasets; * make some data ;'; put 'retain some columns;'; put 'run;'; put '%mm_webout(OPEN)'; put '%mm_webout(ARR,some) * Array format, fast, suitable for large tables ;'; put '%mm_webout(OBJ,datasets) * Object format, easier to work with ;'; put 'Finally, wrap everything up send some helpful system variables too'; put '%mm_webout(CLOSE)'; put '@param [in] action Either FETCH, OPEN, ARR, OBJ or CLOSE'; put '@param [in] ds The dataset to send back to the frontend'; put '@param [out] dslabel= Value to use instead of table name for sending to JSON'; put '@param [in] fmt= (N) Setting Y converts all vars to their formatted values'; put '@param [out] fref= (_webout) The fileref to which to write the JSON'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] workobs= (0) When set to a positive integer, will create a new'; put 'output object (WORK) which contains this number of observations from all'; put 'tables in the WORK library.'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to output JSON'; put '

SAS Macros

'; put '@li mp_jsonout.sas'; put '

Related Macros

'; put '@li ms_webout.sas'; put '@li mv_webout.sas'; put '@version 9.3'; put '@author Allan Bowe'; put '**/'; put '%macro mm_webout(action,ds,dslabel=,fref=_webout,fmt=N,missing=NULL'; put ',showmeta=N,maxobs=MAX,workobs=0'; put ');'; put '%global _webin_file_count _webin_fileref1 _webin_name1 _program _debug'; put 'sasjs_tables;'; put '%local i tempds jsonengine;'; put '/* see https://github.com/sasjs/core/issues/41 */'; put '%if "%upcase(&SYSENCODING)" ne "UTF-8" %then %let jsonengine=PROCJSON;'; put '%else %let jsonengine=DATASTEP;'; put '%if &action=FETCH %then %do;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'options mprint notes mprintnest;'; put '%end;'; put '%let _webin_file_count=%eval(&_webin_file_count+0);'; put '/* now read in the data */'; put '%do i=1 %to &_webin_file_count;'; put '%if &_webin_file_count=1 %then %do;'; put '%let _webin_fileref1=&_webin_fileref;'; put '%let _webin_name1=&_webin_name;'; put '%end;'; put 'data _null_;'; put 'infile &&_webin_fileref&i termstr=crlf;'; put 'input;'; put 'call symputx(''input_statement'',_infile_);'; put 'putlog "&&_webin_name&i input statement: " _infile_;'; put 'stop;'; put 'data &&_webin_name&i;'; put 'infile &&_webin_fileref&i firstobs=2 dsd termstr=crlf encoding=''utf-8'';'; put 'input &input_statement;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'if _n_<20 then putlog _infile_;'; put '%end;'; put 'run;'; put '%let sasjs_tables=&sasjs_tables &&_webin_name&i;'; put '%end;'; put '%end;'; put '%else %if &action=OPEN %then %do;'; put '/* fix encoding */'; put 'OPTIONS NOBOMFILE;'; put '/**'; put '* check xengine type to avoid the below err message:'; put '* > Function is only valid for filerefs using the CACHE access method.'; put '*/'; put 'data _null_;'; put 'set sashelp.vextfl(where=(fileref="_WEBOUT"));'; put 'if xengine=''STREAM'' then do;'; put 'rc=stpsrv_header(''Content-type'',"text/html; encoding=utf-8");'; put 'end;'; put 'run;'; put '/* setup json */'; put 'data _null_;file &fref encoding=''utf-8'';'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutBEGIN<<'';'; put '%end;'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'run;'; put '%end;'; put '%else %if &action=ARR or &action=OBJ %then %do;'; put '%mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt,jref=&fref'; put ',engine=&jsonengine,missing=&missing,showmeta=&showmeta,maxobs=&maxobs'; put ')'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put '/* To avoid issues with _webout on EBI we use a temporary file */'; put 'filename _sjsref temp lrecl=131068;'; put '%if %str(&workobs) > 0 %then %do;'; put '/* if debug mode, send back first XX records of each work table also */'; put 'data;run;%let tempds=%scan(&syslast,2,.);'; put 'ods output Members=&tempds;'; put 'proc datasets library=WORK memtype=data;'; put '%local wtcnt;%let wtcnt=0;'; put 'data _null_;'; put 'set &tempds;'; put 'if not (upcase(name) =:"DATA"); /* ignore temp datasets */'; put 'i+1;'; put 'call symputx(cats(''wt'',i),name,''l'');'; put 'call symputx(''wtcnt'',i,''l'');'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put ",""WORK"":{";'; put '%do i=1 %to &wtcnt;'; put '%let wt=&&wt&i;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'dsid=open("WORK.&wt",''is'');'; put 'nlobs=attrn(dsid,''NLOBS'');'; put 'nvars=attrn(dsid,''NVARS'');'; put 'rc=close(dsid);'; put 'if &i>1 then put '',''@;'; put 'put " ""&wt"" : {";'; put 'put ''"nlobs":'' nlobs;'; put 'put '',"nvars":'' nvars;'; put '%mp_jsonout(OBJ,&wt,jref=_sjsref,dslabel=first10rows,showmeta=Y'; put ',maxobs=&workobs'; put ')'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put '%end;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put 'run;'; put '%end;'; put '/* close off json */'; put 'data _null_;file _sjsref mod encoding=''utf-8'';'; put 'length SYSPROCESSNAME syserrortext syswarningtext autoexec $512;'; put 'put ",""_DEBUG"" : ""&_debug"" ";'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'autoexec=quote(urlencode(trim(getoption(''autoexec''))));'; put 'put '',"AUTOEXEC" : '' autoexec;'; put 'put ",""MF_GETUSER"" : ""%mf_getuser()"" ";'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'put ",""SYSENCODING"" : ""&sysencoding"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSPROCESSID"" : ""&SYSPROCESSID"" ";'; put 'put ",""SYSPROCESSMODE"" : ""&SYSPROCESSMODE"" ";'; put 'SYSPROCESSNAME=quote(urlencode(cats(SYSPROCESSNAME)));'; put 'put ",""SYSPROCESSNAME"" : " SYSPROCESSNAME;'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put '',"SYSWARNINGTEXT" : '' syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'length memsize $32;'; put 'memsize="%sysfunc(INPUTN(%sysfunc(getoption(memsize)), best.),sizekmg.)";'; put 'memsize=quote(cats(memsize));'; put 'put '',"MEMSIZE" : '' memsize;'; put 'put "}" @;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutEND<<'';'; put '%end;'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjsref lrecl=1 recfm=n;'; put 'file &fref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjsref clear;'; put '%end;'; put '%mend mm_webout;'; put '%macro webout(action,ds,dslabel=,fmt=,missing=NULL,showmeta=NO,maxobs=MAX);'; put '%mm_webout(&action,ds=&ds,dslabel=&dslabel,fmt=&fmt'; put ',missing=&missing'; put ',showmeta=&showmeta'; put ',maxobs=&maxobs'; put ') %mend;'; put '/* provide additional debug info */'; put '%global _program;'; put '%put &=syscc;'; put '%put user=%mf_getuser();'; put '%put pgm=&_program;'; put '%put timestamp=%sysfunc(datetime(),datetime19.);'; put '* Service Variables start;'; put '* Service Variables end;'; put '* SAS Macros start;'; put '%macro mf_getapploc(pgm);'; put '%if "&pgm"="" %then %do;'; put '%if %symexist(_program) %then %let pgm=&_program;'; put '%else %do;'; put '%put &sysmacroname: No value provided and no _program variable available;'; put '%return;'; put '%end;'; put '%end;'; put '%local root;'; put '/**'; put '* First check we are not in the tests/macros folder (which has no subfolders)'; put '* or specifically in the testsetup or testteardown services'; put '*/'; put '%if %index(&pgm,/tests/macros/)'; put 'or %index(&pgm,/tests/testsetup)'; put 'or %index(&pgm,/tests/testteardown)'; put '%then %do;'; put '%let root=%substr(&pgm,1,%index(&pgm,/tests)-1);'; put '&root'; put '%return;'; put '%end;'; put '/**'; put '* Next, move up two levels to avoid matches on subfolder or service name'; put '*/'; put '%let root=%substr(&pgm,1,%length(&pgm)-%length(%scan(&pgm,-1,/))-1);'; put '%let root=%substr(&root,1,%length(&root)-%length(%scan(&root,-1,/))-1);'; put '%if %index(&root,/tests/) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/tests/)-1);'; put '%end;'; put '%else %if %index(&root,/services) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/services)-1);'; put '%end;'; put '%else %if %index(&root,/jobs) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/jobs)-1);'; put '%end;'; put '%else %put &sysmacroname: Could not find an app location from &pgm;'; put '&root'; put '%mend mf_getapploc ;'; put '%macro mf_getuniquefileref(prefix=_,maxtries=1000,lrecl=32767);'; put '%local rc fname;'; put '%if &prefix=0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%end;'; put '%else %do;'; put '%local x len;'; put '%let len=%eval(8-%length(&prefix));'; put '%let x=0;'; put '%do x=0 %to &maxtries;'; put '%let fname=&prefix%substr(%sysfunc(ranuni(0)),3,&len);'; put '%if %sysfunc(fileref(&fname)) > 0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%return;'; put '%end;'; put '%end;'; put '%put unable to find available fileref after &maxtries attempts;'; put '%end;'; put '%mend mf_getuniquefileref;'; put '%macro mp_abort(mac=mp_abort.sas, type=, msg=, iftrue=%str(1=1)'; put ', errds=work.mp_abort_errds'; put ', mode=REGULAR'; put ')/*/STORE SOURCE*/;'; put '%global sysprocessmode sysprocessname sasjs_stpsrv_header_loc sasjsprocessmode;'; put '%local fref fid i;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%put NOTE: /// mp_abort macro executing //;'; put '%if %length(&mac)>0 %then %put NOTE- called by &mac;'; put '%put NOTE - &msg;'; put '%if %symexist(_SYSINCLUDEFILEDEVICE)'; put '/* abort cancel FILE does not restart outside the INCLUDE on Viya 3.5 */'; put 'and %superq(SYSPROCESSNAME) ne %str(Compute Server)'; put '%then %do;'; put '%if "*&_SYSINCLUDEFILEDEVICE*" ne "**" %then %do;'; put 'data &errds;'; put 'iftrue=''1=1'';'; put 'length mac $100 msg $5000;'; put 'mac=symget(''mac'');'; put 'msg=symget(''msg'');'; put 'run;'; put 'data _null_;'; put 'abort cancel FILE;'; put 'run;'; put '%return;'; put '%end;'; put '%end;'; put '/* Web App Context */'; put '%if %symexist(_PROGRAM)'; put 'or %superq(SYSPROCESSNAME) = %str(Compute Server)'; put 'or &mode=INCLUDE'; put '%then %do;'; put 'options obs=max replace mprint;'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5"'; put '%then %do;'; put 'options nosyntaxcheck;'; put '%end;'; put '%if &mode=INCLUDE %then %do;'; put '%if %sysfunc(exist(&errds))=1 %then %do;'; put 'data _null_;'; put 'set &errds;'; put 'call symputx(''iftrue'',iftrue,''l'');'; put 'call symputx(''mac'',mac,''l'');'; put 'call symputx(''msg'',msg,''l'');'; put 'putlog (_all_)(=);'; put 'run;'; put '%if (&iftrue)=0 %then %return;'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: No include errors found;'; put '%return;'; put '%end;'; put '%end;'; put '/* extract log errs / warns, if exist */'; put '%local logloc logline;'; put '%global logmsg; /* capture global messages */'; put '%if %symexist(SYSPRINTTOLOG) %then %let logloc=&SYSPRINTTOLOG;'; put '%else %let logloc=%qsysfunc(getoption(LOG));'; put 'proc printto log=log;run;'; put '%let logline=0;'; put '%if %length(&logloc)>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input; putlog _infile_;'; put 'i=1;'; put 'retain logonce 0;'; put 'if ('; put '_infile_=:"%str(WARN)ING" or _infile_=:"%str(ERR)OR"'; put ') and logonce=0 then'; put 'do;'; put 'call symputx(''logline'',_n_);'; put 'logonce+1;'; put 'end;'; put 'run;'; put '/* capture log including lines BEFORE the err */'; put '%if &logline>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input;'; put 'i=1;'; put 'stoploop=0;'; put 'if _n_ ge &logline-15 and stoploop=0 then do until (i>22);'; put 'call symputx(''logmsg'',catx(''\n'',symget(''logmsg''),_infile_));'; put 'input;'; put 'i+1;'; put 'stoploop=1;'; put 'end;'; put 'if stoploop=1 then stop;'; put 'run;'; put '%end;'; put '%end;'; put '%if %symexist(SYS_JES_JOB_URI) %then %do;'; put '/* setup webout for Viya */'; put 'options nobomfile;'; put '%if "X&SYS_JES_JOB_URI.X"="XX" %then %do;'; put 'filename _webout temp lrecl=999999 mod;'; put '%end;'; put '%else %do;'; put 'filename _webout filesrvc parenturi="&SYS_JES_JOB_URI"'; put 'name="_webout.json" lrecl=999999 mod;'; put '%end;'; put '%end;'; put '%else %if %sysfunc(filename(fref,&sasjs_stpsrv_header_loc))=0 %then %do;'; put 'options nobomfile;'; put '/* set up http header for SASjs Server */'; put '%let fid=%sysfunc(fopen(&fref,A));'; put '%if &fid=0 %then %do;'; put '%put %str(ERR)OR: %sysfunc(sysmsg());'; put '%return;'; put '%end;'; put '%let rc=%sysfunc(fput(&fid,%str(Content-Type: application/json)));'; put '%let rc=%sysfunc(fwrite(&fid));'; put '%let rc=%sysfunc(fclose(&fid));'; put '%let rc=%sysfunc(filename(&fref));'; put '%end;'; put '/* send response in SASjs JSON format */'; put 'data _null_;'; put 'file _webout mod lrecl=32000 encoding=''utf-8'';'; put 'length msg syswarningtext syserrortext $32767 mode $10 ;'; put 'sasdatetime=datetime();'; put 'msg=symget(''msg'');'; put '%if &logline>0 %then %do;'; put 'msg=cats(msg,''\n\nLog Extract:\n'',symget(''logmsg''));'; put '%end;'; put '/* escape the escapes */'; put 'msg=tranwrd(msg,''\'',''\\'');'; put '/* escape the quotes */'; put 'msg=tranwrd(msg,''"'',''\"'');'; put '/* ditch the CRLFs as chrome complains */'; put 'msg=compress(msg,,''kw'');'; put '/* quote without quoting the quotes (which are escaped instead) */'; put 'msg=cats(''"'',msg,''"'');'; put 'if symexist(''_debug'') then debug=quote(trim(symget(''_debug'')));'; put 'else debug=''""'';'; put 'if symget(''sasjsprocessmode'')=''Stored Program'' then mode=''SASJS'';'; put 'if mode ne ''SASJS'' then put ''>>weboutBEGIN<<'';'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'put '',"sasjsAbort" : [{'';'; put 'put '' "MSG":'' msg ;'; put 'put '' ,"MAC": "'' "&mac" ''"}]'';'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'put '',"_DEBUG":'' debug ;'; put 'if symexist(''_metauser'') then do;'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put 'end;'; put 'if symexist(''SYS_JES_JOB_URI'') then do;'; put 'SYS_JES_JOB_URI=quote(trim(symget(''SYS_JES_JOB_URI'')));'; put 'put '',"SYS_JES_JOB_URI": '' SYS_JES_JOB_URI;'; put 'end;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put ",""SYSWARNINGTEXT"" : " syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'put "}" ;'; put 'if mode ne ''SASJS'' then put ''>>weboutEND<<'';'; put 'run;'; put '%put _all_;'; put '%if "&sysprocessmode " = "SAS Stored Process Server " %then %do;'; put 'data _null_;'; put 'putlog ''stpsrvset program err and syscc'';'; put 'rc=stpsrvset(''program error'', 0);'; put 'call symputx("syscc",0,"g");'; put 'run;'; put '%if &sysscp=WIN'; put 'and 1=0 /* deprecating this logic until we figure out a consistent abort */'; put 'and "%substr(%str(&sysvlong ),1,8)"="9.04.01M"'; put 'and "%substr(%str(&sysvlong ),9,1)">"5" %then %do;'; put '/* skip approach (below) does not work in windows m6+ envs */'; put 'endsas;'; put '%end;'; put '%else %do;'; put '/**'; put '* endsas kills 9.4m3 deployments by orphaning multibridges.'; put '* Abort variants are ungraceful (non zero return code)'; put '* This approach lets SAS run silently until the end :-)'; put '* Caution - fails when called within a %include within a macro'; put '* Use mp_include() to handle this.'; put '*/'; put 'filename skip temp;'; put 'data _null_;'; put 'file skip;'; put 'put ''%macro skip();'';'; put 'comment ''%mend skip; -> fix lint '';'; put 'put ''%macro skippy();'';'; put 'comment ''%mend skippy; -> fix lint '';'; put 'run;'; put '%inc skip;'; put '%end;'; put '%end;'; put '%else %if "&sysprocessmode " = "SAS Compute Server " %then %do;'; put '/* endsas kills the session making it harder to fetch results */'; put 'data _null_;'; put 'syswarningtext=symget(''syswarningtext'');'; put 'syserrortext=symget(''syserrortext'');'; put 'abort_msg=symget(''msg'');'; put 'syscc=symget(''syscc'');'; put 'sysuserid=symget(''sysuserid'');'; put 'iftrue=symget(''iftrue'');'; put 'put (_all_)(/=);'; put 'call symputx(''syscc'',0);'; put 'abort cancel nolist;'; put 'run;'; put '%end;'; put '%else %do;'; put '%abort cancel;'; put '%end;'; put '%end;'; put '%else %do;'; put '%put _all_;'; put '%abort cancel;'; put '%end;'; put '%mend mp_abort;'; put '/** @endcond */'; put '%macro mm_getstpcode('; put 'tree=/User Folders/sasdemo/somestp'; put ',name='; put ',outloc=0'; put ',outref=0'; put ',mDebug=1'; put ',showlog=NO'; put ');'; put '%local mD;'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing &sysmacroname..sas;'; put '%&mD.put _local_;'; put '%if %length(&name)>0 %then %let name=/&name;'; put '/* first, check if STP exists */'; put '%local tsuri;'; put '%let tsuri=stopifempty ;'; put 'data _null_;'; put 'format type uri tsuri value $200.;'; put 'call missing (of _all_);'; put 'path="&tree&name(StoredProcess)";'; put '/* first, find the STP ID */'; put 'if metadata_pathobj("",path,"StoredProcess",type,uri)>0 then do;'; put '/* get sourcecode */'; put 'cnt=1;'; put 'do while (metadata_getnasn(uri,"Notes",cnt,tsuri)>0);'; put 'rc=metadata_getattr(tsuri,"Name",value);'; put '&mD.put tsuri= value=;'; put 'if value="SourceCode" then do;'; put '/* found it! */'; put 'rc=metadata_getattr(tsuri,"Id",value);'; put 'call symputx(''tsuri'',value,''l'');'; put 'stop;'; put 'end;'; put 'cnt+1;'; put 'end;'; put 'end;'; put 'else put (_all_)(=);'; put 'run;'; put '%mp_abort(iftrue= (&tsuri=stopifempty)'; put ',mac=mm_getstpcode'; put ',msg=%str(&tree&name.(StoredProcess) not found!)'; put ')'; put '/**'; put '* Now we can extract the textstore'; put '*/'; put 'filename __getdoc temp lrecl=10000000;'; put 'proc metadata'; put 'in="$METAREPOSITORY'; put ''; put 'SAS1"'; put 'out=__getdoc ;'; put 'run;'; put '/* find the beginning of the text */'; put '%local start;'; put 'data _null_;'; put 'infile __getdoc lrecl=10000;'; put 'input;'; put 'start=index(_infile_,''StoredText="'');'; put 'if start then do;'; put 'call symputx("start",start+11);'; put '*putlog ''"'' _infile_ ''"'';'; put 'end;'; put 'stop;'; put '%local outeng;'; put '%if "&outloc"="0" %then %let outeng=TEMP;'; put '%else %let outeng="&outloc";'; put '%local fref;'; put '%if &outref=0 %then %let fref=%mf_getuniquefileref();'; put '%else %let fref=&outref;'; put '/* read the content, byte by byte, resolving escaped chars */'; put 'filename &fref &outeng lrecl=100000;'; put 'data _null_;'; put 'length filein 8 fileid 8;'; put 'filein = fopen("__getdoc","I",1,"B");'; put 'fileid = fopen("&fref","O",1,"B");'; put 'rec = "20"x;'; put 'length entity $6;'; put 'do while(fread(filein)=0);'; put 'x+1;'; put 'if x>&start then do;'; put 'rc = fget(filein,rec,1);'; put 'if rec=''"'' then leave;'; put 'else if rec="&" then do;'; put 'entity=rec;'; put 'do until (rec=";");'; put 'if fread(filein) ne 0 then goto getout;'; put 'rc = fget(filein,rec,1);'; put 'entity=cats(entity,rec);'; put 'end;'; put 'select (entity);'; put 'when (''&'' ) rec=''&'' ;'; put 'when (''<'' ) rec=''<'' ;'; put 'when (''>'' ) rec=''>'' ;'; put 'when (''''') rec="''" ;'; put 'when (''"'') rec=''"'' ;'; put 'when ('' '') rec=''0A''x;'; put 'when ('' '') rec=''0D''x;'; put 'when (''$'' ) rec=''$'' ;'; put 'when ('' '') rec=''09''x;'; put 'otherwise putlog "%str(WARN)ING: missing value for " entity=;'; put 'end;'; put 'rc =fput(fileid, substr(rec,1,1));'; put 'rc =fwrite(fileid);'; put 'end;'; put 'else do;'; put 'rc =fput(fileid,rec);'; put 'rc =fwrite(fileid);'; put 'end;'; put 'end;'; put 'end;'; put 'getout:'; put 'rc=fclose(filein);'; put 'rc=fclose(fileid);'; put 'run;'; put '%if &showlog=YES %then %do;'; put 'data _null_;'; put 'infile &fref lrecl=32767 end=last;'; put 'input;'; put 'if _n_=1 then putlog ''>>stpcodeBEGIN<<'';'; put 'putlog _infile_;'; put 'if last then putlog ''>>stpcodeEND<<'';'; put 'run;'; put '%end;'; put 'filename __getdoc clear;'; put '%if &outref=0 %then %do;'; put 'filename &fref clear;'; put '%end;'; put '%mend mm_getstpcode;'; put '%macro dc_getsettings();'; put '%global _program;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(syscc=&syscc on entry (&syswarningtext &syserrortext))'; put ')'; put '%if %length(&_PROGRAM)>1 %then %let root=&_program;'; put '%else %do;'; put '%global _metauser;'; put '%let _metauser=&sysuserid;'; put '/* to mimic a "real" _program we need to give a dummy role and stp name */'; put '%let root=/dummyRole/dummyName;'; put '%end;'; put '/* the DC precode is stored in the Admin folder in the root of'; put 'the project. Lets find that root. */'; put '%put &=root;'; put '%let root=%mf_getapploc();'; put '%put &=root;'; put '/* Now we know the root location we can retrieve the params */'; put '%let temploc=%sysfunc(pathname(work))/settings.txt;'; put '%mm_getstpcode(tree=&root/services/public'; put ',name=Data_Controller_Settings'; put ',outloc=&temploc'; put ')'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Unable to run getstpcode)'; put ')'; put 'filename _getsets "&temploc" lrecl=2000;'; put '/*'; put 'Do not use mp_include here - this puts a copy in every service, which creates'; put 'compilation problems when calling services from mp_include'; put '*/'; put '%inc _getsets/source2;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Problem running &sysmacroname (&syswarningtext &syserrortext))'; put ')'; put '%mend dc_getsettings;'; put '%macro mf_fmtdttm('; put ')/*/STORE SOURCE*/;'; put '%if "&sysver"="9.2" or "&sysver"="9.3"'; put 'or ("&sysver"="9.4" and "%substr(&SYSVLONG,9,1)" le "3")'; put 'or "%substr(&sysver,1,1)"="4"'; put 'or "%substr(&sysver,1,1)"="5"'; put '%then %do;DATETIME19.3%end;'; put '%else %do;E8601DT26.6%end;'; put '%mend mf_fmtdttm;'; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '%macro mp_init(prefix=SASJS'; put ')/*/STORE SOURCE*/;'; put '%if %symexist(SASJS_PREFIX) %then %return; /* only run once */'; put '%global'; put 'SASJS_PREFIX /* the ONLY hard-coded global macro variable in SASjs */'; put '&prefix._FUNCTIONS /* used in mcf_init() to track core function compilation */'; put '&prefix._INIT_NUM /* initialisation time as numeric */'; put '&prefix._INIT_DTTM /* initialisation time in E8601DT26.6 format */'; put '&prefix.WORK /* avoid typing %sysfunc(pathname(work)) every time */'; put ';'; put '%let sasjs_prefix=&prefix;'; put 'data _null_;'; put 'dttm=datetime();'; put 'call symputx("&sasjs_prefix._init_num",dttm,''g'');'; put 'call symputx("&sasjs_prefix._init_dttm",put(dttm,E8601DT26.6),''g'');'; put 'call symputx("&sasjs_prefix.work",pathname(''WORK''),''g'');'; put 'run;'; put 'options'; put 'compress=CHAR /* default is none so ensure we have something! */'; put 'datastmtchk=ALLKEYWORDS /* protection from overwriting input datasets */'; put 'errorcheck=STRICT /* catch errs in libname/filename statements */'; put 'fmterr /* ensure err when a format cannot be found */'; put 'mergenoby=%str(ERR)OR /* throw err when a merge has no BY variables */'; put 'missing=. /* changing this can cause hard to detect errs */'; put 'noquotelenmax /* avoid warnings for long strings */'; put 'noreplace /* avoid overwriting permanent datasets */'; put 'ps=max /* reduce log size slightly */'; put 'ls=max /* reduce log even more and avoid word truncation */'; put 'validmemname=COMPATIBLE /* avoid special characters etc in table names */'; put 'validvarname=V7 /* avoid special characters etc in variable names */'; put 'varinitchk=%str(ERR)OR /* avoid data mistakes from variable name typos */'; put 'varlenchk=%str(ERR)OR /* fail hard if truncation (data loss) can result */'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5" %then %do;'; put 'noautocorrect /* disallow misspelled procedure names */'; put 'dsoptions=note2err /* undocumented - convert bad NOTEs to ERRs */'; put '%end;'; put ';'; put '%mend mp_init;'; put '%macro mpeinit(fetch=YES);'; put '%global mpeinit'; put 'mpeadmins /* group with unrestricted Meditor access */'; put 'mpelocapprovals /* location for landing and staging files */'; put 'mpelib /* location of configuration tables for DC */'; put 'dc_repo_users /* location of user / group metadata */'; put 'dc_licence_key /* extracted in dc_getsettings */'; put 'dc_activation_key /* extracted in dc_getsettings */'; put 'dc_locale /* extracted in dc_getsettings */'; put 'dc_dttmtfmt /* can be overridden in dc_getsettings */'; put '_debug'; put ';'; put '%if &mpeinit=1 %then %return;'; put '%else %let mpeinit=1;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(Problem on service startup (&syswarningtext &syserrortext))'; put ')'; put '%mp_init()'; put '%if &fetch=YES %then %do;'; put '%webout(FETCH)'; put '%end;'; put '%global _CLIENTNAME;'; put '%mp_abort(iftrue= (&_CLIENTNAME=SAS Enterprise Guide)'; put ',mac=&_program..sas'; put ',msg=%str(Data Controller is a web app and should not be executed from EG)'; put ')'; put 'options urlencoding=utf8 nobomfile lrecl=32767;'; put '%let perf=%sysfunc(datetime());'; put '%put perfdiff: 0;'; put '%let dc_locale=SYSTEM; /* default if not set */'; put '/**'; put '* E8601DT26.6 has widest database support - but not all SAS flavours can'; put '* handle it. Override in the settings STP if needed.'; put '*/'; put 'data _null_;'; put 'dc_dttmtfmt=''"%sysfunc(datetime(),''!!"%mf_fmtdttm()"!!'')"dt'';'; put 'call symputx(''dc_dttmtfmt'',dc_dttmtfmt);'; put 'put dc_dttmtfmt=;'; put 'run;'; put '%put &=dc_dttmtfmt;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc prior to dc_getsettings)'; put ')'; put '%dc_getsettings()'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc after dc_getsettings)'; put ')'; put 'data _null_;'; put 'set &DC_LIBREF..mpe_config(where=('; put 'var_scope="DC"'; put 'and &dc_dttmtfmt lt tx_to'; put 'and var_active=1'; put '));'; put 'call symputx(var_name,var_value,''G'');'; put 'putlog var_name "=" var_value;'; put 'run;'; put '%let mpelib=&dc_libref;'; put '%let mpeadmins=&dc_admin_group;'; put '%let mpelocapprovals=&dc_staging_area;'; put '%let dc_repo_users=&dc_repo_users;'; put '%if &dc_locale ne SYSTEM %then %do;'; put 'options locale=&dc_locale;'; put '%end;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(Problem during compilation or with STP precode (&syswarningtext))'; put ')'; put '%mend mpeinit;'; put '%macro mf_mval(var);'; put '%if %symexist(&var) %then %do;'; put '%superq(&var)'; put '%end;'; put '%mend mf_mval;'; put '%macro mf_trimstr(basestr,trimstr);'; put '%local baselen trimlen trimval;'; put '/* return if basestr is shorter than trimstr (or 0) */'; put '%let baselen=%length(%superq(basestr));'; put '%let trimlen=%length(%superq(trimstr));'; put '%if &baselen < &trimlen or &baselen=0 %then %return;'; put '/* obtain the characters from the end of basestr */'; put '%let trimval=%qsubstr(%superq(basestr)'; put ',%length(%superq(basestr))-&trimlen+1'; put ',&trimlen);'; put '/* compare and if matching, chop it off! */'; put '%if %superq(basestr)=%superq(trimstr) %then %do;'; put '%return;'; put '%end;'; put '%else %if %superq(trimval)=%superq(trimstr) %then %do;'; put '%qsubstr(%superq(basestr),1,%length(%superq(basestr))-&trimlen)'; put '%end;'; put '%else %do;'; put '&basestr'; put '%end;'; put '%mend mf_trimstr;'; put '%macro mf_getplatform(switch'; put ')/*/STORE SOURCE*/;'; put '%local a b c;'; put '%if &switch.NONE=NONE %then %do;'; put '%if %symexist(sasjsprocessmode) %then %do;'; put '%if &sasjsprocessmode=Stored Program %then %do;'; put 'SASJS'; put '%return;'; put '%end;'; put '%end;'; put '%if %symexist(sysprocessmode) %then %do;'; put '%if "&sysprocessmode"="SAS Object Server"'; put 'or "&sysprocessmode"= "SAS Compute Server" %then %do;'; put 'SASVIYA'; put '%end;'; put '%else %if "&sysprocessmode"="SAS Stored Process Server"'; put 'or "&sysprocessmode"="SAS Workspace Server"'; put '%then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if %symexist(_metaport) or %symexist(_metauser) %then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if &switch=SASSTUDIO %then %do;'; put '/* return the version of SAS Studio else 0 */'; put '%if %mf_mval(_CLIENTAPP)=%str(SAS Studio) %then %do;'; put '%let a=%mf_mval(_CLIENTVERSION);'; put '%let b=%scan(&a,1,.);'; put '%if %eval(&b >2) %then %do;'; put '&b'; put '%end;'; put '%else 0;'; put '%end;'; put '%else 0;'; put '%end;'; put '%else %if &switch=VIYARESTAPI %then %do;'; put '%mf_trimstr(%sysfunc(getoption(servicesbaseurl)),/)'; put '%end;'; put '%mend mf_getplatform;'; put '%macro mpeterm();'; put '%local oldloc;'; put 'data _null_;'; put 'if symexist(''SYSPRINTTOLOG'') then oldloc=symget(''SYSPRINTTOLOG'');'; put 'else oldloc=getoption(''LOG'');'; put 'if subpad(oldloc,1,1) not in (''"'',"''",'' '') then oldloc=quote(cats(oldloc));'; put 'call symputx(''oldloc'',oldloc,''l'');'; put 'run;'; put '%if %length(&oldloc)>0 %then %do;'; put 'proc printto log=log;'; put 'run;'; put 'data _null_;'; put 'infile &oldloc;'; put 'input; putlog _infile_;'; put 'run;'; put '%end;'; put '%if %sysfunc(exist(&dc_libref..mpe_requests)) and %mf_getplatform() ne SASVIYA'; put '%then %do;'; put 'data ;'; put 'if 0 then set &dc_libref..mpe_requests;'; put 'request_dttm=%sysfunc(datetime());'; put 'request_user="%mf_getuser()";'; put 'request_service="%scan(&_program,-2,/)/%scan(&_program,-1,/)";'; put 'request_params='''';'; put 'output;stop;'; put 'proc append base=&dc_libref..mpe_requests data=&syslast force nowarn;'; put 'run;'; put '%end;'; put '%mend mpeterm;'; put '%macro mm_assignlib('; put 'libref'; put ',mAbort=HARD'; put ')/*/STORE SOURCE*/;'; put '%local mp_abort msg;'; put '%let mp_abort=0;'; put '%if %sysfunc(libref(&libref)) %then %do;'; put 'data _null_;'; put 'length liburi LibName msg $200;'; put 'call missing(of _all_);'; put 'nobj=metadata_getnobj("omsobj:SASLibrary?@Libref=''&libref''",1,liburi);'; put 'if nobj=1 then do;'; put 'rc=metadata_getattr(liburi,"Name",LibName);'; put '/* now try and assign it */'; put 'if libname("&libref",,''meta'',cats(''liburi="'',liburi,''";'')) ne 0 then do;'; put 'putlog "&libref could not be assigned";'; put 'putlog liburi=;'; put '/**'; put '* Fetch the system message for display in the abort modal. This is'; put '* not always helpful though. One example, previously received:'; put '* NOTE: Libref XX refers to the same library metadata as libref XX.'; put '*/'; put 'msg=sysmsg();'; put 'if msg=:''ERROR: Libref SAVE is not assigned.'' then do;'; put 'msg=catx(" ",'; put '"Could not assign %upcase(&libref).",'; put '"Please check metadata permissions! Libname:",libname,'; put '"Liburi:",liburi'; put ');'; put 'end;'; put 'else if msg="ERROR: User does not have appropriate authorization "!!'; put '"level for library SAVE."'; put 'then do;'; put 'msg=catx(" ",'; put '"ERROR: User does not have appropriate authorization level",'; put '"for library %upcase(&libref), libname:",libname,'; put '"Liburi:",liburi'; put ');'; put 'end;'; put 'call symputx(''msg'',msg,''l'');'; put 'if "&mabort"=''HARD'' then call symputx(''mp_abort'',1,''l'');'; put 'end;'; put 'else do;'; put 'put (_all_)(=);'; put 'call symputx(''libname'',libname,''L'');'; put 'call symputx(''liburi'',liburi,''L'');'; put 'end;'; put 'end;'; put 'else if nobj>1 then do;'; put 'if "&mabort"=''HARD'' then call symputx(''mp_abort'',1);'; put 'call symputx(''msg'',"More than one library with libref=&libref");'; put 'end;'; put 'else do;'; put 'if "&mabort"=''HARD'' then call symputx(''mp_abort'',1);'; put 'call symputx(''msg'',"Library &libref not found in metadata");'; put 'end;'; put 'run;'; put '%put NOTE: &msg;'; put '%end;'; put '%else %do;'; put '%put NOTE: Library &libref is already assigned;'; put '%end;'; put '%mp_abort(iftrue= (&mp_abort=1)'; put ',mac=mm_assignlib.sas'; put ',msg=%superq(msg)'; put ')'; put '%mend mm_assignlib;'; put '/** @cond */'; put '%macro mf_getengine(libref'; put ')/*/STORE SOURCE*/;'; put '%local dsid engnum rc engine;'; put '/* in case the parameter is a libref.tablename, pull off just the libref */'; put '%let libref = %upcase(%scan(&libref, 1, %str(.)));'; put '%let dsid=%sysfunc('; put 'open(sashelp.vlibnam(where=(libname="%upcase(&libref)")),i)'; put ');'; put '%if (&dsid ^= 0) %then %do;'; put '%let engnum=%sysfunc(varnum(&dsid,ENGINE));'; put '%let rc=%sysfunc(fetch(&dsid));'; put '%let engine=%sysfunc(getvarc(&dsid,&engnum));'; put '%put &libref. ENGINE is &engine.;'; put '%let rc= %sysfunc(close(&dsid));'; put '%end;'; put '%upcase(&engine)'; put '%mend mf_getengine;'; put '/** @endcond */'; put '%macro mm_assigndirectlib('; put 'libref'; put ',open_passthrough='; put ',sql_options='; put ',mDebug=0'; put ',mAbort=0'; put ')/*/STORE SOURCE*/;'; put '%local mD;'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing mm_assigndirectlib.sas;'; put '%&mD.put _local_;'; put '%if &mAbort=1 %then %let mAbort=;'; put '%else %let mAbort=%str(*);'; put '%&mD.put NOTE: Creating direct (non META) connection to &libref library;'; put '%local cur_engine;'; put '%let cur_engine=%mf_getengine(&libref);'; put '%if &cur_engine ne META and &cur_engine ne %then %do;'; put '%put NOTE: &libref already has a direct (&cur_engine) libname connection;'; put '%return;'; put '%end;'; put '%else %if %upcase(&libref)=WORK %then %do;'; put '%put NOTE: We already have a direct connection to WORK :-) ;'; put '%return;'; put '%end;'; put '/* need to determine the library ENGINE first */'; put '%local engine;'; put 'data _null_;'; put 'length lib_uri engine $256;'; put 'call missing (of _all_);'; put '/* get URI for the particular library */'; put 'rc1=metadata_getnobj("omsobj:SASLibrary?@Libref =''&libref''",1,lib_uri);'; put '/* get the Engine attribute of the previous object */'; put 'rc2=metadata_getattr(lib_uri,''Engine'',engine);'; put 'putlog "mm_assigndirectlib for &libref:" rc1= lib_uri= rc2= engine=;'; put 'call symputx("liburi",lib_uri,''l'');'; put 'call symputx("engine",engine,''l'');'; put 'run;'; put '/* now obtain engine specific connection details */'; put '%if &engine=BASE %then %do;'; put '%&mD.put NOTE: Retrieving BASE library path;'; put 'data _null_;'; put 'length up_uri $256 path cat_path $1024;'; put 'retain cat_path;'; put 'call missing (of _all_);'; put '/* get all the filepaths of the UsingPackages association */'; put 'i=1;'; put 'rc3=metadata_getnasn("&liburi",''UsingPackages'',i,up_uri);'; put 'do while (rc3>0);'; put '/* get the DirectoryName attribute of the previous object */'; put 'rc4=metadata_getattr(up_uri,''DirectoryName'',path);'; put 'if i=1 then path = ''("''!!trim(path)!!''" '';'; put 'else path ='' "''!!trim(path)!!''" '';'; put 'cat_path = trim(cat_path) !! " " !! trim(path) ;'; put 'i+1;'; put 'rc3=metadata_getnasn("&liburi",''UsingPackages'',i,up_uri);'; put 'end;'; put 'cat_path = trim(cat_path) !! ")";'; put '&mD.putlog "NOTE: Getting physical path for &libref library";'; put '&mD.putlog rc3= up_uri= rc4= cat_path= path=;'; put '&mD.putlog "NOTE: Libname cmd will be:";'; put '&mD.putlog "libname &libref" cat_path;'; put 'call symputx("filepath",cat_path,''l'');'; put 'run;'; put '%if %sysevalf(&sysver<9.4) %then %do;'; put 'libname &libref &filepath;'; put '%end;'; put '%else %do;'; put '/* apply the new filelocks option to cater for temporary locks */'; put 'libname &libref &filepath filelockwait=5;'; put '%end;'; put '%end;'; put '%else %if &engine=REMOTE %then %do;'; put 'data x;'; put 'length rcCon rcProp rc k 3 uriCon uriProp PropertyValue PropertyName'; put 'Delimiter $256 properties $2048;'; put 'retain properties;'; put 'rcCon = metadata_getnasn("&liburi", "LibraryConnection", 1, uriCon);'; put 'rcProp = metadata_getnasn(uriCon, "Properties", 1, uriProp);'; put 'k = 1;'; put 'rcProp = metadata_getnasn(uriCon, "Properties", k, uriProp);'; put 'do while (rcProp > 0);'; put 'rc = metadata_getattr(uriProp , "DefaultValue",PropertyValue);'; put 'rc = metadata_getattr(uriProp , "PropertyName",PropertyName);'; put 'rc = metadata_getattr(uriProp , "Delimiter",Delimiter);'; put 'properties = trim(properties) !! " " !! trim(PropertyName)'; put '!! trim(Delimiter) !! trim(PropertyValue);'; put 'output;'; put 'k+1;'; put 'rcProp = metadata_getnasn(uriCon, "Properties", k, uriProp);'; put 'end;'; put '%&mD.put NOTE: Getting properties for REMOTE SHARE &libref library;'; put '&mD.put _all_;'; put '%&mD.put NOTE: Libname cmd will be:;'; put '%&mD.put libname &libref &engine &properties slibref=&libref;'; put 'call symputx ("properties",trim(properties),''l'');'; put 'run;'; put 'libname &libref &engine &properties slibref=&libref;'; put '%end;'; put '%else %if &engine=OLEDB %then %do;'; put '%&mD.put NOTE: Retrieving OLEDB connection details;'; put 'data _null_;'; put 'length domain datasource provider properties schema'; put 'connx_uri domain_uri conprop_uri lib_uri schema_uri value $256.;'; put 'call missing (of _all_);'; put '/* get source connection ID */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,connx_uri);'; put '/* get connection domain */'; put 'rc1=metadata_getnasn(connx_uri,''Domain'',1,domain_uri);'; put 'rc2=metadata_getattr(domain_uri,''Name'',domain);'; put '&mD.putlog / ''NOTE: '' // ''NOTE- connection id: '' connx_uri ;'; put '&mD.putlog ''NOTE- domain: '' domain;'; put '/* get DSN and PROVIDER from connection properties */'; put 'i=0;'; put 'do until (rc<0);'; put 'i+1;'; put 'rc=metadata_getnasn(connx_uri,''Properties'',i,conprop_uri);'; put 'rc2=metadata_getattr(conprop_uri,''Name'',value);'; put 'if value=''Connection.OLE.Property.DATASOURCE.Name.xmlKey.txt'' then do;'; put 'rc3=metadata_getattr(conprop_uri,''DefaultValue'',datasource);'; put 'end;'; put 'else if value=''Connection.OLE.Property.PROVIDER.Name.xmlKey.txt'' then do;'; put 'rc4=metadata_getattr(conprop_uri,''DefaultValue'',provider);'; put 'end;'; put 'else if value=''Connection.OLE.Property.PROPERTIES.Name.xmlKey.txt'' then'; put 'do;'; put 'rc5=metadata_getattr(conprop_uri,''DefaultValue'',properties);'; put 'end;'; put 'end;'; put '&mD.putlog ''NOTE- dsn/provider/properties: '' /'; put 'datasource provider properties;'; put '&mD.putlog ''NOTE- schema: '' schema // ''NOTE-'';'; put '/* get SCHEMA */'; put 'rc6=metadata_getnasn("&liburi",''UsingPackages'',1,lib_uri);'; put 'rc7=metadata_getattr(lib_uri,''SchemaName'',schema);'; put 'call symputx(''SQL_domain'',domain,''l'');'; put 'call symputx(''SQL_dsn'',datasource,''l'');'; put 'call symputx(''SQL_provider'',provider,''l'');'; put 'call symputx(''SQL_properties'',properties,''l'');'; put 'call symputx(''SQL_schema'',schema,''l'');'; put 'run;'; put '%if %length(&open_passthrough)>0 %then %do;'; put 'proc sql &sql_options;'; put 'connect to OLEDB as &open_passthrough(INSERT_SQL=YES'; put '/* need additional properties to make this work */'; put 'properties=(''Integrated Security''=SSPI'; put '''Persist Security Info''=True'; put '%sysfunc(compress(%str(&SQL_properties),%str(())))'; put ')'; put 'DATASOURCE=&sql_dsn PROMPT=NO'; put 'PROVIDER=&sql_provider SCHEMA=&sql_schema CONNECTION = GLOBAL);'; put '%end;'; put '%else %do;'; put 'LIBNAME &libref OLEDB PROPERTIES=&sql_properties'; put 'DATASOURCE=&sql_dsn PROVIDER=&sql_provider SCHEMA=&sql_schema'; put '%if %length(&sql_domain)>0 %then %do;'; put 'authdomain="&sql_domain"'; put '%end;'; put 'connection=shared;'; put '%end;'; put '%end;'; put '%else %if &engine=ODBC %then %do;'; put '%&mD.put NOTE: Retrieving ODBC connection details;'; put 'data _null_;'; put 'length connx_uri conprop_uri value datasource up_uri schema domprop_uri authdomain $256.;'; put 'call missing (of _all_);'; put '/* get source connection ID */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,connx_uri);'; put '/* get connection properties */'; put 'i=0;'; put 'do until (rc2<0);'; put 'i+1;'; put 'rc2=metadata_getnasn(connx_uri,''Properties'',i,conprop_uri);'; put 'rc3=metadata_getattr(conprop_uri,''Name'',value);'; put 'if value=''Connection.ODBC.Property.DATASRC.Name.xmlKey.txt'' then do;'; put 'rc4=metadata_getattr(conprop_uri,''DefaultValue'',datasource);'; put 'rc2=-1;'; put 'end;'; put 'end;'; put '/* get auth domain */'; put 'autrc=metadata_getnasn(connx_uri,"Domain",1,domprop_uri);'; put 'arc=metadata_getattr(domprop_uri,"Name",authdomain);'; put 'if not missing(authdomain) then authdomain=cats(''AUTHDOMAIN='',authdomain);'; put 'call symputx(''authdomain'',authdomain,''l'');'; put '/* get SCHEMA */'; put 'rc6=metadata_getnasn("&liburi",''UsingPackages'',1,up_uri);'; put 'rc7=metadata_getattr(up_uri,''SchemaName'',schema);'; put '&mD.put rc= connx_uri= rc2= conprop_uri= rc3= value= rc4= datasource='; put 'rc6= up_uri= rc7= schema=;'; put 'call symputx(''SQL_schema'',schema,''l'');'; put 'call symputx(''SQL_dsn'',datasource,''l'');'; put 'run;'; put '%if %length(&open_passthrough)>0 %then %do;'; put 'proc sql &sql_options;'; put 'connect to ODBC as &open_passthrough'; put '(INSERT_SQL=YES DATASRC=&sql_dsn. CONNECTION=global);'; put '%end;'; put '%else %do;'; put 'libname &libref ODBC DATASRC=&sql_dsn SCHEMA=&sql_schema &authdomain;'; put '%end;'; put '%end;'; put '%else %if &engine=POSTGRES %then %do;'; put '%put NOTE: Obtaining POSTGRES library details;'; put 'data _null_;'; put 'length database ignore_read_only_columns direct_exe preserve_col_names'; put 'preserve_tab_names server schema authdomain user password'; put 'prop name value uri urisrc $256.;'; put 'call missing (of _all_);'; put '/* get database value */'; put 'prop=''Connection.DBMS.Property.DB.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,database,"");'; put 'if database^='''' then database=''database=''!!quote(trim(database));'; put 'call symputx(''database'',database,''l'');'; put '/* get IGNORE_READ_ONLY_COLUMNS value */'; put 'prop=''Library.DBMS.Property.DBIROC.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,ignore_read_only_columns,"");'; put 'if ignore_read_only_columns^='''' then ignore_read_only_columns='; put '''ignore_read_only_columns=''!!ignore_read_only_columns;'; put 'call symputx(''ignore_read_only_columns'',ignore_read_only_columns,''l'');'; put '/* get DIRECT_EXE value */'; put 'prop=''Library.DBMS.Property.DirectExe.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,direct_exe,"");'; put 'if direct_exe^='''' then direct_exe=''direct_exe=''!!direct_exe;'; put 'call symputx(''direct_exe'',direct_exe,''l'');'; put '/* get PRESERVE_COL_NAMES value */'; put 'prop=''Library.DBMS.Property.PreserveColNames.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,preserve_col_names,"");'; put 'if preserve_col_names^='''' then preserve_col_names='; put '''preserve_col_names=''!!preserve_col_names;'; put 'call symputx(''preserve_col_names'',preserve_col_names,''l'');'; put '/* get PRESERVE_TAB_NAMES value */'; put '/* be careful with PRESERVE_TAB_NAMES=YES - it will mean your table will'; put 'become case sensitive!! */'; put 'prop=''Library.DBMS.Property.PreserveTabNames.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,preserve_tab_names,"");'; put 'if preserve_tab_names^='''' then preserve_tab_names='; put '''preserve_tab_names=''!!preserve_tab_names;'; put 'call symputx(''preserve_tab_names'',preserve_tab_names,''l'');'; put '/* get SERVER value */'; put 'if metadata_getnasn("&liburi","LibraryConnection",1,uri)>0 then do;'; put 'prop=''Connection.DBMS.Property.SERVER.Name.xmlKey.txt'';'; put 'rc=metadata_getprop(uri,prop,server,"");'; put 'end;'; put 'if server^='''' then server=''server=''!!quote(cats(server));'; put 'call symputx(''server'',server,''l'');'; put '/* get SCHEMA value */'; put 'if metadata_getnasn("&liburi","UsingPackages",1,uri)>0 then do;'; put 'rc=metadata_getattr(uri,"SchemaName",schema);'; put 'end;'; put 'if schema^='''' then schema=''schema=''!!schema;'; put 'call symputx(''schema'',schema,''l'');'; put '/* get AUTHDOMAIN value */'; put '/* this is only useful if the user account contains that auth domain'; put 'if metadata_getnasn("&liburi","DefaultLogin",1,uri)>0 then do;'; put 'rc=metadata_getnasn(uri,"Domain",1,urisrc);'; put 'rc=metadata_getattr(urisrc,"Name",authdomain);'; put 'end;'; put 'if authdomain^='''' then authdomain=''authdomain=''!!quote(trim(authdomain));'; put '*/'; put 'call symputx(''authdomain'',authdomain,''l'');'; put '/* get user & pass */'; put 'if authdomain='''' & metadata_getnasn("&liburi","DefaultLogin",1,uri)>0 then'; put 'do;'; put 'rc=metadata_getattr(uri,"UserID",user);'; put 'rc=metadata_getattr(uri,"Password",password);'; put 'end;'; put 'if user^='''' then do;'; put 'user=''user=''!!quote(trim(user));'; put 'password=''password=''!!quote(trim(password));'; put 'end;'; put 'call symputx(''user'',user,''l'');'; put 'call symputx(''password'',password,''l'');'; put '&md.put _all_;'; put 'run;'; put '%if %length(&open_passthrough)>0 %then %do;'; put '%put %str(WARN)ING: Passthrough option for postgres not yet supported;'; put '%return;'; put '%end;'; put '%else %do;'; put '%if &mdebug=1 %then %do;'; put '%put NOTE: Executing the following:/;'; put '%put NOTE- libname &libref POSTGRES &database &ignore_read_only_columns;'; put '%put NOTE- &direct_exe &preserve_col_names &preserve_tab_names;'; put '%put NOTE- &server &schema &authdomain &user &password //;'; put '%end;'; put 'libname &libref POSTGRES &database &ignore_read_only_columns &direct_exe'; put '&preserve_col_names &preserve_tab_names &server &schema &authdomain'; put '&user &password;'; put '%end;'; put '%end;'; put '%else %if &engine=ORACLE %then %do;'; put '%put NOTE: Obtaining &engine library details;'; put 'data _null_;'; put 'length assocuri1 assocuri2 assocuri3 authdomain path schema $256;'; put 'call missing (of _all_);'; put '/* get auth domain */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,assocuri1);'; put 'rc=metadata_getnasn(assocuri1,''Domain'',1,assocuri2);'; put 'rc=metadata_getattr(assocuri2,"Name",authdomain);'; put 'call symputx(''authdomain'',authdomain,''l'');'; put '/* path */'; put 'rc=metadata_getprop(assocuri1,'; put '''Connection.Oracle.Property.PATH.Name.xmlKey.txt'',path);'; put 'call symputx(''path'',path,''l'');'; put '/* schema */'; put 'rc=metadata_getnasn("&liburi",''UsingPackages'',1,assocuri3);'; put 'rc=metadata_getattr(assocuri3,''SchemaName'',schema);'; put 'call symputx(''schema'',schema,''l'');'; put 'run;'; put '%put NOTE: Executing the following:/; %put NOTE-;'; put '%put NOTE- libname &libref ORACLE path=&path schema=&schema;'; put '%put NOTE- authdomain=&authdomain;'; put '%put NOTE-;'; put 'libname &libref ORACLE path=&path schema=&schema authdomain=&authdomain;'; put '%end;'; put '%else %if &engine=SQLSVR %then %do;'; put '%put NOTE: Obtaining &engine library details;'; put 'data _null;'; put 'length assocuri1 assocuri2 assocuri3 authdomain path schema userid'; put 'passwd $256;'; put 'call missing (of _all_);'; put 'rc=metadata_getnasn("&liburi",''DefaultLogin'',1,assocuri1);'; put 'rc=metadata_getattr(assocuri1,"UserID",userid);'; put 'rc=metadata_getattr(assocuri1,"Password",passwd);'; put 'call symputx(''user'',userid,''l'');'; put 'call symputx(''pass'',passwd,''l'');'; put '/* path */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,assocuri2);'; put 'rc=metadata_getprop(assocuri2,'; put '''Connection.SQL.Property.Datasrc.Name.xmlKey.txt'',path);'; put 'call symputx(''path'',path,''l'');'; put '/* schema */'; put 'rc=metadata_getnasn("&liburi",''UsingPackages'',1,assocuri3);'; put 'rc=metadata_getattr(assocuri3,''SchemaName'',schema);'; put 'call symputx(''schema'',schema,''l'');'; put 'run;'; put '%put NOTE: Executing the following:/; %put NOTE-;'; put '%put NOTE- libname &libref SQLSVR datasrc=&path schema=&schema ;'; put '%put NOTE- user="&user" pass="XXX";'; put '%put NOTE-;'; put 'libname &libref SQLSVR datasrc=&path schema=&schema user="&user" pass="&pass";'; put '%end;'; put '%else %if &engine=TERADATA %then %do;'; put '%put NOTE: Obtaining &engine library details;'; put 'data _null;'; put 'length assocuri1 assocuri2 assocuri3 authdomain path schema userid'; put 'passwd $256;'; put 'call missing (of _all_);'; put '/* get auth domain */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,assocuri1);'; put 'rc=metadata_getnasn(assocuri1,''Domain'',1,assocuri2);'; put 'rc=metadata_getattr(assocuri2,"Name",authdomain);'; put 'call symputx(''authdomain'',authdomain,''l'');'; put '/*'; put 'rc=metadata_getnasn("&liburi",''DefaultLogin'',1,assocuri1);'; put 'rc=metadata_getattr(assocuri1,"UserID",userid);'; put 'rc=metadata_getattr(assocuri1,"Password",passwd);'; put 'call symputx(''user'',userid,''l'');'; put 'call symputx(''pass'',passwd,''l'');'; put '*/'; put '/* path */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,assocuri2);'; put 'rc=metadata_getprop(assocuri2,'; put '''Connection.Teradata.Property.SERVER.Name.xmlKey.txt'',path);'; put 'call symputx(''path'',path,''l'');'; put '/* schema */'; put 'rc=metadata_getnasn("&liburi",''UsingPackages'',1,assocuri3);'; put 'rc=metadata_getattr(assocuri3,''SchemaName'',schema);'; put 'call symputx(''schema'',schema,''l'');'; put 'run;'; put '%put NOTE: Executing the following:/; %put NOTE-;'; put '%put NOTE- libname &libref TERADATA server="&path" schema=&schema ;'; put '%put NOTe- authdomain=&authdomain;'; put '%put NOTE-;'; put 'libname &libref TERADATA server="&path" schema=&schema authdomain=&authdomain;'; put '%end;'; put '%else %if &engine= %then %do;'; put '%put NOTE: Libref &libref is not registered in metadata;'; put '%&mAbort.mp_abort('; put 'msg=%str(ERR)OR: Libref &libref is not registered in metadata'; put ',mac=mm_assigndirectlib.sas);'; put '%return;'; put '%end;'; put '%else %do;'; put '%put %str(WARN)ING: Engine &engine is currently unsupported;'; put '%put %str(WARN)ING- Please contact your support team.;'; put '%return;'; put '%end;'; put '%mend mm_assigndirectlib;'; put '%macro mm_getrepos('; put 'outds=work.mm_getrepos'; put ')/*/STORE SOURCE*/;'; put '* use a temporary fileref to hold the response;'; put 'filename response temp;'; put '/* get list of libraries */'; put 'proc metadata in='; put '"1"'; put 'out=response;'; put 'run;'; put '/* write the response to the log for debugging */'; put '/*'; put 'data _null_;'; put 'infile response lrecl=1048576;'; put 'input;'; put 'put _infile_;'; put 'run;'; put '*/'; put '/* create an XML map to read the response */'; put 'filename sxlemap temp;'; put 'data _null_;'; put 'file sxlemap;'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository";'; put 'put "";'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Id";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Name";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Desc";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@DefaultNS";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@RepositoryType";'; put 'put "characterstring20";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@RepositoryFormat";'; put 'put "characterstring10";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@Access";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@CurrentAccess";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@PauseState";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Path";'; put 'put "";'; put 'put "characterstring256";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Engine";'; put 'put "";'; put 'put "characterstring8";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Options";'; put 'put "";'; put 'put "characterstring32";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@MetadataCreated";'; put 'put "characterstring24";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@MetadataUpdated";'; put 'put "characterstring24";'; put 'put '''';'; put 'put ''
'';'; put 'run;'; put 'libname _XML_ xml xmlfileref=response xmlmap=sxlemap;'; put 'proc sort data= _XML_.SASRepos out=&outds;'; put 'by name;'; put 'run;'; put '/* clear references */'; put 'filename sxlemap clear;'; put 'filename response clear;'; put 'libname _XML_ clear;'; put '%mend mm_getrepos;'; put '%macro dc_assignlib(type,libref,passthru=);'; put '%put &sysmacroname entry vars:;'; put '%put _local_;'; put '/* take current repository */'; put '%local repo repocnt x;'; put '%let repo=%sysfunc(getoption(metarepository));'; put '/* get list of repositories and filter */'; put '%mm_getrepos(outds=repos)'; put '%let repocnt=0;'; put 'data repos;'; put 'set repos;'; put 'where repositorytype in(''CUSTOM'',''FOUNDATION'')'; put '& upcase(name) ne "%upcase(&repo)";'; put 'keep id name ;'; put 'call symputx(cats(''repo'',_n_),name,''l'');'; put 'call symputx(''repocnt'',_n_,''l'');'; put 'run;'; put '/* find out which of these repositories has the libref we are searching for */'; put '%local lib_uri;'; put '%let lib_uri=NOTFOUND;'; put 'data _null_; /* check default repo first */'; put 'length lib_uri $256;'; put 'call missing (of _all_);'; put 'rc=metadata_getnobj("omsobj:SASLibrary?@Libref =''&libref''",1,lib_uri);'; put 'call symputx(''lib_uri'',coalescec(lib_uri,''NOTFOUND''),''l'');'; put 'run;'; put '%if &lib_uri=NOTFOUND %then %do;'; put '%do x=1 %to &repocnt;'; put 'options metarepository=&&repo&x;'; put 'data _null_;'; put 'length lib_uri $256;'; put 'call missing (of _all_);'; put 'rc=metadata_getnobj("omsobj:SASLibrary?@Libref =''&libref''",1,lib_uri);'; put 'call symputx(''lib_uri'',coalescec(lib_uri,''NOTFOUND''),''l'');'; put 'run;'; put '%if &lib_uri ne NOTFOUND %then %let x=%eval(&repocnt+1);'; put '%end;'; put '%end;'; put '%if &type=READ %then %do;'; put '%mm_assignlib(&libref)'; put '%end;'; put '%else %if &type=WRITE %then %do;'; put '%mm_assigndirectlib(&libref,open_passthrough=&passthru)'; put '%end;'; put 'options metarepository=&repo;'; put '%mend dc_assignlib;'; put '/** @cond */'; put '%macro mf_existvar(libds /* 2 part dataset name */'; put ', var /* variable name */'; put ')/*/STORE SOURCE*/;'; put '%local dsid rc;'; put '%let dsid=%sysfunc(open(&libds,is));'; put '%if &dsid=0 %then %do;'; put '%put %sysfunc(sysmsg());'; put '0'; put '%end;'; put '%else %if %length(&var)=0 %then %do;'; put '0'; put '%let rc=%sysfunc(close(&dsid));'; put '%end;'; put '%else %do;'; put '%sysfunc(varnum(&dsid,&var))'; put '%let rc=%sysfunc(close(&dsid));'; put '%end;'; put '%mend mf_existvar;'; put '/** @endcond */'; put '%macro mf_getattrn('; put 'libds'; put ',attr'; put ')/*/STORE SOURCE*/;'; put '%local dsid rc;'; put '%let dsid=%sysfunc(open(&libds,is));'; put '%if &dsid = 0 %then %do;'; put '%put %str(WARN)ING: Cannot open %trim(&libds), system message below;'; put '%put %sysfunc(sysmsg());'; put '-1'; put '%end;'; put '%else %do;'; put '%sysfunc(attrn(&dsid,&attr))'; put '%let rc=%sysfunc(close(&dsid));'; put '%end;'; put '%mend mf_getattrn;'; put '%macro mf_getvartype(libds /* two level name */'; put ', var /* variable name from which to return the type */'; put ')/*/STORE SOURCE*/;'; put '%local dsid vnum vtype rc;'; put '/* Open dataset */'; put '%let dsid = %sysfunc(open(&libds));'; put '%if &dsid. > 0 %then %do;'; put '/* Get variable number */'; put '%let vnum = %sysfunc(varnum(&dsid, &var));'; put '/* Get variable type (C/N) */'; put '%if(&vnum. > 0) %then %let vtype = %sysfunc(vartype(&dsid, &vnum.));'; put '%else %do;'; put '%put NOTE: Variable &var does not exist in &libds;'; put '%let vtype = %str( );'; put '%end;'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: dataset &libds not opened! (rc=&dsid);'; put '%put &sysmacroname: %sysfunc(sysmsg());'; put '%return;'; put '%end;'; put '/* Close dataset */'; put '%let rc = %sysfunc(close(&dsid));'; put '/* Return variable type */'; put '&vtype'; put '%mend mf_getvartype;'; put '%macro mf_getattrc('; put 'libds'; put ',attr'; put ')/*/STORE SOURCE*/;'; put '%local dsid rc;'; put '%let dsid=%sysfunc(open(&libds,is));'; put '%if &dsid = 0 %then %do;'; put '%put %str(WARN)ING: Cannot open %trim(&libds), system message below;'; put '%put %sysfunc(sysmsg());'; put '-1'; put '%end;'; put '%else %do;'; put '%sysfunc(attrc(&dsid,&attr))'; put '%let rc=%sysfunc(close(&dsid));'; put '%end;'; put '%mend mf_getattrc;'; put '%macro mp_lockfilecheck('; put 'libds'; put ')/*/STORE SOURCE*/;'; put 'data _null_;'; put 'if _n_=1 then putlog "&sysmacroname entry vars:";'; put 'set sashelp.vmacro;'; put 'where scope="&sysmacroname";'; put 'put name ''='' value;'; put 'run;'; put '%mp_abort(iftrue= (&syscc>0)'; put ',mac=checklock.sas'; put ',msg=Aborting with syscc=&syscc on entry.'; put ')'; put '%mp_abort(iftrue= ("&libds"="0")'; put ',mac=&sysmacroname'; put ',msg=%str(libds not provided)'; put ')'; put '%local msg lib ds;'; put '%let lib=%upcase(%scan(&libds,1,.));'; put '%let ds=%upcase(%scan(&libds,2,.));'; put '/* in DC, format catalogs are passed with a -FC suffix. No saslock here! */'; put '%if %scan(&libds,2,-)=FC %then %do;'; put '%put &sysmacroname: Format Catalog detected, no lockfile applied to &libds;'; put '%return;'; put '%end;'; put '/* do not proceed if no observations can be processed */'; put '%let msg=options obs = 0. syserrortext=%superq(syserrortext);'; put '%mp_abort(iftrue= (%sysfunc(getoption(OBS))=0)'; put ',mac=checklock.sas'; put ',msg=%superq(msg)'; put ')'; put 'data _null_;'; put 'putlog "Checking engine & member type";'; put 'run;'; put '%local engine memtype;'; put '%let memtype=%mf_getattrc(&libds,MTYPE);'; put '%let engine=%mf_getattrc(&libds,ENGINE);'; put '%if &engine ne V9 and &engine ne BASE %then %do;'; put 'data _null_;'; put 'putlog "Lib &lib is not assigned using BASE engine - uses &engine instead";'; put 'putlog "SAS lock check will not be performed";'; put 'run;'; put '%return;'; put '%end;'; put '%else %if &memtype ne DATA %then %do;'; put '%put NOTE: Cannot lock a VIEW!! Memtype=&memtype;'; put '%return;'; put '%end;'; put 'data _null_;'; put 'putlog "Engine = &engine, memtype=&memtype";'; put 'putlog "Attempting lock statement";'; put 'run;'; put 'lock &libds;'; put '%local abortme;'; put '%let abortme=0;'; put '%if &syscc>0 or &SYSLCKRC ne 0 %then %do;'; put '%let msg=Unable to apply lock on &libds (SYSLCKRC=&SYSLCKRC syscc=&syscc);'; put '%put %str(ERR)OR: &sysmacroname: &msg;'; put '%let abortme=1;'; put '%end;'; put 'lock &libds clear;'; put '%mp_abort(iftrue= (&abortme=1)'; put ',mac=&sysmacroname'; put ',msg=%superq(msg)'; put ')'; put '%mend mp_lockfilecheck;'; put '%macro mp_lockanytable('; put 'action'; put ',lib= WORK'; put ',ds=0'; put ',ref='; put ',ctl_ds=0'; put ',loops=25'; put ',loop_secs=1'; put ');'; put 'data _null_;'; put 'if _n_=1 then putlog "&sysmacroname entry vars:";'; put 'set sashelp.vmacro;'; put 'where scope="&sysmacroname";'; put 'put name ''='' value;'; put 'run;'; put '%mp_abort(iftrue= ("&ds"="0" and &action ne MAKETABLE)'; put ',mac=&sysmacroname'; put ',msg=%str(dataset was not provided)'; put ')'; put '%mp_abort(iftrue= (&ctl_ds=0)'; put ',mac=&sysmacroname'; put ',msg=%str(Control dataset was not provided)'; put ')'; put '/* set up lib & mac vars */'; put '%let lib=%upcase(&lib);'; put '%let ds=%upcase(&ds);'; put '%let action=%upcase(&action);'; put '%local user x trans msg abortme;'; put '%let user=%mf_getuser();'; put '%let abortme=0;'; put '%mp_abort(iftrue= (&action ne LOCK & &action ne UNLOCK & &action ne MAKETABLE)'; put ',mac=&sysmacroname'; put ',msg=%str(Invalid action (&action) provided)'; put ')'; put '/* if an err condition exists, exit before we even begin */'; put '%mp_abort(iftrue= (&syscc>0 and &action=LOCK)'; put ',mac=&sysmacroname'; put ',msg=%str(aborting due to syscc=&syscc on LOCK entry)'; put ')'; put '/* do not bother locking work tables (else may affect all WORK libraries) */'; put '%if (%upcase(&lib)=WORK or %str(&lib)=%str()) & &action ne MAKETABLE %then %do;'; put '%put NOTE: WORK libraries will not be registered in the locking system.;'; put '%return;'; put '%end;'; put '/* do not proceed if no observations can be processed */'; put '%mp_abort(iftrue= (%sysfunc(getoption(OBS))=0)'; put ',mac=&sysmacroname'; put ',msg=%str(cannot continue when options obs = 0)'; put ')'; put '%if &ACTION=LOCK %then %do;'; put '/* abort if a SAS lock is already in place, or cannot be applied */'; put '%mp_lockfilecheck(&lib..&ds)'; put '/* next, check there is a record for this table */'; put '%local record_exists_check;'; put 'proc sql noprint;'; put 'select count(*) into: record_exists_check from &ctl_ds'; put 'where LOCK_LIB ="&lib" and LOCK_DS="&ds";'; put 'quit;'; put '%if &syscc>0 %then %put syscc=&syscc sqlrc=&sqlrc;'; put '%if &record_exists_check=0 %then %do;'; put 'data _null_;'; put 'putlog "&sysmacroname: adding record to lock table..";'; put 'run;'; put 'data ;'; put 'if 0 then set &ctl_ds;'; put 'LOCK_LIB ="&lib";'; put 'LOCK_DS="&ds";'; put 'LOCK_STATUS_CD=''LOCKED'';'; put 'LOCK_START_DTTM="%sysfunc(datetime(),%mf_fmtdttm())"dt;'; put 'LOCK_USER_NM="&user";'; put 'LOCK_PID="&sysjobid";'; put 'LOCK_REF="&ref";'; put 'output;stop;'; put 'run;'; put '%let trans=&syslast;'; put 'proc append base=&ctl_ds data=&trans;'; put 'run;'; put '%end;'; put '/* if record does exist, perform lock attempts */'; put '%else %do x=1 %to &loops;'; put 'data _null_;'; put 'putlog "&sysmacroname: attempting lock (iteration &x) "@;'; put 'putlog "at %sysfunc(datetime(),datetime19.) ..";'; put 'run;'; put 'proc sql;'; put 'update &ctl_ds'; put 'set LOCK_STATUS_CD=''LOCKED'''; put ', LOCK_START_DTTM="%sysfunc(datetime(),%mf_fmtdttm())"dt'; put ', LOCK_USER_NM="&user"'; put ', LOCK_PID="&sysjobid"'; put ', LOCK_REF="&ref"'; put 'where LOCK_LIB ="&lib" and LOCK_DS="&ds";'; put 'quit;'; put '/**'; put '* NOTE - occasionally SQL server will return an err code (deadlocked'; put '* transaction). If so, ignore it, keep calm, and carry on..'; put '*/'; put '%if &syscc>0 %then %do;'; put 'data _null_;'; put 'putlog ''NOTE-'' / ''NOTE-'';'; put 'putlog "NOTE- &sysmacroname: Update failed. "@;'; put 'putlog "Resetting err conditions and re-attempting.";'; put 'putlog "NOTE- syscc=&syscc syserr=&syserr sqlrc=&sqlrc";'; put 'putlog ''NOTE-'' / ''NOTE-'';'; put 'run;'; put '%let syscc=0;'; put '%let sqlrc=0;'; put '%end;'; put '/* now check if the record was successfully updated */'; put '%local success_check;'; put 'proc sql noprint;'; put 'select count(*) into: success_check from &ctl_ds'; put 'where LOCK_LIB ="&lib" and LOCK_DS="&ds"'; put 'and LOCK_PID="&sysjobid" and LOCK_STATUS_CD=''LOCKED'';'; put 'quit;'; put '%if &success_check=0 %then %do;'; put '%if &x < &loops %then %do;'; put '/* pause before next check */'; put 'data _null_;'; put 'putlog ''NOTE-'' / ''NOTE-'';'; put 'putlog "NOTE- &sysmacroname: table locked, waiting "@;'; put 'putlog "%sysfunc(sleep(&loop_secs)) seconds.. ";'; put 'putlog "NOTE- (iteration &x of &loops)";'; put 'putlog ''NOTE-'' / ''NOTE-'';'; put 'run;'; put '%end;'; put '%else %do;'; put '%let msg=Unable to lock &lib..&ds via &ctl_ds after &loops attempts.\n'; put 'Please ask your administrator to investigate!;'; put '%let abortme=1;'; put '%end;'; put '%end;'; put '%else %do;'; put 'data _null_;'; put 'putlog ''NOTE-'' / ''NOTE-'';'; put 'putlog "NOTE- &sysmacroname: Table &lib..&ds locked at "@;'; put 'putlog " %sysfunc(datetime(),datetime19.) (iteration &x)"@;'; put 'putlog ''NOTE-'' / ''NOTE-'';'; put 'run;'; put '%if &syscc>0 %then %do;'; put '%put setting syscc(&syscc) back to 0;'; put '%let syscc=0;'; put '%end;'; put '%let x=&loops; /* no more iterations needed */'; put '%end;'; put '%end;'; put '%end;'; put '%else %if &ACTION=UNLOCK %then %do;'; put '%local status cnt;'; put '%let cnt=0;'; put 'proc sql noprint;'; put 'select count(*) into: cnt from &ctl_ds where LOCK_LIB ="&lib" & LOCK_DS="&ds";'; put '%if &cnt=0 %then %do;'; put '%put %str(WAR)NING: &lib..&ds was not previously locked in &ctl_ds!;'; put '%end;'; put '%else %do;'; put 'select LOCK_STATUS_CD into: status from &ctl_ds'; put 'where LOCK_LIB ="&lib" and LOCK_DS="&ds";'; put 'quit;'; put '%if &syscc>0 %then %put syscc=&syscc sqlrc=&sqlrc;'; put '%if &status=LOCKED %then %do;'; put 'data _null_;'; put 'putlog "&sysmacroname: unlocking &lib..&ds:";'; put 'run;'; put 'proc sql;'; put 'update &ctl_ds'; put 'set LOCK_STATUS_CD=''UNLOCKED'''; put ', LOCK_END_DTTM="%sysfunc(datetime(),%mf_fmtdttm())"dt'; put ', LOCK_USER_NM="&user"'; put ', LOCK_PID="&sysjobid"'; put ', LOCK_REF="&ref"'; put 'where LOCK_LIB ="&lib" and LOCK_DS="&ds";'; put 'quit;'; put '%end;'; put '%else %if &status=UNLOCKED %then %do;'; put '%put %str(WAR)NING: &lib..&ds is already unlocked!;'; put '%end;'; put '%else %do;'; put '%put NOTE: Unrecognised STATUS_CD (&status) in &ctl_ds;'; put '%let abortme=1;'; put '%end;'; put '%end;'; put '%end;'; put '%else %do;'; put '%let msg=lock_anytable given unsupported action (&action);'; put '%let abortme=1;'; put '%end;'; put '/* catch errs - mp_abort must be called outside of a logic block */'; put '%mp_abort(iftrue=(&abortme=1),'; put 'msg=%superq(msg),'; put 'mac=&sysmacroname'; put ')'; put '%exit_macro:'; put 'data _null_;'; put 'put "&sysmacroname: Exit vars: action=&action lib=&lib ds=&ds";'; put 'put " syscc=&syscc sqlrc=&sqlrc syserr=&syserr";'; put 'run;'; put '%mend mp_lockanytable;'; put '%macro bitemporal_closeouts('; put 'tech_from=tx_from_dttm'; put ',tech_to = tx_to_dttm /* Technical TO datetime variable.'; put 'Req''d on BASE table only. */'; put ',base_lib=WORK /* Libref of the BASE table. */'; put ',base_dsn=BASETABLE /* Name of BASE table. */'; put ',append_lib=WORK /* Libref of the STAGING table. */'; put ',append_dsn=APPENDTABLE /* Name of STAGING table. */'; put ',PK= name sex /* Business key, space separated. */'; put '/* Should INCLUDE BUS_FROM field if relevant. */'; put ',NOW=DEFINE'; put ',FILTER= /* supply a filter to limit the update */'; put ',outdest= /* supply an unquoted filepath/filename.ext to get'; put 'a text file containing the update statements */'; put ',loadtype='; put ',loadtarget=YES /* if <> YES will return without changing anything */'; put ');'; put '%put ENTERING &sysmacroname;'; put '%local x var start;'; put '%let start=%sysfunc(datetime());'; put '%dc_assignlib(WRITE,&base_lib)'; put '%dc_assignlib(WRITE,&append_lib)'; put '%if &now=DEFINE %then %let now=&dc_dttmtfmt.;'; put '%put &=now;'; put '/**'; put '* perform basic checks'; put '*/'; put '/* do tables exist? */'; put '%if not %sysfunc(exist(&base_lib..&base_dsn)) %then %do;'; put '%mp_abort(msg=&base_lib..&base_dsn does not exist)'; put '%end;'; put '%else %if %sysfunc(exist(&append_lib..&append_dsn))=0'; put 'and %sysfunc(exist(&append_lib..&append_dsn,VIEW))=0 %then %do;'; put '%mp_abort(msg=&append_lib..&append_dsn does not exist)'; put '%end;'; put '/* do TX columns exist? */'; put '%if &loadtype ne UPDATE %then %do;'; put '%if not %mf_existvar(&base_lib..&base_dsn,&tech_from) %then %do;'; put '%mp_abort(msg=&tech_from does not exist on &base_lib..&base_dsn)'; put '%end;'; put '%else %if not %mf_existvar(&base_lib..&base_dsn,&tech_to) %then %do;'; put '%mp_abort(msg=&tech_to does not exist on &base_lib..&base_dsn)'; put '%end;'; put '%end;'; put '/* do PK columns exist? */'; put '%do x=1 %to %sysfunc(countw(&PK));'; put '%let var=%scan(&pk,&x,%str( ));'; put '%if not %mf_existvar(&base_lib..&base_dsn,&var) %then %do;'; put '%mp_abort(msg=&var does not exist on &base_lib..&base_dsn)'; put '%end;'; put '%else %if not %mf_existvar(&append_lib..&append_dsn,&var) %then %do;'; put '%mp_abort(msg=&var does not exist on &append_lib..&append_dsn)'; put '%end;'; put '%end;'; put '/* check uniqueness */'; put 'proc sort data=&append_lib..&append_dsn'; put 'out=___closeout1 noduprecs dupout=___closeout1a;'; put 'by &pk;'; put 'run;'; put '%if %mf_getattrn(___closeout1a,NLOBS)>0 %then'; put '%put NOTE: dups on (&PK) in (&append_lib..&append_dsn);'; put '/* is &NOW value within a tolerance? Should not allow renegade closeouts.. */'; put '%local gap;'; put '%let gap=0;'; put 'data _null_;'; put 'now=&now;'; put 'gap=intck(''HOURS'',now,datetime());'; put 'call symputx(''gap'',gap,''l'');'; put 'run;'; put '%mf_abort('; put 'iftrue=(&gap > 24),'; put 'msg=NOW variable (&now) is not within a 24hr tolerance'; put ')'; put '/* have any warnings / errs occurred thus far? If so, abort */'; put '%mf_abort('; put 'iftrue=(&syscc>0),'; put 'msg=Aborted due to SYSCC=&SYSCC status'; put ')'; put '/**'; put '* Create closeout statements. These are sent as individual SQL statements'; put '* to ensure pass-through utilisation. The update_cnt variable monitors'; put '* how many records were actually updated on the target table.'; put '*/'; put '%local update_cnt;'; put '%let update_cnt=0;'; put 'filename tmp temp;'; put 'data _null_;'; put 'set ___closeout1;'; put 'file tmp;'; put 'if _n_=1 then put ''proc sql noprint;'' ;'; put 'length string $32767.;'; put '%if &loadtype=UPDATE %then %do;'; put 'put "delete from &base_lib..&base_dsn where 1";'; put '%end;'; put '%else %do;'; put 'now=symget(''now'');'; put 'put "update &base_lib..&base_dsn set &tech_to= " now @;'; put '%if %mf_existvar(&base_lib..&base_dsn,PROCESSED_DTTM) %then %do;'; put 'put " ,PROCESSED_DTTM=" now @;'; put '%end;'; put 'put " where " now " lt &tech_to ";'; put '%end;'; put '%do x=1 %to %sysfunc(countw(&PK));'; put '%let var=%scan(&pk,&x,%str( ));'; put '%if %mf_getvartype(&base_lib..&base_dsn,&var)=C %then %do;'; put '/* use single quotes to avoid ampersand resolution in data */'; put 'string=" & &var=''"!!trim(prxchange("s/''/''''/",-1,&var))!!"''";'; put '%end;'; put '%else %do;'; put 'string=cats(" & &var=",&var);'; put '%end;'; put 'put string;'; put '%end;'; put 'put "&filter ;";'; put 'put ''%let update_cnt=%eval(&update_cnt+&sqlobs);%put update_cnt=&update_cnt;'';'; put 'run;'; put 'data _null_;'; put 'infile tmp;'; put 'input;'; put 'putlog _infile_;'; put 'run;'; put '%if &loadtarget ne YES %then %return;'; put '/* ensure we have a lock */'; put '%mp_lockanytable(LOCK,'; put 'lib=&base_lib,ds=&base_dsn'; put ',ref=bitemporal_closeouts'; put ',ctl_ds=&mpelib..mpe_lockanytable'; put ')'; put 'options source2;'; put '%inc tmp;'; put 'filename tmp clear;'; put '/**'; put '* Update audit tracker'; put '*/'; put '%local newobs; %let newobs=%mf_getattrn(work.___closeout1,NLOBS);'; put '%local user; %let user=%mf_getuser();'; put 'proc sql;'; put 'insert into &mpelib..mpe_dataloads'; put 'set libref=%upcase("&base_lib")'; put ',DSN=%upcase("&base_dsn")'; put ',ETLSOURCE="&append_lib..&append_dsn contained &newobs records"'; put ',LOADTYPE="CLOSEOUT"'; put ',DELETED_RECORDS=&update_cnt'; put ',NEW_RECORDS=0'; put ',DURATION=%sysfunc(datetime())-&start'; put ',USER_NM="&user"'; put ',PROCESSED_DTTM=&now;'; put 'quit;'; put '%mend bitemporal_closeouts;'; put '%macro mf_existds(libds'; put ')/*/STORE SOURCE*/;'; put '%if %sysfunc(exist(&libds)) ne 1 & %sysfunc(exist(&libds,VIEW)) ne 1 %then 0;'; put '%else 1;'; put '%mend mf_existds;'; put '%macro mf_getschema(libref'; put ')/*/STORE SOURCE*/;'; put '%local dsid vnum rc schema;'; put '/* in case the parameter is a libref.tablename, pull off just the libref */'; put '%let libref = %upcase(%scan(&libref, 1, %str(.)));'; put '%let dsid=%sysfunc(open(sashelp.vlibnam(where=('; put 'libname="%upcase(&libref)" and sysname=''Schema/Owner'''; put ')),i));'; put '%if (&dsid ^= 0) %then %do;'; put '%let vnum=%sysfunc(varnum(&dsid,SYSVALUE));'; put '%let rc=%sysfunc(fetch(&dsid));'; put '%let schema=%sysfunc(getvarc(&dsid,&vnum));'; put '%put &libref. schema is &schema.;'; put '%let rc= %sysfunc(close(&dsid));'; put '%end;'; put '&schema'; put '%mend mf_getschema;'; put '/** @endcond */'; put '%macro mf_getuniquename(prefix=MC);'; put '&prefix.%substr(%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32-%length(&prefix))'; put '%mend mf_getuniquename;'; put '%macro mf_getvarlist(libds'; put ',dlm=%str( )'; put ',quote=no'; put ',typefilter=A'; put ')/*/STORE SOURCE*/;'; put '/* declare local vars */'; put '%local outvar dsid nvars x rc dlm q var vtype;'; put '/* credit Rowland Hale - byte34 is double quote, 39 is single quote */'; put '%if %upcase("e)=DOUBLE %then %let q=%qsysfunc(byte(34));'; put '%else %if %upcase("e)=SINGLE %then %let q=%qsysfunc(byte(39));'; put '/* open dataset in macro */'; put '%let dsid=%sysfunc(open(&libds));'; put '%if &dsid %then %do;'; put '%let nvars=%sysfunc(attrn(&dsid,NVARS));'; put '%if &nvars>0 %then %do;'; put '/* add variables with supplied delimeter */'; put '%do x=1 %to &nvars;'; put '/* get variable type */'; put '%let vtype=%sysfunc(vartype(&dsid,&x));'; put '%if &vtype=&typefilter or &typefilter=A %then %do;'; put '%let var=&q.%sysfunc(varname(&dsid,&x))&q.;'; put '%if &var=&q&q %then %do;'; put '%put &sysmacroname: Empty column found in &libds!;'; put '%let var=&q. &q.;'; put '%end;'; put '%if %quote(&outvar)=%quote() %then %let outvar=&var;'; put '%else %let outvar=&outvar.&dlm.&var.;'; put '%end;'; put '%end;'; put '%end;'; put '%let rc=%sysfunc(close(&dsid));'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: Unable to open &libds (rc=&dsid);'; put '%put &sysmacroname: SYSMSG= %sysfunc(sysmsg());'; put '%let rc=%sysfunc(close(&dsid));'; put '%end;'; put '%do;%unquote(&outvar)%end;'; put '%mend mf_getvarlist;'; put '%macro mf_abort(mac=mf_abort.sas, msg=, iftrue=%str(1=1)'; put ')/des=''ungraceful abort'' /*STORE SOURCE*/;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%put NOTE: /// mf_abort macro executing //;'; put '%if %length(&mac)>0 %then %put NOTE- called by &mac;'; put '%put NOTE - &msg;'; put '%abort;'; put '%mend mf_abort;'; put '/** @endcond */'; put '%macro mf_verifymacvars('; put 'verifyVars /* list of macro variable NAMES */'; put ',makeUpcase=NO /* set to YES to make all the variable VALUES uppercase */'; put ',mAbort=SOFT'; put ')/*/STORE SOURCE*/;'; put '%local verifyIterator verifyVar abortmsg;'; put '%do verifyIterator=1 %to %sysfunc(countw(&verifyVars,%str( )));'; put '%let verifyVar=%qscan(&verifyVars,&verifyIterator,%str( ));'; put '%if not %symexist(&verifyvar) %then %do;'; put '%let abortmsg= Variable &verifyVar is MISSING;'; put '%goto exit_err;'; put '%end;'; put '%if %length(%trim(&&&verifyVar))=0 %then %do;'; put '%let abortmsg= Variable &verifyVar is EMPTY;'; put '%goto exit_err;'; put '%end;'; put '%if &makeupcase=YES %then %do;'; put '%let &verifyVar=%upcase(&&&verifyvar);'; put '%end;'; put '%end;'; put '%goto exit_success;'; put '%exit_err:'; put '%put &abortmsg;'; put '%mf_abort(iftrue=(&mabort ne SOFT),'; put 'mac=mf_verifymacvars,'; put 'msg=%str(&abortmsg)'; put ')'; put '0'; put '%return;'; put '%exit_success:'; put '1'; put '%mend mf_verifymacvars;'; put '%macro mf_wordsInStr1ButNotStr2('; put 'Str1= /* string containing words to extract */'; put ',Str2= /* used to compare with the extract string */'; put ')/*/STORE SOURCE*/;'; put '%local count_base count_extr i i2 extr_word base_word match outvar;'; put '%if %length(&str1)=0 or %length(&str2)=0 %then %do;'; put '%put base string (str1)= &str1;'; put '%put compare string (str2) = &str2;'; put '%return;'; put '%end;'; put '%let count_base=%sysfunc(countw(&Str2));'; put '%let count_extr=%sysfunc(countw(&Str1));'; put '%do i=1 %to &count_extr;'; put '%let extr_word=%scan(&Str1,&i,%str( ));'; put '%let match=0;'; put '%do i2=1 %to &count_base;'; put '%let base_word=%scan(&Str2,&i2,%str( ));'; put '%if &extr_word=&base_word %then %let match=1;'; put '%end;'; put '%if &match=0 %then %let outvar=&outvar &extr_word;'; put '%end;'; put '&outvar'; put '%mend mf_wordsInStr1ButNotStr2;'; put '%macro mf_isblank(param'; put ')/*/STORE SOURCE*/;'; put '%sysevalf(%superq(param)=,boolean)'; put '%mend mf_isblank;'; put '%macro mp_dropmembers('; put 'list /* space separated list of datasets / views */'; put ',libref=WORK /* can only drop from a single library at a time */'; put ',iftrue=%str(1=1)'; put ')/*/STORE SOURCE*/;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%if %mf_isblank(&list) %then %do;'; put '%put NOTE: nothing to drop!;'; put '%return;'; put '%end;'; put 'proc datasets lib=&libref nolist;'; put 'delete &list;'; put 'delete &list /mtype=view;'; put 'run;'; put '%mend mp_dropmembers;'; put '%macro mf_getquotedstr(IN_STR'; put ',DLM=%str(,)'; put ',QUOTE=S'; put ',indlm=%str( )'; put ')/*/STORE SOURCE*/;'; put '/* credit Rowland Hale - byte34 is double quote, 39 is single quote */'; put '%if "e=S %then %let quote=%qsysfunc(byte(39));'; put '%else %if "e=D %then %let quote=%qsysfunc(byte(34));'; put '%else %if "e=N %then %let quote=;'; put '%local i item buffer;'; put '%let i=1;'; put '%do %while (%qscan(&IN_STR,&i,%str(&indlm)) ne %str() ) ;'; put '%let item=%qscan(&IN_STR,&i,%str(&indlm));'; put '%if %bquote("E) ne %then %let item="E%qtrim(&item)"E;'; put '%else %let item=%qtrim(&item);'; put '%if (&i = 1) %then %let buffer =%qtrim(&item);'; put '%else %let buffer =&buffer&DLM%qtrim(&item);'; put '%let i = %eval(&i+1);'; put '%end;'; put '%let buffer=%sysfunc(coalescec(%qtrim(&buffer),"E"E));'; put '&buffer'; put '%mend mf_getquotedstr;'; put '%macro mf_nobs(libds'; put ')/*/STORE SOURCE*/;'; put '%mf_getattrn(&libds,NLOBS)'; put '%mend mf_nobs;'; put '%macro mp_retainedkey('; put 'base_lib=WORK'; put ',base_dsn=BASETABLE'; put ',append_lib=WORK'; put ',append_dsn=APPENDTABLE'; put ',retained_key=DEFAULT_RK'; put ',business_key= PK1 PK2'; put ',check_uniqueness=NO'; put ',maxkeytable=0'; put ',locktable=0'; put ',outds=WORK.APPEND'; put ',filter_str='; put ');'; put '%put &sysmacroname entry vars:;'; put '%put _local_;'; put '%local base_libds app_libds key_field check maxkey idx_pk newkey_cnt iserr'; put 'msg x tempds1 tempds2 comma_pk appnobs checknobs dropvar tempvar idx_val;'; put '%let base_libds=%upcase(&base_lib..&base_dsn);'; put '%let app_libds=%upcase(&append_lib..&append_dsn);'; put '%let tempds1=%mf_getuniquename();'; put '%let tempds2=%mf_getuniquename();'; put '%let comma_pk=%mf_getquotedstr(in_str=%str(&business_key),dlm=%str(,),quote=);'; put '%let outds=%sysfunc(ifc(%index(&outds,.)=0,work.&outds,&outds));'; put '/* validation checks */'; put '%let iserr=0;'; put '%if &syscc>0 %then %do;'; put '%let iserr=1;'; put '%let msg=%str(SYSCC=&syscc on macro entry);'; put '%end;'; put '%else %if %sysfunc(exist(&base_libds))=0 %then %do;'; put '%let iserr=1;'; put '%let msg=%str(Base LIBDS (&base_libds) expected but NOT FOUND);'; put '%end;'; put '%else %if %sysfunc(exist(&app_libds))=0 %then %do;'; put '%let iserr=1;'; put '%let msg=%str(Append LIBDS (&app_libds) expected but NOT FOUND);'; put '%end;'; put '%else %if &maxkeytable ne 0 and %sysfunc(exist(&maxkeytable))=0 %then %do;'; put '%let iserr=1;'; put '%let msg=%str(Maxkeytable (&maxkeytable) expected but NOT FOUND);'; put '%end;'; put '%else %if &maxkeytable ne 0 and %sysfunc(exist(&locktable))=0 %then %do;'; put '%let iserr=1;'; put '%let msg=%str(Locktable (&locktable) expected but NOT FOUND);'; put '%end;'; put '%else %if %length(&business_key)=0 %then %do;'; put '%let iserr=1;'; put '%let msg=%str(Business key (&business_key) expected but NOT FOUND);'; put '%end;'; put '%do x=1 %to %sysfunc(countw(&business_key));'; put '/* check business key values exist */'; put '%let key_field=%scan(&business_key,&x,%str( ));'; put '%if not %mf_existvar(&app_libds,&key_field) %then %do;'; put '%let iserr=1;'; put '%let msg=Business key (&key_field) not found on &app_libds!;'; put '%goto err;'; put '%end;'; put '%else %if not %mf_existvar(&base_libds,&key_field) %then %do;'; put '%let iserr=1;'; put '%let msg=Business key (&key_field) not found on &base_libds!;'; put '%goto err;'; put '%end;'; put '%end;'; put '%err:'; put '%if &iserr=1 %then %do;'; put '/* err case so first perform an unlock of the base table before exiting */'; put '%mp_lockanytable('; put 'UNLOCK,lib=&base_lib,ds=&base_dsn,ref=%superq(msg),ctl_ds=&locktable'; put ')'; put '%end;'; put '%mp_abort(iftrue=(&iserr=1),mac=mp_retainedkey,msg=%superq(msg))'; put 'proc sql noprint;'; put 'select sum(max(&retained_key),0) into: maxkey from &base_libds;'; put '/**'; put '* get base table RK and bus field values for lookup'; put '*/'; put 'proc sql noprint;'; put 'create table &tempds1 as'; put 'select distinct &comma_pk,&retained_key'; put 'from &base_libds &filter_str'; put 'order by &comma_pk,&retained_key;'; put '%if &check_uniqueness=YES %then %do;'; put 'select count(*) into:checknobs'; put 'from (select distinct &comma_pk from &app_libds);'; put 'select count(*) into: appnobs from &app_libds; /* might be view */'; put '%if &checknobs ne &appnobs %then %do;'; put '%let msg=Source table &app_libds is not unique on (&business_key);'; put '%let iserr=1;'; put '%end;'; put '%end;'; put '%if &iserr=1 %then %do;'; put '/* err case so first perform an unlock of the base table before exiting */'; put '%mp_lockanytable('; put 'UNLOCK,lib=&base_lib,ds=&base_dsn,ref=%superq(msg),ctl_ds=&locktable'; put ')'; put '%end;'; put '%mp_abort(iftrue= (&iserr=1),mac=mp_retainedkey,msg=%superq(msg))'; put '%if %mf_existvar(&app_libds,&retained_key)'; put '%then %let dropvar=(drop=&retained_key);'; put '/* prepare interim table with retained key populated for matching keys */'; put 'proc sql noprint;'; put 'create table &tempds2 as'; put 'select b.&retained_key, a.*'; put 'from &app_libds &dropvar a'; put 'left join &tempds1 b'; put 'on 1'; put '%do idx_pk=1 %to %sysfunc(countw(&business_key));'; put '%let idx_val=%scan(&business_key,&idx_pk);'; put 'and a.&idx_val=b.&idx_val'; put '%end;'; put 'order by &retained_key;'; put '/* identify the number of entries without retained keys (new records) */'; put 'select count(*) into: newkey_cnt'; put 'from &tempds2'; put 'where missing(&retained_key);'; put 'quit;'; put '/**'; put '* Update maxkey table if link provided'; put '*/'; put '%if &maxkeytable ne 0 %then %do;'; put 'proc sql noprint;'; put 'select count(*) into: check from &maxkeytable'; put 'where upcase(keytable)="&base_libds";'; put '%mp_lockanytable(LOCK'; put ',lib=%scan(&maxkeytable,1,.)'; put ',ds=%scan(&maxkeytable,2,.)'; put ',ref=Updating maxkeyvalues with mp_retainedkey'; put ',ctl_ds=&locktable'; put ')'; put 'proc sql;'; put '%if &check=0 %then %do;'; put 'insert into &maxkeytable'; put 'set keytable="&base_libds"'; put ',keycolumn="&retained_key"'; put ',max_key=%eval(&maxkey+&newkey_cnt)'; put ',processed_dttm="%sysfunc(datetime(),%mf_fmtdttm())"dt;'; put '%end;'; put '%else %do;'; put 'update &maxkeytable'; put 'set max_key=%eval(&maxkey+&newkey_cnt)'; put ',processed_dttm="%sysfunc(datetime(),%mf_fmtdttm())"dt'; put 'where keytable="&base_libds";'; put '%end;'; put '%mp_lockanytable(UNLOCK'; put ',lib=%scan(&maxkeytable,1,.)'; put ',ds=%scan(&maxkeytable,2,.)'; put ',ref=Updating maxkeyvalues with maxkey=%eval(&maxkey+&newkey_cnt)'; put ',ctl_ds=&locktable'; put ')'; put '%end;'; put '/* fill in the missing retained key values */'; put '%let tempvar=%mf_getuniquename();'; put 'data &outds(drop=&tempvar);'; put 'retain &tempvar %eval(&maxkey+1);'; put 'set &tempds2;'; put 'if &retained_key =. then &retained_key=&tempvar;'; put '&tempvar=&tempvar+1;'; put 'run;'; put '%mend mp_retainedkey;'; put '/** @cond */'; put '%macro mp_storediffs(libds'; put ',origds'; put ',key'; put ',delds=0'; put ',appds=0'; put ',modds=0'; put ',outds=work.mp_storediffs'; put ',loadref=0'; put ',processed_dttm=0'; put ',mdebug=0'; put ')/*/STORE SOURCE*/;'; put '%local dbg;'; put '%if &mdebug=1 %then %do;'; put '%put &sysmacroname entry vars:;'; put '%put _local_;'; put '%end;'; put '%else %let dbg=*;'; put '/* set up unique and temporary vars */'; put '%local ds1 ds2 ds3 ds4 hashkey inds_auto inds_keep dslist vlist;'; put '%let ds1=%upcase(work.%mf_getuniquename(prefix=mpsd_ds1));'; put '%let ds2=%upcase(work.%mf_getuniquename(prefix=mpsd_ds2));'; put '%let ds3=%upcase(work.%mf_getuniquename(prefix=mpsd_ds3));'; put '%let ds4=%upcase(work.%mf_getuniquename(prefix=mpsd_ds4));'; put '%let hashkey=%upcase(%mf_getuniquename(prefix=mpsd_hashkey));'; put '%let inds_auto=%upcase(%mf_getuniquename(prefix=mpsd_inds_auto));'; put '%let inds_keep=%upcase(%mf_getuniquename(prefix=mpsd_inds_keep));'; put '%let dslist=&origds;'; put '%if &delds ne 0 %then %do;'; put '%let delds=%upcase(&delds);'; put '%if %scan(&delds,-1,.)=&delds %then %let delds=WORK.&delds;'; put '%let dslist=&dslist &delds;'; put '%end;'; put '%if &appds ne 0 %then %do;'; put '%let appds=%upcase(&appds);'; put '%if %scan(&appds,-1,.)=&appds %then %let appds=WORK.&appds;'; put '%let dslist=&dslist &appds;'; put '%end;'; put '%if &modds ne 0 %then %do;'; put '%let modds=%upcase(&modds);'; put '%if %scan(&modds,-1,.)=&modds %then %let modds=WORK.&modds;'; put '%let dslist=&dslist &modds;'; put '%end;'; put '%let origds=%upcase(&origds);'; put '%if %scan(&origds,-1,.)=&origds %then %let origds=WORK.&origds;'; put '%let key=%upcase(&key);'; put '/* hash the key and append all the tables (marking the source) */'; put 'data &ds1;'; put 'set &dslist indsname=&inds_auto;'; put '&hashkey=put(md5(catx(''|'',%mf_getquotedstr(&key,quote=N))),$hex32.);'; put '&inds_keep=upcase(&inds_auto);'; put 'proc sort;'; put 'by &inds_keep &hashkey;'; put 'run;'; put '/* transpose numeric & char vars */'; put 'proc transpose data=&ds1'; put 'out=&ds2(rename=(&hashkey=key_hash _name_=tgtvar_nm col1=newval_num));'; put 'by &inds_keep &hashkey;'; put 'var _numeric_;'; put 'run;'; put 'proc transpose data=&ds1'; put 'out=&ds3('; put 'rename=(&hashkey=key_hash _name_=tgtvar_nm col1=newval_char)'; put 'where=(tgtvar_nm not in ("&hashkey","&inds_keep"))'; put ');'; put 'by &inds_keep &hashkey;'; put 'var _character_;'; put 'run;'; put '%if %index(&libds,-)>0 and %scan(&libds,2,-)=FC %then %do;'; put '/* this is a format catalog - cannot query cols directly */'; put '%let vlist="TYPE","FMTNAME","FMTROW","START","END","LABEL","MIN","MAX"'; put ',"DEFAULT","LENGTH","FUZZ","PREFIX","MULT","FILL","NOEDIT","SEXCL"'; put ',"EEXCL","HLO","DECSEP","DIG3SEP","DATATYPE","LANGUAGE";'; put '%end;'; put '%else %let vlist=%mf_getvarlist(&libds,dlm=%str(,),quote=DOUBLE);'; put 'data &ds4;'; put 'length &inds_keep $41 tgtvar_nm $32 _label_ $256;'; put 'if _n_=1 then call missing(_label_);'; put 'drop _label_;'; put 'set &ds2 &ds3 indsname=&inds_auto;'; put 'tgtvar_nm=upcase(tgtvar_nm);'; put 'if tgtvar_nm in (%upcase(&vlist));'; put 'if upcase(&inds_auto)="&ds2" then tgtvar_type=''N'';'; put 'else if upcase(&inds_auto)="&ds3" then tgtvar_type=''C'';'; put 'else do;'; put 'putlog ''ERR'' +(-1) "OR: unidentified vartype input!" &inds_auto;'; put 'call symputx(''syscc'',98);'; put 'end;'; put 'if &inds_keep="&appds" then move_type=''A'';'; put 'else if &inds_keep="&delds" then move_type=''D'';'; put 'else if &inds_keep="&modds" then move_type=''M'';'; put 'else if &inds_keep="&origds" then move_type=''O'';'; put 'else do;'; put 'putlog ''ERR'' +(-1) "OR: unidentified movetype input!" &inds_keep;'; put 'call symputx(''syscc'',99);'; put 'end;'; put 'tgtvar_nm=upcase(tgtvar_nm);'; put 'if tgtvar_nm in (%mf_getquotedstr(&key)) then is_pk=1;'; put 'else is_pk=0;'; put 'drop &inds_keep;'; put 'run;'; put '%if "&loadref"="0" %then %let loadref=%sysfunc(uuidgen());'; put '%if &processed_dttm=0 %then %let processed_dttm=%sysfunc(datetime());'; put '%let libds=%upcase(&libds);'; put '/* join orig vals for modified & deleted */'; put 'proc sql;'; put 'create table &outds as'; put 'select "&loadref" as load_ref length=36'; put ',&processed_dttm as processed_dttm format=E8601DT26.6'; put ',"%scan(&libds,1,.)" as libref length=8'; put ',"%scan(&libds,2,.)" as dsn length=32'; put ',b.key_hash length=32'; put ',b.move_type length=1'; put ',b.tgtvar_nm length=32'; put ',b.is_pk'; put ',case when b.move_type ne ''M'' then -1'; put 'when a.newval_num=b.newval_num and a.newval_char=b.newval_char then 0'; put 'else 1'; put 'end as is_diff'; put ',b.tgtvar_type length=1'; put ',case when b.move_type=''D'' then b.newval_num'; put 'else a.newval_num'; put 'end as oldval_num format=best32.'; put ',case when b.move_type=''D'' then .'; put 'else b.newval_num'; put 'end as newval_num format=best32.'; put ',case when b.move_type=''D'' then b.newval_char'; put 'else a.newval_char'; put 'end as oldval_char length=32765'; put ',case when b.move_type=''D'' then '''''; put 'else b.newval_char'; put 'end as newval_char length=32765'; put 'from &ds4(where=(move_type=''O'')) as a'; put 'right join &ds4(where=(move_type ne ''O'')) as b'; put 'on a.tgtvar_nm=b.tgtvar_nm'; put 'and a.key_hash=b.key_hash'; put 'order by move_type, key_hash,is_pk desc, tgtvar_nm;'; put '%if &mdebug=0 %then %do;'; put 'proc sql;'; put 'drop table &ds1, &ds2, &ds3, &ds4;'; put '%end;'; put '%mend mp_storediffs;'; put '/** @endcond */'; put '%macro bitemporal_dataloader('; put 'bus_from= /* Business FROM datetime variable. Req''d on'; put 'STAGING & BASE tables.*/'; put ',bus_to = /* Business TO datetime variable. Req''d on'; put 'STAGING & BASE tables. */'; put ',bus_from_override= /* Provide a hard coded BUS_FROM datetime value.*/'; put ',bus_to_override= /* provide a hard coded BUS_TO datetime value */'; put ',tech_from= /* Technical FROM datetime variable. Req''d on'; put 'BASE table only. */'; put ',tech_to = /* Technical TO datetime variable. Req''d on BASE'; put 'table only. */'; put ',processed= 0'; put ',base_lib=WORK /* Libref of the BASE table. */'; put ',base_dsn=BASETABLE /* Name of BASE table. */'; put ',append_lib=WORK /* Libref of the STAGING table. */'; put ',append_dsn=APPENDTABLE'; put ',high_date=''01JAN5999:00:00:00''dt /* High date to close out records */'; put ',PK= name sex'; put ',RK_UNDERLYING='; put ',KEEPVARS= /* Provides option for removing unwanted vars from append table */'; put ',RK_UPDATE_MAXKEYTABLE=NO /* If switching (or mix matching) with regular'; put 'SCD2 loader then set this switch to YES to'; put 'ensure the MAXKEYTABLE is updated with the'; put 'current maximum RK value for the target table'; put '*/'; put ',CHECK_UNIQUENESS=YES /* Perform a check of the APPEND table to ensure it is'; put 'unique on its business key */'; put ',ETLSOURCE=demo /* supply a value ($50.) to show as ETLSOURCE in'; put '&dclib..DATALOADS */'; put ',LOADTYPE=BITEMPORAL'; put ',RK_MAXKEYTABLE= mpe_maxkeyvalues'; put ',LOG=1 /* Switch to 0 to prevent records being added to'; put '&mpelib..mpe_DATALOADS (ie when testing)*/'; put ',DELETE_COL= _____DELETE__THIS__RECORD_____'; put '/* If this variable is found in the append dataset'; put 'then records are closed out (or deleted) in the'; put 'append table where that variable= "Yes" */'; put ',LOADTARGET=YES /* set to anything but uppercase YES to switch off'; put 'target table load and generate temp tables only */'; put ',CLOSE_VARS='; put '/*a problem with regular SCD2 or TXTEMPORAL loads is that there is'; put 'no facility to close out removed records (all records are'; put 'assumed new or changed). But how does one determine which'; put 'records are removed? Short of loading the entire table'; put 'each time? This parameter allows a set of variables'; put '(this should be a subset of the PK) to be declared, and'; put 'the macro will determine which records in the base table'; put 'need to be closed out ahead of the load.'; put 'For instance, given the following:'; put 'Base Table Staging Table'; put 'DATE ENTITY AMOUNT DATE ENTITY AMOUNT'; put 'JAN ACME4 66 JAN ACME4 66'; put 'FEB ACME4 99 FEB ACME4 99'; put 'FEB ACME1 22'; put 'By supplying DATE in CLOSE_VARS and DATE ENTITY as the PK,'; put 'the "FEB PAG 22" record would get closed out.'; put '*/'; put ',config_table=&dclib..MPE_CONFIG'; put ',dclib=&dc_libref'; put ',outds_del=work.outds_del'; put ',outds_add=work.outds_add'; put ',outds_mod=work.outds_mod'; put ',outds_audit=0'; put ');'; put '/* when changing this macro, update the version num here */'; put '%local ver;'; put '%let ver=32;'; put '%put &sysmacroname entry vars:;'; put '%put _local_;'; put '%dc_assignlib(WRITE,&base_lib) /* may not already be assigned */'; put '/* return straight away if nothing to load */'; put '%let nobs= %mf_getattrn(&append_lib..&append_dsn,NLOBS);'; put '%if &nobs=-1 %then %do;'; put 'proc sql noprint; select count(*) into: nobs from &append_lib..&append_dsn;'; put '%end;'; put '%if &nobs=0 %then %do;'; put '%put NOTE:; %put NOTE-;%put NOTE-;%put NOTE-;'; put '%put NOTE- Base dataset &append_lib..&append_dsn is empty. Nothing to upload!;'; put '%put NOTE-;%put NOTE-;%put NOTE-;'; put '%return;'; put '%end;'; put '/* hard exit if err condition exists */'; put '%mp_abort(iftrue= (&syscc > 0)'; put ',mac=bitemporal_dataloader'; put ',msg=%str(Bitemporal transform / job aborted due to SYSCC=&SYSCC status;)'; put ')'; put '%local engine_type;'; put '%let engine_type=%mf_getengine(&base_lib);'; put '%if (&engine_type=REDSHIFT or &engine_type=POSTGRES) and %length(&CLOSE_VARS)>0'; put '%then %do;'; put '%put NOTE:; %put NOTE-;%put NOTE-;%put NOTE-;'; put '%put NOTE- CLOSE_VARS functionality not yet supported in &engine_type;'; put '%put NOTE-;%put NOTE-;%put NOTE-;'; put '%return;'; put '%end;'; put '/**'; put '* The metadata functions (eg mf_existvar) will fail if the base table has a'; put '* SAS lock. So, make a snapshot of the base table for further use.'; put '* Also, make output tables (regardless).'; put '*/'; put '%local basecopy;'; put '%let basecopy=%mf_getuniquename(prefix=basecopy);'; put 'data &basecopy &outds_mod &outds_add &outds_del;'; put 'set &base_lib..&base_dsn;'; put 'stop;'; put 'run;'; put '%mp_abort(iftrue= (&syscc > 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc after base table copy - aborting due to table lock)'; put ')'; put '%local cols idx_pk md5_col ;'; put '%let md5_col=___TMP___md5;'; put '%let check_uniqueness=%upcase(&check_uniqueness);'; put '%let RK_UPDATE_MAXKEYTABLE=%upcase(&RK_UPDATE_MAXKEYTABLE);'; put '%let high_date=%unquote(&high_date);'; put '%let loadtype=%upcase(&loadtype);'; put '/* ensure irrelevant variables are cleared */'; put '%if &loadtype=BUSTEMPORAL %then %do;'; put '%let tech_from=;'; put '%let tech_to=;'; put '%end;'; put '%else %if &loadtype=TXTEMPORAL or &loadtype=UPDATE %then %do;'; put '%let bus_from=;'; put '%let bus_to=;'; put '%end;'; put '/* ensure relevant variables are supplied */'; put '%mp_abort(iftrue=(&loadtype=BITEMPORAL & %mf_verifymacvars(bus_from bus_to)=0)'; put ',mac=bitemporal_dataloader'; put ',msg=%str(Missing BUS_FROM / BUS_TO)'; put ')'; put '%mp_abort(iftrue=(&loadtype=TXTEMPORAL & %mf_verifymacvars(tech_from tech_to)=0)'; put ',mac=bitemporal_dataloader'; put ',msg=%str(Missing TECH_FROM / TECH_TO)'; put ')'; put '/**'; put '* drop any tables (may be defined as views or vice versa preventing overwrite)'; put '*/'; put '%mp_dropmembers(append bitemp0_append bitemp_cols)'; put '/* SQL Server requires its own time values */'; put '/* 9.2 will only give picture format down to seconds. 9.3 allows'; put 'milliseconds by using lower S and defining the decimal in the format name..*/'; put 'PROC FORMAT;'; put 'picture MyMSdt other=''%0Y-%0m-%0dT%0H:%0M:%0S'' (datatype=datetime);'; put 'RUN;'; put '%local dbnow;'; put '%let dbnow="%sysfunc(datetime(),%mf_fmtdttm())"dt;'; put 'data _null_;'; put '/* convert space separated macvar to comma separated for SQL processing */'; put 'call symputx(''PK_COMMA'',tranwrd(compbl("&pk"),'' '','',''),''L'');'; put 'call symputx(''PK_CNT'',countw("&pk",'' ''),''L'');'; put 'now=&dbnow;'; put 'call symputx(''NOW'',now,''L'');'; put 'call symputx(''SQLNOW'',cats("''",put(now,MyMSdt.),"''"),''L'');'; put 'length etlsource $100;'; put 'etlsource=subpad(symget(''etlsource''),1,100);'; put 'call symputx(''etlsource'',etlsource,''l'');'; put 'run;'; put '/**'; put '* Even if no PROCESSED var provided, assume that any variable named'; put '* PROCESSED_DTTM should be updated'; put '*/'; put '%if &processed=0 %then %do;'; put '%if %mf_existvar(&basecopy,PROCESSED_DTTM)'; put '%then %let processed=PROCESSED_DTTM;'; put '%else %let processed=;'; put '%end;'; put '/* extract colnames for md5 creation / change tracking */'; put 'proc contents noprint data=&base_lib..&base_dsn'; put 'out=work.bitemp_cols (keep=name type length varnum format:);'; put 'run;'; put 'proc sql noprint;'; put 'select name into: cols separated by '','''; put 'from work.bitemp_cols'; put 'where upcase(name) not in'; put '(%upcase("&bus_from","&bus_to"'; put ',"&tech_from","&tech_to"'; put ',"&processed","&delete_col")) ;'; put 'select case when type in (2,6) then cats(''put(md5(trim('',name,'')),$hex32.)'')'; put '/* multiply by 1 to strip precision errors (eg 0 != 0) */'; put '/* but ONLY if not missing, else will lose any special missing values */'; put 'else cats(''put(md5(trim(put(ifn(missing('''; put ',name,''),'',name,'','',name,''*1),binary64.))),$hex32.)'') end'; put 'into: stripcols separated by ''||'''; put 'from work.bitemp_cols'; put 'where upcase(name) not in'; put '(%upcase("&bus_from","&bus_to"'; put ',"&tech_from","&tech_to"'; put ',"&processed","&delete_col")) ;'; put '/* set default formats*/'; put '%let bus_from_fmt = datetime19.;'; put '%let bus_to_fmt = datetime19.;'; put '%let processed_fmt = datetime19.;'; put '%let tech_from_fmt = format=datetime19.;'; put '%let tech_to_fmt = format=datetime19.;'; put '%put &=stripcols;'; put '%put &=pk;'; put 'data _null_;'; put 'set work.bitemp_cols;'; put 'if type=2 or type=6 then do;'; put 'length fmt $49.;'; put 'if format='''' then fmt=cats(''$'',length,''.'');'; put 'else fmt=cats(format,formatl,''.'');'; put 'end;'; put 'else do;'; put 'if format='''' then fmt=cats(length,''.'');'; put 'else fmt=cats(format,formatl,''.'',formatd);'; put 'end;'; put 'if upcase(name)="%upcase(&bus_from)" then'; put 'call symputx(''bus_from_fmt'',fmt,''L'');'; put 'else if upcase(name)="%upcase(&bus_to)" then'; put 'call symputx(''bus_to_fmt'',fmt,''L'');'; put 'else if upcase(name)="%upcase(&tech_from)" then'; put 'call symputx(''tech_from_fmt'',"format="!!fmt,''L'');'; put 'else if upcase(name)="%upcase(&tech_to)" then'; put 'call symputx(''tech_to_fmt'',"format="!!fmt,''L'');'; put 'else if upcase(name)="%upcase(&processed)" then'; put 'call symputx(''processed_fmt'',fmt,''L'');'; put 'run;'; put '%if %index(%quote(&cols),___TMP___) %then %do;'; put '%let msg=%str(Table contains a variable name containing "___TMP___".%trim('; put ') This may conflict with temp variable generation!!);'; put '%mp_abort(msg=&msg,mac=bitemporal_dataloader);'; put '%let syscc=5;'; put '%return;'; put '%end;'; put '/* if transaction dates appear on the APPEND table, need to remove them */'; put '%local drop_tx_dates /* used in append table */'; put 'drop_tx_dates_noobs /* used to take the base table structure */;'; put '%if %mf_existvar(&append_lib..&append_dsn, &tech_from)'; put '%then %let drop_tx_dates=&tech_from;'; put '%if %mf_existvar(&append_lib..&append_dsn, &tech_to)'; put '%then %let drop_tx_dates=&drop_tx_dates &tech_to;'; put '%if %length(%trim(&drop_tx_dates))>0'; put '%then %let drop_tx_dates=(drop=&drop_tx_dates);'; put '%if %mf_existvar(&basecopy, &tech_from)'; put '%then %let drop_tx_dates_noobs=&tech_from;'; put '%if %mf_existvar(&basecopy, &tech_to)'; put '%then %let drop_tx_dates_noobs=&drop_tx_dates_noobs &tech_to;'; put '%if %length(%trim(&drop_tx_dates_noobs))>0'; put '%then %let drop_tx_dates_noobs=(drop=&drop_tx_dates_noobs obs=0);'; put '%else %let drop_tx_dates_noobs=(obs=0);'; put '/**'; put '* Lock the table. This is necessary as we are doing a two part update (first'; put '* closing records then appending new records). It is theoretically possible'; put '* that an upload may occur whilst preparing the staging tables. And the'; put '* staging tables are about to be prepared..'; put '*/'; put '%if &LOADTARGET = YES %then %do;'; put '%put locking &base_lib..&base_dsn;'; put '%mp_lockanytable(LOCK,'; put 'lib=&base_lib,ds=&base_dsn,ref=&ETLSOURCE,ctl_ds=&dclib..mpe_lockanytable'; put ')'; put '%if "&outds_audit" ne "0" %then %do;'; put '%put locking &outds_audit;'; put '%mp_lockanytable(LOCK'; put ',lib=%scan(&outds_audit,1,.)'; put ',ds=%scan(&outds_audit,2,.)'; put ',ref=&ETLSOURCE'; put ',ctl_ds=&dclib..mpe_lockanytable'; put ')'; put '%end;'; put '%end;'; put '%else %do;'; put '/* not an actual load, so avoid updating the max key table in next step. */'; put '%let rk_update_maxkeytable=NO;'; put '%end;'; put '%if %length(&RK_UNDERLYING)>0 %then %do;'; put '%mp_retainedkey('; put 'base_lib=&base_lib'; put ',base_dsn=&base_dsn'; put ',append_lib=&append_lib'; put ',append_dsn=&append_dsn'; put ',retained_key=&pk'; put ',business_key=&rk_underlying'; put ',check_uniqueness=&CHECK_UNIQUENESS'; put ',outds=work.append'; put '%if &rk_update_maxkeytable=NO %then %do;'; put ',maxkeytable=0'; put '%end;'; put '%else %do;'; put ',maxkeytable=&dclib..&RK_MAXKEYTABLE'; put '%end;'; put ',locktable=&dclib..mpe_lockanytable'; put '%if &loadtype=BITEMPORAL or &loadtype=TXTEMPORAL %then %do;'; put ',filter_str=%str( (where=( &now < &tech_to)) )'; put '%end;'; put ')'; put '%end;'; put '%else %do;'; put 'proc sql;'; put 'create view work.append as select * from &append_lib..&append_dsn;'; put '%end;'; put '/**'; put '* generate md5 for append table'; put '*/'; put '/* it is possible the source dataset has additional (unwanted) columns.'; put 'Drop if specified; */'; put '%if %length(&keepvars)>0 %then %do;'; put '/* remove tech dates from keepvars as they are generated later */'; put '%let keepvars=%sysfunc(tranwrd(%str( &keepvars ),%str( &tech_from ),%str( )));'; put '%let keepvars=%sysfunc(tranwrd(%str( &keepvars ),%str( &tech_to ),%str( )));'; put '%let keepvars=(keep=&keepvars &bus_from &bus_to &processed &md5_col);'; put '%end;'; put '/* CAS varchar types cause append issues here, so perform autoconvert'; put 'by creating empty local table first */'; put 'data;'; put 'set &base_lib..&base_dsn &drop_tx_dates_noobs;'; put 'run;'; put '%local emptybasetable; %let emptybasetable=&syslast;'; put 'data work.bitemp0_append &keepvars &outds_del(drop=&md5_col )'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5" %then %do;'; put '/nonote2err'; put '%end;'; put ';'; put '/* apply formats for bitemporal vars but not tx dates which are added later */'; put '%if %length(&keepvars)>0 and &loadtype=BITEMPORAL %then %do;'; put 'format &bus_from &bus_from_fmt;'; put 'format &bus_to &bus_to_fmt;'; put '%end;'; put 'set &emptybasetable /* base table reqd in case append has fewer cols */'; put 'work.append &drop_tx_dates;'; put '%if %length(%str(&bus_from_override))>0 %then %do;'; put '&bus_from= %unquote(&bus_from_override) ;'; put '%end;'; put '%if %length(%str(&bus_to_override))>0 %then %do;'; put '&bus_to= %unquote(&bus_to_override) ;'; put '%end;'; put 'length &md5_col $32;'; put '&md5_col=put(md5(&stripcols),hex32.);'; put '%if %length(&processed)>0 %then %do;'; put 'format &processed &processed_fmt;'; put '&processed=&now;'; put '%end;'; put '/**'; put '* If a delete column exists then create the delete dataset'; put '*/'; put '%if %mf_existvar(&append_lib..&append_dsn, &delete_col) %then %do;'; put 'drop &delete_col;'; put 'if upcase(&delete_col) = "YES" then output &outds_del ;'; put 'else output work.bitemp0_append ;'; put 'run;'; put '%if %mf_getattrn(&outds_del,NLOBS)>0 %then %do;'; put '%bitemporal_closeouts('; put 'tech_from=&tech_from'; put ',tech_to = &tech_to'; put ',base_lib=&base_lib'; put ',base_dsn=&base_dsn'; put ',append_lib=work'; put ',append_dsn=%scan(&outds_del,-1,.)'; put ',PK=&bus_from &pk'; put ',NOW=&dbnow'; put ',loadtarget=&loadtarget'; put ',loadtype=&loadtype'; put ')'; put '%end;'; put '%end;'; put '%else %do;'; put 'output work.bitemp0_append;'; put 'run;'; put '%end;'; put '%mp_abort(iftrue= (&syscc gt 0 at line 494)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc)'; put ')'; put '%if %length(&close_vars)>0 %then %do;'; put '/**'; put '* need to close out records that are not provided'; put '*/'; put 'proc sql;'; put 'create table bitemp1_closevars1 as'; put 'select distinct a.%mf_getquotedstr(in_str=&pk,dlm=%str(,a.),quote=)'; put 'from &base_lib..&base_dsn a'; put 'inner join work.bitemp0_append b'; put 'on 1=1'; put '/* join on closevars key */'; put '%do idx_pk=1 %to %sysfunc(countw(&close_vars));'; put '%let idx_val=%scan(&close_vars,&idx_pk);'; put 'and a.&idx_val=b.&idx_val'; put '%end;'; put '/* filter base on tech dates if necessary */'; put '%if &loadtype=TXTEMPORAL %then %do;'; put 'where a.&tech_from <=&now and &now < a.&tech_to'; put '%end;'; put ';'; put 'create table bitemp1_closevars2 as'; put 'select distinct a.*'; put 'from bitemp1_closevars1 a'; put 'left join work.bitemp0_append b'; put 'on 1=1'; put '/* join on primary key */'; put '%do idx_pk=1 %to %sysfunc(countw(&pk));'; put '%let idx_val=%scan(&pk,&idx_pk);'; put 'and a.&idx_val=b.&idx_val'; put '%end;'; put '/* identify removed records by null value in a field in PK but not close_vars'; put '*/'; put 'where b.%scan('; put '%mf_wordsInStr1ButNotStr2(Str1=&pk,Str2=&close_vars),1,%str( )'; put ') IS NULL'; put ';'; put '%if %mf_getattrn(bitemp1_closevars2,NLOBS)>0 %then %do;'; put '%bitemporal_closeouts('; put 'tech_from=&tech_from'; put ',tech_to = &tech_to'; put ',base_lib=&base_lib'; put ',base_dsn=&base_dsn'; put ',append_lib=work'; put ',append_dsn=bitemp1_closevars2'; put ',PK=&bus_from &pk'; put ',NOW=&dbnow'; put ',loadtarget=&loadtarget'; put ',loadtype=&loadtype'; put ')'; put '%end;'; put '%end;'; put '/* return if nothing to load (was just deletes) */'; put '%if %mf_getattrn(work.bitemp0_append,NLOBS)=0 %then %do;'; put '%put NOTE:; %put NOTE-;%put NOTE-;%put NOTE-;'; put '%put NOTE- No updates - just deletes!;'; put '%put NOTE-;%put NOTE-;%put NOTE-;'; put '%end;'; put '/**'; put '* If applying manual overrides to business dates, then the input table MUST'; put '* be unique on the PK. Check, and if not - abort.'; put '*/'; put '%local msg;'; put '%if %length(&bus_from_override.&bus_to_override)>0 or &CHECK_UNIQUENESS=YES'; put '%then %do;'; put 'proc sort data=work.bitemp0_append out=work.bitemp0_check nodupkey;'; put 'by &pk;'; put 'run;'; put '%if %mf_getattrn(work.bitemp0_check,NLOBS)'; put 'ne %mf_getattrn(work.bitemp0_append,NLOBS)'; put '%then %do;'; put '%let msg=INPUT table &append_lib..&append_dsn is not unique on PK (&pk);'; put '%mp_lockanytable(UNLOCK,lib=&base_lib,ds=&base_dsn,ref=&ETLSOURCE (&msg),'; put 'ctl_ds=&dclib..mpe_lockanytable'; put ')'; put '%mp_lockanytable(UNLOCK'; put ',lib=%scan(&outds_audit,1,.)'; put ',ds=%scan(&outds_audit,2,.)'; put ',ref=&ETLSOURCE'; put ',ctl_ds=&dclib..mpe_lockanytable'; put ')'; put '%mp_abort(msg=&msg,mac=bitemporal_dataloader.sas);'; put '%end;'; put '%end;'; put '/**'; put '* extract from BASE table. Only want matching records, as could be very BIG.'; put '* New records are subsequently identified via left join and test for nulls.'; put '*/'; put '%local temp_table temp_table2 base_table baselib_schema;'; put '%put DCNOTE: Extracting matching observations from &base_lib..&base_dsn;'; put '%if &engine_type=OLEDB %then %do;'; put '%let temp_table=##BITEMP_&base_dsn;'; put '%if &loadtype=BITEMPORAL or &loadtype=TXTEMPORAL %then'; put '%let base_table=(select * from [dbo].&base_dsn'; put 'where convert(datetime,&SQLNOW) < &tech_to );'; put '%else %let base_table=[dbo].&base_dsn;'; put 'proc sql;'; put 'create table &base_lib.."&temp_table"n as'; put 'select * from work.bitemp0_append;'; put '/* open up a connection for pass through SQL */'; put '%dc_assignlib(WRITE,&base_lib,passthru=myAlias)'; put 'create table work.bitemp0_base as select * from connection to myAlias('; put '%end;'; put '%else %if &engine_type=REDSHIFT or &engine_type=POSTGRES %then %do;'; put '/* grab schema */'; put '%let baselib_schema=%mf_getschema(&base_lib);'; put '%if &baselib_schema.X ne X %then %let baselib_schema=&baselib_schema..;'; put '/* grab redshift config */'; put '%local redcnt; %let redcnt=0;'; put '%if &engine_type=REDSHIFT %then %do;'; put 'data _null_;'; put 'set &config_table(where=(var_scope=''DCBL_REDSH'' and var_active=1));'; put 'x+1;'; put 'call symputx(cats(''rednm'',x),var_value,''l'');'; put 'call symputx(cats(''redval'',x),var_value,''l'');'; put 'call symputx(''redcnt'',x,''l'');'; put 'run;'; put '%end;'; put '/* cannot persist temp tables so must create a temporary permanent table */'; put '%let temp_table=%mf_getuniquename(prefix=XDCTEMP);'; put '%if &loadtype=BITEMPORAL or &loadtype=TXTEMPORAL %then'; put '%let base_table=(select * from &baselib_schema.&base_dsn'; put 'where timestamp &sqlnow < &tech_to );'; put '%else %let base_table=&baselib_schema.&base_dsn;'; put '/* make empty table first - must clone & drop extra cols as autoload is bad */'; put '%dc_assignlib(WRITE,&base_lib,passthru=myAlias)'; put 'exec (create table &temp_table (like &baselib_schema.&base_dsn)) by myAlias;'; put '%if &engine_type=REDSHIFT %then %do;'; put 'exec (alter table &temp_table alter sortkey none) by myAlias;'; put '%end;'; put '%local dropcols;'; put '%let dropcols=%mf_wordsinstr1butnotstr2('; put 'str1=%upcase(%mf_getvarlist(&basecopy))'; put ',str2=%upcase(&pk)'; put ');'; put '%if %length(&dropcols>0) %then %do idx_pk=1 %to %sysfunc(countw(&dropcols));'; put '%put &=dropcols;'; put '%let idx_val=%scan(&dropcols,&idx_pk);'; put 'exec(alter table &temp_table drop column &idx_val;) by myAlias;'; put '%end;'; put 'exec (alter table &temp_table add column &md5_col varchar(32);) by myAlias;'; put '/* create view to strip formats and avoid warns in log */'; put 'data work.vw_bitemp0/view=work.vw_bitemp0;'; put 'set work.bitemp0_append(keep=&pk &md5_col);'; put 'format _all_;'; put 'run;'; put 'proc append base=&base_lib..&temp_table'; put '%if &engine_type=REDSHIFT %then %do;'; put '('; put '%do idx_pk=1 %to &redcnt;'; put '&&rednm&idx_pk = &&redval&idxpk'; put '%end;'; put ')'; put '%end;'; put 'data=work.vw_bitemp0 force nowarn;'; put 'run;'; put '/* open up a connection for pass through SQL */'; put '%dc_assignlib(WRITE,&base_lib,passthru=myAlias)'; put 'create table work.bitemp0_base as select * from connection to myAlias('; put '%end;'; put '%else %if &engine_type=CAS %then %do;'; put '%if &loadtype=BITEMPORAL or &loadtype=TXTEMPORAL %then'; put '%let base_table=&base_lib..&base_dsn'; put '(where=(&tech_from <=&now and &now < &tech_to));'; put '%else %let base_table=&base_lib..&base_dsn;'; put '%let temp_table=CASUSER.%mf_getuniquename(prefix=DC);'; put 'data &temp_table;'; put 'set work.bitemp0_append;'; put 'run;'; put '%let bitemp0base=CASUSER.%mf_getuniquename(prefix=DC);'; put 'proc fedsql sessref=dcsession;'; put 'create table &bitemp0base{options replace=true} as'; put '%end;'; put '%else %do;'; put '%let temp_table=work.bitemp0_append;'; put '%if &loadtype=BITEMPORAL or &loadtype=TXTEMPORAL %then'; put '%let base_table=&base_lib..&base_dsn'; put '(where=(&tech_from <=&now and &now < &tech_to));'; put '%else %let base_table=&base_lib..&base_dsn;'; put 'proc sql;'; put 'create table work.bitemp0_base as'; put '%end;'; put 'select a.&md5_col /* this identifies NEW records */'; put ', b.*'; put '/* assume first PK field cannot be null (if defined in a PK constraint then'; put 'it definitely cannot be null) */'; put ', case when b.%scan(&pk,1) IS NULL then 1 else 0 end as ___TMP___NEW_FLG'; put 'from &baselib_schema.&temp_table a'; put 'left join &base_table b'; put 'on 1=1'; put '%do idx_pk=1 %to &pk_cnt;'; put '%let idx_val=%scan(&pk,&idx_pk);'; put 'and a.&idx_val=b.&idx_val'; put '%end;'; put '%if &engine_type=OLEDB or &engine_type=REDSHIFT or &engine_type=POSTGRES'; put '%then %do;'; put '); proc sql; drop table &base_lib.."&temp_table"n;'; put '%end;'; put '%else %if &engine_type=CAS %then %do;'; put ';'; put 'quit;'; put 'data work.bitemp0_base;'; put 'set &bitemp0base;'; put 'run;'; put 'proc sql;'; put 'drop table &temp_table;'; put 'drop table &bitemp0base;'; put '%end;'; put '%else %do;'; put ';'; put '%end;'; put '/**'; put '* matching & changed records are those without NULL key values'; put '* &idx_val resolves to rightmost PK value (loop above)'; put '*/'; put '%put syscc (line525)=&syscc, sqlrc=&sqlrc;'; put '%mp_abort(iftrue= (&syscc gt 0 or &sqlrc>0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc sqlrc=&sqlrc)'; put ')'; put '%put hashcols2=&stripcols;'; put 'proc sql;'; put 'create table work.bitemp1_current(drop=___TMP___NEW_FLG) as'; put 'select *'; put ', put(md5(&stripcols),$hex32.) as &md5_col'; put 'from work.bitemp0_base (drop=&md5_col)'; put 'where ___TMP___NEW_FLG=0;'; put '/**'; put '* NEW records were identified in ___TMP___NEW_FLG in bitemp0_base'; put '*/'; put 'proc sql;'; put 'create table &outds_add'; put '(drop=&md5_col'; put '%if %mf_existvar(work.bitemp0_base, &delete_col) %then %do;'; put '&delete_col'; put '%end;'; put ')'; put 'as select a.*'; put '%if &loadtype=BITEMPORAL or &loadtype=TXTEMPORAL %then %do;'; put ',&now as &tech_from &tech_from_fmt'; put ',&high_date as &tech_to &tech_to_fmt'; put '%end;'; put 'from work.bitemp0_append a /* STAGING records (mix of existing & new) */'; put ', work.bitemp0_base b /* BASE records (contains null values for new) */'; put 'where a.&md5_col=b.&md5_col /* took staging md5 across in left join */'; put 'and b.___TMP___NEW_FLG=1; /* NEW records also identified in bitemp0_base */'; put '/**'; put '* identify INSERTS. These are records with the same business key but'; put '* the bus_from and bus_to value are higher / lower (respectively)'; put '* such that the existing record needs to be SPLIT to surround the new'; put '* record.'; put '* eg: OLD RECORD from=1 to=10'; put '* NEW RECORD from=5 to=7'; put '*'; put '* APPENDED RECORDS:'; put '* - from=1 to=5'; put '* - from=5 to=7'; put '* - from=7 to=10'; put '*/'; put '/* inserts cannot happen with TXTEMPORAL */'; put '%if &loadtype=BITEMPORAL or &loadtype=BUSTEMPORAL %then %do;'; put '/* IDENTIFY */'; put 'create table work.bitemp3_inserts as'; put 'select b.*'; put ',a.&bus_from as ___TMP___from'; put ',a.&bus_to as ___TMP___to'; put 'from work.bitemp0_append a'; put ',work.bitemp1_current b'; put 'where a.&bus_from > b.&bus_from'; put 'and a.&bus_to < b.&bus_to'; put '%do idx_pk=1 %to &pk_cnt;'; put '%let idx_val=%scan(&pk,&idx_pk);'; put 'and a.&idx_val=b.&idx_val'; put '%end;'; put 'order by'; put '/* compress blanks and then insert commas (as the datetime fields may'; put 'not be in use) */'; put '%sysfunc(tranwrd(%sysfunc(compbl('; put '&pk &bus_from &bus_to &processed'; put ')),%str( ), %str(,)))'; put ';'; put '/* SPLIT */'; put 'data work.bitemp3a_inserts (drop=___TMP___from ___TMP___retain ___TMP___to) ;'; put 'set work.bitemp3_inserts;'; put 'by &pk &bus_from &bus_to &processed;'; put 'if first.&idx_val then do;'; put '___TMP___retain=&bus_to;'; put '&bus_to=___TMP___from;'; put 'output;'; put '&bus_to=___TMP___retain;'; put 'end;'; put 'if last.&idx_val then do;'; put '&bus_from=___TMP___to;'; put 'output;'; put 'end;'; put 'run;'; put '%end;'; put '%else %do;'; put '/* TX temporal load */'; put 'data work.bitemp3a_inserts;'; put 'set work.bitemp1_current;'; put 'stop;'; put 'run;'; put '%end;'; put '/* APPEND */'; put 'proc sql;'; put 'create view work.bitemp3a_view as'; put 'select * from work.bitemp1_current'; put 'where &md5_col not in (select &md5_col from work.bitemp3a_inserts);'; put 'data bitemp3b_newbase;'; put 'set work.bitemp3a_inserts work.bitemp3a_view;'; put 'run;'; put '/** do not use! this converts short numerics into 8 bytes'; put 'proc sql;'; put 'create table work.bitemp3b_newbase as'; put 'select * from work.bitemp3a_inserts'; put 'union corr'; put 'select * from work.bitemp1_current'; put 'where &md5_col not in (select &md5_col from work.bitemp3a_inserts);'; put '*/'; put '/**'; put '* identify CHANGED records from staging.'; put '* Same business key with different temporal dates or md5 value'; put '* This table must be overlayed onto / into existing business history'; put '*/'; put 'proc sql;'; put 'create table work.bitemp4_updated as select distinct a.*'; put 'from work.bitemp0_append a'; put ',work.bitemp3b_newbase b'; put 'where 1=1'; put '%do idx_pk=1 %to &pk_cnt;'; put '%let idx_val=%scan(&pk,&idx_pk);'; put 'and a.&idx_val=b.&idx_val'; put '%end;'; put 'and ( a.&md5_col ne b.&md5_col'; put '%if &loadtype=BITEMPORAL or &loadtype=BUSTEMPORAL %then %do;'; put 'OR (a.&bus_from ne b.&bus_from or a.&bus_to ne b.&bus_to)'; put '%end;'; put ')'; put ';'; put '/**'; put '* This section would have been one simple step with union all'; put '* but that converts short numerics into 8 bytes!'; put '* so, convoluted alternative to retain the same functionality.'; put '*/'; put '/* base records */'; put 'create view work.bitemp4_prep1 as'; put 'select ''BASE'' as ___TMP___'; put ',b.*'; put 'from work.bitemp4_updated a'; put ',work.bitemp3b_newbase b'; put 'where 1'; put '%do idx_pk=1 %to &pk_cnt;'; put '%let idx_val=%scan(&pk,&idx_pk);'; put 'and a.&idx_val=b.&idx_val'; put '%end;'; put ';'; put '/* updated records */'; put 'create view work.bitemp4_prep2 as'; put 'select ''STAG'' as ___TMP___ ,*'; put 'from work.bitemp4_updated;'; put '/* ensure we only keep columns that appear in both */'; put '%local bp1 bp2 bp3 bp4;'; put '%let bp1=%mf_getvarlist(bitemp4_prep1);'; put '%let bp2=%mf_getvarlist(bitemp4_prep2);'; put '%let bp3=%mf_wordsInStr1ButNotStr2(Str1=&bp1,Str2=&bp2);'; put '%let bp4=%mf_wordsInStr1ButNotStr2(Str1=&bp2,Str2=&bp1);'; put 'data work.bitemp4_prep3/view=bitemp4_prep3;'; put 'set bitemp4_prep1 bitemp4_prep2;'; put '%if %length(XX&bp3&bp4)>2 %then %do;'; put 'drop &bp3 &bp4 ;'; put '%end;'; put 'run;'; put '/* remove duplicates */'; put 'proc sql;'; put 'create table work.bitemp4a_allrecs as'; put 'select distinct *'; put 'from work.bitemp4_prep3'; put 'order by'; put '/* compress blanks and then insert commas (as the datetime fields'; put 'may not be in use) */'; put '%sysfunc(tranwrd(%sysfunc(compbl('; put '&pk &bus_from &bus_to &processed'; put ')),%str( ), %str(,)))'; put ';'; put '%if &loadtype=BITEMPORAL or &loadtype=BUSTEMPORAL %then %do;'; put '/* this section aligns the business dates'; put '(eg for inserts or overlaps in the range) */'; put 'data work.bitemp4b_firstpass (drop=___TMP___cond ___TMP___from ___TMP___to );'; put 'set work.bitemp4a_allrecs;'; put 'by &pk &bus_from &bus_to &processed;'; put 'retain ___TMP___cond ''Name of Condition'';'; put 'retain ___TMP___from ___TMP___to 0;'; put '___TMP___md5lag=lag(&md5_col);'; put '/* reset retained variables */'; put 'if first.&idx_val then do;'; put 'call missing (___TMP___cond, ___TMP___from, ___TMP___to,___TMP___md5lag);'; put 'end;'; put 'else do;'; put '/* if record is identical, carry forward bus_from (and bus_to if higher)*/'; put 'if &md5_col=___TMP___md5lag then do;'; put '&bus_from=___TMP___from;'; put 'if &bus_to<___TMP___to then &bus_to=___TMP___to;'; put 'end;'; put 'end;'; put 'if ___TMP___=''STAG'' then do;'; put '/* need to carry forward the closing record */'; put '___TMP___cond=''Condition 1'';'; put 'end;'; put 'else if ___TMP___cond=''Condition 1'' then do;'; put '/* else ensure bus_from starts from prior record bus_to */'; put 'if &md5_col ne ___TMP___md5lag and &bus_from <= ___TMP___to'; put 'then &bus_from= ___TMP___to;'; put '/* new record may replace old record entirely */'; put 'if &bus_to <= &bus_from then delete;'; put 'else call missing (___TMP___cond, ___TMP___from, ___TMP___to);'; put 'end;'; put '___TMP___from=&bus_from;'; put '___TMP___to=&bus_to;'; put 'run;'; put '%end;'; put '%else %do;'; put '/* keep staged records only */'; put 'data work.bitemp4b_firstpass;'; put 'set work.bitemp4a_allrecs;'; put 'if ___TMP___=''STAG'';'; put 'run;'; put '%end;'; put '/* next phase is to pass through in reverse - so set up the sort statement */'; put '%local byvar;'; put '%do idx_pk=1 %to &pk_cnt;'; put '%let byvar=&byvar descending %scan(&pk,&idx_pk);'; put '%end;'; put '%if &loadtype=BITEMPORAL or &loadtype=BUSTEMPORAL'; put '%then %let byvar=&byvar descending &bus_from descending &bus_to;'; put '/* if matching bus dates supplied, need to ensure we also have a sort'; put 'between BASE and STAGING tables */'; put '%let byvar=&byvar descending ___TMP___;'; put 'proc sort data=work.bitemp4b_firstpass out=work.bitemp4c_sort ;'; put 'by &byvar;'; put 'run;'; put '/**'; put '* Now (in reverse) pass back business start dates'; put '*/'; put 'data work.bitemp4d_secondpass;'; put '%if &loadtype=BITEMPORAL or &loadtype=TXTEMPORAL %then %do;'; put '&tech_from=&now;'; put '&tech_to=&high_date;'; put '%end;'; put 'set work.bitemp4c_sort ;'; put 'by &byvar;'; put 'retain ___TMP___cond ''Name of Condition'';'; put 'retain ___TMP___from ___TMP___to 0;'; put '%if &loadtype=BITEMPORAL or &loadtype=BUSTEMPORAL %then %do;'; put '/* put / _all_ /;*/'; put '___TMP___md5lag=lag(&md5_col);'; put 'if first.&idx_val then do;'; put '/* reset retained variables */'; put 'call missing (___TMP___cond,___TMP___from,___TMP___to,___TMP___md5lag);'; put 'end;'; put 'else do;'; put '/* if record is identical, carry back bus_to */'; put 'if &md5_col=___TMP___md5lag then &bus_to=___TMP___to;'; put 'end;'; put 'if ___TMP___=''STAG'' then do;'; put '/* need to carry forward the closing record */'; put '___TMP___cond=''Condition 2'';'; put 'end;'; put 'else if ___TMP___cond=''Condition 2'' then do;'; put '/* else ensure bus_to stops at subsequent record bus_from */'; put 'if &md5_col ne ___TMP___md5lag and &bus_to >= ___TMP___from'; put 'then &bus_to= ___TMP___from;'; put '/* new record may replace old record entirely */'; put 'if &bus_from >= &bus_to then delete;'; put 'if &bus_from=___TMP___from and &bus_to=___TMP___to then delete;'; put 'else call missing (___TMP___cond, ___TMP___from, ___TMP___to);'; put 'end;'; put '___TMP___from=&bus_from;'; put '___TMP___to=&bus_to;'; put '%end;'; put 'run;'; put '%put syscc (line600)=&syscc;'; put '/**'; put 'There may still be some records (eg old business history) which have not'; put 'changed.'; put 'Need to identify these and remove from the append so they are not updated'; put 'unnecessarily. This is done by generating a new md5 (which INCLUDES the'; put 'business key) and any matching / identical records are split out (from those'; put 'that need to be updated).'; put '*/'; put '%if &loadtype=BITEMPORAL %then %do;'; put '%let cat_string=catx(''|'' ,&bus_from,&bus_to);'; put 'data bitemp5a_lkp (keep=&md5_col);'; put 'set bitemp0_base;'; put '/* for BITEMPORAL we need to compare business dates also */'; put '&md5_col=put(md5(&cat_string!!''|''!!&stripcols),$hex32.);'; put 'run;'; put 'data bitemp5b_updates;'; put 'set bitemp4d_secondpass;'; put 'if _n_=1 then do;'; put 'dcl hash md5_lkp(dataset:''bitemp5a_lkp'');'; put 'md5_lkp.definekey("&md5_col");'; put 'md5_lkp.definedone();'; put 'end;'; put '/* drop old md5 col as will rebuild with new business dates */'; put '&md5_col=put(md5(&cat_string!!''|''!!&stripcols),$hex32.) ;'; put 'if md5_lkp.check()=0 then delete;'; put 'run;'; put 'proc sql;'; put '/* get min bus from as will update (close out) all records from this point'; put '(for that PK)*/'; put 'create table work.bitemp5d_subquery as'; put 'select &pk_comma, min(&bus_from)as &bus_from, max(&bus_to) as &bus_to'; put 'from work.bitemp5b_updates'; put 'group by &pk_comma;'; put '/* index has a huge efficiency impact on upcoming nested subquery */'; put 'create index index1 on work.bitemp5d_subquery(&pk_comma,&bus_from, &bus_to);'; put '%let lastds=work.bitemp5b_updates;'; put '%end;'; put '%else %if &loadtype=TXTEMPORAL or &loadtype=UPDATE %then %do;'; put 'proc sql;'; put 'create table work.bitemp5d_subquery as'; put 'select distinct &pk_comma'; put 'from bitemp4d_secondpass;'; put '%let lastds=work.bitemp4d_secondpass;'; put '%end;'; put '%else %let lastds=work.bitemp4d_secondpass;'; put '/* create single append table (an overlapped pre-sert may be classed as'; put 'both an update AND a new record). Also create temp views that may be'; put 'used for pre-load analysis. */'; put 'data &outds_mod;'; put 'set &lastds(drop=___TMP___: &md5_col);'; put 'run;'; put 'data bitemp6_allrecs / view=bitemp6_allrecs;'; put 'set &outds_mod /* UPDATED records */'; put '&outds_add /* NEW records */;'; put 'run;'; put 'proc sort data=work.bitemp6_allrecs'; put 'out=work.bitemp6_unique'; put 'noduprec'; put 'dupout=work.xx_BADBADBAD;'; put 'by _all_;'; put 'run;'; put '/* we have all our temp tables now so exit if this is all that is needed */'; put '%if &LOADTARGET ne YES %then %return;'; put '/* also exit if an err condition exists */'; put '%if &syscc>0 %then %do;'; put '%put syscc=&syscc;'; put '%mp_lockanytable(UNLOCK,lib=&base_lib,ds=&base_dsn,ref=&ETLSOURCE,'; put 'ctl_ds=&dclib..mpe_lockanytable'; put ')'; put '%if "&outds_audit" ne "0" %then %do;'; put '%mp_lockanytable(UNLOCK'; put ',lib=%scan(&outds_audit,1,.)'; put ',ds=%scan(&outds_audit,2,.)'; put ',ref=&ETLSOURCE'; put ',ctl_ds=&dclib..mpe_lockanytable'; put ')'; put '%end;'; put '%end;'; put '%mp_abort(iftrue= (&syscc>0)'; put ',mac=&sysmacroname in &_program'; put ',msg=%str(Bitemporal transform / job aborted due to SYSCC=&SYSCC status)'; put ')'; put '/* final check - abort if a lock has appeared on the target or audit table */'; put '%mp_lockfilecheck(libds=&base_lib..&base_dsn)'; put '%if %mf_existds(&outds_audit) %then %do;'; put '%mp_lockfilecheck(libds=&outds_audit)'; put '%end;'; put '/**'; put '* STAGING TABLES PREPARED, ERR CONDITION TESTED FOR.. NOW TO LOAD!!'; put '*/'; put '/**'; put '* First, CLOSE OUT changed records (if not a REPLACE)'; put '* Note that SAS does not support ANSI standard for UPDATE with a join condition.'; put '* However - this can be worked around using a nested subquery..'; put '*/'; put 'data _null_;'; put 'putlog "&sysmacroname: CLOSEOUTS commencing";'; put 'run;'; put '%if %mf_getattrn(&lastds,NLOBS)=0 %then %do;'; put 'data _null_;'; put 'putlog "&sysmacroname: No closeouts needed";'; put 'run;'; put '%end;'; put '%else %if &engine_type=CAS %then %do;'; put '%mp_abort(iftrue= (&loadtype=BITEMPORAL or &loadtype=TXTEMPORAL)'; put ',mac=&sysmacroname in &_program'; put ',msg=%str(&loadtype not yet supported in CAS engine)'; put ')'; put '/* create temp table for deletions */'; put '%local delds;%let delds=%mf_getuniquename(prefix=DC);'; put 'data casuser.&delds;'; put 'set work.bitemp5d_subquery;'; put 'run;'; put '/* delete the records */'; put 'proc cas ;'; put 'table.deleteRows / table={'; put 'caslib="&base_lib",'; put 'name="&base_dsn",'; put 'where="1=1",'; put 'whereTable={caslib=''CASUSER'',name="&delds"}'; put '};'; put 'quit;'; put '/* drop temp table */'; put 'proc sql;'; put 'drop table CASUSER.&delds;'; put '%end;'; put '%else %if (&loadtype=BITEMPORAL or &loadtype=TXTEMPORAL or &loadtype=UPDATE)'; put '%then %do;'; put 'data _null_;'; put 'putlog "&sysmacroname: &loadtype operation using &engine_type engine";'; put 'run;'; put '%local flexinow;'; put 'proc sql;'; put '/* if OLEDB then create a temp table for efficiency */'; put '%local innertable;'; put '%if &engine_type=OLEDB %then %do;'; put '%let innertable=[##BITEMP_&base_dsn];'; put '%let top_table=[dbo].&base_dsn;'; put '%let flexinow=&SQLNOW;'; put 'create table &base_lib.."##BITEMP_&base_dsn"n as'; put 'select * from work.bitemp5d_subquery;'; put '/* open up a connection for pass through SQL */'; put '%dc_assignlib(WRITE,&base_lib,passthru=myAlias)'; put 'execute('; put '%end;'; put '%else %if &engine_type=REDSHIFT or &engine_type=POSTGRES %then %do;'; put '%let innertable=%mf_getuniquename(prefix=XDCTEMP);'; put '%let top_table=&baselib_schema.&base_dsn;'; put '%let flexinow=timestamp &SQLNOW;'; put '/* make empty table first - must clone & drop extra cols'; put 'as autoload is bad */'; put '%dc_assignlib(WRITE,&base_lib,passthru=myAlias)'; put 'exec (create table &innertable (like &baselib_schema.&base_dsn)) by myAlias;'; put '%if &engine_type=REDSHIFT %then %do;'; put 'exec (alter table &innertable alter sortkey none) by myAlias;'; put '%end;'; put '%let dropcols=%mf_wordsinstr1butnotstr2('; put 'str1=%upcase(%mf_getvarlist(&basecopy))'; put ',str2=%upcase(%mf_getvarlist(work.bitemp5d_subquery))'; put ');'; put '%if %length(&dropcols>0) %then %do idx_pk=1 %to %sysfunc(countw(&dropcols));'; put '%put &=dropcols;'; put '%let idx_val=%scan(&dropcols,&idx_pk);'; put 'exec(alter table &innertable drop column &idx_val;) by myAlias;;'; put '%end;'; put '/* create view to strip formats and avoid warns in log */'; put 'data work.vw_bitemp5d/view=work.vw_bitemp5d;'; put 'set work.bitemp5d_subquery;'; put 'format _all_;'; put 'run;'; put 'proc append base=&base_lib..&innertable ('; put '%do idx_pk=1 %to &redcnt;'; put '&&rednm&idx_pk = &&redval&idxpk'; put '%end;'; put ')'; put 'data=work.vw_bitemp5d force nowarn;'; put 'run;'; put '/* open up a connection for pass through SQL */'; put '%dc_assignlib(WRITE,&base_lib,passthru=myAlias)'; put 'execute('; put '%end;'; put '%else %do;'; put '%let innertable=bitemp5d_subquery;'; put '%let top_table=&base_lib..&base_dsn;'; put '%let flexinow=&now;'; put '%end;'; put '%if &loadtype=BITEMPORAL or &loadtype=TXTEMPORAL %then %do;'; put 'update &top_table set &tech_to=&flexinow'; put '%if %length(&processed)>0 %then %do;'; put ',&processed=&flexinow'; put '%end;'; put 'where &tech_from <= &flexinow and &flexinow < &tech_to and'; put '%end;'; put '%else %if &loadtype=UPDATE %then %do;'; put '/* changed records are deleted then re-appended when doing UPDATEs */'; put 'delete from &top_table where'; put '%end;'; put '%else %do;'; put '%put %str(ERR)OR: BUSTEMPORAL NOT YET SUPPORTED;'; put '%let syscc=5;'; put '%mp_lockanytable(UNLOCK,lib=&base_lib,ds=&base_dsn,ref=&ETLSOURCE,'; put 'ctl_ds=&dclib..mpe_lockanytable'; put ')'; put '%mp_lockanytable(UNLOCK'; put ',lib=%scan(&outds_audit,1,.)'; put ',ds=%scan(&outds_audit,2,.)'; put ',ref=&ETLSOURCE'; put ',ctl_ds=&dclib..mpe_lockanytable'; put ')'; put '%goto end_of_macro;'; put '%end;'; put '/* perform join inside query as per'; put 'http://stackoverflow.com/questions/24629793/update-with-a-proc-sql */'; put 'exists( select 1 from &baselib_schema.&innertable where'; put '/* loop PK join */'; put '%do idx_pk=1 %to &pk_cnt;'; put '%let idx_val=%scan(&pk,&idx_pk);'; put '&base_dsn..&idx_val=&innertable..&idx_val and'; put '%end;'; put '%if &loadtype=BITEMPORAL %then %do;'; put '&base_dsn..&bus_from >= &innertable..&bus_from'; put 'and &base_dsn..&bus_to <= &innertable..&bus_to and'; put '%end;'; put '/* close the statement */'; put '1=1);'; put '%if &engine_type=OLEDB or &engine_type=REDSHIFT or &engine_type=POSTGRES'; put '%then %do;'; put ') by myAlias;'; put 'execute (drop table &baselib_schema.&innertable) by myAlias;'; put '%end;'; put '%end;'; put 'quit;'; put 'data _null_;'; put 'putlog "&sysmacroname: Closeout complete";'; put 'run;'; put '/**'; put '* Append the new / updated records'; put '*/'; put '%if &engine_type=CAS %then %do;'; put '/* get varchar variables ready for casting */'; put '%local vcfmt vcrename vcassign vcdrop;'; put 'data _null_;'; put 'set work.bitemp_cols(where=(type=6)) end=last;'; put 'length vcrename vcassign vcdrop vcfmt $32767 rancol $32;'; put 'retain vcrename vcassign vcdrop vcfmt;'; put 'if _n_=1 then vcrename=''(rename=('';'; put 'rancol=resolve(''%mf_getuniquename()'');'; put 'vcfmt=trim(vcfmt)!!''length ''!!cats(name)!!'' varchar(*);'';'; put 'vcrename=trim(vcrename)!!'' ''!!cats(name,''='',rancol);'; put 'vcassign=cats(vcassign,name,''='',rancol,'';'');'; put 'vcdrop=cats(vcdrop,''drop ''!!rancol,'';'');'; put 'if last then do;'; put 'vcrename=cats(vcrename,''))'');'; put 'call symputx(''vcfmt'',vcfmt);'; put 'call symputx(''vcrename'',vcrename);'; put 'call symputx(''vcassign'',vcassign);'; put 'call symputx(''vcdrop'',vcdrop);'; put 'end;'; put 'run;'; put '/* prepare a temp cas table with varchars casted */'; put '%let tmp=%mf_getuniquename();'; put 'data casuser.&tmp ;'; put '&vcfmt'; put 'set work.bitemp6_unique &vcrename;'; put '&vcassign'; put '&vcdrop'; put 'run;'; put '/* load the table with varchars applied*/'; put 'data &base_lib..&base_dsn (append=yes )/sessref=dcsession ;'; put 'set casuser.&tmp;'; put 'run;'; put '/* drop temp table */'; put 'proc sql;'; put 'drop table CASUSER.&tmp;'; put '/* this code will not work as regular tables do not have varchars */'; put '/*'; put 'proc casutil;'; put 'load data=work.bitemp6_unique'; put 'outcaslib="&base_lib" casout="&base_dsn" append ;'; put 'quit;'; put '*/'; put '%end;'; put '%else %if &engine_type=REDSHIFT or &engine_type=POSTGRES %then %do;'; put 'proc append base=&base_lib..&base_dsn'; put '%if &engine_type=REDSHIFT %then %do;'; put '('; put '%do idx_pk=1 %to &redcnt;'; put '&&rednm&idx_pk = &&redval&idxpk'; put '%end;'; put ')'; put '%end;'; put 'data=bitemp6_unique force nowarn;'; put 'run;'; put '%end;'; put '%else %do;'; put 'proc append base=&base_lib..&base_dsn data=bitemp6_unique force nowarn; run;'; put '%end;'; put '%mp_lockanytable(UNLOCK,lib=&base_lib,ds=&base_dsn,ref=&ETLSOURCE,'; put 'ctl_ds=&dclib..mpe_lockanytable'; put ')'; put '/* final check on syscc */'; put '%mp_abort(iftrue= (&syscc >4)'; put ',mac=&_program'; put ',msg=%str(!!Upload NOT successful!! Failed on actual update / append stage..)'; put ')'; put '%if &outds_audit ne 0 and &LOADTARGET=YES %then %do;'; put 'data work.vw_outds_orig /view=work.vw_outds_orig;'; put 'set work.bitemp0_base (drop=&md5_col);'; put 'where ___TMP___NEW_FLG=0;'; put 'drop ___TMP___NEW_FLG;'; put 'run;'; put '/* update the AUDIT table */'; put '%if %mf_existds(&outds_audit) %then %do;'; put 'options mprint;'; put '%mp_storediffs(&base_lib..&base_dsn'; put ',work.vw_outds_orig'; put ',&pk &bus_from'; put ',delds=&outds_del'; put ',modds=&outds_mod'; put ',appds=&outds_add'; put ',outds=work.mp_storediffs'; put ',processed_dttm=&now'; put ',loadref=%superq(etlsource)'; put ')'; put '/* exclude unchanged values in modified rows */'; put 'data work.mp_storediffs;'; put 'set work.mp_storediffs;'; put 'if MOVE_TYPE="M" and IS_PK=0 and IS_DIFF=0 then delete;'; put '* putlog load_ref= libref= dsn= key_hash= tgtvar_nm=;'; put 'run;'; put 'proc append base=&outds_audit data=work.mp_storediffs;'; put 'run;'; put '%mp_lockanytable(UNLOCK'; put ',lib=%scan(&outds_audit,1,.)'; put ',ds=%scan(&outds_audit,2,.)'; put ',ref=&ETLSOURCE'; put ',ctl_ds=&dclib..mpe_lockanytable'; put ')'; put '%end;'; put '%end;'; put '%mp_abort(iftrue= (&syscc >4)'; put ',mac=bitemporal_dataloader'; put ',msg=%str(Problem in audit stage (&outds_audit))'; put ')'; put '%let user=%mf_getUser();'; put '/**'; put 'Notify as appropriate EMAILS DISABLED'; put '%sumo_alerts(ALERT_EVENT=UPDATE'; put ', ALERT_TARGET=&base_lib..&base_dsn'; put ', from_user= &user);'; put '*/'; put '/* monitor BiTemporal usage */'; put '%if &log=1 %then %do;'; put '%put syscc=&syscc;'; put '/* do not perform duration calc in pass through */'; put '%local dur;'; put 'data _null_;'; put 'now=symget(''now'');'; put 'dur=%sysfunc(datetime())-&now;'; put 'call symputx(''dur'',dur,''l'');'; put 'run;'; put 'proc sql;'; put 'insert into &dclib..mpe_dataloads'; put 'set libref=%upcase("&base_lib")'; put ',DSN=%upcase("&base_dsn")'; put ',ETLSOURCE="&ETLSOURCE"'; put ',LOADTYPE="&loadtype"'; put ',CHANGED_RECORDS=%mf_getattrn(&lastds,NLOBS)'; put ',NEW_RECORDS=%mf_getattrn(&outds_add,NLOBS)'; put ',DELETED_RECORDS=%mf_getattrn(&outds_del,NLOBS)'; put ',DURATION=&dur'; put ',MAC_VER="v&ver"'; put ',user_nm="&user"'; put ',PROCESSED_DTTM=&now;'; put 'quit;'; put '%put syscc=&syscc;'; put '%end;'; put '%end_of_macro:'; put '%mend bitemporal_dataloader;'; put '%macro mm_getGroups('; put 'user='; put ',outds=work.mm_getGroups'; put ',repo=foundation'; put ',mDebug=0'; put ')/*/STORE SOURCE*/;'; put '%local mD oldrepo;'; put '%let oldrepo=%sysfunc(getoption(metarepository));'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing mm_getGroups.sas;'; put '%&mD.put _local_;'; put '/* on some sites, user / group info is in a different metadata repo to the'; put 'default */'; put '%if &oldrepo ne &repo %then %do;'; put 'options metarepository=&repo;'; put '%end;'; put '%if %length(&user)=0 %then %do;'; put 'data &outds (keep=groupuri groupname groupdesc);'; put 'length groupuri groupname groupdesc group_or_role $256;'; put 'call missing(of _all_);'; put 'i+1;'; put 'do while'; put '(metadata_getnobj("omsobj:IdentityGroup?@Id contains ''.''",i,groupuri)>0);'; put 'rc=metadata_getattr(groupuri, "Name", groupname);'; put 'rc=metadata_getattr(groupuri, "Desc", groupdesc);'; put 'rc=metadata_getattr(groupuri,"PublicType",group_or_role);'; put 'if Group_or_Role = ''UserGroup'' then output;'; put 'i+1;'; put 'end;'; put 'run;'; put '%end;'; put '%else %do;'; put 'data &outds (keep=groupuri groupname groupdesc);'; put 'length uri groupuri groupname groupdesc group_or_role $256;'; put 'call missing(of _all_);'; put 'rc=metadata_getnobj("omsobj:Person?@Name=''&user''",1,uri);'; put 'if rc<=0 then do;'; put 'putlog "%str(WARN)ING: rc=" rc "&user not found "'; put '", or there was an issue reading the repository.";'; put 'stop;'; put 'end;'; put 'a=1;'; put 'grpassn=metadata_getnasn(uri,"IdentityGroups",a,groupuri);'; put 'if grpassn in (-3,-4) then do;'; put 'putlog "%str(WARN)ING: No metadata groups found for &user";'; put 'output;'; put 'end;'; put 'else do while (grpassn > 0);'; put 'rc=metadata_getattr(groupuri, "Name", groupname);'; put 'rc=metadata_getattr(groupuri, "Desc", groupdesc);'; put 'a+1;'; put 'rc=metadata_getattr(groupuri,"PublicType",group_or_role);'; put 'if Group_or_Role = ''UserGroup'' then output;'; put 'grpassn=metadata_getnasn(uri,"IdentityGroups",a,groupuri);'; put 'end;'; put 'run;'; put '%end;'; put '%if &oldrepo ne &repo %then %do;'; put 'options metarepository=&oldrepo;'; put '%end;'; put '%mend mm_getGroups;'; put '%macro dc_getusergroups(user=,outds=mm_getgroups);'; put '%global dc_repo_users;'; put '%if &dc_repo_users= %then %let dc_repo_users=foundation;'; put '%mm_getgroups(user=&user,outds=&outds,repo=&dc_repo_users)'; put '%mend dc_getusergroups;'; put '%macro mpe_getgroups(user=,outds=);'; put '%if not %symexist(dc_repo_users) %then %let dc_repo_users=foundation;'; put '%dc_getusergroups(user=&user,outds=&outds)'; put 'data;'; put 'length groupname groupdesc $256;'; put 'set &dc_libref..mpe_groups;'; put 'where &dc_dttmtfmt. lt tx_to;'; put 'where also upcase(user_name)="%upcase(&user)";'; put 'groupname=group_name;'; put 'groupdesc=group_desc;'; put 'keep groupname groupdesc;'; put 'run;'; put 'data &outds;'; put 'set &syslast &outds(keep=groupname groupdesc);'; put 'run;'; put '%mend mpe_getgroups;'; put '%macro mpe_accesscheck('; put 'base_table'; put ',outds=med_accesscheck /* WORK table to contain access details */'; put ',user= /* metadata user to check for */'; put ',access_level=APPROVE'; put ',cntl_lib_var=MPELIB'; put ');'; put '%if &user= %then %let user=%mf_getuser();'; put '%mp_abort('; put 'iftrue=(%index(&outds,.)>0 and %upcase(%scan(&outds,1,.)) ne WORK)'; put ',mac=mpe_accesscheck'; put ',msg=%str(outds should be a WORK table)'; put ')'; put '%mp_abort('; put 'iftrue=(%mf_verifymacvars(base_table user access_level)=0)'; put ',mac=mpe_accesscheck'; put ',msg=%str(Missing base_table/user access_level variables)'; put ')'; put '/* make unique temp table vars */'; put '%local tempds1 tempds2;'; put '%let tempds1=%mf_getuniquename(prefix=usergroups);'; put '%let tempds2=%mf_getuniquename(prefix=tablegroups);'; put '/* get list of user groups */'; put '%mpe_getgroups(user=&user,outds=&tempds1)'; put '/* get list of groups with access for that table */'; put 'proc sql;'; put 'create table &tempds2 as'; put 'select distinct sas_group'; put 'from &&&cntl_lib_var...mpe_security'; put 'where &dc_dttmtfmt. lt tx_to'; put 'and access_level="&access_level"'; put 'and ('; put '(libref="%scan(&base_table,1,.)" and upcase(dsn)="%scan(&base_table,2,.)")'; put 'or (libref="%scan(&base_table,1,.)" and dsn="*ALL*")'; put 'or (libref="*ALL*")'; put ');'; put '%if &_debug ge 131 %then %do;'; put 'data _null_;'; put 'set &tempds1;'; put 'putlog (_all_)(=);'; put 'run;'; put 'data _null_;'; put 'set &tempds2;'; put 'putlog (_all_)(=);'; put 'run;'; put '%end;'; put 'proc sql;'; put 'create table &outds as'; put 'select * from &tempds1'; put 'where groupname="&mpeadmins"'; put 'or groupname in (select * from &tempds2);'; put '%put &sysmacroname: base_table=&base_table;'; put '%put &sysmacroname: access_level=&access_level;'; put '%mend mpe_accesscheck;'; put '%macro mpe_alerts(alert_event='; put ', alert_lib='; put ', alert_ds='; put ', dsid='; put ');'; put '/* exit if not configured */'; put '%global DC_EMAIL_ALERTS;'; put '%if &DC_EMAIL_ALERTS ne YES %then %do;'; put '%put DCNOTE: Email alerts are not configured;'; put '%put DCNOTE: (dc_email_alerts=&dc_email_alerts in &mpelib..mpe_config);'; put '%return;'; put '%end;'; put '%let alert_event=%upcase(&alert_event);'; put '%let alert_lib=%upcase(&alert_lib);'; put '%let alert_ds=%upcase(&alert_ds);'; put '%let from_user=%mf_getuser();'; put '/* get users TO which the email should be sent */'; put 'proc sql noprint;'; put 'create table work.users as select distinct a.alert_user,'; put 'b.user_displayname,'; put 'b.user_email'; put 'from &mpelib..mpe_alerts'; put '(where=(&dc_dttmtfmt. lt tx_to)) a'; put 'left join &mpelib..mpe_emails'; put '(where=(&dc_dttmtfmt. lt tx_to)) b'; put 'on upcase(trim(a.alert_user))=upcase(trim(b.user_name))'; put 'where a.alert_event in ("&alert_event","*ALL*")'; put 'and a.alert_lib in ("&alert_lib","*ALL*")'; put 'and a.alert_ds in ("&alert_ds","*ALL*");'; put '/* ensure the submitter is included on the email */'; put '%local isThere userdisp user_eml;'; put '%let isThere=0;'; put 'select count(*) into: isThere from &syslast where alert_user="&from_user";'; put '%if &isThere=0 %then %do;'; put 'select user_displayname, user_email'; put 'into: userdisp trimmed, :user_eml trimmed'; put 'from &mpelib..mpe_emails'; put 'where &dc_dttmtfmt. lt tx_to'; put 'and user_name="&from_user";'; put 'insert into work.users'; put 'set alert_user="&from_user"'; put ',user_displayname="&userdisp"'; put ',user_email="&user_eml";'; put '%end;'; put '/* if no email / displayname is provided, then extract from metadata */'; put 'data work.emails;'; put 'set work.users;'; put 'length emailuri uri text $256; call missing(emailuri,uri); drop emailuri uri;'; put '/* get displayname */'; put 'text=cats("omsobj:Person?@Name=''",alert_user,"''");'; put 'if metadata_getnobj(text,1,uri)<=0 then do;'; put 'putlog "DCWARN: &from_user not found";'; put 'return;'; put 'end;'; put 'else if user_displayname = '''' then do;'; put 'if metadata_getattr(uri,''DisplayName'',user_displayname)<0 then do;'; put 'putlog ''DCWARN: strange err, no displayname attribute of user URI'';'; put 'end;'; put 'end;'; put 'if index(user_email,''@'') then return;'; put '/* get email from metadata if not in input table */'; put 'if metadata_getnasn(uri,"EmailAddresses",1,emailuri)<=0 then do;'; put 'putlog "DCWARN: " alert_user " has no emails in MPE_EMAILS or metadata!";'; put 'if metadata_getattr(emailuri,"Address",user_email)<0 then do;'; put 'putlog ''DCWARN: Unexpected error! Valid emailURI but no email. Weird.'';'; put 'end;'; put 'end;'; put '/* only keep valid emails */'; put 'if index(user_email,''@'') ;'; put '/* dump contents for debugging */'; put 'if _n_<21 then putlog (_all_)(=);'; put 'run;'; put '%local emails;'; put 'proc sql noprint;'; put 'select quote(trim(user_email)) into: emails separated by '' '' from work.emails;'; put '/* exit if nobody to email */'; put '%if %mf_getattrn(emails,NLOBS)=0 %then %do;'; put '%put NOTE: No alerts configured (mpe_alerts.sas);'; put '%return;'; put '%end;'; put '/* display email options */'; put 'data _null_;'; put 'set sashelp.voption(where=(group=''EMAIL''));'; put 'put optname ''='' setting;'; put 'run;'; put 'filename __out email (&emails)'; put 'subject="Table &alert_lib..&alert_ds has been &alert_event";'; put '%local SUBMITTED_TXT;'; put '%if &alert_event=SUBMITTED %then %do;'; put 'data _null_;'; put 'set &mpelib..mpe_submit;'; put 'where table_id="&dsid" and submit_status_cd=''SUBMITTED'';'; put 'call symputx(''SUBMITTED_TXT'',submitted_reason_txt,''l'');'; put 'run;'; put 'data _null_;'; put 'File __out lrecl=32000;'; put 'put ''Dear user,'';'; put 'put '' '';'; put 'put "Please be advised that a change to table &alert_lib..&alert_ds has "'; put '"been proposed by &from_user on the ''&syshostname'' SAS server.";'; put 'put " ";'; put 'length txt $2048;'; put 'txt=symget(''SUBMITTED_TXT'');'; put 'put "Reason provided: " txt;'; put 'put " ";'; put 'put "This is an automated email by Data Controller for SAS. For "'; put '"documentation, please visit https://docs.datacontroller.io";'; put 'run;'; put '%end;'; put '%else %if &alert_event=APPROVED %then %do;'; put '/* there is no approval message */'; put 'data _null_;'; put 'File __out lrecl=32000;'; put 'put ''Dear user,'';'; put 'put '' '';'; put 'put "Please be advised that a change to table &alert_lib..&alert_ds has "'; put '"been approved by &from_user on the ''&syshostname'' SAS server.";'; put 'put " ";'; put 'put "This is an automated email by Data Controller for SAS. For "'; put '"documentation, please visit https://docs.datacontroller.io";'; put 'run;'; put '%end;'; put '%else %if &alert_event=REJECTED %then %do;'; put 'data _null_;'; put 'set &mpelib..mpe_review;'; put 'where table_id="&dsid" and review_status_id=''REJECTED'';'; put 'call symputx(''REVIEW_REASON_TXT'',REVIEW_REASON_TXT,''l'');'; put 'run;'; put 'data _null_;'; put 'File __out lrecl=32000;'; put 'put ''Dear user,'';'; put 'put '' '';'; put 'put "Please be advised that a change to table &alert_lib..&alert_ds has "'; put '"been rejected by &from_user on the ''&syshostname'' SAS server.";'; put 'put " ";'; put 'length txt $2048;'; put 'txt=symget(''REVIEW_REASON_TXT'');'; put 'put "Reason provided: " txt;'; put 'put " ";'; put 'put "This is an automated email by Data Controller for SAS. For "'; put '"documentation, please visit https://docs.datacontroller.io";'; put 'run;'; put '%end;'; put 'filename __out clear;'; put '%mend mpe_alerts ;'; put '%macro dc_getservicecode(loc=,outref=);'; put '%mm_getstpcode(tree=&loc'; put ',outref=&outref'; put ')'; put '%mend dc_getservicecode;'; put '%macro mp_include(fileref'; put ',prefix=_'; put ',opts=SOURCE2'; put ',errds=work.mp_abort_errds'; put ')/*/STORE SOURCE*/;'; put '/* prepare precode */'; put '%local tempref;'; put '%let tempref=%mf_getuniquefileref();'; put 'data _null_;'; put 'file &tempref;'; put 'set sashelp.vextfl(where=(fileref="%upcase(&fileref)"));'; put 'put ''%let _SYSINCLUDEFILEDEVICE='' xengine '';'';'; put 'name=scan(xpath,-1,''/\'');'; put 'put ''%let _SYSINCLUDEFILENAME='' name '';'';'; put 'path=subpad(xpath,1,length(xpath)-length(name)-1);'; put 'put ''%let _SYSINCLUDEFILEDIR='' path '';'';'; put 'put ''%let _SYSINCLUDEFILEFILEREF='' "&fileref;";'; put 'run;'; put '/* prepare the errds */'; put 'data &errds;'; put 'length msg mac $1000;'; put 'call missing(msg,mac);'; put 'iftrue=''1=0'';'; put 'run;'; put '/* include the include */'; put '%inc &tempref &fileref/&opts;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=%str(&_SYSINCLUDEFILEDIR/&_SYSINCLUDEFILENAME)'; put ',msg=%str(syscc=&syscc after executing &_SYSINCLUDEFILENAME)'; put ')'; put 'filename &tempref clear;'; put '%mend mp_include;'; put '%macro mpe_runhook(hookvar);'; put '%local pgmloc pgmtype;'; put '%let pgmtype=0;'; put '%put &sysmacroname: &=hookvar;'; put '%if %length(&&&hookvar)>0 %then %do;'; put '%put &sysmacroname: Executing &&&hookvar;'; put 'data _null_;'; put 'rule_value=symget("&hookvar");'; put 'if scan(upcase(rule_value),-1,''.'')=''SAS'' then do;'; put 'call symputx(''pgmtype'',''PGM'');'; put 'call symputx(''pgmloc'',rule_value);'; put 'end;'; put 'else do;'; put 'apploc="%mf_getapploc()";'; put 'if substr(rule_value,1,1) ne ''/'''; put 'then rule_value=cats(apploc,''/'',rule_value);'; put 'call symputx(''pgmloc'',rule_value);'; put 'call symputx(''pgmtype'',''JOB'');'; put 'end;'; put 'run;'; put '%if &pgmtype=PGM %then %do;'; put 'filename sascode "&pgmloc";'; put '%end;'; put '%else %do;'; put '%dc_getservicecode(loc=&pgmloc'; put ',outref=sascode'; put ')'; put '%end;'; put '/* the below script will need to modify work.STAGING_DS */'; put '%local x; %let x=; /* legacy feature */'; put '%mp_include(sascode)'; put '%end;'; put '%mend mpe_runhook;'; put '%macro mp_aligndecimal(var,width=8);'; put '%local tmpvar;'; put '%let tmpvar=%mf_getuniquename(prefix=aligndp);'; put 'length &tmpvar $&width;'; put 'if index(&var,''.'') then do;'; put '&tmpvar=cats(scan(&var,1,''.''));'; put '&tmpvar=right(&tmpvar);'; put '&var=&tmpvar!!''.''!!cats(scan(&var,2,''.''));'; put 'end;'; put 'else do;'; put '&tmpvar=cats(&var);'; put '&tmpvar=right(&tmpvar);'; put '&var=&tmpvar;'; put 'end;'; put 'drop &tmpvar;'; put '%mend mp_aligndecimal;'; put '%macro mddl_sas_cntlout(libds=WORK.CNTLOUT);'; put 'proc sql;'; put 'create table &libds('; put 'TYPE char(1) label='; put '''Format Type: either N (num fmt), C (char fmt), I (num infmt) or J (char infmt)'''; put ',FMTNAME char(32) label=''Format name'''; put ',FMTROW num label='; put '''CALCULATED Position of record by FMTNAME (reqd for multilabel formats)'''; put ',START char(32767) label=''Starting value for format'''; put '/*'; put 'Keep lengths of START and END the same to avoid this err:'; put '"Start is greater than end: -<."'; put 'Similar usage note: https://support.sas.com/kb/69/330.html'; put '*/'; put ',END char(32767) label=''Ending value for format'''; put ',LABEL char(32767) label=''Format value label'''; put ',MIN num length=3 label=''Minimum length'''; put ',MAX num length=3 label=''Maximum length'''; put ',DEFAULT num length=3 label=''Default length'''; put ',LENGTH num length=3 label=''Format length'''; put ',FUZZ num label=''Fuzz value'''; put ',PREFIX char(2) label=''Prefix characters'''; put ',MULT num label=''Multiplier'''; put ',FILL char(1) label=''Fill character'''; put ',NOEDIT num length=3 label=''Is picture string noedit?'''; put ',SEXCL char(1) label=''Start exclusion'''; put ',EEXCL char(1) label=''End exclusion'''; put ',HLO char(13) label='; put '''More info: https://core.sasjs.io/mddl__sas__cntlout_8sas_source.html'''; put ',DECSEP char(1) label=''Decimal separator'''; put ',DIG3SEP char(1) label=''Three-digit separator'''; put ',DATATYPE char(8) label=''Date/time/datetime?'''; put ',LANGUAGE char(8) label=''Language for date strings'''; put ');'; put '%local lib;'; put '%let libds=%upcase(&libds);'; put '%if %index(&libds,.)=0 %then %let lib=WORK;'; put '%else %let lib=%scan(&libds,1,.);'; put 'proc datasets lib=&lib noprint;'; put 'modify %scan(&libds,-1,.);'; put 'index create'; put 'pk_cntlout=(type fmtname fmtrow)'; put '/nomiss unique;'; put 'quit;'; put '%mend mddl_sas_cntlout;'; put '%macro mp_cntlout('; put 'iftrue=(1=1)'; put ',libcat='; put ',cntlout=work.fmtextract'; put ',fmtlist=0'; put ')/*/STORE SOURCE*/;'; put '%local ddlds cntlds i;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%let ddlds=%mf_getuniquename();'; put '%let cntlds=%mf_getuniquename();'; put '%mddl_sas_cntlout(libds=&ddlds)'; put '%if %index(&libcat,-)>0 and %scan(&libcat,2,-)=FC %then %do;'; put '%let libcat=%scan(&libcat,1,-);'; put '%end;'; put 'proc format lib=&libcat cntlout=&cntlds;'; put '%if "&fmtlist" ne "0" and "&fmtlist" ne "" %then %do;'; put 'select'; put '%do i=1 %to %sysfunc(countw(&fmtlist,%str( )));'; put '%scan(&fmtlist,&i,%str( ))'; put '%end;'; put ';'; put '%end;'; put 'run;'; put 'data &cntlout/nonote2err;'; put 'if 0 then set &ddlds;'; put 'set &cntlds;'; put 'by type fmtname notsorted;'; put '/* align the numeric values to avoid overlapping ranges */'; put 'if type in ("I","N") then do;'; put '%mp_aligndecimal(start,width=16)'; put '%mp_aligndecimal(end,width=16)'; put 'end;'; put '/* create row marker. Data cannot be sorted without it! */'; put 'if first.fmtname then fmtrow=1;'; put 'else fmtrow+1;'; put 'run;'; put 'proc sort;'; put 'by type fmtname fmtrow;'; put 'run;'; put 'proc sql;'; put 'drop table &ddlds,&cntlds;'; put '%mend mp_cntlout;'; put '/** @endcond */'; put '%macro mp_md5(cvars=,nvars=);'; put '%local i var sep;'; put 'put(md5('; put '%do i=1 %to %sysfunc(countw(&cvars));'; put '%let var=%scan(&cvars,&i,%str( ));'; put '&sep put(md5(trim(&var)),$hex32.)'; put '%let sep=!!;'; put '%end;'; put '%do i=1 %to %sysfunc(countw(&nvars));'; put '%let var=%scan(&nvars,&i,%str( ));'; put '/* multiply by 1 to strip precision errors (eg 0 != 0) */'; put '/* but ONLY if not missing, else will lose any special missing values */'; put '&sep put(md5(trim(put(ifn(missing(&var),&var,&var*1),binary64.))),$hex32.)'; put '%let sep=!!;'; put '%end;'; put '),$hex32.)'; put '%mend mp_md5;'; put '%macro mp_loadformat(libcat,libds'; put ',loadtarget=NO'; put ',auditlibds=0'; put ',locklibds=0'; put ',delete_col=_____DELETE__THIS__RECORD_____'; put ',outds_add=0'; put ',outds_del=0'; put ',outds_mod=0'; put ',mdebug=0'; put ');'; put '/* set up local macro variables and temporary tables (with a prefix) */'; put '%local err msg prefix dslist i var fmtlist ibufsize;'; put '%let dslist=base_fmts template inlibds ds1 stagedata storediffs del1 del2;'; put '%if &outds_add=0 %then %let dslist=&dslist outds_add;'; put '%if &outds_del=0 %then %let dslist=&dslist outds_del;'; put '%if &outds_mod=0 %then %let dslist=&dslist outds_mod;'; put '%let prefix=%substr(%mf_getuniquename(),1,21);'; put '%do i=1 %to %sysfunc(countw(&dslist));'; put '%let var=%scan(&dslist,&i);'; put '%local &var;'; put '%let &var=%upcase(&prefix._&var);'; put '%end;'; put '/* in DC, format catalogs maybe specified in the libds with a -FC extension */'; put '%let libcat=%scan(&libcat,1,-);'; put '/* perform input validations */'; put '%mp_abort('; put 'iftrue=(%mf_existds(&libds)=0)'; put ',mac=&sysmacroname'; put ',msg=%str(&libds could not be found)'; put ')'; put '%mp_abort('; put 'iftrue=(%mf_existvar(&libds,FMTROW)=0)'; put ',mac=&sysmacroname'; put ',msg=%str(FMTROW not found in &libds)'; put ')'; put '%let err=0;'; put '%let msg=0;'; put 'data _null_;'; put 'if _n_=1 then putlog "&sysmacroname entry vars:";'; put 'set sashelp.vmacro;'; put 'where scope="&sysmacroname";'; put 'value=upcase(value);'; put 'if &mdebug=0 then put name ''='' value;'; put 'if name=:''LOAD'' and value not in (''YES'',''NO'') then do;'; put 'call symputx(''msg'',"invalid value for "!!name!!":"!!value);'; put 'call symputx(''err'',1);'; put 'stop;'; put 'end;'; put 'else if name=''LIBCAT'' then do;'; put 'if exist(value,''CATALOG'') le 0 then do;'; put 'call symputx(''msg'',"Unable to open catalog: "!!value);'; put 'call symputx(''err'',1);'; put 'stop;'; put 'end;'; put 'end;'; put 'else if (name=:''OUTDS'' or name in (''DELETE_COL'',''LOCKLIBDS'',''AUDITLIBDS''))'; put 'and missing(value) then do;'; put 'call symputx(''msg'',"missing value in var: "!!name);'; put 'call symputx(''err'',1);'; put 'stop;'; put 'end;'; put 'run;'; put 'data _null_;'; put 'set &libds;'; put 'if missing(fmtrow) then do;'; put 'call symputx(''msg'',"missing fmtrow in format: "!!FMTNAME);'; put 'call symputx(''err'',1);'; put 'stop;'; put 'end;'; put 'run;'; put '%mp_abort('; put 'iftrue=(&err ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(&msg)'; put ')'; put '%local cnt;'; put 'proc sql noprint;'; put 'select count(distinct catx(''|'',type,fmtname,fmtrow)) into: cnt from &libds;'; put '%mp_abort('; put 'iftrue=(&cnt ne %mf_nobs(&libds))'; put ',mac=&sysmacroname'; put ',msg=%str(Non-unique primary key on &libds)'; put ')'; put '/**'; put '* First, extract only relevant formats from the catalog'; put '*/'; put 'proc sql noprint;'; put 'select distinct'; put 'case'; put 'when type=''N'' then upcase(fmtname)'; put 'when type=''C'' then cats(''$'',upcase(fmtname))'; put 'when type=''I'' then cats(''@'',upcase(fmtname))'; put 'when type=''J'' then cats(''@$'',upcase(fmtname))'; put 'else "&sysmacroname:UNHANDLED"'; put 'end'; put 'into: fmtlist separated by '' '''; put 'from &libds;'; put '%mp_cntlout(libcat=&libcat,fmtlist=&fmtlist,cntlout=&base_fmts)'; put '/* get a hash of the row */'; put '%local cvars nvars;'; put '%let cvars=TYPE FMTNAME START END LABEL PREFIX FILL SEXCL EEXCL HLO DECSEP'; put 'DIG3SEP DATATYPE LANGUAGE;'; put '%let nvars=FMTROW MIN MAX DEFAULT LENGTH FUZZ MULT NOEDIT;'; put 'data &base_fmts/note2err;'; put 'set &base_fmts;'; put 'fmthash=%mp_md5(cvars=&cvars, nvars=&nvars);'; put 'run;'; put '/**'; put '* Ensure input table and base_formats have consistent lengths and types'; put '*/'; put 'data &inlibds/nonote2err;'; put 'length &delete_col $3 FMTROW 8 start end label $32767;'; put 'if 0 then set &base_fmts;'; put 'set &libds;'; put 'by type fmtname notsorted;'; put 'if &delete_col='''' then &delete_col=''No'';'; put 'fmtname=upcase(fmtname);'; put 'type=upcase(type);'; put 'if missing(type) then do;'; put 'if substr(fmtname,1,1)=''@'' then do;'; put 'if substr(fmtname,2,1)=''$'' then type=''J'';'; put 'else type=''I'';'; put 'end;'; put 'else do;'; put 'if substr(fmtname,1,1)=''$'' then type=''C'';'; put 'else type=''N'';'; put 'end;'; put 'end;'; put 'if type in (''N'',''I'') then do;'; put '%mp_aligndecimal(start,width=16)'; put '%mp_aligndecimal(end,width=16)'; put 'end;'; put 'fmthash=%mp_md5(cvars=&cvars, nvars=&nvars);'; put 'run;'; put '/**'; put '* Identify new records'; put '*/'; put 'proc sql;'; put 'create table &outds_add(drop=&delete_col) as'; put 'select a.*'; put 'from &inlibds a'; put 'left join &base_fmts b'; put 'on a.type=b.type and a.fmtname=b.fmtname and a.fmtrow=b.fmtrow'; put 'where b.fmtname is null'; put 'and upcase(a.&delete_col) ne "YES"'; put 'order by type, fmtname, fmtrow;'; put '/**'; put '* Identify modified records'; put '*/'; put 'create table &outds_mod (drop=&delete_col) as'; put 'select a.*'; put 'from &inlibds a'; put 'inner join &base_fmts b'; put 'on a.type=b.type and a.fmtname=b.fmtname and a.fmtrow=b.fmtrow'; put 'where upcase(a.&delete_col) ne "YES"'; put 'and a.fmthash ne b.fmthash'; put 'order by type, fmtname, fmtrow;'; put '/**'; put '* Identify deleted records'; put '*/'; put 'create table &outds_del(drop=&delete_col) as'; put 'select a.*'; put 'from &inlibds a'; put 'inner join &base_fmts b'; put 'on a.type=b.type and a.fmtname=b.fmtname and a.fmtrow=b.fmtrow'; put 'where upcase(a.&delete_col)="YES"'; put 'order by type, fmtname, fmtrow;'; put '/**'; put '* Identify fully deleted formats (where every record is removed)'; put '* These require to be explicitly deleted in proc format'; put '* del1 - identify _partial_ deletes'; put '* del2 - exclude these, and also formats that come with _additions_'; put '*/'; put 'create table &del1 as'; put 'select a.*'; put 'from &base_fmts a'; put 'left join &outds_del b'; put 'on a.type=b.type and a.fmtname=b.fmtname and a.fmtrow=b.fmtrow'; put 'where b.fmtrow is null;'; put 'create table &del2 as'; put 'select * from &outds_del'; put 'where cats(type,fmtname) not in (select cats(type,fmtname) from &outds_add)'; put 'and cats(type,fmtname) not in (select cats(type,fmtname) from &del1);'; put '%mp_abort('; put 'iftrue=(&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(SYSCC=&syscc prior to load prep)'; put ')'; put '%if &loadtarget=YES %then %do;'; put '/* new records plus base records that are not deleted or modified */'; put 'data &ds1;'; put 'merge &base_fmts(in=base)'; put '&outds_mod(in=mod)'; put '&outds_add(in=add)'; put '&outds_del(in=del);'; put 'if not del and not mod;'; put 'by type fmtname fmtrow;'; put 'run;'; put '/* add back the modified records */'; put 'data &stagedata;'; put 'set &ds1 &outds_mod;'; put 'run;'; put 'proc sort;'; put 'by type fmtname fmtrow;'; put 'run;'; put '%end;'; put '/* mp abort needs to run outside of conditional blocks */'; put '%mp_abort('; put 'iftrue=(&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(SYSCC=&syscc prior to actual load)'; put ')'; put '%if &loadtarget=YES %then %do;'; put '%if %mf_nobs(&stagedata)=0 and %mf_nobs(&del2)=0 %then %do;'; put '%put There are no changes to load in &libcat!;'; put '%return;'; put '%end;'; put '%if &locklibds ne 0 %then %do;'; put '/* prevent parallel updates */'; put '%mp_lockanytable(LOCK'; put ',lib=%scan(&libcat,1,.)'; put ',ds=%scan(&libcat,2,.)-FC'; put ',ref=MP_LOADFORMAT commencing format load'; put ',ctl_ds=&locklibds'; put ')'; put '%end;'; put '/* do the actual load */'; put 'proc format lib=&libcat cntlin=&stagedata;'; put 'run;'; put '/* apply any full deletes */'; put '%if %mf_nobs(&del2)>0 %then %do;'; put '%local delfmtlist;'; put 'proc sql noprint;'; put 'select distinct case when type=''N'' then cats(fmtname,''.FORMAT'')'; put 'when type=''C'' then cats(fmtname,''.FORMATC'')'; put 'when type=''J'' then cats(fmtname,''.INFMTC'')'; put 'when type=''I'' then cats(fmtname,''.INFMT'')'; put 'else cats(fmtname,''.BADENTRY!!!'') end'; put 'into: delfmtlist'; put 'separated by '' '''; put 'from &del2;'; put 'proc catalog catalog=&libcat;'; put 'delete &delfmtlist;'; put 'quit;'; put '%end;'; put '%if &locklibds ne 0 %then %do;'; put '/* unlock the table */'; put '%mp_lockanytable(UNLOCK'; put ',lib=%scan(&libcat,1,.)'; put ',ds=%scan(&libcat,2,.)-FC'; put ',ref=MP_LOADFORMAT completed format load'; put ',ctl_ds=&locklibds'; put ')'; put '%end;'; put '/* track the changes */'; put '%if &auditlibds ne 0 %then %do;'; put '%if &locklibds ne 0 %then %do;'; put '%mp_lockanytable(LOCK'; put ',lib=%scan(&auditlibds,1,.)'; put ',ds=%scan(&auditlibds,2,.)'; put ',ref=MP_LOADFORMAT commencing audit table load'; put ',ctl_ds=&locklibds'; put ')'; put '%end;'; put '%mp_storediffs(&libcat-FC'; put ',&base_fmts'; put ',TYPE FMTNAME FMTROW'; put ',delds=&outds_del'; put ',modds=&outds_mod'; put ',appds=&outds_add'; put ',outds=&storediffs'; put ',mdebug=&mdebug'; put ')'; put 'proc append base=&auditlibds data=&storediffs;'; put 'run;'; put '%if &locklibds ne 0 %then %do;'; put '%mp_lockanytable(UNLOCK'; put ',lib=%scan(&auditlibds,1,.)'; put ',ds=%scan(&auditlibds,2,.)'; put ',ref=MP_LOADFORMAT commencing audit table load'; put ',ctl_ds=&locklibds'; put ')'; put '%end;'; put '%end;'; put '%end;'; put '%mp_abort('; put 'iftrue=(&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(SYSCC=&syscc after load)'; put ')'; put '%if &mdebug=0 %then %do;'; put 'proc datasets lib=work;'; put 'delete &prefix:;'; put 'run;'; put '%put &sysmacroname exit vars:;'; put '%put _local_;'; put '%end;'; put '%mend mp_loadformat;'; put '%macro mpe_targetloader(libds= /* library.dataset to LOAD (target) */'; put ',now= %sysfunc(datetime()) /* static processed timestamp */'; put ',etlsource= /* process from whence the data came */'; put ',STAGING_DS= STAGING_DS /* name of staging (work) dataset which should'; put 'be appended into the target. */'; put ',LOADTARGET=NO /* set to yes to actually load the target */'; put ',CLOSE_VARS= /* provide close vars to override defaults */'; put ',dclib=NOTPROVIDED'; put ',mdebug=0'; put ',dc_dttmtfmt=E8601DT26.6'; put ');'; put '%local lib ds nobs;'; put '/**'; put '* if a format catalog (suffix "-FC") we assume the catalog has already been'; put '* created by the calling program with a libds of work.fmtextract'; put '*/'; put '%let orig_lib=%upcase(%scan(&libds,1,.));'; put '%let orig_ds=%upcase(%scan(&libds,2,.));'; put '%let orig_libds=&libds;'; put '%if %scan(&libds,2,-)=FC %then %do;'; put '%let lib=WORK;'; put '%let ds=FMTEXTRACT;'; put '%let libds=&lib..&ds;'; put '%end;'; put '%else %do;'; put '%let lib=&orig_lib;'; put '%let ds=&orig_ds;'; put '%end;'; put '%mp_abort(iftrue= (&dclib=NOTPROVIDED)'; put ',mac=&sysmacroname'; put ',msg=%str(dclib=NOTPROVIDED)'; put ')'; put '/* get table attributes */'; put '%let nobs=0;'; put 'data work.sumo_config;'; put 'set &mpelib..mpe_tables;'; put 'where &dc_dttmtfmt. lt tx_to'; put 'and libref="&orig_lib"'; put 'and dsn="&orig_ds";'; put 'call symputx(''LOADTYPE'',loadtype,''l'');'; put 'call symputx(''BUSKEY'',buskey,''l'');'; put 'call symputx(''VAR_TXFROM'',var_txfrom,''l'');'; put 'call symputx(''VAR_TXTO'',var_txto,''l'');'; put 'call symputx(''VAR_BUSFROM'',var_busfrom,''l'');'; put 'call symputx(''VAR_BUSTO'',var_busto,''l'');'; put 'call symputx(''VAR_PROCESSED'',VAR_PROCESSED,''l'');'; put 'call symputx(''RK_UNDERLYING'',RK_UNDERLYING,''l'');'; put '%if %length(&CLOSE_VARS)=0 %then %do;'; put 'call symputx(''CLOSE_VARS'',CLOSE_VARS,''l'');'; put '%end;'; put 'call symputx(''nobs'',_n_,''l'');'; put 'if missing(AUDIT_LIBDS) then AUDIT_LIBDS="&dclib..MPE_AUDIT";'; put 'call symputx(''AUDIT_LIBDS'',AUDIT_LIBDS,''l'');'; put 'put (_all_)(=);'; put 'run;'; put '/* check if table is actually configured to load */'; put '%if &nobs ne 1 %then %do;'; put 'proc sql;'; put 'insert into &mpelib..mpe_loads'; put 'set USER_NM="%mf_getuser()"'; put ',STATUS=''FAILED (BAD DS)'''; put ',CSV_DIR=symget(''ETLSOURCE'')'; put ',PROCESSED_DTTM=&now;'; put '%end;'; put '%mp_abort(iftrue= (&nobs=0)'; put ',mac=&sysmacroname'; put ',msg=%str(Table not registered in &mpelib..mpe_tables)'; put ')'; put '%mp_abort(iftrue= (&nobs>1)'; put ',mac=&sysmacroname'; put ',msg=%str(Something is very wrong with the PK in &mpelib..mpe_tables)'; put ')'; put '%if &LOADTYPE=TXTEMPORAL %then %do;'; put '%bitemporal_dataloader(bus_from=,bus_to= /* explicitly empty*/'; put ',tech_from=&VAR_TXFROM'; put ',tech_to = &VAR_TXTO'; put ',base_lib=&lib'; put ',base_dsn=&ds'; put ',append_lib=WORK'; put ',append_dsn=&STAGING_DS'; put ',high_date=''31DEC9999:23:59:59''dt'; put ',PK= &buskey'; put ',ETLSOURCE=&ETLSOURCE'; put ',LOADTYPE=&loadtype'; put ',RK_UNDERLYING=&RK_UNDERLYING'; put ',LOADTARGET=&LOADTARGET'; put ',RK_UPDATE_MAXKEYTABLE=&LOADTARGET'; put ',CLOSE_VARS=&CLOSE_VARS'; put ',processed=&VAR_PROCESSED'; put ',dclib=&dclib'; put ',outds_audit=&AUDIT_LIBDS'; put ')'; put '%end;'; put '%else %if &loadtype=REPLACE %then %do;'; put '%if &LOADTARGET=YES %then %do;'; put '%mp_lockanytable(LOCK,lib=&lib,ds=&ds,ref=%str(&etlsource),'; put 'ctl_ds=&dclib..mpe_lockanytable'; put ')'; put 'data WORK.&STAGING_DS;'; put 'set WORK.&STAGING_DS;'; put '%if %mf_existvar(&libds,&VAR_PROCESSED) %then %do;'; put '&VAR_PROCESSED = &now;'; put '%end;'; put 'drop _____DELETE__THIS__RECORD_____;'; put 'run;'; put 'proc sql; delete * from &libds;'; put 'proc append base=&libds data=WORK.&STAGING_DS force nowarn;run;'; put '%mp_lockanytable(UNLOCK,lib=&lib,ds=&ds,ctl_ds=&dclib..mpe_lockanytable)'; put '%end;'; put '%else %do;'; put '/* is full replace so treat all staged records as new in diff screen */'; put 'data work.outds_mod work.outds_add ;'; put 'set work.&staging_ds;'; put 'output work.outds_add;'; put 'run;'; put '/* previous table will be considered fully deleted */'; put 'data work.outds_del;'; put 'set &lib..&ds;'; put 'run;'; put '%end;'; put '%end;'; put '%else %if &loadtype=UPDATE %then %do;'; put '%bitemporal_dataloader(bus_from=,bus_to='; put ',tech_from= ,tech_to = /* explicitly empty*/'; put ',base_lib=&lib'; put ',base_dsn=&ds'; put ',append_lib=WORK'; put ',append_dsn=&STAGING_DS'; put ',high_date=''31DEC9999:23:59:59''dt'; put ',PK= &buskey'; put ',ETLSOURCE=%superq(etlsource)'; put ',LOADTYPE=UPDATE'; put ',RK_UNDERLYING=&RK_UNDERLYING'; put ',LOADTARGET=&LOADTARGET'; put ',RK_UPDATE_MAXKEYTABLE=&LOADTARGET'; put ',processed=&VAR_PROCESSED'; put ',dclib=&dclib'; put ',outds_audit=&AUDIT_LIBDS'; put ')'; put '%end;'; put '%else %if &loadtype=FORMAT_CAT %then %do;'; put '/**'; put '* run mp_formatload'; put '* inputs:'; put '* - LOADTARGET'; put '* - CATALOG'; put '* - STAGEDATA'; put '* - LOADAUDIT'; put '* outputs:'; put '* work.outds_add'; put '* work.outds_del'; put '* work.outds_mod'; put '*/'; put '%mp_loadformat(&orig_libds'; put ',&staging_ds'; put ',loadtarget=&LOADTARGET'; put ',auditlibds=&AUDIT_LIBDS'; put ',locklibds=&dclib..mpe_lockanytable'; put ',delete_col=_____DELETE__THIS__RECORD_____'; put ',outds_add=outds_add'; put ',outds_del=outds_del'; put ',outds_mod=outds_mod'; put ',mdebug=&mdebug'; put ')'; put '%end;'; put '%else %if &loadtype=BITEMPORAL %then %do;'; put '%bitemporal_dataloader(bus_from=&VAR_BUSFROM,bus_to=&VAR_BUSTO'; put ',tech_from=&VAR_TXFROM'; put ',tech_to = &VAR_TXTO'; put ',base_lib=&lib'; put ',base_dsn=&ds'; put ',append_lib=WORK'; put ',append_dsn=&STAGING_DS'; put ',high_date=''31DEC9999:23:59:59''dt'; put ',PK= &buskey'; put ',ETLSOURCE=%superq(etlsource)'; put ',LOADTYPE=BITEMPORAL'; put ',RK_UNDERLYING=&RK_UNDERLYING'; put ',LOADTARGET=&LOADTARGET'; put ',RK_UPDATE_MAXKEYTABLE=&LOADTARGET'; put ',CLOSE_VARS=&CLOSE_VARS'; put ',processed=&VAR_PROCESSED'; put ',dclib=&dclib'; put ',outds_audit=&AUDIT_LIBDS'; put ')'; put '%end;'; put '%else %do;'; put '%put WARNING: LOADTYPE &LOADTYPE not supported;'; put '%let syscc=4;'; put '%mp_abort(msg=LOADTYPE &LOADTYPE not supported,mac=mpe_targetloader.sas)'; put '%end;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc exiting MPE_TARGETLOADER macro)'; put ')'; put '%mend mpe_targetloader;'; put '%macro removecolsfromwork(col);'; put '/* only an issue if debug mode enabled */'; put '%global _debug;'; put '%if &_debug ge 131 %then %do;'; put '%let col=%upcase(&col);'; put '%local memlist;'; put 'proc sql noprint;'; put 'select distinct memname into: memlist'; put 'separated by '' '''; put 'from dictionary.columns'; put 'where libname=''WORK'' and upcase(name)="&col";'; put '%if %mf_isblank(&memlist) %then %return;'; put '%mp_dropmembers(list=&memlist)'; put '%end;'; put '%mend removecolsfromwork;'; put '%macro mf_getuniquelibref(prefix=mclib,maxtries=1000);'; put '%local x;'; put '%if ( %length(&prefix) gt 7 ) %then %do;'; put '%put %str(ERR)OR: The prefix parameter cannot exceed 7 characters.;'; put '0'; put '%return;'; put '%end;'; put '%else %if (%sysfunc(NVALID(&prefix,v7))=0) %then %do;'; put '%put %str(ERR)OR: Invalid prefix (&prefix);'; put '0'; put '%return;'; put '%end;'; put '/* Set maxtries equal to ''10 to the power of [# unused characters] - 1'' */'; put '%let maxtries=%eval(10**(8-%length(&prefix))-1);'; put '%do x = 0 %to &maxtries;'; put '%if %sysfunc(libref(&prefix&x)) ne 0 %then %do;'; put '&prefix&x'; put '%return;'; put '%end;'; put '%let x = %eval(&x + 1);'; put '%end;'; put '%put %str(ERR)OR: No usable libref in range &prefix.0-&maxtries;'; put '%put %str(ERR)OR- Try reducing the prefix or deleting some libraries!;'; put '0'; put '%mend mf_getuniquelibref;'; put '* SAS Macros end;'; put '* SAS Includes start;'; put '* SAS Includes end;'; put '* Binary Files start;'; put '* Binary Files end;'; put '* ServiceInit start;'; put 'options noquotelenmax ps=max;'; put '/* create dummy macros to prevent stpbegin / stpend from corrupting sessions */'; put '%macro stpbegin();'; put '%put NOTE: the STPBEGIN macro should not be used for web apps!;'; put '%mend stpbegin;'; put '%macro stpend();'; put '%put NOTE: the STPEND macro should not be used for web apps!;'; put '%mend stpend;'; put '* ServiceInit end;'; put '* Service start;'; put '/**'; put '@file postdata.sas'; put '@brief Either returns the file diffs or actually loads the data to target'; put '@details Before loading the target, a check is made against the time the'; put 'target was last updated (backend) and the time the DIFF was generated'; put '(frontend). If the target was updated whilst the DIFF was on the screen,'; put 'then the provided diff may have been incorrect and so a new DIFF should be'; put 'generated and approved before load.'; put 'Only 100 rows (of each DIFF type) are displayed on the DIFF screen.'; put '

Service Inputs

'; put '
SASCONTROLTABLE
'; put '|ACTION:$char10.|TABLE:$char32.|DIFFTIME:$char29.|'; put '|---|---|---|'; put '|SHOW_DIFFS|DC20220208T142124517_124703_1184|"Tue, 08 Feb 2022 14:23:05 GMT"|'; put '

SAS Macros

'; put '@li bitemporal_dataloader.sas'; put '@li dc_assignlib.sas'; put '@li mf_existds.sas'; put '@li mf_existvar.sas'; put '@li mf_getattrn.sas'; put '@li mf_getengine.sas'; put '@li mf_getquotedstr.sas'; put '@li mf_getuniquelibref.sas'; put '@li mf_getuser.sas'; put '@li mf_getvarlist.sas'; put '@li mf_nobs.sas'; put '@li mf_verifymacvars.sas'; put '@li mp_abort.sas'; put '@li mp_cntlout.sas'; put '@li mp_lockanytable.sas'; put '@li mpe_accesscheck.sas'; put '@li mpe_alerts.sas'; put '@li mpe_runhook.sas'; put '@li mpe_targetloader.sas'; put '@li removecolsfromwork.sas'; put '@version 9.2'; put '@author 4GL Apps Ltd'; put '@copyright 4GL Apps Ltd. This code may only be used within Data Controller'; put 'and may not be re-distributed or re-sold without the express permission of'; put '4GL Apps Ltd.'; put '**/'; put '/* this could be a config setting if required */'; put '%let maxdiff=100;'; put '%mpeinit()'; put '/* load parameters */'; put 'data _null_;'; put 'set work.sascontroltable;'; put 'call symputx(''ACTION'',ACTION);'; put 'call symputx(''LOAD_REF'',TABLE);'; put '/* DIFFTIME is when the DIFF was generated on the frontend */'; put 'call symputx(''DIFFTIME'',DIFFTIME);'; put 'run;'; put '%global action is_err err_msg msg;'; put '%let is_err=0;'; put '%let user=%mf_getuser();'; put '%let sastime=%sysfunc(datetime());'; put 'data sastime;'; put 'dt_sastime=&sastime;'; put 'run;'; put 'PROC FORMAT;'; put 'picture yymmddhhmmss other=''%0Y-%0m-%0d %0H:%0M:%0S'' (datatype=datetime);'; put 'picture flatdate other=''%0Y%0m%0d_%0H%0M%0S'' (datatype=datetime);'; put 'RUN;'; put '/* SHOW_DIFFS works by getting the temp tables from the bitemporal loader */'; put '/* so we share much of the logic from the actual load process */'; put '%let isfmtcat=0;'; put 'data APPROVE1;'; put 'set &mpelib..mpe_submit;'; put 'where TABLE_ID="&LOAD_REF";'; put '/* fetch mpe_submit data */'; put 'libds=cats(base_lib,''.'',base_ds);'; put 'REVIEWED_ON=put(reviewed_on_dttm,datetime19.);'; put 'call symputx(''REVIEW_STATUS_ID'',submit_status_cd,''l'');'; put 'call symputx(''NUM_OF_APPROVALS_REQUIRED'',NUM_OF_APPROVALS_REQUIRED);'; put 'call symputx(''num_of_approvals_remaining'',num_of_approvals_remaining);'; put '/* other stuff that''s useful to do in data step */'; put 'call symputx(''orig_libds'',libds);'; put 'call symputx(''libds'',libds);'; put 'if substr(cats(reverse(libds)),1,3)=:''CF-'' then do;'; put 'libds=scan(libds,1,''-'');'; put 'putlog "Format Catalog Captured";'; put 'call symputx(''isfmtcat'',1);'; put 'libds=''work.fmtextract'';'; put 'call symputx(''libds'',libds);'; put 'end;'; put 'putlog (_all_)(=);'; put '/* convert provided string DIFFTIME back to a numeric SAS datetime */'; put 'if "&action" ne "SHOW_DIFFS" then do;'; put 'call symputx(''DIFFTIME'',input(symget(''DIFFTIME''),anydtdtm18.));'; put 'end;'; put 'length difftime $32;'; put 'DIFFTIME=put(&sastime,datetime19.2);'; put 'run;'; put '%mp_cntlout('; put 'iftrue=(&isfmtcat=1)'; put ',libcat=&orig_libds'; put ',fmtlist=0'; put ',cntlout=work.fmtextract'; put ')'; put '%mp_abort('; put 'iftrue=(%mf_verifymacvars(difftime orig_libds libds load_ref)=0)'; put ',mac=&_program'; put ',msg=%str(Missing: difftime orig_libds libds load_ref)'; put ')'; put '/* security checks */'; put '%mpe_accesscheck(&orig_libds,outds=authEDIT,user=&user,access_level=EDIT)'; put '%mpe_accesscheck(&orig_libds,outds=authAPP,user=&user,access_level=APPROVE)'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc Before entering postdata macro)'; put ')'; put '%mp_abort('; put 'iftrue=('; put '%mf_getattrn(work.authEDIT,NLOBS)=0 & %mf_getattrn(work.authAPP,NLOBS)=0'; put ')'; put ',mac=&_program'; put ',msg=%str(&user not authorised to view approval screen for &orig_libds)'; put ')'; put '%macro quickmacro(inds,outds);'; put 'data &outds ;'; put '%if %length(&VAR_BUSFROM)>0 %then %do;'; put 'format &VAR_BUSFROM &VAR_BUSTO yymmddhhmmss.;'; put '%end;'; put 'if 0 then set &emptybasetable;'; put 'set &inds;'; put '%if %mf_existvar(&libds,&var_txfrom) %then %do;'; put 'drop &var_txfrom &var_txto;'; put '%end;'; put '%if %mf_existvar(&inds,_____DELETE__THIS__RECORD_____) %then %do;'; put 'drop _____DELETE__THIS__RECORD_____;'; put '%end;'; put '%if %mf_existvar(&inds,&VAR_PROCESSED) %then %do;'; put 'drop &VAR_PROCESSED;'; put '%end;'; put 'run;'; put '%mend quickmacro;'; put '%macro postdata();'; put '%if %quote(&REVIEW_STATUS_ID)=%quote(REJECTED)'; put 'or %quote(&REVIEW_STATUS_ID)=%quote(APPROVED) %then'; put '%do;'; put 'data params; set approve1; run;'; put '%webout(OPEN)'; put '%webout(OBJ,PARAMS)'; put '%webout(CLOSE)'; put '%return;'; put '%end;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(syscc=&syscc)'; put ')'; put '%if &action=APPROVE_TABLE %then %do;'; put '/* check user is authorised to approve table */'; put '/* user could be an editor but not an approver */'; put '%mp_abort(iftrue= (%mf_getattrn(work.authAPP,NLOBS)=0)'; put ',mac=&_program'; put ',msg=%str(&user may not APPROVE changes)'; put ')'; put '/* see if this user has already submitted an approval */'; put '%let prev_upload_check=1;'; put 'proc sql;'; put 'select count(*) into: prev_upload_check from &mpelib..mpe_review'; put 'where TABLE_ID="&LOAD_REF" and REVIEWED_BY_NM="&user"'; put 'and REVIEW_STATUS_ID ne "SUBMITTED";'; put '%let authcheck=%mf_getattrn(work.authAPP,NLOBS);'; put '%if &authcheck=0 or &prev_upload_check=1 %then %do;'; put '%put WARNING: authcheck=&authcheck prev_upload_check=&prev_upload_check;'; put 'data apPARAMS;'; put 'AUTHORISED=&authcheck;'; put 'PREV_UPLOAD_CHECK=&prev_upload_check;'; put 'run;'; put '%webout(OPEN)'; put '%webout(OBJ,apPARAMS);'; put '%webout(CLOSE)'; put '%return;'; put '%end;'; put '/* now check if table has been updated since DIFF screen shown */'; put '%local fmt_tm usernm last_load etlsource;'; put '%let last_load=0;'; put 'proc sql noprint;'; put 'select max(processed_dttm) format=16.2 into: last_load'; put 'from &mpelib..mpe_dataloads'; put 'where libref="%scan(&orig_libds,1,.)" and dsn="%scan(&orig_libds,2,.)";'; put 'select processed_dttm format=datetime19., user_nm, etlsource'; put 'into: fmt_tm, :usernm, :etlsource'; put 'from &mpelib..mpe_dataloads'; put 'where libref="%scan(&orig_libds,1,.)" and dsn="%scan(&orig_libds,2,.)"'; put 'and processed_dttm=&last_load;'; put '%put TIMECHECK: &last_load>&difftime;'; put '%if %sysevalf(&last_load>&difftime,boolean)=1 %then %do;'; put '%let is_err=1;'; put '%let err_msg=&orig_libds was updated in batch %trim(&etlsource'; put ') by %trim(&usernm) on &fmt_tm - please refresh the page!!;'; put '%return;'; put '%end;'; put '%if &syscc ne 0 %then %do;'; put '%let is_err=1;'; put '%let err_msg=syscc=&syscc before logchange;'; put '%return;'; put '%end;'; put '/* upload about to commence so ensure logs */'; put 'options notes mprint source2;'; put '%local oldloc;'; put '%if %symexist(SYSPRINTTOLOG) %then %let oldloc=&SYSPRINTTOLOG;'; put '%else %let oldloc=%qsysfunc(getoption(LOG));'; put '%if %length(&oldloc)>0 %then %do;'; put 'proc printto'; put 'log="&mpelocapprovals/&LOAD_REF/approval.log";'; put 'run;'; put 'data _null_;'; put 'if _n_=1 then do;'; put 'length oldloc $1000;'; put 'oldloc=symget(''oldloc'');'; put 'putlog "****** redirected:" oldloc " *****";'; put 'end;'; put 'infile &oldloc;'; put 'input; putlog _infile_;'; put 'run;'; put '%end;'; put '%else %do;'; put 'proc printto'; put 'log="&mpelocapprovals/&LOAD_REF/approval.log";'; put 'run;'; put '%end;'; put '%if &syscc ne 0 %then %do;'; put '%let is_err=1;'; put '%let err_msg=syscc=&syscc after logchange;'; put '%return;'; put '%end;'; put '%end;'; put '/**'; put '* upload the actual table'; put '*/'; put '%local libref ds;'; put '%let libref=%scan(&orig_libds,1,.);'; put '%let ds=%scan(&orig_libds,2,.);'; put 'proc sql noprint;'; put 'select PRE_APPROVE_HOOK, POST_APPROVE_HOOK, LOADTYPE, var_txfrom, var_txto'; put ',BUSKEY, VAR_BUSFROM, VAR_BUSTO'; put ',AUDIT_LIBDS, NOTES, coalesce(NUM_OF_APPROVALS_REQUIRED,1)'; put ',VAR_PROCESSED'; put 'into: PRE_APPROVE_HOOK, :POST_APPROVE_HOOK, :LOADTYPE,:var_txfrom,:var_txto'; put ',:BUSKEY,:VAR_BUSFROM,:VAR_BUSTO'; put ',:AUDIT_LIBDS, :TABLE_DESC, :NUM_OF_APPROVALS_REQUIRED_TOT'; put ',:VAR_PROCESSED'; put 'from &mpelib..mpe_tables'; put 'where &dc_dttmtfmt. lt tx_to'; put 'and libref="&libref"'; put 'and dsn="&ds";'; put '%mp_abort('; put 'iftrue=(%mf_verifymacvars(mpelocapprovals orig_libds)=0)'; put ',mac=&_program'; put ',msg=%str(Missing: mpelocapprovals orig_libds)'; put ')'; put '/* get dataset from approvals location (has same name as load_ref) */'; put '%let tmplib=%mf_getuniquelibref();'; put 'libname &tmplib "&mpelocapprovals/&LOAD_REF";'; put 'data STAGING_DS;'; put 'set &tmplib..&LOAD_REF;'; put 'run;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(syscc=&syscc before preapprove)'; put ')'; put '%dc_assignlib(WRITE,&libref)'; put '/* run pre-approve hook - occurs both BEFORE _and_ AFTER the diff */'; put '%mpe_runhook(PRE_APPROVE_HOOK)'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(syscc=&syscc after preapprove)'; put ')'; put '%if &num_of_approvals_remaining>1 and &action=APPROVE_TABLE %then %do;'; put '/* append to mpe_review table */'; put '%let apprno=%eval(&num_of_approvals_required-&num_of_approvals_remaining+1);'; put 'data work.append_review;'; put 'if 0 then set &mpelib..mpe_review;'; put 'TABLE_ID="&LOAD_REF";'; put 'BASE_TABLE="&orig_libds";'; put 'REVIEW_STATUS_ID="APPROVED";'; put 'REVIEWED_BY_NM="&user";'; put 'REVIEWED_ON_DTTM=&sastime;'; put 'REVIEW_REASON_TXT="APPROVAL &apprno of &num_of_approvals_required";'; put 'output;'; put 'stop;'; put 'run;'; put '%mp_lockanytable(LOCK,'; put 'lib=&mpelib,ds=mpe_review,ref=%str(&LOAD_REF Approval),'; put 'ctl_ds=&mpelib..mpe_lockanytable'; put ')'; put 'proc append base=&mpelib..mpe_review data=work.append_review;'; put 'run;'; put '%mp_lockanytable(UNLOCK,'; put 'lib=&mpelib,ds=mpe_review,'; put 'ctl_ds=&mpelib..mpe_lockanytable'; put ')'; put '/* update mpe_submit table */'; put '%mp_lockanytable(LOCK,'; put 'lib=&mpelib,ds=mpe_submit,ref=%str(&LOAD_REF Approval),'; put 'ctl_ds=&mpelib..mpe_lockanytable'; put ')'; put 'proc sql;'; put 'update &mpelib..mpe_submit'; put 'set num_of_approvals_remaining=&num_of_approvals_remaining-1,'; put 'reviewed_by_nm="&user",'; put 'reviewed_on_dttm=&sastime'; put 'where table_id="&LOAD_REF";'; put '%mp_lockanytable(UNLOCK,'; put 'lib=&mpelib,ds=mpe_submit,'; put 'ctl_ds=&mpelib..mpe_lockanytable'; put ')'; put 'data apReqd;'; put 'AUTHORISED=1;'; put 'ALREADY_UPDATED=0;'; put 'ALREADY_UPDATED_DTTM=.;'; put 'set approve1; /* js will test for NUM_OF_APPROVALS_REQUIRED */'; put 'run;'; put '%removecolsfromwork(___TMP___MD5)'; put '%webout(OPEN)'; put '%webout(OBJ,apReqd);'; put '%webout(CLOSE)'; put '%return;'; put '%end;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(syscc=&syscc entering TARGETLOADER)'; put ')'; put '%mpe_targetloader(libds=&orig_libds'; put ',now= &sastime'; put ',etlsource=&LOAD_REF'; put ',STAGING_DS=STAGING_DS'; put ',dclib=&mpelib'; put '%if &action=APPROVE_TABLE %then %do;'; put ',LOADTARGET=YES'; put '%end;'; put '%else %do;'; put ',LOADTARGET=NO'; put '%end;'; put ',dc_dttmtfmt=&dc_dttmtfmt.'; put ')'; put '%if %mf_getattrn(STAGING_DS,NLOBS)=0 %then %do;'; put '/* empty dataset! */'; put 'data out;'; put 'set STAGING_DS;'; put 'run;'; put '%return;'; put '%end;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(syscc=&syscc entering SHOWDIFFS)'; put ')'; put '%if &action=SHOW_DIFFS %then %do;'; put '/**'; put '* Now prepare the SHOW DIFFS (approve) screen'; put '*/'; put '/*To create the CURRENT diffs, we compare with the ACTUAL data. But first'; put 'need to find out what version TIME to query it for.. */'; put 'proc sql noprint;'; put 'select max(processed_dttm)-1 format=datetime19. into: tstamp'; put 'from &mpelib..mpe_dataloads'; put 'where libref="&libref" and dsn="&ds" and ETLSOURCE="&LOAD_REF";'; put 'quit;'; put '%if &tstamp=. %then %let tstamp=%sysfunc(datetime(),datetime19.);'; put '/**'; put '* now create the DIFFS dataset'; put '* If using a database, then utilise pass through!'; put '* Create a temporary table inside the database for joins..'; put '*/'; put 'options mprint;'; put '%let engine_type=%mf_getEngine(%scan(&libds,1,.));'; put '%put &libds engine type = &engine_type;'; put '%local inner_table ;'; put '%if &engine_type=OLEDB %then %do;'; put '/* generate a unique ID for the temporary table */'; put 'data _null_;'; put 'call symputx(''UNIQUE_REF'''; put ',cats(round(datetime(),1)'; put ',''_'''; put ',round(ranuni(0)*100000,1)'; put ')'; put ',''l'''; put ');'; put 'run;'; put '%let inner_table=&libref.."##DIFF_&UNIQUE_REF"n;'; put 'proc sql;'; put 'create table &inner_table as'; put 'select * from work.outds_mod;'; put '%end;'; put '%else %let inner_table=work.outds_mod;'; put 'proc sql;'; put 'create view work.originals2 as'; put 'select b.*'; put 'from &inner_table a'; put 'inner join &libds'; put '%if &loadtype=BITEMPORAL or &loadtype=TXTEMPORAL %then %do;'; put '(where=("&tstamp"dt < &VAR_TXTO))'; put '%end;'; put 'b'; put 'on 1'; put '%do idx_pk=1 %to %sysfunc(countw(&buskey));'; put '%let idx_val=%scan(&buskey,&idx_pk);'; put 'and a.&idx_val=b.&idx_val'; put '%end;'; put 'order by %mf_getquotedstr(in_str=&buskey,dlm=%str(,),quote=)'; put ';'; put 'create view bitemp5c_updates2 as'; put 'select * from work.outds_mod'; put 'order by %mf_getquotedstr(in_str=&buskey,dlm=%str(,),quote=)'; put ';'; put 'data; set &libds;stop;run;'; put '%let emptybasetable=&syslast;'; put 'options varlenchk=nowarn; /* for small numerics (<8) */'; put '%quickmacro(work.outds_del,deleted)'; put '%quickmacro(work.outds_add,new)'; put '%quickmacro(bitemp5c_updates2,updates)'; put '%quickmacro(originals2,originals)'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(syscc=&syscc in quickmacro)'; put ')'; put '/* extract colnames for md5 creation / change tracking */'; put 'proc contents noprint data=work.updates'; put 'out=cols (keep=name type length varnum format);'; put 'run;'; put 'proc sort data=cols out=cols(drop=varnum); by varnum;run;'; put 'data cols; set cols; name=upcase(name);run;'; put '%let tempDIFFS_CSV=tempDiffs_%trim('; put '%sysfunc(datetime(),flatdate.)).csv;'; put '/**'; put '* Store temp tables so we have a record of diffs'; put '* do not change this libname or table name as it is used in some'; put '* post approve hooks'; put '*/'; put 'data TEMPDIFFS (compress=no) /* for realistic file size */;'; put 'length _____status $10;'; put 'set work.deleted (in=_____del)'; put 'work.new(in=_____new)'; put 'work.updates (in=_____upd)'; put 'work.originals2 (in=_____orig);'; put 'if _____del then _____status=''DELETED '';'; put 'else if _____new then _____status=''NEW'';'; put 'else if _____upd then _____status=''UPDATED'';'; put 'else if _____orig then _____status=''ORIGINAL'';'; put 'run;'; put 'proc export data=TEMPDIFFS dbms=csv replace'; put 'outfile="&mpelocapprovals/&LOAD_REF/&tempDIFFS_CSV" ;'; put 'run;'; put 'proc sql noprint;'; put 'select filesize format=sizekmg10.1, filesize as filesize_raw'; put 'into: filesize,:filesize_raw'; put 'from dictionary.tables'; put 'where libname=''WORK'' and memtype=''DATA'' and memname=''TEMPDIFFS'';'; put 'data params;'; put 'set approve1;'; put 'DIFFS_CSV="&tempDIFFS_CSV";'; put 'FILESIZE="&filesize";'; put 'FILESIZE_RAW=&filesize_raw;'; put 'if %mf_nobs(work.originals)>&maxdiff'; put 'or %mf_nobs(work.new)>&maxdiff'; put 'or %mf_nobs(work.deleted)>&maxdiff'; put 'or %mf_nobs(work.updates)>&maxdiff'; put 'then TRUNCATED="YES";'; put 'else TRUNCATED="NO";'; put 'NUM_ADDED=%mf_getattrn(work.new,NLOBS);'; put 'NUM_DELETED=%mf_getattrn(work.deleted,NLOBS);'; put 'NUM_UPDATED=%mf_getattrn(work.updates,NLOBS);'; put 'SUBMITTED_ON=put(submitted_on_dttm,datetime19.);'; put '%if %mf_getattrn(work.authAPP,NLOBS)>0 %then %do;'; put 'ISAPPROVER=''YES'';'; put '%end;'; put '%else %do;'; put 'ISAPPROVER=''NO'';'; put '%end;'; put 'run;'; put '/*'; put '* The PRE_APPROVE_HOOK may have applied custom formats to the staged table.'; put '* To ensure consistency in the DIFF screen, we should apply the same formats'; put '* to the base table. Limit rows at the same time.'; put '*/'; put 'data work.originals;'; put 'if 0 then set deleted new updates;'; put 'set work.originals;'; put 'if _n_>&maxdiff then stop;'; put 'run;'; put '/* get additional submits against the same base table */'; put 'proc sort data=&mpelib..mpe_submit(where=('; put 'submit_status_cd=''SUBMITTED'''; put 'and cats(base_lib,''.'',base_ds)="&orig_libds"'; put 'and table_id ne "&LOAD_REF"'; put ')) out=submits;'; put 'by descending submitted_on_dttm;'; put 'run;'; put '/* filter last 10 */'; put 'data submits;'; put 'set submits;'; put 'if _n_>10 then stop;'; put 'run;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(syscc=&syscc SHOWDIFFS prior to streamout)'; put ')'; put '%removecolsfromwork(___TMP___MD5)'; put '%webout(OPEN)'; put '%webout(OBJ,params)'; put '%webout(OBJ,cols)'; put '%webout(OBJ,submits)'; put '%webout(OBJ,deleted,fmt=N,missing=STRING,maxobs=&maxdiff)'; put '%webout(OBJ,new,fmt=N,missing=STRING,maxobs=&maxdiff)'; put '%webout(OBJ,updates,fmt=N,missing=STRING,maxobs=&maxdiff)'; put '%webout(OBJ,ORIGINALS,fmt=N,missing=STRING)'; put '/* need same for formatted view */'; put '%webout(OBJ,deleted,dslabel=fmt_deleted,fmt=Y,missing=STRING,maxobs=&maxdiff)'; put '%webout(OBJ,new,dslabel=fmt_new,fmt=Y,missing=STRING,maxobs=&maxdiff)'; put '%webout(OBJ,updates,dslabel=fmt_updates,fmt=Y,missing=STRING,maxobs=&maxdiff)'; put '%webout(OBJ,originals,dslabel=fmt_ORIGINALS,fmt=Y,missing=STRING)'; put '%webout(CLOSE)'; put '%if &engine_type=OLEDB %then %do;'; put 'proc sql; /* needs to be dropped AFTER view execution */'; put 'drop table &inner_table;'; put '%end;'; put '%return;'; put '%end;'; put '%if &action=APPROVE_TABLE %then %do;'; put '%approve:'; put '/**'; put '* store temp tables so we have a record of diffs'; put '* do not change this libname or table name as it is used in some'; put '* post approve hooks'; put '* for REPLACE loads, temp tables not made, so make them'; put '*/'; put '%if &LOADTYPE=REPLACE %then %do;'; put 'data work.outds_add; run;'; put 'data work.outds_mod; run;'; put 'data work.outds_del; run;'; put '%end;'; put 'libname approve "&mpelocapprovals/&LOAD_REF";'; put 'data; set &libds;stop;run;'; put '%let emptybasetable=&syslast;'; put 'data approve.ActualDiffs;'; put 'length _____STATUS_____ $10;'; put 'if 0 then set &emptybasetable;'; put 'set work.outds_del (in=_____del)'; put 'work.outds_add (in=_____new)'; put 'work.outds_mod (in=_____upd);'; put 'if _____del then _____STATUS_____=''DELETED'';'; put 'else if _____new then _____STATUS_____=''NEW'';'; put 'else if _____upd then _____STATUS_____=''UPDATED'';'; put '%if %mf_existvar(&libds,&var_txfrom) %then %do;'; put 'drop &var_txfrom &var_txto;'; put '%end;'; put '%if %mf_existvar(&libds,&VAR_PROCESSED) %then %do;'; put 'drop &VAR_PROCESSED;'; put '%end;'; put 'run;'; put 'proc export data=approve.ActualDiffs'; put 'outfile="&mpelocapprovals/&LOAD_REF/ActualDiffs.csv"'; put 'dbms=csv'; put 'replace;'; put 'run;'; put '/* update the control table to show table as approved */'; put '/* append to mpe_review table */'; put '%let apprno=%eval(&num_of_approvals_required-&num_of_approvals_remaining+1);'; put 'data work.append_review;'; put 'if 0 then set &mpelib..mpe_review;'; put 'TABLE_ID="&LOAD_REF";'; put 'BASE_TABLE="&orig_libds";'; put 'REVIEW_STATUS_ID="APPROVED";'; put 'REVIEWED_BY_NM="&user";'; put 'REVIEWED_ON_DTTM=&sastime;'; put 'REVIEW_REASON_TXT="APPROVAL &apprno of &num_of_approvals_required";'; put 'output;'; put 'stop;'; put 'run;'; put '%mp_lockanytable(LOCK,'; put 'lib=&mpelib,ds=mpe_review,ref=%str(&LOAD_REF Approval),'; put 'ctl_ds=&mpelib..mpe_lockanytable'; put ')'; put 'proc append base=&mpelib..mpe_review data=work.append_review;'; put 'run;'; put '%mp_lockanytable(UNLOCK,'; put 'lib=&mpelib,ds=mpe_review,'; put 'ctl_ds=&mpelib..mpe_lockanytable'; put ')'; put '/* update mpe_submit table */'; put '%mp_lockanytable(LOCK,'; put 'lib=&mpelib,ds=mpe_submit,ref=%str(&LOAD_REF Approval in auditors/postdata),'; put 'ctl_ds=&mpelib..mpe_lockanytable'; put ')'; put 'proc sql;'; put 'update &mpelib..mpe_submit'; put 'set submit_status_cd=''APPROVED'','; put 'num_of_approvals_remaining=&num_of_approvals_remaining-1,'; put 'reviewed_by_nm="&user",'; put 'reviewed_on_dttm=&sastime'; put 'where table_id="&LOAD_REF";'; put '%mp_lockanytable(UNLOCK,'; put 'lib=&mpelib,ds=mpe_submit,'; put 'ctl_ds=&mpelib..mpe_lockanytable'; put ')'; put '/* run post-approve hook */'; put '%mpe_runhook(POST_APPROVE_HOOK)'; put 'data apPARAMS;'; put 'AUTHORISED=1;'; put 'ALREADY_UPDATED=0;'; put 'ALREADY_UPDATED_DTTM=.;'; put 'DIFFTIME="&difftime";'; put 'if &syscc=0 then RESPONSE=''SUCCESS!'';'; put 'else response="SYSCC=&syscc.";'; put 'run;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program 582'; put ',msg=%superq(msg)'; put ')'; put '%mpe_alerts(alert_event=APPROVED'; put ', alert_lib=&libref'; put ', alert_ds=&ds'; put ', dsid=&LOAD_REF'; put ')'; put '%removecolsfromwork(___TMP___MD5)'; put '%webout(OPEN)'; put '%webout(OBJ,apPARAMS)'; put '%webout(CLOSE)'; put '%return;'; put '%end;'; put '%mend postdata;'; put '%postdata()'; put '%mp_abort(mode=INCLUDE)'; put '%mp_abort(iftrue= (&is_err=1)'; put ',mac=&_program'; put ',msg=%superq(err_msg)'; put ')'; put '%mpeterm()'; put '* Service end;'; run; %mm_createwebservice(path=&appLoc/&path, name=&service, code=sascode, server=&serverName, replace=yes) filename sascode clear; %let path=services/editors; %let service=getdata; filename sascode temp lrecl=32767; data _null_; file sascode; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '/**'; put '@file mp_jsonout.sas'; put '@brief Writes JSON in SASjs format to a fileref'; put '@details This macro can be used to OPEN a JSON stream and send one or more'; put 'tables as arrays of rows, where each row can be an object or a nested array.'; put 'There are two engines available - DATASTEP or PROCJSON.'; put 'PROC JSON is fast but will produce errs like the ones below if'; put 'special chars are encountered.'; put '> (ERR)OR: Some code points did not transcode.'; put '> An object or array close is not valid at this point in the JSON text.'; put '> Date value out of range'; put 'If this happens, try running with ENGINE=DATASTEP.'; put 'The DATASTEP engine is used to handle special SAS missing numerics, and'; put 'can also convert entire datasets to formatted values. Output JSON is always'; put 'in UTF-8.'; put 'Usage:'; put 'filename tmp temp;'; put 'data class; set sashelp.class;run;'; put '%mp_jsonout(OPEN,jref=tmp)'; put '%mp_jsonout(OBJ,class,jref=tmp)'; put '%mp_jsonout(OBJ,class,dslabel=class2,jref=tmp,showmeta=Y)'; put '%mp_jsonout(CLOSE,jref=tmp)'; put 'data _null_;'; put 'infile tmp;'; put 'input;putlog _infile_;'; put 'run;'; put 'If you are building web apps with SAS then you are strongly encouraged to use'; put 'the mX_createwebservice macros in combination with the'; put '[sasjs adapter](https://github.com/sasjs/adapter).'; put 'For more information see https://sasjs.io'; put '@param [in] action Valid values:'; put '@li OPEN - opens the JSON'; put '@li OBJ - sends a table with each row as an object'; put '@li ARR - sends a table with each row in an array'; put '@li CLOSE - closes the JSON'; put '@param [in] ds The dataset to send. Must be a work table.'; put '@param [out] jref= (_webout) The fileref to which to send the JSON'; put '@param [out] dslabel= The name to give the table in the exported JSON'; put '@param [in] fmt= (Y) Whether to keep (Y) or strip (N) formats from the table'; put '@param [in] engine= (DATASTEP) Which engine to use to send the JSON. Options:'; put '@li PROCJSON (default)'; put '@li DATASTEP (more reliable when data has non standard characters)'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to JSON'; put '

Related Files

'; put '@li mp_ds2fmtds.sas'; put '@version 9.2'; put '@author Allan Bowe'; put '@source https://github.com/sasjs/core'; put '**/'; put '%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y'; put ',engine=DATASTEP'; put ',missing=NULL'; put ',showmeta=N'; put ',maxobs=MAX'; put ')/*/STORE SOURCE*/;'; put '%local tempds colinfo fmtds i numcols numobs stmt_obs lastobs optval'; put 'tmpds1 tmpds2 tmpds3 tmpds4;'; put '%let numcols=0;'; put '%if &maxobs ne MAX %then %let stmt_obs=%str(if _n_>&maxobs then stop;);'; put '%if &action=OPEN %then %do;'; put 'options nobomfile;'; put 'data _null_;file &jref encoding=''utf-8'' lrecl=200;'; put 'put ''{"PROCESSED_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''"'';'; put 'run;'; put '%end;'; put '%else %if (&action=ARR or &action=OBJ) %then %do;'; put '/* force variable names to always be uppercase in the JSON */'; put 'options validvarname=upcase;'; put '/* To avoid issues with _webout on EBI - such as encoding diffs and truncation'; put '(https://support.sas.com/kb/49/325.html) we use temporary files */'; put 'filename _sjs1 temp lrecl=200 ;'; put 'data _null_; file _sjs1 encoding=''utf-8'';'; put 'put ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":";'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjs1 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs1 clear;'; put '/* grab col defs */'; put 'proc contents noprint data=&ds'; put 'out=_data_(keep=name type length format formatl formatd varnum label);'; put 'run;'; put '%let colinfo=%scan(&syslast,2,.);'; put 'proc sort data=&colinfo;'; put 'by varnum;'; put 'run;'; put '/* move meta to mac vars */'; put 'data &colinfo;'; put 'if _n_=1 then call symputx(''numcols'',nobs,''l'');'; put 'set &colinfo end=last nobs=nobs;'; put 'name=upcase(name);'; put '/* fix formats */'; put 'if type=2 or type=6 then do;'; put 'typelong=''char'';'; put 'length fmt $49.;'; put 'if format='''' then fmt=cats(''$'',length,''.'');'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else fmt=cats(format,formatl,''.'');'; put 'end;'; put 'else do;'; put 'typelong=''num'';'; put 'if format='''' then fmt=''best.'';'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else if formatd=0 then fmt=cats(format,formatl,''.'');'; put 'else fmt=cats(format,formatl,''.'',formatd);'; put 'end;'; put '/* 32 char unique name */'; put 'newname=''sasjs''!!substr(cats(put(md5(name),$hex32.)),1,27);'; put 'call symputx(cats(''name'',_n_),name,''l'');'; put 'call symputx(cats(''newname'',_n_),newname,''l'');'; put 'call symputx(cats(''length'',_n_),length,''l'');'; put 'call symputx(cats(''fmt'',_n_),fmt,''l'');'; put 'call symputx(cats(''type'',_n_),type,''l'');'; put 'call symputx(cats(''typelong'',_n_),typelong,''l'');'; put 'call symputx(cats(''label'',_n_),coalescec(label,name),''l'');'; put '/* overwritten when fmt=Y and a custom format exists in catalog */'; put 'if typelong=''num'' then call symputx(cats(''fmtlen'',_n_),200,''l'');'; put 'else call symputx(cats(''fmtlen'',_n_),min(32767,ceil((length+10)*1.5)),''l'');'; put 'run;'; put '%let tempds=%substr(_%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql;'; put 'select count(*) into: lastobs from &ds;'; put '%if &maxobs ne MAX %then %let lastobs=%sysfunc(min(&lastobs,&maxobs));'; put '%if &engine=PROCJSON %then %do;'; put '%if &missing=STRING %then %do;'; put '%put &sysmacroname: Special Missings not supported in proc json.;'; put '%put &sysmacroname: Switching to DATASTEP engine;'; put '%goto datastep;'; put '%end;'; put 'data &tempds;'; put 'set &ds;'; put '&stmt_obs;'; put '%if &fmt=N %then format _numeric_ best32.;;'; put '/* PRETTY is necessary to avoid line truncation in large files */'; put 'filename _sjs2 temp lrecl=131068 encoding=''utf-8'';'; put 'proc json out=_sjs2 pretty'; put '%if &action=ARR %then nokeys ;'; put ';export &tempds / nosastags fmtnumeric;'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs2 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs2 clear;'; put '%end;'; put '%else %if &engine=DATASTEP %then %do;'; put '%datastep:'; put '%if %sysfunc(exist(&ds)) ne 1 & %sysfunc(exist(&ds,VIEW)) ne 1'; put '%then %do;'; put '%put &sysmacroname: &ds NOT FOUND!!!;'; put '%return;'; put '%end;'; put '%if &fmt=Y %then %do;'; put '/**'; put '* Extract format definitions'; put '* First, by getting library locations from dictionary.formats'; put '* Then, by exporting the width using proc format'; put '* Cannot use maxw from sashelp.vformat as not always populated'; put '* Cannot use fmtinfo() as not supported in all flavours'; put '*/'; put '%let tmpds1=%substr(fmtsum%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds2=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds3=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds4=%substr(col%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql noprint;'; put 'create table &tmpds1 as'; put 'select cats(libname,''.'',memname) as FMTCAT,'; put 'FMTNAME'; put 'from dictionary.formats'; put 'where fmttype=''F'' and libname is not null'; put 'and fmtname in (select format from &colinfo where format is not null)'; put 'order by 1;'; put 'create table &tmpds2('; put 'FMTNAME char(32),'; put 'LENGTH num'; put ');'; put '%local catlist cat fmtlist i;'; put 'select distinct fmtcat into: catlist separated by '' '' from &tmpds1;'; put '%do i=1 %to %sysfunc(countw(&catlist,%str( )));'; put '%let cat=%scan(&catlist,&i,%str( ));'; put 'proc sql;'; put 'select distinct fmtname into: fmtlist separated by '' '''; put 'from &tmpds1 where fmtcat="&cat";'; put 'proc format lib=&cat cntlout=&tmpds3(keep=fmtname length);'; put 'select &fmtlist;'; put 'run;'; put 'proc sql;'; put 'insert into &tmpds2 select distinct fmtname,length from &tmpds3;'; put '%end;'; put 'proc sql;'; put 'create table &tmpds4 as'; put 'select a.*, b.length as MAXW'; put 'from &colinfo a'; put 'left join &tmpds2 b'; put 'on cats(a.format)=cats(upcase(b.fmtname))'; put 'order by a.varnum;'; put 'data _null_;'; put 'set &tmpds4;'; put 'if not missing(maxw);'; put 'call symputx('; put 'cats(''fmtlen'',_n_),'; put '/* vars need extra padding due to JSON escaping of special chars */'; put 'min(32767,ceil((max(length,maxw)+10)*1.5))'; put ',''l'''; put ');'; put 'run;'; put '/* configure varlenchk - as we are explicitly shortening the variables */'; put '%let optval=%sysfunc(getoption(varlenchk));'; put 'options varlenchk=NOWARN;'; put 'data _data_(compress=char);'; put '/* shorten the new vars */'; put 'length'; put '%do i=1 %to &numcols;'; put '&&name&i $&&fmtlen&i'; put '%end;'; put ';'; put '/* rename on entry */'; put 'set &ds(rename=('; put '%do i=1 %to &numcols;'; put '&&name&i=&&newname&i'; put '%end;'; put '));'; put '&stmt_obs;'; put 'drop'; put '%do i=1 %to &numcols;'; put '&&newname&i'; put '%end;'; put ';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=num %then %do;'; put '&&name&i=cats(put(&&newname&i,&&fmt&i));'; put '%end;'; put '%else %do;'; put '&&name&i=put(&&newname&i,&&fmt&i);'; put '%end;'; put '%end;'; put 'if _error_ then do;'; put 'call symputx(''syscc'',1012);'; put 'stop;'; put 'end;'; put 'run;'; put '%let fmtds=&syslast;'; put 'options varlenchk=&optval;'; put '%end;'; put 'proc format; /* credit yabwon for special null removal */'; put 'value bart (default=40)'; put '%if &missing=NULL %then %do;'; put '._ - .z = null'; put '%end;'; put '%else %do;'; put '._ = [quote()]'; put '. = null'; put '.a - .z = [quote()]'; put '%end;'; put 'other = [best.];'; put 'data &tempds;'; put 'attrib _all_ label='''';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'length &&name&i $&&fmtlen&i...;'; put 'format &&name&i $&&fmtlen&i...;'; put '%end;'; put '%end;'; put '%if &fmt=Y %then %do;'; put 'set &fmtds;'; put '%end;'; put '%else %do;'; put 'set &ds;'; put '%end;'; put '&stmt_obs;'; put 'format _numeric_ bart.;'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'if findc(&&name&i,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put '&&name&i=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,&&name&i)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else &&name&i=quote(cats(&&name&i));'; put '%end;'; put '%end;'; put 'run;'; put 'filename _sjs3 temp lrecl=131068 ;'; put 'data _null_;'; put 'file _sjs3 encoding=''utf-8'';'; put 'if _n_=1 then put "[";'; put 'set &tempds;'; put 'if _n_>1 then put "," @; put'; put '%if &action=ARR %then "[" ; %else "{" ;'; put '%do i=1 %to &numcols;'; put '%if &i>1 %then "," ;'; put '%if &action=OBJ %then """&&name&i"":" ;'; put '"&&name&i"n /* name literal for reserved variable names */'; put '%end;'; put '%if &action=ARR %then "]" ; %else "}" ; ;'; put '/* close out the table */'; put 'data _null_;'; put 'file _sjs3 mod encoding=''utf-8'';'; put 'put '']'';'; put 'run;'; put 'data _null_;'; put 'infile _sjs3 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs3 clear;'; put '%end;'; put 'proc sql;'; put 'drop table &colinfo, &tempds;'; put '%if %substr(&showmeta,1,1)=Y %then %do;'; put 'filename _sjs4 temp lrecl=131068 encoding=''utf-8'';'; put 'data _null_;'; put 'file _sjs4;'; put 'length label $350;'; put 'put ", ""$%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":{""vars"":{";'; put 'do i=1 to &numcols;'; put 'name=quote(trim(symget(cats(''name'',i))));'; put 'format=quote(trim(symget(cats(''fmt'',i))));'; put 'label=quote(prxchange(''s/\\/\\\\/'',-1,trim(symget(cats(''label'',i)))));'; put 'length=quote(trim(symget(cats(''length'',i))));'; put 'type=quote(trim(symget(cats(''typelong'',i))));'; put 'if i>1 then put "," @@;'; put 'put name '':{"format":'' format '',"label":'' label'; put ''',"length":'' length '',"type":'' type ''}'';'; put 'end;'; put 'put ''}}'';'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs4 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs4 clear;'; put '%end;'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put 'data _null_; file &jref encoding=''utf-8'' mod ;'; put 'put "}";'; put 'run;'; put '%end;'; put '%mend mp_jsonout;'; put '/**'; put '@file mm_webout.sas'; put '@brief Send data to/from SAS Stored Processes'; put '@details This macro should be added to the start of each Stored Process,'; put '**immediately** followed by a call to:'; put '%mm_webout(FETCH)'; put 'This will read all the input data and create same-named SAS datasets in the'; put 'WORK library. You can then insert your code, and send data back using the'; put 'following syntax:'; put 'data some datasets; * make some data ;'; put 'retain some columns;'; put 'run;'; put '%mm_webout(OPEN)'; put '%mm_webout(ARR,some) * Array format, fast, suitable for large tables ;'; put '%mm_webout(OBJ,datasets) * Object format, easier to work with ;'; put 'Finally, wrap everything up send some helpful system variables too'; put '%mm_webout(CLOSE)'; put '@param [in] action Either FETCH, OPEN, ARR, OBJ or CLOSE'; put '@param [in] ds The dataset to send back to the frontend'; put '@param [out] dslabel= Value to use instead of table name for sending to JSON'; put '@param [in] fmt= (N) Setting Y converts all vars to their formatted values'; put '@param [out] fref= (_webout) The fileref to which to write the JSON'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] workobs= (0) When set to a positive integer, will create a new'; put 'output object (WORK) which contains this number of observations from all'; put 'tables in the WORK library.'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to output JSON'; put '

SAS Macros

'; put '@li mp_jsonout.sas'; put '

Related Macros

'; put '@li ms_webout.sas'; put '@li mv_webout.sas'; put '@version 9.3'; put '@author Allan Bowe'; put '**/'; put '%macro mm_webout(action,ds,dslabel=,fref=_webout,fmt=N,missing=NULL'; put ',showmeta=N,maxobs=MAX,workobs=0'; put ');'; put '%global _webin_file_count _webin_fileref1 _webin_name1 _program _debug'; put 'sasjs_tables;'; put '%local i tempds jsonengine;'; put '/* see https://github.com/sasjs/core/issues/41 */'; put '%if "%upcase(&SYSENCODING)" ne "UTF-8" %then %let jsonengine=PROCJSON;'; put '%else %let jsonengine=DATASTEP;'; put '%if &action=FETCH %then %do;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'options mprint notes mprintnest;'; put '%end;'; put '%let _webin_file_count=%eval(&_webin_file_count+0);'; put '/* now read in the data */'; put '%do i=1 %to &_webin_file_count;'; put '%if &_webin_file_count=1 %then %do;'; put '%let _webin_fileref1=&_webin_fileref;'; put '%let _webin_name1=&_webin_name;'; put '%end;'; put 'data _null_;'; put 'infile &&_webin_fileref&i termstr=crlf;'; put 'input;'; put 'call symputx(''input_statement'',_infile_);'; put 'putlog "&&_webin_name&i input statement: " _infile_;'; put 'stop;'; put 'data &&_webin_name&i;'; put 'infile &&_webin_fileref&i firstobs=2 dsd termstr=crlf encoding=''utf-8'';'; put 'input &input_statement;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'if _n_<20 then putlog _infile_;'; put '%end;'; put 'run;'; put '%let sasjs_tables=&sasjs_tables &&_webin_name&i;'; put '%end;'; put '%end;'; put '%else %if &action=OPEN %then %do;'; put '/* fix encoding */'; put 'OPTIONS NOBOMFILE;'; put '/**'; put '* check xengine type to avoid the below err message:'; put '* > Function is only valid for filerefs using the CACHE access method.'; put '*/'; put 'data _null_;'; put 'set sashelp.vextfl(where=(fileref="_WEBOUT"));'; put 'if xengine=''STREAM'' then do;'; put 'rc=stpsrv_header(''Content-type'',"text/html; encoding=utf-8");'; put 'end;'; put 'run;'; put '/* setup json */'; put 'data _null_;file &fref encoding=''utf-8'';'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutBEGIN<<'';'; put '%end;'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'run;'; put '%end;'; put '%else %if &action=ARR or &action=OBJ %then %do;'; put '%mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt,jref=&fref'; put ',engine=&jsonengine,missing=&missing,showmeta=&showmeta,maxobs=&maxobs'; put ')'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put '/* To avoid issues with _webout on EBI we use a temporary file */'; put 'filename _sjsref temp lrecl=131068;'; put '%if %str(&workobs) > 0 %then %do;'; put '/* if debug mode, send back first XX records of each work table also */'; put 'data;run;%let tempds=%scan(&syslast,2,.);'; put 'ods output Members=&tempds;'; put 'proc datasets library=WORK memtype=data;'; put '%local wtcnt;%let wtcnt=0;'; put 'data _null_;'; put 'set &tempds;'; put 'if not (upcase(name) =:"DATA"); /* ignore temp datasets */'; put 'i+1;'; put 'call symputx(cats(''wt'',i),name,''l'');'; put 'call symputx(''wtcnt'',i,''l'');'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put ",""WORK"":{";'; put '%do i=1 %to &wtcnt;'; put '%let wt=&&wt&i;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'dsid=open("WORK.&wt",''is'');'; put 'nlobs=attrn(dsid,''NLOBS'');'; put 'nvars=attrn(dsid,''NVARS'');'; put 'rc=close(dsid);'; put 'if &i>1 then put '',''@;'; put 'put " ""&wt"" : {";'; put 'put ''"nlobs":'' nlobs;'; put 'put '',"nvars":'' nvars;'; put '%mp_jsonout(OBJ,&wt,jref=_sjsref,dslabel=first10rows,showmeta=Y'; put ',maxobs=&workobs'; put ')'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put '%end;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put 'run;'; put '%end;'; put '/* close off json */'; put 'data _null_;file _sjsref mod encoding=''utf-8'';'; put 'length SYSPROCESSNAME syserrortext syswarningtext autoexec $512;'; put 'put ",""_DEBUG"" : ""&_debug"" ";'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'autoexec=quote(urlencode(trim(getoption(''autoexec''))));'; put 'put '',"AUTOEXEC" : '' autoexec;'; put 'put ",""MF_GETUSER"" : ""%mf_getuser()"" ";'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'put ",""SYSENCODING"" : ""&sysencoding"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSPROCESSID"" : ""&SYSPROCESSID"" ";'; put 'put ",""SYSPROCESSMODE"" : ""&SYSPROCESSMODE"" ";'; put 'SYSPROCESSNAME=quote(urlencode(cats(SYSPROCESSNAME)));'; put 'put ",""SYSPROCESSNAME"" : " SYSPROCESSNAME;'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put '',"SYSWARNINGTEXT" : '' syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'length memsize $32;'; put 'memsize="%sysfunc(INPUTN(%sysfunc(getoption(memsize)), best.),sizekmg.)";'; put 'memsize=quote(cats(memsize));'; put 'put '',"MEMSIZE" : '' memsize;'; put 'put "}" @;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutEND<<'';'; put '%end;'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjsref lrecl=1 recfm=n;'; put 'file &fref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjsref clear;'; put '%end;'; put '%mend mm_webout;'; put '%macro webout(action,ds,dslabel=,fmt=,missing=NULL,showmeta=NO,maxobs=MAX);'; put '%mm_webout(&action,ds=&ds,dslabel=&dslabel,fmt=&fmt'; put ',missing=&missing'; put ',showmeta=&showmeta'; put ',maxobs=&maxobs'; put ') %mend;'; put '/* provide additional debug info */'; put '%global _program;'; put '%put &=syscc;'; put '%put user=%mf_getuser();'; put '%put pgm=&_program;'; put '%put timestamp=%sysfunc(datetime(),datetime19.);'; put '* Service Variables start;'; put '* Service Variables end;'; put '* SAS Macros start;'; put '%macro mf_getapploc(pgm);'; put '%if "&pgm"="" %then %do;'; put '%if %symexist(_program) %then %let pgm=&_program;'; put '%else %do;'; put '%put &sysmacroname: No value provided and no _program variable available;'; put '%return;'; put '%end;'; put '%end;'; put '%local root;'; put '/**'; put '* First check we are not in the tests/macros folder (which has no subfolders)'; put '* or specifically in the testsetup or testteardown services'; put '*/'; put '%if %index(&pgm,/tests/macros/)'; put 'or %index(&pgm,/tests/testsetup)'; put 'or %index(&pgm,/tests/testteardown)'; put '%then %do;'; put '%let root=%substr(&pgm,1,%index(&pgm,/tests)-1);'; put '&root'; put '%return;'; put '%end;'; put '/**'; put '* Next, move up two levels to avoid matches on subfolder or service name'; put '*/'; put '%let root=%substr(&pgm,1,%length(&pgm)-%length(%scan(&pgm,-1,/))-1);'; put '%let root=%substr(&root,1,%length(&root)-%length(%scan(&root,-1,/))-1);'; put '%if %index(&root,/tests/) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/tests/)-1);'; put '%end;'; put '%else %if %index(&root,/services) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/services)-1);'; put '%end;'; put '%else %if %index(&root,/jobs) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/jobs)-1);'; put '%end;'; put '%else %put &sysmacroname: Could not find an app location from &pgm;'; put '&root'; put '%mend mf_getapploc ;'; put '%macro mf_getuniquefileref(prefix=_,maxtries=1000,lrecl=32767);'; put '%local rc fname;'; put '%if &prefix=0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%end;'; put '%else %do;'; put '%local x len;'; put '%let len=%eval(8-%length(&prefix));'; put '%let x=0;'; put '%do x=0 %to &maxtries;'; put '%let fname=&prefix%substr(%sysfunc(ranuni(0)),3,&len);'; put '%if %sysfunc(fileref(&fname)) > 0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%return;'; put '%end;'; put '%end;'; put '%put unable to find available fileref after &maxtries attempts;'; put '%end;'; put '%mend mf_getuniquefileref;'; put '%macro mp_abort(mac=mp_abort.sas, type=, msg=, iftrue=%str(1=1)'; put ', errds=work.mp_abort_errds'; put ', mode=REGULAR'; put ')/*/STORE SOURCE*/;'; put '%global sysprocessmode sysprocessname sasjs_stpsrv_header_loc sasjsprocessmode;'; put '%local fref fid i;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%put NOTE: /// mp_abort macro executing //;'; put '%if %length(&mac)>0 %then %put NOTE- called by &mac;'; put '%put NOTE - &msg;'; put '%if %symexist(_SYSINCLUDEFILEDEVICE)'; put '/* abort cancel FILE does not restart outside the INCLUDE on Viya 3.5 */'; put 'and %superq(SYSPROCESSNAME) ne %str(Compute Server)'; put '%then %do;'; put '%if "*&_SYSINCLUDEFILEDEVICE*" ne "**" %then %do;'; put 'data &errds;'; put 'iftrue=''1=1'';'; put 'length mac $100 msg $5000;'; put 'mac=symget(''mac'');'; put 'msg=symget(''msg'');'; put 'run;'; put 'data _null_;'; put 'abort cancel FILE;'; put 'run;'; put '%return;'; put '%end;'; put '%end;'; put '/* Web App Context */'; put '%if %symexist(_PROGRAM)'; put 'or %superq(SYSPROCESSNAME) = %str(Compute Server)'; put 'or &mode=INCLUDE'; put '%then %do;'; put 'options obs=max replace mprint;'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5"'; put '%then %do;'; put 'options nosyntaxcheck;'; put '%end;'; put '%if &mode=INCLUDE %then %do;'; put '%if %sysfunc(exist(&errds))=1 %then %do;'; put 'data _null_;'; put 'set &errds;'; put 'call symputx(''iftrue'',iftrue,''l'');'; put 'call symputx(''mac'',mac,''l'');'; put 'call symputx(''msg'',msg,''l'');'; put 'putlog (_all_)(=);'; put 'run;'; put '%if (&iftrue)=0 %then %return;'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: No include errors found;'; put '%return;'; put '%end;'; put '%end;'; put '/* extract log errs / warns, if exist */'; put '%local logloc logline;'; put '%global logmsg; /* capture global messages */'; put '%if %symexist(SYSPRINTTOLOG) %then %let logloc=&SYSPRINTTOLOG;'; put '%else %let logloc=%qsysfunc(getoption(LOG));'; put 'proc printto log=log;run;'; put '%let logline=0;'; put '%if %length(&logloc)>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input; putlog _infile_;'; put 'i=1;'; put 'retain logonce 0;'; put 'if ('; put '_infile_=:"%str(WARN)ING" or _infile_=:"%str(ERR)OR"'; put ') and logonce=0 then'; put 'do;'; put 'call symputx(''logline'',_n_);'; put 'logonce+1;'; put 'end;'; put 'run;'; put '/* capture log including lines BEFORE the err */'; put '%if &logline>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input;'; put 'i=1;'; put 'stoploop=0;'; put 'if _n_ ge &logline-15 and stoploop=0 then do until (i>22);'; put 'call symputx(''logmsg'',catx(''\n'',symget(''logmsg''),_infile_));'; put 'input;'; put 'i+1;'; put 'stoploop=1;'; put 'end;'; put 'if stoploop=1 then stop;'; put 'run;'; put '%end;'; put '%end;'; put '%if %symexist(SYS_JES_JOB_URI) %then %do;'; put '/* setup webout for Viya */'; put 'options nobomfile;'; put '%if "X&SYS_JES_JOB_URI.X"="XX" %then %do;'; put 'filename _webout temp lrecl=999999 mod;'; put '%end;'; put '%else %do;'; put 'filename _webout filesrvc parenturi="&SYS_JES_JOB_URI"'; put 'name="_webout.json" lrecl=999999 mod;'; put '%end;'; put '%end;'; put '%else %if %sysfunc(filename(fref,&sasjs_stpsrv_header_loc))=0 %then %do;'; put 'options nobomfile;'; put '/* set up http header for SASjs Server */'; put '%let fid=%sysfunc(fopen(&fref,A));'; put '%if &fid=0 %then %do;'; put '%put %str(ERR)OR: %sysfunc(sysmsg());'; put '%return;'; put '%end;'; put '%let rc=%sysfunc(fput(&fid,%str(Content-Type: application/json)));'; put '%let rc=%sysfunc(fwrite(&fid));'; put '%let rc=%sysfunc(fclose(&fid));'; put '%let rc=%sysfunc(filename(&fref));'; put '%end;'; put '/* send response in SASjs JSON format */'; put 'data _null_;'; put 'file _webout mod lrecl=32000 encoding=''utf-8'';'; put 'length msg syswarningtext syserrortext $32767 mode $10 ;'; put 'sasdatetime=datetime();'; put 'msg=symget(''msg'');'; put '%if &logline>0 %then %do;'; put 'msg=cats(msg,''\n\nLog Extract:\n'',symget(''logmsg''));'; put '%end;'; put '/* escape the escapes */'; put 'msg=tranwrd(msg,''\'',''\\'');'; put '/* escape the quotes */'; put 'msg=tranwrd(msg,''"'',''\"'');'; put '/* ditch the CRLFs as chrome complains */'; put 'msg=compress(msg,,''kw'');'; put '/* quote without quoting the quotes (which are escaped instead) */'; put 'msg=cats(''"'',msg,''"'');'; put 'if symexist(''_debug'') then debug=quote(trim(symget(''_debug'')));'; put 'else debug=''""'';'; put 'if symget(''sasjsprocessmode'')=''Stored Program'' then mode=''SASJS'';'; put 'if mode ne ''SASJS'' then put ''>>weboutBEGIN<<'';'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'put '',"sasjsAbort" : [{'';'; put 'put '' "MSG":'' msg ;'; put 'put '' ,"MAC": "'' "&mac" ''"}]'';'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'put '',"_DEBUG":'' debug ;'; put 'if symexist(''_metauser'') then do;'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put 'end;'; put 'if symexist(''SYS_JES_JOB_URI'') then do;'; put 'SYS_JES_JOB_URI=quote(trim(symget(''SYS_JES_JOB_URI'')));'; put 'put '',"SYS_JES_JOB_URI": '' SYS_JES_JOB_URI;'; put 'end;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put ",""SYSWARNINGTEXT"" : " syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'put "}" ;'; put 'if mode ne ''SASJS'' then put ''>>weboutEND<<'';'; put 'run;'; put '%put _all_;'; put '%if "&sysprocessmode " = "SAS Stored Process Server " %then %do;'; put 'data _null_;'; put 'putlog ''stpsrvset program err and syscc'';'; put 'rc=stpsrvset(''program error'', 0);'; put 'call symputx("syscc",0,"g");'; put 'run;'; put '%if &sysscp=WIN'; put 'and 1=0 /* deprecating this logic until we figure out a consistent abort */'; put 'and "%substr(%str(&sysvlong ),1,8)"="9.04.01M"'; put 'and "%substr(%str(&sysvlong ),9,1)">"5" %then %do;'; put '/* skip approach (below) does not work in windows m6+ envs */'; put 'endsas;'; put '%end;'; put '%else %do;'; put '/**'; put '* endsas kills 9.4m3 deployments by orphaning multibridges.'; put '* Abort variants are ungraceful (non zero return code)'; put '* This approach lets SAS run silently until the end :-)'; put '* Caution - fails when called within a %include within a macro'; put '* Use mp_include() to handle this.'; put '*/'; put 'filename skip temp;'; put 'data _null_;'; put 'file skip;'; put 'put ''%macro skip();'';'; put 'comment ''%mend skip; -> fix lint '';'; put 'put ''%macro skippy();'';'; put 'comment ''%mend skippy; -> fix lint '';'; put 'run;'; put '%inc skip;'; put '%end;'; put '%end;'; put '%else %if "&sysprocessmode " = "SAS Compute Server " %then %do;'; put '/* endsas kills the session making it harder to fetch results */'; put 'data _null_;'; put 'syswarningtext=symget(''syswarningtext'');'; put 'syserrortext=symget(''syserrortext'');'; put 'abort_msg=symget(''msg'');'; put 'syscc=symget(''syscc'');'; put 'sysuserid=symget(''sysuserid'');'; put 'iftrue=symget(''iftrue'');'; put 'put (_all_)(/=);'; put 'call symputx(''syscc'',0);'; put 'abort cancel nolist;'; put 'run;'; put '%end;'; put '%else %do;'; put '%abort cancel;'; put '%end;'; put '%end;'; put '%else %do;'; put '%put _all_;'; put '%abort cancel;'; put '%end;'; put '%mend mp_abort;'; put '/** @endcond */'; put '%macro mm_getstpcode('; put 'tree=/User Folders/sasdemo/somestp'; put ',name='; put ',outloc=0'; put ',outref=0'; put ',mDebug=1'; put ',showlog=NO'; put ');'; put '%local mD;'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing &sysmacroname..sas;'; put '%&mD.put _local_;'; put '%if %length(&name)>0 %then %let name=/&name;'; put '/* first, check if STP exists */'; put '%local tsuri;'; put '%let tsuri=stopifempty ;'; put 'data _null_;'; put 'format type uri tsuri value $200.;'; put 'call missing (of _all_);'; put 'path="&tree&name(StoredProcess)";'; put '/* first, find the STP ID */'; put 'if metadata_pathobj("",path,"StoredProcess",type,uri)>0 then do;'; put '/* get sourcecode */'; put 'cnt=1;'; put 'do while (metadata_getnasn(uri,"Notes",cnt,tsuri)>0);'; put 'rc=metadata_getattr(tsuri,"Name",value);'; put '&mD.put tsuri= value=;'; put 'if value="SourceCode" then do;'; put '/* found it! */'; put 'rc=metadata_getattr(tsuri,"Id",value);'; put 'call symputx(''tsuri'',value,''l'');'; put 'stop;'; put 'end;'; put 'cnt+1;'; put 'end;'; put 'end;'; put 'else put (_all_)(=);'; put 'run;'; put '%mp_abort(iftrue= (&tsuri=stopifempty)'; put ',mac=mm_getstpcode'; put ',msg=%str(&tree&name.(StoredProcess) not found!)'; put ')'; put '/**'; put '* Now we can extract the textstore'; put '*/'; put 'filename __getdoc temp lrecl=10000000;'; put 'proc metadata'; put 'in="$METAREPOSITORY'; put ''; put 'SAS1"'; put 'out=__getdoc ;'; put 'run;'; put '/* find the beginning of the text */'; put '%local start;'; put 'data _null_;'; put 'infile __getdoc lrecl=10000;'; put 'input;'; put 'start=index(_infile_,''StoredText="'');'; put 'if start then do;'; put 'call symputx("start",start+11);'; put '*putlog ''"'' _infile_ ''"'';'; put 'end;'; put 'stop;'; put '%local outeng;'; put '%if "&outloc"="0" %then %let outeng=TEMP;'; put '%else %let outeng="&outloc";'; put '%local fref;'; put '%if &outref=0 %then %let fref=%mf_getuniquefileref();'; put '%else %let fref=&outref;'; put '/* read the content, byte by byte, resolving escaped chars */'; put 'filename &fref &outeng lrecl=100000;'; put 'data _null_;'; put 'length filein 8 fileid 8;'; put 'filein = fopen("__getdoc","I",1,"B");'; put 'fileid = fopen("&fref","O",1,"B");'; put 'rec = "20"x;'; put 'length entity $6;'; put 'do while(fread(filein)=0);'; put 'x+1;'; put 'if x>&start then do;'; put 'rc = fget(filein,rec,1);'; put 'if rec=''"'' then leave;'; put 'else if rec="&" then do;'; put 'entity=rec;'; put 'do until (rec=";");'; put 'if fread(filein) ne 0 then goto getout;'; put 'rc = fget(filein,rec,1);'; put 'entity=cats(entity,rec);'; put 'end;'; put 'select (entity);'; put 'when (''&'' ) rec=''&'' ;'; put 'when (''<'' ) rec=''<'' ;'; put 'when (''>'' ) rec=''>'' ;'; put 'when (''''') rec="''" ;'; put 'when (''"'') rec=''"'' ;'; put 'when ('' '') rec=''0A''x;'; put 'when ('' '') rec=''0D''x;'; put 'when (''$'' ) rec=''$'' ;'; put 'when ('' '') rec=''09''x;'; put 'otherwise putlog "%str(WARN)ING: missing value for " entity=;'; put 'end;'; put 'rc =fput(fileid, substr(rec,1,1));'; put 'rc =fwrite(fileid);'; put 'end;'; put 'else do;'; put 'rc =fput(fileid,rec);'; put 'rc =fwrite(fileid);'; put 'end;'; put 'end;'; put 'end;'; put 'getout:'; put 'rc=fclose(filein);'; put 'rc=fclose(fileid);'; put 'run;'; put '%if &showlog=YES %then %do;'; put 'data _null_;'; put 'infile &fref lrecl=32767 end=last;'; put 'input;'; put 'if _n_=1 then putlog ''>>stpcodeBEGIN<<'';'; put 'putlog _infile_;'; put 'if last then putlog ''>>stpcodeEND<<'';'; put 'run;'; put '%end;'; put 'filename __getdoc clear;'; put '%if &outref=0 %then %do;'; put 'filename &fref clear;'; put '%end;'; put '%mend mm_getstpcode;'; put '%macro dc_getsettings();'; put '%global _program;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(syscc=&syscc on entry (&syswarningtext &syserrortext))'; put ')'; put '%if %length(&_PROGRAM)>1 %then %let root=&_program;'; put '%else %do;'; put '%global _metauser;'; put '%let _metauser=&sysuserid;'; put '/* to mimic a "real" _program we need to give a dummy role and stp name */'; put '%let root=/dummyRole/dummyName;'; put '%end;'; put '/* the DC precode is stored in the Admin folder in the root of'; put 'the project. Lets find that root. */'; put '%put &=root;'; put '%let root=%mf_getapploc();'; put '%put &=root;'; put '/* Now we know the root location we can retrieve the params */'; put '%let temploc=%sysfunc(pathname(work))/settings.txt;'; put '%mm_getstpcode(tree=&root/services/public'; put ',name=Data_Controller_Settings'; put ',outloc=&temploc'; put ')'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Unable to run getstpcode)'; put ')'; put 'filename _getsets "&temploc" lrecl=2000;'; put '/*'; put 'Do not use mp_include here - this puts a copy in every service, which creates'; put 'compilation problems when calling services from mp_include'; put '*/'; put '%inc _getsets/source2;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Problem running &sysmacroname (&syswarningtext &syserrortext))'; put ')'; put '%mend dc_getsettings;'; put '%macro mf_fmtdttm('; put ')/*/STORE SOURCE*/;'; put '%if "&sysver"="9.2" or "&sysver"="9.3"'; put 'or ("&sysver"="9.4" and "%substr(&SYSVLONG,9,1)" le "3")'; put 'or "%substr(&sysver,1,1)"="4"'; put 'or "%substr(&sysver,1,1)"="5"'; put '%then %do;DATETIME19.3%end;'; put '%else %do;E8601DT26.6%end;'; put '%mend mf_fmtdttm;'; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '%macro mp_init(prefix=SASJS'; put ')/*/STORE SOURCE*/;'; put '%if %symexist(SASJS_PREFIX) %then %return; /* only run once */'; put '%global'; put 'SASJS_PREFIX /* the ONLY hard-coded global macro variable in SASjs */'; put '&prefix._FUNCTIONS /* used in mcf_init() to track core function compilation */'; put '&prefix._INIT_NUM /* initialisation time as numeric */'; put '&prefix._INIT_DTTM /* initialisation time in E8601DT26.6 format */'; put '&prefix.WORK /* avoid typing %sysfunc(pathname(work)) every time */'; put ';'; put '%let sasjs_prefix=&prefix;'; put 'data _null_;'; put 'dttm=datetime();'; put 'call symputx("&sasjs_prefix._init_num",dttm,''g'');'; put 'call symputx("&sasjs_prefix._init_dttm",put(dttm,E8601DT26.6),''g'');'; put 'call symputx("&sasjs_prefix.work",pathname(''WORK''),''g'');'; put 'run;'; put 'options'; put 'compress=CHAR /* default is none so ensure we have something! */'; put 'datastmtchk=ALLKEYWORDS /* protection from overwriting input datasets */'; put 'errorcheck=STRICT /* catch errs in libname/filename statements */'; put 'fmterr /* ensure err when a format cannot be found */'; put 'mergenoby=%str(ERR)OR /* throw err when a merge has no BY variables */'; put 'missing=. /* changing this can cause hard to detect errs */'; put 'noquotelenmax /* avoid warnings for long strings */'; put 'noreplace /* avoid overwriting permanent datasets */'; put 'ps=max /* reduce log size slightly */'; put 'ls=max /* reduce log even more and avoid word truncation */'; put 'validmemname=COMPATIBLE /* avoid special characters etc in table names */'; put 'validvarname=V7 /* avoid special characters etc in variable names */'; put 'varinitchk=%str(ERR)OR /* avoid data mistakes from variable name typos */'; put 'varlenchk=%str(ERR)OR /* fail hard if truncation (data loss) can result */'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5" %then %do;'; put 'noautocorrect /* disallow misspelled procedure names */'; put 'dsoptions=note2err /* undocumented - convert bad NOTEs to ERRs */'; put '%end;'; put ';'; put '%mend mp_init;'; put '%macro mpeinit(fetch=YES);'; put '%global mpeinit'; put 'mpeadmins /* group with unrestricted Meditor access */'; put 'mpelocapprovals /* location for landing and staging files */'; put 'mpelib /* location of configuration tables for DC */'; put 'dc_repo_users /* location of user / group metadata */'; put 'dc_licence_key /* extracted in dc_getsettings */'; put 'dc_activation_key /* extracted in dc_getsettings */'; put 'dc_locale /* extracted in dc_getsettings */'; put 'dc_dttmtfmt /* can be overridden in dc_getsettings */'; put '_debug'; put ';'; put '%if &mpeinit=1 %then %return;'; put '%else %let mpeinit=1;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(Problem on service startup (&syswarningtext &syserrortext))'; put ')'; put '%mp_init()'; put '%if &fetch=YES %then %do;'; put '%webout(FETCH)'; put '%end;'; put '%global _CLIENTNAME;'; put '%mp_abort(iftrue= (&_CLIENTNAME=SAS Enterprise Guide)'; put ',mac=&_program..sas'; put ',msg=%str(Data Controller is a web app and should not be executed from EG)'; put ')'; put 'options urlencoding=utf8 nobomfile lrecl=32767;'; put '%let perf=%sysfunc(datetime());'; put '%put perfdiff: 0;'; put '%let dc_locale=SYSTEM; /* default if not set */'; put '/**'; put '* E8601DT26.6 has widest database support - but not all SAS flavours can'; put '* handle it. Override in the settings STP if needed.'; put '*/'; put 'data _null_;'; put 'dc_dttmtfmt=''"%sysfunc(datetime(),''!!"%mf_fmtdttm()"!!'')"dt'';'; put 'call symputx(''dc_dttmtfmt'',dc_dttmtfmt);'; put 'put dc_dttmtfmt=;'; put 'run;'; put '%put &=dc_dttmtfmt;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc prior to dc_getsettings)'; put ')'; put '%dc_getsettings()'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc after dc_getsettings)'; put ')'; put 'data _null_;'; put 'set &DC_LIBREF..mpe_config(where=('; put 'var_scope="DC"'; put 'and &dc_dttmtfmt lt tx_to'; put 'and var_active=1'; put '));'; put 'call symputx(var_name,var_value,''G'');'; put 'putlog var_name "=" var_value;'; put 'run;'; put '%let mpelib=&dc_libref;'; put '%let mpeadmins=&dc_admin_group;'; put '%let mpelocapprovals=&dc_staging_area;'; put '%let dc_repo_users=&dc_repo_users;'; put '%if &dc_locale ne SYSTEM %then %do;'; put 'options locale=&dc_locale;'; put '%end;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(Problem during compilation or with STP precode (&syswarningtext))'; put ')'; put '%mend mpeinit;'; put '%macro mf_mval(var);'; put '%if %symexist(&var) %then %do;'; put '%superq(&var)'; put '%end;'; put '%mend mf_mval;'; put '%macro mf_trimstr(basestr,trimstr);'; put '%local baselen trimlen trimval;'; put '/* return if basestr is shorter than trimstr (or 0) */'; put '%let baselen=%length(%superq(basestr));'; put '%let trimlen=%length(%superq(trimstr));'; put '%if &baselen < &trimlen or &baselen=0 %then %return;'; put '/* obtain the characters from the end of basestr */'; put '%let trimval=%qsubstr(%superq(basestr)'; put ',%length(%superq(basestr))-&trimlen+1'; put ',&trimlen);'; put '/* compare and if matching, chop it off! */'; put '%if %superq(basestr)=%superq(trimstr) %then %do;'; put '%return;'; put '%end;'; put '%else %if %superq(trimval)=%superq(trimstr) %then %do;'; put '%qsubstr(%superq(basestr),1,%length(%superq(basestr))-&trimlen)'; put '%end;'; put '%else %do;'; put '&basestr'; put '%end;'; put '%mend mf_trimstr;'; put '%macro mf_getplatform(switch'; put ')/*/STORE SOURCE*/;'; put '%local a b c;'; put '%if &switch.NONE=NONE %then %do;'; put '%if %symexist(sasjsprocessmode) %then %do;'; put '%if &sasjsprocessmode=Stored Program %then %do;'; put 'SASJS'; put '%return;'; put '%end;'; put '%end;'; put '%if %symexist(sysprocessmode) %then %do;'; put '%if "&sysprocessmode"="SAS Object Server"'; put 'or "&sysprocessmode"= "SAS Compute Server" %then %do;'; put 'SASVIYA'; put '%end;'; put '%else %if "&sysprocessmode"="SAS Stored Process Server"'; put 'or "&sysprocessmode"="SAS Workspace Server"'; put '%then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if %symexist(_metaport) or %symexist(_metauser) %then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if &switch=SASSTUDIO %then %do;'; put '/* return the version of SAS Studio else 0 */'; put '%if %mf_mval(_CLIENTAPP)=%str(SAS Studio) %then %do;'; put '%let a=%mf_mval(_CLIENTVERSION);'; put '%let b=%scan(&a,1,.);'; put '%if %eval(&b >2) %then %do;'; put '&b'; put '%end;'; put '%else 0;'; put '%end;'; put '%else 0;'; put '%end;'; put '%else %if &switch=VIYARESTAPI %then %do;'; put '%mf_trimstr(%sysfunc(getoption(servicesbaseurl)),/)'; put '%end;'; put '%mend mf_getplatform;'; put '%macro mpeterm();'; put '%local oldloc;'; put 'data _null_;'; put 'if symexist(''SYSPRINTTOLOG'') then oldloc=symget(''SYSPRINTTOLOG'');'; put 'else oldloc=getoption(''LOG'');'; put 'if subpad(oldloc,1,1) not in (''"'',"''",'' '') then oldloc=quote(cats(oldloc));'; put 'call symputx(''oldloc'',oldloc,''l'');'; put 'run;'; put '%if %length(&oldloc)>0 %then %do;'; put 'proc printto log=log;'; put 'run;'; put 'data _null_;'; put 'infile &oldloc;'; put 'input; putlog _infile_;'; put 'run;'; put '%end;'; put '%if %sysfunc(exist(&dc_libref..mpe_requests)) and %mf_getplatform() ne SASVIYA'; put '%then %do;'; put 'data ;'; put 'if 0 then set &dc_libref..mpe_requests;'; put 'request_dttm=%sysfunc(datetime());'; put 'request_user="%mf_getuser()";'; put 'request_service="%scan(&_program,-2,/)/%scan(&_program,-1,/)";'; put 'request_params='''';'; put 'output;stop;'; put 'proc append base=&dc_libref..mpe_requests data=&syslast force nowarn;'; put 'run;'; put '%end;'; put '%mend mpeterm;'; put '%macro mm_getGroups('; put 'user='; put ',outds=work.mm_getGroups'; put ',repo=foundation'; put ',mDebug=0'; put ')/*/STORE SOURCE*/;'; put '%local mD oldrepo;'; put '%let oldrepo=%sysfunc(getoption(metarepository));'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing mm_getGroups.sas;'; put '%&mD.put _local_;'; put '/* on some sites, user / group info is in a different metadata repo to the'; put 'default */'; put '%if &oldrepo ne &repo %then %do;'; put 'options metarepository=&repo;'; put '%end;'; put '%if %length(&user)=0 %then %do;'; put 'data &outds (keep=groupuri groupname groupdesc);'; put 'length groupuri groupname groupdesc group_or_role $256;'; put 'call missing(of _all_);'; put 'i+1;'; put 'do while'; put '(metadata_getnobj("omsobj:IdentityGroup?@Id contains ''.''",i,groupuri)>0);'; put 'rc=metadata_getattr(groupuri, "Name", groupname);'; put 'rc=metadata_getattr(groupuri, "Desc", groupdesc);'; put 'rc=metadata_getattr(groupuri,"PublicType",group_or_role);'; put 'if Group_or_Role = ''UserGroup'' then output;'; put 'i+1;'; put 'end;'; put 'run;'; put '%end;'; put '%else %do;'; put 'data &outds (keep=groupuri groupname groupdesc);'; put 'length uri groupuri groupname groupdesc group_or_role $256;'; put 'call missing(of _all_);'; put 'rc=metadata_getnobj("omsobj:Person?@Name=''&user''",1,uri);'; put 'if rc<=0 then do;'; put 'putlog "%str(WARN)ING: rc=" rc "&user not found "'; put '", or there was an issue reading the repository.";'; put 'stop;'; put 'end;'; put 'a=1;'; put 'grpassn=metadata_getnasn(uri,"IdentityGroups",a,groupuri);'; put 'if grpassn in (-3,-4) then do;'; put 'putlog "%str(WARN)ING: No metadata groups found for &user";'; put 'output;'; put 'end;'; put 'else do while (grpassn > 0);'; put 'rc=metadata_getattr(groupuri, "Name", groupname);'; put 'rc=metadata_getattr(groupuri, "Desc", groupdesc);'; put 'a+1;'; put 'rc=metadata_getattr(groupuri,"PublicType",group_or_role);'; put 'if Group_or_Role = ''UserGroup'' then output;'; put 'grpassn=metadata_getnasn(uri,"IdentityGroups",a,groupuri);'; put 'end;'; put 'run;'; put '%end;'; put '%if &oldrepo ne &repo %then %do;'; put 'options metarepository=&oldrepo;'; put '%end;'; put '%mend mm_getGroups;'; put '%macro dc_getusergroups(user=,outds=mm_getgroups);'; put '%global dc_repo_users;'; put '%if &dc_repo_users= %then %let dc_repo_users=foundation;'; put '%mm_getgroups(user=&user,outds=&outds,repo=&dc_repo_users)'; put '%mend dc_getusergroups;'; put '%macro mpe_getgroups(user=,outds=);'; put '%if not %symexist(dc_repo_users) %then %let dc_repo_users=foundation;'; put '%dc_getusergroups(user=&user,outds=&outds)'; put 'data;'; put 'length groupname groupdesc $256;'; put 'set &dc_libref..mpe_groups;'; put 'where &dc_dttmtfmt. lt tx_to;'; put 'where also upcase(user_name)="%upcase(&user)";'; put 'groupname=group_name;'; put 'groupdesc=group_desc;'; put 'keep groupname groupdesc;'; put 'run;'; put 'data &outds;'; put 'set &syslast &outds(keep=groupname groupdesc);'; put 'run;'; put '%mend mpe_getgroups;'; put '%macro mf_getuniquename(prefix=MC);'; put '&prefix.%substr(%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32-%length(&prefix))'; put '%mend mf_getuniquename;'; put '%macro mf_abort(mac=mf_abort.sas, msg=, iftrue=%str(1=1)'; put ')/des=''ungraceful abort'' /*STORE SOURCE*/;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%put NOTE: /// mf_abort macro executing //;'; put '%if %length(&mac)>0 %then %put NOTE- called by &mac;'; put '%put NOTE - &msg;'; put '%abort;'; put '%mend mf_abort;'; put '/** @endcond */'; put '%macro mf_verifymacvars('; put 'verifyVars /* list of macro variable NAMES */'; put ',makeUpcase=NO /* set to YES to make all the variable VALUES uppercase */'; put ',mAbort=SOFT'; put ')/*/STORE SOURCE*/;'; put '%local verifyIterator verifyVar abortmsg;'; put '%do verifyIterator=1 %to %sysfunc(countw(&verifyVars,%str( )));'; put '%let verifyVar=%qscan(&verifyVars,&verifyIterator,%str( ));'; put '%if not %symexist(&verifyvar) %then %do;'; put '%let abortmsg= Variable &verifyVar is MISSING;'; put '%goto exit_err;'; put '%end;'; put '%if %length(%trim(&&&verifyVar))=0 %then %do;'; put '%let abortmsg= Variable &verifyVar is EMPTY;'; put '%goto exit_err;'; put '%end;'; put '%if &makeupcase=YES %then %do;'; put '%let &verifyVar=%upcase(&&&verifyvar);'; put '%end;'; put '%end;'; put '%goto exit_success;'; put '%exit_err:'; put '%put &abortmsg;'; put '%mf_abort(iftrue=(&mabort ne SOFT),'; put 'mac=mf_verifymacvars,'; put 'msg=%str(&abortmsg)'; put ')'; put '0'; put '%return;'; put '%exit_success:'; put '1'; put '%mend mf_verifymacvars;'; put '%macro mpe_accesscheck('; put 'base_table'; put ',outds=med_accesscheck /* WORK table to contain access details */'; put ',user= /* metadata user to check for */'; put ',access_level=APPROVE'; put ',cntl_lib_var=MPELIB'; put ');'; put '%if &user= %then %let user=%mf_getuser();'; put '%mp_abort('; put 'iftrue=(%index(&outds,.)>0 and %upcase(%scan(&outds,1,.)) ne WORK)'; put ',mac=mpe_accesscheck'; put ',msg=%str(outds should be a WORK table)'; put ')'; put '%mp_abort('; put 'iftrue=(%mf_verifymacvars(base_table user access_level)=0)'; put ',mac=mpe_accesscheck'; put ',msg=%str(Missing base_table/user access_level variables)'; put ')'; put '/* make unique temp table vars */'; put '%local tempds1 tempds2;'; put '%let tempds1=%mf_getuniquename(prefix=usergroups);'; put '%let tempds2=%mf_getuniquename(prefix=tablegroups);'; put '/* get list of user groups */'; put '%mpe_getgroups(user=&user,outds=&tempds1)'; put '/* get list of groups with access for that table */'; put 'proc sql;'; put 'create table &tempds2 as'; put 'select distinct sas_group'; put 'from &&&cntl_lib_var...mpe_security'; put 'where &dc_dttmtfmt. lt tx_to'; put 'and access_level="&access_level"'; put 'and ('; put '(libref="%scan(&base_table,1,.)" and upcase(dsn)="%scan(&base_table,2,.)")'; put 'or (libref="%scan(&base_table,1,.)" and dsn="*ALL*")'; put 'or (libref="*ALL*")'; put ');'; put '%if &_debug ge 131 %then %do;'; put 'data _null_;'; put 'set &tempds1;'; put 'putlog (_all_)(=);'; put 'run;'; put 'data _null_;'; put 'set &tempds2;'; put 'putlog (_all_)(=);'; put 'run;'; put '%end;'; put 'proc sql;'; put 'create table &outds as'; put 'select * from &tempds1'; put 'where groupname="&mpeadmins"'; put 'or groupname in (select * from &tempds2);'; put '%put &sysmacroname: base_table=&base_table;'; put '%put &sysmacroname: access_level=&access_level;'; put '%mend mpe_accesscheck;'; put '%macro mpe_columnlevelsecurity(tgtlib,tgtds,inds'; put ',mode=VIEW'; put ',groupds=work.groups'; put ',clsds=work.clsview'; put ',outds=CLSVIEW'; put ',outmeta=work.cls_rules'; put ');'; put '%local col_list is_admin;'; put '/* filter for the appropriate rules */'; put 'proc sql;'; put 'create table &outmeta as'; put 'select CLS_VARIABLE_NM,'; put 'min(case when CLS_HIDE=1 then 1 else 0 end) as CLS_HIDE'; put 'from &clsds'; put 'where &dc_dttmtfmt. lt tx_to'; put 'and CLS_SCOPE in ("&mode",''ALL'')'; put 'and CLS_ACTIVE=1'; put '%if &mode=VIEW %then %do;'; put 'and CLS_HIDE ne 1'; put '%end;'; put 'and upcase(CLS_GROUP) in (select upcase(groupname) from &groupds)'; put 'and CLS_LIBREF="%upcase(&tgtlib)"'; put 'and CLS_TABLE="%upcase(&tgtds)"'; put 'group by CLS_VARIABLE_NM;'; put '%let is_admin=0;'; put 'proc sql;'; put 'select count(*) into: is_admin from &groupds where groupname="&MPEADMINS";'; put '%put &sysmacroname: &=is_admin;'; put '%if %mf_nobs(work.cls_rules) = 0 or &is_admin>0 %then %do;'; put '%put &sysmacroname: no CLS rules to apply;'; put '%put &=is_admin;'; put '/* copy using append for speed */'; put 'data &outds;'; put 'set &inds;'; put 'stop;'; put 'run;'; put 'proc append base=&outds data=&inds;'; put 'run;'; put '/* ensure CLS_RULES is empty in case of admin */'; put 'data &outmeta;'; put 'set &outmeta;'; put 'stop;'; put 'run;'; put '%return;'; put '%end;'; put '%else %if &mode=VIEW %then %do;'; put '/* just send back the relevant columns */'; put '%let col_list=0;'; put 'proc sql noprint;'; put 'select CLS_VARIABLE_NM into: col_list separated by '' '' from &outmeta'; put 'where CLS_HIDE=0;'; put '%if &col_list=0 %then %do;'; put '/*'; put 'We have columns that are set to CLS_HIDE=1 but we do not have any to'; put 'explicitly show. Therefore we assume all columns are to be shown except'; put 'those that are explicitly hidden.'; put '*/'; put 'proc sql noprint;'; put 'select CLS_VARIABLE_NM into: col_list separated by '' '' from &outmeta'; put 'where CLS_HIDE=1;'; put 'data &outds;'; put 'set &inds;'; put 'drop &col_list;'; put 'run;'; put '%end;'; put '%else %do;'; put 'data &outds;'; put 'set &inds;'; put 'keep &col_list;'; put 'run;'; put '%end;'; put '%end;'; put '%else %if &mode=EDIT %then %do;'; put '/*'; put 'In this case we pass all columns and the frontend will filter out the'; put 'ones that are not allowed to be edited.'; put '*/'; put 'data &outds;'; put 'set &inds;'; put 'stop;'; put 'run;'; put 'proc append base=&outds data=&inds;'; put 'run;'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: invalid mode - &mode!;'; put '%abort;'; put '%end;'; put '%mend mpe_columnlevelsecurity;'; put '%macro mp_dsmeta(libds,outds=work.dsmeta);'; put '%local ds1 ds2;'; put 'data;run; %let ds1=&syslast;'; put 'data;run; %let ds2=&syslast;'; put '/* setup the ODS capture */'; put 'ods output attributes=&ds1 enginehost=&ds2;'; put '/* export the metadata */'; put 'proc contents data=&libds;'; put 'run;'; put '/* load it into a single table */'; put 'data &outds (keep=ods_table name value);'; put 'length ods_table $10 name label2 label1 label $100'; put 'value cvalue cvalue1 cvalue2 $1000'; put 'nvalue nvalue1 nvalue2 8;'; put 'if _n_=1 then call missing (of _all_);'; put '* putlog (_all_)(=);'; put 'set &ds1 (in=atrs) &ds2 (in=eng);'; put 'if atrs then do;'; put 'ods_table=''ATTRIBUTES'';'; put 'name=coalescec(label1,label);'; put 'value=coalescec(cvalue1,cvalue,put(coalesce(nvalue1,nvalue),best.));'; put 'output;'; put 'if label2 ne '''' then do;'; put 'name=label2;'; put 'value=coalescec(cvalue2,put(nvalue2,best.));'; put 'output;'; put 'end;'; put 'end;'; put 'else if eng then do;'; put 'ods_table=''ENGINEHOST'';'; put 'name=coalescec(label1,label);'; put 'value=coalescec(cvalue1,cvalue,put(coalesce(nvalue1,nvalue),best.));'; put 'output;'; put 'end;'; put 'run;'; put 'proc sql;'; put 'drop table &ds1, &ds2;'; put '%mend mp_dsmeta;'; put '%macro mpe_dsmeta(libds, outds=dsmeta);'; put '%local ddsd ddld notes lenstmt;'; put '%let lenstmt=length ods_table $18 name $100 value $1000;'; put '%let libds=%upcase(&libds);'; put '%mp_dsmeta(&libds, outds=&outds)'; put 'data _null_;'; put 'set &mpelib..mpe_datadictionary;'; put 'where &dc_dttmtfmt < tx_to & dd_source=%upcase("&libds") & dd_type=''TABLE'';'; put 'call symputx(''ddsd'',dd_shortdesc,''l'');'; put 'call symputx(''ddld'',dd_longdesc,''l'');'; put 'run;'; put 'data &outds;'; put '&lenstmt;'; put 'if last then do;'; put 'ODS_TABLE=''MPE_DATADICTIONARY'';'; put 'NAME=''DD_SHORTDESC'';'; put 'VALUE="&ddsd";'; put 'output;'; put 'NAME=''DD_LONGDESC'';'; put 'VALUE="&ddld";'; put 'output;'; put 'end;'; put 'set &outds end=last;'; put 'output;'; put 'run;'; put 'data _data_;'; put 'set &mpelib..mpe_tables;'; put 'where libref="%scan(&libds,1,.)"'; put '& dsn="%scan(&libds,2,.)"'; put '& &dc_dttmtfmt 0 then put ''AND '' filter_text;'; put 'else put filter_text;'; put 'run;'; put '%end;'; put '%end;'; put '/**'; put '* Now do Row Level Security based on the MPE_ROW_LEVEL_SECURITY table'; put '*/'; put '/* first determine users group membership */'; put '%mpe_getgroups(user=%mf_getuser(),outds=work.groups)'; put '%local admin_check;'; put 'proc sql;'; put 'select count(*) into: admin_check'; put 'from work.groups'; put 'where groupname="&mpeadmins";'; put '%put &sysmacroname: &=admin_check &=mpeadmins;'; put '%if &admin_check=0 %then %do;'; put '%local scopeval;'; put '%if &mode=DLOAD %then %let scopeval=VIEW;'; put '%if &mode=ULOAD %then %let scopeval=EDIT;'; put '%else %let scopeval=&mode;'; put '/* extract relevant rows */'; put '%local rlsds;'; put '%let rlsds=%mf_getuniquename();'; put 'proc sql;'; put 'create table work.&rlsds as'; put 'select rls_group,'; put 'rls_group_logic as group_logic,'; put 'rls_subgroup_logic as subgroup_logic,'; put 'rls_subgroup_id as subgroup_id,'; put 'rls_variable_nm as variable_nm,'; put 'rls_operator_nm as operator_nm,'; put 'rls_raw_value as raw_value'; put 'from &mpelib..mpe_row_level_security'; put 'where &dc_dttmtfmt. lt tx_to'; put 'and rls_scope in ("&scopeval",''ALL'')'; put 'and upcase(rls_group) in (select upcase(groupname) from work.groups)'; put 'and rls_libref="%scan(&libds,1,.)"'; put 'and rls_table="%scan(&libds,2,.)"'; put 'and rls_active=1'; put 'order by rls_group,rls_subgroup_id;'; put '%if &sqlobs>0 %then %do;'; put '/* check if we currently have filter or not */'; put 'data ;'; put 'infile &outref end=eof;'; put 'input;'; put 'if _n_=1 and eof and cats(_infile_)='''' then newfilter=1;'; put 'output;'; put 'stop;'; put 'run;'; put 'data _null_;'; put 'set &syslast;'; put 'file &outref mod;'; put 'if newfilter=1 then put ''('';'; put 'else put ''AND ('';'; put 'run;'; put '/* loop through and apply filters for each group membership */'; put '%local fref ds;'; put '%let fref=%mf_getuniquefileref();'; put '%let ds=%mf_getuniquename();'; put 'proc sql noprint;'; put 'select distinct rls_group into : group1 -'; put 'from work.&rlsds;'; put '%do i=1 %to &sqlobs;'; put 'data work.&ds;'; put 'set work.&rlsds;'; put 'where rls_group="&&group&i";'; put 'drop rls_group;'; put 'run;'; put '%mp_filtergenerate(&ds,outref=&fref)'; put 'data _null_;'; put 'infile &fref;'; put 'file &outref mod;'; put 'input;'; put 'if &i>1 and _n_=1 then put '' OR '';'; put 'put _infile_;'; put 'run;'; put '%end;'; put 'data _null_;'; put 'file &outref mod;'; put 'put '')'';'; put 'run;'; put '%end; /* &sqlobs>0 */'; put '%else %do;'; put '%put &sysmacroname: no matching groups;'; put 'data _null_;'; put 'set work.groups;'; put 'putlog (_all_)(=);'; put 'run;'; put '%end;'; put '%mp_abort(iftrue= (&syscc>0)'; put ',mac=&sysmacroname'; put ',msg=%str(Row Level Security Generation Error)'; put ')'; put '%end; /* &admin_check=0 */'; put '%put leaving &sysmacroname with the following query:;'; put '%local empty;'; put '%let empty=0;'; put 'data _null_;'; put 'infile &outref end=eof;'; put 'input;'; put 'putlog _infile_;'; put 'if _n_=1 and eof and cats(_infile_)='''' then do;'; put 'put ''1=1'';'; put 'call symputx(''empty'',1,''l'');'; put 'end;'; put 'run;'; put '%if &empty=1 %then %do;'; put 'data _null_;'; put 'file &outref;'; put 'put ''1=1'';'; put 'run;'; put '%end;'; put '%mend mpe_filtermaster;'; put '%macro dc_getservicecode(loc=,outref=);'; put '%mm_getstpcode(tree=&loc'; put ',outref=&outref'; put ')'; put '%mend dc_getservicecode;'; put '%macro mp_include(fileref'; put ',prefix=_'; put ',opts=SOURCE2'; put ',errds=work.mp_abort_errds'; put ')/*/STORE SOURCE*/;'; put '/* prepare precode */'; put '%local tempref;'; put '%let tempref=%mf_getuniquefileref();'; put 'data _null_;'; put 'file &tempref;'; put 'set sashelp.vextfl(where=(fileref="%upcase(&fileref)"));'; put 'put ''%let _SYSINCLUDEFILEDEVICE='' xengine '';'';'; put 'name=scan(xpath,-1,''/\'');'; put 'put ''%let _SYSINCLUDEFILENAME='' name '';'';'; put 'path=subpad(xpath,1,length(xpath)-length(name)-1);'; put 'put ''%let _SYSINCLUDEFILEDIR='' path '';'';'; put 'put ''%let _SYSINCLUDEFILEFILEREF='' "&fileref;";'; put 'run;'; put '/* prepare the errds */'; put 'data &errds;'; put 'length msg mac $1000;'; put 'call missing(msg,mac);'; put 'iftrue=''1=0'';'; put 'run;'; put '/* include the include */'; put '%inc &tempref &fileref/&opts;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=%str(&_SYSINCLUDEFILEDIR/&_SYSINCLUDEFILENAME)'; put ',msg=%str(syscc=&syscc after executing &_SYSINCLUDEFILENAME)'; put ')'; put 'filename &tempref clear;'; put '%mend mp_include;'; put '%macro mpe_runhook(hookvar);'; put '%local pgmloc pgmtype;'; put '%let pgmtype=0;'; put '%put &sysmacroname: &=hookvar;'; put '%if %length(&&&hookvar)>0 %then %do;'; put '%put &sysmacroname: Executing &&&hookvar;'; put 'data _null_;'; put 'rule_value=symget("&hookvar");'; put 'if scan(upcase(rule_value),-1,''.'')=''SAS'' then do;'; put 'call symputx(''pgmtype'',''PGM'');'; put 'call symputx(''pgmloc'',rule_value);'; put 'end;'; put 'else do;'; put 'apploc="%mf_getapploc()";'; put 'if substr(rule_value,1,1) ne ''/'''; put 'then rule_value=cats(apploc,''/'',rule_value);'; put 'call symputx(''pgmloc'',rule_value);'; put 'call symputx(''pgmtype'',''JOB'');'; put 'end;'; put 'run;'; put '%if &pgmtype=PGM %then %do;'; put 'filename sascode "&pgmloc";'; put '%end;'; put '%else %do;'; put '%dc_getservicecode(loc=&pgmloc'; put ',outref=sascode'; put ')'; put '%end;'; put '/* the below script will need to modify work.STAGING_DS */'; put '%local x; %let x=; /* legacy feature */'; put '%mp_include(sascode)'; put '%end;'; put '%mend mpe_runhook;'; put '%macro mm_assignlib('; put 'libref'; put ',mAbort=HARD'; put ')/*/STORE SOURCE*/;'; put '%local mp_abort msg;'; put '%let mp_abort=0;'; put '%if %sysfunc(libref(&libref)) %then %do;'; put 'data _null_;'; put 'length liburi LibName msg $200;'; put 'call missing(of _all_);'; put 'nobj=metadata_getnobj("omsobj:SASLibrary?@Libref=''&libref''",1,liburi);'; put 'if nobj=1 then do;'; put 'rc=metadata_getattr(liburi,"Name",LibName);'; put '/* now try and assign it */'; put 'if libname("&libref",,''meta'',cats(''liburi="'',liburi,''";'')) ne 0 then do;'; put 'putlog "&libref could not be assigned";'; put 'putlog liburi=;'; put '/**'; put '* Fetch the system message for display in the abort modal. This is'; put '* not always helpful though. One example, previously received:'; put '* NOTE: Libref XX refers to the same library metadata as libref XX.'; put '*/'; put 'msg=sysmsg();'; put 'if msg=:''ERROR: Libref SAVE is not assigned.'' then do;'; put 'msg=catx(" ",'; put '"Could not assign %upcase(&libref).",'; put '"Please check metadata permissions! Libname:",libname,'; put '"Liburi:",liburi'; put ');'; put 'end;'; put 'else if msg="ERROR: User does not have appropriate authorization "!!'; put '"level for library SAVE."'; put 'then do;'; put 'msg=catx(" ",'; put '"ERROR: User does not have appropriate authorization level",'; put '"for library %upcase(&libref), libname:",libname,'; put '"Liburi:",liburi'; put ');'; put 'end;'; put 'call symputx(''msg'',msg,''l'');'; put 'if "&mabort"=''HARD'' then call symputx(''mp_abort'',1,''l'');'; put 'end;'; put 'else do;'; put 'put (_all_)(=);'; put 'call symputx(''libname'',libname,''L'');'; put 'call symputx(''liburi'',liburi,''L'');'; put 'end;'; put 'end;'; put 'else if nobj>1 then do;'; put 'if "&mabort"=''HARD'' then call symputx(''mp_abort'',1);'; put 'call symputx(''msg'',"More than one library with libref=&libref");'; put 'end;'; put 'else do;'; put 'if "&mabort"=''HARD'' then call symputx(''mp_abort'',1);'; put 'call symputx(''msg'',"Library &libref not found in metadata");'; put 'end;'; put 'run;'; put '%put NOTE: &msg;'; put '%end;'; put '%else %do;'; put '%put NOTE: Library &libref is already assigned;'; put '%end;'; put '%mp_abort(iftrue= (&mp_abort=1)'; put ',mac=mm_assignlib.sas'; put ',msg=%superq(msg)'; put ')'; put '%mend mm_assignlib;'; put '/** @cond */'; put '%macro mf_getengine(libref'; put ')/*/STORE SOURCE*/;'; put '%local dsid engnum rc engine;'; put '/* in case the parameter is a libref.tablename, pull off just the libref */'; put '%let libref = %upcase(%scan(&libref, 1, %str(.)));'; put '%let dsid=%sysfunc('; put 'open(sashelp.vlibnam(where=(libname="%upcase(&libref)")),i)'; put ');'; put '%if (&dsid ^= 0) %then %do;'; put '%let engnum=%sysfunc(varnum(&dsid,ENGINE));'; put '%let rc=%sysfunc(fetch(&dsid));'; put '%let engine=%sysfunc(getvarc(&dsid,&engnum));'; put '%put &libref. ENGINE is &engine.;'; put '%let rc= %sysfunc(close(&dsid));'; put '%end;'; put '%upcase(&engine)'; put '%mend mf_getengine;'; put '/** @endcond */'; put '%macro mm_assigndirectlib('; put 'libref'; put ',open_passthrough='; put ',sql_options='; put ',mDebug=0'; put ',mAbort=0'; put ')/*/STORE SOURCE*/;'; put '%local mD;'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing mm_assigndirectlib.sas;'; put '%&mD.put _local_;'; put '%if &mAbort=1 %then %let mAbort=;'; put '%else %let mAbort=%str(*);'; put '%&mD.put NOTE: Creating direct (non META) connection to &libref library;'; put '%local cur_engine;'; put '%let cur_engine=%mf_getengine(&libref);'; put '%if &cur_engine ne META and &cur_engine ne %then %do;'; put '%put NOTE: &libref already has a direct (&cur_engine) libname connection;'; put '%return;'; put '%end;'; put '%else %if %upcase(&libref)=WORK %then %do;'; put '%put NOTE: We already have a direct connection to WORK :-) ;'; put '%return;'; put '%end;'; put '/* need to determine the library ENGINE first */'; put '%local engine;'; put 'data _null_;'; put 'length lib_uri engine $256;'; put 'call missing (of _all_);'; put '/* get URI for the particular library */'; put 'rc1=metadata_getnobj("omsobj:SASLibrary?@Libref =''&libref''",1,lib_uri);'; put '/* get the Engine attribute of the previous object */'; put 'rc2=metadata_getattr(lib_uri,''Engine'',engine);'; put 'putlog "mm_assigndirectlib for &libref:" rc1= lib_uri= rc2= engine=;'; put 'call symputx("liburi",lib_uri,''l'');'; put 'call symputx("engine",engine,''l'');'; put 'run;'; put '/* now obtain engine specific connection details */'; put '%if &engine=BASE %then %do;'; put '%&mD.put NOTE: Retrieving BASE library path;'; put 'data _null_;'; put 'length up_uri $256 path cat_path $1024;'; put 'retain cat_path;'; put 'call missing (of _all_);'; put '/* get all the filepaths of the UsingPackages association */'; put 'i=1;'; put 'rc3=metadata_getnasn("&liburi",''UsingPackages'',i,up_uri);'; put 'do while (rc3>0);'; put '/* get the DirectoryName attribute of the previous object */'; put 'rc4=metadata_getattr(up_uri,''DirectoryName'',path);'; put 'if i=1 then path = ''("''!!trim(path)!!''" '';'; put 'else path ='' "''!!trim(path)!!''" '';'; put 'cat_path = trim(cat_path) !! " " !! trim(path) ;'; put 'i+1;'; put 'rc3=metadata_getnasn("&liburi",''UsingPackages'',i,up_uri);'; put 'end;'; put 'cat_path = trim(cat_path) !! ")";'; put '&mD.putlog "NOTE: Getting physical path for &libref library";'; put '&mD.putlog rc3= up_uri= rc4= cat_path= path=;'; put '&mD.putlog "NOTE: Libname cmd will be:";'; put '&mD.putlog "libname &libref" cat_path;'; put 'call symputx("filepath",cat_path,''l'');'; put 'run;'; put '%if %sysevalf(&sysver<9.4) %then %do;'; put 'libname &libref &filepath;'; put '%end;'; put '%else %do;'; put '/* apply the new filelocks option to cater for temporary locks */'; put 'libname &libref &filepath filelockwait=5;'; put '%end;'; put '%end;'; put '%else %if &engine=REMOTE %then %do;'; put 'data x;'; put 'length rcCon rcProp rc k 3 uriCon uriProp PropertyValue PropertyName'; put 'Delimiter $256 properties $2048;'; put 'retain properties;'; put 'rcCon = metadata_getnasn("&liburi", "LibraryConnection", 1, uriCon);'; put 'rcProp = metadata_getnasn(uriCon, "Properties", 1, uriProp);'; put 'k = 1;'; put 'rcProp = metadata_getnasn(uriCon, "Properties", k, uriProp);'; put 'do while (rcProp > 0);'; put 'rc = metadata_getattr(uriProp , "DefaultValue",PropertyValue);'; put 'rc = metadata_getattr(uriProp , "PropertyName",PropertyName);'; put 'rc = metadata_getattr(uriProp , "Delimiter",Delimiter);'; put 'properties = trim(properties) !! " " !! trim(PropertyName)'; put '!! trim(Delimiter) !! trim(PropertyValue);'; put 'output;'; put 'k+1;'; put 'rcProp = metadata_getnasn(uriCon, "Properties", k, uriProp);'; put 'end;'; put '%&mD.put NOTE: Getting properties for REMOTE SHARE &libref library;'; put '&mD.put _all_;'; put '%&mD.put NOTE: Libname cmd will be:;'; put '%&mD.put libname &libref &engine &properties slibref=&libref;'; put 'call symputx ("properties",trim(properties),''l'');'; put 'run;'; put 'libname &libref &engine &properties slibref=&libref;'; put '%end;'; put '%else %if &engine=OLEDB %then %do;'; put '%&mD.put NOTE: Retrieving OLEDB connection details;'; put 'data _null_;'; put 'length domain datasource provider properties schema'; put 'connx_uri domain_uri conprop_uri lib_uri schema_uri value $256.;'; put 'call missing (of _all_);'; put '/* get source connection ID */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,connx_uri);'; put '/* get connection domain */'; put 'rc1=metadata_getnasn(connx_uri,''Domain'',1,domain_uri);'; put 'rc2=metadata_getattr(domain_uri,''Name'',domain);'; put '&mD.putlog / ''NOTE: '' // ''NOTE- connection id: '' connx_uri ;'; put '&mD.putlog ''NOTE- domain: '' domain;'; put '/* get DSN and PROVIDER from connection properties */'; put 'i=0;'; put 'do until (rc<0);'; put 'i+1;'; put 'rc=metadata_getnasn(connx_uri,''Properties'',i,conprop_uri);'; put 'rc2=metadata_getattr(conprop_uri,''Name'',value);'; put 'if value=''Connection.OLE.Property.DATASOURCE.Name.xmlKey.txt'' then do;'; put 'rc3=metadata_getattr(conprop_uri,''DefaultValue'',datasource);'; put 'end;'; put 'else if value=''Connection.OLE.Property.PROVIDER.Name.xmlKey.txt'' then do;'; put 'rc4=metadata_getattr(conprop_uri,''DefaultValue'',provider);'; put 'end;'; put 'else if value=''Connection.OLE.Property.PROPERTIES.Name.xmlKey.txt'' then'; put 'do;'; put 'rc5=metadata_getattr(conprop_uri,''DefaultValue'',properties);'; put 'end;'; put 'end;'; put '&mD.putlog ''NOTE- dsn/provider/properties: '' /'; put 'datasource provider properties;'; put '&mD.putlog ''NOTE- schema: '' schema // ''NOTE-'';'; put '/* get SCHEMA */'; put 'rc6=metadata_getnasn("&liburi",''UsingPackages'',1,lib_uri);'; put 'rc7=metadata_getattr(lib_uri,''SchemaName'',schema);'; put 'call symputx(''SQL_domain'',domain,''l'');'; put 'call symputx(''SQL_dsn'',datasource,''l'');'; put 'call symputx(''SQL_provider'',provider,''l'');'; put 'call symputx(''SQL_properties'',properties,''l'');'; put 'call symputx(''SQL_schema'',schema,''l'');'; put 'run;'; put '%if %length(&open_passthrough)>0 %then %do;'; put 'proc sql &sql_options;'; put 'connect to OLEDB as &open_passthrough(INSERT_SQL=YES'; put '/* need additional properties to make this work */'; put 'properties=(''Integrated Security''=SSPI'; put '''Persist Security Info''=True'; put '%sysfunc(compress(%str(&SQL_properties),%str(())))'; put ')'; put 'DATASOURCE=&sql_dsn PROMPT=NO'; put 'PROVIDER=&sql_provider SCHEMA=&sql_schema CONNECTION = GLOBAL);'; put '%end;'; put '%else %do;'; put 'LIBNAME &libref OLEDB PROPERTIES=&sql_properties'; put 'DATASOURCE=&sql_dsn PROVIDER=&sql_provider SCHEMA=&sql_schema'; put '%if %length(&sql_domain)>0 %then %do;'; put 'authdomain="&sql_domain"'; put '%end;'; put 'connection=shared;'; put '%end;'; put '%end;'; put '%else %if &engine=ODBC %then %do;'; put '%&mD.put NOTE: Retrieving ODBC connection details;'; put 'data _null_;'; put 'length connx_uri conprop_uri value datasource up_uri schema domprop_uri authdomain $256.;'; put 'call missing (of _all_);'; put '/* get source connection ID */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,connx_uri);'; put '/* get connection properties */'; put 'i=0;'; put 'do until (rc2<0);'; put 'i+1;'; put 'rc2=metadata_getnasn(connx_uri,''Properties'',i,conprop_uri);'; put 'rc3=metadata_getattr(conprop_uri,''Name'',value);'; put 'if value=''Connection.ODBC.Property.DATASRC.Name.xmlKey.txt'' then do;'; put 'rc4=metadata_getattr(conprop_uri,''DefaultValue'',datasource);'; put 'rc2=-1;'; put 'end;'; put 'end;'; put '/* get auth domain */'; put 'autrc=metadata_getnasn(connx_uri,"Domain",1,domprop_uri);'; put 'arc=metadata_getattr(domprop_uri,"Name",authdomain);'; put 'if not missing(authdomain) then authdomain=cats(''AUTHDOMAIN='',authdomain);'; put 'call symputx(''authdomain'',authdomain,''l'');'; put '/* get SCHEMA */'; put 'rc6=metadata_getnasn("&liburi",''UsingPackages'',1,up_uri);'; put 'rc7=metadata_getattr(up_uri,''SchemaName'',schema);'; put '&mD.put rc= connx_uri= rc2= conprop_uri= rc3= value= rc4= datasource='; put 'rc6= up_uri= rc7= schema=;'; put 'call symputx(''SQL_schema'',schema,''l'');'; put 'call symputx(''SQL_dsn'',datasource,''l'');'; put 'run;'; put '%if %length(&open_passthrough)>0 %then %do;'; put 'proc sql &sql_options;'; put 'connect to ODBC as &open_passthrough'; put '(INSERT_SQL=YES DATASRC=&sql_dsn. CONNECTION=global);'; put '%end;'; put '%else %do;'; put 'libname &libref ODBC DATASRC=&sql_dsn SCHEMA=&sql_schema &authdomain;'; put '%end;'; put '%end;'; put '%else %if &engine=POSTGRES %then %do;'; put '%put NOTE: Obtaining POSTGRES library details;'; put 'data _null_;'; put 'length database ignore_read_only_columns direct_exe preserve_col_names'; put 'preserve_tab_names server schema authdomain user password'; put 'prop name value uri urisrc $256.;'; put 'call missing (of _all_);'; put '/* get database value */'; put 'prop=''Connection.DBMS.Property.DB.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,database,"");'; put 'if database^='''' then database=''database=''!!quote(trim(database));'; put 'call symputx(''database'',database,''l'');'; put '/* get IGNORE_READ_ONLY_COLUMNS value */'; put 'prop=''Library.DBMS.Property.DBIROC.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,ignore_read_only_columns,"");'; put 'if ignore_read_only_columns^='''' then ignore_read_only_columns='; put '''ignore_read_only_columns=''!!ignore_read_only_columns;'; put 'call symputx(''ignore_read_only_columns'',ignore_read_only_columns,''l'');'; put '/* get DIRECT_EXE value */'; put 'prop=''Library.DBMS.Property.DirectExe.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,direct_exe,"");'; put 'if direct_exe^='''' then direct_exe=''direct_exe=''!!direct_exe;'; put 'call symputx(''direct_exe'',direct_exe,''l'');'; put '/* get PRESERVE_COL_NAMES value */'; put 'prop=''Library.DBMS.Property.PreserveColNames.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,preserve_col_names,"");'; put 'if preserve_col_names^='''' then preserve_col_names='; put '''preserve_col_names=''!!preserve_col_names;'; put 'call symputx(''preserve_col_names'',preserve_col_names,''l'');'; put '/* get PRESERVE_TAB_NAMES value */'; put '/* be careful with PRESERVE_TAB_NAMES=YES - it will mean your table will'; put 'become case sensitive!! */'; put 'prop=''Library.DBMS.Property.PreserveTabNames.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,preserve_tab_names,"");'; put 'if preserve_tab_names^='''' then preserve_tab_names='; put '''preserve_tab_names=''!!preserve_tab_names;'; put 'call symputx(''preserve_tab_names'',preserve_tab_names,''l'');'; put '/* get SERVER value */'; put 'if metadata_getnasn("&liburi","LibraryConnection",1,uri)>0 then do;'; put 'prop=''Connection.DBMS.Property.SERVER.Name.xmlKey.txt'';'; put 'rc=metadata_getprop(uri,prop,server,"");'; put 'end;'; put 'if server^='''' then server=''server=''!!quote(cats(server));'; put 'call symputx(''server'',server,''l'');'; put '/* get SCHEMA value */'; put 'if metadata_getnasn("&liburi","UsingPackages",1,uri)>0 then do;'; put 'rc=metadata_getattr(uri,"SchemaName",schema);'; put 'end;'; put 'if schema^='''' then schema=''schema=''!!schema;'; put 'call symputx(''schema'',schema,''l'');'; put '/* get AUTHDOMAIN value */'; put '/* this is only useful if the user account contains that auth domain'; put 'if metadata_getnasn("&liburi","DefaultLogin",1,uri)>0 then do;'; put 'rc=metadata_getnasn(uri,"Domain",1,urisrc);'; put 'rc=metadata_getattr(urisrc,"Name",authdomain);'; put 'end;'; put 'if authdomain^='''' then authdomain=''authdomain=''!!quote(trim(authdomain));'; put '*/'; put 'call symputx(''authdomain'',authdomain,''l'');'; put '/* get user & pass */'; put 'if authdomain='''' & metadata_getnasn("&liburi","DefaultLogin",1,uri)>0 then'; put 'do;'; put 'rc=metadata_getattr(uri,"UserID",user);'; put 'rc=metadata_getattr(uri,"Password",password);'; put 'end;'; put 'if user^='''' then do;'; put 'user=''user=''!!quote(trim(user));'; put 'password=''password=''!!quote(trim(password));'; put 'end;'; put 'call symputx(''user'',user,''l'');'; put 'call symputx(''password'',password,''l'');'; put '&md.put _all_;'; put 'run;'; put '%if %length(&open_passthrough)>0 %then %do;'; put '%put %str(WARN)ING: Passthrough option for postgres not yet supported;'; put '%return;'; put '%end;'; put '%else %do;'; put '%if &mdebug=1 %then %do;'; put '%put NOTE: Executing the following:/;'; put '%put NOTE- libname &libref POSTGRES &database &ignore_read_only_columns;'; put '%put NOTE- &direct_exe &preserve_col_names &preserve_tab_names;'; put '%put NOTE- &server &schema &authdomain &user &password //;'; put '%end;'; put 'libname &libref POSTGRES &database &ignore_read_only_columns &direct_exe'; put '&preserve_col_names &preserve_tab_names &server &schema &authdomain'; put '&user &password;'; put '%end;'; put '%end;'; put '%else %if &engine=ORACLE %then %do;'; put '%put NOTE: Obtaining &engine library details;'; put 'data _null_;'; put 'length assocuri1 assocuri2 assocuri3 authdomain path schema $256;'; put 'call missing (of _all_);'; put '/* get auth domain */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,assocuri1);'; put 'rc=metadata_getnasn(assocuri1,''Domain'',1,assocuri2);'; put 'rc=metadata_getattr(assocuri2,"Name",authdomain);'; put 'call symputx(''authdomain'',authdomain,''l'');'; put '/* path */'; put 'rc=metadata_getprop(assocuri1,'; put '''Connection.Oracle.Property.PATH.Name.xmlKey.txt'',path);'; put 'call symputx(''path'',path,''l'');'; put '/* schema */'; put 'rc=metadata_getnasn("&liburi",''UsingPackages'',1,assocuri3);'; put 'rc=metadata_getattr(assocuri3,''SchemaName'',schema);'; put 'call symputx(''schema'',schema,''l'');'; put 'run;'; put '%put NOTE: Executing the following:/; %put NOTE-;'; put '%put NOTE- libname &libref ORACLE path=&path schema=&schema;'; put '%put NOTE- authdomain=&authdomain;'; put '%put NOTE-;'; put 'libname &libref ORACLE path=&path schema=&schema authdomain=&authdomain;'; put '%end;'; put '%else %if &engine=SQLSVR %then %do;'; put '%put NOTE: Obtaining &engine library details;'; put 'data _null;'; put 'length assocuri1 assocuri2 assocuri3 authdomain path schema userid'; put 'passwd $256;'; put 'call missing (of _all_);'; put 'rc=metadata_getnasn("&liburi",''DefaultLogin'',1,assocuri1);'; put 'rc=metadata_getattr(assocuri1,"UserID",userid);'; put 'rc=metadata_getattr(assocuri1,"Password",passwd);'; put 'call symputx(''user'',userid,''l'');'; put 'call symputx(''pass'',passwd,''l'');'; put '/* path */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,assocuri2);'; put 'rc=metadata_getprop(assocuri2,'; put '''Connection.SQL.Property.Datasrc.Name.xmlKey.txt'',path);'; put 'call symputx(''path'',path,''l'');'; put '/* schema */'; put 'rc=metadata_getnasn("&liburi",''UsingPackages'',1,assocuri3);'; put 'rc=metadata_getattr(assocuri3,''SchemaName'',schema);'; put 'call symputx(''schema'',schema,''l'');'; put 'run;'; put '%put NOTE: Executing the following:/; %put NOTE-;'; put '%put NOTE- libname &libref SQLSVR datasrc=&path schema=&schema ;'; put '%put NOTE- user="&user" pass="XXX";'; put '%put NOTE-;'; put 'libname &libref SQLSVR datasrc=&path schema=&schema user="&user" pass="&pass";'; put '%end;'; put '%else %if &engine=TERADATA %then %do;'; put '%put NOTE: Obtaining &engine library details;'; put 'data _null;'; put 'length assocuri1 assocuri2 assocuri3 authdomain path schema userid'; put 'passwd $256;'; put 'call missing (of _all_);'; put '/* get auth domain */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,assocuri1);'; put 'rc=metadata_getnasn(assocuri1,''Domain'',1,assocuri2);'; put 'rc=metadata_getattr(assocuri2,"Name",authdomain);'; put 'call symputx(''authdomain'',authdomain,''l'');'; put '/*'; put 'rc=metadata_getnasn("&liburi",''DefaultLogin'',1,assocuri1);'; put 'rc=metadata_getattr(assocuri1,"UserID",userid);'; put 'rc=metadata_getattr(assocuri1,"Password",passwd);'; put 'call symputx(''user'',userid,''l'');'; put 'call symputx(''pass'',passwd,''l'');'; put '*/'; put '/* path */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,assocuri2);'; put 'rc=metadata_getprop(assocuri2,'; put '''Connection.Teradata.Property.SERVER.Name.xmlKey.txt'',path);'; put 'call symputx(''path'',path,''l'');'; put '/* schema */'; put 'rc=metadata_getnasn("&liburi",''UsingPackages'',1,assocuri3);'; put 'rc=metadata_getattr(assocuri3,''SchemaName'',schema);'; put 'call symputx(''schema'',schema,''l'');'; put 'run;'; put '%put NOTE: Executing the following:/; %put NOTE-;'; put '%put NOTE- libname &libref TERADATA server="&path" schema=&schema ;'; put '%put NOTe- authdomain=&authdomain;'; put '%put NOTE-;'; put 'libname &libref TERADATA server="&path" schema=&schema authdomain=&authdomain;'; put '%end;'; put '%else %if &engine= %then %do;'; put '%put NOTE: Libref &libref is not registered in metadata;'; put '%&mAbort.mp_abort('; put 'msg=%str(ERR)OR: Libref &libref is not registered in metadata'; put ',mac=mm_assigndirectlib.sas);'; put '%return;'; put '%end;'; put '%else %do;'; put '%put %str(WARN)ING: Engine &engine is currently unsupported;'; put '%put %str(WARN)ING- Please contact your support team.;'; put '%return;'; put '%end;'; put '%mend mm_assigndirectlib;'; put '%macro mm_getrepos('; put 'outds=work.mm_getrepos'; put ')/*/STORE SOURCE*/;'; put '* use a temporary fileref to hold the response;'; put 'filename response temp;'; put '/* get list of libraries */'; put 'proc metadata in='; put '"1"'; put 'out=response;'; put 'run;'; put '/* write the response to the log for debugging */'; put '/*'; put 'data _null_;'; put 'infile response lrecl=1048576;'; put 'input;'; put 'put _infile_;'; put 'run;'; put '*/'; put '/* create an XML map to read the response */'; put 'filename sxlemap temp;'; put 'data _null_;'; put 'file sxlemap;'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository";'; put 'put "";'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Id";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Name";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Desc";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@DefaultNS";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@RepositoryType";'; put 'put "characterstring20";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@RepositoryFormat";'; put 'put "characterstring10";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@Access";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@CurrentAccess";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@PauseState";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Path";'; put 'put "";'; put 'put "characterstring256";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Engine";'; put 'put "";'; put 'put "characterstring8";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Options";'; put 'put "";'; put 'put "characterstring32";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@MetadataCreated";'; put 'put "characterstring24";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@MetadataUpdated";'; put 'put "characterstring24";'; put 'put '''';'; put 'put ''
'';'; put 'run;'; put 'libname _XML_ xml xmlfileref=response xmlmap=sxlemap;'; put 'proc sort data= _XML_.SASRepos out=&outds;'; put 'by name;'; put 'run;'; put '/* clear references */'; put 'filename sxlemap clear;'; put 'filename response clear;'; put 'libname _XML_ clear;'; put '%mend mm_getrepos;'; put '%macro dc_assignlib(type,libref,passthru=);'; put '%put &sysmacroname entry vars:;'; put '%put _local_;'; put '/* take current repository */'; put '%local repo repocnt x;'; put '%let repo=%sysfunc(getoption(metarepository));'; put '/* get list of repositories and filter */'; put '%mm_getrepos(outds=repos)'; put '%let repocnt=0;'; put 'data repos;'; put 'set repos;'; put 'where repositorytype in(''CUSTOM'',''FOUNDATION'')'; put '& upcase(name) ne "%upcase(&repo)";'; put 'keep id name ;'; put 'call symputx(cats(''repo'',_n_),name,''l'');'; put 'call symputx(''repocnt'',_n_,''l'');'; put 'run;'; put '/* find out which of these repositories has the libref we are searching for */'; put '%local lib_uri;'; put '%let lib_uri=NOTFOUND;'; put 'data _null_; /* check default repo first */'; put 'length lib_uri $256;'; put 'call missing (of _all_);'; put 'rc=metadata_getnobj("omsobj:SASLibrary?@Libref =''&libref''",1,lib_uri);'; put 'call symputx(''lib_uri'',coalescec(lib_uri,''NOTFOUND''),''l'');'; put 'run;'; put '%if &lib_uri=NOTFOUND %then %do;'; put '%do x=1 %to &repocnt;'; put 'options metarepository=&&repo&x;'; put 'data _null_;'; put 'length lib_uri $256;'; put 'call missing (of _all_);'; put 'rc=metadata_getnobj("omsobj:SASLibrary?@Libref =''&libref''",1,lib_uri);'; put 'call symputx(''lib_uri'',coalescec(lib_uri,''NOTFOUND''),''l'');'; put 'run;'; put '%if &lib_uri ne NOTFOUND %then %let x=%eval(&repocnt+1);'; put '%end;'; put '%end;'; put '%if &type=READ %then %do;'; put '%mm_assignlib(&libref)'; put '%end;'; put '%else %if &type=WRITE %then %do;'; put '%mm_assigndirectlib(&libref,open_passthrough=&passthru)'; put '%end;'; put 'options metarepository=&repo;'; put '%mend dc_assignlib;'; put '%macro mm_getgroupmembers('; put 'group /* metadata group for which to bring back members */'; put ',outds=work.mm_getgroupmembers /* output dataset to contain the results */'; put ',emails=NO /* set to yes to bring back emails also */'; put ',id=NO /* set to yes if passing an ID rather than group name */'; put ')/*/STORE SOURCE*/;'; put 'data &outds ;'; put 'attrib uriGrp uriMem GroupId GroupName Group_or_Role MemberName MemberType'; put 'euri email length=$64'; put 'GroupDesc length=$256'; put 'rcGrp rcMem rc i j length=3;'; put 'call missing (of _all_);'; put 'drop uriGrp uriMem rcGrp rcMem rc i j arc ;'; put 'i=1;'; put '* Grab the URI for the first Group ;'; put '%if &id=NO %then %do;'; put 'rcGrp=metadata_getnobj("omsobj:IdentityGroup?@Name=''&group''",i,uriGrp);'; put '%end;'; put '%else %do;'; put 'rcGrp=metadata_getnobj("omsobj:IdentityGroup?@Id=''&group''",i,uriGrp);'; put '%end;'; put '* If Group found, enter do loop ;'; put 'if rcGrp>0 then do;'; put 'call missing (rcMem,uriMem,GroupId,GroupName,Group_or_Role'; put ',MemberName,MemberType);'; put '* get group info ;'; put 'rc = metadata_getattr(uriGrp,"Id",GroupId);'; put 'rc = metadata_getattr(uriGrp,"Name",GroupName);'; put 'rc = metadata_getattr(uriGrp,"PublicType",Group_or_Role);'; put 'rc = metadata_getattr(uriGrp,"Desc",GroupDesc);'; put 'j=1;'; put 'do while (metadata_getnasn(uriGrp,"MemberIdentities",j,uriMem) > 0);'; put 'call missing (MemberName, MemberType, email);'; put 'rc = metadata_getattr(uriMem,"Name",MemberName);'; put 'rc = metadata_getattr(uriMem,"PublicType",MemberType);'; put 'if membertype=''User'' and "&emails"=''YES'' then do;'; put 'if metadata_getnasn(uriMem,"EmailAddresses",1,euri)>0 then do;'; put 'arc=metadata_getattr(euri,"Address",email);'; put 'end;'; put 'end;'; put 'output;'; put 'j+1;'; put 'end;'; put 'end;'; put 'run;'; put '%mend mm_getgroupmembers;'; put '%macro dc_getgroupmembers(group,outds=dc_getgroupmembers);'; put '%mm_getgroupmembers(&group,outds=&outds)'; put '%mend dc_getgroupmembers;'; put '/** @cond */'; put '%macro mf_existvar(libds /* 2 part dataset name */'; put ', var /* variable name */'; put ')/*/STORE SOURCE*/;'; put '%local dsid rc;'; put '%let dsid=%sysfunc(open(&libds,is));'; put '%if &dsid=0 %then %do;'; put '%put %sysfunc(sysmsg());'; put '0'; put '%end;'; put '%else %if %length(&var)=0 %then %do;'; put '0'; put '%let rc=%sysfunc(close(&dsid));'; put '%end;'; put '%else %do;'; put '%sysfunc(varnum(&dsid,&var))'; put '%let rc=%sysfunc(close(&dsid));'; put '%end;'; put '%mend mf_existvar;'; put '/** @endcond */'; put '%macro mf_getvarlist(libds'; put ',dlm=%str( )'; put ',quote=no'; put ',typefilter=A'; put ')/*/STORE SOURCE*/;'; put '/* declare local vars */'; put '%local outvar dsid nvars x rc dlm q var vtype;'; put '/* credit Rowland Hale - byte34 is double quote, 39 is single quote */'; put '%if %upcase("e)=DOUBLE %then %let q=%qsysfunc(byte(34));'; put '%else %if %upcase("e)=SINGLE %then %let q=%qsysfunc(byte(39));'; put '/* open dataset in macro */'; put '%let dsid=%sysfunc(open(&libds));'; put '%if &dsid %then %do;'; put '%let nvars=%sysfunc(attrn(&dsid,NVARS));'; put '%if &nvars>0 %then %do;'; put '/* add variables with supplied delimeter */'; put '%do x=1 %to &nvars;'; put '/* get variable type */'; put '%let vtype=%sysfunc(vartype(&dsid,&x));'; put '%if &vtype=&typefilter or &typefilter=A %then %do;'; put '%let var=&q.%sysfunc(varname(&dsid,&x))&q.;'; put '%if &var=&q&q %then %do;'; put '%put &sysmacroname: Empty column found in &libds!;'; put '%let var=&q. &q.;'; put '%end;'; put '%if %quote(&outvar)=%quote() %then %let outvar=&var;'; put '%else %let outvar=&outvar.&dlm.&var.;'; put '%end;'; put '%end;'; put '%end;'; put '%let rc=%sysfunc(close(&dsid));'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: Unable to open &libds (rc=&dsid);'; put '%put &sysmacroname: SYSMSG= %sysfunc(sysmsg());'; put '%let rc=%sysfunc(close(&dsid));'; put '%end;'; put '%do;%unquote(&outvar)%end;'; put '%mend mf_getvarlist;'; put '%macro mf_existds(libds'; put ')/*/STORE SOURCE*/;'; put '%if %sysfunc(exist(&libds)) ne 1 & %sysfunc(exist(&libds,VIEW)) ne 1 %then 0;'; put '%else 1;'; put '%mend mf_existds;'; put '%macro mf_getquotedstr(IN_STR'; put ',DLM=%str(,)'; put ',QUOTE=S'; put ',indlm=%str( )'; put ')/*/STORE SOURCE*/;'; put '/* credit Rowland Hale - byte34 is double quote, 39 is single quote */'; put '%if "e=S %then %let quote=%qsysfunc(byte(39));'; put '%else %if "e=D %then %let quote=%qsysfunc(byte(34));'; put '%else %if "e=N %then %let quote=;'; put '%local i item buffer;'; put '%let i=1;'; put '%do %while (%qscan(&IN_STR,&i,%str(&indlm)) ne %str() ) ;'; put '%let item=%qscan(&IN_STR,&i,%str(&indlm));'; put '%if %bquote("E) ne %then %let item="E%qtrim(&item)"E;'; put '%else %let item=%qtrim(&item);'; put '%if (&i = 1) %then %let buffer =%qtrim(&item);'; put '%else %let buffer =&buffer&DLM%qtrim(&item);'; put '%let i = %eval(&i+1);'; put '%end;'; put '%let buffer=%sysfunc(coalescec(%qtrim(&buffer),"E"E));'; put '&buffer'; put '%mend mf_getquotedstr;'; put '%macro mf_wordsInStr1ButNotStr2('; put 'Str1= /* string containing words to extract */'; put ',Str2= /* used to compare with the extract string */'; put ')/*/STORE SOURCE*/;'; put '%local count_base count_extr i i2 extr_word base_word match outvar;'; put '%if %length(&str1)=0 or %length(&str2)=0 %then %do;'; put '%put base string (str1)= &str1;'; put '%put compare string (str2) = &str2;'; put '%return;'; put '%end;'; put '%let count_base=%sysfunc(countw(&Str2));'; put '%let count_extr=%sysfunc(countw(&Str1));'; put '%do i=1 %to &count_extr;'; put '%let extr_word=%scan(&Str1,&i,%str( ));'; put '%let match=0;'; put '%do i2=1 %to &count_base;'; put '%let base_word=%scan(&Str2,&i2,%str( ));'; put '%if &extr_word=&base_word %then %let match=1;'; put '%end;'; put '%if &match=0 %then %let outvar=&outvar &extr_word;'; put '%end;'; put '&outvar'; put '%mend mf_wordsInStr1ButNotStr2;'; put '%macro mddl_sas_cntlout(libds=WORK.CNTLOUT);'; put 'proc sql;'; put 'create table &libds('; put 'TYPE char(1) label='; put '''Format Type: either N (num fmt), C (char fmt), I (num infmt) or J (char infmt)'''; put ',FMTNAME char(32) label=''Format name'''; put ',FMTROW num label='; put '''CALCULATED Position of record by FMTNAME (reqd for multilabel formats)'''; put ',START char(32767) label=''Starting value for format'''; put '/*'; put 'Keep lengths of START and END the same to avoid this err:'; put '"Start is greater than end: -<."'; put 'Similar usage note: https://support.sas.com/kb/69/330.html'; put '*/'; put ',END char(32767) label=''Ending value for format'''; put ',LABEL char(32767) label=''Format value label'''; put ',MIN num length=3 label=''Minimum length'''; put ',MAX num length=3 label=''Maximum length'''; put ',DEFAULT num length=3 label=''Default length'''; put ',LENGTH num length=3 label=''Format length'''; put ',FUZZ num label=''Fuzz value'''; put ',PREFIX char(2) label=''Prefix characters'''; put ',MULT num label=''Multiplier'''; put ',FILL char(1) label=''Fill character'''; put ',NOEDIT num length=3 label=''Is picture string noedit?'''; put ',SEXCL char(1) label=''Start exclusion'''; put ',EEXCL char(1) label=''End exclusion'''; put ',HLO char(13) label='; put '''More info: https://core.sasjs.io/mddl__sas__cntlout_8sas_source.html'''; put ',DECSEP char(1) label=''Decimal separator'''; put ',DIG3SEP char(1) label=''Three-digit separator'''; put ',DATATYPE char(8) label=''Date/time/datetime?'''; put ',LANGUAGE char(8) label=''Language for date strings'''; put ');'; put '%local lib;'; put '%let libds=%upcase(&libds);'; put '%if %index(&libds,.)=0 %then %let lib=WORK;'; put '%else %let lib=%scan(&libds,1,.);'; put 'proc datasets lib=&lib noprint;'; put 'modify %scan(&libds,-1,.);'; put 'index create'; put 'pk_cntlout=(type fmtname fmtrow)'; put '/nomiss unique;'; put 'quit;'; put '%mend mddl_sas_cntlout;'; put '%macro mp_aligndecimal(var,width=8);'; put '%local tmpvar;'; put '%let tmpvar=%mf_getuniquename(prefix=aligndp);'; put 'length &tmpvar $&width;'; put 'if index(&var,''.'') then do;'; put '&tmpvar=cats(scan(&var,1,''.''));'; put '&tmpvar=right(&tmpvar);'; put '&var=&tmpvar!!''.''!!cats(scan(&var,2,''.''));'; put 'end;'; put 'else do;'; put '&tmpvar=cats(&var);'; put '&tmpvar=right(&tmpvar);'; put '&var=&tmpvar;'; put 'end;'; put 'drop &tmpvar;'; put '%mend mp_aligndecimal;'; put '%macro mp_cntlout('; put 'iftrue=(1=1)'; put ',libcat='; put ',cntlout=work.fmtextract'; put ',fmtlist=0'; put ')/*/STORE SOURCE*/;'; put '%local ddlds cntlds i;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%let ddlds=%mf_getuniquename();'; put '%let cntlds=%mf_getuniquename();'; put '%mddl_sas_cntlout(libds=&ddlds)'; put '%if %index(&libcat,-)>0 and %scan(&libcat,2,-)=FC %then %do;'; put '%let libcat=%scan(&libcat,1,-);'; put '%end;'; put 'proc format lib=&libcat cntlout=&cntlds;'; put '%if "&fmtlist" ne "0" and "&fmtlist" ne "" %then %do;'; put 'select'; put '%do i=1 %to %sysfunc(countw(&fmtlist,%str( )));'; put '%scan(&fmtlist,&i,%str( ))'; put '%end;'; put ';'; put '%end;'; put 'run;'; put 'data &cntlout/nonote2err;'; put 'if 0 then set &ddlds;'; put 'set &cntlds;'; put 'by type fmtname notsorted;'; put '/* align the numeric values to avoid overlapping ranges */'; put 'if type in ("I","N") then do;'; put '%mp_aligndecimal(start,width=16)'; put '%mp_aligndecimal(end,width=16)'; put 'end;'; put '/* create row marker. Data cannot be sorted without it! */'; put 'if first.fmtname then fmtrow=1;'; put 'else fmtrow+1;'; put 'run;'; put 'proc sort;'; put 'by type fmtname fmtrow;'; put 'run;'; put 'proc sql;'; put 'drop table &ddlds,&cntlds;'; put '%mend mp_cntlout;'; put '/** @endcond */'; put '%macro mp_getcols(ds, outds=work.cols);'; put '%local dropds;'; put 'proc contents noprint data=&ds'; put 'out=_data_ (keep=name type length label varnum format:);'; put 'run;'; put '%let dropds=&syslast;'; put 'data &outds(keep=name type length varnum format label ddtype fmtname);'; put 'set &dropds(rename=(format=fmtname type=type2));'; put 'name=upcase(name);'; put 'if type2=2 then do;'; put 'length format $49.;'; put 'if fmtname='''' then format=cats(''$'',length,''.'');'; put 'else if formatl=0 then format=cats(fmtname,''.'');'; put 'else format=cats(fmtname,formatl,''.'');'; put 'type=''C'';'; put 'ddtype=''CHARACTER'';'; put 'end;'; put 'else do;'; put 'if fmtname='''' then format=cats(length,''.'');'; put 'else if formatl=0 then format=cats(fmtname,''.'');'; put 'else if formatd=0 then format=cats(fmtname,formatl,''.'');'; put 'else format=cats(fmtname,formatl,''.'',formatd);'; put 'type=''N'';'; put 'if format=:''DATETIME'' or format=:''E8601DT'' then ddtype=''DATETIME'';'; put 'else if format=:''DATE'' or format=:''DDMMYY'' or format=:''MMDDYY'''; put 'or format=:''YYMMDD'' or format=:''E8601DA'' or format=:''B8601DA'''; put 'or format=:''MONYY'''; put 'then ddtype=''DATE'';'; put 'else if format=:''TIME'' then ddtype=''TIME'';'; put 'else ddtype=''NUMERIC'';'; put 'end;'; put 'if label='''' then label=name;'; put 'run;'; put 'proc sql;'; put 'drop table &dropds;'; put '%mend mp_getcols;'; put '%macro mcf_init(func'; put ')/*/STORE SOURCE*/;'; put '%if not (%symexist(SASJS_PREFIX)) %then %do;'; put '%global SASJS_PREFIX;'; put '%let SASJS_PREFIX=SASJS;'; put '%end;'; put '%let func=%upcase(&func);'; put '/* the / character is just a seperator */'; put '%global &sasjs_prefix._FUNCTIONS;'; put '%if %index(&&&sasjs_prefix._FUNCTIONS,&func/)>0 %then %do;'; put '1'; put '%return;'; put '%end;'; put '%else %do;'; put '%let &sasjs_prefix._FUNCTIONS=&&&sasjs_prefix._FUNCTIONS &func/;'; put '0'; put '%end;'; put '%mend mcf_init;'; put '%macro mcf_length(wrap=NO'; put ',insert_cmplib=DEPRECATED'; put ',lib=WORK'; put ',cat=SASJS'; put ',pkg=UTILS'; put ')/*/STORE SOURCE*/;'; put '%local i var cmpval found;'; put '%if %mcf_init(mcf_length)=1 %then %return;'; put '%if &wrap=YES %then %do;'; put 'proc fcmp outlib=&lib..&cat..&pkg;'; put '%end;'; put 'function mcf_length(var);'; put 'if var=. then len=0;'; put 'else if missing(var) or trunc(var,3)=var then len=3;'; put 'else if trunc(var,4)=var then len=4;'; put 'else if trunc(var,5)=var then len=5;'; put 'else if trunc(var,6)=var then len=6;'; put 'else if trunc(var,7)=var then len=7;'; put 'else len=8;'; put 'return(len);'; put 'endsub;'; put '%if &wrap=YES %then %do;'; put 'quit;'; put '%end;'; put '/* insert the CMPLIB if not already there */'; put '%let cmpval=%sysfunc(getoption(cmplib));'; put '%let found=0;'; put '%do i=1 %to %sysfunc(countw(&cmpval,%str( %(%))));'; put '%let var=%scan(&cmpval,&i,%str( %(%)));'; put '%if &var=&lib..&cat %then %let found=1;'; put '%end;'; put '%if &found=0 %then %do;'; put 'options insert=(CMPLIB=(&lib..&cat));'; put '%end;'; put '%mend mcf_length;'; put '%macro mf_getvarcount(libds,typefilter=A'; put ')/*/STORE SOURCE*/;'; put '%local dsid nvars rc outcnt x;'; put '%let dsid=%sysfunc(open(&libds));'; put '%let nvars=.;'; put '%let outcnt=0;'; put '%let typefilter=%upcase(&typefilter);'; put '%if &dsid %then %do;'; put '%let nvars=%sysfunc(attrn(&dsid,NVARS));'; put '%if &typefilter=A %then %let outcnt=&nvars;'; put '%else %if &nvars>0 %then %do x=1 %to &nvars;'; put '/* increment based on variable type */'; put '%if %sysfunc(vartype(&dsid,&x))=&typefilter %then %do;'; put '%let outcnt=%eval(&outcnt+1);'; put '%end;'; put '%end;'; put '%let rc=%sysfunc(close(&dsid));'; put '%end;'; put '%else %do;'; put '%put unable to open &libds (rc=&dsid);'; put '%let rc=%sysfunc(close(&dsid));'; put '%end;'; put '&outcnt'; put '%mend mf_getvarcount;'; put '%macro mf_getvartype(libds /* two level name */'; put ', var /* variable name from which to return the type */'; put ')/*/STORE SOURCE*/;'; put '%local dsid vnum vtype rc;'; put '/* Open dataset */'; put '%let dsid = %sysfunc(open(&libds));'; put '%if &dsid. > 0 %then %do;'; put '/* Get variable number */'; put '%let vnum = %sysfunc(varnum(&dsid, &var));'; put '/* Get variable type (C/N) */'; put '%if(&vnum. > 0) %then %let vtype = %sysfunc(vartype(&dsid, &vnum.));'; put '%else %do;'; put '%put NOTE: Variable &var does not exist in &libds;'; put '%let vtype = %str( );'; put '%end;'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: dataset &libds not opened! (rc=&dsid);'; put '%put &sysmacroname: %sysfunc(sysmsg());'; put '%return;'; put '%end;'; put '/* Close dataset */'; put '%let rc = %sysfunc(close(&dsid));'; put '/* Return variable type */'; put '&vtype'; put '%mend mf_getvartype;'; put '%macro mf_getVarFormat(libds /* two level ds name */'; put ', var /* variable name from which to return the format */'; put ', force=0'; put ')/*/STORE SOURCE*/;'; put '%local dsid vnum vformat rc vlen vtype;'; put '/* Open dataset */'; put '%let dsid = %sysfunc(open(&libds));'; put '%if &dsid > 0 %then %do;'; put '/* Get variable number */'; put '%let vnum = %sysfunc(varnum(&dsid, &var));'; put '/* Get variable format */'; put '%if(&vnum > 0) %then %let vformat=%sysfunc(varfmt(&dsid, &vnum));'; put '%else %do;'; put '%put NOTE: Variable &var does not exist in &libds;'; put '%let rc = %sysfunc(close(&dsid));'; put '%return;'; put '%end;'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: dataset &libds not opened! (rc=&dsid);'; put '%put &sysmacroname: %sysfunc(sysmsg());'; put '%return;'; put '%end;'; put '/* supply a default if no format available */'; put '%if %length(&vformat)<2 & &force=1 %then %do;'; put '%let vlen = %sysfunc(varlen(&dsid, &vnum));'; put '%let vtype = %sysfunc(vartype(&dsid, &vnum.));'; put '%if &vtype=C %then %let vformat=$&vlen..;'; put '%else %let vformat=best.;'; put '%end;'; put '/* Close dataset */'; put '%let rc = %sysfunc(close(&dsid));'; put '/* Return variable format */'; put '&vformat'; put '%mend mf_getVarFormat;'; put '%macro mp_getmaxvarlengths('; put 'libds'; put ',num2char=NO'; put ',outds=work.mp_getmaxvarlengths'; put ')/*/STORE SOURCE*/;'; put '%local vars prefix x var fmt srcds;'; put '%let vars=%mf_getvarlist(libds=&libds);'; put '%let prefix=%substr(%mf_getuniquename(),1,25);'; put '%let num2char=%upcase(&num2char);'; put '%if &num2char=NO %then %do;'; put '/* compile length function for numeric fields */'; put '%mcf_length(wrap=YES, insert_cmplib=YES)'; put '%end;'; put '%if &num2char=NO'; put 'and ("%substr(&sysver,1,1)"="4" or "%substr(&sysver,1,1)"="5")'; put 'and %mf_getvarcount(&libds,typefilter=N) gt 0'; put '%then %do;'; put '/* custom functions not supported in summary operations */'; put '%let srcds=%mf_getuniquename();'; put 'data &srcds/view=&srcds;'; put 'set &libds;'; put '%do x=1 %to %sysfunc(countw(&vars,%str( )));'; put '%let var=%scan(&vars,&x);'; put '%if %mf_getvartype(&libds,&var)=N %then %do;'; put '&prefix.&x=mcf_length(&var);'; put '%end;'; put '%end;'; put 'run;'; put '%end;'; put '%else %let srcds=&libds;'; put 'proc sql;'; put 'create table &outds (rename=('; put '%do x=1 %to %sysfunc(countw(&vars,%str( )));'; put '&prefix.&x=%scan(&vars,&x)'; put '%end;'; put '))'; put 'as select'; put '%do x=1 %to %sysfunc(countw(&vars,%str( )));'; put '%let var=%scan(&vars,&x);'; put '%if &x>1 %then ,;'; put '%if %mf_getvartype(&libds,&var)=C %then %do;'; put 'max(lengthn(&var)) as &prefix.&x'; put '%end;'; put '%else %if &num2char=YES %then %do;'; put '%let fmt=%mf_getvarformat(&libds,&var);'; put '%put fmt=&fmt;'; put '%if %str(&fmt)=%str() %then %do;'; put 'max(lengthn(cats(&var))) as &prefix.&x'; put '%end;'; put '%else %do;'; put 'max(lengthn(put(&var,&fmt))) as &prefix.&x'; put '%end;'; put '%end;'; put '%else %do;'; put '%if "%substr(&sysver,1,1)"="4" or "%substr(&sysver,1,1)"="5" %then %do;'; put 'max(&prefix.&x) as &prefix.&x'; put '%end;'; put '%else %do;'; put 'max(mcf_length(&var)) as &prefix.&x'; put '%end;'; put '%end;'; put '%end;'; put 'from &srcds;'; put 'proc transpose data=&outds'; put 'out=&outds(rename=(_name_=NAME COL1=MAXLEN));'; put 'run;'; put '%mend mp_getmaxvarlengths;'; put '%macro mp_validatecol(incol,rule,outcol);'; put '/* tempcol is given a unique name with every invocation */'; put '%local tempcol;'; put '%let tempcol=%mf_getuniquename();'; put '%if &rule=ISINT %then %do;'; put '&outcol=0;'; put 'if not missing(&incol) then do;'; put '&tempcol=input(&incol,?? best32.);'; put 'if not missing(&tempcol) then if mod(&tempcol,1)=0 then &outcol=1;'; put 'end;'; put 'drop &tempcol;'; put '%end;'; put '%else %if &rule=ISNUM %then %do;'; put '/*'; put 'credit SOREN LASSEN'; put 'https://sasmacro.blogspot.com/2009/06/welcome-isnum-macro.html'; put '*/'; put '&tempcol=input(&incol,?? best32.);'; put 'if missing(&tempcol) then &outcol=0;'; put 'else &outcol=1;'; put 'drop &tempcol;'; put '%end;'; put '%else %if &rule=LIBDS %then %do;'; put '/* match libref.dataset */'; put 'if _n_=1 then do;'; put 'retain &tempcol;'; put '&tempcol=prxparse(''/^[_a-z]\w{0,7}\.[_a-z]\w{0,31}$/i'');'; put 'if missing(&tempcol) then do;'; put 'putlog ''ERR'' +(-1) "OR: Invalid expression for LIBDS";'; put 'stop;'; put 'end;'; put 'drop &tempcol;'; put 'end;'; put 'if prxmatch(&tempcol, trim(&incol)) then &outcol=1;'; put 'else &outcol=0;'; put '%end;'; put '%else %if &rule=FORMAT %then %do;'; put '/* match valid format - regex could probably be improved */'; put 'if _n_=1 then do;'; put 'retain &tempcol;'; put '&tempcol=prxparse(''/^[_a-z\$]\w{0,31}\.[0-9]*$/i'');'; put 'if missing(&tempcol) then do;'; put 'putlog ''ERR'' +(-1) "OR: Invalid expression for FORMAT";'; put 'stop;'; put 'end;'; put 'drop &tempcol;'; put 'end;'; put 'if prxmatch(&tempcol, trim(&incol)) then &outcol=1;'; put 'else &outcol=0;'; put '%end;'; put '%mend mp_validatecol;'; put '* SAS Macros end;'; put '* SAS Includes start;'; put '* SAS Includes end;'; put '* Binary Files start;'; put '* Binary Files end;'; put '* ServiceInit start;'; put 'options noquotelenmax ps=max;'; put '/* create dummy macros to prevent stpbegin / stpend from corrupting sessions */'; put '%macro stpbegin();'; put '%put NOTE: the STPBEGIN macro should not be used for web apps!;'; put '%mend stpbegin;'; put '%macro stpend();'; put '%put NOTE: the STPEND macro should not be used for web apps!;'; put '%mend stpend;'; put '* ServiceInit end;'; put '* Service start;'; put '/**'; put '@file getdata.sas'; put '@brief Returns a dataset to the editor front end'; put '@details'; put '

Service Inputs

'; put '
SASCONTROLTABLE
'; put '|LIBDS:$41.|FILTER_RK:$5.|'; put '|---|---|'; put '|DC258467.MPE_X_TEST|-1|'; put '

Service Outputs

'; put '
sasdata
'; put '
sasparams
'; put 'Contains info on the request. One row is returned.'; put '@li CLS_FLG - set to 0 if there are no CLS rules (everything editable)'; put 'else set to 1 (CLS rules exist)'; put '@li ISMAP - set to 1 if the target DS is an excel map target, else 0'; put '
approvers
'; put '
dqrules
'; put '
dqdata
'; put '
cols
'; put 'Contains column level attributes.'; put '@li NAME - column name'; put '@li VARNUM - variable position. Source: https://core.sasjs.io/mp__getcols_8sas.html'; put '@li LABEL - variable label. Source: https://core.sasjs.io/mp__getcols_8sas.html'; put '@li FMTNAME - derived format name. Source: https://core.sasjs.io/mp__getcols_8sas.html'; put '@li DDTYPE - derived dropdown type. Source: https://core.sasjs.io/mp__getcols_8sas.html'; put '@li CLS_RULE - values include:'; put '- EDIT - the column is editable'; put '- READ - the column should be readonly'; put '- HIDE - the column should be hidden'; put '@li memlabel'; put '@li desc- augmented with MPE_DATADICTIONARY if exists, else label'; put '@li longdesc - from MPE_DATADICTIONARY'; put '
maxvarlengths
'; put '
xl_rules
'; put '
query
'; put '

SAS Macros

'; put '@li dc_assignlib.sas'; put '@li dc_getgroupmembers.sas'; put '@li mf_existvar.sas'; put '@li mf_getattrn.sas'; put '@li mf_getvarlist.sas'; put '@li mf_existds.sas'; put '@li mf_getquotedstr.sas'; put '@li mf_getuser.sas'; put '@li mf_nobs.sas'; put '@li mf_verifymacvars.sas'; put '@li mf_wordsinstr1butnotstr2.sas'; put '@li mp_abort.sas'; put '@li mp_cntlout.sas'; put '@li mp_getcols.sas'; put '@li mp_getmaxvarlengths.sas'; put '@li mp_validatecol.sas'; put '@li mpe_accesscheck.sas'; put '@li mpe_columnlevelsecurity.sas'; put '@li mpe_dsmeta.sas'; put '@li mpe_getlabels.sas'; put '@li mpe_filtermaster.sas'; put '@li mpe_runhook.sas'; put '@version 9.2'; put '@author 4GL Apps Ltd'; put '@copyright 4GL Apps Ltd. This code may only be used within Data Controller'; put 'and may not be re-distributed or re-sold without the express permission of'; put '4GL Apps Ltd.'; put '**/'; put '%mpeinit()'; put '/**'; put '* Validate inputs'; put '*/'; put 'data work.intest;'; put 'length filter_rk 8;'; put 'set work.SASCONTROLTABLE;'; put '/* validate filter_rk */'; put 'if filter_rk le 0 then filter_rk=-1;'; put 'call symputx(''orig_libds'',upcase(libds));'; put 'is_fmt=0;'; put 'if substr(cats(reverse(libds)),1,3)=:''CF-'' then do;'; put 'libds=scan(libds,1,''-'');'; put 'putlog "Format Catalog Captured";'; put 'is_fmt=1;'; put 'libds=''work.fmtextract'';'; put 'call symputx(''libds'',libds);'; put 'end;'; put 'call symputx(''is_fmt'',is_fmt);'; put 'putlog (_all_)(=);'; put '/* validate libds */'; put '%mp_validatecol(LIBDS,LIBDS,is_libds)'; put 'if is_libds=0 then do;'; put 'putlog ''ERR'' ''OR: Invalid libds:'' libds;'; put 'stop;'; put 'end;'; put 'else do;'; put 'call symputx(''filter_rk'',filter_rk);'; put 'call symputx(''libds'',libds);'; put 'end;'; put 'output;'; put 'stop;'; put 'run;'; put '%mp_abort(iftrue= (%mf_nobs(work.intest)=0)'; put ',mac=&_program'; put ',msg=%str(Some err with service inputs)'; put ')'; put '%mp_abort('; put 'iftrue=(%mf_verifymacvars(libds filter_rk)=0)'; put ',mac=&_program'; put ',msg=%str(Missing: libds filter_rk)'; put ')'; put '/* export format catalog */'; put '%mp_cntlout('; put 'iftrue=(&is_fmt=1)'; put ',libcat=&orig_libds'; put ',fmtlist=0'; put ',cntlout=work.fmtextract'; put ')'; put '/* stream back meta info, further calls will return col metadata and actual data'; put '*/'; put '%let libref=%upcase(%scan(&libds,1,.));'; put '%let dsn=%upcase(%scan(&libds,2,.));'; put '%dc_assignlib(WRITE,&libref)'; put '/**'; put '* First check user has access permission to edit the table'; put '*/'; put '%put checking access;'; put '%let user=%mf_getuser();'; put '%mpe_accesscheck(&orig_libds,outds=mw_auth,user=&user,access_level=EDIT)'; put '%mp_abort(iftrue= (%mf_getattrn(work.mw_auth,NLOBS)=0)'; put ',mac=mpestp_getdata.sas'; put ',msg=&user is not authorised to edit &orig_libds %trim('; put ')in the &mpelib..MPE_SECURITY table'; put ')'; put '%mp_abort(iftrue= ( %mf_existds(libds=&libds) ne 1)'; put ',mac=mpestp_getdata.sas'; put ',msg=dataset &libds does not exist!!'; put ')'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(syscc=&syscc at line 60 )'; put ')'; put '%global loadtype var_txfrom var_txto var_processed filter_text pk coltype'; put 'sortpk;'; put '%put getting table attributes;'; put 'proc sql noprint;'; put 'select upcase(loadtype)'; put ',var_txfrom,var_txto'; put ',var_busfrom,var_busto'; put ',var_processed,rk_underlying,buskey'; put ',coalesce(rk_underlying,buskey)'; put ',pre_edit_hook'; put ',case when missing(rk_underlying) then buskey else rk_underlying end'; put 'into: loadtype,:var_txfrom,:var_txto'; put ',:var_busfrom ,:var_busto'; put ',:var_processed,:rk_underlying,:buskey, :sortPK, :pre_edit_hook,:pk'; put 'from &mpelib..mpe_tables'; put 'where &dc_dttmtfmt. lt TX_TO'; put 'and upcase(dsn)="%scan(&orig_libds,2,.)"'; put 'and upcase(libref)="%scan(&orig_libds,1,.)";'; put '%put preparing filter query:;'; put '%mpe_filtermaster(EDIT,&orig_libds,'; put 'dclib=&mpelib,'; put 'filter_rk=&filter_rk,'; put 'outref=filtref,'; put 'outds=work.query'; put ')'; put '%macro mpestp_getdata();'; put '%if not %symexist(DC_MAXOBS_WEBEDIT) %then %do;'; put '%put NOTE:;%put NOTE- DC_MAXOBS_WEBEDIT not found!;'; put '%put NOTE- Please add to &mpelib..MPE_CONFIG table;'; put '%put NOTE-;%put NOTE-;'; put '%global DC_MAXOBS_WEBEDIT;'; put '%let DC_MAXOBS_WEBEDIT=500;'; put '%end;'; put '/* for tables which use RKs/SKs then we just expose the business key to'; put 'users - this lets uploads be sent to multiple environments (with'; put 'potentially different RK/SK values for the same business key).'; put 'Note that the config table has the RK column in the buskey field in'; put 'this scenario. */'; put '%if %length(&rk_underlying)>0 %then %let drop_rk=&buskey;'; put '%else %let drop_rk=;'; put '/* always remove the PROCESSED_DTTM column, if it exists */'; put '%if %length(&var_processed)=0 %then %do;'; put '%if %mf_existvar(&libds,PROCESSED_DTTM)>0 %then'; put '%let var_processed=PROCESSED_DTTM;'; put '%end;'; put '/**'; put '* Now get the slice of the actual table'; put '*/'; put 'options obs=10000;'; put '%if &loadtype=BITEMPORAL %then %do;'; put 'data out (drop=&var_txfrom &var_txto &var_processed &drop_rk );'; put '_____DELETE__THIS__RECORD_____="No";'; put 'set &libds;'; put 'where %inc filtref;;'; put 'run;'; put 'proc sort data=out;'; put 'by &pk &var_busfrom;'; put 'run;'; put 'data out;'; put 'set out;'; put 'by &pk &var_busfrom;'; put 'if last.%scan(&pk,-1);'; put 'run;'; put '%end;'; put '%else %do;'; put 'data out (drop=&var_txfrom &var_txto &var_processed &drop_rk);'; put '_____DELETE__THIS__RECORD_____="No";'; put 'set &libds;'; put 'where %inc filtref;;'; put 'run;'; put '%end;'; put 'options obs=max;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(Issue with filtering (line 165) )'; put ')'; put 'options obs=&DC_MAXOBS_WEBEDIT;'; put '%let sortpk=%sysfunc(coalescec(&sortpk &var_busfrom,_ALL_));'; put 'proc sort data=work.out; by &sortPK; run;'; put 'options obs=max;'; put '%mpe_runhook(PRE_EDIT_HOOK)'; put '%let obscnt=%mf_getattrn(work.out,NLOBS);'; put '%mp_abort(iftrue=(&obscnt>&DC_MAXOBS_WEBEDIT)'; put ',mac=&_program'; put ',msg=Table is too big (&obscnt rows) - please filter and try again!'; put ')'; put '/* order delete var and pk fields at start of table */'; put '%let sourcevars=%mf_wordsInStr1ButNotStr2('; put 'Str1=%mf_getvarlist(work.out)'; put ',Str2= _____DELETE__THIS__RECORD_____ &pk'; put ');'; put '%put sourcevars=&sourcevars;'; put 'data outdata;'; put '/* delete & pk fields come first */'; put 'attrib _____DELETE__THIS__RECORD_____ &pk label='''';'; put '/* keep remaining variable order */'; put '%if %length(&sourcevars)>0 %then %do;'; put 'attrib &sourcevars label='''';'; put '%end;'; put '_____DELETE__THIS__RECORD_____="No ";'; put '%if %mf_nobs(work.out)=0 %then %do;'; put '/* send empty row if empty table to help with hot rendering */'; put 'output;'; put '%end;'; put 'set work.out ;'; put 'run;'; put '/* get list of variables and their formats */'; put 'proc contents noprint data=outdata'; put 'out=vars(keep=name type length varnum format: label);'; put 'run;'; put 'proc sort;'; put 'by varnum;'; put 'run;'; put 'data vars3(keep=name type length format label pk varnum ctrloptions formatd);'; put 'set vars(rename=(format=format2 type=type2));'; put 'name=upcase(name);'; put '/* not interested in transaction or processing dates'; put '(append table must be supplied without them) */'; put 'if name not in ("&VAR_TXFROM","&VAR_TXTO","&VAR_PROCESSED");'; put 'if type2=2 or type2=6 then do;'; put 'length format $49.;'; put 'if format2='''' then format=cats(''$'',length,''.'');'; put 'else format=cats(format2,formatl,''.'');'; put 'type=''char'';'; put 'end;'; put 'else do;'; put 'if format2='''' then format=cats(length,''.'');'; put 'else if upcase(format2)=''DATETIME'' and formatl=0 then format=''DATETIME.'';'; put 'else format=cats(format2,formatl,''.'',formatd);'; put 'type=''num'';'; put 'end;'; put 'if name in ('''',%upcase(%mf_getQuotedStr(&pk,dlm=%str(,),quote=S)))'; put 'then PK=''YES'';'; put 'length ctrlOptions $500;'; put 'if name="_____DELETE__THIS__RECORD_____" then ctrlOptions=''["No","Yes"]'';'; put 'else ctrlOptions='''';'; put 'run;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(syscc=&syscc at 242 (vars3 step) in &_program \n'; put '%superq(syserrortext)'; put ')'; put ')'; put '%global jsdttmvars jsdtvars jstmvars;'; put 'data _null_;'; put 'set vars3 end=last;'; put 'if _n_>1 then comma='','';'; put 'length coltype $500.;'; put 'format=upcase(format);'; put 'coltype=cats(comma,''{"data":"'',name,''"'');'; put 'if ctrlOptions ne '''' then'; put 'colType=cats(coltype,'',"type":"dropdown","source":'',ctrlOptions,"}");'; put 'else if type=''num'' then do;'; put 'if format=:''DATETIME'' or format=:''E8601DT'' then do;'; put 'colType=cats(coltype'; put ','',"type":"date","dateFormat":"YYYY-MM-DD HH:mm:ss"'''; put ','',"correctFormat":"true"}'');'; put '/* build var list to reformat datetimes in javascript format */'; put 'call symput(''jsdttmvars'',symget(''jsdttmvars'')!!'' ''!!name);'; put 'end;'; put 'else if format=:''DATE'' or format=:''DDMMYY'' or format=:''MMDDYY'''; put 'or format=:''YYMMDD'' or format=:''E8601DA'' or format=:''B8601DA'''; put 'or format=:''MONYY'''; put 'then do;'; put '/* see bottom of file for more date formats!! */'; put '/* also when updating, update stagedata.sas and mp_getcols.sas'; put 'and mpe_loader.sas */'; put 'colType=cats(coltype,'',"type":"date","dateFormat":"YYYY-MM-DD"'''; put '/*colType=cats(coltype,'',"type":"date","dateFormat":"MM/DD/YYYY"''*/'; put ','',"correctFormat":"true"}'');'; put '/* build var list to reformat as javascript dates */'; put 'call symput(''jsdtvars'',symget(''jsdtvars'')!!'' ''!!name);'; put 'end;'; put 'else if format=:''TIME'' or format=:''HHMM'' then do;'; put 'colType=cats(coltype,'',"type":"time","timeFormat":"HH:mm:ss"'''; put ','',"correctFormat":"true"}'');'; put '/* build var list to reformat as javascript times */'; put 'call symput(''jstmvars'',symget(''jstmvars'')!!'' ''!!name);'; put 'end;'; put 'else do;'; put '/* is standard numeric but need to ascertain precision */'; put 'retain base ''000000000000000000'';'; put 'if formatd>0 then numFormat=cats(''.'',substr(base,1,formatd));'; put 'colType=cats(coltype,'',"type":"numeric","format":"0'',numFormat,''"}'');'; put 'end;'; put 'end;'; put 'else colType=cats(coltype,''}'');'; put 'length concatcoltype $32767;'; put 'retain concatcoltype;'; put 'concatcoltype=cats(concatcoltype,coltype);'; put 'if last then call symputx(''colType'',strip(concatcoltype),''g'');'; put 'putlog (_all_)(=);'; put 'run;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(syscc=&syscc at 283 (null step) in &_program)'; put ')'; put 'PROC FORMAT;'; put 'picture yymmddThhmmss (default=28) other=''%0Y-%0m-%0d %0H:%0M:%0s'''; put '(datatype=datetime);'; put 'picture JSyymmdd other=''%0Y-%0m-%0d'' (datatype=date);'; put 'picture JShhmmss (default=16) other=''%0H:%0M:%0s'' (datatype=time);'; put 'RUN;'; put '/* before we send the data, need to rebuild all date & datetime vars as char*/'; put '%let finalvars=%mf_getvarlist(work.outdata);'; put 'data sasdata;'; put '/* set formats & col order ahead of rename+import */'; put 'informat &finalvars ;'; put '/* read dataset and rename date / datetime vars as necessary */'; put 'set outdata'; put '%if %length(&jsdttmvars&jsdtvars&jstmvars)>0 %then %do;'; put '(rename=('; put '%local dtvarnum dtvar tmvar;'; put '/* temp datetime vars end in _____ */'; put '%do dtvarnum=1 %to %sysfunc(countw(&jsdttmvars,%str( )));'; put '%let dtvar=%scan(&jsdttmvars ,&dtvarnum);'; put '&dtvar=_____&dtvarnum._____'; put '%end;'; put '/* temp date vars do not end in _____ */'; put '%do dtvarnum=1 %to %sysfunc(countw(&jsdtvars,%str( )));'; put '%let dtvar=%scan( &jsdtvars,&dtvarnum);'; put '&dtvar=_____&dtvarnum'; put '%end;'; put '/* temp time vars end in ___tm */'; put '%do tmvarnum=1 %to %sysfunc(countw(&jstmvars,%str( )));'; put '%let tmvar=%scan( &jstmvars,&tmvarnum);'; put '&tmvar=_____&tmvarnum.___tm'; put '%end;'; put '))'; put '%end;'; put ';'; put '%if %length(&jsdttmvars)>0 %then %do ;'; put '%do dtvarnum=1 %to %sysfunc(countw(&jsdttmvars,%str( )));'; put '%let dtvar=%scan(&jsdttmvars,&dtvarnum);'; put '&dtvar=cats(put(_____&dtvarnum._____,yymmddThhmmss28.));'; put 'if &dtvar="ERROR" then call missing(&dtvar);'; put 'drop _____&dtvarnum._____;'; put '%end;'; put '%end;'; put '%if %length(&jsdtvars)>0 %then %do;'; put '%do dtvarnum=1 %to %sysfunc(countw(&jsdtvars,%str( )));'; put '%let dtvar=%scan(&jsdtvars,&dtvarnum);'; put '&dtvar=cats(put(_____&dtvarnum,JSyymmdd.));'; put 'if &dtvar="ERROR" then call missing(&dtvar);'; put 'drop _____&dtvarnum;'; put '%end;'; put '%end;'; put '%if %length(&jstmvars)>0 %then %do;'; put '%do tmvarnum=1 %to %sysfunc(countw(&jstmvars,%str( )));'; put '%let tmvar=%scan(&jstmvars,&tmvarnum);'; put '&tmvar=cats(put(_____&tmvarnum.___tm,JShhmmss14.));'; put 'if &tmvar="ERROR" then call missing(&tmvar);'; put 'drop _____&tmvarnum.___tm;'; put '%end;'; put '%end;'; put 'output;'; put 'run;'; put '/* get the relevant approvers for the drop down */'; put '%put getting approvers;'; put '%local sas_groups sas_i sas_group;'; put 'proc sql noprint;'; put 'select distinct sas_Group into: sas_groups separated by "|"'; put 'from &mpelib..mpe_security'; put 'where libref="%scan(&orig_libds,1,.)"'; put 'and dsn="%scan(&orig_libds,2,.)"'; put 'and access_level=''APPROVE'''; put 'and &dc_dttmtfmt. lt TX_TO;'; put '%if %length(&sas_groups)=0 %then %do;'; put '%dc_getgroupmembers(&dc_admin_group,outds=work.access1)'; put '%end;'; put '%else %do sas_i=1 %to %sysfunc(countw(&sas_groups,%str(|)));'; put '%let sas_group=%scan(&sas_Groups,&sas_i,%str(|));'; put '%dc_getgroupmembers(&sas_group,outds=work.temp&sas_i)'; put 'proc append base=work.access1 data=work.temp&sas_i;run;'; put '%end;'; put '%mend mpestp_getdata;'; put '%mpestp_getdata()'; put '%mp_abort(mode=INCLUDE)'; put '/* extract column level security rules */'; put '%mpe_columnlevelsecurity(%scan(&libds,1,.),%scan(&libds,2,.),work.sasdata'; put ',mode=EDIT'; put ',clsds=&mpelib..mpe_column_level_security'; put ',groupds=work.groups /* was created in mpe_filtermaster */'; put ',outds=work.sasdata1'; put ',outmeta=work.cls_rules'; put ')'; put '/* get labels */'; put '%mpe_getlabels(COLUMNS,sasdata1,outds=spec)'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc extracting spec info)'; put ')'; put '/* extract col info */'; put '%mp_getcols(&libds, outds=cols1)'; put '/* join with cls rules */'; put 'proc sql;'; put 'create table work.cols as'; put 'select a.NAME'; put ',a.VARNUM'; put ',a.LABEL'; put ',a.FMTNAME'; put ',a.DDTYPE'; put ',case b.cls_hide'; put 'when 1 then ''HIDE'''; put 'when 0 then ''EDIT'''; put 'else ''READ'' end as CLS_RULE'; put ',c.memlabel'; put ',c.desc'; put ',c.longdesc'; put 'from work.cols1 a'; put 'left join work.cls_rules b'; put 'on a.NAME=b.CLS_VARIABLE_NM'; put 'left join work.spec c'; put 'on a.NAME=c.NAME;'; put 'proc sql;'; put 'create table approvers as select distinct membername as personname'; put ',membername as email, membername as userid'; put 'from work.access1;'; put '/*'; put 'create table access3 as select b.userid,b.email'; put 'from access2 a'; put ',support.users b'; put 'where a.personname=b.userid'; put 'and a.personname ne "%mf_getuser()"'; put 'and %sysfunc(datetime()) lt b.tx_to_dttm'; put 'order by 1;'; put '*/'; put 'data _null_;'; put 'infile filtref end=eof;'; put 'input;'; put 'length filter_text $32767;'; put 'retain filter_text;'; put 'filter_text=catx('' '',filter_text,_infile_);'; put 'if eof then do;'; put 'if cats(filter_text)=''1=1'' then filter_text='''';'; put 'call symputx(''filter_text'',filter_text);'; put 'end;'; put 'run;'; put '%put params;'; put '%let ismap=0;'; put 'proc sql noprint;'; put 'select count(*) into: ismap from &mpelib..mpe_xlmap_info'; put 'where XLMAP_TARGETLIBDS="&orig_libds" and &dc_dttmtfmt. le TX_TO;'; put 'data sasparams;'; put 'length colHeaders $20000 filter_text $32767;'; put 'colHeaders=cats(upcase("%mf_getvarlist(sasdata1,dlm=%str(,))"));'; put 'pkCnt=countw("&pk");'; put 'pk="&pk";'; put 'dtvars=compbl("&jsdtvars");'; put 'dttmvars=compbl("&jsdttmvars");'; put 'tmvars=compbl("&jstmvars");'; put 'length coltype $32000;'; put 'coltype=symget(''coltype'');'; put 'loadtype=symget(''loadtype'');'; put 'if trim(symget(''rk_underlying'')) ne '''' then rk_flag=1;'; put 'else rk_flag=0;'; put 'filter_text=symget(''filter_text'');'; put 'if %mf_nobs(work.cls_rules)=0 then cls_flag=0;'; put 'else cls_flag=1;'; put 'put (_all_)(=);'; put 'if "&orig_libds"="&mpelib..MPE_XLMAP_DATA" or &ismap ne 0 then ismap=1;'; put 'else ismap=0;'; put 'run;'; put '/* Extract validation DQ Rules */'; put 'proc sort data=&mpelib..mpe_validations'; put '(where=(&dc_dttmtfmt. le TX_TO'; put 'and BASE_LIB="%scan(&orig_libds,1,.)" and BASE_DS="%scan(&orig_libds,2,.)"'; put 'and rule_active=1))'; put 'out=dqrules (keep=base_col rule_type rule_value);'; put 'by base_col rule_type rule_value;'; put 'run;'; put '/* merge with NOTNULL constraints in the physical table */'; put 'proc sql;'; put 'create table _data_ as'; put 'select * from dqrules'; put 'union'; put 'select upcase(name) as base_col'; put ',''NOTNULL'' as rule_type'; put ','''' as rule_value'; put 'from dictionary.columns'; put 'where upcase(libname)="%scan(&orig_libds,1,.)"'; put 'and upcase(memname)="%scan(&orig_libds,2,.)"'; put 'and upcase(name) in (select name from vars3)'; put 'and notnull=''yes'''; put 'order by 1,2,3;'; put 'data dqrules;'; put 'set &syslast;'; put 'by base_col rule_type rule_value;'; put 'if last.rule_type;'; put 'if rule_type in (''HARDSELECT'',''SOFTSELECT'') and countw(rule_value)=3 then'; put 'do;'; put 'retain x 0; x+1;'; put 'call symputx(cats(''source'',x),rule_value);'; put '%let sourcecnt=0;'; put 'call symputx(''sourcecnt'',x);'; put 'call symputx(cats(''base_col'',x),base_col);'; put 'end;'; put 'run;'; put 'proc sql;'; put 'create table dqdata as'; put 'select distinct base_column as base_col length=32'; put ',upcase(base_column) as rule_value length=74 /* deprecated */'; put ',selectbox_value as rule_data length=1000'; put ',selectbox_order'; put 'from &mpelib..mpe_selectbox'; put 'where &dc_dttmtfmt. lt ver_to_dttm'; put 'and select_lib="%scan(&orig_libds,1,.)"'; put 'and select_ds="%scan(&orig_libds,2,.)";'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc during DQ rule validation)'; put ')'; put '/* extract selectbox data */'; put '%macro dq_selects();'; put '%local x source lib ds col;'; put '%do x=1 %to &sourcecnt;'; put '%let source=&&source&x;'; put '%let lib=%scan(&source,1,.);'; put '%let ds=%scan(&source,2,.);'; put '%let col=%scan(&source,3,.);'; put '%put &=source;'; put '%put &=lib;'; put '%dc_assignlib(READ,&lib)'; put 'proc sql;'; put 'create table dqdata&x as'; put 'select distinct "&&base_col&x" as base_col length=32'; put ',"&source" as rule_value length=74'; put ',cats(&col) as rule_data length=1000'; put ',0 as selectbox_order'; put 'from &lib..&ds'; put 'order by 1;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc when selecting &&base_col&x from &orig_libds)'; put ')'; put 'proc append base=dqdata data=dqdata&x;run;'; put 'proc sql; drop table dqdata&x;'; put '%end;'; put '%mend dq_selects;'; put '%dq_selects()'; put 'proc sort data=dqdata;'; put '/* order by selectbox_order then the value */'; put 'by base_col selectbox_order rule_data;'; put 'run;'; put '%mp_getmaxvarlengths(work.sasdata1,outds=maxvarlengths)'; put 'data maxvarlengths;'; put 'set maxvarlengths;'; put 'if name=''_____DELETE__THIS__RECORD_____'' then mAXLEN=3;'; put 'run;'; put 'data xl_rules;'; put 'set &mpelib..mpe_excel_config;'; put 'where &dc_dttmtfmt. lt tx_to;'; put 'where also upcase(xl_libref)="%scan(&orig_libds,1,.)";'; put 'where also upcase(xl_table)="%scan(&orig_libds,2,.)";'; put 'where also xl_active=1;'; put 'keep xl_column xl_rule;'; put 'run;'; put '%mpe_dsmeta(&libds, outds=dsmeta)'; put '/* send to the client */'; put '%webout(OPEN)'; put '%webout(OBJ,approvers)'; put '%webout(OBJ,cols)'; put '%webout(OBJ,dqdata)'; put '%webout(OBJ,dqrules)'; put '%webout(OBJ,dsmeta)'; put '%webout(OBJ,maxvarlengths)'; put '%webout(OBJ,query)'; put '%webout(OBJ,sasdata1,fmt=N,missing=STRING,showmeta=YES,dslabel=sasdata)'; put '%webout(OBJ,sasparams)'; put '%webout(OBJ,xl_rules)'; put '%webout(CLOSE)'; put '/*'; put '$N8601Bw'; put '$N8601BAw'; put '$N8601Ew'; put '$N8601EAw'; put '$N8601EHw'; put '$N8601EXw'; put '$N8601Hw'; put '$N8601Xw'; put 'B8601DAw'; put 'B8601DNw'; put 'B8601DTw'; put 'B8601DZw'; put 'B8601LZw'; put 'B8601TMw'; put 'B8601TZw'; put 'DATEw'; put 'DATEAMPMw'; put 'DATETIMEw'; put 'DAYw'; put 'DDMMYYw'; put 'DDMMYYxw'; put 'DOWNAMEw'; put 'DTDATEw'; put 'DTMONYYw'; put 'DTWKDATXw'; put 'DTYEARw'; put 'DTYYQCw'; put 'E8601DAw'; put 'E8601DNw'; put 'E8601DTw'; put 'E8601DZw'; put 'E8601LZw'; put 'E8601TMw'; put 'E8601TZw'; put 'HHMMw'; put 'HOURw'; put 'JULDAYw'; put 'JULIANw'; put 'MMDDYYw'; put 'MMDDYYxw'; put 'MMSSw'; put 'MMYYw'; put 'MMYYxw'; put 'MONNAMEw'; put 'MONTHw'; put 'MONYYw'; put 'PDJULGw'; put 'PDJULIw'; put 'QTRw'; put 'QTRRw'; put 'TIMEw'; put 'TIMEAMPMw'; put 'TODw'; put 'WEEKDATEw'; put 'WEEKDATXw'; put 'WEEKDAYw'; put 'WEEKUw'; put 'WEEKVw'; put 'WEEKWw'; put 'WORDDATEw'; put 'WORDDATXw'; put 'YEARw'; put 'YYMMw'; put 'YYMMxw'; put 'YYMMDDw'; put 'YYMMDDxw'; put 'YYMONw'; put 'YYQw'; put 'YYQxw'; put 'YYQRw'; put 'YYQRxw'; put '$N8601BAw'; put '$N8601Ew'; put '$N8601EAw'; put '$N8601EHw'; put '$N8601EXw'; put '$N8601Hw'; put '$N8601Xw'; put 'B8601DAw'; put 'B8601DNw'; put 'B8601DTw'; put 'B8601DZw'; put 'B8601LZw'; put 'B8601TMw'; put 'B8601TZw'; put 'E8601DAw'; put 'E8601DNw'; put 'E8601DTw'; put 'E8601DZw'; put 'E8601LZw'; put 'E8601TMw'; put 'E8601TZw'; put '*/'; put '%mpeterm()'; put '* Service end;'; run; %mm_createwebservice(path=&appLoc/&path, name=&service, code=sascode, server=&serverName, replace=yes) filename sascode clear; %let service=getdynamiccolvals; filename sascode temp lrecl=32767; data _null_; file sascode; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '/**'; put '@file mp_jsonout.sas'; put '@brief Writes JSON in SASjs format to a fileref'; put '@details This macro can be used to OPEN a JSON stream and send one or more'; put 'tables as arrays of rows, where each row can be an object or a nested array.'; put 'There are two engines available - DATASTEP or PROCJSON.'; put 'PROC JSON is fast but will produce errs like the ones below if'; put 'special chars are encountered.'; put '> (ERR)OR: Some code points did not transcode.'; put '> An object or array close is not valid at this point in the JSON text.'; put '> Date value out of range'; put 'If this happens, try running with ENGINE=DATASTEP.'; put 'The DATASTEP engine is used to handle special SAS missing numerics, and'; put 'can also convert entire datasets to formatted values. Output JSON is always'; put 'in UTF-8.'; put 'Usage:'; put 'filename tmp temp;'; put 'data class; set sashelp.class;run;'; put '%mp_jsonout(OPEN,jref=tmp)'; put '%mp_jsonout(OBJ,class,jref=tmp)'; put '%mp_jsonout(OBJ,class,dslabel=class2,jref=tmp,showmeta=Y)'; put '%mp_jsonout(CLOSE,jref=tmp)'; put 'data _null_;'; put 'infile tmp;'; put 'input;putlog _infile_;'; put 'run;'; put 'If you are building web apps with SAS then you are strongly encouraged to use'; put 'the mX_createwebservice macros in combination with the'; put '[sasjs adapter](https://github.com/sasjs/adapter).'; put 'For more information see https://sasjs.io'; put '@param [in] action Valid values:'; put '@li OPEN - opens the JSON'; put '@li OBJ - sends a table with each row as an object'; put '@li ARR - sends a table with each row in an array'; put '@li CLOSE - closes the JSON'; put '@param [in] ds The dataset to send. Must be a work table.'; put '@param [out] jref= (_webout) The fileref to which to send the JSON'; put '@param [out] dslabel= The name to give the table in the exported JSON'; put '@param [in] fmt= (Y) Whether to keep (Y) or strip (N) formats from the table'; put '@param [in] engine= (DATASTEP) Which engine to use to send the JSON. Options:'; put '@li PROCJSON (default)'; put '@li DATASTEP (more reliable when data has non standard characters)'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to JSON'; put '

Related Files

'; put '@li mp_ds2fmtds.sas'; put '@version 9.2'; put '@author Allan Bowe'; put '@source https://github.com/sasjs/core'; put '**/'; put '%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y'; put ',engine=DATASTEP'; put ',missing=NULL'; put ',showmeta=N'; put ',maxobs=MAX'; put ')/*/STORE SOURCE*/;'; put '%local tempds colinfo fmtds i numcols numobs stmt_obs lastobs optval'; put 'tmpds1 tmpds2 tmpds3 tmpds4;'; put '%let numcols=0;'; put '%if &maxobs ne MAX %then %let stmt_obs=%str(if _n_>&maxobs then stop;);'; put '%if &action=OPEN %then %do;'; put 'options nobomfile;'; put 'data _null_;file &jref encoding=''utf-8'' lrecl=200;'; put 'put ''{"PROCESSED_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''"'';'; put 'run;'; put '%end;'; put '%else %if (&action=ARR or &action=OBJ) %then %do;'; put '/* force variable names to always be uppercase in the JSON */'; put 'options validvarname=upcase;'; put '/* To avoid issues with _webout on EBI - such as encoding diffs and truncation'; put '(https://support.sas.com/kb/49/325.html) we use temporary files */'; put 'filename _sjs1 temp lrecl=200 ;'; put 'data _null_; file _sjs1 encoding=''utf-8'';'; put 'put ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":";'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjs1 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs1 clear;'; put '/* grab col defs */'; put 'proc contents noprint data=&ds'; put 'out=_data_(keep=name type length format formatl formatd varnum label);'; put 'run;'; put '%let colinfo=%scan(&syslast,2,.);'; put 'proc sort data=&colinfo;'; put 'by varnum;'; put 'run;'; put '/* move meta to mac vars */'; put 'data &colinfo;'; put 'if _n_=1 then call symputx(''numcols'',nobs,''l'');'; put 'set &colinfo end=last nobs=nobs;'; put 'name=upcase(name);'; put '/* fix formats */'; put 'if type=2 or type=6 then do;'; put 'typelong=''char'';'; put 'length fmt $49.;'; put 'if format='''' then fmt=cats(''$'',length,''.'');'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else fmt=cats(format,formatl,''.'');'; put 'end;'; put 'else do;'; put 'typelong=''num'';'; put 'if format='''' then fmt=''best.'';'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else if formatd=0 then fmt=cats(format,formatl,''.'');'; put 'else fmt=cats(format,formatl,''.'',formatd);'; put 'end;'; put '/* 32 char unique name */'; put 'newname=''sasjs''!!substr(cats(put(md5(name),$hex32.)),1,27);'; put 'call symputx(cats(''name'',_n_),name,''l'');'; put 'call symputx(cats(''newname'',_n_),newname,''l'');'; put 'call symputx(cats(''length'',_n_),length,''l'');'; put 'call symputx(cats(''fmt'',_n_),fmt,''l'');'; put 'call symputx(cats(''type'',_n_),type,''l'');'; put 'call symputx(cats(''typelong'',_n_),typelong,''l'');'; put 'call symputx(cats(''label'',_n_),coalescec(label,name),''l'');'; put '/* overwritten when fmt=Y and a custom format exists in catalog */'; put 'if typelong=''num'' then call symputx(cats(''fmtlen'',_n_),200,''l'');'; put 'else call symputx(cats(''fmtlen'',_n_),min(32767,ceil((length+10)*1.5)),''l'');'; put 'run;'; put '%let tempds=%substr(_%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql;'; put 'select count(*) into: lastobs from &ds;'; put '%if &maxobs ne MAX %then %let lastobs=%sysfunc(min(&lastobs,&maxobs));'; put '%if &engine=PROCJSON %then %do;'; put '%if &missing=STRING %then %do;'; put '%put &sysmacroname: Special Missings not supported in proc json.;'; put '%put &sysmacroname: Switching to DATASTEP engine;'; put '%goto datastep;'; put '%end;'; put 'data &tempds;'; put 'set &ds;'; put '&stmt_obs;'; put '%if &fmt=N %then format _numeric_ best32.;;'; put '/* PRETTY is necessary to avoid line truncation in large files */'; put 'filename _sjs2 temp lrecl=131068 encoding=''utf-8'';'; put 'proc json out=_sjs2 pretty'; put '%if &action=ARR %then nokeys ;'; put ';export &tempds / nosastags fmtnumeric;'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs2 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs2 clear;'; put '%end;'; put '%else %if &engine=DATASTEP %then %do;'; put '%datastep:'; put '%if %sysfunc(exist(&ds)) ne 1 & %sysfunc(exist(&ds,VIEW)) ne 1'; put '%then %do;'; put '%put &sysmacroname: &ds NOT FOUND!!!;'; put '%return;'; put '%end;'; put '%if &fmt=Y %then %do;'; put '/**'; put '* Extract format definitions'; put '* First, by getting library locations from dictionary.formats'; put '* Then, by exporting the width using proc format'; put '* Cannot use maxw from sashelp.vformat as not always populated'; put '* Cannot use fmtinfo() as not supported in all flavours'; put '*/'; put '%let tmpds1=%substr(fmtsum%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds2=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds3=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds4=%substr(col%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql noprint;'; put 'create table &tmpds1 as'; put 'select cats(libname,''.'',memname) as FMTCAT,'; put 'FMTNAME'; put 'from dictionary.formats'; put 'where fmttype=''F'' and libname is not null'; put 'and fmtname in (select format from &colinfo where format is not null)'; put 'order by 1;'; put 'create table &tmpds2('; put 'FMTNAME char(32),'; put 'LENGTH num'; put ');'; put '%local catlist cat fmtlist i;'; put 'select distinct fmtcat into: catlist separated by '' '' from &tmpds1;'; put '%do i=1 %to %sysfunc(countw(&catlist,%str( )));'; put '%let cat=%scan(&catlist,&i,%str( ));'; put 'proc sql;'; put 'select distinct fmtname into: fmtlist separated by '' '''; put 'from &tmpds1 where fmtcat="&cat";'; put 'proc format lib=&cat cntlout=&tmpds3(keep=fmtname length);'; put 'select &fmtlist;'; put 'run;'; put 'proc sql;'; put 'insert into &tmpds2 select distinct fmtname,length from &tmpds3;'; put '%end;'; put 'proc sql;'; put 'create table &tmpds4 as'; put 'select a.*, b.length as MAXW'; put 'from &colinfo a'; put 'left join &tmpds2 b'; put 'on cats(a.format)=cats(upcase(b.fmtname))'; put 'order by a.varnum;'; put 'data _null_;'; put 'set &tmpds4;'; put 'if not missing(maxw);'; put 'call symputx('; put 'cats(''fmtlen'',_n_),'; put '/* vars need extra padding due to JSON escaping of special chars */'; put 'min(32767,ceil((max(length,maxw)+10)*1.5))'; put ',''l'''; put ');'; put 'run;'; put '/* configure varlenchk - as we are explicitly shortening the variables */'; put '%let optval=%sysfunc(getoption(varlenchk));'; put 'options varlenchk=NOWARN;'; put 'data _data_(compress=char);'; put '/* shorten the new vars */'; put 'length'; put '%do i=1 %to &numcols;'; put '&&name&i $&&fmtlen&i'; put '%end;'; put ';'; put '/* rename on entry */'; put 'set &ds(rename=('; put '%do i=1 %to &numcols;'; put '&&name&i=&&newname&i'; put '%end;'; put '));'; put '&stmt_obs;'; put 'drop'; put '%do i=1 %to &numcols;'; put '&&newname&i'; put '%end;'; put ';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=num %then %do;'; put '&&name&i=cats(put(&&newname&i,&&fmt&i));'; put '%end;'; put '%else %do;'; put '&&name&i=put(&&newname&i,&&fmt&i);'; put '%end;'; put '%end;'; put 'if _error_ then do;'; put 'call symputx(''syscc'',1012);'; put 'stop;'; put 'end;'; put 'run;'; put '%let fmtds=&syslast;'; put 'options varlenchk=&optval;'; put '%end;'; put 'proc format; /* credit yabwon for special null removal */'; put 'value bart (default=40)'; put '%if &missing=NULL %then %do;'; put '._ - .z = null'; put '%end;'; put '%else %do;'; put '._ = [quote()]'; put '. = null'; put '.a - .z = [quote()]'; put '%end;'; put 'other = [best.];'; put 'data &tempds;'; put 'attrib _all_ label='''';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'length &&name&i $&&fmtlen&i...;'; put 'format &&name&i $&&fmtlen&i...;'; put '%end;'; put '%end;'; put '%if &fmt=Y %then %do;'; put 'set &fmtds;'; put '%end;'; put '%else %do;'; put 'set &ds;'; put '%end;'; put '&stmt_obs;'; put 'format _numeric_ bart.;'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'if findc(&&name&i,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put '&&name&i=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,&&name&i)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else &&name&i=quote(cats(&&name&i));'; put '%end;'; put '%end;'; put 'run;'; put 'filename _sjs3 temp lrecl=131068 ;'; put 'data _null_;'; put 'file _sjs3 encoding=''utf-8'';'; put 'if _n_=1 then put "[";'; put 'set &tempds;'; put 'if _n_>1 then put "," @; put'; put '%if &action=ARR %then "[" ; %else "{" ;'; put '%do i=1 %to &numcols;'; put '%if &i>1 %then "," ;'; put '%if &action=OBJ %then """&&name&i"":" ;'; put '"&&name&i"n /* name literal for reserved variable names */'; put '%end;'; put '%if &action=ARR %then "]" ; %else "}" ; ;'; put '/* close out the table */'; put 'data _null_;'; put 'file _sjs3 mod encoding=''utf-8'';'; put 'put '']'';'; put 'run;'; put 'data _null_;'; put 'infile _sjs3 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs3 clear;'; put '%end;'; put 'proc sql;'; put 'drop table &colinfo, &tempds;'; put '%if %substr(&showmeta,1,1)=Y %then %do;'; put 'filename _sjs4 temp lrecl=131068 encoding=''utf-8'';'; put 'data _null_;'; put 'file _sjs4;'; put 'length label $350;'; put 'put ", ""$%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":{""vars"":{";'; put 'do i=1 to &numcols;'; put 'name=quote(trim(symget(cats(''name'',i))));'; put 'format=quote(trim(symget(cats(''fmt'',i))));'; put 'label=quote(prxchange(''s/\\/\\\\/'',-1,trim(symget(cats(''label'',i)))));'; put 'length=quote(trim(symget(cats(''length'',i))));'; put 'type=quote(trim(symget(cats(''typelong'',i))));'; put 'if i>1 then put "," @@;'; put 'put name '':{"format":'' format '',"label":'' label'; put ''',"length":'' length '',"type":'' type ''}'';'; put 'end;'; put 'put ''}}'';'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs4 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs4 clear;'; put '%end;'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put 'data _null_; file &jref encoding=''utf-8'' mod ;'; put 'put "}";'; put 'run;'; put '%end;'; put '%mend mp_jsonout;'; put '/**'; put '@file mm_webout.sas'; put '@brief Send data to/from SAS Stored Processes'; put '@details This macro should be added to the start of each Stored Process,'; put '**immediately** followed by a call to:'; put '%mm_webout(FETCH)'; put 'This will read all the input data and create same-named SAS datasets in the'; put 'WORK library. You can then insert your code, and send data back using the'; put 'following syntax:'; put 'data some datasets; * make some data ;'; put 'retain some columns;'; put 'run;'; put '%mm_webout(OPEN)'; put '%mm_webout(ARR,some) * Array format, fast, suitable for large tables ;'; put '%mm_webout(OBJ,datasets) * Object format, easier to work with ;'; put 'Finally, wrap everything up send some helpful system variables too'; put '%mm_webout(CLOSE)'; put '@param [in] action Either FETCH, OPEN, ARR, OBJ or CLOSE'; put '@param [in] ds The dataset to send back to the frontend'; put '@param [out] dslabel= Value to use instead of table name for sending to JSON'; put '@param [in] fmt= (N) Setting Y converts all vars to their formatted values'; put '@param [out] fref= (_webout) The fileref to which to write the JSON'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] workobs= (0) When set to a positive integer, will create a new'; put 'output object (WORK) which contains this number of observations from all'; put 'tables in the WORK library.'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to output JSON'; put '

SAS Macros

'; put '@li mp_jsonout.sas'; put '

Related Macros

'; put '@li ms_webout.sas'; put '@li mv_webout.sas'; put '@version 9.3'; put '@author Allan Bowe'; put '**/'; put '%macro mm_webout(action,ds,dslabel=,fref=_webout,fmt=N,missing=NULL'; put ',showmeta=N,maxobs=MAX,workobs=0'; put ');'; put '%global _webin_file_count _webin_fileref1 _webin_name1 _program _debug'; put 'sasjs_tables;'; put '%local i tempds jsonengine;'; put '/* see https://github.com/sasjs/core/issues/41 */'; put '%if "%upcase(&SYSENCODING)" ne "UTF-8" %then %let jsonengine=PROCJSON;'; put '%else %let jsonengine=DATASTEP;'; put '%if &action=FETCH %then %do;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'options mprint notes mprintnest;'; put '%end;'; put '%let _webin_file_count=%eval(&_webin_file_count+0);'; put '/* now read in the data */'; put '%do i=1 %to &_webin_file_count;'; put '%if &_webin_file_count=1 %then %do;'; put '%let _webin_fileref1=&_webin_fileref;'; put '%let _webin_name1=&_webin_name;'; put '%end;'; put 'data _null_;'; put 'infile &&_webin_fileref&i termstr=crlf;'; put 'input;'; put 'call symputx(''input_statement'',_infile_);'; put 'putlog "&&_webin_name&i input statement: " _infile_;'; put 'stop;'; put 'data &&_webin_name&i;'; put 'infile &&_webin_fileref&i firstobs=2 dsd termstr=crlf encoding=''utf-8'';'; put 'input &input_statement;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'if _n_<20 then putlog _infile_;'; put '%end;'; put 'run;'; put '%let sasjs_tables=&sasjs_tables &&_webin_name&i;'; put '%end;'; put '%end;'; put '%else %if &action=OPEN %then %do;'; put '/* fix encoding */'; put 'OPTIONS NOBOMFILE;'; put '/**'; put '* check xengine type to avoid the below err message:'; put '* > Function is only valid for filerefs using the CACHE access method.'; put '*/'; put 'data _null_;'; put 'set sashelp.vextfl(where=(fileref="_WEBOUT"));'; put 'if xengine=''STREAM'' then do;'; put 'rc=stpsrv_header(''Content-type'',"text/html; encoding=utf-8");'; put 'end;'; put 'run;'; put '/* setup json */'; put 'data _null_;file &fref encoding=''utf-8'';'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutBEGIN<<'';'; put '%end;'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'run;'; put '%end;'; put '%else %if &action=ARR or &action=OBJ %then %do;'; put '%mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt,jref=&fref'; put ',engine=&jsonengine,missing=&missing,showmeta=&showmeta,maxobs=&maxobs'; put ')'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put '/* To avoid issues with _webout on EBI we use a temporary file */'; put 'filename _sjsref temp lrecl=131068;'; put '%if %str(&workobs) > 0 %then %do;'; put '/* if debug mode, send back first XX records of each work table also */'; put 'data;run;%let tempds=%scan(&syslast,2,.);'; put 'ods output Members=&tempds;'; put 'proc datasets library=WORK memtype=data;'; put '%local wtcnt;%let wtcnt=0;'; put 'data _null_;'; put 'set &tempds;'; put 'if not (upcase(name) =:"DATA"); /* ignore temp datasets */'; put 'i+1;'; put 'call symputx(cats(''wt'',i),name,''l'');'; put 'call symputx(''wtcnt'',i,''l'');'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put ",""WORK"":{";'; put '%do i=1 %to &wtcnt;'; put '%let wt=&&wt&i;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'dsid=open("WORK.&wt",''is'');'; put 'nlobs=attrn(dsid,''NLOBS'');'; put 'nvars=attrn(dsid,''NVARS'');'; put 'rc=close(dsid);'; put 'if &i>1 then put '',''@;'; put 'put " ""&wt"" : {";'; put 'put ''"nlobs":'' nlobs;'; put 'put '',"nvars":'' nvars;'; put '%mp_jsonout(OBJ,&wt,jref=_sjsref,dslabel=first10rows,showmeta=Y'; put ',maxobs=&workobs'; put ')'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put '%end;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put 'run;'; put '%end;'; put '/* close off json */'; put 'data _null_;file _sjsref mod encoding=''utf-8'';'; put 'length SYSPROCESSNAME syserrortext syswarningtext autoexec $512;'; put 'put ",""_DEBUG"" : ""&_debug"" ";'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'autoexec=quote(urlencode(trim(getoption(''autoexec''))));'; put 'put '',"AUTOEXEC" : '' autoexec;'; put 'put ",""MF_GETUSER"" : ""%mf_getuser()"" ";'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'put ",""SYSENCODING"" : ""&sysencoding"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSPROCESSID"" : ""&SYSPROCESSID"" ";'; put 'put ",""SYSPROCESSMODE"" : ""&SYSPROCESSMODE"" ";'; put 'SYSPROCESSNAME=quote(urlencode(cats(SYSPROCESSNAME)));'; put 'put ",""SYSPROCESSNAME"" : " SYSPROCESSNAME;'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put '',"SYSWARNINGTEXT" : '' syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'length memsize $32;'; put 'memsize="%sysfunc(INPUTN(%sysfunc(getoption(memsize)), best.),sizekmg.)";'; put 'memsize=quote(cats(memsize));'; put 'put '',"MEMSIZE" : '' memsize;'; put 'put "}" @;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutEND<<'';'; put '%end;'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjsref lrecl=1 recfm=n;'; put 'file &fref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjsref clear;'; put '%end;'; put '%mend mm_webout;'; put '%macro webout(action,ds,dslabel=,fmt=,missing=NULL,showmeta=NO,maxobs=MAX);'; put '%mm_webout(&action,ds=&ds,dslabel=&dslabel,fmt=&fmt'; put ',missing=&missing'; put ',showmeta=&showmeta'; put ',maxobs=&maxobs'; put ') %mend;'; put '/* provide additional debug info */'; put '%global _program;'; put '%put &=syscc;'; put '%put user=%mf_getuser();'; put '%put pgm=&_program;'; put '%put timestamp=%sysfunc(datetime(),datetime19.);'; put '* Service Variables start;'; put '* Service Variables end;'; put '* SAS Macros start;'; put '%macro mf_getapploc(pgm);'; put '%if "&pgm"="" %then %do;'; put '%if %symexist(_program) %then %let pgm=&_program;'; put '%else %do;'; put '%put &sysmacroname: No value provided and no _program variable available;'; put '%return;'; put '%end;'; put '%end;'; put '%local root;'; put '/**'; put '* First check we are not in the tests/macros folder (which has no subfolders)'; put '* or specifically in the testsetup or testteardown services'; put '*/'; put '%if %index(&pgm,/tests/macros/)'; put 'or %index(&pgm,/tests/testsetup)'; put 'or %index(&pgm,/tests/testteardown)'; put '%then %do;'; put '%let root=%substr(&pgm,1,%index(&pgm,/tests)-1);'; put '&root'; put '%return;'; put '%end;'; put '/**'; put '* Next, move up two levels to avoid matches on subfolder or service name'; put '*/'; put '%let root=%substr(&pgm,1,%length(&pgm)-%length(%scan(&pgm,-1,/))-1);'; put '%let root=%substr(&root,1,%length(&root)-%length(%scan(&root,-1,/))-1);'; put '%if %index(&root,/tests/) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/tests/)-1);'; put '%end;'; put '%else %if %index(&root,/services) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/services)-1);'; put '%end;'; put '%else %if %index(&root,/jobs) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/jobs)-1);'; put '%end;'; put '%else %put &sysmacroname: Could not find an app location from &pgm;'; put '&root'; put '%mend mf_getapploc ;'; put '%macro mf_getuniquefileref(prefix=_,maxtries=1000,lrecl=32767);'; put '%local rc fname;'; put '%if &prefix=0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%end;'; put '%else %do;'; put '%local x len;'; put '%let len=%eval(8-%length(&prefix));'; put '%let x=0;'; put '%do x=0 %to &maxtries;'; put '%let fname=&prefix%substr(%sysfunc(ranuni(0)),3,&len);'; put '%if %sysfunc(fileref(&fname)) > 0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%return;'; put '%end;'; put '%end;'; put '%put unable to find available fileref after &maxtries attempts;'; put '%end;'; put '%mend mf_getuniquefileref;'; put '%macro mp_abort(mac=mp_abort.sas, type=, msg=, iftrue=%str(1=1)'; put ', errds=work.mp_abort_errds'; put ', mode=REGULAR'; put ')/*/STORE SOURCE*/;'; put '%global sysprocessmode sysprocessname sasjs_stpsrv_header_loc sasjsprocessmode;'; put '%local fref fid i;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%put NOTE: /// mp_abort macro executing //;'; put '%if %length(&mac)>0 %then %put NOTE- called by &mac;'; put '%put NOTE - &msg;'; put '%if %symexist(_SYSINCLUDEFILEDEVICE)'; put '/* abort cancel FILE does not restart outside the INCLUDE on Viya 3.5 */'; put 'and %superq(SYSPROCESSNAME) ne %str(Compute Server)'; put '%then %do;'; put '%if "*&_SYSINCLUDEFILEDEVICE*" ne "**" %then %do;'; put 'data &errds;'; put 'iftrue=''1=1'';'; put 'length mac $100 msg $5000;'; put 'mac=symget(''mac'');'; put 'msg=symget(''msg'');'; put 'run;'; put 'data _null_;'; put 'abort cancel FILE;'; put 'run;'; put '%return;'; put '%end;'; put '%end;'; put '/* Web App Context */'; put '%if %symexist(_PROGRAM)'; put 'or %superq(SYSPROCESSNAME) = %str(Compute Server)'; put 'or &mode=INCLUDE'; put '%then %do;'; put 'options obs=max replace mprint;'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5"'; put '%then %do;'; put 'options nosyntaxcheck;'; put '%end;'; put '%if &mode=INCLUDE %then %do;'; put '%if %sysfunc(exist(&errds))=1 %then %do;'; put 'data _null_;'; put 'set &errds;'; put 'call symputx(''iftrue'',iftrue,''l'');'; put 'call symputx(''mac'',mac,''l'');'; put 'call symputx(''msg'',msg,''l'');'; put 'putlog (_all_)(=);'; put 'run;'; put '%if (&iftrue)=0 %then %return;'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: No include errors found;'; put '%return;'; put '%end;'; put '%end;'; put '/* extract log errs / warns, if exist */'; put '%local logloc logline;'; put '%global logmsg; /* capture global messages */'; put '%if %symexist(SYSPRINTTOLOG) %then %let logloc=&SYSPRINTTOLOG;'; put '%else %let logloc=%qsysfunc(getoption(LOG));'; put 'proc printto log=log;run;'; put '%let logline=0;'; put '%if %length(&logloc)>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input; putlog _infile_;'; put 'i=1;'; put 'retain logonce 0;'; put 'if ('; put '_infile_=:"%str(WARN)ING" or _infile_=:"%str(ERR)OR"'; put ') and logonce=0 then'; put 'do;'; put 'call symputx(''logline'',_n_);'; put 'logonce+1;'; put 'end;'; put 'run;'; put '/* capture log including lines BEFORE the err */'; put '%if &logline>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input;'; put 'i=1;'; put 'stoploop=0;'; put 'if _n_ ge &logline-15 and stoploop=0 then do until (i>22);'; put 'call symputx(''logmsg'',catx(''\n'',symget(''logmsg''),_infile_));'; put 'input;'; put 'i+1;'; put 'stoploop=1;'; put 'end;'; put 'if stoploop=1 then stop;'; put 'run;'; put '%end;'; put '%end;'; put '%if %symexist(SYS_JES_JOB_URI) %then %do;'; put '/* setup webout for Viya */'; put 'options nobomfile;'; put '%if "X&SYS_JES_JOB_URI.X"="XX" %then %do;'; put 'filename _webout temp lrecl=999999 mod;'; put '%end;'; put '%else %do;'; put 'filename _webout filesrvc parenturi="&SYS_JES_JOB_URI"'; put 'name="_webout.json" lrecl=999999 mod;'; put '%end;'; put '%end;'; put '%else %if %sysfunc(filename(fref,&sasjs_stpsrv_header_loc))=0 %then %do;'; put 'options nobomfile;'; put '/* set up http header for SASjs Server */'; put '%let fid=%sysfunc(fopen(&fref,A));'; put '%if &fid=0 %then %do;'; put '%put %str(ERR)OR: %sysfunc(sysmsg());'; put '%return;'; put '%end;'; put '%let rc=%sysfunc(fput(&fid,%str(Content-Type: application/json)));'; put '%let rc=%sysfunc(fwrite(&fid));'; put '%let rc=%sysfunc(fclose(&fid));'; put '%let rc=%sysfunc(filename(&fref));'; put '%end;'; put '/* send response in SASjs JSON format */'; put 'data _null_;'; put 'file _webout mod lrecl=32000 encoding=''utf-8'';'; put 'length msg syswarningtext syserrortext $32767 mode $10 ;'; put 'sasdatetime=datetime();'; put 'msg=symget(''msg'');'; put '%if &logline>0 %then %do;'; put 'msg=cats(msg,''\n\nLog Extract:\n'',symget(''logmsg''));'; put '%end;'; put '/* escape the escapes */'; put 'msg=tranwrd(msg,''\'',''\\'');'; put '/* escape the quotes */'; put 'msg=tranwrd(msg,''"'',''\"'');'; put '/* ditch the CRLFs as chrome complains */'; put 'msg=compress(msg,,''kw'');'; put '/* quote without quoting the quotes (which are escaped instead) */'; put 'msg=cats(''"'',msg,''"'');'; put 'if symexist(''_debug'') then debug=quote(trim(symget(''_debug'')));'; put 'else debug=''""'';'; put 'if symget(''sasjsprocessmode'')=''Stored Program'' then mode=''SASJS'';'; put 'if mode ne ''SASJS'' then put ''>>weboutBEGIN<<'';'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'put '',"sasjsAbort" : [{'';'; put 'put '' "MSG":'' msg ;'; put 'put '' ,"MAC": "'' "&mac" ''"}]'';'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'put '',"_DEBUG":'' debug ;'; put 'if symexist(''_metauser'') then do;'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put 'end;'; put 'if symexist(''SYS_JES_JOB_URI'') then do;'; put 'SYS_JES_JOB_URI=quote(trim(symget(''SYS_JES_JOB_URI'')));'; put 'put '',"SYS_JES_JOB_URI": '' SYS_JES_JOB_URI;'; put 'end;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put ",""SYSWARNINGTEXT"" : " syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'put "}" ;'; put 'if mode ne ''SASJS'' then put ''>>weboutEND<<'';'; put 'run;'; put '%put _all_;'; put '%if "&sysprocessmode " = "SAS Stored Process Server " %then %do;'; put 'data _null_;'; put 'putlog ''stpsrvset program err and syscc'';'; put 'rc=stpsrvset(''program error'', 0);'; put 'call symputx("syscc",0,"g");'; put 'run;'; put '%if &sysscp=WIN'; put 'and 1=0 /* deprecating this logic until we figure out a consistent abort */'; put 'and "%substr(%str(&sysvlong ),1,8)"="9.04.01M"'; put 'and "%substr(%str(&sysvlong ),9,1)">"5" %then %do;'; put '/* skip approach (below) does not work in windows m6+ envs */'; put 'endsas;'; put '%end;'; put '%else %do;'; put '/**'; put '* endsas kills 9.4m3 deployments by orphaning multibridges.'; put '* Abort variants are ungraceful (non zero return code)'; put '* This approach lets SAS run silently until the end :-)'; put '* Caution - fails when called within a %include within a macro'; put '* Use mp_include() to handle this.'; put '*/'; put 'filename skip temp;'; put 'data _null_;'; put 'file skip;'; put 'put ''%macro skip();'';'; put 'comment ''%mend skip; -> fix lint '';'; put 'put ''%macro skippy();'';'; put 'comment ''%mend skippy; -> fix lint '';'; put 'run;'; put '%inc skip;'; put '%end;'; put '%end;'; put '%else %if "&sysprocessmode " = "SAS Compute Server " %then %do;'; put '/* endsas kills the session making it harder to fetch results */'; put 'data _null_;'; put 'syswarningtext=symget(''syswarningtext'');'; put 'syserrortext=symget(''syserrortext'');'; put 'abort_msg=symget(''msg'');'; put 'syscc=symget(''syscc'');'; put 'sysuserid=symget(''sysuserid'');'; put 'iftrue=symget(''iftrue'');'; put 'put (_all_)(/=);'; put 'call symputx(''syscc'',0);'; put 'abort cancel nolist;'; put 'run;'; put '%end;'; put '%else %do;'; put '%abort cancel;'; put '%end;'; put '%end;'; put '%else %do;'; put '%put _all_;'; put '%abort cancel;'; put '%end;'; put '%mend mp_abort;'; put '/** @endcond */'; put '%macro mm_getstpcode('; put 'tree=/User Folders/sasdemo/somestp'; put ',name='; put ',outloc=0'; put ',outref=0'; put ',mDebug=1'; put ',showlog=NO'; put ');'; put '%local mD;'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing &sysmacroname..sas;'; put '%&mD.put _local_;'; put '%if %length(&name)>0 %then %let name=/&name;'; put '/* first, check if STP exists */'; put '%local tsuri;'; put '%let tsuri=stopifempty ;'; put 'data _null_;'; put 'format type uri tsuri value $200.;'; put 'call missing (of _all_);'; put 'path="&tree&name(StoredProcess)";'; put '/* first, find the STP ID */'; put 'if metadata_pathobj("",path,"StoredProcess",type,uri)>0 then do;'; put '/* get sourcecode */'; put 'cnt=1;'; put 'do while (metadata_getnasn(uri,"Notes",cnt,tsuri)>0);'; put 'rc=metadata_getattr(tsuri,"Name",value);'; put '&mD.put tsuri= value=;'; put 'if value="SourceCode" then do;'; put '/* found it! */'; put 'rc=metadata_getattr(tsuri,"Id",value);'; put 'call symputx(''tsuri'',value,''l'');'; put 'stop;'; put 'end;'; put 'cnt+1;'; put 'end;'; put 'end;'; put 'else put (_all_)(=);'; put 'run;'; put '%mp_abort(iftrue= (&tsuri=stopifempty)'; put ',mac=mm_getstpcode'; put ',msg=%str(&tree&name.(StoredProcess) not found!)'; put ')'; put '/**'; put '* Now we can extract the textstore'; put '*/'; put 'filename __getdoc temp lrecl=10000000;'; put 'proc metadata'; put 'in="$METAREPOSITORY'; put ''; put 'SAS1"'; put 'out=__getdoc ;'; put 'run;'; put '/* find the beginning of the text */'; put '%local start;'; put 'data _null_;'; put 'infile __getdoc lrecl=10000;'; put 'input;'; put 'start=index(_infile_,''StoredText="'');'; put 'if start then do;'; put 'call symputx("start",start+11);'; put '*putlog ''"'' _infile_ ''"'';'; put 'end;'; put 'stop;'; put '%local outeng;'; put '%if "&outloc"="0" %then %let outeng=TEMP;'; put '%else %let outeng="&outloc";'; put '%local fref;'; put '%if &outref=0 %then %let fref=%mf_getuniquefileref();'; put '%else %let fref=&outref;'; put '/* read the content, byte by byte, resolving escaped chars */'; put 'filename &fref &outeng lrecl=100000;'; put 'data _null_;'; put 'length filein 8 fileid 8;'; put 'filein = fopen("__getdoc","I",1,"B");'; put 'fileid = fopen("&fref","O",1,"B");'; put 'rec = "20"x;'; put 'length entity $6;'; put 'do while(fread(filein)=0);'; put 'x+1;'; put 'if x>&start then do;'; put 'rc = fget(filein,rec,1);'; put 'if rec=''"'' then leave;'; put 'else if rec="&" then do;'; put 'entity=rec;'; put 'do until (rec=";");'; put 'if fread(filein) ne 0 then goto getout;'; put 'rc = fget(filein,rec,1);'; put 'entity=cats(entity,rec);'; put 'end;'; put 'select (entity);'; put 'when (''&'' ) rec=''&'' ;'; put 'when (''<'' ) rec=''<'' ;'; put 'when (''>'' ) rec=''>'' ;'; put 'when (''''') rec="''" ;'; put 'when (''"'') rec=''"'' ;'; put 'when ('' '') rec=''0A''x;'; put 'when ('' '') rec=''0D''x;'; put 'when (''$'' ) rec=''$'' ;'; put 'when ('' '') rec=''09''x;'; put 'otherwise putlog "%str(WARN)ING: missing value for " entity=;'; put 'end;'; put 'rc =fput(fileid, substr(rec,1,1));'; put 'rc =fwrite(fileid);'; put 'end;'; put 'else do;'; put 'rc =fput(fileid,rec);'; put 'rc =fwrite(fileid);'; put 'end;'; put 'end;'; put 'end;'; put 'getout:'; put 'rc=fclose(filein);'; put 'rc=fclose(fileid);'; put 'run;'; put '%if &showlog=YES %then %do;'; put 'data _null_;'; put 'infile &fref lrecl=32767 end=last;'; put 'input;'; put 'if _n_=1 then putlog ''>>stpcodeBEGIN<<'';'; put 'putlog _infile_;'; put 'if last then putlog ''>>stpcodeEND<<'';'; put 'run;'; put '%end;'; put 'filename __getdoc clear;'; put '%if &outref=0 %then %do;'; put 'filename &fref clear;'; put '%end;'; put '%mend mm_getstpcode;'; put '%macro dc_getsettings();'; put '%global _program;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(syscc=&syscc on entry (&syswarningtext &syserrortext))'; put ')'; put '%if %length(&_PROGRAM)>1 %then %let root=&_program;'; put '%else %do;'; put '%global _metauser;'; put '%let _metauser=&sysuserid;'; put '/* to mimic a "real" _program we need to give a dummy role and stp name */'; put '%let root=/dummyRole/dummyName;'; put '%end;'; put '/* the DC precode is stored in the Admin folder in the root of'; put 'the project. Lets find that root. */'; put '%put &=root;'; put '%let root=%mf_getapploc();'; put '%put &=root;'; put '/* Now we know the root location we can retrieve the params */'; put '%let temploc=%sysfunc(pathname(work))/settings.txt;'; put '%mm_getstpcode(tree=&root/services/public'; put ',name=Data_Controller_Settings'; put ',outloc=&temploc'; put ')'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Unable to run getstpcode)'; put ')'; put 'filename _getsets "&temploc" lrecl=2000;'; put '/*'; put 'Do not use mp_include here - this puts a copy in every service, which creates'; put 'compilation problems when calling services from mp_include'; put '*/'; put '%inc _getsets/source2;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Problem running &sysmacroname (&syswarningtext &syserrortext))'; put ')'; put '%mend dc_getsettings;'; put '%macro mf_fmtdttm('; put ')/*/STORE SOURCE*/;'; put '%if "&sysver"="9.2" or "&sysver"="9.3"'; put 'or ("&sysver"="9.4" and "%substr(&SYSVLONG,9,1)" le "3")'; put 'or "%substr(&sysver,1,1)"="4"'; put 'or "%substr(&sysver,1,1)"="5"'; put '%then %do;DATETIME19.3%end;'; put '%else %do;E8601DT26.6%end;'; put '%mend mf_fmtdttm;'; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '%macro mp_init(prefix=SASJS'; put ')/*/STORE SOURCE*/;'; put '%if %symexist(SASJS_PREFIX) %then %return; /* only run once */'; put '%global'; put 'SASJS_PREFIX /* the ONLY hard-coded global macro variable in SASjs */'; put '&prefix._FUNCTIONS /* used in mcf_init() to track core function compilation */'; put '&prefix._INIT_NUM /* initialisation time as numeric */'; put '&prefix._INIT_DTTM /* initialisation time in E8601DT26.6 format */'; put '&prefix.WORK /* avoid typing %sysfunc(pathname(work)) every time */'; put ';'; put '%let sasjs_prefix=&prefix;'; put 'data _null_;'; put 'dttm=datetime();'; put 'call symputx("&sasjs_prefix._init_num",dttm,''g'');'; put 'call symputx("&sasjs_prefix._init_dttm",put(dttm,E8601DT26.6),''g'');'; put 'call symputx("&sasjs_prefix.work",pathname(''WORK''),''g'');'; put 'run;'; put 'options'; put 'compress=CHAR /* default is none so ensure we have something! */'; put 'datastmtchk=ALLKEYWORDS /* protection from overwriting input datasets */'; put 'errorcheck=STRICT /* catch errs in libname/filename statements */'; put 'fmterr /* ensure err when a format cannot be found */'; put 'mergenoby=%str(ERR)OR /* throw err when a merge has no BY variables */'; put 'missing=. /* changing this can cause hard to detect errs */'; put 'noquotelenmax /* avoid warnings for long strings */'; put 'noreplace /* avoid overwriting permanent datasets */'; put 'ps=max /* reduce log size slightly */'; put 'ls=max /* reduce log even more and avoid word truncation */'; put 'validmemname=COMPATIBLE /* avoid special characters etc in table names */'; put 'validvarname=V7 /* avoid special characters etc in variable names */'; put 'varinitchk=%str(ERR)OR /* avoid data mistakes from variable name typos */'; put 'varlenchk=%str(ERR)OR /* fail hard if truncation (data loss) can result */'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5" %then %do;'; put 'noautocorrect /* disallow misspelled procedure names */'; put 'dsoptions=note2err /* undocumented - convert bad NOTEs to ERRs */'; put '%end;'; put ';'; put '%mend mp_init;'; put '%macro mpeinit(fetch=YES);'; put '%global mpeinit'; put 'mpeadmins /* group with unrestricted Meditor access */'; put 'mpelocapprovals /* location for landing and staging files */'; put 'mpelib /* location of configuration tables for DC */'; put 'dc_repo_users /* location of user / group metadata */'; put 'dc_licence_key /* extracted in dc_getsettings */'; put 'dc_activation_key /* extracted in dc_getsettings */'; put 'dc_locale /* extracted in dc_getsettings */'; put 'dc_dttmtfmt /* can be overridden in dc_getsettings */'; put '_debug'; put ';'; put '%if &mpeinit=1 %then %return;'; put '%else %let mpeinit=1;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(Problem on service startup (&syswarningtext &syserrortext))'; put ')'; put '%mp_init()'; put '%if &fetch=YES %then %do;'; put '%webout(FETCH)'; put '%end;'; put '%global _CLIENTNAME;'; put '%mp_abort(iftrue= (&_CLIENTNAME=SAS Enterprise Guide)'; put ',mac=&_program..sas'; put ',msg=%str(Data Controller is a web app and should not be executed from EG)'; put ')'; put 'options urlencoding=utf8 nobomfile lrecl=32767;'; put '%let perf=%sysfunc(datetime());'; put '%put perfdiff: 0;'; put '%let dc_locale=SYSTEM; /* default if not set */'; put '/**'; put '* E8601DT26.6 has widest database support - but not all SAS flavours can'; put '* handle it. Override in the settings STP if needed.'; put '*/'; put 'data _null_;'; put 'dc_dttmtfmt=''"%sysfunc(datetime(),''!!"%mf_fmtdttm()"!!'')"dt'';'; put 'call symputx(''dc_dttmtfmt'',dc_dttmtfmt);'; put 'put dc_dttmtfmt=;'; put 'run;'; put '%put &=dc_dttmtfmt;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc prior to dc_getsettings)'; put ')'; put '%dc_getsettings()'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc after dc_getsettings)'; put ')'; put 'data _null_;'; put 'set &DC_LIBREF..mpe_config(where=('; put 'var_scope="DC"'; put 'and &dc_dttmtfmt lt tx_to'; put 'and var_active=1'; put '));'; put 'call symputx(var_name,var_value,''G'');'; put 'putlog var_name "=" var_value;'; put 'run;'; put '%let mpelib=&dc_libref;'; put '%let mpeadmins=&dc_admin_group;'; put '%let mpelocapprovals=&dc_staging_area;'; put '%let dc_repo_users=&dc_repo_users;'; put '%if &dc_locale ne SYSTEM %then %do;'; put 'options locale=&dc_locale;'; put '%end;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(Problem during compilation or with STP precode (&syswarningtext))'; put ')'; put '%mend mpeinit;'; put '%macro mf_mval(var);'; put '%if %symexist(&var) %then %do;'; put '%superq(&var)'; put '%end;'; put '%mend mf_mval;'; put '%macro mf_trimstr(basestr,trimstr);'; put '%local baselen trimlen trimval;'; put '/* return if basestr is shorter than trimstr (or 0) */'; put '%let baselen=%length(%superq(basestr));'; put '%let trimlen=%length(%superq(trimstr));'; put '%if &baselen < &trimlen or &baselen=0 %then %return;'; put '/* obtain the characters from the end of basestr */'; put '%let trimval=%qsubstr(%superq(basestr)'; put ',%length(%superq(basestr))-&trimlen+1'; put ',&trimlen);'; put '/* compare and if matching, chop it off! */'; put '%if %superq(basestr)=%superq(trimstr) %then %do;'; put '%return;'; put '%end;'; put '%else %if %superq(trimval)=%superq(trimstr) %then %do;'; put '%qsubstr(%superq(basestr),1,%length(%superq(basestr))-&trimlen)'; put '%end;'; put '%else %do;'; put '&basestr'; put '%end;'; put '%mend mf_trimstr;'; put '%macro mf_getplatform(switch'; put ')/*/STORE SOURCE*/;'; put '%local a b c;'; put '%if &switch.NONE=NONE %then %do;'; put '%if %symexist(sasjsprocessmode) %then %do;'; put '%if &sasjsprocessmode=Stored Program %then %do;'; put 'SASJS'; put '%return;'; put '%end;'; put '%end;'; put '%if %symexist(sysprocessmode) %then %do;'; put '%if "&sysprocessmode"="SAS Object Server"'; put 'or "&sysprocessmode"= "SAS Compute Server" %then %do;'; put 'SASVIYA'; put '%end;'; put '%else %if "&sysprocessmode"="SAS Stored Process Server"'; put 'or "&sysprocessmode"="SAS Workspace Server"'; put '%then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if %symexist(_metaport) or %symexist(_metauser) %then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if &switch=SASSTUDIO %then %do;'; put '/* return the version of SAS Studio else 0 */'; put '%if %mf_mval(_CLIENTAPP)=%str(SAS Studio) %then %do;'; put '%let a=%mf_mval(_CLIENTVERSION);'; put '%let b=%scan(&a,1,.);'; put '%if %eval(&b >2) %then %do;'; put '&b'; put '%end;'; put '%else 0;'; put '%end;'; put '%else 0;'; put '%end;'; put '%else %if &switch=VIYARESTAPI %then %do;'; put '%mf_trimstr(%sysfunc(getoption(servicesbaseurl)),/)'; put '%end;'; put '%mend mf_getplatform;'; put '%macro mpeterm();'; put '%local oldloc;'; put 'data _null_;'; put 'if symexist(''SYSPRINTTOLOG'') then oldloc=symget(''SYSPRINTTOLOG'');'; put 'else oldloc=getoption(''LOG'');'; put 'if subpad(oldloc,1,1) not in (''"'',"''",'' '') then oldloc=quote(cats(oldloc));'; put 'call symputx(''oldloc'',oldloc,''l'');'; put 'run;'; put '%if %length(&oldloc)>0 %then %do;'; put 'proc printto log=log;'; put 'run;'; put 'data _null_;'; put 'infile &oldloc;'; put 'input; putlog _infile_;'; put 'run;'; put '%end;'; put '%if %sysfunc(exist(&dc_libref..mpe_requests)) and %mf_getplatform() ne SASVIYA'; put '%then %do;'; put 'data ;'; put 'if 0 then set &dc_libref..mpe_requests;'; put 'request_dttm=%sysfunc(datetime());'; put 'request_user="%mf_getuser()";'; put 'request_service="%scan(&_program,-2,/)/%scan(&_program,-1,/)";'; put 'request_params='''';'; put 'output;stop;'; put 'proc append base=&dc_libref..mpe_requests data=&syslast force nowarn;'; put 'run;'; put '%end;'; put '%mend mpeterm;'; put '%macro mm_assignlib('; put 'libref'; put ',mAbort=HARD'; put ')/*/STORE SOURCE*/;'; put '%local mp_abort msg;'; put '%let mp_abort=0;'; put '%if %sysfunc(libref(&libref)) %then %do;'; put 'data _null_;'; put 'length liburi LibName msg $200;'; put 'call missing(of _all_);'; put 'nobj=metadata_getnobj("omsobj:SASLibrary?@Libref=''&libref''",1,liburi);'; put 'if nobj=1 then do;'; put 'rc=metadata_getattr(liburi,"Name",LibName);'; put '/* now try and assign it */'; put 'if libname("&libref",,''meta'',cats(''liburi="'',liburi,''";'')) ne 0 then do;'; put 'putlog "&libref could not be assigned";'; put 'putlog liburi=;'; put '/**'; put '* Fetch the system message for display in the abort modal. This is'; put '* not always helpful though. One example, previously received:'; put '* NOTE: Libref XX refers to the same library metadata as libref XX.'; put '*/'; put 'msg=sysmsg();'; put 'if msg=:''ERROR: Libref SAVE is not assigned.'' then do;'; put 'msg=catx(" ",'; put '"Could not assign %upcase(&libref).",'; put '"Please check metadata permissions! Libname:",libname,'; put '"Liburi:",liburi'; put ');'; put 'end;'; put 'else if msg="ERROR: User does not have appropriate authorization "!!'; put '"level for library SAVE."'; put 'then do;'; put 'msg=catx(" ",'; put '"ERROR: User does not have appropriate authorization level",'; put '"for library %upcase(&libref), libname:",libname,'; put '"Liburi:",liburi'; put ');'; put 'end;'; put 'call symputx(''msg'',msg,''l'');'; put 'if "&mabort"=''HARD'' then call symputx(''mp_abort'',1,''l'');'; put 'end;'; put 'else do;'; put 'put (_all_)(=);'; put 'call symputx(''libname'',libname,''L'');'; put 'call symputx(''liburi'',liburi,''L'');'; put 'end;'; put 'end;'; put 'else if nobj>1 then do;'; put 'if "&mabort"=''HARD'' then call symputx(''mp_abort'',1);'; put 'call symputx(''msg'',"More than one library with libref=&libref");'; put 'end;'; put 'else do;'; put 'if "&mabort"=''HARD'' then call symputx(''mp_abort'',1);'; put 'call symputx(''msg'',"Library &libref not found in metadata");'; put 'end;'; put 'run;'; put '%put NOTE: &msg;'; put '%end;'; put '%else %do;'; put '%put NOTE: Library &libref is already assigned;'; put '%end;'; put '%mp_abort(iftrue= (&mp_abort=1)'; put ',mac=mm_assignlib.sas'; put ',msg=%superq(msg)'; put ')'; put '%mend mm_assignlib;'; put '/** @cond */'; put '%macro mf_getengine(libref'; put ')/*/STORE SOURCE*/;'; put '%local dsid engnum rc engine;'; put '/* in case the parameter is a libref.tablename, pull off just the libref */'; put '%let libref = %upcase(%scan(&libref, 1, %str(.)));'; put '%let dsid=%sysfunc('; put 'open(sashelp.vlibnam(where=(libname="%upcase(&libref)")),i)'; put ');'; put '%if (&dsid ^= 0) %then %do;'; put '%let engnum=%sysfunc(varnum(&dsid,ENGINE));'; put '%let rc=%sysfunc(fetch(&dsid));'; put '%let engine=%sysfunc(getvarc(&dsid,&engnum));'; put '%put &libref. ENGINE is &engine.;'; put '%let rc= %sysfunc(close(&dsid));'; put '%end;'; put '%upcase(&engine)'; put '%mend mf_getengine;'; put '/** @endcond */'; put '%macro mm_assigndirectlib('; put 'libref'; put ',open_passthrough='; put ',sql_options='; put ',mDebug=0'; put ',mAbort=0'; put ')/*/STORE SOURCE*/;'; put '%local mD;'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing mm_assigndirectlib.sas;'; put '%&mD.put _local_;'; put '%if &mAbort=1 %then %let mAbort=;'; put '%else %let mAbort=%str(*);'; put '%&mD.put NOTE: Creating direct (non META) connection to &libref library;'; put '%local cur_engine;'; put '%let cur_engine=%mf_getengine(&libref);'; put '%if &cur_engine ne META and &cur_engine ne %then %do;'; put '%put NOTE: &libref already has a direct (&cur_engine) libname connection;'; put '%return;'; put '%end;'; put '%else %if %upcase(&libref)=WORK %then %do;'; put '%put NOTE: We already have a direct connection to WORK :-) ;'; put '%return;'; put '%end;'; put '/* need to determine the library ENGINE first */'; put '%local engine;'; put 'data _null_;'; put 'length lib_uri engine $256;'; put 'call missing (of _all_);'; put '/* get URI for the particular library */'; put 'rc1=metadata_getnobj("omsobj:SASLibrary?@Libref =''&libref''",1,lib_uri);'; put '/* get the Engine attribute of the previous object */'; put 'rc2=metadata_getattr(lib_uri,''Engine'',engine);'; put 'putlog "mm_assigndirectlib for &libref:" rc1= lib_uri= rc2= engine=;'; put 'call symputx("liburi",lib_uri,''l'');'; put 'call symputx("engine",engine,''l'');'; put 'run;'; put '/* now obtain engine specific connection details */'; put '%if &engine=BASE %then %do;'; put '%&mD.put NOTE: Retrieving BASE library path;'; put 'data _null_;'; put 'length up_uri $256 path cat_path $1024;'; put 'retain cat_path;'; put 'call missing (of _all_);'; put '/* get all the filepaths of the UsingPackages association */'; put 'i=1;'; put 'rc3=metadata_getnasn("&liburi",''UsingPackages'',i,up_uri);'; put 'do while (rc3>0);'; put '/* get the DirectoryName attribute of the previous object */'; put 'rc4=metadata_getattr(up_uri,''DirectoryName'',path);'; put 'if i=1 then path = ''("''!!trim(path)!!''" '';'; put 'else path ='' "''!!trim(path)!!''" '';'; put 'cat_path = trim(cat_path) !! " " !! trim(path) ;'; put 'i+1;'; put 'rc3=metadata_getnasn("&liburi",''UsingPackages'',i,up_uri);'; put 'end;'; put 'cat_path = trim(cat_path) !! ")";'; put '&mD.putlog "NOTE: Getting physical path for &libref library";'; put '&mD.putlog rc3= up_uri= rc4= cat_path= path=;'; put '&mD.putlog "NOTE: Libname cmd will be:";'; put '&mD.putlog "libname &libref" cat_path;'; put 'call symputx("filepath",cat_path,''l'');'; put 'run;'; put '%if %sysevalf(&sysver<9.4) %then %do;'; put 'libname &libref &filepath;'; put '%end;'; put '%else %do;'; put '/* apply the new filelocks option to cater for temporary locks */'; put 'libname &libref &filepath filelockwait=5;'; put '%end;'; put '%end;'; put '%else %if &engine=REMOTE %then %do;'; put 'data x;'; put 'length rcCon rcProp rc k 3 uriCon uriProp PropertyValue PropertyName'; put 'Delimiter $256 properties $2048;'; put 'retain properties;'; put 'rcCon = metadata_getnasn("&liburi", "LibraryConnection", 1, uriCon);'; put 'rcProp = metadata_getnasn(uriCon, "Properties", 1, uriProp);'; put 'k = 1;'; put 'rcProp = metadata_getnasn(uriCon, "Properties", k, uriProp);'; put 'do while (rcProp > 0);'; put 'rc = metadata_getattr(uriProp , "DefaultValue",PropertyValue);'; put 'rc = metadata_getattr(uriProp , "PropertyName",PropertyName);'; put 'rc = metadata_getattr(uriProp , "Delimiter",Delimiter);'; put 'properties = trim(properties) !! " " !! trim(PropertyName)'; put '!! trim(Delimiter) !! trim(PropertyValue);'; put 'output;'; put 'k+1;'; put 'rcProp = metadata_getnasn(uriCon, "Properties", k, uriProp);'; put 'end;'; put '%&mD.put NOTE: Getting properties for REMOTE SHARE &libref library;'; put '&mD.put _all_;'; put '%&mD.put NOTE: Libname cmd will be:;'; put '%&mD.put libname &libref &engine &properties slibref=&libref;'; put 'call symputx ("properties",trim(properties),''l'');'; put 'run;'; put 'libname &libref &engine &properties slibref=&libref;'; put '%end;'; put '%else %if &engine=OLEDB %then %do;'; put '%&mD.put NOTE: Retrieving OLEDB connection details;'; put 'data _null_;'; put 'length domain datasource provider properties schema'; put 'connx_uri domain_uri conprop_uri lib_uri schema_uri value $256.;'; put 'call missing (of _all_);'; put '/* get source connection ID */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,connx_uri);'; put '/* get connection domain */'; put 'rc1=metadata_getnasn(connx_uri,''Domain'',1,domain_uri);'; put 'rc2=metadata_getattr(domain_uri,''Name'',domain);'; put '&mD.putlog / ''NOTE: '' // ''NOTE- connection id: '' connx_uri ;'; put '&mD.putlog ''NOTE- domain: '' domain;'; put '/* get DSN and PROVIDER from connection properties */'; put 'i=0;'; put 'do until (rc<0);'; put 'i+1;'; put 'rc=metadata_getnasn(connx_uri,''Properties'',i,conprop_uri);'; put 'rc2=metadata_getattr(conprop_uri,''Name'',value);'; put 'if value=''Connection.OLE.Property.DATASOURCE.Name.xmlKey.txt'' then do;'; put 'rc3=metadata_getattr(conprop_uri,''DefaultValue'',datasource);'; put 'end;'; put 'else if value=''Connection.OLE.Property.PROVIDER.Name.xmlKey.txt'' then do;'; put 'rc4=metadata_getattr(conprop_uri,''DefaultValue'',provider);'; put 'end;'; put 'else if value=''Connection.OLE.Property.PROPERTIES.Name.xmlKey.txt'' then'; put 'do;'; put 'rc5=metadata_getattr(conprop_uri,''DefaultValue'',properties);'; put 'end;'; put 'end;'; put '&mD.putlog ''NOTE- dsn/provider/properties: '' /'; put 'datasource provider properties;'; put '&mD.putlog ''NOTE- schema: '' schema // ''NOTE-'';'; put '/* get SCHEMA */'; put 'rc6=metadata_getnasn("&liburi",''UsingPackages'',1,lib_uri);'; put 'rc7=metadata_getattr(lib_uri,''SchemaName'',schema);'; put 'call symputx(''SQL_domain'',domain,''l'');'; put 'call symputx(''SQL_dsn'',datasource,''l'');'; put 'call symputx(''SQL_provider'',provider,''l'');'; put 'call symputx(''SQL_properties'',properties,''l'');'; put 'call symputx(''SQL_schema'',schema,''l'');'; put 'run;'; put '%if %length(&open_passthrough)>0 %then %do;'; put 'proc sql &sql_options;'; put 'connect to OLEDB as &open_passthrough(INSERT_SQL=YES'; put '/* need additional properties to make this work */'; put 'properties=(''Integrated Security''=SSPI'; put '''Persist Security Info''=True'; put '%sysfunc(compress(%str(&SQL_properties),%str(())))'; put ')'; put 'DATASOURCE=&sql_dsn PROMPT=NO'; put 'PROVIDER=&sql_provider SCHEMA=&sql_schema CONNECTION = GLOBAL);'; put '%end;'; put '%else %do;'; put 'LIBNAME &libref OLEDB PROPERTIES=&sql_properties'; put 'DATASOURCE=&sql_dsn PROVIDER=&sql_provider SCHEMA=&sql_schema'; put '%if %length(&sql_domain)>0 %then %do;'; put 'authdomain="&sql_domain"'; put '%end;'; put 'connection=shared;'; put '%end;'; put '%end;'; put '%else %if &engine=ODBC %then %do;'; put '%&mD.put NOTE: Retrieving ODBC connection details;'; put 'data _null_;'; put 'length connx_uri conprop_uri value datasource up_uri schema domprop_uri authdomain $256.;'; put 'call missing (of _all_);'; put '/* get source connection ID */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,connx_uri);'; put '/* get connection properties */'; put 'i=0;'; put 'do until (rc2<0);'; put 'i+1;'; put 'rc2=metadata_getnasn(connx_uri,''Properties'',i,conprop_uri);'; put 'rc3=metadata_getattr(conprop_uri,''Name'',value);'; put 'if value=''Connection.ODBC.Property.DATASRC.Name.xmlKey.txt'' then do;'; put 'rc4=metadata_getattr(conprop_uri,''DefaultValue'',datasource);'; put 'rc2=-1;'; put 'end;'; put 'end;'; put '/* get auth domain */'; put 'autrc=metadata_getnasn(connx_uri,"Domain",1,domprop_uri);'; put 'arc=metadata_getattr(domprop_uri,"Name",authdomain);'; put 'if not missing(authdomain) then authdomain=cats(''AUTHDOMAIN='',authdomain);'; put 'call symputx(''authdomain'',authdomain,''l'');'; put '/* get SCHEMA */'; put 'rc6=metadata_getnasn("&liburi",''UsingPackages'',1,up_uri);'; put 'rc7=metadata_getattr(up_uri,''SchemaName'',schema);'; put '&mD.put rc= connx_uri= rc2= conprop_uri= rc3= value= rc4= datasource='; put 'rc6= up_uri= rc7= schema=;'; put 'call symputx(''SQL_schema'',schema,''l'');'; put 'call symputx(''SQL_dsn'',datasource,''l'');'; put 'run;'; put '%if %length(&open_passthrough)>0 %then %do;'; put 'proc sql &sql_options;'; put 'connect to ODBC as &open_passthrough'; put '(INSERT_SQL=YES DATASRC=&sql_dsn. CONNECTION=global);'; put '%end;'; put '%else %do;'; put 'libname &libref ODBC DATASRC=&sql_dsn SCHEMA=&sql_schema &authdomain;'; put '%end;'; put '%end;'; put '%else %if &engine=POSTGRES %then %do;'; put '%put NOTE: Obtaining POSTGRES library details;'; put 'data _null_;'; put 'length database ignore_read_only_columns direct_exe preserve_col_names'; put 'preserve_tab_names server schema authdomain user password'; put 'prop name value uri urisrc $256.;'; put 'call missing (of _all_);'; put '/* get database value */'; put 'prop=''Connection.DBMS.Property.DB.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,database,"");'; put 'if database^='''' then database=''database=''!!quote(trim(database));'; put 'call symputx(''database'',database,''l'');'; put '/* get IGNORE_READ_ONLY_COLUMNS value */'; put 'prop=''Library.DBMS.Property.DBIROC.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,ignore_read_only_columns,"");'; put 'if ignore_read_only_columns^='''' then ignore_read_only_columns='; put '''ignore_read_only_columns=''!!ignore_read_only_columns;'; put 'call symputx(''ignore_read_only_columns'',ignore_read_only_columns,''l'');'; put '/* get DIRECT_EXE value */'; put 'prop=''Library.DBMS.Property.DirectExe.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,direct_exe,"");'; put 'if direct_exe^='''' then direct_exe=''direct_exe=''!!direct_exe;'; put 'call symputx(''direct_exe'',direct_exe,''l'');'; put '/* get PRESERVE_COL_NAMES value */'; put 'prop=''Library.DBMS.Property.PreserveColNames.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,preserve_col_names,"");'; put 'if preserve_col_names^='''' then preserve_col_names='; put '''preserve_col_names=''!!preserve_col_names;'; put 'call symputx(''preserve_col_names'',preserve_col_names,''l'');'; put '/* get PRESERVE_TAB_NAMES value */'; put '/* be careful with PRESERVE_TAB_NAMES=YES - it will mean your table will'; put 'become case sensitive!! */'; put 'prop=''Library.DBMS.Property.PreserveTabNames.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,preserve_tab_names,"");'; put 'if preserve_tab_names^='''' then preserve_tab_names='; put '''preserve_tab_names=''!!preserve_tab_names;'; put 'call symputx(''preserve_tab_names'',preserve_tab_names,''l'');'; put '/* get SERVER value */'; put 'if metadata_getnasn("&liburi","LibraryConnection",1,uri)>0 then do;'; put 'prop=''Connection.DBMS.Property.SERVER.Name.xmlKey.txt'';'; put 'rc=metadata_getprop(uri,prop,server,"");'; put 'end;'; put 'if server^='''' then server=''server=''!!quote(cats(server));'; put 'call symputx(''server'',server,''l'');'; put '/* get SCHEMA value */'; put 'if metadata_getnasn("&liburi","UsingPackages",1,uri)>0 then do;'; put 'rc=metadata_getattr(uri,"SchemaName",schema);'; put 'end;'; put 'if schema^='''' then schema=''schema=''!!schema;'; put 'call symputx(''schema'',schema,''l'');'; put '/* get AUTHDOMAIN value */'; put '/* this is only useful if the user account contains that auth domain'; put 'if metadata_getnasn("&liburi","DefaultLogin",1,uri)>0 then do;'; put 'rc=metadata_getnasn(uri,"Domain",1,urisrc);'; put 'rc=metadata_getattr(urisrc,"Name",authdomain);'; put 'end;'; put 'if authdomain^='''' then authdomain=''authdomain=''!!quote(trim(authdomain));'; put '*/'; put 'call symputx(''authdomain'',authdomain,''l'');'; put '/* get user & pass */'; put 'if authdomain='''' & metadata_getnasn("&liburi","DefaultLogin",1,uri)>0 then'; put 'do;'; put 'rc=metadata_getattr(uri,"UserID",user);'; put 'rc=metadata_getattr(uri,"Password",password);'; put 'end;'; put 'if user^='''' then do;'; put 'user=''user=''!!quote(trim(user));'; put 'password=''password=''!!quote(trim(password));'; put 'end;'; put 'call symputx(''user'',user,''l'');'; put 'call symputx(''password'',password,''l'');'; put '&md.put _all_;'; put 'run;'; put '%if %length(&open_passthrough)>0 %then %do;'; put '%put %str(WARN)ING: Passthrough option for postgres not yet supported;'; put '%return;'; put '%end;'; put '%else %do;'; put '%if &mdebug=1 %then %do;'; put '%put NOTE: Executing the following:/;'; put '%put NOTE- libname &libref POSTGRES &database &ignore_read_only_columns;'; put '%put NOTE- &direct_exe &preserve_col_names &preserve_tab_names;'; put '%put NOTE- &server &schema &authdomain &user &password //;'; put '%end;'; put 'libname &libref POSTGRES &database &ignore_read_only_columns &direct_exe'; put '&preserve_col_names &preserve_tab_names &server &schema &authdomain'; put '&user &password;'; put '%end;'; put '%end;'; put '%else %if &engine=ORACLE %then %do;'; put '%put NOTE: Obtaining &engine library details;'; put 'data _null_;'; put 'length assocuri1 assocuri2 assocuri3 authdomain path schema $256;'; put 'call missing (of _all_);'; put '/* get auth domain */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,assocuri1);'; put 'rc=metadata_getnasn(assocuri1,''Domain'',1,assocuri2);'; put 'rc=metadata_getattr(assocuri2,"Name",authdomain);'; put 'call symputx(''authdomain'',authdomain,''l'');'; put '/* path */'; put 'rc=metadata_getprop(assocuri1,'; put '''Connection.Oracle.Property.PATH.Name.xmlKey.txt'',path);'; put 'call symputx(''path'',path,''l'');'; put '/* schema */'; put 'rc=metadata_getnasn("&liburi",''UsingPackages'',1,assocuri3);'; put 'rc=metadata_getattr(assocuri3,''SchemaName'',schema);'; put 'call symputx(''schema'',schema,''l'');'; put 'run;'; put '%put NOTE: Executing the following:/; %put NOTE-;'; put '%put NOTE- libname &libref ORACLE path=&path schema=&schema;'; put '%put NOTE- authdomain=&authdomain;'; put '%put NOTE-;'; put 'libname &libref ORACLE path=&path schema=&schema authdomain=&authdomain;'; put '%end;'; put '%else %if &engine=SQLSVR %then %do;'; put '%put NOTE: Obtaining &engine library details;'; put 'data _null;'; put 'length assocuri1 assocuri2 assocuri3 authdomain path schema userid'; put 'passwd $256;'; put 'call missing (of _all_);'; put 'rc=metadata_getnasn("&liburi",''DefaultLogin'',1,assocuri1);'; put 'rc=metadata_getattr(assocuri1,"UserID",userid);'; put 'rc=metadata_getattr(assocuri1,"Password",passwd);'; put 'call symputx(''user'',userid,''l'');'; put 'call symputx(''pass'',passwd,''l'');'; put '/* path */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,assocuri2);'; put 'rc=metadata_getprop(assocuri2,'; put '''Connection.SQL.Property.Datasrc.Name.xmlKey.txt'',path);'; put 'call symputx(''path'',path,''l'');'; put '/* schema */'; put 'rc=metadata_getnasn("&liburi",''UsingPackages'',1,assocuri3);'; put 'rc=metadata_getattr(assocuri3,''SchemaName'',schema);'; put 'call symputx(''schema'',schema,''l'');'; put 'run;'; put '%put NOTE: Executing the following:/; %put NOTE-;'; put '%put NOTE- libname &libref SQLSVR datasrc=&path schema=&schema ;'; put '%put NOTE- user="&user" pass="XXX";'; put '%put NOTE-;'; put 'libname &libref SQLSVR datasrc=&path schema=&schema user="&user" pass="&pass";'; put '%end;'; put '%else %if &engine=TERADATA %then %do;'; put '%put NOTE: Obtaining &engine library details;'; put 'data _null;'; put 'length assocuri1 assocuri2 assocuri3 authdomain path schema userid'; put 'passwd $256;'; put 'call missing (of _all_);'; put '/* get auth domain */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,assocuri1);'; put 'rc=metadata_getnasn(assocuri1,''Domain'',1,assocuri2);'; put 'rc=metadata_getattr(assocuri2,"Name",authdomain);'; put 'call symputx(''authdomain'',authdomain,''l'');'; put '/*'; put 'rc=metadata_getnasn("&liburi",''DefaultLogin'',1,assocuri1);'; put 'rc=metadata_getattr(assocuri1,"UserID",userid);'; put 'rc=metadata_getattr(assocuri1,"Password",passwd);'; put 'call symputx(''user'',userid,''l'');'; put 'call symputx(''pass'',passwd,''l'');'; put '*/'; put '/* path */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,assocuri2);'; put 'rc=metadata_getprop(assocuri2,'; put '''Connection.Teradata.Property.SERVER.Name.xmlKey.txt'',path);'; put 'call symputx(''path'',path,''l'');'; put '/* schema */'; put 'rc=metadata_getnasn("&liburi",''UsingPackages'',1,assocuri3);'; put 'rc=metadata_getattr(assocuri3,''SchemaName'',schema);'; put 'call symputx(''schema'',schema,''l'');'; put 'run;'; put '%put NOTE: Executing the following:/; %put NOTE-;'; put '%put NOTE- libname &libref TERADATA server="&path" schema=&schema ;'; put '%put NOTe- authdomain=&authdomain;'; put '%put NOTE-;'; put 'libname &libref TERADATA server="&path" schema=&schema authdomain=&authdomain;'; put '%end;'; put '%else %if &engine= %then %do;'; put '%put NOTE: Libref &libref is not registered in metadata;'; put '%&mAbort.mp_abort('; put 'msg=%str(ERR)OR: Libref &libref is not registered in metadata'; put ',mac=mm_assigndirectlib.sas);'; put '%return;'; put '%end;'; put '%else %do;'; put '%put %str(WARN)ING: Engine &engine is currently unsupported;'; put '%put %str(WARN)ING- Please contact your support team.;'; put '%return;'; put '%end;'; put '%mend mm_assigndirectlib;'; put '%macro mm_getrepos('; put 'outds=work.mm_getrepos'; put ')/*/STORE SOURCE*/;'; put '* use a temporary fileref to hold the response;'; put 'filename response temp;'; put '/* get list of libraries */'; put 'proc metadata in='; put '"1"'; put 'out=response;'; put 'run;'; put '/* write the response to the log for debugging */'; put '/*'; put 'data _null_;'; put 'infile response lrecl=1048576;'; put 'input;'; put 'put _infile_;'; put 'run;'; put '*/'; put '/* create an XML map to read the response */'; put 'filename sxlemap temp;'; put 'data _null_;'; put 'file sxlemap;'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository";'; put 'put "";'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Id";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Name";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Desc";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@DefaultNS";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@RepositoryType";'; put 'put "characterstring20";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@RepositoryFormat";'; put 'put "characterstring10";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@Access";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@CurrentAccess";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@PauseState";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Path";'; put 'put "";'; put 'put "characterstring256";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Engine";'; put 'put "";'; put 'put "characterstring8";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Options";'; put 'put "";'; put 'put "characterstring32";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@MetadataCreated";'; put 'put "characterstring24";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@MetadataUpdated";'; put 'put "characterstring24";'; put 'put '''';'; put 'put ''
'';'; put 'run;'; put 'libname _XML_ xml xmlfileref=response xmlmap=sxlemap;'; put 'proc sort data= _XML_.SASRepos out=&outds;'; put 'by name;'; put 'run;'; put '/* clear references */'; put 'filename sxlemap clear;'; put 'filename response clear;'; put 'libname _XML_ clear;'; put '%mend mm_getrepos;'; put '%macro dc_assignlib(type,libref,passthru=);'; put '%put &sysmacroname entry vars:;'; put '%put _local_;'; put '/* take current repository */'; put '%local repo repocnt x;'; put '%let repo=%sysfunc(getoption(metarepository));'; put '/* get list of repositories and filter */'; put '%mm_getrepos(outds=repos)'; put '%let repocnt=0;'; put 'data repos;'; put 'set repos;'; put 'where repositorytype in(''CUSTOM'',''FOUNDATION'')'; put '& upcase(name) ne "%upcase(&repo)";'; put 'keep id name ;'; put 'call symputx(cats(''repo'',_n_),name,''l'');'; put 'call symputx(''repocnt'',_n_,''l'');'; put 'run;'; put '/* find out which of these repositories has the libref we are searching for */'; put '%local lib_uri;'; put '%let lib_uri=NOTFOUND;'; put 'data _null_; /* check default repo first */'; put 'length lib_uri $256;'; put 'call missing (of _all_);'; put 'rc=metadata_getnobj("omsobj:SASLibrary?@Libref =''&libref''",1,lib_uri);'; put 'call symputx(''lib_uri'',coalescec(lib_uri,''NOTFOUND''),''l'');'; put 'run;'; put '%if &lib_uri=NOTFOUND %then %do;'; put '%do x=1 %to &repocnt;'; put 'options metarepository=&&repo&x;'; put 'data _null_;'; put 'length lib_uri $256;'; put 'call missing (of _all_);'; put 'rc=metadata_getnobj("omsobj:SASLibrary?@Libref =''&libref''",1,lib_uri);'; put 'call symputx(''lib_uri'',coalescec(lib_uri,''NOTFOUND''),''l'');'; put 'run;'; put '%if &lib_uri ne NOTFOUND %then %let x=%eval(&repocnt+1);'; put '%end;'; put '%end;'; put '%if &type=READ %then %do;'; put '%mm_assignlib(&libref)'; put '%end;'; put '%else %if &type=WRITE %then %do;'; put '%mm_assigndirectlib(&libref,open_passthrough=&passthru)'; put '%end;'; put 'options metarepository=&repo;'; put '%mend dc_assignlib;'; put '%macro dc_getservicecode(loc=,outref=);'; put '%mm_getstpcode(tree=&loc'; put ',outref=&outref'; put ')'; put '%mend dc_getservicecode;'; put '%macro mf_getattrn('; put 'libds'; put ',attr'; put ')/*/STORE SOURCE*/;'; put '%local dsid rc;'; put '%let dsid=%sysfunc(open(&libds,is));'; put '%if &dsid = 0 %then %do;'; put '%put %str(WARN)ING: Cannot open %trim(&libds), system message below;'; put '%put %sysfunc(sysmsg());'; put '-1'; put '%end;'; put '%else %do;'; put '%sysfunc(attrn(&dsid,&attr))'; put '%let rc=%sysfunc(close(&dsid));'; put '%end;'; put '%mend mf_getattrn;'; put '%macro mf_nobs(libds'; put ')/*/STORE SOURCE*/;'; put '%mf_getattrn(&libds,NLOBS)'; put '%mend mf_nobs;'; put '%macro mp_include(fileref'; put ',prefix=_'; put ',opts=SOURCE2'; put ',errds=work.mp_abort_errds'; put ')/*/STORE SOURCE*/;'; put '/* prepare precode */'; put '%local tempref;'; put '%let tempref=%mf_getuniquefileref();'; put 'data _null_;'; put 'file &tempref;'; put 'set sashelp.vextfl(where=(fileref="%upcase(&fileref)"));'; put 'put ''%let _SYSINCLUDEFILEDEVICE='' xengine '';'';'; put 'name=scan(xpath,-1,''/\'');'; put 'put ''%let _SYSINCLUDEFILENAME='' name '';'';'; put 'path=subpad(xpath,1,length(xpath)-length(name)-1);'; put 'put ''%let _SYSINCLUDEFILEDIR='' path '';'';'; put 'put ''%let _SYSINCLUDEFILEFILEREF='' "&fileref;";'; put 'run;'; put '/* prepare the errds */'; put 'data &errds;'; put 'length msg mac $1000;'; put 'call missing(msg,mac);'; put 'iftrue=''1=0'';'; put 'run;'; put '/* include the include */'; put '%inc &tempref &fileref/&opts;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=%str(&_SYSINCLUDEFILEDIR/&_SYSINCLUDEFILENAME)'; put ',msg=%str(syscc=&syscc after executing &_SYSINCLUDEFILENAME)'; put ')'; put 'filename &tempref clear;'; put '%mend mp_include;'; put '%macro mf_getuniquename(prefix=MC);'; put '&prefix.%substr(%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32-%length(&prefix))'; put '%mend mf_getuniquename;'; put '%macro mp_validatecol(incol,rule,outcol);'; put '/* tempcol is given a unique name with every invocation */'; put '%local tempcol;'; put '%let tempcol=%mf_getuniquename();'; put '%if &rule=ISINT %then %do;'; put '&outcol=0;'; put 'if not missing(&incol) then do;'; put '&tempcol=input(&incol,?? best32.);'; put 'if not missing(&tempcol) then if mod(&tempcol,1)=0 then &outcol=1;'; put 'end;'; put 'drop &tempcol;'; put '%end;'; put '%else %if &rule=ISNUM %then %do;'; put '/*'; put 'credit SOREN LASSEN'; put 'https://sasmacro.blogspot.com/2009/06/welcome-isnum-macro.html'; put '*/'; put '&tempcol=input(&incol,?? best32.);'; put 'if missing(&tempcol) then &outcol=0;'; put 'else &outcol=1;'; put 'drop &tempcol;'; put '%end;'; put '%else %if &rule=LIBDS %then %do;'; put '/* match libref.dataset */'; put 'if _n_=1 then do;'; put 'retain &tempcol;'; put '&tempcol=prxparse(''/^[_a-z]\w{0,7}\.[_a-z]\w{0,31}$/i'');'; put 'if missing(&tempcol) then do;'; put 'putlog ''ERR'' +(-1) "OR: Invalid expression for LIBDS";'; put 'stop;'; put 'end;'; put 'drop &tempcol;'; put 'end;'; put 'if prxmatch(&tempcol, trim(&incol)) then &outcol=1;'; put 'else &outcol=0;'; put '%end;'; put '%else %if &rule=FORMAT %then %do;'; put '/* match valid format - regex could probably be improved */'; put 'if _n_=1 then do;'; put 'retain &tempcol;'; put '&tempcol=prxparse(''/^[_a-z\$]\w{0,31}\.[0-9]*$/i'');'; put 'if missing(&tempcol) then do;'; put 'putlog ''ERR'' +(-1) "OR: Invalid expression for FORMAT";'; put 'stop;'; put 'end;'; put 'drop &tempcol;'; put 'end;'; put 'if prxmatch(&tempcol, trim(&incol)) then &outcol=1;'; put 'else &outcol=0;'; put '%end;'; put '%mend mp_validatecol;'; put '* SAS Macros end;'; put '* SAS Includes start;'; put '* SAS Includes end;'; put '* Binary Files start;'; put '* Binary Files end;'; put '* ServiceInit start;'; put 'options noquotelenmax ps=max;'; put '/* create dummy macros to prevent stpbegin / stpend from corrupting sessions */'; put '%macro stpbegin();'; put '%put NOTE: the STPBEGIN macro should not be used for web apps!;'; put '%mend stpbegin;'; put '%macro stpend();'; put '%put NOTE: the STPEND macro should not be used for web apps!;'; put '%mend stpend;'; put '* ServiceInit end;'; put '* Service start;'; put '/**'; put '@file getdynamiccolvals.sas'; put '@brief Provide dynamic list of values according to a SAS program or service'; put '@details Configuration is made in the MPE_VALIDATIONS table, the dropdown'; put 'can be either a SOFTSELECT_HOOK or HARDSELECT_HOOK.'; put 'Results are sent in ARRAY format for efficiency.'; put '

Service Inputs

'; put '
SASCONTROLTABLE
'; put '|LIBDS:$41.|VARIABLE_NM:$32.|'; put '|---|---|'; put '|DC258467.MPE_SECURITY|SAS_GROUP|'; put '
SOURCE_ROW
'; put 'This contains the raw values from the source table.'; put '

Service Outputs

'; put '
DYNAMIC_VALUES
'; put 'The RAW_VALUE column may be charactor or numeric. If DISPLAY_INDEX is not'; put 'provided, it is added automatically.'; put '|DISPLAY_INDEX:best.|DISPLAY_VALUE:$|RAW_VALUE|'; put '|---|---|---|'; put '|1|$77.43|77.43|'; put '|2|$88.43|88.43|'; put '
DYNAMIC_EXTENDED_VALUES
'; put 'This table is optional. If provided, it will map the DISPLAY_INDEX from the'; put 'DYNAMIC_VALUES table to additional column/value pairs, that will be used to'; put 'populate dropdowns for _other_ cells in the _same_ row.'; put 'Should be used sparingly! The use of large tables here can slow down the'; put 'browser.'; put '|DISPLAY_INDEX:best.|EXTRA_COL_NAME:$32.|DISPLAY_VALUE:$|DISPLAY_TYPE:$1.|RAW_VALUE_NUM|RAW_VALUE_CHAR:$5000|'; put '|---|---|---|'; put '|1|DISCOUNT_RT|"50%"|N|0.5||'; put '|1|DISCOUNT_RT|"40%"|N|0.4||'; put '|1|DISCOUNT_RT|"30%"|N|0.3||'; put '|1|CURRENCY_SYMBOL|"GBP"|C||"GBP"|'; put '|1|CURRENCY_SYMBOL|"RSD"|C||"RSD"|'; put '|2|DISCOUNT_RT|"50%"|N|0.5||'; put '|2|DISCOUNT_RT|"40%"|N|0.4||'; put '|2|CURRENCY_SYMBOL|"EUR"|C||"EUR"|'; put '|2|CURRENCY_SYMBOL|"HKD"|C||"HKD"|'; put '

SAS Macros

'; put '@li dc_assignlib.sas'; put '@li dc_getservicecode.sas'; put '@li mf_nobs.sas'; put '@li mp_abort.sas'; put '@li mp_include.sas'; put '@li mp_validatecol.sas'; put '@li mf_getapploc.sas'; put '@version 9.3'; put '@author 4GL Apps Ltd'; put '@copyright 4GL Apps Ltd. This code may only be used within Data Controller'; put 'and may not be re-distributed or re-sold without the express permission of'; put '4GL Apps Ltd.'; put '**/'; put '%mpeinit()'; put '/**'; put '* Validate inputs'; put '*/'; put '%let err_msg=;'; put 'data work.intest;'; put 'set work.SASCONTROLTABLE;'; put '/* validate libds */'; put '%mp_validatecol(LIBDS,LIBDS,is_libds)'; put '/* validate varname */'; put 'is_name=nvalid(variable_nm,''v7'');'; put 'putlog (_all_)(=);'; put 'if is_libds ne 1 then do;'; put 'msg=''ERR''!!''OR: Invalid libds:''!!libds;'; put 'call symputx(''err_msg'',msg);'; put 'stop;'; put 'end;'; put 'else if is_name ne 1 then do;'; put 'msg=''ERR''!!''OR: Invalid name:''!!variable_nm;'; put 'call symputx(''err_msg'',msg);'; put 'stop;'; put 'end;'; put 'else do;'; put 'call symputx(''variable_nm'',variable_nm);'; put 'call symputx(''libds'',libds);'; put 'end;'; put 'output;'; put 'stop;'; put 'run;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(syscc=&syscc after reading work.sascontroltable)'; put ')'; put '%mp_abort(iftrue= (%mf_nobs(work.intest)=0)'; put ',mac=&_program'; put ',msg=%str(&err_msg)'; put ')'; put '%dc_assignlib(READ,%scan(&libds,1,.))'; put '/* ensure that work.dynamic_extended_values exists */'; put 'data work.dynamic_extended_values;'; put 'run;'; put '/**'; put '* Get the code to execute'; put '*/'; put 'data work.codetest;'; put 'set &mpelib..MPE_VALIDATIONS;'; put 'where &dc_dttmtfmt. lt tx_to'; put 'and base_lib="%scan(&libds,1,.)"'; put 'and base_ds="%scan(&libds,2,.)"'; put 'and base_col="&variable_nm"'; put 'and RULE_TYPE in (''HARDSELECT_HOOK'',''SOFTSELECT_HOOK'')'; put 'and RULE_ACTIVE=1;'; put 'putlog (_all_)(=);'; put 'if length(rule_value)>1 then do;'; put 'call symputx(''pgmloc'',rule_value);'; put 'if scan(upcase(rule_value),-1,''.'')=''SAS'' then do;'; put 'call symputx(''pgmtype'',''PGM'');'; put 'call symputx(''pgmloc'',rule_value);'; put 'end;'; put 'else do;'; put 'apploc="%mf_getapploc()";'; put 'if substr(rule_value,1,1) ne ''/'''; put 'then rule_value=cats(apploc,''/'',rule_value);'; put 'call symputx(''pgmloc'',rule_value);'; put 'call symputx(''pgmtype'',''JOB'');'; put 'end;'; put 'output;'; put 'stop;'; put 'end;'; put 'else stop;'; put 'run;'; put '%mp_abort(iftrue= (%mf_nobs(work.codetest)=0)'; put ',mac=&_program'; put ',msg=%str(Hook not found in &mpelib..mpe_validations for &libds..&variable_nm)'; put ')'; put '%macro getdynamiccolvals();'; put '%if &pgmtype=PGM %then %do;'; put 'filename sascode "&pgmloc";'; put '%end;'; put '%else %do;'; put '%dc_getservicecode(loc=&pgmloc'; put ',outref=sascode'; put ')'; put '%end;'; put '%mend getdynamiccolvals;'; put '%getdynamiccolvals()'; put '/* execute the dynamic code */'; put '%mp_include(sascode)'; put '%mp_abort(mode=INCLUDE)'; put '/* ensure that the DISPLAY_INDEX variable exists */'; put 'data work.dynamic_values;'; put 'length DISPLAY_INDEX 8 DISPLAY_VALUE $32767;'; put 'if _n_=1 then call missing(of _all_);'; put 'set work.dynamic_values;'; put 'display_index=coalesce(display_index,_n_);'; put 'keep DISPLAY_INDEX DISPLAY_VALUE RAW_VALUE;'; put 'run;'; put '/* ensure that work.dynamic_extended_values exists with correct types */'; put 'data work.dynamic_extended_values;'; put 'length DISPLAY_INDEX 8 EXTRA_COL_NAME $32 DISPLAY_VALUE $5000 DISPLAY_TYPE $1'; put 'RAW_VALUE_NUM 8 RAW_VALUE_CHAR $5000 FORCED_VALUE 8;'; put 'if _n_=1 then call missing(of _all_);'; put 'set work.dynamic_extended_values;'; put 'run;'; put '%webout(OPEN)'; put '%webout(ARR,dynamic_values,fmt=N)'; put '%webout(ARR,dynamic_extended_values,fmt=N)'; put '%webout(CLOSE)'; put '%mpeterm()'; put '* Service end;'; run; %mm_createwebservice(path=&appLoc/&path, name=&service, code=sascode, server=&serverName, replace=yes) filename sascode clear; %let service=getlog; filename sascode temp lrecl=32767; data _null_; file sascode; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '/**'; put '@file mp_jsonout.sas'; put '@brief Writes JSON in SASjs format to a fileref'; put '@details This macro can be used to OPEN a JSON stream and send one or more'; put 'tables as arrays of rows, where each row can be an object or a nested array.'; put 'There are two engines available - DATASTEP or PROCJSON.'; put 'PROC JSON is fast but will produce errs like the ones below if'; put 'special chars are encountered.'; put '> (ERR)OR: Some code points did not transcode.'; put '> An object or array close is not valid at this point in the JSON text.'; put '> Date value out of range'; put 'If this happens, try running with ENGINE=DATASTEP.'; put 'The DATASTEP engine is used to handle special SAS missing numerics, and'; put 'can also convert entire datasets to formatted values. Output JSON is always'; put 'in UTF-8.'; put 'Usage:'; put 'filename tmp temp;'; put 'data class; set sashelp.class;run;'; put '%mp_jsonout(OPEN,jref=tmp)'; put '%mp_jsonout(OBJ,class,jref=tmp)'; put '%mp_jsonout(OBJ,class,dslabel=class2,jref=tmp,showmeta=Y)'; put '%mp_jsonout(CLOSE,jref=tmp)'; put 'data _null_;'; put 'infile tmp;'; put 'input;putlog _infile_;'; put 'run;'; put 'If you are building web apps with SAS then you are strongly encouraged to use'; put 'the mX_createwebservice macros in combination with the'; put '[sasjs adapter](https://github.com/sasjs/adapter).'; put 'For more information see https://sasjs.io'; put '@param [in] action Valid values:'; put '@li OPEN - opens the JSON'; put '@li OBJ - sends a table with each row as an object'; put '@li ARR - sends a table with each row in an array'; put '@li CLOSE - closes the JSON'; put '@param [in] ds The dataset to send. Must be a work table.'; put '@param [out] jref= (_webout) The fileref to which to send the JSON'; put '@param [out] dslabel= The name to give the table in the exported JSON'; put '@param [in] fmt= (Y) Whether to keep (Y) or strip (N) formats from the table'; put '@param [in] engine= (DATASTEP) Which engine to use to send the JSON. Options:'; put '@li PROCJSON (default)'; put '@li DATASTEP (more reliable when data has non standard characters)'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to JSON'; put '

Related Files

'; put '@li mp_ds2fmtds.sas'; put '@version 9.2'; put '@author Allan Bowe'; put '@source https://github.com/sasjs/core'; put '**/'; put '%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y'; put ',engine=DATASTEP'; put ',missing=NULL'; put ',showmeta=N'; put ',maxobs=MAX'; put ')/*/STORE SOURCE*/;'; put '%local tempds colinfo fmtds i numcols numobs stmt_obs lastobs optval'; put 'tmpds1 tmpds2 tmpds3 tmpds4;'; put '%let numcols=0;'; put '%if &maxobs ne MAX %then %let stmt_obs=%str(if _n_>&maxobs then stop;);'; put '%if &action=OPEN %then %do;'; put 'options nobomfile;'; put 'data _null_;file &jref encoding=''utf-8'' lrecl=200;'; put 'put ''{"PROCESSED_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''"'';'; put 'run;'; put '%end;'; put '%else %if (&action=ARR or &action=OBJ) %then %do;'; put '/* force variable names to always be uppercase in the JSON */'; put 'options validvarname=upcase;'; put '/* To avoid issues with _webout on EBI - such as encoding diffs and truncation'; put '(https://support.sas.com/kb/49/325.html) we use temporary files */'; put 'filename _sjs1 temp lrecl=200 ;'; put 'data _null_; file _sjs1 encoding=''utf-8'';'; put 'put ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":";'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjs1 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs1 clear;'; put '/* grab col defs */'; put 'proc contents noprint data=&ds'; put 'out=_data_(keep=name type length format formatl formatd varnum label);'; put 'run;'; put '%let colinfo=%scan(&syslast,2,.);'; put 'proc sort data=&colinfo;'; put 'by varnum;'; put 'run;'; put '/* move meta to mac vars */'; put 'data &colinfo;'; put 'if _n_=1 then call symputx(''numcols'',nobs,''l'');'; put 'set &colinfo end=last nobs=nobs;'; put 'name=upcase(name);'; put '/* fix formats */'; put 'if type=2 or type=6 then do;'; put 'typelong=''char'';'; put 'length fmt $49.;'; put 'if format='''' then fmt=cats(''$'',length,''.'');'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else fmt=cats(format,formatl,''.'');'; put 'end;'; put 'else do;'; put 'typelong=''num'';'; put 'if format='''' then fmt=''best.'';'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else if formatd=0 then fmt=cats(format,formatl,''.'');'; put 'else fmt=cats(format,formatl,''.'',formatd);'; put 'end;'; put '/* 32 char unique name */'; put 'newname=''sasjs''!!substr(cats(put(md5(name),$hex32.)),1,27);'; put 'call symputx(cats(''name'',_n_),name,''l'');'; put 'call symputx(cats(''newname'',_n_),newname,''l'');'; put 'call symputx(cats(''length'',_n_),length,''l'');'; put 'call symputx(cats(''fmt'',_n_),fmt,''l'');'; put 'call symputx(cats(''type'',_n_),type,''l'');'; put 'call symputx(cats(''typelong'',_n_),typelong,''l'');'; put 'call symputx(cats(''label'',_n_),coalescec(label,name),''l'');'; put '/* overwritten when fmt=Y and a custom format exists in catalog */'; put 'if typelong=''num'' then call symputx(cats(''fmtlen'',_n_),200,''l'');'; put 'else call symputx(cats(''fmtlen'',_n_),min(32767,ceil((length+10)*1.5)),''l'');'; put 'run;'; put '%let tempds=%substr(_%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql;'; put 'select count(*) into: lastobs from &ds;'; put '%if &maxobs ne MAX %then %let lastobs=%sysfunc(min(&lastobs,&maxobs));'; put '%if &engine=PROCJSON %then %do;'; put '%if &missing=STRING %then %do;'; put '%put &sysmacroname: Special Missings not supported in proc json.;'; put '%put &sysmacroname: Switching to DATASTEP engine;'; put '%goto datastep;'; put '%end;'; put 'data &tempds;'; put 'set &ds;'; put '&stmt_obs;'; put '%if &fmt=N %then format _numeric_ best32.;;'; put '/* PRETTY is necessary to avoid line truncation in large files */'; put 'filename _sjs2 temp lrecl=131068 encoding=''utf-8'';'; put 'proc json out=_sjs2 pretty'; put '%if &action=ARR %then nokeys ;'; put ';export &tempds / nosastags fmtnumeric;'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs2 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs2 clear;'; put '%end;'; put '%else %if &engine=DATASTEP %then %do;'; put '%datastep:'; put '%if %sysfunc(exist(&ds)) ne 1 & %sysfunc(exist(&ds,VIEW)) ne 1'; put '%then %do;'; put '%put &sysmacroname: &ds NOT FOUND!!!;'; put '%return;'; put '%end;'; put '%if &fmt=Y %then %do;'; put '/**'; put '* Extract format definitions'; put '* First, by getting library locations from dictionary.formats'; put '* Then, by exporting the width using proc format'; put '* Cannot use maxw from sashelp.vformat as not always populated'; put '* Cannot use fmtinfo() as not supported in all flavours'; put '*/'; put '%let tmpds1=%substr(fmtsum%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds2=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds3=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds4=%substr(col%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql noprint;'; put 'create table &tmpds1 as'; put 'select cats(libname,''.'',memname) as FMTCAT,'; put 'FMTNAME'; put 'from dictionary.formats'; put 'where fmttype=''F'' and libname is not null'; put 'and fmtname in (select format from &colinfo where format is not null)'; put 'order by 1;'; put 'create table &tmpds2('; put 'FMTNAME char(32),'; put 'LENGTH num'; put ');'; put '%local catlist cat fmtlist i;'; put 'select distinct fmtcat into: catlist separated by '' '' from &tmpds1;'; put '%do i=1 %to %sysfunc(countw(&catlist,%str( )));'; put '%let cat=%scan(&catlist,&i,%str( ));'; put 'proc sql;'; put 'select distinct fmtname into: fmtlist separated by '' '''; put 'from &tmpds1 where fmtcat="&cat";'; put 'proc format lib=&cat cntlout=&tmpds3(keep=fmtname length);'; put 'select &fmtlist;'; put 'run;'; put 'proc sql;'; put 'insert into &tmpds2 select distinct fmtname,length from &tmpds3;'; put '%end;'; put 'proc sql;'; put 'create table &tmpds4 as'; put 'select a.*, b.length as MAXW'; put 'from &colinfo a'; put 'left join &tmpds2 b'; put 'on cats(a.format)=cats(upcase(b.fmtname))'; put 'order by a.varnum;'; put 'data _null_;'; put 'set &tmpds4;'; put 'if not missing(maxw);'; put 'call symputx('; put 'cats(''fmtlen'',_n_),'; put '/* vars need extra padding due to JSON escaping of special chars */'; put 'min(32767,ceil((max(length,maxw)+10)*1.5))'; put ',''l'''; put ');'; put 'run;'; put '/* configure varlenchk - as we are explicitly shortening the variables */'; put '%let optval=%sysfunc(getoption(varlenchk));'; put 'options varlenchk=NOWARN;'; put 'data _data_(compress=char);'; put '/* shorten the new vars */'; put 'length'; put '%do i=1 %to &numcols;'; put '&&name&i $&&fmtlen&i'; put '%end;'; put ';'; put '/* rename on entry */'; put 'set &ds(rename=('; put '%do i=1 %to &numcols;'; put '&&name&i=&&newname&i'; put '%end;'; put '));'; put '&stmt_obs;'; put 'drop'; put '%do i=1 %to &numcols;'; put '&&newname&i'; put '%end;'; put ';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=num %then %do;'; put '&&name&i=cats(put(&&newname&i,&&fmt&i));'; put '%end;'; put '%else %do;'; put '&&name&i=put(&&newname&i,&&fmt&i);'; put '%end;'; put '%end;'; put 'if _error_ then do;'; put 'call symputx(''syscc'',1012);'; put 'stop;'; put 'end;'; put 'run;'; put '%let fmtds=&syslast;'; put 'options varlenchk=&optval;'; put '%end;'; put 'proc format; /* credit yabwon for special null removal */'; put 'value bart (default=40)'; put '%if &missing=NULL %then %do;'; put '._ - .z = null'; put '%end;'; put '%else %do;'; put '._ = [quote()]'; put '. = null'; put '.a - .z = [quote()]'; put '%end;'; put 'other = [best.];'; put 'data &tempds;'; put 'attrib _all_ label='''';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'length &&name&i $&&fmtlen&i...;'; put 'format &&name&i $&&fmtlen&i...;'; put '%end;'; put '%end;'; put '%if &fmt=Y %then %do;'; put 'set &fmtds;'; put '%end;'; put '%else %do;'; put 'set &ds;'; put '%end;'; put '&stmt_obs;'; put 'format _numeric_ bart.;'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'if findc(&&name&i,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put '&&name&i=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,&&name&i)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else &&name&i=quote(cats(&&name&i));'; put '%end;'; put '%end;'; put 'run;'; put 'filename _sjs3 temp lrecl=131068 ;'; put 'data _null_;'; put 'file _sjs3 encoding=''utf-8'';'; put 'if _n_=1 then put "[";'; put 'set &tempds;'; put 'if _n_>1 then put "," @; put'; put '%if &action=ARR %then "[" ; %else "{" ;'; put '%do i=1 %to &numcols;'; put '%if &i>1 %then "," ;'; put '%if &action=OBJ %then """&&name&i"":" ;'; put '"&&name&i"n /* name literal for reserved variable names */'; put '%end;'; put '%if &action=ARR %then "]" ; %else "}" ; ;'; put '/* close out the table */'; put 'data _null_;'; put 'file _sjs3 mod encoding=''utf-8'';'; put 'put '']'';'; put 'run;'; put 'data _null_;'; put 'infile _sjs3 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs3 clear;'; put '%end;'; put 'proc sql;'; put 'drop table &colinfo, &tempds;'; put '%if %substr(&showmeta,1,1)=Y %then %do;'; put 'filename _sjs4 temp lrecl=131068 encoding=''utf-8'';'; put 'data _null_;'; put 'file _sjs4;'; put 'length label $350;'; put 'put ", ""$%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":{""vars"":{";'; put 'do i=1 to &numcols;'; put 'name=quote(trim(symget(cats(''name'',i))));'; put 'format=quote(trim(symget(cats(''fmt'',i))));'; put 'label=quote(prxchange(''s/\\/\\\\/'',-1,trim(symget(cats(''label'',i)))));'; put 'length=quote(trim(symget(cats(''length'',i))));'; put 'type=quote(trim(symget(cats(''typelong'',i))));'; put 'if i>1 then put "," @@;'; put 'put name '':{"format":'' format '',"label":'' label'; put ''',"length":'' length '',"type":'' type ''}'';'; put 'end;'; put 'put ''}}'';'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs4 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs4 clear;'; put '%end;'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put 'data _null_; file &jref encoding=''utf-8'' mod ;'; put 'put "}";'; put 'run;'; put '%end;'; put '%mend mp_jsonout;'; put '/**'; put '@file mm_webout.sas'; put '@brief Send data to/from SAS Stored Processes'; put '@details This macro should be added to the start of each Stored Process,'; put '**immediately** followed by a call to:'; put '%mm_webout(FETCH)'; put 'This will read all the input data and create same-named SAS datasets in the'; put 'WORK library. You can then insert your code, and send data back using the'; put 'following syntax:'; put 'data some datasets; * make some data ;'; put 'retain some columns;'; put 'run;'; put '%mm_webout(OPEN)'; put '%mm_webout(ARR,some) * Array format, fast, suitable for large tables ;'; put '%mm_webout(OBJ,datasets) * Object format, easier to work with ;'; put 'Finally, wrap everything up send some helpful system variables too'; put '%mm_webout(CLOSE)'; put '@param [in] action Either FETCH, OPEN, ARR, OBJ or CLOSE'; put '@param [in] ds The dataset to send back to the frontend'; put '@param [out] dslabel= Value to use instead of table name for sending to JSON'; put '@param [in] fmt= (N) Setting Y converts all vars to their formatted values'; put '@param [out] fref= (_webout) The fileref to which to write the JSON'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] workobs= (0) When set to a positive integer, will create a new'; put 'output object (WORK) which contains this number of observations from all'; put 'tables in the WORK library.'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to output JSON'; put '

SAS Macros

'; put '@li mp_jsonout.sas'; put '

Related Macros

'; put '@li ms_webout.sas'; put '@li mv_webout.sas'; put '@version 9.3'; put '@author Allan Bowe'; put '**/'; put '%macro mm_webout(action,ds,dslabel=,fref=_webout,fmt=N,missing=NULL'; put ',showmeta=N,maxobs=MAX,workobs=0'; put ');'; put '%global _webin_file_count _webin_fileref1 _webin_name1 _program _debug'; put 'sasjs_tables;'; put '%local i tempds jsonengine;'; put '/* see https://github.com/sasjs/core/issues/41 */'; put '%if "%upcase(&SYSENCODING)" ne "UTF-8" %then %let jsonengine=PROCJSON;'; put '%else %let jsonengine=DATASTEP;'; put '%if &action=FETCH %then %do;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'options mprint notes mprintnest;'; put '%end;'; put '%let _webin_file_count=%eval(&_webin_file_count+0);'; put '/* now read in the data */'; put '%do i=1 %to &_webin_file_count;'; put '%if &_webin_file_count=1 %then %do;'; put '%let _webin_fileref1=&_webin_fileref;'; put '%let _webin_name1=&_webin_name;'; put '%end;'; put 'data _null_;'; put 'infile &&_webin_fileref&i termstr=crlf;'; put 'input;'; put 'call symputx(''input_statement'',_infile_);'; put 'putlog "&&_webin_name&i input statement: " _infile_;'; put 'stop;'; put 'data &&_webin_name&i;'; put 'infile &&_webin_fileref&i firstobs=2 dsd termstr=crlf encoding=''utf-8'';'; put 'input &input_statement;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'if _n_<20 then putlog _infile_;'; put '%end;'; put 'run;'; put '%let sasjs_tables=&sasjs_tables &&_webin_name&i;'; put '%end;'; put '%end;'; put '%else %if &action=OPEN %then %do;'; put '/* fix encoding */'; put 'OPTIONS NOBOMFILE;'; put '/**'; put '* check xengine type to avoid the below err message:'; put '* > Function is only valid for filerefs using the CACHE access method.'; put '*/'; put 'data _null_;'; put 'set sashelp.vextfl(where=(fileref="_WEBOUT"));'; put 'if xengine=''STREAM'' then do;'; put 'rc=stpsrv_header(''Content-type'',"text/html; encoding=utf-8");'; put 'end;'; put 'run;'; put '/* setup json */'; put 'data _null_;file &fref encoding=''utf-8'';'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutBEGIN<<'';'; put '%end;'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'run;'; put '%end;'; put '%else %if &action=ARR or &action=OBJ %then %do;'; put '%mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt,jref=&fref'; put ',engine=&jsonengine,missing=&missing,showmeta=&showmeta,maxobs=&maxobs'; put ')'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put '/* To avoid issues with _webout on EBI we use a temporary file */'; put 'filename _sjsref temp lrecl=131068;'; put '%if %str(&workobs) > 0 %then %do;'; put '/* if debug mode, send back first XX records of each work table also */'; put 'data;run;%let tempds=%scan(&syslast,2,.);'; put 'ods output Members=&tempds;'; put 'proc datasets library=WORK memtype=data;'; put '%local wtcnt;%let wtcnt=0;'; put 'data _null_;'; put 'set &tempds;'; put 'if not (upcase(name) =:"DATA"); /* ignore temp datasets */'; put 'i+1;'; put 'call symputx(cats(''wt'',i),name,''l'');'; put 'call symputx(''wtcnt'',i,''l'');'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put ",""WORK"":{";'; put '%do i=1 %to &wtcnt;'; put '%let wt=&&wt&i;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'dsid=open("WORK.&wt",''is'');'; put 'nlobs=attrn(dsid,''NLOBS'');'; put 'nvars=attrn(dsid,''NVARS'');'; put 'rc=close(dsid);'; put 'if &i>1 then put '',''@;'; put 'put " ""&wt"" : {";'; put 'put ''"nlobs":'' nlobs;'; put 'put '',"nvars":'' nvars;'; put '%mp_jsonout(OBJ,&wt,jref=_sjsref,dslabel=first10rows,showmeta=Y'; put ',maxobs=&workobs'; put ')'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put '%end;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put 'run;'; put '%end;'; put '/* close off json */'; put 'data _null_;file _sjsref mod encoding=''utf-8'';'; put 'length SYSPROCESSNAME syserrortext syswarningtext autoexec $512;'; put 'put ",""_DEBUG"" : ""&_debug"" ";'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'autoexec=quote(urlencode(trim(getoption(''autoexec''))));'; put 'put '',"AUTOEXEC" : '' autoexec;'; put 'put ",""MF_GETUSER"" : ""%mf_getuser()"" ";'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'put ",""SYSENCODING"" : ""&sysencoding"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSPROCESSID"" : ""&SYSPROCESSID"" ";'; put 'put ",""SYSPROCESSMODE"" : ""&SYSPROCESSMODE"" ";'; put 'SYSPROCESSNAME=quote(urlencode(cats(SYSPROCESSNAME)));'; put 'put ",""SYSPROCESSNAME"" : " SYSPROCESSNAME;'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put '',"SYSWARNINGTEXT" : '' syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'length memsize $32;'; put 'memsize="%sysfunc(INPUTN(%sysfunc(getoption(memsize)), best.),sizekmg.)";'; put 'memsize=quote(cats(memsize));'; put 'put '',"MEMSIZE" : '' memsize;'; put 'put "}" @;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutEND<<'';'; put '%end;'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjsref lrecl=1 recfm=n;'; put 'file &fref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjsref clear;'; put '%end;'; put '%mend mm_webout;'; put '%macro webout(action,ds,dslabel=,fmt=,missing=NULL,showmeta=NO,maxobs=MAX);'; put '%mm_webout(&action,ds=&ds,dslabel=&dslabel,fmt=&fmt'; put ',missing=&missing'; put ',showmeta=&showmeta'; put ',maxobs=&maxobs'; put ') %mend;'; put '/* provide additional debug info */'; put '%global _program;'; put '%put &=syscc;'; put '%put user=%mf_getuser();'; put '%put pgm=&_program;'; put '%put timestamp=%sysfunc(datetime(),datetime19.);'; put '* Service Variables start;'; put '* Service Variables end;'; put '* SAS Macros start;'; put '%macro mf_getapploc(pgm);'; put '%if "&pgm"="" %then %do;'; put '%if %symexist(_program) %then %let pgm=&_program;'; put '%else %do;'; put '%put &sysmacroname: No value provided and no _program variable available;'; put '%return;'; put '%end;'; put '%end;'; put '%local root;'; put '/**'; put '* First check we are not in the tests/macros folder (which has no subfolders)'; put '* or specifically in the testsetup or testteardown services'; put '*/'; put '%if %index(&pgm,/tests/macros/)'; put 'or %index(&pgm,/tests/testsetup)'; put 'or %index(&pgm,/tests/testteardown)'; put '%then %do;'; put '%let root=%substr(&pgm,1,%index(&pgm,/tests)-1);'; put '&root'; put '%return;'; put '%end;'; put '/**'; put '* Next, move up two levels to avoid matches on subfolder or service name'; put '*/'; put '%let root=%substr(&pgm,1,%length(&pgm)-%length(%scan(&pgm,-1,/))-1);'; put '%let root=%substr(&root,1,%length(&root)-%length(%scan(&root,-1,/))-1);'; put '%if %index(&root,/tests/) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/tests/)-1);'; put '%end;'; put '%else %if %index(&root,/services) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/services)-1);'; put '%end;'; put '%else %if %index(&root,/jobs) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/jobs)-1);'; put '%end;'; put '%else %put &sysmacroname: Could not find an app location from &pgm;'; put '&root'; put '%mend mf_getapploc ;'; put '%macro mf_getuniquefileref(prefix=_,maxtries=1000,lrecl=32767);'; put '%local rc fname;'; put '%if &prefix=0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%end;'; put '%else %do;'; put '%local x len;'; put '%let len=%eval(8-%length(&prefix));'; put '%let x=0;'; put '%do x=0 %to &maxtries;'; put '%let fname=&prefix%substr(%sysfunc(ranuni(0)),3,&len);'; put '%if %sysfunc(fileref(&fname)) > 0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%return;'; put '%end;'; put '%end;'; put '%put unable to find available fileref after &maxtries attempts;'; put '%end;'; put '%mend mf_getuniquefileref;'; put '%macro mp_abort(mac=mp_abort.sas, type=, msg=, iftrue=%str(1=1)'; put ', errds=work.mp_abort_errds'; put ', mode=REGULAR'; put ')/*/STORE SOURCE*/;'; put '%global sysprocessmode sysprocessname sasjs_stpsrv_header_loc sasjsprocessmode;'; put '%local fref fid i;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%put NOTE: /// mp_abort macro executing //;'; put '%if %length(&mac)>0 %then %put NOTE- called by &mac;'; put '%put NOTE - &msg;'; put '%if %symexist(_SYSINCLUDEFILEDEVICE)'; put '/* abort cancel FILE does not restart outside the INCLUDE on Viya 3.5 */'; put 'and %superq(SYSPROCESSNAME) ne %str(Compute Server)'; put '%then %do;'; put '%if "*&_SYSINCLUDEFILEDEVICE*" ne "**" %then %do;'; put 'data &errds;'; put 'iftrue=''1=1'';'; put 'length mac $100 msg $5000;'; put 'mac=symget(''mac'');'; put 'msg=symget(''msg'');'; put 'run;'; put 'data _null_;'; put 'abort cancel FILE;'; put 'run;'; put '%return;'; put '%end;'; put '%end;'; put '/* Web App Context */'; put '%if %symexist(_PROGRAM)'; put 'or %superq(SYSPROCESSNAME) = %str(Compute Server)'; put 'or &mode=INCLUDE'; put '%then %do;'; put 'options obs=max replace mprint;'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5"'; put '%then %do;'; put 'options nosyntaxcheck;'; put '%end;'; put '%if &mode=INCLUDE %then %do;'; put '%if %sysfunc(exist(&errds))=1 %then %do;'; put 'data _null_;'; put 'set &errds;'; put 'call symputx(''iftrue'',iftrue,''l'');'; put 'call symputx(''mac'',mac,''l'');'; put 'call symputx(''msg'',msg,''l'');'; put 'putlog (_all_)(=);'; put 'run;'; put '%if (&iftrue)=0 %then %return;'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: No include errors found;'; put '%return;'; put '%end;'; put '%end;'; put '/* extract log errs / warns, if exist */'; put '%local logloc logline;'; put '%global logmsg; /* capture global messages */'; put '%if %symexist(SYSPRINTTOLOG) %then %let logloc=&SYSPRINTTOLOG;'; put '%else %let logloc=%qsysfunc(getoption(LOG));'; put 'proc printto log=log;run;'; put '%let logline=0;'; put '%if %length(&logloc)>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input; putlog _infile_;'; put 'i=1;'; put 'retain logonce 0;'; put 'if ('; put '_infile_=:"%str(WARN)ING" or _infile_=:"%str(ERR)OR"'; put ') and logonce=0 then'; put 'do;'; put 'call symputx(''logline'',_n_);'; put 'logonce+1;'; put 'end;'; put 'run;'; put '/* capture log including lines BEFORE the err */'; put '%if &logline>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input;'; put 'i=1;'; put 'stoploop=0;'; put 'if _n_ ge &logline-15 and stoploop=0 then do until (i>22);'; put 'call symputx(''logmsg'',catx(''\n'',symget(''logmsg''),_infile_));'; put 'input;'; put 'i+1;'; put 'stoploop=1;'; put 'end;'; put 'if stoploop=1 then stop;'; put 'run;'; put '%end;'; put '%end;'; put '%if %symexist(SYS_JES_JOB_URI) %then %do;'; put '/* setup webout for Viya */'; put 'options nobomfile;'; put '%if "X&SYS_JES_JOB_URI.X"="XX" %then %do;'; put 'filename _webout temp lrecl=999999 mod;'; put '%end;'; put '%else %do;'; put 'filename _webout filesrvc parenturi="&SYS_JES_JOB_URI"'; put 'name="_webout.json" lrecl=999999 mod;'; put '%end;'; put '%end;'; put '%else %if %sysfunc(filename(fref,&sasjs_stpsrv_header_loc))=0 %then %do;'; put 'options nobomfile;'; put '/* set up http header for SASjs Server */'; put '%let fid=%sysfunc(fopen(&fref,A));'; put '%if &fid=0 %then %do;'; put '%put %str(ERR)OR: %sysfunc(sysmsg());'; put '%return;'; put '%end;'; put '%let rc=%sysfunc(fput(&fid,%str(Content-Type: application/json)));'; put '%let rc=%sysfunc(fwrite(&fid));'; put '%let rc=%sysfunc(fclose(&fid));'; put '%let rc=%sysfunc(filename(&fref));'; put '%end;'; put '/* send response in SASjs JSON format */'; put 'data _null_;'; put 'file _webout mod lrecl=32000 encoding=''utf-8'';'; put 'length msg syswarningtext syserrortext $32767 mode $10 ;'; put 'sasdatetime=datetime();'; put 'msg=symget(''msg'');'; put '%if &logline>0 %then %do;'; put 'msg=cats(msg,''\n\nLog Extract:\n'',symget(''logmsg''));'; put '%end;'; put '/* escape the escapes */'; put 'msg=tranwrd(msg,''\'',''\\'');'; put '/* escape the quotes */'; put 'msg=tranwrd(msg,''"'',''\"'');'; put '/* ditch the CRLFs as chrome complains */'; put 'msg=compress(msg,,''kw'');'; put '/* quote without quoting the quotes (which are escaped instead) */'; put 'msg=cats(''"'',msg,''"'');'; put 'if symexist(''_debug'') then debug=quote(trim(symget(''_debug'')));'; put 'else debug=''""'';'; put 'if symget(''sasjsprocessmode'')=''Stored Program'' then mode=''SASJS'';'; put 'if mode ne ''SASJS'' then put ''>>weboutBEGIN<<'';'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'put '',"sasjsAbort" : [{'';'; put 'put '' "MSG":'' msg ;'; put 'put '' ,"MAC": "'' "&mac" ''"}]'';'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'put '',"_DEBUG":'' debug ;'; put 'if symexist(''_metauser'') then do;'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put 'end;'; put 'if symexist(''SYS_JES_JOB_URI'') then do;'; put 'SYS_JES_JOB_URI=quote(trim(symget(''SYS_JES_JOB_URI'')));'; put 'put '',"SYS_JES_JOB_URI": '' SYS_JES_JOB_URI;'; put 'end;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put ",""SYSWARNINGTEXT"" : " syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'put "}" ;'; put 'if mode ne ''SASJS'' then put ''>>weboutEND<<'';'; put 'run;'; put '%put _all_;'; put '%if "&sysprocessmode " = "SAS Stored Process Server " %then %do;'; put 'data _null_;'; put 'putlog ''stpsrvset program err and syscc'';'; put 'rc=stpsrvset(''program error'', 0);'; put 'call symputx("syscc",0,"g");'; put 'run;'; put '%if &sysscp=WIN'; put 'and 1=0 /* deprecating this logic until we figure out a consistent abort */'; put 'and "%substr(%str(&sysvlong ),1,8)"="9.04.01M"'; put 'and "%substr(%str(&sysvlong ),9,1)">"5" %then %do;'; put '/* skip approach (below) does not work in windows m6+ envs */'; put 'endsas;'; put '%end;'; put '%else %do;'; put '/**'; put '* endsas kills 9.4m3 deployments by orphaning multibridges.'; put '* Abort variants are ungraceful (non zero return code)'; put '* This approach lets SAS run silently until the end :-)'; put '* Caution - fails when called within a %include within a macro'; put '* Use mp_include() to handle this.'; put '*/'; put 'filename skip temp;'; put 'data _null_;'; put 'file skip;'; put 'put ''%macro skip();'';'; put 'comment ''%mend skip; -> fix lint '';'; put 'put ''%macro skippy();'';'; put 'comment ''%mend skippy; -> fix lint '';'; put 'run;'; put '%inc skip;'; put '%end;'; put '%end;'; put '%else %if "&sysprocessmode " = "SAS Compute Server " %then %do;'; put '/* endsas kills the session making it harder to fetch results */'; put 'data _null_;'; put 'syswarningtext=symget(''syswarningtext'');'; put 'syserrortext=symget(''syserrortext'');'; put 'abort_msg=symget(''msg'');'; put 'syscc=symget(''syscc'');'; put 'sysuserid=symget(''sysuserid'');'; put 'iftrue=symget(''iftrue'');'; put 'put (_all_)(/=);'; put 'call symputx(''syscc'',0);'; put 'abort cancel nolist;'; put 'run;'; put '%end;'; put '%else %do;'; put '%abort cancel;'; put '%end;'; put '%end;'; put '%else %do;'; put '%put _all_;'; put '%abort cancel;'; put '%end;'; put '%mend mp_abort;'; put '/** @endcond */'; put '%macro mm_getstpcode('; put 'tree=/User Folders/sasdemo/somestp'; put ',name='; put ',outloc=0'; put ',outref=0'; put ',mDebug=1'; put ',showlog=NO'; put ');'; put '%local mD;'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing &sysmacroname..sas;'; put '%&mD.put _local_;'; put '%if %length(&name)>0 %then %let name=/&name;'; put '/* first, check if STP exists */'; put '%local tsuri;'; put '%let tsuri=stopifempty ;'; put 'data _null_;'; put 'format type uri tsuri value $200.;'; put 'call missing (of _all_);'; put 'path="&tree&name(StoredProcess)";'; put '/* first, find the STP ID */'; put 'if metadata_pathobj("",path,"StoredProcess",type,uri)>0 then do;'; put '/* get sourcecode */'; put 'cnt=1;'; put 'do while (metadata_getnasn(uri,"Notes",cnt,tsuri)>0);'; put 'rc=metadata_getattr(tsuri,"Name",value);'; put '&mD.put tsuri= value=;'; put 'if value="SourceCode" then do;'; put '/* found it! */'; put 'rc=metadata_getattr(tsuri,"Id",value);'; put 'call symputx(''tsuri'',value,''l'');'; put 'stop;'; put 'end;'; put 'cnt+1;'; put 'end;'; put 'end;'; put 'else put (_all_)(=);'; put 'run;'; put '%mp_abort(iftrue= (&tsuri=stopifempty)'; put ',mac=mm_getstpcode'; put ',msg=%str(&tree&name.(StoredProcess) not found!)'; put ')'; put '/**'; put '* Now we can extract the textstore'; put '*/'; put 'filename __getdoc temp lrecl=10000000;'; put 'proc metadata'; put 'in="$METAREPOSITORY'; put ''; put 'SAS1"'; put 'out=__getdoc ;'; put 'run;'; put '/* find the beginning of the text */'; put '%local start;'; put 'data _null_;'; put 'infile __getdoc lrecl=10000;'; put 'input;'; put 'start=index(_infile_,''StoredText="'');'; put 'if start then do;'; put 'call symputx("start",start+11);'; put '*putlog ''"'' _infile_ ''"'';'; put 'end;'; put 'stop;'; put '%local outeng;'; put '%if "&outloc"="0" %then %let outeng=TEMP;'; put '%else %let outeng="&outloc";'; put '%local fref;'; put '%if &outref=0 %then %let fref=%mf_getuniquefileref();'; put '%else %let fref=&outref;'; put '/* read the content, byte by byte, resolving escaped chars */'; put 'filename &fref &outeng lrecl=100000;'; put 'data _null_;'; put 'length filein 8 fileid 8;'; put 'filein = fopen("__getdoc","I",1,"B");'; put 'fileid = fopen("&fref","O",1,"B");'; put 'rec = "20"x;'; put 'length entity $6;'; put 'do while(fread(filein)=0);'; put 'x+1;'; put 'if x>&start then do;'; put 'rc = fget(filein,rec,1);'; put 'if rec=''"'' then leave;'; put 'else if rec="&" then do;'; put 'entity=rec;'; put 'do until (rec=";");'; put 'if fread(filein) ne 0 then goto getout;'; put 'rc = fget(filein,rec,1);'; put 'entity=cats(entity,rec);'; put 'end;'; put 'select (entity);'; put 'when (''&'' ) rec=''&'' ;'; put 'when (''<'' ) rec=''<'' ;'; put 'when (''>'' ) rec=''>'' ;'; put 'when (''''') rec="''" ;'; put 'when (''"'') rec=''"'' ;'; put 'when ('' '') rec=''0A''x;'; put 'when ('' '') rec=''0D''x;'; put 'when (''$'' ) rec=''$'' ;'; put 'when ('' '') rec=''09''x;'; put 'otherwise putlog "%str(WARN)ING: missing value for " entity=;'; put 'end;'; put 'rc =fput(fileid, substr(rec,1,1));'; put 'rc =fwrite(fileid);'; put 'end;'; put 'else do;'; put 'rc =fput(fileid,rec);'; put 'rc =fwrite(fileid);'; put 'end;'; put 'end;'; put 'end;'; put 'getout:'; put 'rc=fclose(filein);'; put 'rc=fclose(fileid);'; put 'run;'; put '%if &showlog=YES %then %do;'; put 'data _null_;'; put 'infile &fref lrecl=32767 end=last;'; put 'input;'; put 'if _n_=1 then putlog ''>>stpcodeBEGIN<<'';'; put 'putlog _infile_;'; put 'if last then putlog ''>>stpcodeEND<<'';'; put 'run;'; put '%end;'; put 'filename __getdoc clear;'; put '%if &outref=0 %then %do;'; put 'filename &fref clear;'; put '%end;'; put '%mend mm_getstpcode;'; put '%macro dc_getsettings();'; put '%global _program;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(syscc=&syscc on entry (&syswarningtext &syserrortext))'; put ')'; put '%if %length(&_PROGRAM)>1 %then %let root=&_program;'; put '%else %do;'; put '%global _metauser;'; put '%let _metauser=&sysuserid;'; put '/* to mimic a "real" _program we need to give a dummy role and stp name */'; put '%let root=/dummyRole/dummyName;'; put '%end;'; put '/* the DC precode is stored in the Admin folder in the root of'; put 'the project. Lets find that root. */'; put '%put &=root;'; put '%let root=%mf_getapploc();'; put '%put &=root;'; put '/* Now we know the root location we can retrieve the params */'; put '%let temploc=%sysfunc(pathname(work))/settings.txt;'; put '%mm_getstpcode(tree=&root/services/public'; put ',name=Data_Controller_Settings'; put ',outloc=&temploc'; put ')'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Unable to run getstpcode)'; put ')'; put 'filename _getsets "&temploc" lrecl=2000;'; put '/*'; put 'Do not use mp_include here - this puts a copy in every service, which creates'; put 'compilation problems when calling services from mp_include'; put '*/'; put '%inc _getsets/source2;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Problem running &sysmacroname (&syswarningtext &syserrortext))'; put ')'; put '%mend dc_getsettings;'; put '%macro mf_fmtdttm('; put ')/*/STORE SOURCE*/;'; put '%if "&sysver"="9.2" or "&sysver"="9.3"'; put 'or ("&sysver"="9.4" and "%substr(&SYSVLONG,9,1)" le "3")'; put 'or "%substr(&sysver,1,1)"="4"'; put 'or "%substr(&sysver,1,1)"="5"'; put '%then %do;DATETIME19.3%end;'; put '%else %do;E8601DT26.6%end;'; put '%mend mf_fmtdttm;'; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '%macro mp_init(prefix=SASJS'; put ')/*/STORE SOURCE*/;'; put '%if %symexist(SASJS_PREFIX) %then %return; /* only run once */'; put '%global'; put 'SASJS_PREFIX /* the ONLY hard-coded global macro variable in SASjs */'; put '&prefix._FUNCTIONS /* used in mcf_init() to track core function compilation */'; put '&prefix._INIT_NUM /* initialisation time as numeric */'; put '&prefix._INIT_DTTM /* initialisation time in E8601DT26.6 format */'; put '&prefix.WORK /* avoid typing %sysfunc(pathname(work)) every time */'; put ';'; put '%let sasjs_prefix=&prefix;'; put 'data _null_;'; put 'dttm=datetime();'; put 'call symputx("&sasjs_prefix._init_num",dttm,''g'');'; put 'call symputx("&sasjs_prefix._init_dttm",put(dttm,E8601DT26.6),''g'');'; put 'call symputx("&sasjs_prefix.work",pathname(''WORK''),''g'');'; put 'run;'; put 'options'; put 'compress=CHAR /* default is none so ensure we have something! */'; put 'datastmtchk=ALLKEYWORDS /* protection from overwriting input datasets */'; put 'errorcheck=STRICT /* catch errs in libname/filename statements */'; put 'fmterr /* ensure err when a format cannot be found */'; put 'mergenoby=%str(ERR)OR /* throw err when a merge has no BY variables */'; put 'missing=. /* changing this can cause hard to detect errs */'; put 'noquotelenmax /* avoid warnings for long strings */'; put 'noreplace /* avoid overwriting permanent datasets */'; put 'ps=max /* reduce log size slightly */'; put 'ls=max /* reduce log even more and avoid word truncation */'; put 'validmemname=COMPATIBLE /* avoid special characters etc in table names */'; put 'validvarname=V7 /* avoid special characters etc in variable names */'; put 'varinitchk=%str(ERR)OR /* avoid data mistakes from variable name typos */'; put 'varlenchk=%str(ERR)OR /* fail hard if truncation (data loss) can result */'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5" %then %do;'; put 'noautocorrect /* disallow misspelled procedure names */'; put 'dsoptions=note2err /* undocumented - convert bad NOTEs to ERRs */'; put '%end;'; put ';'; put '%mend mp_init;'; put '%macro mpeinit(fetch=YES);'; put '%global mpeinit'; put 'mpeadmins /* group with unrestricted Meditor access */'; put 'mpelocapprovals /* location for landing and staging files */'; put 'mpelib /* location of configuration tables for DC */'; put 'dc_repo_users /* location of user / group metadata */'; put 'dc_licence_key /* extracted in dc_getsettings */'; put 'dc_activation_key /* extracted in dc_getsettings */'; put 'dc_locale /* extracted in dc_getsettings */'; put 'dc_dttmtfmt /* can be overridden in dc_getsettings */'; put '_debug'; put ';'; put '%if &mpeinit=1 %then %return;'; put '%else %let mpeinit=1;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(Problem on service startup (&syswarningtext &syserrortext))'; put ')'; put '%mp_init()'; put '%if &fetch=YES %then %do;'; put '%webout(FETCH)'; put '%end;'; put '%global _CLIENTNAME;'; put '%mp_abort(iftrue= (&_CLIENTNAME=SAS Enterprise Guide)'; put ',mac=&_program..sas'; put ',msg=%str(Data Controller is a web app and should not be executed from EG)'; put ')'; put 'options urlencoding=utf8 nobomfile lrecl=32767;'; put '%let perf=%sysfunc(datetime());'; put '%put perfdiff: 0;'; put '%let dc_locale=SYSTEM; /* default if not set */'; put '/**'; put '* E8601DT26.6 has widest database support - but not all SAS flavours can'; put '* handle it. Override in the settings STP if needed.'; put '*/'; put 'data _null_;'; put 'dc_dttmtfmt=''"%sysfunc(datetime(),''!!"%mf_fmtdttm()"!!'')"dt'';'; put 'call symputx(''dc_dttmtfmt'',dc_dttmtfmt);'; put 'put dc_dttmtfmt=;'; put 'run;'; put '%put &=dc_dttmtfmt;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc prior to dc_getsettings)'; put ')'; put '%dc_getsettings()'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc after dc_getsettings)'; put ')'; put 'data _null_;'; put 'set &DC_LIBREF..mpe_config(where=('; put 'var_scope="DC"'; put 'and &dc_dttmtfmt lt tx_to'; put 'and var_active=1'; put '));'; put 'call symputx(var_name,var_value,''G'');'; put 'putlog var_name "=" var_value;'; put 'run;'; put '%let mpelib=&dc_libref;'; put '%let mpeadmins=&dc_admin_group;'; put '%let mpelocapprovals=&dc_staging_area;'; put '%let dc_repo_users=&dc_repo_users;'; put '%if &dc_locale ne SYSTEM %then %do;'; put 'options locale=&dc_locale;'; put '%end;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(Problem during compilation or with STP precode (&syswarningtext))'; put ')'; put '%mend mpeinit;'; put '%macro mf_mval(var);'; put '%if %symexist(&var) %then %do;'; put '%superq(&var)'; put '%end;'; put '%mend mf_mval;'; put '%macro mf_trimstr(basestr,trimstr);'; put '%local baselen trimlen trimval;'; put '/* return if basestr is shorter than trimstr (or 0) */'; put '%let baselen=%length(%superq(basestr));'; put '%let trimlen=%length(%superq(trimstr));'; put '%if &baselen < &trimlen or &baselen=0 %then %return;'; put '/* obtain the characters from the end of basestr */'; put '%let trimval=%qsubstr(%superq(basestr)'; put ',%length(%superq(basestr))-&trimlen+1'; put ',&trimlen);'; put '/* compare and if matching, chop it off! */'; put '%if %superq(basestr)=%superq(trimstr) %then %do;'; put '%return;'; put '%end;'; put '%else %if %superq(trimval)=%superq(trimstr) %then %do;'; put '%qsubstr(%superq(basestr),1,%length(%superq(basestr))-&trimlen)'; put '%end;'; put '%else %do;'; put '&basestr'; put '%end;'; put '%mend mf_trimstr;'; put '%macro mf_getplatform(switch'; put ')/*/STORE SOURCE*/;'; put '%local a b c;'; put '%if &switch.NONE=NONE %then %do;'; put '%if %symexist(sasjsprocessmode) %then %do;'; put '%if &sasjsprocessmode=Stored Program %then %do;'; put 'SASJS'; put '%return;'; put '%end;'; put '%end;'; put '%if %symexist(sysprocessmode) %then %do;'; put '%if "&sysprocessmode"="SAS Object Server"'; put 'or "&sysprocessmode"= "SAS Compute Server" %then %do;'; put 'SASVIYA'; put '%end;'; put '%else %if "&sysprocessmode"="SAS Stored Process Server"'; put 'or "&sysprocessmode"="SAS Workspace Server"'; put '%then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if %symexist(_metaport) or %symexist(_metauser) %then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if &switch=SASSTUDIO %then %do;'; put '/* return the version of SAS Studio else 0 */'; put '%if %mf_mval(_CLIENTAPP)=%str(SAS Studio) %then %do;'; put '%let a=%mf_mval(_CLIENTVERSION);'; put '%let b=%scan(&a,1,.);'; put '%if %eval(&b >2) %then %do;'; put '&b'; put '%end;'; put '%else 0;'; put '%end;'; put '%else 0;'; put '%end;'; put '%else %if &switch=VIYARESTAPI %then %do;'; put '%mf_trimstr(%sysfunc(getoption(servicesbaseurl)),/)'; put '%end;'; put '%mend mf_getplatform;'; put '%macro mpeterm();'; put '%local oldloc;'; put 'data _null_;'; put 'if symexist(''SYSPRINTTOLOG'') then oldloc=symget(''SYSPRINTTOLOG'');'; put 'else oldloc=getoption(''LOG'');'; put 'if subpad(oldloc,1,1) not in (''"'',"''",'' '') then oldloc=quote(cats(oldloc));'; put 'call symputx(''oldloc'',oldloc,''l'');'; put 'run;'; put '%if %length(&oldloc)>0 %then %do;'; put 'proc printto log=log;'; put 'run;'; put 'data _null_;'; put 'infile &oldloc;'; put 'input; putlog _infile_;'; put 'run;'; put '%end;'; put '%if %sysfunc(exist(&dc_libref..mpe_requests)) and %mf_getplatform() ne SASVIYA'; put '%then %do;'; put 'data ;'; put 'if 0 then set &dc_libref..mpe_requests;'; put 'request_dttm=%sysfunc(datetime());'; put 'request_user="%mf_getuser()";'; put 'request_service="%scan(&_program,-2,/)/%scan(&_program,-1,/)";'; put 'request_params='''';'; put 'output;stop;'; put 'proc append base=&dc_libref..mpe_requests data=&syslast force nowarn;'; put 'run;'; put '%end;'; put '%mend mpeterm;'; put '%macro mf_abort(mac=mf_abort.sas, msg=, iftrue=%str(1=1)'; put ')/des=''ungraceful abort'' /*STORE SOURCE*/;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%put NOTE: /// mf_abort macro executing //;'; put '%if %length(&mac)>0 %then %put NOTE- called by &mac;'; put '%put NOTE - &msg;'; put '%abort;'; put '%mend mf_abort;'; put '/** @endcond */'; put '%macro mf_verifymacvars('; put 'verifyVars /* list of macro variable NAMES */'; put ',makeUpcase=NO /* set to YES to make all the variable VALUES uppercase */'; put ',mAbort=SOFT'; put ')/*/STORE SOURCE*/;'; put '%local verifyIterator verifyVar abortmsg;'; put '%do verifyIterator=1 %to %sysfunc(countw(&verifyVars,%str( )));'; put '%let verifyVar=%qscan(&verifyVars,&verifyIterator,%str( ));'; put '%if not %symexist(&verifyvar) %then %do;'; put '%let abortmsg= Variable &verifyVar is MISSING;'; put '%goto exit_err;'; put '%end;'; put '%if %length(%trim(&&&verifyVar))=0 %then %do;'; put '%let abortmsg= Variable &verifyVar is EMPTY;'; put '%goto exit_err;'; put '%end;'; put '%if &makeupcase=YES %then %do;'; put '%let &verifyVar=%upcase(&&&verifyvar);'; put '%end;'; put '%end;'; put '%goto exit_success;'; put '%exit_err:'; put '%put &abortmsg;'; put '%mf_abort(iftrue=(&mabort ne SOFT),'; put 'mac=mf_verifymacvars,'; put 'msg=%str(&abortmsg)'; put ')'; put '0'; put '%return;'; put '%exit_success:'; put '1'; put '%mend mf_verifymacvars;'; put '%macro mf_existds(libds'; put ')/*/STORE SOURCE*/;'; put '%if %sysfunc(exist(&libds)) ne 1 & %sysfunc(exist(&libds,VIEW)) ne 1 %then 0;'; put '%else 1;'; put '%mend mf_existds;'; put '%macro mf_getvarlist(libds'; put ',dlm=%str( )'; put ',quote=no'; put ',typefilter=A'; put ')/*/STORE SOURCE*/;'; put '/* declare local vars */'; put '%local outvar dsid nvars x rc dlm q var vtype;'; put '/* credit Rowland Hale - byte34 is double quote, 39 is single quote */'; put '%if %upcase("e)=DOUBLE %then %let q=%qsysfunc(byte(34));'; put '%else %if %upcase("e)=SINGLE %then %let q=%qsysfunc(byte(39));'; put '/* open dataset in macro */'; put '%let dsid=%sysfunc(open(&libds));'; put '%if &dsid %then %do;'; put '%let nvars=%sysfunc(attrn(&dsid,NVARS));'; put '%if &nvars>0 %then %do;'; put '/* add variables with supplied delimeter */'; put '%do x=1 %to &nvars;'; put '/* get variable type */'; put '%let vtype=%sysfunc(vartype(&dsid,&x));'; put '%if &vtype=&typefilter or &typefilter=A %then %do;'; put '%let var=&q.%sysfunc(varname(&dsid,&x))&q.;'; put '%if &var=&q&q %then %do;'; put '%put &sysmacroname: Empty column found in &libds!;'; put '%let var=&q. &q.;'; put '%end;'; put '%if %quote(&outvar)=%quote() %then %let outvar=&var;'; put '%else %let outvar=&outvar.&dlm.&var.;'; put '%end;'; put '%end;'; put '%end;'; put '%let rc=%sysfunc(close(&dsid));'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: Unable to open &libds (rc=&dsid);'; put '%put &sysmacroname: SYSMSG= %sysfunc(sysmsg());'; put '%let rc=%sysfunc(close(&dsid));'; put '%end;'; put '%do;%unquote(&outvar)%end;'; put '%mend mf_getvarlist;'; put '%macro mf_wordsInStr1ButNotStr2('; put 'Str1= /* string containing words to extract */'; put ',Str2= /* used to compare with the extract string */'; put ')/*/STORE SOURCE*/;'; put '%local count_base count_extr i i2 extr_word base_word match outvar;'; put '%if %length(&str1)=0 or %length(&str2)=0 %then %do;'; put '%put base string (str1)= &str1;'; put '%put compare string (str2) = &str2;'; put '%return;'; put '%end;'; put '%let count_base=%sysfunc(countw(&Str2));'; put '%let count_extr=%sysfunc(countw(&Str1));'; put '%do i=1 %to &count_extr;'; put '%let extr_word=%scan(&Str1,&i,%str( ));'; put '%let match=0;'; put '%do i2=1 %to &count_base;'; put '%let base_word=%scan(&Str2,&i2,%str( ));'; put '%if &extr_word=&base_word %then %let match=1;'; put '%end;'; put '%if &match=0 %then %let outvar=&outvar &extr_word;'; put '%end;'; put '&outvar'; put '%mend mf_wordsInStr1ButNotStr2;'; put '%macro mf_isblank(param'; put ')/*/STORE SOURCE*/;'; put '%sysevalf(%superq(param)=,boolean)'; put '%mend mf_isblank;'; put '%macro mp_dropmembers('; put 'list /* space separated list of datasets / views */'; put ',libref=WORK /* can only drop from a single library at a time */'; put ',iftrue=%str(1=1)'; put ')/*/STORE SOURCE*/;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%if %mf_isblank(&list) %then %do;'; put '%put NOTE: nothing to drop!;'; put '%return;'; put '%end;'; put 'proc datasets lib=&libref nolist;'; put 'delete &list;'; put 'delete &list /mtype=view;'; put 'run;'; put '%mend mp_dropmembers;'; put '%macro mp_dirlist(path=%sysfunc(pathname(work))'; put ', fref=0'; put ', outds=work.mp_dirlist'; put ', getattrs=NO'; put ', showparent=NO'; put ', maxdepth=0'; put ', level=0 /* The level of recursion to perform. For internal use only. */'; put ')/*/STORE SOURCE*/;'; put '%let getattrs=%upcase(&getattrs)XX;'; put '/* temp table */'; put '%local out_ds;'; put 'data;run;'; put '%let out_ds=%str(&syslast);'; put '/* drop main (top) table if it exists */'; put '%if &level=0 %then %do;'; put '%mp_dropmembers(%scan(&outds,-1,.), libref=WORK)'; put '%end;'; put 'data &out_ds(compress=no'; put 'keep=file_or_folder filepath filename ext msg directory level'; put ');'; put 'length directory filepath $500 fref fref2 $8 file_or_folder $6 filename $80'; put 'ext $20 msg $200 foption $16;'; put 'if _n_=1 then call missing(of _all_);'; put 'retain level &level;'; put '%if &fref=0 %then %do;'; put 'rc = filename(fref, "&path");'; put '%end;'; put '%else %do;'; put 'fref="&fref";'; put 'rc=0;'; put '%end;'; put 'if rc = 0 then do;'; put 'did = dopen(fref);'; put 'if did=0 then do;'; put 'putlog "NOTE: This directory is empty, or does not exist - &path";'; put 'msg=sysmsg();'; put 'put (_all_)(=);'; put 'stop;'; put 'end;'; put '/* attribute is OS-dependent - could be "Directory" or "Directory Name" */'; put 'numopts=doptnum(did);'; put 'do i=1 to numopts;'; put 'foption=doptname(did,i);'; put 'if foption=:''Directory'' then i=numopts;'; put 'end;'; put 'directory=dinfo(did,foption);'; put 'rc = filename(fref);'; put 'end;'; put 'else do;'; put 'msg=sysmsg();'; put 'put _all_;'; put 'stop;'; put 'end;'; put 'dnum = dnum(did);'; put 'do i = 1 to dnum;'; put 'filename = dread(did, i);'; put 'filepath=cats(directory,''/'',filename);'; put 'rc = filename(fref2,filepath);'; put 'midd=dopen(fref2);'; put 'dmsg=sysmsg();'; put 'if did > 0 then file_or_folder=''folder'';'; put 'rc=dclose(midd);'; put 'midf=fopen(fref2);'; put 'fmsg=sysmsg();'; put 'if midf > 0 then file_or_folder=''file'';'; put 'rc=fclose(midf);'; put 'if index(fmsg,''File is in use'') or index(dmsg,''is not a directory'')'; put 'then file_or_folder=''file'';'; put 'else if index(fmsg,''Insufficient authorization'') then file_or_folder=''file'';'; put 'else if file_or_folder='''' then file_or_folder=''locked'';'; put 'if file_or_folder=''file'' then do;'; put 'ext = prxchange(''s/.*\.{1,1}(.*)/$1/'', 1, filename);'; put 'if filename = ext then ext = '' '';'; put 'end;'; put 'else do;'; put 'ext='''';'; put 'file_or_folder=''folder'';'; put 'end;'; put 'output;'; put 'end;'; put 'rc = dclose(did);'; put '%if &showparent=YES and &level=0 %then %do;'; put 'filepath=directory;'; put 'file_or_folder=''folder'';'; put 'ext='''';'; put 'filename=scan(directory,-1,''/\'');'; put 'msg='''';'; put 'level=&level;'; put 'output;'; put '%end;'; put 'stop;'; put 'run;'; put '%if %substr(&getattrs,1,1)=Y %then %do;'; put 'data &out_ds;'; put 'set &out_ds;'; put 'length infoname infoval $60 fref $8;'; put 'if _n_=1 then call missing(fref);'; put 'rc=filename(fref,filepath);'; put 'drop rc infoname fid i close fref;'; put 'if file_or_folder=''file'' then do;'; put 'fid=fopen(fref);'; put 'if fid le 0 then do;'; put 'msg=sysmsg();'; put 'putlog "Could not open file:" filepath fid= ;'; put 'sasname=''_MCNOTVALID_'';'; put 'output;'; put 'end;'; put 'else do i=1 to foptnum(fid);'; put 'infoname=foptname(fid,i);'; put 'infoval=finfo(fid,infoname);'; put 'sasname=compress(infoname, ''_'', ''adik'');'; put 'if anydigit(sasname)=1 then sasname=substr(sasname,anyalpha(sasname));'; put 'if upcase(sasname) ne ''FILENAME'' then output;'; put 'end;'; put 'close=fclose(fid);'; put 'end;'; put 'else do;'; put 'fid=dopen(fref);'; put 'if fid le 0 then do;'; put 'msg=sysmsg();'; put 'putlog "Could not open folder:" filepath fid= ;'; put 'sasname=''_MCNOTVALID_'';'; put 'output;'; put 'end;'; put 'else do i=1 to doptnum(fid);'; put 'infoname=doptname(fid,i);'; put 'infoval=dinfo(fid,infoname);'; put 'sasname=compress(infoname, ''_'', ''adik'');'; put 'if anydigit(sasname)=1 then sasname=substr(sasname,anyalpha(sasname));'; put 'if upcase(sasname) ne ''FILENAME'' then output;'; put 'end;'; put 'close=dclose(fid);'; put 'end;'; put 'run;'; put 'proc sort;'; put 'by filepath sasname;'; put 'proc transpose data=&out_ds out=&out_ds(drop=_:);'; put 'id sasname;'; put 'var infoval;'; put 'by filepath file_or_folder filename ext ;'; put 'run;'; put '%end;'; put 'data &out_ds;'; put 'set &out_ds(where=(filepath ne ''''));'; put 'run;'; put '/**'; put '* The above transpose can mean that some updates create additional columns.'; put '* This necessitates the occasional use of datastep over proc append.'; put '*/'; put '%if %mf_existds(&outds) %then %do;'; put '%local basevars appvars newvars;'; put '%let basevars=%mf_getvarlist(&outds);'; put '%let appvars=%mf_getvarlist(&out_ds);'; put '%let newvars=%length(%mf_wordsinstr1butnotstr2(Str1=&appvars,Str2=&basevars));'; put '%if &newvars>0 %then %do;'; put 'data &outds;'; put 'set &outds &out_ds;'; put 'run;'; put '%end;'; put '%else %do;'; put 'proc append base=&outds data=&out_ds force nowarn;'; put 'run;'; put '%end;'; put '%end;'; put '%else %do;'; put 'proc append base=&outds data=&out_ds;'; put 'run;'; put '%end;'; put '/* recursive call */'; put '%if &maxdepth>&level or &maxdepth=MAX %then %do;'; put 'data _null_;'; put 'set &out_ds;'; put 'where file_or_folder=''folder'';'; put '%if &showparent=YES and &level=0 %then %do;'; put 'if filepath ne directory;'; put '%end;'; put 'length code $10000;'; put 'code=cats(''%nrstr(%mp_dirlist(path='',filepath,",outds=&outds"'; put ',",getattrs=&getattrs,level=%eval(&level+1),maxdepth=&maxdepth))");'; put 'put code=;'; put 'call execute(code);'; put 'run;'; put '%end;'; put '/* tidy up */'; put 'proc sql;'; put 'drop table &out_ds;'; put '%mend mp_dirlist;'; put '%macro mp_binarycopy('; put 'inloc= /* full path and filename of the object to be copied */'; put ',outloc= /* full path and filename of object to be created */'; put ',inref=____in /* override default to use own filerefs */'; put ',outref=____out /* override default to use own filerefs */'; put ',mode=CREATE'; put ',iftrue=%str(1=1)'; put ')/*/STORE SOURCE*/;'; put '%local mod;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%if &mode=APPEND %then %let mod=mod;'; put '/* these IN and OUT filerefs can point to anything */'; put '%if &inref = ____in %then %do;'; put 'filename &inref &inloc lrecl=1048576 ;'; put '%end;'; put '%if &outref=____out %then %do;'; put 'filename &outref &outloc lrecl=1048576 &mod;'; put '%end;'; put '/* copy the file byte-for-byte */'; put 'data _null_;'; put 'infile &inref lrecl=1 recfm=n;'; put 'file &outref &mod recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put '%if &inref = ____in %then %do;'; put 'filename &inref clear;'; put '%end;'; put '%if &outref=____out %then %do;'; put 'filename &outref clear;'; put '%end;'; put '%mend mp_binarycopy;'; put '%macro mfs_httpheader(header_name'; put ',header_value'; put ')/*/STORE SOURCE*/;'; put '%global sasjs_stpsrv_header_loc;'; put '%local fref fid i;'; put '%if %sysfunc(filename(fref,&sasjs_stpsrv_header_loc)) ne 0 %then %do;'; put '%put &=fref &=sasjs_stpsrv_header_loc;'; put '%put %str(ERR)OR: %sysfunc(sysmsg());'; put '%return;'; put '%end;'; put '%let fid=%sysfunc(fopen(&fref,A));'; put '%if &fid=0 %then %do;'; put '%put %str(ERR)OR: %sysfunc(sysmsg());'; put '%return;'; put '%end;'; put '%let rc=%sysfunc(fput(&fid,%str(&header_name): %str(&header_value)));'; put '%let rc=%sysfunc(fwrite(&fid));'; put '%let rc=%sysfunc(fclose(&fid));'; put '%let rc=%sysfunc(filename(&fref));'; put '%mend mfs_httpheader;'; put '%macro mp_streamfile('; put 'contenttype=TEXT'; put ',inloc='; put ',inref=0'; put ',iftrue=%str(1=1)'; put ',outname='; put ',outref=_webout'; put ')/*/STORE SOURCE*/;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%let contentype=%upcase(&contenttype);'; put '%let outref=%upcase(&outref);'; put '%local platform; %let platform=%mf_getplatform();'; put '/**'; put '* check engine type to avoid the below err message:'; put '* > Function is only valid for filerefs using the CACHE access method.'; put '*/'; put '%local streamweb;'; put '%let streamweb=0;'; put 'data _null_;'; put 'set sashelp.vextfl(where=(upcase(fileref)="&outref"));'; put 'if xengine=''STREAM'' then call symputx(''streamweb'',1,''l'');'; put 'run;'; put '%if &contentype=CSV %then %do;'; put '%if (&platform=SASMETA and &streamweb=1) %then %do;'; put 'data _null_;'; put 'rc=stpsrv_header(''Content-Type'',''application/csv'');'; put 'rc=stpsrv_header(''Content-disposition'',"attachment; filename=&outname");'; put 'run;'; put '%end;'; put '%else %if &platform=SASVIYA %then %do;'; put 'filename &outref filesrvc parenturi="&SYS_JES_JOB_URI" name=''_webout.txt'''; put 'contenttype=''application/csv'''; put 'contentdisp="attachment; filename=&outname";'; put '%end;'; put '%else %if &platform=SASJS %then %do;'; put '%mfs_httpheader(Content-Type,application/csv)'; put '%mfs_httpheader(Content-disposition,%str(attachment; filename=&outname))'; put '%end;'; put '%end;'; put '%else %if &contentype=EXCEL %then %do;'; put '/* suitable for XLS format */'; put '%if (&platform=SASMETA and &streamweb=1) %then %do;'; put 'data _null_;'; put 'rc=stpsrv_header(''Content-Type'',''application/vnd.ms-excel'');'; put 'rc=stpsrv_header(''Content-disposition'',"attachment; filename=&outname");'; put 'run;'; put '%end;'; put '%else %if &platform=SASVIYA %then %do;'; put 'filename &outref filesrvc parenturi="&SYS_JES_JOB_URI" name=''_webout.xls'''; put 'contenttype=''application/vnd.ms-excel'''; put 'contentdisp="attachment; filename=&outname";'; put '%end;'; put '%else %if &platform=SASJS %then %do;'; put '%mfs_httpheader(Content-Type,application/vnd.ms-excel)'; put '%mfs_httpheader(Content-disposition,%str(attachment; filename=&outname))'; put '%end;'; put '%end;'; put '%else %if &contentype=GIF or &contentype=JPEG or &contentype=PNG %then %do;'; put '%if (&platform=SASMETA and &streamweb=1) %then %do;'; put 'data _null_;'; put 'rc=stpsrv_header(''Content-Type'',"image/%lowcase(&contenttype)");'; put 'run;'; put '%end;'; put '%else %if &platform=SASVIYA %then %do;'; put 'filename &outref filesrvc parenturi="&SYS_JES_JOB_URI"'; put 'contenttype="image/%lowcase(&contenttype)";'; put '%end;'; put '%else %if &platform=SASJS %then %do;'; put '%mfs_httpheader(Content-Type,image/%lowcase(&contenttype))'; put '%end;'; put '%end;'; put '%else %if &contentype=HTML or &contenttype=MARKDOWN %then %do;'; put '%if (&platform=SASMETA and &streamweb=1) %then %do;'; put 'data _null_;'; put 'rc=stpsrv_header(''Content-Type'',"text/%lowcase(&contenttype)");'; put 'rc=stpsrv_header(''Content-disposition'',"attachment; filename=&outname");'; put 'run;'; put '%end;'; put '%else %if &platform=SASVIYA %then %do;'; put 'filename &outref filesrvc parenturi="&SYS_JES_JOB_URI" name="_webout.json"'; put 'contenttype="text/%lowcase(&contenttype)"'; put 'contentdisp="attachment; filename=&outname";'; put '%end;'; put '%else %if &platform=SASJS %then %do;'; put '%mfs_httpheader(Content-Type,text/%lowcase(&contenttype))'; put '%mfs_httpheader(Content-disposition,%str(attachment; filename=&outname))'; put '%end;'; put '%end;'; put '%else %if &contentype=TEXT %then %do;'; put '%if (&platform=SASMETA and &streamweb=1) %then %do;'; put 'data _null_;'; put 'rc=stpsrv_header(''Content-Type'',''application/text'');'; put 'rc=stpsrv_header(''Content-disposition'',"attachment; filename=&outname");'; put 'run;'; put '%end;'; put '%else %if &platform=SASVIYA %then %do;'; put 'filename &outref filesrvc parenturi="&SYS_JES_JOB_URI" name=''_webout.txt'''; put 'contenttype=''application/text'''; put 'contentdisp="attachment; filename=&outname";'; put '%end;'; put '%else %if &platform=SASJS %then %do;'; put '%mfs_httpheader(Content-Type,application/text)'; put '%mfs_httpheader(Content-disposition,%str(attachment; filename=&outname))'; put '%end;'; put '%end;'; put '%else %if &contentype=WOFF or &contentype=WOFF2 or &contentype=TTF %then %do;'; put '%if (&platform=SASMETA and &streamweb=1) %then %do;'; put 'data _null_;'; put 'rc=stpsrv_header(''Content-Type'',"font/%lowcase(&contenttype)");'; put 'run;'; put '%end;'; put '%else %if &platform=SASVIYA %then %do;'; put 'filename &outref filesrvc parenturi="&SYS_JES_JOB_URI"'; put 'contenttype="font/%lowcase(&contenttype)";'; put '%end;'; put '%else %if &platform=SASJS %then %do;'; put '%mfs_httpheader(Content-Type,font/%lowcase(&contenttype))'; put '%end;'; put '%end;'; put '%else %if &contentype=XLSX %then %do;'; put '%if (&platform=SASMETA and &streamweb=1) %then %do;'; put 'data _null_;'; put 'rc=stpsrv_header(''Content-Type'','; put '''application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'');'; put 'rc=stpsrv_header(''Content-disposition'',"attachment; filename=&outname");'; put 'run;'; put '%end;'; put '%else %if &platform=SASVIYA %then %do;'; put 'filename &outref filesrvc parenturi="&SYS_JES_JOB_URI" name=''_webout.xls'''; put 'contenttype='; put '''application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'''; put 'contentdisp="attachment; filename=&outname";'; put '%end;'; put '%else %if &platform=SASJS %then %do;'; put '%mfs_httpheader(Content-Type'; put ',application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'; put ')'; put '%mfs_httpheader(Content-disposition,%str(attachment; filename=&outname))'; put '%end;'; put '%end;'; put '%else %if &contentype=ZIP %then %do;'; put '%if (&platform=SASMETA and &streamweb=1) %then %do;'; put 'data _null_;'; put 'rc=stpsrv_header(''Content-Type'',''application/zip'');'; put 'rc=stpsrv_header(''Content-disposition'',"attachment; filename=&outname");'; put 'run;'; put '%end;'; put '%else %if &platform=SASVIYA %then %do;'; put 'filename &outref filesrvc parenturi="&SYS_JES_JOB_URI" name=''_webout.zip'''; put 'contenttype=''application/zip'''; put 'contentdisp="attachment; filename=&outname";'; put '%end;'; put '%else %if &platform=SASJS %then %do;'; put '%mfs_httpheader(Content-Type,application/zip)'; put '%mfs_httpheader(Content-disposition,%str(attachment; filename=&outname))'; put '%end;'; put '%end;'; put '%else %do;'; put '%put %str(ERR)OR: Content Type &contenttype NOT SUPPORTED by &sysmacroname!;'; put '%end;'; put '%if &inref ne 0 %then %do;'; put '%mp_binarycopy(inref=&inref,outref=&outref)'; put '%end;'; put '%else %do;'; put '%mp_binarycopy(inloc="&inloc",outref=&outref)'; put '%end;'; put '%mend mp_streamfile;'; put '* SAS Macros end;'; put '* SAS Includes start;'; put '* SAS Includes end;'; put '* Binary Files start;'; put '* Binary Files end;'; put '* ServiceInit start;'; put 'options noquotelenmax ps=max;'; put '/* create dummy macros to prevent stpbegin / stpend from corrupting sessions */'; put '%macro stpbegin();'; put '%put NOTE: the STPBEGIN macro should not be used for web apps!;'; put '%mend stpbegin;'; put '%macro stpend();'; put '%put NOTE: the STPEND macro should not be used for web apps!;'; put '%mend stpend;'; put '* ServiceInit end;'; put '* Service start;'; put '/**'; put '@file getlog.sas'; put '@brief Downloads the submission, useful if there is an error'; put '@details'; put '

SAS Macros

'; put '@li mf_verifymacvars.sas'; put '@li mf_getuser.sas'; put '@li mp_abort.sas'; put '@li mp_dirlist.sas'; put '@li mp_binarycopy.sas'; put '@li mp_streamfile.sas'; put '@version 9.2'; put '@author 4GL Apps Ltd'; put '@copyright 4GL Apps Ltd. This code may only be used within Data Controller'; put 'and may not be re-distributed or re-sold without the express permission of'; put '4GL Apps Ltd.'; put '**/'; put '%mpeinit()'; put '%mp_abort('; put 'iftrue=(%mf_verifymacvars(table)=0)'; put ',mac=&_program'; put ',msg=%str(Missing: table)'; put ')'; put '/* security checks */'; put '%let user=%mf_getuser();'; put '%let check_access=0;'; put 'proc sql noprint;'; put 'select count(*) into: check_access from &mpelib..mpe_loads'; put 'where csv_dir="&table" and user_nm="&user";'; put '%mp_abort(iftrue= (&check_access=0 )'; put ',msg=%str(&user not authorised to download audit data for &table)'; put ',mac=mpestp_getlog.sas'; put ')'; put 'ods package(ProdOutput) open nopf;'; put 'options notes source2 mprint;'; put '%mp_dirlist(outds=dirs, path=&mpelocapprovals/&TABLE)'; put 'data _null_;'; put 'set dirs;'; put 'if scan(filename,-1,''.'') not in (''sas7bdat'',''wpd'');'; put 'retain str1'; put '"ods package(ProdOutput) add file=''&mpelocapprovals/&TABLE/";'; put 'retain str2 "'' mimetype=''text/plain'' path=''contents/'';";'; put 'call execute(cats(str1,filename,str2));'; put 'run;'; put '%let archive_path=%sysfunc(pathname(work));'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(syscc=&syscc)'; put ')'; put 'ods package(ProdOutput) publish archive properties'; put '(archive_name= "&table..zip" archive_path="&archive_path");'; put 'ods package(ProdOutput) close;'; put '/* now serve zip file to client */'; put '%mp_streamfile(contenttype=ZIP'; put ',inloc=%str(&archive_path/&table..zip)'; put ',outname=&table..zip'; put ')'; put '%mpeterm()'; put '* Service end;'; run; %mm_createwebservice(path=&appLoc/&path, name=&service, code=sascode, server=&serverName, replace=yes) filename sascode clear; %let service=getsubmits; filename sascode temp lrecl=32767; data _null_; file sascode; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '/**'; put '@file mp_jsonout.sas'; put '@brief Writes JSON in SASjs format to a fileref'; put '@details This macro can be used to OPEN a JSON stream and send one or more'; put 'tables as arrays of rows, where each row can be an object or a nested array.'; put 'There are two engines available - DATASTEP or PROCJSON.'; put 'PROC JSON is fast but will produce errs like the ones below if'; put 'special chars are encountered.'; put '> (ERR)OR: Some code points did not transcode.'; put '> An object or array close is not valid at this point in the JSON text.'; put '> Date value out of range'; put 'If this happens, try running with ENGINE=DATASTEP.'; put 'The DATASTEP engine is used to handle special SAS missing numerics, and'; put 'can also convert entire datasets to formatted values. Output JSON is always'; put 'in UTF-8.'; put 'Usage:'; put 'filename tmp temp;'; put 'data class; set sashelp.class;run;'; put '%mp_jsonout(OPEN,jref=tmp)'; put '%mp_jsonout(OBJ,class,jref=tmp)'; put '%mp_jsonout(OBJ,class,dslabel=class2,jref=tmp,showmeta=Y)'; put '%mp_jsonout(CLOSE,jref=tmp)'; put 'data _null_;'; put 'infile tmp;'; put 'input;putlog _infile_;'; put 'run;'; put 'If you are building web apps with SAS then you are strongly encouraged to use'; put 'the mX_createwebservice macros in combination with the'; put '[sasjs adapter](https://github.com/sasjs/adapter).'; put 'For more information see https://sasjs.io'; put '@param [in] action Valid values:'; put '@li OPEN - opens the JSON'; put '@li OBJ - sends a table with each row as an object'; put '@li ARR - sends a table with each row in an array'; put '@li CLOSE - closes the JSON'; put '@param [in] ds The dataset to send. Must be a work table.'; put '@param [out] jref= (_webout) The fileref to which to send the JSON'; put '@param [out] dslabel= The name to give the table in the exported JSON'; put '@param [in] fmt= (Y) Whether to keep (Y) or strip (N) formats from the table'; put '@param [in] engine= (DATASTEP) Which engine to use to send the JSON. Options:'; put '@li PROCJSON (default)'; put '@li DATASTEP (more reliable when data has non standard characters)'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to JSON'; put '

Related Files

'; put '@li mp_ds2fmtds.sas'; put '@version 9.2'; put '@author Allan Bowe'; put '@source https://github.com/sasjs/core'; put '**/'; put '%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y'; put ',engine=DATASTEP'; put ',missing=NULL'; put ',showmeta=N'; put ',maxobs=MAX'; put ')/*/STORE SOURCE*/;'; put '%local tempds colinfo fmtds i numcols numobs stmt_obs lastobs optval'; put 'tmpds1 tmpds2 tmpds3 tmpds4;'; put '%let numcols=0;'; put '%if &maxobs ne MAX %then %let stmt_obs=%str(if _n_>&maxobs then stop;);'; put '%if &action=OPEN %then %do;'; put 'options nobomfile;'; put 'data _null_;file &jref encoding=''utf-8'' lrecl=200;'; put 'put ''{"PROCESSED_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''"'';'; put 'run;'; put '%end;'; put '%else %if (&action=ARR or &action=OBJ) %then %do;'; put '/* force variable names to always be uppercase in the JSON */'; put 'options validvarname=upcase;'; put '/* To avoid issues with _webout on EBI - such as encoding diffs and truncation'; put '(https://support.sas.com/kb/49/325.html) we use temporary files */'; put 'filename _sjs1 temp lrecl=200 ;'; put 'data _null_; file _sjs1 encoding=''utf-8'';'; put 'put ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":";'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjs1 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs1 clear;'; put '/* grab col defs */'; put 'proc contents noprint data=&ds'; put 'out=_data_(keep=name type length format formatl formatd varnum label);'; put 'run;'; put '%let colinfo=%scan(&syslast,2,.);'; put 'proc sort data=&colinfo;'; put 'by varnum;'; put 'run;'; put '/* move meta to mac vars */'; put 'data &colinfo;'; put 'if _n_=1 then call symputx(''numcols'',nobs,''l'');'; put 'set &colinfo end=last nobs=nobs;'; put 'name=upcase(name);'; put '/* fix formats */'; put 'if type=2 or type=6 then do;'; put 'typelong=''char'';'; put 'length fmt $49.;'; put 'if format='''' then fmt=cats(''$'',length,''.'');'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else fmt=cats(format,formatl,''.'');'; put 'end;'; put 'else do;'; put 'typelong=''num'';'; put 'if format='''' then fmt=''best.'';'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else if formatd=0 then fmt=cats(format,formatl,''.'');'; put 'else fmt=cats(format,formatl,''.'',formatd);'; put 'end;'; put '/* 32 char unique name */'; put 'newname=''sasjs''!!substr(cats(put(md5(name),$hex32.)),1,27);'; put 'call symputx(cats(''name'',_n_),name,''l'');'; put 'call symputx(cats(''newname'',_n_),newname,''l'');'; put 'call symputx(cats(''length'',_n_),length,''l'');'; put 'call symputx(cats(''fmt'',_n_),fmt,''l'');'; put 'call symputx(cats(''type'',_n_),type,''l'');'; put 'call symputx(cats(''typelong'',_n_),typelong,''l'');'; put 'call symputx(cats(''label'',_n_),coalescec(label,name),''l'');'; put '/* overwritten when fmt=Y and a custom format exists in catalog */'; put 'if typelong=''num'' then call symputx(cats(''fmtlen'',_n_),200,''l'');'; put 'else call symputx(cats(''fmtlen'',_n_),min(32767,ceil((length+10)*1.5)),''l'');'; put 'run;'; put '%let tempds=%substr(_%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql;'; put 'select count(*) into: lastobs from &ds;'; put '%if &maxobs ne MAX %then %let lastobs=%sysfunc(min(&lastobs,&maxobs));'; put '%if &engine=PROCJSON %then %do;'; put '%if &missing=STRING %then %do;'; put '%put &sysmacroname: Special Missings not supported in proc json.;'; put '%put &sysmacroname: Switching to DATASTEP engine;'; put '%goto datastep;'; put '%end;'; put 'data &tempds;'; put 'set &ds;'; put '&stmt_obs;'; put '%if &fmt=N %then format _numeric_ best32.;;'; put '/* PRETTY is necessary to avoid line truncation in large files */'; put 'filename _sjs2 temp lrecl=131068 encoding=''utf-8'';'; put 'proc json out=_sjs2 pretty'; put '%if &action=ARR %then nokeys ;'; put ';export &tempds / nosastags fmtnumeric;'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs2 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs2 clear;'; put '%end;'; put '%else %if &engine=DATASTEP %then %do;'; put '%datastep:'; put '%if %sysfunc(exist(&ds)) ne 1 & %sysfunc(exist(&ds,VIEW)) ne 1'; put '%then %do;'; put '%put &sysmacroname: &ds NOT FOUND!!!;'; put '%return;'; put '%end;'; put '%if &fmt=Y %then %do;'; put '/**'; put '* Extract format definitions'; put '* First, by getting library locations from dictionary.formats'; put '* Then, by exporting the width using proc format'; put '* Cannot use maxw from sashelp.vformat as not always populated'; put '* Cannot use fmtinfo() as not supported in all flavours'; put '*/'; put '%let tmpds1=%substr(fmtsum%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds2=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds3=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds4=%substr(col%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql noprint;'; put 'create table &tmpds1 as'; put 'select cats(libname,''.'',memname) as FMTCAT,'; put 'FMTNAME'; put 'from dictionary.formats'; put 'where fmttype=''F'' and libname is not null'; put 'and fmtname in (select format from &colinfo where format is not null)'; put 'order by 1;'; put 'create table &tmpds2('; put 'FMTNAME char(32),'; put 'LENGTH num'; put ');'; put '%local catlist cat fmtlist i;'; put 'select distinct fmtcat into: catlist separated by '' '' from &tmpds1;'; put '%do i=1 %to %sysfunc(countw(&catlist,%str( )));'; put '%let cat=%scan(&catlist,&i,%str( ));'; put 'proc sql;'; put 'select distinct fmtname into: fmtlist separated by '' '''; put 'from &tmpds1 where fmtcat="&cat";'; put 'proc format lib=&cat cntlout=&tmpds3(keep=fmtname length);'; put 'select &fmtlist;'; put 'run;'; put 'proc sql;'; put 'insert into &tmpds2 select distinct fmtname,length from &tmpds3;'; put '%end;'; put 'proc sql;'; put 'create table &tmpds4 as'; put 'select a.*, b.length as MAXW'; put 'from &colinfo a'; put 'left join &tmpds2 b'; put 'on cats(a.format)=cats(upcase(b.fmtname))'; put 'order by a.varnum;'; put 'data _null_;'; put 'set &tmpds4;'; put 'if not missing(maxw);'; put 'call symputx('; put 'cats(''fmtlen'',_n_),'; put '/* vars need extra padding due to JSON escaping of special chars */'; put 'min(32767,ceil((max(length,maxw)+10)*1.5))'; put ',''l'''; put ');'; put 'run;'; put '/* configure varlenchk - as we are explicitly shortening the variables */'; put '%let optval=%sysfunc(getoption(varlenchk));'; put 'options varlenchk=NOWARN;'; put 'data _data_(compress=char);'; put '/* shorten the new vars */'; put 'length'; put '%do i=1 %to &numcols;'; put '&&name&i $&&fmtlen&i'; put '%end;'; put ';'; put '/* rename on entry */'; put 'set &ds(rename=('; put '%do i=1 %to &numcols;'; put '&&name&i=&&newname&i'; put '%end;'; put '));'; put '&stmt_obs;'; put 'drop'; put '%do i=1 %to &numcols;'; put '&&newname&i'; put '%end;'; put ';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=num %then %do;'; put '&&name&i=cats(put(&&newname&i,&&fmt&i));'; put '%end;'; put '%else %do;'; put '&&name&i=put(&&newname&i,&&fmt&i);'; put '%end;'; put '%end;'; put 'if _error_ then do;'; put 'call symputx(''syscc'',1012);'; put 'stop;'; put 'end;'; put 'run;'; put '%let fmtds=&syslast;'; put 'options varlenchk=&optval;'; put '%end;'; put 'proc format; /* credit yabwon for special null removal */'; put 'value bart (default=40)'; put '%if &missing=NULL %then %do;'; put '._ - .z = null'; put '%end;'; put '%else %do;'; put '._ = [quote()]'; put '. = null'; put '.a - .z = [quote()]'; put '%end;'; put 'other = [best.];'; put 'data &tempds;'; put 'attrib _all_ label='''';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'length &&name&i $&&fmtlen&i...;'; put 'format &&name&i $&&fmtlen&i...;'; put '%end;'; put '%end;'; put '%if &fmt=Y %then %do;'; put 'set &fmtds;'; put '%end;'; put '%else %do;'; put 'set &ds;'; put '%end;'; put '&stmt_obs;'; put 'format _numeric_ bart.;'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'if findc(&&name&i,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put '&&name&i=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,&&name&i)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else &&name&i=quote(cats(&&name&i));'; put '%end;'; put '%end;'; put 'run;'; put 'filename _sjs3 temp lrecl=131068 ;'; put 'data _null_;'; put 'file _sjs3 encoding=''utf-8'';'; put 'if _n_=1 then put "[";'; put 'set &tempds;'; put 'if _n_>1 then put "," @; put'; put '%if &action=ARR %then "[" ; %else "{" ;'; put '%do i=1 %to &numcols;'; put '%if &i>1 %then "," ;'; put '%if &action=OBJ %then """&&name&i"":" ;'; put '"&&name&i"n /* name literal for reserved variable names */'; put '%end;'; put '%if &action=ARR %then "]" ; %else "}" ; ;'; put '/* close out the table */'; put 'data _null_;'; put 'file _sjs3 mod encoding=''utf-8'';'; put 'put '']'';'; put 'run;'; put 'data _null_;'; put 'infile _sjs3 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs3 clear;'; put '%end;'; put 'proc sql;'; put 'drop table &colinfo, &tempds;'; put '%if %substr(&showmeta,1,1)=Y %then %do;'; put 'filename _sjs4 temp lrecl=131068 encoding=''utf-8'';'; put 'data _null_;'; put 'file _sjs4;'; put 'length label $350;'; put 'put ", ""$%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":{""vars"":{";'; put 'do i=1 to &numcols;'; put 'name=quote(trim(symget(cats(''name'',i))));'; put 'format=quote(trim(symget(cats(''fmt'',i))));'; put 'label=quote(prxchange(''s/\\/\\\\/'',-1,trim(symget(cats(''label'',i)))));'; put 'length=quote(trim(symget(cats(''length'',i))));'; put 'type=quote(trim(symget(cats(''typelong'',i))));'; put 'if i>1 then put "," @@;'; put 'put name '':{"format":'' format '',"label":'' label'; put ''',"length":'' length '',"type":'' type ''}'';'; put 'end;'; put 'put ''}}'';'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs4 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs4 clear;'; put '%end;'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put 'data _null_; file &jref encoding=''utf-8'' mod ;'; put 'put "}";'; put 'run;'; put '%end;'; put '%mend mp_jsonout;'; put '/**'; put '@file mm_webout.sas'; put '@brief Send data to/from SAS Stored Processes'; put '@details This macro should be added to the start of each Stored Process,'; put '**immediately** followed by a call to:'; put '%mm_webout(FETCH)'; put 'This will read all the input data and create same-named SAS datasets in the'; put 'WORK library. You can then insert your code, and send data back using the'; put 'following syntax:'; put 'data some datasets; * make some data ;'; put 'retain some columns;'; put 'run;'; put '%mm_webout(OPEN)'; put '%mm_webout(ARR,some) * Array format, fast, suitable for large tables ;'; put '%mm_webout(OBJ,datasets) * Object format, easier to work with ;'; put 'Finally, wrap everything up send some helpful system variables too'; put '%mm_webout(CLOSE)'; put '@param [in] action Either FETCH, OPEN, ARR, OBJ or CLOSE'; put '@param [in] ds The dataset to send back to the frontend'; put '@param [out] dslabel= Value to use instead of table name for sending to JSON'; put '@param [in] fmt= (N) Setting Y converts all vars to their formatted values'; put '@param [out] fref= (_webout) The fileref to which to write the JSON'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] workobs= (0) When set to a positive integer, will create a new'; put 'output object (WORK) which contains this number of observations from all'; put 'tables in the WORK library.'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to output JSON'; put '

SAS Macros

'; put '@li mp_jsonout.sas'; put '

Related Macros

'; put '@li ms_webout.sas'; put '@li mv_webout.sas'; put '@version 9.3'; put '@author Allan Bowe'; put '**/'; put '%macro mm_webout(action,ds,dslabel=,fref=_webout,fmt=N,missing=NULL'; put ',showmeta=N,maxobs=MAX,workobs=0'; put ');'; put '%global _webin_file_count _webin_fileref1 _webin_name1 _program _debug'; put 'sasjs_tables;'; put '%local i tempds jsonengine;'; put '/* see https://github.com/sasjs/core/issues/41 */'; put '%if "%upcase(&SYSENCODING)" ne "UTF-8" %then %let jsonengine=PROCJSON;'; put '%else %let jsonengine=DATASTEP;'; put '%if &action=FETCH %then %do;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'options mprint notes mprintnest;'; put '%end;'; put '%let _webin_file_count=%eval(&_webin_file_count+0);'; put '/* now read in the data */'; put '%do i=1 %to &_webin_file_count;'; put '%if &_webin_file_count=1 %then %do;'; put '%let _webin_fileref1=&_webin_fileref;'; put '%let _webin_name1=&_webin_name;'; put '%end;'; put 'data _null_;'; put 'infile &&_webin_fileref&i termstr=crlf;'; put 'input;'; put 'call symputx(''input_statement'',_infile_);'; put 'putlog "&&_webin_name&i input statement: " _infile_;'; put 'stop;'; put 'data &&_webin_name&i;'; put 'infile &&_webin_fileref&i firstobs=2 dsd termstr=crlf encoding=''utf-8'';'; put 'input &input_statement;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'if _n_<20 then putlog _infile_;'; put '%end;'; put 'run;'; put '%let sasjs_tables=&sasjs_tables &&_webin_name&i;'; put '%end;'; put '%end;'; put '%else %if &action=OPEN %then %do;'; put '/* fix encoding */'; put 'OPTIONS NOBOMFILE;'; put '/**'; put '* check xengine type to avoid the below err message:'; put '* > Function is only valid for filerefs using the CACHE access method.'; put '*/'; put 'data _null_;'; put 'set sashelp.vextfl(where=(fileref="_WEBOUT"));'; put 'if xengine=''STREAM'' then do;'; put 'rc=stpsrv_header(''Content-type'',"text/html; encoding=utf-8");'; put 'end;'; put 'run;'; put '/* setup json */'; put 'data _null_;file &fref encoding=''utf-8'';'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutBEGIN<<'';'; put '%end;'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'run;'; put '%end;'; put '%else %if &action=ARR or &action=OBJ %then %do;'; put '%mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt,jref=&fref'; put ',engine=&jsonengine,missing=&missing,showmeta=&showmeta,maxobs=&maxobs'; put ')'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put '/* To avoid issues with _webout on EBI we use a temporary file */'; put 'filename _sjsref temp lrecl=131068;'; put '%if %str(&workobs) > 0 %then %do;'; put '/* if debug mode, send back first XX records of each work table also */'; put 'data;run;%let tempds=%scan(&syslast,2,.);'; put 'ods output Members=&tempds;'; put 'proc datasets library=WORK memtype=data;'; put '%local wtcnt;%let wtcnt=0;'; put 'data _null_;'; put 'set &tempds;'; put 'if not (upcase(name) =:"DATA"); /* ignore temp datasets */'; put 'i+1;'; put 'call symputx(cats(''wt'',i),name,''l'');'; put 'call symputx(''wtcnt'',i,''l'');'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put ",""WORK"":{";'; put '%do i=1 %to &wtcnt;'; put '%let wt=&&wt&i;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'dsid=open("WORK.&wt",''is'');'; put 'nlobs=attrn(dsid,''NLOBS'');'; put 'nvars=attrn(dsid,''NVARS'');'; put 'rc=close(dsid);'; put 'if &i>1 then put '',''@;'; put 'put " ""&wt"" : {";'; put 'put ''"nlobs":'' nlobs;'; put 'put '',"nvars":'' nvars;'; put '%mp_jsonout(OBJ,&wt,jref=_sjsref,dslabel=first10rows,showmeta=Y'; put ',maxobs=&workobs'; put ')'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put '%end;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put 'run;'; put '%end;'; put '/* close off json */'; put 'data _null_;file _sjsref mod encoding=''utf-8'';'; put 'length SYSPROCESSNAME syserrortext syswarningtext autoexec $512;'; put 'put ",""_DEBUG"" : ""&_debug"" ";'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'autoexec=quote(urlencode(trim(getoption(''autoexec''))));'; put 'put '',"AUTOEXEC" : '' autoexec;'; put 'put ",""MF_GETUSER"" : ""%mf_getuser()"" ";'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'put ",""SYSENCODING"" : ""&sysencoding"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSPROCESSID"" : ""&SYSPROCESSID"" ";'; put 'put ",""SYSPROCESSMODE"" : ""&SYSPROCESSMODE"" ";'; put 'SYSPROCESSNAME=quote(urlencode(cats(SYSPROCESSNAME)));'; put 'put ",""SYSPROCESSNAME"" : " SYSPROCESSNAME;'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put '',"SYSWARNINGTEXT" : '' syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'length memsize $32;'; put 'memsize="%sysfunc(INPUTN(%sysfunc(getoption(memsize)), best.),sizekmg.)";'; put 'memsize=quote(cats(memsize));'; put 'put '',"MEMSIZE" : '' memsize;'; put 'put "}" @;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutEND<<'';'; put '%end;'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjsref lrecl=1 recfm=n;'; put 'file &fref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjsref clear;'; put '%end;'; put '%mend mm_webout;'; put '%macro webout(action,ds,dslabel=,fmt=,missing=NULL,showmeta=NO,maxobs=MAX);'; put '%mm_webout(&action,ds=&ds,dslabel=&dslabel,fmt=&fmt'; put ',missing=&missing'; put ',showmeta=&showmeta'; put ',maxobs=&maxobs'; put ') %mend;'; put '/* provide additional debug info */'; put '%global _program;'; put '%put &=syscc;'; put '%put user=%mf_getuser();'; put '%put pgm=&_program;'; put '%put timestamp=%sysfunc(datetime(),datetime19.);'; put '* Service Variables start;'; put '* Service Variables end;'; put '* SAS Macros start;'; put '%macro mf_getapploc(pgm);'; put '%if "&pgm"="" %then %do;'; put '%if %symexist(_program) %then %let pgm=&_program;'; put '%else %do;'; put '%put &sysmacroname: No value provided and no _program variable available;'; put '%return;'; put '%end;'; put '%end;'; put '%local root;'; put '/**'; put '* First check we are not in the tests/macros folder (which has no subfolders)'; put '* or specifically in the testsetup or testteardown services'; put '*/'; put '%if %index(&pgm,/tests/macros/)'; put 'or %index(&pgm,/tests/testsetup)'; put 'or %index(&pgm,/tests/testteardown)'; put '%then %do;'; put '%let root=%substr(&pgm,1,%index(&pgm,/tests)-1);'; put '&root'; put '%return;'; put '%end;'; put '/**'; put '* Next, move up two levels to avoid matches on subfolder or service name'; put '*/'; put '%let root=%substr(&pgm,1,%length(&pgm)-%length(%scan(&pgm,-1,/))-1);'; put '%let root=%substr(&root,1,%length(&root)-%length(%scan(&root,-1,/))-1);'; put '%if %index(&root,/tests/) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/tests/)-1);'; put '%end;'; put '%else %if %index(&root,/services) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/services)-1);'; put '%end;'; put '%else %if %index(&root,/jobs) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/jobs)-1);'; put '%end;'; put '%else %put &sysmacroname: Could not find an app location from &pgm;'; put '&root'; put '%mend mf_getapploc ;'; put '%macro mf_getuniquefileref(prefix=_,maxtries=1000,lrecl=32767);'; put '%local rc fname;'; put '%if &prefix=0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%end;'; put '%else %do;'; put '%local x len;'; put '%let len=%eval(8-%length(&prefix));'; put '%let x=0;'; put '%do x=0 %to &maxtries;'; put '%let fname=&prefix%substr(%sysfunc(ranuni(0)),3,&len);'; put '%if %sysfunc(fileref(&fname)) > 0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%return;'; put '%end;'; put '%end;'; put '%put unable to find available fileref after &maxtries attempts;'; put '%end;'; put '%mend mf_getuniquefileref;'; put '%macro mp_abort(mac=mp_abort.sas, type=, msg=, iftrue=%str(1=1)'; put ', errds=work.mp_abort_errds'; put ', mode=REGULAR'; put ')/*/STORE SOURCE*/;'; put '%global sysprocessmode sysprocessname sasjs_stpsrv_header_loc sasjsprocessmode;'; put '%local fref fid i;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%put NOTE: /// mp_abort macro executing //;'; put '%if %length(&mac)>0 %then %put NOTE- called by &mac;'; put '%put NOTE - &msg;'; put '%if %symexist(_SYSINCLUDEFILEDEVICE)'; put '/* abort cancel FILE does not restart outside the INCLUDE on Viya 3.5 */'; put 'and %superq(SYSPROCESSNAME) ne %str(Compute Server)'; put '%then %do;'; put '%if "*&_SYSINCLUDEFILEDEVICE*" ne "**" %then %do;'; put 'data &errds;'; put 'iftrue=''1=1'';'; put 'length mac $100 msg $5000;'; put 'mac=symget(''mac'');'; put 'msg=symget(''msg'');'; put 'run;'; put 'data _null_;'; put 'abort cancel FILE;'; put 'run;'; put '%return;'; put '%end;'; put '%end;'; put '/* Web App Context */'; put '%if %symexist(_PROGRAM)'; put 'or %superq(SYSPROCESSNAME) = %str(Compute Server)'; put 'or &mode=INCLUDE'; put '%then %do;'; put 'options obs=max replace mprint;'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5"'; put '%then %do;'; put 'options nosyntaxcheck;'; put '%end;'; put '%if &mode=INCLUDE %then %do;'; put '%if %sysfunc(exist(&errds))=1 %then %do;'; put 'data _null_;'; put 'set &errds;'; put 'call symputx(''iftrue'',iftrue,''l'');'; put 'call symputx(''mac'',mac,''l'');'; put 'call symputx(''msg'',msg,''l'');'; put 'putlog (_all_)(=);'; put 'run;'; put '%if (&iftrue)=0 %then %return;'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: No include errors found;'; put '%return;'; put '%end;'; put '%end;'; put '/* extract log errs / warns, if exist */'; put '%local logloc logline;'; put '%global logmsg; /* capture global messages */'; put '%if %symexist(SYSPRINTTOLOG) %then %let logloc=&SYSPRINTTOLOG;'; put '%else %let logloc=%qsysfunc(getoption(LOG));'; put 'proc printto log=log;run;'; put '%let logline=0;'; put '%if %length(&logloc)>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input; putlog _infile_;'; put 'i=1;'; put 'retain logonce 0;'; put 'if ('; put '_infile_=:"%str(WARN)ING" or _infile_=:"%str(ERR)OR"'; put ') and logonce=0 then'; put 'do;'; put 'call symputx(''logline'',_n_);'; put 'logonce+1;'; put 'end;'; put 'run;'; put '/* capture log including lines BEFORE the err */'; put '%if &logline>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input;'; put 'i=1;'; put 'stoploop=0;'; put 'if _n_ ge &logline-15 and stoploop=0 then do until (i>22);'; put 'call symputx(''logmsg'',catx(''\n'',symget(''logmsg''),_infile_));'; put 'input;'; put 'i+1;'; put 'stoploop=1;'; put 'end;'; put 'if stoploop=1 then stop;'; put 'run;'; put '%end;'; put '%end;'; put '%if %symexist(SYS_JES_JOB_URI) %then %do;'; put '/* setup webout for Viya */'; put 'options nobomfile;'; put '%if "X&SYS_JES_JOB_URI.X"="XX" %then %do;'; put 'filename _webout temp lrecl=999999 mod;'; put '%end;'; put '%else %do;'; put 'filename _webout filesrvc parenturi="&SYS_JES_JOB_URI"'; put 'name="_webout.json" lrecl=999999 mod;'; put '%end;'; put '%end;'; put '%else %if %sysfunc(filename(fref,&sasjs_stpsrv_header_loc))=0 %then %do;'; put 'options nobomfile;'; put '/* set up http header for SASjs Server */'; put '%let fid=%sysfunc(fopen(&fref,A));'; put '%if &fid=0 %then %do;'; put '%put %str(ERR)OR: %sysfunc(sysmsg());'; put '%return;'; put '%end;'; put '%let rc=%sysfunc(fput(&fid,%str(Content-Type: application/json)));'; put '%let rc=%sysfunc(fwrite(&fid));'; put '%let rc=%sysfunc(fclose(&fid));'; put '%let rc=%sysfunc(filename(&fref));'; put '%end;'; put '/* send response in SASjs JSON format */'; put 'data _null_;'; put 'file _webout mod lrecl=32000 encoding=''utf-8'';'; put 'length msg syswarningtext syserrortext $32767 mode $10 ;'; put 'sasdatetime=datetime();'; put 'msg=symget(''msg'');'; put '%if &logline>0 %then %do;'; put 'msg=cats(msg,''\n\nLog Extract:\n'',symget(''logmsg''));'; put '%end;'; put '/* escape the escapes */'; put 'msg=tranwrd(msg,''\'',''\\'');'; put '/* escape the quotes */'; put 'msg=tranwrd(msg,''"'',''\"'');'; put '/* ditch the CRLFs as chrome complains */'; put 'msg=compress(msg,,''kw'');'; put '/* quote without quoting the quotes (which are escaped instead) */'; put 'msg=cats(''"'',msg,''"'');'; put 'if symexist(''_debug'') then debug=quote(trim(symget(''_debug'')));'; put 'else debug=''""'';'; put 'if symget(''sasjsprocessmode'')=''Stored Program'' then mode=''SASJS'';'; put 'if mode ne ''SASJS'' then put ''>>weboutBEGIN<<'';'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'put '',"sasjsAbort" : [{'';'; put 'put '' "MSG":'' msg ;'; put 'put '' ,"MAC": "'' "&mac" ''"}]'';'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'put '',"_DEBUG":'' debug ;'; put 'if symexist(''_metauser'') then do;'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put 'end;'; put 'if symexist(''SYS_JES_JOB_URI'') then do;'; put 'SYS_JES_JOB_URI=quote(trim(symget(''SYS_JES_JOB_URI'')));'; put 'put '',"SYS_JES_JOB_URI": '' SYS_JES_JOB_URI;'; put 'end;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put ",""SYSWARNINGTEXT"" : " syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'put "}" ;'; put 'if mode ne ''SASJS'' then put ''>>weboutEND<<'';'; put 'run;'; put '%put _all_;'; put '%if "&sysprocessmode " = "SAS Stored Process Server " %then %do;'; put 'data _null_;'; put 'putlog ''stpsrvset program err and syscc'';'; put 'rc=stpsrvset(''program error'', 0);'; put 'call symputx("syscc",0,"g");'; put 'run;'; put '%if &sysscp=WIN'; put 'and 1=0 /* deprecating this logic until we figure out a consistent abort */'; put 'and "%substr(%str(&sysvlong ),1,8)"="9.04.01M"'; put 'and "%substr(%str(&sysvlong ),9,1)">"5" %then %do;'; put '/* skip approach (below) does not work in windows m6+ envs */'; put 'endsas;'; put '%end;'; put '%else %do;'; put '/**'; put '* endsas kills 9.4m3 deployments by orphaning multibridges.'; put '* Abort variants are ungraceful (non zero return code)'; put '* This approach lets SAS run silently until the end :-)'; put '* Caution - fails when called within a %include within a macro'; put '* Use mp_include() to handle this.'; put '*/'; put 'filename skip temp;'; put 'data _null_;'; put 'file skip;'; put 'put ''%macro skip();'';'; put 'comment ''%mend skip; -> fix lint '';'; put 'put ''%macro skippy();'';'; put 'comment ''%mend skippy; -> fix lint '';'; put 'run;'; put '%inc skip;'; put '%end;'; put '%end;'; put '%else %if "&sysprocessmode " = "SAS Compute Server " %then %do;'; put '/* endsas kills the session making it harder to fetch results */'; put 'data _null_;'; put 'syswarningtext=symget(''syswarningtext'');'; put 'syserrortext=symget(''syserrortext'');'; put 'abort_msg=symget(''msg'');'; put 'syscc=symget(''syscc'');'; put 'sysuserid=symget(''sysuserid'');'; put 'iftrue=symget(''iftrue'');'; put 'put (_all_)(/=);'; put 'call symputx(''syscc'',0);'; put 'abort cancel nolist;'; put 'run;'; put '%end;'; put '%else %do;'; put '%abort cancel;'; put '%end;'; put '%end;'; put '%else %do;'; put '%put _all_;'; put '%abort cancel;'; put '%end;'; put '%mend mp_abort;'; put '/** @endcond */'; put '%macro mm_getstpcode('; put 'tree=/User Folders/sasdemo/somestp'; put ',name='; put ',outloc=0'; put ',outref=0'; put ',mDebug=1'; put ',showlog=NO'; put ');'; put '%local mD;'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing &sysmacroname..sas;'; put '%&mD.put _local_;'; put '%if %length(&name)>0 %then %let name=/&name;'; put '/* first, check if STP exists */'; put '%local tsuri;'; put '%let tsuri=stopifempty ;'; put 'data _null_;'; put 'format type uri tsuri value $200.;'; put 'call missing (of _all_);'; put 'path="&tree&name(StoredProcess)";'; put '/* first, find the STP ID */'; put 'if metadata_pathobj("",path,"StoredProcess",type,uri)>0 then do;'; put '/* get sourcecode */'; put 'cnt=1;'; put 'do while (metadata_getnasn(uri,"Notes",cnt,tsuri)>0);'; put 'rc=metadata_getattr(tsuri,"Name",value);'; put '&mD.put tsuri= value=;'; put 'if value="SourceCode" then do;'; put '/* found it! */'; put 'rc=metadata_getattr(tsuri,"Id",value);'; put 'call symputx(''tsuri'',value,''l'');'; put 'stop;'; put 'end;'; put 'cnt+1;'; put 'end;'; put 'end;'; put 'else put (_all_)(=);'; put 'run;'; put '%mp_abort(iftrue= (&tsuri=stopifempty)'; put ',mac=mm_getstpcode'; put ',msg=%str(&tree&name.(StoredProcess) not found!)'; put ')'; put '/**'; put '* Now we can extract the textstore'; put '*/'; put 'filename __getdoc temp lrecl=10000000;'; put 'proc metadata'; put 'in="$METAREPOSITORY'; put ''; put 'SAS1"'; put 'out=__getdoc ;'; put 'run;'; put '/* find the beginning of the text */'; put '%local start;'; put 'data _null_;'; put 'infile __getdoc lrecl=10000;'; put 'input;'; put 'start=index(_infile_,''StoredText="'');'; put 'if start then do;'; put 'call symputx("start",start+11);'; put '*putlog ''"'' _infile_ ''"'';'; put 'end;'; put 'stop;'; put '%local outeng;'; put '%if "&outloc"="0" %then %let outeng=TEMP;'; put '%else %let outeng="&outloc";'; put '%local fref;'; put '%if &outref=0 %then %let fref=%mf_getuniquefileref();'; put '%else %let fref=&outref;'; put '/* read the content, byte by byte, resolving escaped chars */'; put 'filename &fref &outeng lrecl=100000;'; put 'data _null_;'; put 'length filein 8 fileid 8;'; put 'filein = fopen("__getdoc","I",1,"B");'; put 'fileid = fopen("&fref","O",1,"B");'; put 'rec = "20"x;'; put 'length entity $6;'; put 'do while(fread(filein)=0);'; put 'x+1;'; put 'if x>&start then do;'; put 'rc = fget(filein,rec,1);'; put 'if rec=''"'' then leave;'; put 'else if rec="&" then do;'; put 'entity=rec;'; put 'do until (rec=";");'; put 'if fread(filein) ne 0 then goto getout;'; put 'rc = fget(filein,rec,1);'; put 'entity=cats(entity,rec);'; put 'end;'; put 'select (entity);'; put 'when (''&'' ) rec=''&'' ;'; put 'when (''<'' ) rec=''<'' ;'; put 'when (''>'' ) rec=''>'' ;'; put 'when (''''') rec="''" ;'; put 'when (''"'') rec=''"'' ;'; put 'when ('' '') rec=''0A''x;'; put 'when ('' '') rec=''0D''x;'; put 'when (''$'' ) rec=''$'' ;'; put 'when ('' '') rec=''09''x;'; put 'otherwise putlog "%str(WARN)ING: missing value for " entity=;'; put 'end;'; put 'rc =fput(fileid, substr(rec,1,1));'; put 'rc =fwrite(fileid);'; put 'end;'; put 'else do;'; put 'rc =fput(fileid,rec);'; put 'rc =fwrite(fileid);'; put 'end;'; put 'end;'; put 'end;'; put 'getout:'; put 'rc=fclose(filein);'; put 'rc=fclose(fileid);'; put 'run;'; put '%if &showlog=YES %then %do;'; put 'data _null_;'; put 'infile &fref lrecl=32767 end=last;'; put 'input;'; put 'if _n_=1 then putlog ''>>stpcodeBEGIN<<'';'; put 'putlog _infile_;'; put 'if last then putlog ''>>stpcodeEND<<'';'; put 'run;'; put '%end;'; put 'filename __getdoc clear;'; put '%if &outref=0 %then %do;'; put 'filename &fref clear;'; put '%end;'; put '%mend mm_getstpcode;'; put '%macro dc_getsettings();'; put '%global _program;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(syscc=&syscc on entry (&syswarningtext &syserrortext))'; put ')'; put '%if %length(&_PROGRAM)>1 %then %let root=&_program;'; put '%else %do;'; put '%global _metauser;'; put '%let _metauser=&sysuserid;'; put '/* to mimic a "real" _program we need to give a dummy role and stp name */'; put '%let root=/dummyRole/dummyName;'; put '%end;'; put '/* the DC precode is stored in the Admin folder in the root of'; put 'the project. Lets find that root. */'; put '%put &=root;'; put '%let root=%mf_getapploc();'; put '%put &=root;'; put '/* Now we know the root location we can retrieve the params */'; put '%let temploc=%sysfunc(pathname(work))/settings.txt;'; put '%mm_getstpcode(tree=&root/services/public'; put ',name=Data_Controller_Settings'; put ',outloc=&temploc'; put ')'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Unable to run getstpcode)'; put ')'; put 'filename _getsets "&temploc" lrecl=2000;'; put '/*'; put 'Do not use mp_include here - this puts a copy in every service, which creates'; put 'compilation problems when calling services from mp_include'; put '*/'; put '%inc _getsets/source2;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Problem running &sysmacroname (&syswarningtext &syserrortext))'; put ')'; put '%mend dc_getsettings;'; put '%macro mf_fmtdttm('; put ')/*/STORE SOURCE*/;'; put '%if "&sysver"="9.2" or "&sysver"="9.3"'; put 'or ("&sysver"="9.4" and "%substr(&SYSVLONG,9,1)" le "3")'; put 'or "%substr(&sysver,1,1)"="4"'; put 'or "%substr(&sysver,1,1)"="5"'; put '%then %do;DATETIME19.3%end;'; put '%else %do;E8601DT26.6%end;'; put '%mend mf_fmtdttm;'; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '%macro mp_init(prefix=SASJS'; put ')/*/STORE SOURCE*/;'; put '%if %symexist(SASJS_PREFIX) %then %return; /* only run once */'; put '%global'; put 'SASJS_PREFIX /* the ONLY hard-coded global macro variable in SASjs */'; put '&prefix._FUNCTIONS /* used in mcf_init() to track core function compilation */'; put '&prefix._INIT_NUM /* initialisation time as numeric */'; put '&prefix._INIT_DTTM /* initialisation time in E8601DT26.6 format */'; put '&prefix.WORK /* avoid typing %sysfunc(pathname(work)) every time */'; put ';'; put '%let sasjs_prefix=&prefix;'; put 'data _null_;'; put 'dttm=datetime();'; put 'call symputx("&sasjs_prefix._init_num",dttm,''g'');'; put 'call symputx("&sasjs_prefix._init_dttm",put(dttm,E8601DT26.6),''g'');'; put 'call symputx("&sasjs_prefix.work",pathname(''WORK''),''g'');'; put 'run;'; put 'options'; put 'compress=CHAR /* default is none so ensure we have something! */'; put 'datastmtchk=ALLKEYWORDS /* protection from overwriting input datasets */'; put 'errorcheck=STRICT /* catch errs in libname/filename statements */'; put 'fmterr /* ensure err when a format cannot be found */'; put 'mergenoby=%str(ERR)OR /* throw err when a merge has no BY variables */'; put 'missing=. /* changing this can cause hard to detect errs */'; put 'noquotelenmax /* avoid warnings for long strings */'; put 'noreplace /* avoid overwriting permanent datasets */'; put 'ps=max /* reduce log size slightly */'; put 'ls=max /* reduce log even more and avoid word truncation */'; put 'validmemname=COMPATIBLE /* avoid special characters etc in table names */'; put 'validvarname=V7 /* avoid special characters etc in variable names */'; put 'varinitchk=%str(ERR)OR /* avoid data mistakes from variable name typos */'; put 'varlenchk=%str(ERR)OR /* fail hard if truncation (data loss) can result */'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5" %then %do;'; put 'noautocorrect /* disallow misspelled procedure names */'; put 'dsoptions=note2err /* undocumented - convert bad NOTEs to ERRs */'; put '%end;'; put ';'; put '%mend mp_init;'; put '%macro mpeinit(fetch=YES);'; put '%global mpeinit'; put 'mpeadmins /* group with unrestricted Meditor access */'; put 'mpelocapprovals /* location for landing and staging files */'; put 'mpelib /* location of configuration tables for DC */'; put 'dc_repo_users /* location of user / group metadata */'; put 'dc_licence_key /* extracted in dc_getsettings */'; put 'dc_activation_key /* extracted in dc_getsettings */'; put 'dc_locale /* extracted in dc_getsettings */'; put 'dc_dttmtfmt /* can be overridden in dc_getsettings */'; put '_debug'; put ';'; put '%if &mpeinit=1 %then %return;'; put '%else %let mpeinit=1;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(Problem on service startup (&syswarningtext &syserrortext))'; put ')'; put '%mp_init()'; put '%if &fetch=YES %then %do;'; put '%webout(FETCH)'; put '%end;'; put '%global _CLIENTNAME;'; put '%mp_abort(iftrue= (&_CLIENTNAME=SAS Enterprise Guide)'; put ',mac=&_program..sas'; put ',msg=%str(Data Controller is a web app and should not be executed from EG)'; put ')'; put 'options urlencoding=utf8 nobomfile lrecl=32767;'; put '%let perf=%sysfunc(datetime());'; put '%put perfdiff: 0;'; put '%let dc_locale=SYSTEM; /* default if not set */'; put '/**'; put '* E8601DT26.6 has widest database support - but not all SAS flavours can'; put '* handle it. Override in the settings STP if needed.'; put '*/'; put 'data _null_;'; put 'dc_dttmtfmt=''"%sysfunc(datetime(),''!!"%mf_fmtdttm()"!!'')"dt'';'; put 'call symputx(''dc_dttmtfmt'',dc_dttmtfmt);'; put 'put dc_dttmtfmt=;'; put 'run;'; put '%put &=dc_dttmtfmt;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc prior to dc_getsettings)'; put ')'; put '%dc_getsettings()'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc after dc_getsettings)'; put ')'; put 'data _null_;'; put 'set &DC_LIBREF..mpe_config(where=('; put 'var_scope="DC"'; put 'and &dc_dttmtfmt lt tx_to'; put 'and var_active=1'; put '));'; put 'call symputx(var_name,var_value,''G'');'; put 'putlog var_name "=" var_value;'; put 'run;'; put '%let mpelib=&dc_libref;'; put '%let mpeadmins=&dc_admin_group;'; put '%let mpelocapprovals=&dc_staging_area;'; put '%let dc_repo_users=&dc_repo_users;'; put '%if &dc_locale ne SYSTEM %then %do;'; put 'options locale=&dc_locale;'; put '%end;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(Problem during compilation or with STP precode (&syswarningtext))'; put ')'; put '%mend mpeinit;'; put '%macro mf_mval(var);'; put '%if %symexist(&var) %then %do;'; put '%superq(&var)'; put '%end;'; put '%mend mf_mval;'; put '%macro mf_trimstr(basestr,trimstr);'; put '%local baselen trimlen trimval;'; put '/* return if basestr is shorter than trimstr (or 0) */'; put '%let baselen=%length(%superq(basestr));'; put '%let trimlen=%length(%superq(trimstr));'; put '%if &baselen < &trimlen or &baselen=0 %then %return;'; put '/* obtain the characters from the end of basestr */'; put '%let trimval=%qsubstr(%superq(basestr)'; put ',%length(%superq(basestr))-&trimlen+1'; put ',&trimlen);'; put '/* compare and if matching, chop it off! */'; put '%if %superq(basestr)=%superq(trimstr) %then %do;'; put '%return;'; put '%end;'; put '%else %if %superq(trimval)=%superq(trimstr) %then %do;'; put '%qsubstr(%superq(basestr),1,%length(%superq(basestr))-&trimlen)'; put '%end;'; put '%else %do;'; put '&basestr'; put '%end;'; put '%mend mf_trimstr;'; put '%macro mf_getplatform(switch'; put ')/*/STORE SOURCE*/;'; put '%local a b c;'; put '%if &switch.NONE=NONE %then %do;'; put '%if %symexist(sasjsprocessmode) %then %do;'; put '%if &sasjsprocessmode=Stored Program %then %do;'; put 'SASJS'; put '%return;'; put '%end;'; put '%end;'; put '%if %symexist(sysprocessmode) %then %do;'; put '%if "&sysprocessmode"="SAS Object Server"'; put 'or "&sysprocessmode"= "SAS Compute Server" %then %do;'; put 'SASVIYA'; put '%end;'; put '%else %if "&sysprocessmode"="SAS Stored Process Server"'; put 'or "&sysprocessmode"="SAS Workspace Server"'; put '%then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if %symexist(_metaport) or %symexist(_metauser) %then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if &switch=SASSTUDIO %then %do;'; put '/* return the version of SAS Studio else 0 */'; put '%if %mf_mval(_CLIENTAPP)=%str(SAS Studio) %then %do;'; put '%let a=%mf_mval(_CLIENTVERSION);'; put '%let b=%scan(&a,1,.);'; put '%if %eval(&b >2) %then %do;'; put '&b'; put '%end;'; put '%else 0;'; put '%end;'; put '%else 0;'; put '%end;'; put '%else %if &switch=VIYARESTAPI %then %do;'; put '%mf_trimstr(%sysfunc(getoption(servicesbaseurl)),/)'; put '%end;'; put '%mend mf_getplatform;'; put '%macro mpeterm();'; put '%local oldloc;'; put 'data _null_;'; put 'if symexist(''SYSPRINTTOLOG'') then oldloc=symget(''SYSPRINTTOLOG'');'; put 'else oldloc=getoption(''LOG'');'; put 'if subpad(oldloc,1,1) not in (''"'',"''",'' '') then oldloc=quote(cats(oldloc));'; put 'call symputx(''oldloc'',oldloc,''l'');'; put 'run;'; put '%if %length(&oldloc)>0 %then %do;'; put 'proc printto log=log;'; put 'run;'; put 'data _null_;'; put 'infile &oldloc;'; put 'input; putlog _infile_;'; put 'run;'; put '%end;'; put '%if %sysfunc(exist(&dc_libref..mpe_requests)) and %mf_getplatform() ne SASVIYA'; put '%then %do;'; put 'data ;'; put 'if 0 then set &dc_libref..mpe_requests;'; put 'request_dttm=%sysfunc(datetime());'; put 'request_user="%mf_getuser()";'; put 'request_service="%scan(&_program,-2,/)/%scan(&_program,-1,/)";'; put 'request_params='''';'; put 'output;stop;'; put 'proc append base=&dc_libref..mpe_requests data=&syslast force nowarn;'; put 'run;'; put '%end;'; put '%mend mpeterm;'; put '* SAS Macros end;'; put '* SAS Includes start;'; put '* SAS Includes end;'; put '* Binary Files start;'; put '* Binary Files end;'; put '* ServiceInit start;'; put 'options noquotelenmax ps=max;'; put '/* create dummy macros to prevent stpbegin / stpend from corrupting sessions */'; put '%macro stpbegin();'; put '%put NOTE: the STPBEGIN macro should not be used for web apps!;'; put '%mend stpbegin;'; put '%macro stpend();'; put '%put NOTE: the STPEND macro should not be used for web apps!;'; put '%mend stpend;'; put '* ServiceInit end;'; put '* Service start;'; put '/**'; put '@file getsubmits.sas'; put '@brief Returns a list of staged data items that need to be approved'; put '@details'; put '

SAS Macros

'; put '@li mp_abort.sas'; put '@li mf_getuser.sas'; put '@li mpeinit.sas'; put '@version 9.2'; put '@author 4GL Apps Ltd'; put '@copyright 4GL Apps Ltd. This code may only be used within Data Controller'; put 'and may not be re-distributed or re-sold without the express permission of'; put '4GL Apps Ltd.'; put '**/'; put '%mpeinit()'; put 'PROC FORMAT;'; put 'picture yymmddhhmmss other=''%0Y-%0m-%0d %0H:%0M:%0S'' (datatype=datetime);'; put 'RUN;'; put 'proc sql noprint;'; put 'create table work.fromsas (rename=(SUBMITTED_ON=SUBMITTED_ON_DTTM)) as'; put 'select table_id'; put ',cats(base_lib,''.'',base_ds) as base_table'; put ',input_vars'; put ',input_obs'; put ',submitted_by_nm'; put ',submitted_reason_txt'; put ',''DEPRECATED'' as approve_group'; put ',submit_status_cd as review_status_id'; put ',reviewed_by_nm'; put ',reviewed_on_dttm'; put ',cats(put(SUBMITTED_ON_DTTM,yymmddhhmmss.)) as SUBMITTED_ON'; put 'from &mpelib..mpe_submit'; put 'where submitted_by_nm="%mf_getuser()" and submit_status_cd=''SUBMITTED'''; put 'order by submitted_on_dttm desc;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(syscc=&syscc)'; put ')'; put '%webout(OPEN)'; put '%webout(OBJ,fromSAS)'; put '%webout(CLOSE)'; put '* Service end;'; run; %mm_createwebservice(path=&appLoc/&path, name=&service, code=sascode, server=&serverName, replace=yes) filename sascode clear; %let service=getxlmaps; filename sascode temp lrecl=32767; data _null_; file sascode; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '/**'; put '@file mp_jsonout.sas'; put '@brief Writes JSON in SASjs format to a fileref'; put '@details This macro can be used to OPEN a JSON stream and send one or more'; put 'tables as arrays of rows, where each row can be an object or a nested array.'; put 'There are two engines available - DATASTEP or PROCJSON.'; put 'PROC JSON is fast but will produce errs like the ones below if'; put 'special chars are encountered.'; put '> (ERR)OR: Some code points did not transcode.'; put '> An object or array close is not valid at this point in the JSON text.'; put '> Date value out of range'; put 'If this happens, try running with ENGINE=DATASTEP.'; put 'The DATASTEP engine is used to handle special SAS missing numerics, and'; put 'can also convert entire datasets to formatted values. Output JSON is always'; put 'in UTF-8.'; put 'Usage:'; put 'filename tmp temp;'; put 'data class; set sashelp.class;run;'; put '%mp_jsonout(OPEN,jref=tmp)'; put '%mp_jsonout(OBJ,class,jref=tmp)'; put '%mp_jsonout(OBJ,class,dslabel=class2,jref=tmp,showmeta=Y)'; put '%mp_jsonout(CLOSE,jref=tmp)'; put 'data _null_;'; put 'infile tmp;'; put 'input;putlog _infile_;'; put 'run;'; put 'If you are building web apps with SAS then you are strongly encouraged to use'; put 'the mX_createwebservice macros in combination with the'; put '[sasjs adapter](https://github.com/sasjs/adapter).'; put 'For more information see https://sasjs.io'; put '@param [in] action Valid values:'; put '@li OPEN - opens the JSON'; put '@li OBJ - sends a table with each row as an object'; put '@li ARR - sends a table with each row in an array'; put '@li CLOSE - closes the JSON'; put '@param [in] ds The dataset to send. Must be a work table.'; put '@param [out] jref= (_webout) The fileref to which to send the JSON'; put '@param [out] dslabel= The name to give the table in the exported JSON'; put '@param [in] fmt= (Y) Whether to keep (Y) or strip (N) formats from the table'; put '@param [in] engine= (DATASTEP) Which engine to use to send the JSON. Options:'; put '@li PROCJSON (default)'; put '@li DATASTEP (more reliable when data has non standard characters)'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to JSON'; put '

Related Files

'; put '@li mp_ds2fmtds.sas'; put '@version 9.2'; put '@author Allan Bowe'; put '@source https://github.com/sasjs/core'; put '**/'; put '%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y'; put ',engine=DATASTEP'; put ',missing=NULL'; put ',showmeta=N'; put ',maxobs=MAX'; put ')/*/STORE SOURCE*/;'; put '%local tempds colinfo fmtds i numcols numobs stmt_obs lastobs optval'; put 'tmpds1 tmpds2 tmpds3 tmpds4;'; put '%let numcols=0;'; put '%if &maxobs ne MAX %then %let stmt_obs=%str(if _n_>&maxobs then stop;);'; put '%if &action=OPEN %then %do;'; put 'options nobomfile;'; put 'data _null_;file &jref encoding=''utf-8'' lrecl=200;'; put 'put ''{"PROCESSED_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''"'';'; put 'run;'; put '%end;'; put '%else %if (&action=ARR or &action=OBJ) %then %do;'; put '/* force variable names to always be uppercase in the JSON */'; put 'options validvarname=upcase;'; put '/* To avoid issues with _webout on EBI - such as encoding diffs and truncation'; put '(https://support.sas.com/kb/49/325.html) we use temporary files */'; put 'filename _sjs1 temp lrecl=200 ;'; put 'data _null_; file _sjs1 encoding=''utf-8'';'; put 'put ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":";'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjs1 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs1 clear;'; put '/* grab col defs */'; put 'proc contents noprint data=&ds'; put 'out=_data_(keep=name type length format formatl formatd varnum label);'; put 'run;'; put '%let colinfo=%scan(&syslast,2,.);'; put 'proc sort data=&colinfo;'; put 'by varnum;'; put 'run;'; put '/* move meta to mac vars */'; put 'data &colinfo;'; put 'if _n_=1 then call symputx(''numcols'',nobs,''l'');'; put 'set &colinfo end=last nobs=nobs;'; put 'name=upcase(name);'; put '/* fix formats */'; put 'if type=2 or type=6 then do;'; put 'typelong=''char'';'; put 'length fmt $49.;'; put 'if format='''' then fmt=cats(''$'',length,''.'');'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else fmt=cats(format,formatl,''.'');'; put 'end;'; put 'else do;'; put 'typelong=''num'';'; put 'if format='''' then fmt=''best.'';'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else if formatd=0 then fmt=cats(format,formatl,''.'');'; put 'else fmt=cats(format,formatl,''.'',formatd);'; put 'end;'; put '/* 32 char unique name */'; put 'newname=''sasjs''!!substr(cats(put(md5(name),$hex32.)),1,27);'; put 'call symputx(cats(''name'',_n_),name,''l'');'; put 'call symputx(cats(''newname'',_n_),newname,''l'');'; put 'call symputx(cats(''length'',_n_),length,''l'');'; put 'call symputx(cats(''fmt'',_n_),fmt,''l'');'; put 'call symputx(cats(''type'',_n_),type,''l'');'; put 'call symputx(cats(''typelong'',_n_),typelong,''l'');'; put 'call symputx(cats(''label'',_n_),coalescec(label,name),''l'');'; put '/* overwritten when fmt=Y and a custom format exists in catalog */'; put 'if typelong=''num'' then call symputx(cats(''fmtlen'',_n_),200,''l'');'; put 'else call symputx(cats(''fmtlen'',_n_),min(32767,ceil((length+10)*1.5)),''l'');'; put 'run;'; put '%let tempds=%substr(_%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql;'; put 'select count(*) into: lastobs from &ds;'; put '%if &maxobs ne MAX %then %let lastobs=%sysfunc(min(&lastobs,&maxobs));'; put '%if &engine=PROCJSON %then %do;'; put '%if &missing=STRING %then %do;'; put '%put &sysmacroname: Special Missings not supported in proc json.;'; put '%put &sysmacroname: Switching to DATASTEP engine;'; put '%goto datastep;'; put '%end;'; put 'data &tempds;'; put 'set &ds;'; put '&stmt_obs;'; put '%if &fmt=N %then format _numeric_ best32.;;'; put '/* PRETTY is necessary to avoid line truncation in large files */'; put 'filename _sjs2 temp lrecl=131068 encoding=''utf-8'';'; put 'proc json out=_sjs2 pretty'; put '%if &action=ARR %then nokeys ;'; put ';export &tempds / nosastags fmtnumeric;'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs2 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs2 clear;'; put '%end;'; put '%else %if &engine=DATASTEP %then %do;'; put '%datastep:'; put '%if %sysfunc(exist(&ds)) ne 1 & %sysfunc(exist(&ds,VIEW)) ne 1'; put '%then %do;'; put '%put &sysmacroname: &ds NOT FOUND!!!;'; put '%return;'; put '%end;'; put '%if &fmt=Y %then %do;'; put '/**'; put '* Extract format definitions'; put '* First, by getting library locations from dictionary.formats'; put '* Then, by exporting the width using proc format'; put '* Cannot use maxw from sashelp.vformat as not always populated'; put '* Cannot use fmtinfo() as not supported in all flavours'; put '*/'; put '%let tmpds1=%substr(fmtsum%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds2=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds3=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds4=%substr(col%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql noprint;'; put 'create table &tmpds1 as'; put 'select cats(libname,''.'',memname) as FMTCAT,'; put 'FMTNAME'; put 'from dictionary.formats'; put 'where fmttype=''F'' and libname is not null'; put 'and fmtname in (select format from &colinfo where format is not null)'; put 'order by 1;'; put 'create table &tmpds2('; put 'FMTNAME char(32),'; put 'LENGTH num'; put ');'; put '%local catlist cat fmtlist i;'; put 'select distinct fmtcat into: catlist separated by '' '' from &tmpds1;'; put '%do i=1 %to %sysfunc(countw(&catlist,%str( )));'; put '%let cat=%scan(&catlist,&i,%str( ));'; put 'proc sql;'; put 'select distinct fmtname into: fmtlist separated by '' '''; put 'from &tmpds1 where fmtcat="&cat";'; put 'proc format lib=&cat cntlout=&tmpds3(keep=fmtname length);'; put 'select &fmtlist;'; put 'run;'; put 'proc sql;'; put 'insert into &tmpds2 select distinct fmtname,length from &tmpds3;'; put '%end;'; put 'proc sql;'; put 'create table &tmpds4 as'; put 'select a.*, b.length as MAXW'; put 'from &colinfo a'; put 'left join &tmpds2 b'; put 'on cats(a.format)=cats(upcase(b.fmtname))'; put 'order by a.varnum;'; put 'data _null_;'; put 'set &tmpds4;'; put 'if not missing(maxw);'; put 'call symputx('; put 'cats(''fmtlen'',_n_),'; put '/* vars need extra padding due to JSON escaping of special chars */'; put 'min(32767,ceil((max(length,maxw)+10)*1.5))'; put ',''l'''; put ');'; put 'run;'; put '/* configure varlenchk - as we are explicitly shortening the variables */'; put '%let optval=%sysfunc(getoption(varlenchk));'; put 'options varlenchk=NOWARN;'; put 'data _data_(compress=char);'; put '/* shorten the new vars */'; put 'length'; put '%do i=1 %to &numcols;'; put '&&name&i $&&fmtlen&i'; put '%end;'; put ';'; put '/* rename on entry */'; put 'set &ds(rename=('; put '%do i=1 %to &numcols;'; put '&&name&i=&&newname&i'; put '%end;'; put '));'; put '&stmt_obs;'; put 'drop'; put '%do i=1 %to &numcols;'; put '&&newname&i'; put '%end;'; put ';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=num %then %do;'; put '&&name&i=cats(put(&&newname&i,&&fmt&i));'; put '%end;'; put '%else %do;'; put '&&name&i=put(&&newname&i,&&fmt&i);'; put '%end;'; put '%end;'; put 'if _error_ then do;'; put 'call symputx(''syscc'',1012);'; put 'stop;'; put 'end;'; put 'run;'; put '%let fmtds=&syslast;'; put 'options varlenchk=&optval;'; put '%end;'; put 'proc format; /* credit yabwon for special null removal */'; put 'value bart (default=40)'; put '%if &missing=NULL %then %do;'; put '._ - .z = null'; put '%end;'; put '%else %do;'; put '._ = [quote()]'; put '. = null'; put '.a - .z = [quote()]'; put '%end;'; put 'other = [best.];'; put 'data &tempds;'; put 'attrib _all_ label='''';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'length &&name&i $&&fmtlen&i...;'; put 'format &&name&i $&&fmtlen&i...;'; put '%end;'; put '%end;'; put '%if &fmt=Y %then %do;'; put 'set &fmtds;'; put '%end;'; put '%else %do;'; put 'set &ds;'; put '%end;'; put '&stmt_obs;'; put 'format _numeric_ bart.;'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'if findc(&&name&i,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put '&&name&i=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,&&name&i)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else &&name&i=quote(cats(&&name&i));'; put '%end;'; put '%end;'; put 'run;'; put 'filename _sjs3 temp lrecl=131068 ;'; put 'data _null_;'; put 'file _sjs3 encoding=''utf-8'';'; put 'if _n_=1 then put "[";'; put 'set &tempds;'; put 'if _n_>1 then put "," @; put'; put '%if &action=ARR %then "[" ; %else "{" ;'; put '%do i=1 %to &numcols;'; put '%if &i>1 %then "," ;'; put '%if &action=OBJ %then """&&name&i"":" ;'; put '"&&name&i"n /* name literal for reserved variable names */'; put '%end;'; put '%if &action=ARR %then "]" ; %else "}" ; ;'; put '/* close out the table */'; put 'data _null_;'; put 'file _sjs3 mod encoding=''utf-8'';'; put 'put '']'';'; put 'run;'; put 'data _null_;'; put 'infile _sjs3 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs3 clear;'; put '%end;'; put 'proc sql;'; put 'drop table &colinfo, &tempds;'; put '%if %substr(&showmeta,1,1)=Y %then %do;'; put 'filename _sjs4 temp lrecl=131068 encoding=''utf-8'';'; put 'data _null_;'; put 'file _sjs4;'; put 'length label $350;'; put 'put ", ""$%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":{""vars"":{";'; put 'do i=1 to &numcols;'; put 'name=quote(trim(symget(cats(''name'',i))));'; put 'format=quote(trim(symget(cats(''fmt'',i))));'; put 'label=quote(prxchange(''s/\\/\\\\/'',-1,trim(symget(cats(''label'',i)))));'; put 'length=quote(trim(symget(cats(''length'',i))));'; put 'type=quote(trim(symget(cats(''typelong'',i))));'; put 'if i>1 then put "," @@;'; put 'put name '':{"format":'' format '',"label":'' label'; put ''',"length":'' length '',"type":'' type ''}'';'; put 'end;'; put 'put ''}}'';'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs4 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs4 clear;'; put '%end;'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put 'data _null_; file &jref encoding=''utf-8'' mod ;'; put 'put "}";'; put 'run;'; put '%end;'; put '%mend mp_jsonout;'; put '/**'; put '@file mm_webout.sas'; put '@brief Send data to/from SAS Stored Processes'; put '@details This macro should be added to the start of each Stored Process,'; put '**immediately** followed by a call to:'; put '%mm_webout(FETCH)'; put 'This will read all the input data and create same-named SAS datasets in the'; put 'WORK library. You can then insert your code, and send data back using the'; put 'following syntax:'; put 'data some datasets; * make some data ;'; put 'retain some columns;'; put 'run;'; put '%mm_webout(OPEN)'; put '%mm_webout(ARR,some) * Array format, fast, suitable for large tables ;'; put '%mm_webout(OBJ,datasets) * Object format, easier to work with ;'; put 'Finally, wrap everything up send some helpful system variables too'; put '%mm_webout(CLOSE)'; put '@param [in] action Either FETCH, OPEN, ARR, OBJ or CLOSE'; put '@param [in] ds The dataset to send back to the frontend'; put '@param [out] dslabel= Value to use instead of table name for sending to JSON'; put '@param [in] fmt= (N) Setting Y converts all vars to their formatted values'; put '@param [out] fref= (_webout) The fileref to which to write the JSON'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] workobs= (0) When set to a positive integer, will create a new'; put 'output object (WORK) which contains this number of observations from all'; put 'tables in the WORK library.'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to output JSON'; put '

SAS Macros

'; put '@li mp_jsonout.sas'; put '

Related Macros

'; put '@li ms_webout.sas'; put '@li mv_webout.sas'; put '@version 9.3'; put '@author Allan Bowe'; put '**/'; put '%macro mm_webout(action,ds,dslabel=,fref=_webout,fmt=N,missing=NULL'; put ',showmeta=N,maxobs=MAX,workobs=0'; put ');'; put '%global _webin_file_count _webin_fileref1 _webin_name1 _program _debug'; put 'sasjs_tables;'; put '%local i tempds jsonengine;'; put '/* see https://github.com/sasjs/core/issues/41 */'; put '%if "%upcase(&SYSENCODING)" ne "UTF-8" %then %let jsonengine=PROCJSON;'; put '%else %let jsonengine=DATASTEP;'; put '%if &action=FETCH %then %do;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'options mprint notes mprintnest;'; put '%end;'; put '%let _webin_file_count=%eval(&_webin_file_count+0);'; put '/* now read in the data */'; put '%do i=1 %to &_webin_file_count;'; put '%if &_webin_file_count=1 %then %do;'; put '%let _webin_fileref1=&_webin_fileref;'; put '%let _webin_name1=&_webin_name;'; put '%end;'; put 'data _null_;'; put 'infile &&_webin_fileref&i termstr=crlf;'; put 'input;'; put 'call symputx(''input_statement'',_infile_);'; put 'putlog "&&_webin_name&i input statement: " _infile_;'; put 'stop;'; put 'data &&_webin_name&i;'; put 'infile &&_webin_fileref&i firstobs=2 dsd termstr=crlf encoding=''utf-8'';'; put 'input &input_statement;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'if _n_<20 then putlog _infile_;'; put '%end;'; put 'run;'; put '%let sasjs_tables=&sasjs_tables &&_webin_name&i;'; put '%end;'; put '%end;'; put '%else %if &action=OPEN %then %do;'; put '/* fix encoding */'; put 'OPTIONS NOBOMFILE;'; put '/**'; put '* check xengine type to avoid the below err message:'; put '* > Function is only valid for filerefs using the CACHE access method.'; put '*/'; put 'data _null_;'; put 'set sashelp.vextfl(where=(fileref="_WEBOUT"));'; put 'if xengine=''STREAM'' then do;'; put 'rc=stpsrv_header(''Content-type'',"text/html; encoding=utf-8");'; put 'end;'; put 'run;'; put '/* setup json */'; put 'data _null_;file &fref encoding=''utf-8'';'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutBEGIN<<'';'; put '%end;'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'run;'; put '%end;'; put '%else %if &action=ARR or &action=OBJ %then %do;'; put '%mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt,jref=&fref'; put ',engine=&jsonengine,missing=&missing,showmeta=&showmeta,maxobs=&maxobs'; put ')'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put '/* To avoid issues with _webout on EBI we use a temporary file */'; put 'filename _sjsref temp lrecl=131068;'; put '%if %str(&workobs) > 0 %then %do;'; put '/* if debug mode, send back first XX records of each work table also */'; put 'data;run;%let tempds=%scan(&syslast,2,.);'; put 'ods output Members=&tempds;'; put 'proc datasets library=WORK memtype=data;'; put '%local wtcnt;%let wtcnt=0;'; put 'data _null_;'; put 'set &tempds;'; put 'if not (upcase(name) =:"DATA"); /* ignore temp datasets */'; put 'i+1;'; put 'call symputx(cats(''wt'',i),name,''l'');'; put 'call symputx(''wtcnt'',i,''l'');'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put ",""WORK"":{";'; put '%do i=1 %to &wtcnt;'; put '%let wt=&&wt&i;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'dsid=open("WORK.&wt",''is'');'; put 'nlobs=attrn(dsid,''NLOBS'');'; put 'nvars=attrn(dsid,''NVARS'');'; put 'rc=close(dsid);'; put 'if &i>1 then put '',''@;'; put 'put " ""&wt"" : {";'; put 'put ''"nlobs":'' nlobs;'; put 'put '',"nvars":'' nvars;'; put '%mp_jsonout(OBJ,&wt,jref=_sjsref,dslabel=first10rows,showmeta=Y'; put ',maxobs=&workobs'; put ')'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put '%end;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put 'run;'; put '%end;'; put '/* close off json */'; put 'data _null_;file _sjsref mod encoding=''utf-8'';'; put 'length SYSPROCESSNAME syserrortext syswarningtext autoexec $512;'; put 'put ",""_DEBUG"" : ""&_debug"" ";'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'autoexec=quote(urlencode(trim(getoption(''autoexec''))));'; put 'put '',"AUTOEXEC" : '' autoexec;'; put 'put ",""MF_GETUSER"" : ""%mf_getuser()"" ";'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'put ",""SYSENCODING"" : ""&sysencoding"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSPROCESSID"" : ""&SYSPROCESSID"" ";'; put 'put ",""SYSPROCESSMODE"" : ""&SYSPROCESSMODE"" ";'; put 'SYSPROCESSNAME=quote(urlencode(cats(SYSPROCESSNAME)));'; put 'put ",""SYSPROCESSNAME"" : " SYSPROCESSNAME;'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put '',"SYSWARNINGTEXT" : '' syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'length memsize $32;'; put 'memsize="%sysfunc(INPUTN(%sysfunc(getoption(memsize)), best.),sizekmg.)";'; put 'memsize=quote(cats(memsize));'; put 'put '',"MEMSIZE" : '' memsize;'; put 'put "}" @;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutEND<<'';'; put '%end;'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjsref lrecl=1 recfm=n;'; put 'file &fref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjsref clear;'; put '%end;'; put '%mend mm_webout;'; put '%macro webout(action,ds,dslabel=,fmt=,missing=NULL,showmeta=NO,maxobs=MAX);'; put '%mm_webout(&action,ds=&ds,dslabel=&dslabel,fmt=&fmt'; put ',missing=&missing'; put ',showmeta=&showmeta'; put ',maxobs=&maxobs'; put ') %mend;'; put '/* provide additional debug info */'; put '%global _program;'; put '%put &=syscc;'; put '%put user=%mf_getuser();'; put '%put pgm=&_program;'; put '%put timestamp=%sysfunc(datetime(),datetime19.);'; put '* Service Variables start;'; put '* Service Variables end;'; put '* SAS Macros start;'; put '%macro mf_getapploc(pgm);'; put '%if "&pgm"="" %then %do;'; put '%if %symexist(_program) %then %let pgm=&_program;'; put '%else %do;'; put '%put &sysmacroname: No value provided and no _program variable available;'; put '%return;'; put '%end;'; put '%end;'; put '%local root;'; put '/**'; put '* First check we are not in the tests/macros folder (which has no subfolders)'; put '* or specifically in the testsetup or testteardown services'; put '*/'; put '%if %index(&pgm,/tests/macros/)'; put 'or %index(&pgm,/tests/testsetup)'; put 'or %index(&pgm,/tests/testteardown)'; put '%then %do;'; put '%let root=%substr(&pgm,1,%index(&pgm,/tests)-1);'; put '&root'; put '%return;'; put '%end;'; put '/**'; put '* Next, move up two levels to avoid matches on subfolder or service name'; put '*/'; put '%let root=%substr(&pgm,1,%length(&pgm)-%length(%scan(&pgm,-1,/))-1);'; put '%let root=%substr(&root,1,%length(&root)-%length(%scan(&root,-1,/))-1);'; put '%if %index(&root,/tests/) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/tests/)-1);'; put '%end;'; put '%else %if %index(&root,/services) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/services)-1);'; put '%end;'; put '%else %if %index(&root,/jobs) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/jobs)-1);'; put '%end;'; put '%else %put &sysmacroname: Could not find an app location from &pgm;'; put '&root'; put '%mend mf_getapploc ;'; put '%macro mf_getuniquefileref(prefix=_,maxtries=1000,lrecl=32767);'; put '%local rc fname;'; put '%if &prefix=0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%end;'; put '%else %do;'; put '%local x len;'; put '%let len=%eval(8-%length(&prefix));'; put '%let x=0;'; put '%do x=0 %to &maxtries;'; put '%let fname=&prefix%substr(%sysfunc(ranuni(0)),3,&len);'; put '%if %sysfunc(fileref(&fname)) > 0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%return;'; put '%end;'; put '%end;'; put '%put unable to find available fileref after &maxtries attempts;'; put '%end;'; put '%mend mf_getuniquefileref;'; put '%macro mp_abort(mac=mp_abort.sas, type=, msg=, iftrue=%str(1=1)'; put ', errds=work.mp_abort_errds'; put ', mode=REGULAR'; put ')/*/STORE SOURCE*/;'; put '%global sysprocessmode sysprocessname sasjs_stpsrv_header_loc sasjsprocessmode;'; put '%local fref fid i;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%put NOTE: /// mp_abort macro executing //;'; put '%if %length(&mac)>0 %then %put NOTE- called by &mac;'; put '%put NOTE - &msg;'; put '%if %symexist(_SYSINCLUDEFILEDEVICE)'; put '/* abort cancel FILE does not restart outside the INCLUDE on Viya 3.5 */'; put 'and %superq(SYSPROCESSNAME) ne %str(Compute Server)'; put '%then %do;'; put '%if "*&_SYSINCLUDEFILEDEVICE*" ne "**" %then %do;'; put 'data &errds;'; put 'iftrue=''1=1'';'; put 'length mac $100 msg $5000;'; put 'mac=symget(''mac'');'; put 'msg=symget(''msg'');'; put 'run;'; put 'data _null_;'; put 'abort cancel FILE;'; put 'run;'; put '%return;'; put '%end;'; put '%end;'; put '/* Web App Context */'; put '%if %symexist(_PROGRAM)'; put 'or %superq(SYSPROCESSNAME) = %str(Compute Server)'; put 'or &mode=INCLUDE'; put '%then %do;'; put 'options obs=max replace mprint;'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5"'; put '%then %do;'; put 'options nosyntaxcheck;'; put '%end;'; put '%if &mode=INCLUDE %then %do;'; put '%if %sysfunc(exist(&errds))=1 %then %do;'; put 'data _null_;'; put 'set &errds;'; put 'call symputx(''iftrue'',iftrue,''l'');'; put 'call symputx(''mac'',mac,''l'');'; put 'call symputx(''msg'',msg,''l'');'; put 'putlog (_all_)(=);'; put 'run;'; put '%if (&iftrue)=0 %then %return;'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: No include errors found;'; put '%return;'; put '%end;'; put '%end;'; put '/* extract log errs / warns, if exist */'; put '%local logloc logline;'; put '%global logmsg; /* capture global messages */'; put '%if %symexist(SYSPRINTTOLOG) %then %let logloc=&SYSPRINTTOLOG;'; put '%else %let logloc=%qsysfunc(getoption(LOG));'; put 'proc printto log=log;run;'; put '%let logline=0;'; put '%if %length(&logloc)>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input; putlog _infile_;'; put 'i=1;'; put 'retain logonce 0;'; put 'if ('; put '_infile_=:"%str(WARN)ING" or _infile_=:"%str(ERR)OR"'; put ') and logonce=0 then'; put 'do;'; put 'call symputx(''logline'',_n_);'; put 'logonce+1;'; put 'end;'; put 'run;'; put '/* capture log including lines BEFORE the err */'; put '%if &logline>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input;'; put 'i=1;'; put 'stoploop=0;'; put 'if _n_ ge &logline-15 and stoploop=0 then do until (i>22);'; put 'call symputx(''logmsg'',catx(''\n'',symget(''logmsg''),_infile_));'; put 'input;'; put 'i+1;'; put 'stoploop=1;'; put 'end;'; put 'if stoploop=1 then stop;'; put 'run;'; put '%end;'; put '%end;'; put '%if %symexist(SYS_JES_JOB_URI) %then %do;'; put '/* setup webout for Viya */'; put 'options nobomfile;'; put '%if "X&SYS_JES_JOB_URI.X"="XX" %then %do;'; put 'filename _webout temp lrecl=999999 mod;'; put '%end;'; put '%else %do;'; put 'filename _webout filesrvc parenturi="&SYS_JES_JOB_URI"'; put 'name="_webout.json" lrecl=999999 mod;'; put '%end;'; put '%end;'; put '%else %if %sysfunc(filename(fref,&sasjs_stpsrv_header_loc))=0 %then %do;'; put 'options nobomfile;'; put '/* set up http header for SASjs Server */'; put '%let fid=%sysfunc(fopen(&fref,A));'; put '%if &fid=0 %then %do;'; put '%put %str(ERR)OR: %sysfunc(sysmsg());'; put '%return;'; put '%end;'; put '%let rc=%sysfunc(fput(&fid,%str(Content-Type: application/json)));'; put '%let rc=%sysfunc(fwrite(&fid));'; put '%let rc=%sysfunc(fclose(&fid));'; put '%let rc=%sysfunc(filename(&fref));'; put '%end;'; put '/* send response in SASjs JSON format */'; put 'data _null_;'; put 'file _webout mod lrecl=32000 encoding=''utf-8'';'; put 'length msg syswarningtext syserrortext $32767 mode $10 ;'; put 'sasdatetime=datetime();'; put 'msg=symget(''msg'');'; put '%if &logline>0 %then %do;'; put 'msg=cats(msg,''\n\nLog Extract:\n'',symget(''logmsg''));'; put '%end;'; put '/* escape the escapes */'; put 'msg=tranwrd(msg,''\'',''\\'');'; put '/* escape the quotes */'; put 'msg=tranwrd(msg,''"'',''\"'');'; put '/* ditch the CRLFs as chrome complains */'; put 'msg=compress(msg,,''kw'');'; put '/* quote without quoting the quotes (which are escaped instead) */'; put 'msg=cats(''"'',msg,''"'');'; put 'if symexist(''_debug'') then debug=quote(trim(symget(''_debug'')));'; put 'else debug=''""'';'; put 'if symget(''sasjsprocessmode'')=''Stored Program'' then mode=''SASJS'';'; put 'if mode ne ''SASJS'' then put ''>>weboutBEGIN<<'';'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'put '',"sasjsAbort" : [{'';'; put 'put '' "MSG":'' msg ;'; put 'put '' ,"MAC": "'' "&mac" ''"}]'';'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'put '',"_DEBUG":'' debug ;'; put 'if symexist(''_metauser'') then do;'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put 'end;'; put 'if symexist(''SYS_JES_JOB_URI'') then do;'; put 'SYS_JES_JOB_URI=quote(trim(symget(''SYS_JES_JOB_URI'')));'; put 'put '',"SYS_JES_JOB_URI": '' SYS_JES_JOB_URI;'; put 'end;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put ",""SYSWARNINGTEXT"" : " syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'put "}" ;'; put 'if mode ne ''SASJS'' then put ''>>weboutEND<<'';'; put 'run;'; put '%put _all_;'; put '%if "&sysprocessmode " = "SAS Stored Process Server " %then %do;'; put 'data _null_;'; put 'putlog ''stpsrvset program err and syscc'';'; put 'rc=stpsrvset(''program error'', 0);'; put 'call symputx("syscc",0,"g");'; put 'run;'; put '%if &sysscp=WIN'; put 'and 1=0 /* deprecating this logic until we figure out a consistent abort */'; put 'and "%substr(%str(&sysvlong ),1,8)"="9.04.01M"'; put 'and "%substr(%str(&sysvlong ),9,1)">"5" %then %do;'; put '/* skip approach (below) does not work in windows m6+ envs */'; put 'endsas;'; put '%end;'; put '%else %do;'; put '/**'; put '* endsas kills 9.4m3 deployments by orphaning multibridges.'; put '* Abort variants are ungraceful (non zero return code)'; put '* This approach lets SAS run silently until the end :-)'; put '* Caution - fails when called within a %include within a macro'; put '* Use mp_include() to handle this.'; put '*/'; put 'filename skip temp;'; put 'data _null_;'; put 'file skip;'; put 'put ''%macro skip();'';'; put 'comment ''%mend skip; -> fix lint '';'; put 'put ''%macro skippy();'';'; put 'comment ''%mend skippy; -> fix lint '';'; put 'run;'; put '%inc skip;'; put '%end;'; put '%end;'; put '%else %if "&sysprocessmode " = "SAS Compute Server " %then %do;'; put '/* endsas kills the session making it harder to fetch results */'; put 'data _null_;'; put 'syswarningtext=symget(''syswarningtext'');'; put 'syserrortext=symget(''syserrortext'');'; put 'abort_msg=symget(''msg'');'; put 'syscc=symget(''syscc'');'; put 'sysuserid=symget(''sysuserid'');'; put 'iftrue=symget(''iftrue'');'; put 'put (_all_)(/=);'; put 'call symputx(''syscc'',0);'; put 'abort cancel nolist;'; put 'run;'; put '%end;'; put '%else %do;'; put '%abort cancel;'; put '%end;'; put '%end;'; put '%else %do;'; put '%put _all_;'; put '%abort cancel;'; put '%end;'; put '%mend mp_abort;'; put '/** @endcond */'; put '%macro mm_getstpcode('; put 'tree=/User Folders/sasdemo/somestp'; put ',name='; put ',outloc=0'; put ',outref=0'; put ',mDebug=1'; put ',showlog=NO'; put ');'; put '%local mD;'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing &sysmacroname..sas;'; put '%&mD.put _local_;'; put '%if %length(&name)>0 %then %let name=/&name;'; put '/* first, check if STP exists */'; put '%local tsuri;'; put '%let tsuri=stopifempty ;'; put 'data _null_;'; put 'format type uri tsuri value $200.;'; put 'call missing (of _all_);'; put 'path="&tree&name(StoredProcess)";'; put '/* first, find the STP ID */'; put 'if metadata_pathobj("",path,"StoredProcess",type,uri)>0 then do;'; put '/* get sourcecode */'; put 'cnt=1;'; put 'do while (metadata_getnasn(uri,"Notes",cnt,tsuri)>0);'; put 'rc=metadata_getattr(tsuri,"Name",value);'; put '&mD.put tsuri= value=;'; put 'if value="SourceCode" then do;'; put '/* found it! */'; put 'rc=metadata_getattr(tsuri,"Id",value);'; put 'call symputx(''tsuri'',value,''l'');'; put 'stop;'; put 'end;'; put 'cnt+1;'; put 'end;'; put 'end;'; put 'else put (_all_)(=);'; put 'run;'; put '%mp_abort(iftrue= (&tsuri=stopifempty)'; put ',mac=mm_getstpcode'; put ',msg=%str(&tree&name.(StoredProcess) not found!)'; put ')'; put '/**'; put '* Now we can extract the textstore'; put '*/'; put 'filename __getdoc temp lrecl=10000000;'; put 'proc metadata'; put 'in="$METAREPOSITORY'; put ''; put 'SAS1"'; put 'out=__getdoc ;'; put 'run;'; put '/* find the beginning of the text */'; put '%local start;'; put 'data _null_;'; put 'infile __getdoc lrecl=10000;'; put 'input;'; put 'start=index(_infile_,''StoredText="'');'; put 'if start then do;'; put 'call symputx("start",start+11);'; put '*putlog ''"'' _infile_ ''"'';'; put 'end;'; put 'stop;'; put '%local outeng;'; put '%if "&outloc"="0" %then %let outeng=TEMP;'; put '%else %let outeng="&outloc";'; put '%local fref;'; put '%if &outref=0 %then %let fref=%mf_getuniquefileref();'; put '%else %let fref=&outref;'; put '/* read the content, byte by byte, resolving escaped chars */'; put 'filename &fref &outeng lrecl=100000;'; put 'data _null_;'; put 'length filein 8 fileid 8;'; put 'filein = fopen("__getdoc","I",1,"B");'; put 'fileid = fopen("&fref","O",1,"B");'; put 'rec = "20"x;'; put 'length entity $6;'; put 'do while(fread(filein)=0);'; put 'x+1;'; put 'if x>&start then do;'; put 'rc = fget(filein,rec,1);'; put 'if rec=''"'' then leave;'; put 'else if rec="&" then do;'; put 'entity=rec;'; put 'do until (rec=";");'; put 'if fread(filein) ne 0 then goto getout;'; put 'rc = fget(filein,rec,1);'; put 'entity=cats(entity,rec);'; put 'end;'; put 'select (entity);'; put 'when (''&'' ) rec=''&'' ;'; put 'when (''<'' ) rec=''<'' ;'; put 'when (''>'' ) rec=''>'' ;'; put 'when (''''') rec="''" ;'; put 'when (''"'') rec=''"'' ;'; put 'when ('' '') rec=''0A''x;'; put 'when ('' '') rec=''0D''x;'; put 'when (''$'' ) rec=''$'' ;'; put 'when ('' '') rec=''09''x;'; put 'otherwise putlog "%str(WARN)ING: missing value for " entity=;'; put 'end;'; put 'rc =fput(fileid, substr(rec,1,1));'; put 'rc =fwrite(fileid);'; put 'end;'; put 'else do;'; put 'rc =fput(fileid,rec);'; put 'rc =fwrite(fileid);'; put 'end;'; put 'end;'; put 'end;'; put 'getout:'; put 'rc=fclose(filein);'; put 'rc=fclose(fileid);'; put 'run;'; put '%if &showlog=YES %then %do;'; put 'data _null_;'; put 'infile &fref lrecl=32767 end=last;'; put 'input;'; put 'if _n_=1 then putlog ''>>stpcodeBEGIN<<'';'; put 'putlog _infile_;'; put 'if last then putlog ''>>stpcodeEND<<'';'; put 'run;'; put '%end;'; put 'filename __getdoc clear;'; put '%if &outref=0 %then %do;'; put 'filename &fref clear;'; put '%end;'; put '%mend mm_getstpcode;'; put '%macro dc_getsettings();'; put '%global _program;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(syscc=&syscc on entry (&syswarningtext &syserrortext))'; put ')'; put '%if %length(&_PROGRAM)>1 %then %let root=&_program;'; put '%else %do;'; put '%global _metauser;'; put '%let _metauser=&sysuserid;'; put '/* to mimic a "real" _program we need to give a dummy role and stp name */'; put '%let root=/dummyRole/dummyName;'; put '%end;'; put '/* the DC precode is stored in the Admin folder in the root of'; put 'the project. Lets find that root. */'; put '%put &=root;'; put '%let root=%mf_getapploc();'; put '%put &=root;'; put '/* Now we know the root location we can retrieve the params */'; put '%let temploc=%sysfunc(pathname(work))/settings.txt;'; put '%mm_getstpcode(tree=&root/services/public'; put ',name=Data_Controller_Settings'; put ',outloc=&temploc'; put ')'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Unable to run getstpcode)'; put ')'; put 'filename _getsets "&temploc" lrecl=2000;'; put '/*'; put 'Do not use mp_include here - this puts a copy in every service, which creates'; put 'compilation problems when calling services from mp_include'; put '*/'; put '%inc _getsets/source2;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Problem running &sysmacroname (&syswarningtext &syserrortext))'; put ')'; put '%mend dc_getsettings;'; put '%macro mf_fmtdttm('; put ')/*/STORE SOURCE*/;'; put '%if "&sysver"="9.2" or "&sysver"="9.3"'; put 'or ("&sysver"="9.4" and "%substr(&SYSVLONG,9,1)" le "3")'; put 'or "%substr(&sysver,1,1)"="4"'; put 'or "%substr(&sysver,1,1)"="5"'; put '%then %do;DATETIME19.3%end;'; put '%else %do;E8601DT26.6%end;'; put '%mend mf_fmtdttm;'; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '%macro mp_init(prefix=SASJS'; put ')/*/STORE SOURCE*/;'; put '%if %symexist(SASJS_PREFIX) %then %return; /* only run once */'; put '%global'; put 'SASJS_PREFIX /* the ONLY hard-coded global macro variable in SASjs */'; put '&prefix._FUNCTIONS /* used in mcf_init() to track core function compilation */'; put '&prefix._INIT_NUM /* initialisation time as numeric */'; put '&prefix._INIT_DTTM /* initialisation time in E8601DT26.6 format */'; put '&prefix.WORK /* avoid typing %sysfunc(pathname(work)) every time */'; put ';'; put '%let sasjs_prefix=&prefix;'; put 'data _null_;'; put 'dttm=datetime();'; put 'call symputx("&sasjs_prefix._init_num",dttm,''g'');'; put 'call symputx("&sasjs_prefix._init_dttm",put(dttm,E8601DT26.6),''g'');'; put 'call symputx("&sasjs_prefix.work",pathname(''WORK''),''g'');'; put 'run;'; put 'options'; put 'compress=CHAR /* default is none so ensure we have something! */'; put 'datastmtchk=ALLKEYWORDS /* protection from overwriting input datasets */'; put 'errorcheck=STRICT /* catch errs in libname/filename statements */'; put 'fmterr /* ensure err when a format cannot be found */'; put 'mergenoby=%str(ERR)OR /* throw err when a merge has no BY variables */'; put 'missing=. /* changing this can cause hard to detect errs */'; put 'noquotelenmax /* avoid warnings for long strings */'; put 'noreplace /* avoid overwriting permanent datasets */'; put 'ps=max /* reduce log size slightly */'; put 'ls=max /* reduce log even more and avoid word truncation */'; put 'validmemname=COMPATIBLE /* avoid special characters etc in table names */'; put 'validvarname=V7 /* avoid special characters etc in variable names */'; put 'varinitchk=%str(ERR)OR /* avoid data mistakes from variable name typos */'; put 'varlenchk=%str(ERR)OR /* fail hard if truncation (data loss) can result */'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5" %then %do;'; put 'noautocorrect /* disallow misspelled procedure names */'; put 'dsoptions=note2err /* undocumented - convert bad NOTEs to ERRs */'; put '%end;'; put ';'; put '%mend mp_init;'; put '%macro mpeinit(fetch=YES);'; put '%global mpeinit'; put 'mpeadmins /* group with unrestricted Meditor access */'; put 'mpelocapprovals /* location for landing and staging files */'; put 'mpelib /* location of configuration tables for DC */'; put 'dc_repo_users /* location of user / group metadata */'; put 'dc_licence_key /* extracted in dc_getsettings */'; put 'dc_activation_key /* extracted in dc_getsettings */'; put 'dc_locale /* extracted in dc_getsettings */'; put 'dc_dttmtfmt /* can be overridden in dc_getsettings */'; put '_debug'; put ';'; put '%if &mpeinit=1 %then %return;'; put '%else %let mpeinit=1;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(Problem on service startup (&syswarningtext &syserrortext))'; put ')'; put '%mp_init()'; put '%if &fetch=YES %then %do;'; put '%webout(FETCH)'; put '%end;'; put '%global _CLIENTNAME;'; put '%mp_abort(iftrue= (&_CLIENTNAME=SAS Enterprise Guide)'; put ',mac=&_program..sas'; put ',msg=%str(Data Controller is a web app and should not be executed from EG)'; put ')'; put 'options urlencoding=utf8 nobomfile lrecl=32767;'; put '%let perf=%sysfunc(datetime());'; put '%put perfdiff: 0;'; put '%let dc_locale=SYSTEM; /* default if not set */'; put '/**'; put '* E8601DT26.6 has widest database support - but not all SAS flavours can'; put '* handle it. Override in the settings STP if needed.'; put '*/'; put 'data _null_;'; put 'dc_dttmtfmt=''"%sysfunc(datetime(),''!!"%mf_fmtdttm()"!!'')"dt'';'; put 'call symputx(''dc_dttmtfmt'',dc_dttmtfmt);'; put 'put dc_dttmtfmt=;'; put 'run;'; put '%put &=dc_dttmtfmt;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc prior to dc_getsettings)'; put ')'; put '%dc_getsettings()'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc after dc_getsettings)'; put ')'; put 'data _null_;'; put 'set &DC_LIBREF..mpe_config(where=('; put 'var_scope="DC"'; put 'and &dc_dttmtfmt lt tx_to'; put 'and var_active=1'; put '));'; put 'call symputx(var_name,var_value,''G'');'; put 'putlog var_name "=" var_value;'; put 'run;'; put '%let mpelib=&dc_libref;'; put '%let mpeadmins=&dc_admin_group;'; put '%let mpelocapprovals=&dc_staging_area;'; put '%let dc_repo_users=&dc_repo_users;'; put '%if &dc_locale ne SYSTEM %then %do;'; put 'options locale=&dc_locale;'; put '%end;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(Problem during compilation or with STP precode (&syswarningtext))'; put ')'; put '%mend mpeinit;'; put '%macro mf_mval(var);'; put '%if %symexist(&var) %then %do;'; put '%superq(&var)'; put '%end;'; put '%mend mf_mval;'; put '%macro mf_trimstr(basestr,trimstr);'; put '%local baselen trimlen trimval;'; put '/* return if basestr is shorter than trimstr (or 0) */'; put '%let baselen=%length(%superq(basestr));'; put '%let trimlen=%length(%superq(trimstr));'; put '%if &baselen < &trimlen or &baselen=0 %then %return;'; put '/* obtain the characters from the end of basestr */'; put '%let trimval=%qsubstr(%superq(basestr)'; put ',%length(%superq(basestr))-&trimlen+1'; put ',&trimlen);'; put '/* compare and if matching, chop it off! */'; put '%if %superq(basestr)=%superq(trimstr) %then %do;'; put '%return;'; put '%end;'; put '%else %if %superq(trimval)=%superq(trimstr) %then %do;'; put '%qsubstr(%superq(basestr),1,%length(%superq(basestr))-&trimlen)'; put '%end;'; put '%else %do;'; put '&basestr'; put '%end;'; put '%mend mf_trimstr;'; put '%macro mf_getplatform(switch'; put ')/*/STORE SOURCE*/;'; put '%local a b c;'; put '%if &switch.NONE=NONE %then %do;'; put '%if %symexist(sasjsprocessmode) %then %do;'; put '%if &sasjsprocessmode=Stored Program %then %do;'; put 'SASJS'; put '%return;'; put '%end;'; put '%end;'; put '%if %symexist(sysprocessmode) %then %do;'; put '%if "&sysprocessmode"="SAS Object Server"'; put 'or "&sysprocessmode"= "SAS Compute Server" %then %do;'; put 'SASVIYA'; put '%end;'; put '%else %if "&sysprocessmode"="SAS Stored Process Server"'; put 'or "&sysprocessmode"="SAS Workspace Server"'; put '%then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if %symexist(_metaport) or %symexist(_metauser) %then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if &switch=SASSTUDIO %then %do;'; put '/* return the version of SAS Studio else 0 */'; put '%if %mf_mval(_CLIENTAPP)=%str(SAS Studio) %then %do;'; put '%let a=%mf_mval(_CLIENTVERSION);'; put '%let b=%scan(&a,1,.);'; put '%if %eval(&b >2) %then %do;'; put '&b'; put '%end;'; put '%else 0;'; put '%end;'; put '%else 0;'; put '%end;'; put '%else %if &switch=VIYARESTAPI %then %do;'; put '%mf_trimstr(%sysfunc(getoption(servicesbaseurl)),/)'; put '%end;'; put '%mend mf_getplatform;'; put '%macro mpeterm();'; put '%local oldloc;'; put 'data _null_;'; put 'if symexist(''SYSPRINTTOLOG'') then oldloc=symget(''SYSPRINTTOLOG'');'; put 'else oldloc=getoption(''LOG'');'; put 'if subpad(oldloc,1,1) not in (''"'',"''",'' '') then oldloc=quote(cats(oldloc));'; put 'call symputx(''oldloc'',oldloc,''l'');'; put 'run;'; put '%if %length(&oldloc)>0 %then %do;'; put 'proc printto log=log;'; put 'run;'; put 'data _null_;'; put 'infile &oldloc;'; put 'input; putlog _infile_;'; put 'run;'; put '%end;'; put '%if %sysfunc(exist(&dc_libref..mpe_requests)) and %mf_getplatform() ne SASVIYA'; put '%then %do;'; put 'data ;'; put 'if 0 then set &dc_libref..mpe_requests;'; put 'request_dttm=%sysfunc(datetime());'; put 'request_user="%mf_getuser()";'; put 'request_service="%scan(&_program,-2,/)/%scan(&_program,-1,/)";'; put 'request_params='''';'; put 'output;stop;'; put 'proc append base=&dc_libref..mpe_requests data=&syslast force nowarn;'; put 'run;'; put '%end;'; put '%mend mpeterm;'; put '* SAS Macros end;'; put '* SAS Includes start;'; put '* SAS Includes end;'; put '* Binary Files start;'; put '* Binary Files end;'; put '* ServiceInit start;'; put 'options noquotelenmax ps=max;'; put '/* create dummy macros to prevent stpbegin / stpend from corrupting sessions */'; put '%macro stpbegin();'; put '%put NOTE: the STPBEGIN macro should not be used for web apps!;'; put '%mend stpbegin;'; put '%macro stpend();'; put '%put NOTE: the STPEND macro should not be used for web apps!;'; put '%mend stpend;'; put '* ServiceInit end;'; put '* Service start;'; put '/**'; put '@file getxlmaps.sas'; put '@brief Returns a list of rules and other info for a specific xlmap_id'; put '

Service Inputs

'; put '
getxlmaps_in
'; put '|XLMAP_ID|'; put '|---|'; put '|Sample|'; put '

Service Outputs

'; put '
xlmaprules
'; put 'Filtered output of the dc.MPE_XLMAP_RULES table'; put '|XLMAP_ID|XLMAP_RANGE_ID|XLMAP_SHEET|XLMAP_START|XLMAP_FINISH|'; put '|---|---|---|---|---|'; put '|Sample|Range1|Sheet1|ABSOLUTE A1| |'; put '|Sample|Range2|Sheet1|RELATIVE R[2]C[2]|ABSOLUTE H11|'; put '
xlmapinfo
'; put 'Extra info for a map id'; put '|TARGET_DS|'; put '|---|'; put '|DCXXX.MPE_XLMAP_DATA|'; put '

SAS Macros

'; put '@li mp_abort.sas'; put '@li mpeinit.sas'; put '@version 9.3'; put '@author 4GL Apps Ltd'; put '@copyright 4GL Apps Ltd. This code may only be used within Data Controller'; put 'and may not be re-distributed or re-sold without the express permission of'; put '4GL Apps Ltd.'; put '**/'; put '%mpeinit()'; put 'data _null_;'; put 'set work.getxlmaps_in;'; put 'putlog (_all_)(=);'; put 'call symputx(''xlmap_id'',xlmap_id);'; put 'run;'; put 'proc sql noprint;'; put 'create table work.xlmaprules as'; put 'select xlmap_id'; put ',XLMAP_RANGE_ID'; put ',XLMAP_SHEET'; put ',XLMAP_START'; put ',XLMAP_FINISH'; put 'from &mpelib..MPE_XLMAP_RULES'; put 'where &dc_dttmtfmt. lt tx_to and xlmap_id="&xlmap_id"'; put 'order by xlmap_sheet, xlmap_range_id;'; put '%global target_ds;'; put 'select XLMAP_TARGETLIBDS into: target_ds'; put 'from &mpelib..MPE_XLMAP_INFO'; put 'where &dc_dttmtfmt. lt tx_to and xlmap_id="&xlmap_id";'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(syscc=&syscc)'; put ')'; put 'data work.xlmapinfo;'; put 'target_ds=coalescec("&target_ds","&mpelib..MPE_XLMAP_DATA");'; put 'output;'; put 'stop;'; put 'run;'; put '%webout(OPEN)'; put '%webout(OBJ,xlmaprules)'; put '%webout(OBJ,xlmapinfo)'; put '%webout(CLOSE)'; put '* Service end;'; run; %mm_createwebservice(path=&appLoc/&path, name=&service, code=sascode, server=&serverName, replace=yes) filename sascode clear; %let service=loadfile; filename sascode temp lrecl=32767; data _null_; file sascode; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '/**'; put '@file mp_jsonout.sas'; put '@brief Writes JSON in SASjs format to a fileref'; put '@details This macro can be used to OPEN a JSON stream and send one or more'; put 'tables as arrays of rows, where each row can be an object or a nested array.'; put 'There are two engines available - DATASTEP or PROCJSON.'; put 'PROC JSON is fast but will produce errs like the ones below if'; put 'special chars are encountered.'; put '> (ERR)OR: Some code points did not transcode.'; put '> An object or array close is not valid at this point in the JSON text.'; put '> Date value out of range'; put 'If this happens, try running with ENGINE=DATASTEP.'; put 'The DATASTEP engine is used to handle special SAS missing numerics, and'; put 'can also convert entire datasets to formatted values. Output JSON is always'; put 'in UTF-8.'; put 'Usage:'; put 'filename tmp temp;'; put 'data class; set sashelp.class;run;'; put '%mp_jsonout(OPEN,jref=tmp)'; put '%mp_jsonout(OBJ,class,jref=tmp)'; put '%mp_jsonout(OBJ,class,dslabel=class2,jref=tmp,showmeta=Y)'; put '%mp_jsonout(CLOSE,jref=tmp)'; put 'data _null_;'; put 'infile tmp;'; put 'input;putlog _infile_;'; put 'run;'; put 'If you are building web apps with SAS then you are strongly encouraged to use'; put 'the mX_createwebservice macros in combination with the'; put '[sasjs adapter](https://github.com/sasjs/adapter).'; put 'For more information see https://sasjs.io'; put '@param [in] action Valid values:'; put '@li OPEN - opens the JSON'; put '@li OBJ - sends a table with each row as an object'; put '@li ARR - sends a table with each row in an array'; put '@li CLOSE - closes the JSON'; put '@param [in] ds The dataset to send. Must be a work table.'; put '@param [out] jref= (_webout) The fileref to which to send the JSON'; put '@param [out] dslabel= The name to give the table in the exported JSON'; put '@param [in] fmt= (Y) Whether to keep (Y) or strip (N) formats from the table'; put '@param [in] engine= (DATASTEP) Which engine to use to send the JSON. Options:'; put '@li PROCJSON (default)'; put '@li DATASTEP (more reliable when data has non standard characters)'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to JSON'; put '

Related Files

'; put '@li mp_ds2fmtds.sas'; put '@version 9.2'; put '@author Allan Bowe'; put '@source https://github.com/sasjs/core'; put '**/'; put '%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y'; put ',engine=DATASTEP'; put ',missing=NULL'; put ',showmeta=N'; put ',maxobs=MAX'; put ')/*/STORE SOURCE*/;'; put '%local tempds colinfo fmtds i numcols numobs stmt_obs lastobs optval'; put 'tmpds1 tmpds2 tmpds3 tmpds4;'; put '%let numcols=0;'; put '%if &maxobs ne MAX %then %let stmt_obs=%str(if _n_>&maxobs then stop;);'; put '%if &action=OPEN %then %do;'; put 'options nobomfile;'; put 'data _null_;file &jref encoding=''utf-8'' lrecl=200;'; put 'put ''{"PROCESSED_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''"'';'; put 'run;'; put '%end;'; put '%else %if (&action=ARR or &action=OBJ) %then %do;'; put '/* force variable names to always be uppercase in the JSON */'; put 'options validvarname=upcase;'; put '/* To avoid issues with _webout on EBI - such as encoding diffs and truncation'; put '(https://support.sas.com/kb/49/325.html) we use temporary files */'; put 'filename _sjs1 temp lrecl=200 ;'; put 'data _null_; file _sjs1 encoding=''utf-8'';'; put 'put ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":";'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjs1 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs1 clear;'; put '/* grab col defs */'; put 'proc contents noprint data=&ds'; put 'out=_data_(keep=name type length format formatl formatd varnum label);'; put 'run;'; put '%let colinfo=%scan(&syslast,2,.);'; put 'proc sort data=&colinfo;'; put 'by varnum;'; put 'run;'; put '/* move meta to mac vars */'; put 'data &colinfo;'; put 'if _n_=1 then call symputx(''numcols'',nobs,''l'');'; put 'set &colinfo end=last nobs=nobs;'; put 'name=upcase(name);'; put '/* fix formats */'; put 'if type=2 or type=6 then do;'; put 'typelong=''char'';'; put 'length fmt $49.;'; put 'if format='''' then fmt=cats(''$'',length,''.'');'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else fmt=cats(format,formatl,''.'');'; put 'end;'; put 'else do;'; put 'typelong=''num'';'; put 'if format='''' then fmt=''best.'';'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else if formatd=0 then fmt=cats(format,formatl,''.'');'; put 'else fmt=cats(format,formatl,''.'',formatd);'; put 'end;'; put '/* 32 char unique name */'; put 'newname=''sasjs''!!substr(cats(put(md5(name),$hex32.)),1,27);'; put 'call symputx(cats(''name'',_n_),name,''l'');'; put 'call symputx(cats(''newname'',_n_),newname,''l'');'; put 'call symputx(cats(''length'',_n_),length,''l'');'; put 'call symputx(cats(''fmt'',_n_),fmt,''l'');'; put 'call symputx(cats(''type'',_n_),type,''l'');'; put 'call symputx(cats(''typelong'',_n_),typelong,''l'');'; put 'call symputx(cats(''label'',_n_),coalescec(label,name),''l'');'; put '/* overwritten when fmt=Y and a custom format exists in catalog */'; put 'if typelong=''num'' then call symputx(cats(''fmtlen'',_n_),200,''l'');'; put 'else call symputx(cats(''fmtlen'',_n_),min(32767,ceil((length+10)*1.5)),''l'');'; put 'run;'; put '%let tempds=%substr(_%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql;'; put 'select count(*) into: lastobs from &ds;'; put '%if &maxobs ne MAX %then %let lastobs=%sysfunc(min(&lastobs,&maxobs));'; put '%if &engine=PROCJSON %then %do;'; put '%if &missing=STRING %then %do;'; put '%put &sysmacroname: Special Missings not supported in proc json.;'; put '%put &sysmacroname: Switching to DATASTEP engine;'; put '%goto datastep;'; put '%end;'; put 'data &tempds;'; put 'set &ds;'; put '&stmt_obs;'; put '%if &fmt=N %then format _numeric_ best32.;;'; put '/* PRETTY is necessary to avoid line truncation in large files */'; put 'filename _sjs2 temp lrecl=131068 encoding=''utf-8'';'; put 'proc json out=_sjs2 pretty'; put '%if &action=ARR %then nokeys ;'; put ';export &tempds / nosastags fmtnumeric;'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs2 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs2 clear;'; put '%end;'; put '%else %if &engine=DATASTEP %then %do;'; put '%datastep:'; put '%if %sysfunc(exist(&ds)) ne 1 & %sysfunc(exist(&ds,VIEW)) ne 1'; put '%then %do;'; put '%put &sysmacroname: &ds NOT FOUND!!!;'; put '%return;'; put '%end;'; put '%if &fmt=Y %then %do;'; put '/**'; put '* Extract format definitions'; put '* First, by getting library locations from dictionary.formats'; put '* Then, by exporting the width using proc format'; put '* Cannot use maxw from sashelp.vformat as not always populated'; put '* Cannot use fmtinfo() as not supported in all flavours'; put '*/'; put '%let tmpds1=%substr(fmtsum%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds2=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds3=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds4=%substr(col%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql noprint;'; put 'create table &tmpds1 as'; put 'select cats(libname,''.'',memname) as FMTCAT,'; put 'FMTNAME'; put 'from dictionary.formats'; put 'where fmttype=''F'' and libname is not null'; put 'and fmtname in (select format from &colinfo where format is not null)'; put 'order by 1;'; put 'create table &tmpds2('; put 'FMTNAME char(32),'; put 'LENGTH num'; put ');'; put '%local catlist cat fmtlist i;'; put 'select distinct fmtcat into: catlist separated by '' '' from &tmpds1;'; put '%do i=1 %to %sysfunc(countw(&catlist,%str( )));'; put '%let cat=%scan(&catlist,&i,%str( ));'; put 'proc sql;'; put 'select distinct fmtname into: fmtlist separated by '' '''; put 'from &tmpds1 where fmtcat="&cat";'; put 'proc format lib=&cat cntlout=&tmpds3(keep=fmtname length);'; put 'select &fmtlist;'; put 'run;'; put 'proc sql;'; put 'insert into &tmpds2 select distinct fmtname,length from &tmpds3;'; put '%end;'; put 'proc sql;'; put 'create table &tmpds4 as'; put 'select a.*, b.length as MAXW'; put 'from &colinfo a'; put 'left join &tmpds2 b'; put 'on cats(a.format)=cats(upcase(b.fmtname))'; put 'order by a.varnum;'; put 'data _null_;'; put 'set &tmpds4;'; put 'if not missing(maxw);'; put 'call symputx('; put 'cats(''fmtlen'',_n_),'; put '/* vars need extra padding due to JSON escaping of special chars */'; put 'min(32767,ceil((max(length,maxw)+10)*1.5))'; put ',''l'''; put ');'; put 'run;'; put '/* configure varlenchk - as we are explicitly shortening the variables */'; put '%let optval=%sysfunc(getoption(varlenchk));'; put 'options varlenchk=NOWARN;'; put 'data _data_(compress=char);'; put '/* shorten the new vars */'; put 'length'; put '%do i=1 %to &numcols;'; put '&&name&i $&&fmtlen&i'; put '%end;'; put ';'; put '/* rename on entry */'; put 'set &ds(rename=('; put '%do i=1 %to &numcols;'; put '&&name&i=&&newname&i'; put '%end;'; put '));'; put '&stmt_obs;'; put 'drop'; put '%do i=1 %to &numcols;'; put '&&newname&i'; put '%end;'; put ';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=num %then %do;'; put '&&name&i=cats(put(&&newname&i,&&fmt&i));'; put '%end;'; put '%else %do;'; put '&&name&i=put(&&newname&i,&&fmt&i);'; put '%end;'; put '%end;'; put 'if _error_ then do;'; put 'call symputx(''syscc'',1012);'; put 'stop;'; put 'end;'; put 'run;'; put '%let fmtds=&syslast;'; put 'options varlenchk=&optval;'; put '%end;'; put 'proc format; /* credit yabwon for special null removal */'; put 'value bart (default=40)'; put '%if &missing=NULL %then %do;'; put '._ - .z = null'; put '%end;'; put '%else %do;'; put '._ = [quote()]'; put '. = null'; put '.a - .z = [quote()]'; put '%end;'; put 'other = [best.];'; put 'data &tempds;'; put 'attrib _all_ label='''';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'length &&name&i $&&fmtlen&i...;'; put 'format &&name&i $&&fmtlen&i...;'; put '%end;'; put '%end;'; put '%if &fmt=Y %then %do;'; put 'set &fmtds;'; put '%end;'; put '%else %do;'; put 'set &ds;'; put '%end;'; put '&stmt_obs;'; put 'format _numeric_ bart.;'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'if findc(&&name&i,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put '&&name&i=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,&&name&i)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else &&name&i=quote(cats(&&name&i));'; put '%end;'; put '%end;'; put 'run;'; put 'filename _sjs3 temp lrecl=131068 ;'; put 'data _null_;'; put 'file _sjs3 encoding=''utf-8'';'; put 'if _n_=1 then put "[";'; put 'set &tempds;'; put 'if _n_>1 then put "," @; put'; put '%if &action=ARR %then "[" ; %else "{" ;'; put '%do i=1 %to &numcols;'; put '%if &i>1 %then "," ;'; put '%if &action=OBJ %then """&&name&i"":" ;'; put '"&&name&i"n /* name literal for reserved variable names */'; put '%end;'; put '%if &action=ARR %then "]" ; %else "}" ; ;'; put '/* close out the table */'; put 'data _null_;'; put 'file _sjs3 mod encoding=''utf-8'';'; put 'put '']'';'; put 'run;'; put 'data _null_;'; put 'infile _sjs3 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs3 clear;'; put '%end;'; put 'proc sql;'; put 'drop table &colinfo, &tempds;'; put '%if %substr(&showmeta,1,1)=Y %then %do;'; put 'filename _sjs4 temp lrecl=131068 encoding=''utf-8'';'; put 'data _null_;'; put 'file _sjs4;'; put 'length label $350;'; put 'put ", ""$%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":{""vars"":{";'; put 'do i=1 to &numcols;'; put 'name=quote(trim(symget(cats(''name'',i))));'; put 'format=quote(trim(symget(cats(''fmt'',i))));'; put 'label=quote(prxchange(''s/\\/\\\\/'',-1,trim(symget(cats(''label'',i)))));'; put 'length=quote(trim(symget(cats(''length'',i))));'; put 'type=quote(trim(symget(cats(''typelong'',i))));'; put 'if i>1 then put "," @@;'; put 'put name '':{"format":'' format '',"label":'' label'; put ''',"length":'' length '',"type":'' type ''}'';'; put 'end;'; put 'put ''}}'';'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs4 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs4 clear;'; put '%end;'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put 'data _null_; file &jref encoding=''utf-8'' mod ;'; put 'put "}";'; put 'run;'; put '%end;'; put '%mend mp_jsonout;'; put '/**'; put '@file mm_webout.sas'; put '@brief Send data to/from SAS Stored Processes'; put '@details This macro should be added to the start of each Stored Process,'; put '**immediately** followed by a call to:'; put '%mm_webout(FETCH)'; put 'This will read all the input data and create same-named SAS datasets in the'; put 'WORK library. You can then insert your code, and send data back using the'; put 'following syntax:'; put 'data some datasets; * make some data ;'; put 'retain some columns;'; put 'run;'; put '%mm_webout(OPEN)'; put '%mm_webout(ARR,some) * Array format, fast, suitable for large tables ;'; put '%mm_webout(OBJ,datasets) * Object format, easier to work with ;'; put 'Finally, wrap everything up send some helpful system variables too'; put '%mm_webout(CLOSE)'; put '@param [in] action Either FETCH, OPEN, ARR, OBJ or CLOSE'; put '@param [in] ds The dataset to send back to the frontend'; put '@param [out] dslabel= Value to use instead of table name for sending to JSON'; put '@param [in] fmt= (N) Setting Y converts all vars to their formatted values'; put '@param [out] fref= (_webout) The fileref to which to write the JSON'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] workobs= (0) When set to a positive integer, will create a new'; put 'output object (WORK) which contains this number of observations from all'; put 'tables in the WORK library.'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to output JSON'; put '

SAS Macros

'; put '@li mp_jsonout.sas'; put '

Related Macros

'; put '@li ms_webout.sas'; put '@li mv_webout.sas'; put '@version 9.3'; put '@author Allan Bowe'; put '**/'; put '%macro mm_webout(action,ds,dslabel=,fref=_webout,fmt=N,missing=NULL'; put ',showmeta=N,maxobs=MAX,workobs=0'; put ');'; put '%global _webin_file_count _webin_fileref1 _webin_name1 _program _debug'; put 'sasjs_tables;'; put '%local i tempds jsonengine;'; put '/* see https://github.com/sasjs/core/issues/41 */'; put '%if "%upcase(&SYSENCODING)" ne "UTF-8" %then %let jsonengine=PROCJSON;'; put '%else %let jsonengine=DATASTEP;'; put '%if &action=FETCH %then %do;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'options mprint notes mprintnest;'; put '%end;'; put '%let _webin_file_count=%eval(&_webin_file_count+0);'; put '/* now read in the data */'; put '%do i=1 %to &_webin_file_count;'; put '%if &_webin_file_count=1 %then %do;'; put '%let _webin_fileref1=&_webin_fileref;'; put '%let _webin_name1=&_webin_name;'; put '%end;'; put 'data _null_;'; put 'infile &&_webin_fileref&i termstr=crlf;'; put 'input;'; put 'call symputx(''input_statement'',_infile_);'; put 'putlog "&&_webin_name&i input statement: " _infile_;'; put 'stop;'; put 'data &&_webin_name&i;'; put 'infile &&_webin_fileref&i firstobs=2 dsd termstr=crlf encoding=''utf-8'';'; put 'input &input_statement;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'if _n_<20 then putlog _infile_;'; put '%end;'; put 'run;'; put '%let sasjs_tables=&sasjs_tables &&_webin_name&i;'; put '%end;'; put '%end;'; put '%else %if &action=OPEN %then %do;'; put '/* fix encoding */'; put 'OPTIONS NOBOMFILE;'; put '/**'; put '* check xengine type to avoid the below err message:'; put '* > Function is only valid for filerefs using the CACHE access method.'; put '*/'; put 'data _null_;'; put 'set sashelp.vextfl(where=(fileref="_WEBOUT"));'; put 'if xengine=''STREAM'' then do;'; put 'rc=stpsrv_header(''Content-type'',"text/html; encoding=utf-8");'; put 'end;'; put 'run;'; put '/* setup json */'; put 'data _null_;file &fref encoding=''utf-8'';'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutBEGIN<<'';'; put '%end;'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'run;'; put '%end;'; put '%else %if &action=ARR or &action=OBJ %then %do;'; put '%mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt,jref=&fref'; put ',engine=&jsonengine,missing=&missing,showmeta=&showmeta,maxobs=&maxobs'; put ')'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put '/* To avoid issues with _webout on EBI we use a temporary file */'; put 'filename _sjsref temp lrecl=131068;'; put '%if %str(&workobs) > 0 %then %do;'; put '/* if debug mode, send back first XX records of each work table also */'; put 'data;run;%let tempds=%scan(&syslast,2,.);'; put 'ods output Members=&tempds;'; put 'proc datasets library=WORK memtype=data;'; put '%local wtcnt;%let wtcnt=0;'; put 'data _null_;'; put 'set &tempds;'; put 'if not (upcase(name) =:"DATA"); /* ignore temp datasets */'; put 'i+1;'; put 'call symputx(cats(''wt'',i),name,''l'');'; put 'call symputx(''wtcnt'',i,''l'');'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put ",""WORK"":{";'; put '%do i=1 %to &wtcnt;'; put '%let wt=&&wt&i;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'dsid=open("WORK.&wt",''is'');'; put 'nlobs=attrn(dsid,''NLOBS'');'; put 'nvars=attrn(dsid,''NVARS'');'; put 'rc=close(dsid);'; put 'if &i>1 then put '',''@;'; put 'put " ""&wt"" : {";'; put 'put ''"nlobs":'' nlobs;'; put 'put '',"nvars":'' nvars;'; put '%mp_jsonout(OBJ,&wt,jref=_sjsref,dslabel=first10rows,showmeta=Y'; put ',maxobs=&workobs'; put ')'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put '%end;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put 'run;'; put '%end;'; put '/* close off json */'; put 'data _null_;file _sjsref mod encoding=''utf-8'';'; put 'length SYSPROCESSNAME syserrortext syswarningtext autoexec $512;'; put 'put ",""_DEBUG"" : ""&_debug"" ";'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'autoexec=quote(urlencode(trim(getoption(''autoexec''))));'; put 'put '',"AUTOEXEC" : '' autoexec;'; put 'put ",""MF_GETUSER"" : ""%mf_getuser()"" ";'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'put ",""SYSENCODING"" : ""&sysencoding"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSPROCESSID"" : ""&SYSPROCESSID"" ";'; put 'put ",""SYSPROCESSMODE"" : ""&SYSPROCESSMODE"" ";'; put 'SYSPROCESSNAME=quote(urlencode(cats(SYSPROCESSNAME)));'; put 'put ",""SYSPROCESSNAME"" : " SYSPROCESSNAME;'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put '',"SYSWARNINGTEXT" : '' syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'length memsize $32;'; put 'memsize="%sysfunc(INPUTN(%sysfunc(getoption(memsize)), best.),sizekmg.)";'; put 'memsize=quote(cats(memsize));'; put 'put '',"MEMSIZE" : '' memsize;'; put 'put "}" @;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutEND<<'';'; put '%end;'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjsref lrecl=1 recfm=n;'; put 'file &fref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjsref clear;'; put '%end;'; put '%mend mm_webout;'; put '%macro webout(action,ds,dslabel=,fmt=,missing=NULL,showmeta=NO,maxobs=MAX);'; put '%mm_webout(&action,ds=&ds,dslabel=&dslabel,fmt=&fmt'; put ',missing=&missing'; put ',showmeta=&showmeta'; put ',maxobs=&maxobs'; put ') %mend;'; put '/* provide additional debug info */'; put '%global _program;'; put '%put &=syscc;'; put '%put user=%mf_getuser();'; put '%put pgm=&_program;'; put '%put timestamp=%sysfunc(datetime(),datetime19.);'; put '* Service Variables start;'; put '* Service Variables end;'; put '* SAS Macros start;'; put '%macro mf_getapploc(pgm);'; put '%if "&pgm"="" %then %do;'; put '%if %symexist(_program) %then %let pgm=&_program;'; put '%else %do;'; put '%put &sysmacroname: No value provided and no _program variable available;'; put '%return;'; put '%end;'; put '%end;'; put '%local root;'; put '/**'; put '* First check we are not in the tests/macros folder (which has no subfolders)'; put '* or specifically in the testsetup or testteardown services'; put '*/'; put '%if %index(&pgm,/tests/macros/)'; put 'or %index(&pgm,/tests/testsetup)'; put 'or %index(&pgm,/tests/testteardown)'; put '%then %do;'; put '%let root=%substr(&pgm,1,%index(&pgm,/tests)-1);'; put '&root'; put '%return;'; put '%end;'; put '/**'; put '* Next, move up two levels to avoid matches on subfolder or service name'; put '*/'; put '%let root=%substr(&pgm,1,%length(&pgm)-%length(%scan(&pgm,-1,/))-1);'; put '%let root=%substr(&root,1,%length(&root)-%length(%scan(&root,-1,/))-1);'; put '%if %index(&root,/tests/) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/tests/)-1);'; put '%end;'; put '%else %if %index(&root,/services) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/services)-1);'; put '%end;'; put '%else %if %index(&root,/jobs) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/jobs)-1);'; put '%end;'; put '%else %put &sysmacroname: Could not find an app location from &pgm;'; put '&root'; put '%mend mf_getapploc ;'; put '%macro mf_getuniquefileref(prefix=_,maxtries=1000,lrecl=32767);'; put '%local rc fname;'; put '%if &prefix=0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%end;'; put '%else %do;'; put '%local x len;'; put '%let len=%eval(8-%length(&prefix));'; put '%let x=0;'; put '%do x=0 %to &maxtries;'; put '%let fname=&prefix%substr(%sysfunc(ranuni(0)),3,&len);'; put '%if %sysfunc(fileref(&fname)) > 0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%return;'; put '%end;'; put '%end;'; put '%put unable to find available fileref after &maxtries attempts;'; put '%end;'; put '%mend mf_getuniquefileref;'; put '%macro mp_abort(mac=mp_abort.sas, type=, msg=, iftrue=%str(1=1)'; put ', errds=work.mp_abort_errds'; put ', mode=REGULAR'; put ')/*/STORE SOURCE*/;'; put '%global sysprocessmode sysprocessname sasjs_stpsrv_header_loc sasjsprocessmode;'; put '%local fref fid i;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%put NOTE: /// mp_abort macro executing //;'; put '%if %length(&mac)>0 %then %put NOTE- called by &mac;'; put '%put NOTE - &msg;'; put '%if %symexist(_SYSINCLUDEFILEDEVICE)'; put '/* abort cancel FILE does not restart outside the INCLUDE on Viya 3.5 */'; put 'and %superq(SYSPROCESSNAME) ne %str(Compute Server)'; put '%then %do;'; put '%if "*&_SYSINCLUDEFILEDEVICE*" ne "**" %then %do;'; put 'data &errds;'; put 'iftrue=''1=1'';'; put 'length mac $100 msg $5000;'; put 'mac=symget(''mac'');'; put 'msg=symget(''msg'');'; put 'run;'; put 'data _null_;'; put 'abort cancel FILE;'; put 'run;'; put '%return;'; put '%end;'; put '%end;'; put '/* Web App Context */'; put '%if %symexist(_PROGRAM)'; put 'or %superq(SYSPROCESSNAME) = %str(Compute Server)'; put 'or &mode=INCLUDE'; put '%then %do;'; put 'options obs=max replace mprint;'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5"'; put '%then %do;'; put 'options nosyntaxcheck;'; put '%end;'; put '%if &mode=INCLUDE %then %do;'; put '%if %sysfunc(exist(&errds))=1 %then %do;'; put 'data _null_;'; put 'set &errds;'; put 'call symputx(''iftrue'',iftrue,''l'');'; put 'call symputx(''mac'',mac,''l'');'; put 'call symputx(''msg'',msg,''l'');'; put 'putlog (_all_)(=);'; put 'run;'; put '%if (&iftrue)=0 %then %return;'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: No include errors found;'; put '%return;'; put '%end;'; put '%end;'; put '/* extract log errs / warns, if exist */'; put '%local logloc logline;'; put '%global logmsg; /* capture global messages */'; put '%if %symexist(SYSPRINTTOLOG) %then %let logloc=&SYSPRINTTOLOG;'; put '%else %let logloc=%qsysfunc(getoption(LOG));'; put 'proc printto log=log;run;'; put '%let logline=0;'; put '%if %length(&logloc)>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input; putlog _infile_;'; put 'i=1;'; put 'retain logonce 0;'; put 'if ('; put '_infile_=:"%str(WARN)ING" or _infile_=:"%str(ERR)OR"'; put ') and logonce=0 then'; put 'do;'; put 'call symputx(''logline'',_n_);'; put 'logonce+1;'; put 'end;'; put 'run;'; put '/* capture log including lines BEFORE the err */'; put '%if &logline>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input;'; put 'i=1;'; put 'stoploop=0;'; put 'if _n_ ge &logline-15 and stoploop=0 then do until (i>22);'; put 'call symputx(''logmsg'',catx(''\n'',symget(''logmsg''),_infile_));'; put 'input;'; put 'i+1;'; put 'stoploop=1;'; put 'end;'; put 'if stoploop=1 then stop;'; put 'run;'; put '%end;'; put '%end;'; put '%if %symexist(SYS_JES_JOB_URI) %then %do;'; put '/* setup webout for Viya */'; put 'options nobomfile;'; put '%if "X&SYS_JES_JOB_URI.X"="XX" %then %do;'; put 'filename _webout temp lrecl=999999 mod;'; put '%end;'; put '%else %do;'; put 'filename _webout filesrvc parenturi="&SYS_JES_JOB_URI"'; put 'name="_webout.json" lrecl=999999 mod;'; put '%end;'; put '%end;'; put '%else %if %sysfunc(filename(fref,&sasjs_stpsrv_header_loc))=0 %then %do;'; put 'options nobomfile;'; put '/* set up http header for SASjs Server */'; put '%let fid=%sysfunc(fopen(&fref,A));'; put '%if &fid=0 %then %do;'; put '%put %str(ERR)OR: %sysfunc(sysmsg());'; put '%return;'; put '%end;'; put '%let rc=%sysfunc(fput(&fid,%str(Content-Type: application/json)));'; put '%let rc=%sysfunc(fwrite(&fid));'; put '%let rc=%sysfunc(fclose(&fid));'; put '%let rc=%sysfunc(filename(&fref));'; put '%end;'; put '/* send response in SASjs JSON format */'; put 'data _null_;'; put 'file _webout mod lrecl=32000 encoding=''utf-8'';'; put 'length msg syswarningtext syserrortext $32767 mode $10 ;'; put 'sasdatetime=datetime();'; put 'msg=symget(''msg'');'; put '%if &logline>0 %then %do;'; put 'msg=cats(msg,''\n\nLog Extract:\n'',symget(''logmsg''));'; put '%end;'; put '/* escape the escapes */'; put 'msg=tranwrd(msg,''\'',''\\'');'; put '/* escape the quotes */'; put 'msg=tranwrd(msg,''"'',''\"'');'; put '/* ditch the CRLFs as chrome complains */'; put 'msg=compress(msg,,''kw'');'; put '/* quote without quoting the quotes (which are escaped instead) */'; put 'msg=cats(''"'',msg,''"'');'; put 'if symexist(''_debug'') then debug=quote(trim(symget(''_debug'')));'; put 'else debug=''""'';'; put 'if symget(''sasjsprocessmode'')=''Stored Program'' then mode=''SASJS'';'; put 'if mode ne ''SASJS'' then put ''>>weboutBEGIN<<'';'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'put '',"sasjsAbort" : [{'';'; put 'put '' "MSG":'' msg ;'; put 'put '' ,"MAC": "'' "&mac" ''"}]'';'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'put '',"_DEBUG":'' debug ;'; put 'if symexist(''_metauser'') then do;'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put 'end;'; put 'if symexist(''SYS_JES_JOB_URI'') then do;'; put 'SYS_JES_JOB_URI=quote(trim(symget(''SYS_JES_JOB_URI'')));'; put 'put '',"SYS_JES_JOB_URI": '' SYS_JES_JOB_URI;'; put 'end;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put ",""SYSWARNINGTEXT"" : " syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'put "}" ;'; put 'if mode ne ''SASJS'' then put ''>>weboutEND<<'';'; put 'run;'; put '%put _all_;'; put '%if "&sysprocessmode " = "SAS Stored Process Server " %then %do;'; put 'data _null_;'; put 'putlog ''stpsrvset program err and syscc'';'; put 'rc=stpsrvset(''program error'', 0);'; put 'call symputx("syscc",0,"g");'; put 'run;'; put '%if &sysscp=WIN'; put 'and 1=0 /* deprecating this logic until we figure out a consistent abort */'; put 'and "%substr(%str(&sysvlong ),1,8)"="9.04.01M"'; put 'and "%substr(%str(&sysvlong ),9,1)">"5" %then %do;'; put '/* skip approach (below) does not work in windows m6+ envs */'; put 'endsas;'; put '%end;'; put '%else %do;'; put '/**'; put '* endsas kills 9.4m3 deployments by orphaning multibridges.'; put '* Abort variants are ungraceful (non zero return code)'; put '* This approach lets SAS run silently until the end :-)'; put '* Caution - fails when called within a %include within a macro'; put '* Use mp_include() to handle this.'; put '*/'; put 'filename skip temp;'; put 'data _null_;'; put 'file skip;'; put 'put ''%macro skip();'';'; put 'comment ''%mend skip; -> fix lint '';'; put 'put ''%macro skippy();'';'; put 'comment ''%mend skippy; -> fix lint '';'; put 'run;'; put '%inc skip;'; put '%end;'; put '%end;'; put '%else %if "&sysprocessmode " = "SAS Compute Server " %then %do;'; put '/* endsas kills the session making it harder to fetch results */'; put 'data _null_;'; put 'syswarningtext=symget(''syswarningtext'');'; put 'syserrortext=symget(''syserrortext'');'; put 'abort_msg=symget(''msg'');'; put 'syscc=symget(''syscc'');'; put 'sysuserid=symget(''sysuserid'');'; put 'iftrue=symget(''iftrue'');'; put 'put (_all_)(/=);'; put 'call symputx(''syscc'',0);'; put 'abort cancel nolist;'; put 'run;'; put '%end;'; put '%else %do;'; put '%abort cancel;'; put '%end;'; put '%end;'; put '%else %do;'; put '%put _all_;'; put '%abort cancel;'; put '%end;'; put '%mend mp_abort;'; put '/** @endcond */'; put '%macro mm_getstpcode('; put 'tree=/User Folders/sasdemo/somestp'; put ',name='; put ',outloc=0'; put ',outref=0'; put ',mDebug=1'; put ',showlog=NO'; put ');'; put '%local mD;'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing &sysmacroname..sas;'; put '%&mD.put _local_;'; put '%if %length(&name)>0 %then %let name=/&name;'; put '/* first, check if STP exists */'; put '%local tsuri;'; put '%let tsuri=stopifempty ;'; put 'data _null_;'; put 'format type uri tsuri value $200.;'; put 'call missing (of _all_);'; put 'path="&tree&name(StoredProcess)";'; put '/* first, find the STP ID */'; put 'if metadata_pathobj("",path,"StoredProcess",type,uri)>0 then do;'; put '/* get sourcecode */'; put 'cnt=1;'; put 'do while (metadata_getnasn(uri,"Notes",cnt,tsuri)>0);'; put 'rc=metadata_getattr(tsuri,"Name",value);'; put '&mD.put tsuri= value=;'; put 'if value="SourceCode" then do;'; put '/* found it! */'; put 'rc=metadata_getattr(tsuri,"Id",value);'; put 'call symputx(''tsuri'',value,''l'');'; put 'stop;'; put 'end;'; put 'cnt+1;'; put 'end;'; put 'end;'; put 'else put (_all_)(=);'; put 'run;'; put '%mp_abort(iftrue= (&tsuri=stopifempty)'; put ',mac=mm_getstpcode'; put ',msg=%str(&tree&name.(StoredProcess) not found!)'; put ')'; put '/**'; put '* Now we can extract the textstore'; put '*/'; put 'filename __getdoc temp lrecl=10000000;'; put 'proc metadata'; put 'in="$METAREPOSITORY'; put ''; put 'SAS1"'; put 'out=__getdoc ;'; put 'run;'; put '/* find the beginning of the text */'; put '%local start;'; put 'data _null_;'; put 'infile __getdoc lrecl=10000;'; put 'input;'; put 'start=index(_infile_,''StoredText="'');'; put 'if start then do;'; put 'call symputx("start",start+11);'; put '*putlog ''"'' _infile_ ''"'';'; put 'end;'; put 'stop;'; put '%local outeng;'; put '%if "&outloc"="0" %then %let outeng=TEMP;'; put '%else %let outeng="&outloc";'; put '%local fref;'; put '%if &outref=0 %then %let fref=%mf_getuniquefileref();'; put '%else %let fref=&outref;'; put '/* read the content, byte by byte, resolving escaped chars */'; put 'filename &fref &outeng lrecl=100000;'; put 'data _null_;'; put 'length filein 8 fileid 8;'; put 'filein = fopen("__getdoc","I",1,"B");'; put 'fileid = fopen("&fref","O",1,"B");'; put 'rec = "20"x;'; put 'length entity $6;'; put 'do while(fread(filein)=0);'; put 'x+1;'; put 'if x>&start then do;'; put 'rc = fget(filein,rec,1);'; put 'if rec=''"'' then leave;'; put 'else if rec="&" then do;'; put 'entity=rec;'; put 'do until (rec=";");'; put 'if fread(filein) ne 0 then goto getout;'; put 'rc = fget(filein,rec,1);'; put 'entity=cats(entity,rec);'; put 'end;'; put 'select (entity);'; put 'when (''&'' ) rec=''&'' ;'; put 'when (''<'' ) rec=''<'' ;'; put 'when (''>'' ) rec=''>'' ;'; put 'when (''''') rec="''" ;'; put 'when (''"'') rec=''"'' ;'; put 'when ('' '') rec=''0A''x;'; put 'when ('' '') rec=''0D''x;'; put 'when (''$'' ) rec=''$'' ;'; put 'when ('' '') rec=''09''x;'; put 'otherwise putlog "%str(WARN)ING: missing value for " entity=;'; put 'end;'; put 'rc =fput(fileid, substr(rec,1,1));'; put 'rc =fwrite(fileid);'; put 'end;'; put 'else do;'; put 'rc =fput(fileid,rec);'; put 'rc =fwrite(fileid);'; put 'end;'; put 'end;'; put 'end;'; put 'getout:'; put 'rc=fclose(filein);'; put 'rc=fclose(fileid);'; put 'run;'; put '%if &showlog=YES %then %do;'; put 'data _null_;'; put 'infile &fref lrecl=32767 end=last;'; put 'input;'; put 'if _n_=1 then putlog ''>>stpcodeBEGIN<<'';'; put 'putlog _infile_;'; put 'if last then putlog ''>>stpcodeEND<<'';'; put 'run;'; put '%end;'; put 'filename __getdoc clear;'; put '%if &outref=0 %then %do;'; put 'filename &fref clear;'; put '%end;'; put '%mend mm_getstpcode;'; put '%macro dc_getsettings();'; put '%global _program;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(syscc=&syscc on entry (&syswarningtext &syserrortext))'; put ')'; put '%if %length(&_PROGRAM)>1 %then %let root=&_program;'; put '%else %do;'; put '%global _metauser;'; put '%let _metauser=&sysuserid;'; put '/* to mimic a "real" _program we need to give a dummy role and stp name */'; put '%let root=/dummyRole/dummyName;'; put '%end;'; put '/* the DC precode is stored in the Admin folder in the root of'; put 'the project. Lets find that root. */'; put '%put &=root;'; put '%let root=%mf_getapploc();'; put '%put &=root;'; put '/* Now we know the root location we can retrieve the params */'; put '%let temploc=%sysfunc(pathname(work))/settings.txt;'; put '%mm_getstpcode(tree=&root/services/public'; put ',name=Data_Controller_Settings'; put ',outloc=&temploc'; put ')'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Unable to run getstpcode)'; put ')'; put 'filename _getsets "&temploc" lrecl=2000;'; put '/*'; put 'Do not use mp_include here - this puts a copy in every service, which creates'; put 'compilation problems when calling services from mp_include'; put '*/'; put '%inc _getsets/source2;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Problem running &sysmacroname (&syswarningtext &syserrortext))'; put ')'; put '%mend dc_getsettings;'; put '%macro mf_fmtdttm('; put ')/*/STORE SOURCE*/;'; put '%if "&sysver"="9.2" or "&sysver"="9.3"'; put 'or ("&sysver"="9.4" and "%substr(&SYSVLONG,9,1)" le "3")'; put 'or "%substr(&sysver,1,1)"="4"'; put 'or "%substr(&sysver,1,1)"="5"'; put '%then %do;DATETIME19.3%end;'; put '%else %do;E8601DT26.6%end;'; put '%mend mf_fmtdttm;'; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '%macro mp_init(prefix=SASJS'; put ')/*/STORE SOURCE*/;'; put '%if %symexist(SASJS_PREFIX) %then %return; /* only run once */'; put '%global'; put 'SASJS_PREFIX /* the ONLY hard-coded global macro variable in SASjs */'; put '&prefix._FUNCTIONS /* used in mcf_init() to track core function compilation */'; put '&prefix._INIT_NUM /* initialisation time as numeric */'; put '&prefix._INIT_DTTM /* initialisation time in E8601DT26.6 format */'; put '&prefix.WORK /* avoid typing %sysfunc(pathname(work)) every time */'; put ';'; put '%let sasjs_prefix=&prefix;'; put 'data _null_;'; put 'dttm=datetime();'; put 'call symputx("&sasjs_prefix._init_num",dttm,''g'');'; put 'call symputx("&sasjs_prefix._init_dttm",put(dttm,E8601DT26.6),''g'');'; put 'call symputx("&sasjs_prefix.work",pathname(''WORK''),''g'');'; put 'run;'; put 'options'; put 'compress=CHAR /* default is none so ensure we have something! */'; put 'datastmtchk=ALLKEYWORDS /* protection from overwriting input datasets */'; put 'errorcheck=STRICT /* catch errs in libname/filename statements */'; put 'fmterr /* ensure err when a format cannot be found */'; put 'mergenoby=%str(ERR)OR /* throw err when a merge has no BY variables */'; put 'missing=. /* changing this can cause hard to detect errs */'; put 'noquotelenmax /* avoid warnings for long strings */'; put 'noreplace /* avoid overwriting permanent datasets */'; put 'ps=max /* reduce log size slightly */'; put 'ls=max /* reduce log even more and avoid word truncation */'; put 'validmemname=COMPATIBLE /* avoid special characters etc in table names */'; put 'validvarname=V7 /* avoid special characters etc in variable names */'; put 'varinitchk=%str(ERR)OR /* avoid data mistakes from variable name typos */'; put 'varlenchk=%str(ERR)OR /* fail hard if truncation (data loss) can result */'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5" %then %do;'; put 'noautocorrect /* disallow misspelled procedure names */'; put 'dsoptions=note2err /* undocumented - convert bad NOTEs to ERRs */'; put '%end;'; put ';'; put '%mend mp_init;'; put '%macro mpeinit(fetch=YES);'; put '%global mpeinit'; put 'mpeadmins /* group with unrestricted Meditor access */'; put 'mpelocapprovals /* location for landing and staging files */'; put 'mpelib /* location of configuration tables for DC */'; put 'dc_repo_users /* location of user / group metadata */'; put 'dc_licence_key /* extracted in dc_getsettings */'; put 'dc_activation_key /* extracted in dc_getsettings */'; put 'dc_locale /* extracted in dc_getsettings */'; put 'dc_dttmtfmt /* can be overridden in dc_getsettings */'; put '_debug'; put ';'; put '%if &mpeinit=1 %then %return;'; put '%else %let mpeinit=1;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(Problem on service startup (&syswarningtext &syserrortext))'; put ')'; put '%mp_init()'; put '%if &fetch=YES %then %do;'; put '%webout(FETCH)'; put '%end;'; put '%global _CLIENTNAME;'; put '%mp_abort(iftrue= (&_CLIENTNAME=SAS Enterprise Guide)'; put ',mac=&_program..sas'; put ',msg=%str(Data Controller is a web app and should not be executed from EG)'; put ')'; put 'options urlencoding=utf8 nobomfile lrecl=32767;'; put '%let perf=%sysfunc(datetime());'; put '%put perfdiff: 0;'; put '%let dc_locale=SYSTEM; /* default if not set */'; put '/**'; put '* E8601DT26.6 has widest database support - but not all SAS flavours can'; put '* handle it. Override in the settings STP if needed.'; put '*/'; put 'data _null_;'; put 'dc_dttmtfmt=''"%sysfunc(datetime(),''!!"%mf_fmtdttm()"!!'')"dt'';'; put 'call symputx(''dc_dttmtfmt'',dc_dttmtfmt);'; put 'put dc_dttmtfmt=;'; put 'run;'; put '%put &=dc_dttmtfmt;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc prior to dc_getsettings)'; put ')'; put '%dc_getsettings()'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc after dc_getsettings)'; put ')'; put 'data _null_;'; put 'set &DC_LIBREF..mpe_config(where=('; put 'var_scope="DC"'; put 'and &dc_dttmtfmt lt tx_to'; put 'and var_active=1'; put '));'; put 'call symputx(var_name,var_value,''G'');'; put 'putlog var_name "=" var_value;'; put 'run;'; put '%let mpelib=&dc_libref;'; put '%let mpeadmins=&dc_admin_group;'; put '%let mpelocapprovals=&dc_staging_area;'; put '%let dc_repo_users=&dc_repo_users;'; put '%if &dc_locale ne SYSTEM %then %do;'; put 'options locale=&dc_locale;'; put '%end;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(Problem during compilation or with STP precode (&syswarningtext))'; put ')'; put '%mend mpeinit;'; put '%macro mf_mval(var);'; put '%if %symexist(&var) %then %do;'; put '%superq(&var)'; put '%end;'; put '%mend mf_mval;'; put '%macro mf_trimstr(basestr,trimstr);'; put '%local baselen trimlen trimval;'; put '/* return if basestr is shorter than trimstr (or 0) */'; put '%let baselen=%length(%superq(basestr));'; put '%let trimlen=%length(%superq(trimstr));'; put '%if &baselen < &trimlen or &baselen=0 %then %return;'; put '/* obtain the characters from the end of basestr */'; put '%let trimval=%qsubstr(%superq(basestr)'; put ',%length(%superq(basestr))-&trimlen+1'; put ',&trimlen);'; put '/* compare and if matching, chop it off! */'; put '%if %superq(basestr)=%superq(trimstr) %then %do;'; put '%return;'; put '%end;'; put '%else %if %superq(trimval)=%superq(trimstr) %then %do;'; put '%qsubstr(%superq(basestr),1,%length(%superq(basestr))-&trimlen)'; put '%end;'; put '%else %do;'; put '&basestr'; put '%end;'; put '%mend mf_trimstr;'; put '%macro mf_getplatform(switch'; put ')/*/STORE SOURCE*/;'; put '%local a b c;'; put '%if &switch.NONE=NONE %then %do;'; put '%if %symexist(sasjsprocessmode) %then %do;'; put '%if &sasjsprocessmode=Stored Program %then %do;'; put 'SASJS'; put '%return;'; put '%end;'; put '%end;'; put '%if %symexist(sysprocessmode) %then %do;'; put '%if "&sysprocessmode"="SAS Object Server"'; put 'or "&sysprocessmode"= "SAS Compute Server" %then %do;'; put 'SASVIYA'; put '%end;'; put '%else %if "&sysprocessmode"="SAS Stored Process Server"'; put 'or "&sysprocessmode"="SAS Workspace Server"'; put '%then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if %symexist(_metaport) or %symexist(_metauser) %then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if &switch=SASSTUDIO %then %do;'; put '/* return the version of SAS Studio else 0 */'; put '%if %mf_mval(_CLIENTAPP)=%str(SAS Studio) %then %do;'; put '%let a=%mf_mval(_CLIENTVERSION);'; put '%let b=%scan(&a,1,.);'; put '%if %eval(&b >2) %then %do;'; put '&b'; put '%end;'; put '%else 0;'; put '%end;'; put '%else 0;'; put '%end;'; put '%else %if &switch=VIYARESTAPI %then %do;'; put '%mf_trimstr(%sysfunc(getoption(servicesbaseurl)),/)'; put '%end;'; put '%mend mf_getplatform;'; put '%macro mpeterm();'; put '%local oldloc;'; put 'data _null_;'; put 'if symexist(''SYSPRINTTOLOG'') then oldloc=symget(''SYSPRINTTOLOG'');'; put 'else oldloc=getoption(''LOG'');'; put 'if subpad(oldloc,1,1) not in (''"'',"''",'' '') then oldloc=quote(cats(oldloc));'; put 'call symputx(''oldloc'',oldloc,''l'');'; put 'run;'; put '%if %length(&oldloc)>0 %then %do;'; put 'proc printto log=log;'; put 'run;'; put 'data _null_;'; put 'infile &oldloc;'; put 'input; putlog _infile_;'; put 'run;'; put '%end;'; put '%if %sysfunc(exist(&dc_libref..mpe_requests)) and %mf_getplatform() ne SASVIYA'; put '%then %do;'; put 'data ;'; put 'if 0 then set &dc_libref..mpe_requests;'; put 'request_dttm=%sysfunc(datetime());'; put 'request_user="%mf_getuser()";'; put 'request_service="%scan(&_program,-2,/)/%scan(&_program,-1,/)";'; put 'request_params='''';'; put 'output;stop;'; put 'proc append base=&dc_libref..mpe_requests data=&syslast force nowarn;'; put 'run;'; put '%end;'; put '%mend mpeterm;'; put '%macro mm_getGroups('; put 'user='; put ',outds=work.mm_getGroups'; put ',repo=foundation'; put ',mDebug=0'; put ')/*/STORE SOURCE*/;'; put '%local mD oldrepo;'; put '%let oldrepo=%sysfunc(getoption(metarepository));'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing mm_getGroups.sas;'; put '%&mD.put _local_;'; put '/* on some sites, user / group info is in a different metadata repo to the'; put 'default */'; put '%if &oldrepo ne &repo %then %do;'; put 'options metarepository=&repo;'; put '%end;'; put '%if %length(&user)=0 %then %do;'; put 'data &outds (keep=groupuri groupname groupdesc);'; put 'length groupuri groupname groupdesc group_or_role $256;'; put 'call missing(of _all_);'; put 'i+1;'; put 'do while'; put '(metadata_getnobj("omsobj:IdentityGroup?@Id contains ''.''",i,groupuri)>0);'; put 'rc=metadata_getattr(groupuri, "Name", groupname);'; put 'rc=metadata_getattr(groupuri, "Desc", groupdesc);'; put 'rc=metadata_getattr(groupuri,"PublicType",group_or_role);'; put 'if Group_or_Role = ''UserGroup'' then output;'; put 'i+1;'; put 'end;'; put 'run;'; put '%end;'; put '%else %do;'; put 'data &outds (keep=groupuri groupname groupdesc);'; put 'length uri groupuri groupname groupdesc group_or_role $256;'; put 'call missing(of _all_);'; put 'rc=metadata_getnobj("omsobj:Person?@Name=''&user''",1,uri);'; put 'if rc<=0 then do;'; put 'putlog "%str(WARN)ING: rc=" rc "&user not found "'; put '", or there was an issue reading the repository.";'; put 'stop;'; put 'end;'; put 'a=1;'; put 'grpassn=metadata_getnasn(uri,"IdentityGroups",a,groupuri);'; put 'if grpassn in (-3,-4) then do;'; put 'putlog "%str(WARN)ING: No metadata groups found for &user";'; put 'output;'; put 'end;'; put 'else do while (grpassn > 0);'; put 'rc=metadata_getattr(groupuri, "Name", groupname);'; put 'rc=metadata_getattr(groupuri, "Desc", groupdesc);'; put 'a+1;'; put 'rc=metadata_getattr(groupuri,"PublicType",group_or_role);'; put 'if Group_or_Role = ''UserGroup'' then output;'; put 'grpassn=metadata_getnasn(uri,"IdentityGroups",a,groupuri);'; put 'end;'; put 'run;'; put '%end;'; put '%if &oldrepo ne &repo %then %do;'; put 'options metarepository=&oldrepo;'; put '%end;'; put '%mend mm_getGroups;'; put '%macro dc_getusergroups(user=,outds=mm_getgroups);'; put '%global dc_repo_users;'; put '%if &dc_repo_users= %then %let dc_repo_users=foundation;'; put '%mm_getgroups(user=&user,outds=&outds,repo=&dc_repo_users)'; put '%mend dc_getusergroups;'; put '%macro mpe_getgroups(user=,outds=);'; put '%if not %symexist(dc_repo_users) %then %let dc_repo_users=foundation;'; put '%dc_getusergroups(user=&user,outds=&outds)'; put 'data;'; put 'length groupname groupdesc $256;'; put 'set &dc_libref..mpe_groups;'; put 'where &dc_dttmtfmt. lt tx_to;'; put 'where also upcase(user_name)="%upcase(&user)";'; put 'groupname=group_name;'; put 'groupdesc=group_desc;'; put 'keep groupname groupdesc;'; put 'run;'; put 'data &outds;'; put 'set &syslast &outds(keep=groupname groupdesc);'; put 'run;'; put '%mend mpe_getgroups;'; put '%macro mf_getuniquename(prefix=MC);'; put '&prefix.%substr(%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32-%length(&prefix))'; put '%mend mf_getuniquename;'; put '%macro mf_abort(mac=mf_abort.sas, msg=, iftrue=%str(1=1)'; put ')/des=''ungraceful abort'' /*STORE SOURCE*/;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%put NOTE: /// mf_abort macro executing //;'; put '%if %length(&mac)>0 %then %put NOTE- called by &mac;'; put '%put NOTE - &msg;'; put '%abort;'; put '%mend mf_abort;'; put '/** @endcond */'; put '%macro mf_verifymacvars('; put 'verifyVars /* list of macro variable NAMES */'; put ',makeUpcase=NO /* set to YES to make all the variable VALUES uppercase */'; put ',mAbort=SOFT'; put ')/*/STORE SOURCE*/;'; put '%local verifyIterator verifyVar abortmsg;'; put '%do verifyIterator=1 %to %sysfunc(countw(&verifyVars,%str( )));'; put '%let verifyVar=%qscan(&verifyVars,&verifyIterator,%str( ));'; put '%if not %symexist(&verifyvar) %then %do;'; put '%let abortmsg= Variable &verifyVar is MISSING;'; put '%goto exit_err;'; put '%end;'; put '%if %length(%trim(&&&verifyVar))=0 %then %do;'; put '%let abortmsg= Variable &verifyVar is EMPTY;'; put '%goto exit_err;'; put '%end;'; put '%if &makeupcase=YES %then %do;'; put '%let &verifyVar=%upcase(&&&verifyvar);'; put '%end;'; put '%end;'; put '%goto exit_success;'; put '%exit_err:'; put '%put &abortmsg;'; put '%mf_abort(iftrue=(&mabort ne SOFT),'; put 'mac=mf_verifymacvars,'; put 'msg=%str(&abortmsg)'; put ')'; put '0'; put '%return;'; put '%exit_success:'; put '1'; put '%mend mf_verifymacvars;'; put '%macro mpe_accesscheck('; put 'base_table'; put ',outds=med_accesscheck /* WORK table to contain access details */'; put ',user= /* metadata user to check for */'; put ',access_level=APPROVE'; put ',cntl_lib_var=MPELIB'; put ');'; put '%if &user= %then %let user=%mf_getuser();'; put '%mp_abort('; put 'iftrue=(%index(&outds,.)>0 and %upcase(%scan(&outds,1,.)) ne WORK)'; put ',mac=mpe_accesscheck'; put ',msg=%str(outds should be a WORK table)'; put ')'; put '%mp_abort('; put 'iftrue=(%mf_verifymacvars(base_table user access_level)=0)'; put ',mac=mpe_accesscheck'; put ',msg=%str(Missing base_table/user access_level variables)'; put ')'; put '/* make unique temp table vars */'; put '%local tempds1 tempds2;'; put '%let tempds1=%mf_getuniquename(prefix=usergroups);'; put '%let tempds2=%mf_getuniquename(prefix=tablegroups);'; put '/* get list of user groups */'; put '%mpe_getgroups(user=&user,outds=&tempds1)'; put '/* get list of groups with access for that table */'; put 'proc sql;'; put 'create table &tempds2 as'; put 'select distinct sas_group'; put 'from &&&cntl_lib_var...mpe_security'; put 'where &dc_dttmtfmt. lt tx_to'; put 'and access_level="&access_level"'; put 'and ('; put '(libref="%scan(&base_table,1,.)" and upcase(dsn)="%scan(&base_table,2,.)")'; put 'or (libref="%scan(&base_table,1,.)" and dsn="*ALL*")'; put 'or (libref="*ALL*")'; put ');'; put '%if &_debug ge 131 %then %do;'; put 'data _null_;'; put 'set &tempds1;'; put 'putlog (_all_)(=);'; put 'run;'; put 'data _null_;'; put 'set &tempds2;'; put 'putlog (_all_)(=);'; put 'run;'; put '%end;'; put 'proc sql;'; put 'create table &outds as'; put 'select * from &tempds1'; put 'where groupname="&mpeadmins"'; put 'or groupname in (select * from &tempds2);'; put '%put &sysmacroname: base_table=&base_table;'; put '%put &sysmacroname: access_level=&access_level;'; put '%mend mpe_accesscheck;'; put '%macro mf_getattrn('; put 'libds'; put ',attr'; put ')/*/STORE SOURCE*/;'; put '%local dsid rc;'; put '%let dsid=%sysfunc(open(&libds,is));'; put '%if &dsid = 0 %then %do;'; put '%put %str(WARN)ING: Cannot open %trim(&libds), system message below;'; put '%put %sysfunc(sysmsg());'; put '-1'; put '%end;'; put '%else %do;'; put '%sysfunc(attrn(&dsid,&attr))'; put '%let rc=%sysfunc(close(&dsid));'; put '%end;'; put '%mend mf_getattrn;'; put '%macro mf_existds(libds'; put ')/*/STORE SOURCE*/;'; put '%if %sysfunc(exist(&libds)) ne 1 & %sysfunc(exist(&libds,VIEW)) ne 1 %then 0;'; put '%else 1;'; put '%mend mf_existds;'; put '%macro mpe_alerts(alert_event='; put ', alert_lib='; put ', alert_ds='; put ', dsid='; put ');'; put '/* exit if not configured */'; put '%global DC_EMAIL_ALERTS;'; put '%if &DC_EMAIL_ALERTS ne YES %then %do;'; put '%put DCNOTE: Email alerts are not configured;'; put '%put DCNOTE: (dc_email_alerts=&dc_email_alerts in &mpelib..mpe_config);'; put '%return;'; put '%end;'; put '%let alert_event=%upcase(&alert_event);'; put '%let alert_lib=%upcase(&alert_lib);'; put '%let alert_ds=%upcase(&alert_ds);'; put '%let from_user=%mf_getuser();'; put '/* get users TO which the email should be sent */'; put 'proc sql noprint;'; put 'create table work.users as select distinct a.alert_user,'; put 'b.user_displayname,'; put 'b.user_email'; put 'from &mpelib..mpe_alerts'; put '(where=(&dc_dttmtfmt. lt tx_to)) a'; put 'left join &mpelib..mpe_emails'; put '(where=(&dc_dttmtfmt. lt tx_to)) b'; put 'on upcase(trim(a.alert_user))=upcase(trim(b.user_name))'; put 'where a.alert_event in ("&alert_event","*ALL*")'; put 'and a.alert_lib in ("&alert_lib","*ALL*")'; put 'and a.alert_ds in ("&alert_ds","*ALL*");'; put '/* ensure the submitter is included on the email */'; put '%local isThere userdisp user_eml;'; put '%let isThere=0;'; put 'select count(*) into: isThere from &syslast where alert_user="&from_user";'; put '%if &isThere=0 %then %do;'; put 'select user_displayname, user_email'; put 'into: userdisp trimmed, :user_eml trimmed'; put 'from &mpelib..mpe_emails'; put 'where &dc_dttmtfmt. lt tx_to'; put 'and user_name="&from_user";'; put 'insert into work.users'; put 'set alert_user="&from_user"'; put ',user_displayname="&userdisp"'; put ',user_email="&user_eml";'; put '%end;'; put '/* if no email / displayname is provided, then extract from metadata */'; put 'data work.emails;'; put 'set work.users;'; put 'length emailuri uri text $256; call missing(emailuri,uri); drop emailuri uri;'; put '/* get displayname */'; put 'text=cats("omsobj:Person?@Name=''",alert_user,"''");'; put 'if metadata_getnobj(text,1,uri)<=0 then do;'; put 'putlog "DCWARN: &from_user not found";'; put 'return;'; put 'end;'; put 'else if user_displayname = '''' then do;'; put 'if metadata_getattr(uri,''DisplayName'',user_displayname)<0 then do;'; put 'putlog ''DCWARN: strange err, no displayname attribute of user URI'';'; put 'end;'; put 'end;'; put 'if index(user_email,''@'') then return;'; put '/* get email from metadata if not in input table */'; put 'if metadata_getnasn(uri,"EmailAddresses",1,emailuri)<=0 then do;'; put 'putlog "DCWARN: " alert_user " has no emails in MPE_EMAILS or metadata!";'; put 'if metadata_getattr(emailuri,"Address",user_email)<0 then do;'; put 'putlog ''DCWARN: Unexpected error! Valid emailURI but no email. Weird.'';'; put 'end;'; put 'end;'; put '/* only keep valid emails */'; put 'if index(user_email,''@'') ;'; put '/* dump contents for debugging */'; put 'if _n_<21 then putlog (_all_)(=);'; put 'run;'; put '%local emails;'; put 'proc sql noprint;'; put 'select quote(trim(user_email)) into: emails separated by '' '' from work.emails;'; put '/* exit if nobody to email */'; put '%if %mf_getattrn(emails,NLOBS)=0 %then %do;'; put '%put NOTE: No alerts configured (mpe_alerts.sas);'; put '%return;'; put '%end;'; put '/* display email options */'; put 'data _null_;'; put 'set sashelp.voption(where=(group=''EMAIL''));'; put 'put optname ''='' setting;'; put 'run;'; put 'filename __out email (&emails)'; put 'subject="Table &alert_lib..&alert_ds has been &alert_event";'; put '%local SUBMITTED_TXT;'; put '%if &alert_event=SUBMITTED %then %do;'; put 'data _null_;'; put 'set &mpelib..mpe_submit;'; put 'where table_id="&dsid" and submit_status_cd=''SUBMITTED'';'; put 'call symputx(''SUBMITTED_TXT'',submitted_reason_txt,''l'');'; put 'run;'; put 'data _null_;'; put 'File __out lrecl=32000;'; put 'put ''Dear user,'';'; put 'put '' '';'; put 'put "Please be advised that a change to table &alert_lib..&alert_ds has "'; put '"been proposed by &from_user on the ''&syshostname'' SAS server.";'; put 'put " ";'; put 'length txt $2048;'; put 'txt=symget(''SUBMITTED_TXT'');'; put 'put "Reason provided: " txt;'; put 'put " ";'; put 'put "This is an automated email by Data Controller for SAS. For "'; put '"documentation, please visit https://docs.datacontroller.io";'; put 'run;'; put '%end;'; put '%else %if &alert_event=APPROVED %then %do;'; put '/* there is no approval message */'; put 'data _null_;'; put 'File __out lrecl=32000;'; put 'put ''Dear user,'';'; put 'put '' '';'; put 'put "Please be advised that a change to table &alert_lib..&alert_ds has "'; put '"been approved by &from_user on the ''&syshostname'' SAS server.";'; put 'put " ";'; put 'put "This is an automated email by Data Controller for SAS. For "'; put '"documentation, please visit https://docs.datacontroller.io";'; put 'run;'; put '%end;'; put '%else %if &alert_event=REJECTED %then %do;'; put 'data _null_;'; put 'set &mpelib..mpe_review;'; put 'where table_id="&dsid" and review_status_id=''REJECTED'';'; put 'call symputx(''REVIEW_REASON_TXT'',REVIEW_REASON_TXT,''l'');'; put 'run;'; put 'data _null_;'; put 'File __out lrecl=32000;'; put 'put ''Dear user,'';'; put 'put '' '';'; put 'put "Please be advised that a change to table &alert_lib..&alert_ds has "'; put '"been rejected by &from_user on the ''&syshostname'' SAS server.";'; put 'put " ";'; put 'length txt $2048;'; put 'txt=symget(''REVIEW_REASON_TXT'');'; put 'put "Reason provided: " txt;'; put 'put " ";'; put 'put "This is an automated email by Data Controller for SAS. For "'; put '"documentation, please visit https://docs.datacontroller.io";'; put 'run;'; put '%end;'; put 'filename __out clear;'; put '%mend mpe_alerts ;'; put '%macro mpe_xlmapvalidate(mperef,inds,dclib,tgtds);'; put '%local ismap;'; put 'proc sql noprint;'; put 'select count(*) into: ismap'; put 'from &dclib..mpe_xlmap_info'; put 'where XLMAP_TARGETLIBDS="&tgtds" and &dc_dttmtfmt. le TX_TO ;'; put '%if "&tgtds"="&dclib..MPE_XLMAP_DATA" or &ismap>0 %then %do;'; put 'data &inds;'; put 'set &inds;'; put 'LOAD_REF="&mperef";'; put 'run;'; put '%end;'; put '%mend mpe_xlmapvalidate;'; put '%macro mpe_loadfail('; put 'status=FAILED - &syscc'; put ',now=%sysfunc(datetime())'; put ',approvals='; put ',mperef='; put ',reason_txt='; put ',mac=mpe_loadfail.sas'; put ',dc_dttmtfmt=E8601DT26.6'; put ');'; put '/* do not perform duration calc in pass through */'; put '%local dur;'; put 'data _null_;'; put 'now=symget(''now'');'; put 'dur=%sysfunc(datetime())-&now;'; put 'call symputx(''dur'',dur,''l'');'; put 'run;'; put 'proc sql;'; put 'update &mpelib..mpe_loads'; put 'set STATUS=symget(''status'')'; put ', duration=&dur'; put ', processed_dttm=&dc_dttmtfmt.'; put ', approvals = symget(''approvals'')'; put ', reason_txt= symget(''reason_txt'')'; put 'where CSV_DIR="&mperef";'; put '%let syscc=666;'; put '%mp_abort(msg=%superq(status)\n%superq(reason_txt),mac=&mac)'; put '%mend mpe_loadfail;'; put '%macro dc_getservicecode(loc=,outref=);'; put '%mm_getstpcode(tree=&loc'; put ',outref=&outref'; put ')'; put '%mend dc_getservicecode;'; put '%macro mp_include(fileref'; put ',prefix=_'; put ',opts=SOURCE2'; put ',errds=work.mp_abort_errds'; put ')/*/STORE SOURCE*/;'; put '/* prepare precode */'; put '%local tempref;'; put '%let tempref=%mf_getuniquefileref();'; put 'data _null_;'; put 'file &tempref;'; put 'set sashelp.vextfl(where=(fileref="%upcase(&fileref)"));'; put 'put ''%let _SYSINCLUDEFILEDEVICE='' xengine '';'';'; put 'name=scan(xpath,-1,''/\'');'; put 'put ''%let _SYSINCLUDEFILENAME='' name '';'';'; put 'path=subpad(xpath,1,length(xpath)-length(name)-1);'; put 'put ''%let _SYSINCLUDEFILEDIR='' path '';'';'; put 'put ''%let _SYSINCLUDEFILEFILEREF='' "&fileref;";'; put 'run;'; put '/* prepare the errds */'; put 'data &errds;'; put 'length msg mac $1000;'; put 'call missing(msg,mac);'; put 'iftrue=''1=0'';'; put 'run;'; put '/* include the include */'; put '%inc &tempref &fileref/&opts;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=%str(&_SYSINCLUDEFILEDIR/&_SYSINCLUDEFILENAME)'; put ',msg=%str(syscc=&syscc after executing &_SYSINCLUDEFILENAME)'; put ')'; put 'filename &tempref clear;'; put '%mend mp_include;'; put '%macro mpe_runhook(hookvar);'; put '%local pgmloc pgmtype;'; put '%let pgmtype=0;'; put '%put &sysmacroname: &=hookvar;'; put '%if %length(&&&hookvar)>0 %then %do;'; put '%put &sysmacroname: Executing &&&hookvar;'; put 'data _null_;'; put 'rule_value=symget("&hookvar");'; put 'if scan(upcase(rule_value),-1,''.'')=''SAS'' then do;'; put 'call symputx(''pgmtype'',''PGM'');'; put 'call symputx(''pgmloc'',rule_value);'; put 'end;'; put 'else do;'; put 'apploc="%mf_getapploc()";'; put 'if substr(rule_value,1,1) ne ''/'''; put 'then rule_value=cats(apploc,''/'',rule_value);'; put 'call symputx(''pgmloc'',rule_value);'; put 'call symputx(''pgmtype'',''JOB'');'; put 'end;'; put 'run;'; put '%if &pgmtype=PGM %then %do;'; put 'filename sascode "&pgmloc";'; put '%end;'; put '%else %do;'; put '%dc_getservicecode(loc=&pgmloc'; put ',outref=sascode'; put ')'; put '%end;'; put '/* the below script will need to modify work.STAGING_DS */'; put '%local x; %let x=; /* legacy feature */'; put '%mp_include(sascode)'; put '%end;'; put '%mend mpe_runhook;'; put '%macro mm_assignlib('; put 'libref'; put ',mAbort=HARD'; put ')/*/STORE SOURCE*/;'; put '%local mp_abort msg;'; put '%let mp_abort=0;'; put '%if %sysfunc(libref(&libref)) %then %do;'; put 'data _null_;'; put 'length liburi LibName msg $200;'; put 'call missing(of _all_);'; put 'nobj=metadata_getnobj("omsobj:SASLibrary?@Libref=''&libref''",1,liburi);'; put 'if nobj=1 then do;'; put 'rc=metadata_getattr(liburi,"Name",LibName);'; put '/* now try and assign it */'; put 'if libname("&libref",,''meta'',cats(''liburi="'',liburi,''";'')) ne 0 then do;'; put 'putlog "&libref could not be assigned";'; put 'putlog liburi=;'; put '/**'; put '* Fetch the system message for display in the abort modal. This is'; put '* not always helpful though. One example, previously received:'; put '* NOTE: Libref XX refers to the same library metadata as libref XX.'; put '*/'; put 'msg=sysmsg();'; put 'if msg=:''ERROR: Libref SAVE is not assigned.'' then do;'; put 'msg=catx(" ",'; put '"Could not assign %upcase(&libref).",'; put '"Please check metadata permissions! Libname:",libname,'; put '"Liburi:",liburi'; put ');'; put 'end;'; put 'else if msg="ERROR: User does not have appropriate authorization "!!'; put '"level for library SAVE."'; put 'then do;'; put 'msg=catx(" ",'; put '"ERROR: User does not have appropriate authorization level",'; put '"for library %upcase(&libref), libname:",libname,'; put '"Liburi:",liburi'; put ');'; put 'end;'; put 'call symputx(''msg'',msg,''l'');'; put 'if "&mabort"=''HARD'' then call symputx(''mp_abort'',1,''l'');'; put 'end;'; put 'else do;'; put 'put (_all_)(=);'; put 'call symputx(''libname'',libname,''L'');'; put 'call symputx(''liburi'',liburi,''L'');'; put 'end;'; put 'end;'; put 'else if nobj>1 then do;'; put 'if "&mabort"=''HARD'' then call symputx(''mp_abort'',1);'; put 'call symputx(''msg'',"More than one library with libref=&libref");'; put 'end;'; put 'else do;'; put 'if "&mabort"=''HARD'' then call symputx(''mp_abort'',1);'; put 'call symputx(''msg'',"Library &libref not found in metadata");'; put 'end;'; put 'run;'; put '%put NOTE: &msg;'; put '%end;'; put '%else %do;'; put '%put NOTE: Library &libref is already assigned;'; put '%end;'; put '%mp_abort(iftrue= (&mp_abort=1)'; put ',mac=mm_assignlib.sas'; put ',msg=%superq(msg)'; put ')'; put '%mend mm_assignlib;'; put '/** @cond */'; put '%macro mf_getengine(libref'; put ')/*/STORE SOURCE*/;'; put '%local dsid engnum rc engine;'; put '/* in case the parameter is a libref.tablename, pull off just the libref */'; put '%let libref = %upcase(%scan(&libref, 1, %str(.)));'; put '%let dsid=%sysfunc('; put 'open(sashelp.vlibnam(where=(libname="%upcase(&libref)")),i)'; put ');'; put '%if (&dsid ^= 0) %then %do;'; put '%let engnum=%sysfunc(varnum(&dsid,ENGINE));'; put '%let rc=%sysfunc(fetch(&dsid));'; put '%let engine=%sysfunc(getvarc(&dsid,&engnum));'; put '%put &libref. ENGINE is &engine.;'; put '%let rc= %sysfunc(close(&dsid));'; put '%end;'; put '%upcase(&engine)'; put '%mend mf_getengine;'; put '/** @endcond */'; put '%macro mm_assigndirectlib('; put 'libref'; put ',open_passthrough='; put ',sql_options='; put ',mDebug=0'; put ',mAbort=0'; put ')/*/STORE SOURCE*/;'; put '%local mD;'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing mm_assigndirectlib.sas;'; put '%&mD.put _local_;'; put '%if &mAbort=1 %then %let mAbort=;'; put '%else %let mAbort=%str(*);'; put '%&mD.put NOTE: Creating direct (non META) connection to &libref library;'; put '%local cur_engine;'; put '%let cur_engine=%mf_getengine(&libref);'; put '%if &cur_engine ne META and &cur_engine ne %then %do;'; put '%put NOTE: &libref already has a direct (&cur_engine) libname connection;'; put '%return;'; put '%end;'; put '%else %if %upcase(&libref)=WORK %then %do;'; put '%put NOTE: We already have a direct connection to WORK :-) ;'; put '%return;'; put '%end;'; put '/* need to determine the library ENGINE first */'; put '%local engine;'; put 'data _null_;'; put 'length lib_uri engine $256;'; put 'call missing (of _all_);'; put '/* get URI for the particular library */'; put 'rc1=metadata_getnobj("omsobj:SASLibrary?@Libref =''&libref''",1,lib_uri);'; put '/* get the Engine attribute of the previous object */'; put 'rc2=metadata_getattr(lib_uri,''Engine'',engine);'; put 'putlog "mm_assigndirectlib for &libref:" rc1= lib_uri= rc2= engine=;'; put 'call symputx("liburi",lib_uri,''l'');'; put 'call symputx("engine",engine,''l'');'; put 'run;'; put '/* now obtain engine specific connection details */'; put '%if &engine=BASE %then %do;'; put '%&mD.put NOTE: Retrieving BASE library path;'; put 'data _null_;'; put 'length up_uri $256 path cat_path $1024;'; put 'retain cat_path;'; put 'call missing (of _all_);'; put '/* get all the filepaths of the UsingPackages association */'; put 'i=1;'; put 'rc3=metadata_getnasn("&liburi",''UsingPackages'',i,up_uri);'; put 'do while (rc3>0);'; put '/* get the DirectoryName attribute of the previous object */'; put 'rc4=metadata_getattr(up_uri,''DirectoryName'',path);'; put 'if i=1 then path = ''("''!!trim(path)!!''" '';'; put 'else path ='' "''!!trim(path)!!''" '';'; put 'cat_path = trim(cat_path) !! " " !! trim(path) ;'; put 'i+1;'; put 'rc3=metadata_getnasn("&liburi",''UsingPackages'',i,up_uri);'; put 'end;'; put 'cat_path = trim(cat_path) !! ")";'; put '&mD.putlog "NOTE: Getting physical path for &libref library";'; put '&mD.putlog rc3= up_uri= rc4= cat_path= path=;'; put '&mD.putlog "NOTE: Libname cmd will be:";'; put '&mD.putlog "libname &libref" cat_path;'; put 'call symputx("filepath",cat_path,''l'');'; put 'run;'; put '%if %sysevalf(&sysver<9.4) %then %do;'; put 'libname &libref &filepath;'; put '%end;'; put '%else %do;'; put '/* apply the new filelocks option to cater for temporary locks */'; put 'libname &libref &filepath filelockwait=5;'; put '%end;'; put '%end;'; put '%else %if &engine=REMOTE %then %do;'; put 'data x;'; put 'length rcCon rcProp rc k 3 uriCon uriProp PropertyValue PropertyName'; put 'Delimiter $256 properties $2048;'; put 'retain properties;'; put 'rcCon = metadata_getnasn("&liburi", "LibraryConnection", 1, uriCon);'; put 'rcProp = metadata_getnasn(uriCon, "Properties", 1, uriProp);'; put 'k = 1;'; put 'rcProp = metadata_getnasn(uriCon, "Properties", k, uriProp);'; put 'do while (rcProp > 0);'; put 'rc = metadata_getattr(uriProp , "DefaultValue",PropertyValue);'; put 'rc = metadata_getattr(uriProp , "PropertyName",PropertyName);'; put 'rc = metadata_getattr(uriProp , "Delimiter",Delimiter);'; put 'properties = trim(properties) !! " " !! trim(PropertyName)'; put '!! trim(Delimiter) !! trim(PropertyValue);'; put 'output;'; put 'k+1;'; put 'rcProp = metadata_getnasn(uriCon, "Properties", k, uriProp);'; put 'end;'; put '%&mD.put NOTE: Getting properties for REMOTE SHARE &libref library;'; put '&mD.put _all_;'; put '%&mD.put NOTE: Libname cmd will be:;'; put '%&mD.put libname &libref &engine &properties slibref=&libref;'; put 'call symputx ("properties",trim(properties),''l'');'; put 'run;'; put 'libname &libref &engine &properties slibref=&libref;'; put '%end;'; put '%else %if &engine=OLEDB %then %do;'; put '%&mD.put NOTE: Retrieving OLEDB connection details;'; put 'data _null_;'; put 'length domain datasource provider properties schema'; put 'connx_uri domain_uri conprop_uri lib_uri schema_uri value $256.;'; put 'call missing (of _all_);'; put '/* get source connection ID */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,connx_uri);'; put '/* get connection domain */'; put 'rc1=metadata_getnasn(connx_uri,''Domain'',1,domain_uri);'; put 'rc2=metadata_getattr(domain_uri,''Name'',domain);'; put '&mD.putlog / ''NOTE: '' // ''NOTE- connection id: '' connx_uri ;'; put '&mD.putlog ''NOTE- domain: '' domain;'; put '/* get DSN and PROVIDER from connection properties */'; put 'i=0;'; put 'do until (rc<0);'; put 'i+1;'; put 'rc=metadata_getnasn(connx_uri,''Properties'',i,conprop_uri);'; put 'rc2=metadata_getattr(conprop_uri,''Name'',value);'; put 'if value=''Connection.OLE.Property.DATASOURCE.Name.xmlKey.txt'' then do;'; put 'rc3=metadata_getattr(conprop_uri,''DefaultValue'',datasource);'; put 'end;'; put 'else if value=''Connection.OLE.Property.PROVIDER.Name.xmlKey.txt'' then do;'; put 'rc4=metadata_getattr(conprop_uri,''DefaultValue'',provider);'; put 'end;'; put 'else if value=''Connection.OLE.Property.PROPERTIES.Name.xmlKey.txt'' then'; put 'do;'; put 'rc5=metadata_getattr(conprop_uri,''DefaultValue'',properties);'; put 'end;'; put 'end;'; put '&mD.putlog ''NOTE- dsn/provider/properties: '' /'; put 'datasource provider properties;'; put '&mD.putlog ''NOTE- schema: '' schema // ''NOTE-'';'; put '/* get SCHEMA */'; put 'rc6=metadata_getnasn("&liburi",''UsingPackages'',1,lib_uri);'; put 'rc7=metadata_getattr(lib_uri,''SchemaName'',schema);'; put 'call symputx(''SQL_domain'',domain,''l'');'; put 'call symputx(''SQL_dsn'',datasource,''l'');'; put 'call symputx(''SQL_provider'',provider,''l'');'; put 'call symputx(''SQL_properties'',properties,''l'');'; put 'call symputx(''SQL_schema'',schema,''l'');'; put 'run;'; put '%if %length(&open_passthrough)>0 %then %do;'; put 'proc sql &sql_options;'; put 'connect to OLEDB as &open_passthrough(INSERT_SQL=YES'; put '/* need additional properties to make this work */'; put 'properties=(''Integrated Security''=SSPI'; put '''Persist Security Info''=True'; put '%sysfunc(compress(%str(&SQL_properties),%str(())))'; put ')'; put 'DATASOURCE=&sql_dsn PROMPT=NO'; put 'PROVIDER=&sql_provider SCHEMA=&sql_schema CONNECTION = GLOBAL);'; put '%end;'; put '%else %do;'; put 'LIBNAME &libref OLEDB PROPERTIES=&sql_properties'; put 'DATASOURCE=&sql_dsn PROVIDER=&sql_provider SCHEMA=&sql_schema'; put '%if %length(&sql_domain)>0 %then %do;'; put 'authdomain="&sql_domain"'; put '%end;'; put 'connection=shared;'; put '%end;'; put '%end;'; put '%else %if &engine=ODBC %then %do;'; put '%&mD.put NOTE: Retrieving ODBC connection details;'; put 'data _null_;'; put 'length connx_uri conprop_uri value datasource up_uri schema domprop_uri authdomain $256.;'; put 'call missing (of _all_);'; put '/* get source connection ID */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,connx_uri);'; put '/* get connection properties */'; put 'i=0;'; put 'do until (rc2<0);'; put 'i+1;'; put 'rc2=metadata_getnasn(connx_uri,''Properties'',i,conprop_uri);'; put 'rc3=metadata_getattr(conprop_uri,''Name'',value);'; put 'if value=''Connection.ODBC.Property.DATASRC.Name.xmlKey.txt'' then do;'; put 'rc4=metadata_getattr(conprop_uri,''DefaultValue'',datasource);'; put 'rc2=-1;'; put 'end;'; put 'end;'; put '/* get auth domain */'; put 'autrc=metadata_getnasn(connx_uri,"Domain",1,domprop_uri);'; put 'arc=metadata_getattr(domprop_uri,"Name",authdomain);'; put 'if not missing(authdomain) then authdomain=cats(''AUTHDOMAIN='',authdomain);'; put 'call symputx(''authdomain'',authdomain,''l'');'; put '/* get SCHEMA */'; put 'rc6=metadata_getnasn("&liburi",''UsingPackages'',1,up_uri);'; put 'rc7=metadata_getattr(up_uri,''SchemaName'',schema);'; put '&mD.put rc= connx_uri= rc2= conprop_uri= rc3= value= rc4= datasource='; put 'rc6= up_uri= rc7= schema=;'; put 'call symputx(''SQL_schema'',schema,''l'');'; put 'call symputx(''SQL_dsn'',datasource,''l'');'; put 'run;'; put '%if %length(&open_passthrough)>0 %then %do;'; put 'proc sql &sql_options;'; put 'connect to ODBC as &open_passthrough'; put '(INSERT_SQL=YES DATASRC=&sql_dsn. CONNECTION=global);'; put '%end;'; put '%else %do;'; put 'libname &libref ODBC DATASRC=&sql_dsn SCHEMA=&sql_schema &authdomain;'; put '%end;'; put '%end;'; put '%else %if &engine=POSTGRES %then %do;'; put '%put NOTE: Obtaining POSTGRES library details;'; put 'data _null_;'; put 'length database ignore_read_only_columns direct_exe preserve_col_names'; put 'preserve_tab_names server schema authdomain user password'; put 'prop name value uri urisrc $256.;'; put 'call missing (of _all_);'; put '/* get database value */'; put 'prop=''Connection.DBMS.Property.DB.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,database,"");'; put 'if database^='''' then database=''database=''!!quote(trim(database));'; put 'call symputx(''database'',database,''l'');'; put '/* get IGNORE_READ_ONLY_COLUMNS value */'; put 'prop=''Library.DBMS.Property.DBIROC.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,ignore_read_only_columns,"");'; put 'if ignore_read_only_columns^='''' then ignore_read_only_columns='; put '''ignore_read_only_columns=''!!ignore_read_only_columns;'; put 'call symputx(''ignore_read_only_columns'',ignore_read_only_columns,''l'');'; put '/* get DIRECT_EXE value */'; put 'prop=''Library.DBMS.Property.DirectExe.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,direct_exe,"");'; put 'if direct_exe^='''' then direct_exe=''direct_exe=''!!direct_exe;'; put 'call symputx(''direct_exe'',direct_exe,''l'');'; put '/* get PRESERVE_COL_NAMES value */'; put 'prop=''Library.DBMS.Property.PreserveColNames.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,preserve_col_names,"");'; put 'if preserve_col_names^='''' then preserve_col_names='; put '''preserve_col_names=''!!preserve_col_names;'; put 'call symputx(''preserve_col_names'',preserve_col_names,''l'');'; put '/* get PRESERVE_TAB_NAMES value */'; put '/* be careful with PRESERVE_TAB_NAMES=YES - it will mean your table will'; put 'become case sensitive!! */'; put 'prop=''Library.DBMS.Property.PreserveTabNames.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,preserve_tab_names,"");'; put 'if preserve_tab_names^='''' then preserve_tab_names='; put '''preserve_tab_names=''!!preserve_tab_names;'; put 'call symputx(''preserve_tab_names'',preserve_tab_names,''l'');'; put '/* get SERVER value */'; put 'if metadata_getnasn("&liburi","LibraryConnection",1,uri)>0 then do;'; put 'prop=''Connection.DBMS.Property.SERVER.Name.xmlKey.txt'';'; put 'rc=metadata_getprop(uri,prop,server,"");'; put 'end;'; put 'if server^='''' then server=''server=''!!quote(cats(server));'; put 'call symputx(''server'',server,''l'');'; put '/* get SCHEMA value */'; put 'if metadata_getnasn("&liburi","UsingPackages",1,uri)>0 then do;'; put 'rc=metadata_getattr(uri,"SchemaName",schema);'; put 'end;'; put 'if schema^='''' then schema=''schema=''!!schema;'; put 'call symputx(''schema'',schema,''l'');'; put '/* get AUTHDOMAIN value */'; put '/* this is only useful if the user account contains that auth domain'; put 'if metadata_getnasn("&liburi","DefaultLogin",1,uri)>0 then do;'; put 'rc=metadata_getnasn(uri,"Domain",1,urisrc);'; put 'rc=metadata_getattr(urisrc,"Name",authdomain);'; put 'end;'; put 'if authdomain^='''' then authdomain=''authdomain=''!!quote(trim(authdomain));'; put '*/'; put 'call symputx(''authdomain'',authdomain,''l'');'; put '/* get user & pass */'; put 'if authdomain='''' & metadata_getnasn("&liburi","DefaultLogin",1,uri)>0 then'; put 'do;'; put 'rc=metadata_getattr(uri,"UserID",user);'; put 'rc=metadata_getattr(uri,"Password",password);'; put 'end;'; put 'if user^='''' then do;'; put 'user=''user=''!!quote(trim(user));'; put 'password=''password=''!!quote(trim(password));'; put 'end;'; put 'call symputx(''user'',user,''l'');'; put 'call symputx(''password'',password,''l'');'; put '&md.put _all_;'; put 'run;'; put '%if %length(&open_passthrough)>0 %then %do;'; put '%put %str(WARN)ING: Passthrough option for postgres not yet supported;'; put '%return;'; put '%end;'; put '%else %do;'; put '%if &mdebug=1 %then %do;'; put '%put NOTE: Executing the following:/;'; put '%put NOTE- libname &libref POSTGRES &database &ignore_read_only_columns;'; put '%put NOTE- &direct_exe &preserve_col_names &preserve_tab_names;'; put '%put NOTE- &server &schema &authdomain &user &password //;'; put '%end;'; put 'libname &libref POSTGRES &database &ignore_read_only_columns &direct_exe'; put '&preserve_col_names &preserve_tab_names &server &schema &authdomain'; put '&user &password;'; put '%end;'; put '%end;'; put '%else %if &engine=ORACLE %then %do;'; put '%put NOTE: Obtaining &engine library details;'; put 'data _null_;'; put 'length assocuri1 assocuri2 assocuri3 authdomain path schema $256;'; put 'call missing (of _all_);'; put '/* get auth domain */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,assocuri1);'; put 'rc=metadata_getnasn(assocuri1,''Domain'',1,assocuri2);'; put 'rc=metadata_getattr(assocuri2,"Name",authdomain);'; put 'call symputx(''authdomain'',authdomain,''l'');'; put '/* path */'; put 'rc=metadata_getprop(assocuri1,'; put '''Connection.Oracle.Property.PATH.Name.xmlKey.txt'',path);'; put 'call symputx(''path'',path,''l'');'; put '/* schema */'; put 'rc=metadata_getnasn("&liburi",''UsingPackages'',1,assocuri3);'; put 'rc=metadata_getattr(assocuri3,''SchemaName'',schema);'; put 'call symputx(''schema'',schema,''l'');'; put 'run;'; put '%put NOTE: Executing the following:/; %put NOTE-;'; put '%put NOTE- libname &libref ORACLE path=&path schema=&schema;'; put '%put NOTE- authdomain=&authdomain;'; put '%put NOTE-;'; put 'libname &libref ORACLE path=&path schema=&schema authdomain=&authdomain;'; put '%end;'; put '%else %if &engine=SQLSVR %then %do;'; put '%put NOTE: Obtaining &engine library details;'; put 'data _null;'; put 'length assocuri1 assocuri2 assocuri3 authdomain path schema userid'; put 'passwd $256;'; put 'call missing (of _all_);'; put 'rc=metadata_getnasn("&liburi",''DefaultLogin'',1,assocuri1);'; put 'rc=metadata_getattr(assocuri1,"UserID",userid);'; put 'rc=metadata_getattr(assocuri1,"Password",passwd);'; put 'call symputx(''user'',userid,''l'');'; put 'call symputx(''pass'',passwd,''l'');'; put '/* path */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,assocuri2);'; put 'rc=metadata_getprop(assocuri2,'; put '''Connection.SQL.Property.Datasrc.Name.xmlKey.txt'',path);'; put 'call symputx(''path'',path,''l'');'; put '/* schema */'; put 'rc=metadata_getnasn("&liburi",''UsingPackages'',1,assocuri3);'; put 'rc=metadata_getattr(assocuri3,''SchemaName'',schema);'; put 'call symputx(''schema'',schema,''l'');'; put 'run;'; put '%put NOTE: Executing the following:/; %put NOTE-;'; put '%put NOTE- libname &libref SQLSVR datasrc=&path schema=&schema ;'; put '%put NOTE- user="&user" pass="XXX";'; put '%put NOTE-;'; put 'libname &libref SQLSVR datasrc=&path schema=&schema user="&user" pass="&pass";'; put '%end;'; put '%else %if &engine=TERADATA %then %do;'; put '%put NOTE: Obtaining &engine library details;'; put 'data _null;'; put 'length assocuri1 assocuri2 assocuri3 authdomain path schema userid'; put 'passwd $256;'; put 'call missing (of _all_);'; put '/* get auth domain */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,assocuri1);'; put 'rc=metadata_getnasn(assocuri1,''Domain'',1,assocuri2);'; put 'rc=metadata_getattr(assocuri2,"Name",authdomain);'; put 'call symputx(''authdomain'',authdomain,''l'');'; put '/*'; put 'rc=metadata_getnasn("&liburi",''DefaultLogin'',1,assocuri1);'; put 'rc=metadata_getattr(assocuri1,"UserID",userid);'; put 'rc=metadata_getattr(assocuri1,"Password",passwd);'; put 'call symputx(''user'',userid,''l'');'; put 'call symputx(''pass'',passwd,''l'');'; put '*/'; put '/* path */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,assocuri2);'; put 'rc=metadata_getprop(assocuri2,'; put '''Connection.Teradata.Property.SERVER.Name.xmlKey.txt'',path);'; put 'call symputx(''path'',path,''l'');'; put '/* schema */'; put 'rc=metadata_getnasn("&liburi",''UsingPackages'',1,assocuri3);'; put 'rc=metadata_getattr(assocuri3,''SchemaName'',schema);'; put 'call symputx(''schema'',schema,''l'');'; put 'run;'; put '%put NOTE: Executing the following:/; %put NOTE-;'; put '%put NOTE- libname &libref TERADATA server="&path" schema=&schema ;'; put '%put NOTe- authdomain=&authdomain;'; put '%put NOTE-;'; put 'libname &libref TERADATA server="&path" schema=&schema authdomain=&authdomain;'; put '%end;'; put '%else %if &engine= %then %do;'; put '%put NOTE: Libref &libref is not registered in metadata;'; put '%&mAbort.mp_abort('; put 'msg=%str(ERR)OR: Libref &libref is not registered in metadata'; put ',mac=mm_assigndirectlib.sas);'; put '%return;'; put '%end;'; put '%else %do;'; put '%put %str(WARN)ING: Engine &engine is currently unsupported;'; put '%put %str(WARN)ING- Please contact your support team.;'; put '%return;'; put '%end;'; put '%mend mm_assigndirectlib;'; put '%macro mm_getrepos('; put 'outds=work.mm_getrepos'; put ')/*/STORE SOURCE*/;'; put '* use a temporary fileref to hold the response;'; put 'filename response temp;'; put '/* get list of libraries */'; put 'proc metadata in='; put '"1"'; put 'out=response;'; put 'run;'; put '/* write the response to the log for debugging */'; put '/*'; put 'data _null_;'; put 'infile response lrecl=1048576;'; put 'input;'; put 'put _infile_;'; put 'run;'; put '*/'; put '/* create an XML map to read the response */'; put 'filename sxlemap temp;'; put 'data _null_;'; put 'file sxlemap;'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository";'; put 'put "";'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Id";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Name";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Desc";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@DefaultNS";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@RepositoryType";'; put 'put "characterstring20";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@RepositoryFormat";'; put 'put "characterstring10";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@Access";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@CurrentAccess";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@PauseState";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Path";'; put 'put "";'; put 'put "characterstring256";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Engine";'; put 'put "";'; put 'put "characterstring8";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Options";'; put 'put "";'; put 'put "characterstring32";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@MetadataCreated";'; put 'put "characterstring24";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@MetadataUpdated";'; put 'put "characterstring24";'; put 'put '''';'; put 'put ''
'';'; put 'run;'; put 'libname _XML_ xml xmlfileref=response xmlmap=sxlemap;'; put 'proc sort data= _XML_.SASRepos out=&outds;'; put 'by name;'; put 'run;'; put '/* clear references */'; put 'filename sxlemap clear;'; put 'filename response clear;'; put 'libname _XML_ clear;'; put '%mend mm_getrepos;'; put '%macro dc_assignlib(type,libref,passthru=);'; put '%put &sysmacroname entry vars:;'; put '%put _local_;'; put '/* take current repository */'; put '%local repo repocnt x;'; put '%let repo=%sysfunc(getoption(metarepository));'; put '/* get list of repositories and filter */'; put '%mm_getrepos(outds=repos)'; put '%let repocnt=0;'; put 'data repos;'; put 'set repos;'; put 'where repositorytype in(''CUSTOM'',''FOUNDATION'')'; put '& upcase(name) ne "%upcase(&repo)";'; put 'keep id name ;'; put 'call symputx(cats(''repo'',_n_),name,''l'');'; put 'call symputx(''repocnt'',_n_,''l'');'; put 'run;'; put '/* find out which of these repositories has the libref we are searching for */'; put '%local lib_uri;'; put '%let lib_uri=NOTFOUND;'; put 'data _null_; /* check default repo first */'; put 'length lib_uri $256;'; put 'call missing (of _all_);'; put 'rc=metadata_getnobj("omsobj:SASLibrary?@Libref =''&libref''",1,lib_uri);'; put 'call symputx(''lib_uri'',coalescec(lib_uri,''NOTFOUND''),''l'');'; put 'run;'; put '%if &lib_uri=NOTFOUND %then %do;'; put '%do x=1 %to &repocnt;'; put 'options metarepository=&&repo&x;'; put 'data _null_;'; put 'length lib_uri $256;'; put 'call missing (of _all_);'; put 'rc=metadata_getnobj("omsobj:SASLibrary?@Libref =''&libref''",1,lib_uri);'; put 'call symputx(''lib_uri'',coalescec(lib_uri,''NOTFOUND''),''l'');'; put 'run;'; put '%if &lib_uri ne NOTFOUND %then %let x=%eval(&repocnt+1);'; put '%end;'; put '%end;'; put '%if &type=READ %then %do;'; put '%mm_assignlib(&libref)'; put '%end;'; put '%else %if &type=WRITE %then %do;'; put '%mm_assigndirectlib(&libref,open_passthrough=&passthru)'; put '%end;'; put 'options metarepository=&repo;'; put '%mend dc_assignlib;'; put '%macro mf_mkdir(dir'; put ')/*/STORE SOURCE*/;'; put '%local lastchar child parent;'; put '%let lastchar = %substr(&dir, %length(&dir));'; put '%if (%bquote(&lastchar) eq %str(:)) %then %do;'; put '/* Cannot create drive mappings */'; put '%return;'; put '%end;'; put '%if (%bquote(&lastchar)=%str(/)) or (%bquote(&lastchar)=%str(\)) %then %do;'; put '/* last char is a slash */'; put '%if (%length(&dir) eq 1) %then %do;'; put '/* one single slash - root location is assumed to exist */'; put '%return;'; put '%end;'; put '%else %do;'; put '/* strip last slash */'; put '%let dir = %substr(&dir, 1, %length(&dir)-1);'; put '%end;'; put '%end;'; put '%if (%sysfunc(fileexist(%bquote(&dir))) = 0) %then %do;'; put '/* directory does not exist so prepare to create */'; put '/* first get the childmost directory */'; put '%let child = %scan(&dir, -1, %str(/\:));'; put '/*'; put 'If child name = path name then there are no parents to create. Else'; put 'they must be recursively scanned.'; put '*/'; put '%if (%length(&dir) gt %length(&child)) %then %do;'; put '%let parent = %substr(&dir, 1, %length(&dir)-%length(&child));'; put '%mf_mkdir(&parent)'; put '%end;'; put '/*'; put 'Now create the directory. Complain loudly of any errs.'; put '*/'; put '%let dname = %sysfunc(dcreate(&child, &parent));'; put '%if (%bquote(&dname) eq ) %then %do;'; put '%put %str(ERR)OR: could not create &parent + &child;'; put '%abort cancel;'; put '%end;'; put '%else %do;'; put '%put Directory created: &dir;'; put '%end;'; put '%end;'; put '/* exit quietly if directory did exist.*/'; put '%mend mf_mkdir;'; put '%macro mddl_sas_cntlout(libds=WORK.CNTLOUT);'; put 'proc sql;'; put 'create table &libds('; put 'TYPE char(1) label='; put '''Format Type: either N (num fmt), C (char fmt), I (num infmt) or J (char infmt)'''; put ',FMTNAME char(32) label=''Format name'''; put ',FMTROW num label='; put '''CALCULATED Position of record by FMTNAME (reqd for multilabel formats)'''; put ',START char(32767) label=''Starting value for format'''; put '/*'; put 'Keep lengths of START and END the same to avoid this err:'; put '"Start is greater than end: -<."'; put 'Similar usage note: https://support.sas.com/kb/69/330.html'; put '*/'; put ',END char(32767) label=''Ending value for format'''; put ',LABEL char(32767) label=''Format value label'''; put ',MIN num length=3 label=''Minimum length'''; put ',MAX num length=3 label=''Maximum length'''; put ',DEFAULT num length=3 label=''Default length'''; put ',LENGTH num length=3 label=''Format length'''; put ',FUZZ num label=''Fuzz value'''; put ',PREFIX char(2) label=''Prefix characters'''; put ',MULT num label=''Multiplier'''; put ',FILL char(1) label=''Fill character'''; put ',NOEDIT num length=3 label=''Is picture string noedit?'''; put ',SEXCL char(1) label=''Start exclusion'''; put ',EEXCL char(1) label=''End exclusion'''; put ',HLO char(13) label='; put '''More info: https://core.sasjs.io/mddl__sas__cntlout_8sas_source.html'''; put ',DECSEP char(1) label=''Decimal separator'''; put ',DIG3SEP char(1) label=''Three-digit separator'''; put ',DATATYPE char(8) label=''Date/time/datetime?'''; put ',LANGUAGE char(8) label=''Language for date strings'''; put ');'; put '%local lib;'; put '%let libds=%upcase(&libds);'; put '%if %index(&libds,.)=0 %then %let lib=WORK;'; put '%else %let lib=%scan(&libds,1,.);'; put 'proc datasets lib=&lib noprint;'; put 'modify %scan(&libds,-1,.);'; put 'index create'; put 'pk_cntlout=(type fmtname fmtrow)'; put '/nomiss unique;'; put 'quit;'; put '%mend mddl_sas_cntlout;'; put '%macro mp_aligndecimal(var,width=8);'; put '%local tmpvar;'; put '%let tmpvar=%mf_getuniquename(prefix=aligndp);'; put 'length &tmpvar $&width;'; put 'if index(&var,''.'') then do;'; put '&tmpvar=cats(scan(&var,1,''.''));'; put '&tmpvar=right(&tmpvar);'; put '&var=&tmpvar!!''.''!!cats(scan(&var,2,''.''));'; put 'end;'; put 'else do;'; put '&tmpvar=cats(&var);'; put '&tmpvar=right(&tmpvar);'; put '&var=&tmpvar;'; put 'end;'; put 'drop &tmpvar;'; put '%mend mp_aligndecimal;'; put '%macro mp_cntlout('; put 'iftrue=(1=1)'; put ',libcat='; put ',cntlout=work.fmtextract'; put ',fmtlist=0'; put ')/*/STORE SOURCE*/;'; put '%local ddlds cntlds i;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%let ddlds=%mf_getuniquename();'; put '%let cntlds=%mf_getuniquename();'; put '%mddl_sas_cntlout(libds=&ddlds)'; put '%if %index(&libcat,-)>0 and %scan(&libcat,2,-)=FC %then %do;'; put '%let libcat=%scan(&libcat,1,-);'; put '%end;'; put 'proc format lib=&libcat cntlout=&cntlds;'; put '%if "&fmtlist" ne "0" and "&fmtlist" ne "" %then %do;'; put 'select'; put '%do i=1 %to %sysfunc(countw(&fmtlist,%str( )));'; put '%scan(&fmtlist,&i,%str( ))'; put '%end;'; put ';'; put '%end;'; put 'run;'; put 'data &cntlout/nonote2err;'; put 'if 0 then set &ddlds;'; put 'set &cntlds;'; put 'by type fmtname notsorted;'; put '/* align the numeric values to avoid overlapping ranges */'; put 'if type in ("I","N") then do;'; put '%mp_aligndecimal(start,width=16)'; put '%mp_aligndecimal(end,width=16)'; put 'end;'; put '/* create row marker. Data cannot be sorted without it! */'; put 'if first.fmtname then fmtrow=1;'; put 'else fmtrow+1;'; put 'run;'; put 'proc sort;'; put 'by type fmtname fmtrow;'; put 'run;'; put 'proc sql;'; put 'drop table &ddlds,&cntlds;'; put '%mend mp_cntlout;'; put '/** @endcond */'; put '%macro mf_getvarlist(libds'; put ',dlm=%str( )'; put ',quote=no'; put ',typefilter=A'; put ')/*/STORE SOURCE*/;'; put '/* declare local vars */'; put '%local outvar dsid nvars x rc dlm q var vtype;'; put '/* credit Rowland Hale - byte34 is double quote, 39 is single quote */'; put '%if %upcase("e)=DOUBLE %then %let q=%qsysfunc(byte(34));'; put '%else %if %upcase("e)=SINGLE %then %let q=%qsysfunc(byte(39));'; put '/* open dataset in macro */'; put '%let dsid=%sysfunc(open(&libds));'; put '%if &dsid %then %do;'; put '%let nvars=%sysfunc(attrn(&dsid,NVARS));'; put '%if &nvars>0 %then %do;'; put '/* add variables with supplied delimeter */'; put '%do x=1 %to &nvars;'; put '/* get variable type */'; put '%let vtype=%sysfunc(vartype(&dsid,&x));'; put '%if &vtype=&typefilter or &typefilter=A %then %do;'; put '%let var=&q.%sysfunc(varname(&dsid,&x))&q.;'; put '%if &var=&q&q %then %do;'; put '%put &sysmacroname: Empty column found in &libds!;'; put '%let var=&q. &q.;'; put '%end;'; put '%if %quote(&outvar)=%quote() %then %let outvar=&var;'; put '%else %let outvar=&outvar.&dlm.&var.;'; put '%end;'; put '%end;'; put '%end;'; put '%let rc=%sysfunc(close(&dsid));'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: Unable to open &libds (rc=&dsid);'; put '%put &sysmacroname: SYSMSG= %sysfunc(sysmsg());'; put '%let rc=%sysfunc(close(&dsid));'; put '%end;'; put '%do;%unquote(&outvar)%end;'; put '%mend mf_getvarlist;'; put '%macro mf_wordsInStr1ButNotStr2('; put 'Str1= /* string containing words to extract */'; put ',Str2= /* used to compare with the extract string */'; put ')/*/STORE SOURCE*/;'; put '%local count_base count_extr i i2 extr_word base_word match outvar;'; put '%if %length(&str1)=0 or %length(&str2)=0 %then %do;'; put '%put base string (str1)= &str1;'; put '%put compare string (str2) = &str2;'; put '%return;'; put '%end;'; put '%let count_base=%sysfunc(countw(&Str2));'; put '%let count_extr=%sysfunc(countw(&Str1));'; put '%do i=1 %to &count_extr;'; put '%let extr_word=%scan(&Str1,&i,%str( ));'; put '%let match=0;'; put '%do i2=1 %to &count_base;'; put '%let base_word=%scan(&Str2,&i2,%str( ));'; put '%if &extr_word=&base_word %then %let match=1;'; put '%end;'; put '%if &match=0 %then %let outvar=&outvar &extr_word;'; put '%end;'; put '&outvar'; put '%mend mf_wordsInStr1ButNotStr2;'; put '%macro mf_isblank(param'; put ')/*/STORE SOURCE*/;'; put '%sysevalf(%superq(param)=,boolean)'; put '%mend mf_isblank;'; put '%macro mp_dropmembers('; put 'list /* space separated list of datasets / views */'; put ',libref=WORK /* can only drop from a single library at a time */'; put ',iftrue=%str(1=1)'; put ')/*/STORE SOURCE*/;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%if %mf_isblank(&list) %then %do;'; put '%put NOTE: nothing to drop!;'; put '%return;'; put '%end;'; put 'proc datasets lib=&libref nolist;'; put 'delete &list;'; put 'delete &list /mtype=view;'; put 'run;'; put '%mend mp_dropmembers;'; put '%macro mp_dirlist(path=%sysfunc(pathname(work))'; put ', fref=0'; put ', outds=work.mp_dirlist'; put ', getattrs=NO'; put ', showparent=NO'; put ', maxdepth=0'; put ', level=0 /* The level of recursion to perform. For internal use only. */'; put ')/*/STORE SOURCE*/;'; put '%let getattrs=%upcase(&getattrs)XX;'; put '/* temp table */'; put '%local out_ds;'; put 'data;run;'; put '%let out_ds=%str(&syslast);'; put '/* drop main (top) table if it exists */'; put '%if &level=0 %then %do;'; put '%mp_dropmembers(%scan(&outds,-1,.), libref=WORK)'; put '%end;'; put 'data &out_ds(compress=no'; put 'keep=file_or_folder filepath filename ext msg directory level'; put ');'; put 'length directory filepath $500 fref fref2 $8 file_or_folder $6 filename $80'; put 'ext $20 msg $200 foption $16;'; put 'if _n_=1 then call missing(of _all_);'; put 'retain level &level;'; put '%if &fref=0 %then %do;'; put 'rc = filename(fref, "&path");'; put '%end;'; put '%else %do;'; put 'fref="&fref";'; put 'rc=0;'; put '%end;'; put 'if rc = 0 then do;'; put 'did = dopen(fref);'; put 'if did=0 then do;'; put 'putlog "NOTE: This directory is empty, or does not exist - &path";'; put 'msg=sysmsg();'; put 'put (_all_)(=);'; put 'stop;'; put 'end;'; put '/* attribute is OS-dependent - could be "Directory" or "Directory Name" */'; put 'numopts=doptnum(did);'; put 'do i=1 to numopts;'; put 'foption=doptname(did,i);'; put 'if foption=:''Directory'' then i=numopts;'; put 'end;'; put 'directory=dinfo(did,foption);'; put 'rc = filename(fref);'; put 'end;'; put 'else do;'; put 'msg=sysmsg();'; put 'put _all_;'; put 'stop;'; put 'end;'; put 'dnum = dnum(did);'; put 'do i = 1 to dnum;'; put 'filename = dread(did, i);'; put 'filepath=cats(directory,''/'',filename);'; put 'rc = filename(fref2,filepath);'; put 'midd=dopen(fref2);'; put 'dmsg=sysmsg();'; put 'if did > 0 then file_or_folder=''folder'';'; put 'rc=dclose(midd);'; put 'midf=fopen(fref2);'; put 'fmsg=sysmsg();'; put 'if midf > 0 then file_or_folder=''file'';'; put 'rc=fclose(midf);'; put 'if index(fmsg,''File is in use'') or index(dmsg,''is not a directory'')'; put 'then file_or_folder=''file'';'; put 'else if index(fmsg,''Insufficient authorization'') then file_or_folder=''file'';'; put 'else if file_or_folder='''' then file_or_folder=''locked'';'; put 'if file_or_folder=''file'' then do;'; put 'ext = prxchange(''s/.*\.{1,1}(.*)/$1/'', 1, filename);'; put 'if filename = ext then ext = '' '';'; put 'end;'; put 'else do;'; put 'ext='''';'; put 'file_or_folder=''folder'';'; put 'end;'; put 'output;'; put 'end;'; put 'rc = dclose(did);'; put '%if &showparent=YES and &level=0 %then %do;'; put 'filepath=directory;'; put 'file_or_folder=''folder'';'; put 'ext='''';'; put 'filename=scan(directory,-1,''/\'');'; put 'msg='''';'; put 'level=&level;'; put 'output;'; put '%end;'; put 'stop;'; put 'run;'; put '%if %substr(&getattrs,1,1)=Y %then %do;'; put 'data &out_ds;'; put 'set &out_ds;'; put 'length infoname infoval $60 fref $8;'; put 'if _n_=1 then call missing(fref);'; put 'rc=filename(fref,filepath);'; put 'drop rc infoname fid i close fref;'; put 'if file_or_folder=''file'' then do;'; put 'fid=fopen(fref);'; put 'if fid le 0 then do;'; put 'msg=sysmsg();'; put 'putlog "Could not open file:" filepath fid= ;'; put 'sasname=''_MCNOTVALID_'';'; put 'output;'; put 'end;'; put 'else do i=1 to foptnum(fid);'; put 'infoname=foptname(fid,i);'; put 'infoval=finfo(fid,infoname);'; put 'sasname=compress(infoname, ''_'', ''adik'');'; put 'if anydigit(sasname)=1 then sasname=substr(sasname,anyalpha(sasname));'; put 'if upcase(sasname) ne ''FILENAME'' then output;'; put 'end;'; put 'close=fclose(fid);'; put 'end;'; put 'else do;'; put 'fid=dopen(fref);'; put 'if fid le 0 then do;'; put 'msg=sysmsg();'; put 'putlog "Could not open folder:" filepath fid= ;'; put 'sasname=''_MCNOTVALID_'';'; put 'output;'; put 'end;'; put 'else do i=1 to doptnum(fid);'; put 'infoname=doptname(fid,i);'; put 'infoval=dinfo(fid,infoname);'; put 'sasname=compress(infoname, ''_'', ''adik'');'; put 'if anydigit(sasname)=1 then sasname=substr(sasname,anyalpha(sasname));'; put 'if upcase(sasname) ne ''FILENAME'' then output;'; put 'end;'; put 'close=dclose(fid);'; put 'end;'; put 'run;'; put 'proc sort;'; put 'by filepath sasname;'; put 'proc transpose data=&out_ds out=&out_ds(drop=_:);'; put 'id sasname;'; put 'var infoval;'; put 'by filepath file_or_folder filename ext ;'; put 'run;'; put '%end;'; put 'data &out_ds;'; put 'set &out_ds(where=(filepath ne ''''));'; put 'run;'; put '/**'; put '* The above transpose can mean that some updates create additional columns.'; put '* This necessitates the occasional use of datastep over proc append.'; put '*/'; put '%if %mf_existds(&outds) %then %do;'; put '%local basevars appvars newvars;'; put '%let basevars=%mf_getvarlist(&outds);'; put '%let appvars=%mf_getvarlist(&out_ds);'; put '%let newvars=%length(%mf_wordsinstr1butnotstr2(Str1=&appvars,Str2=&basevars));'; put '%if &newvars>0 %then %do;'; put 'data &outds;'; put 'set &outds &out_ds;'; put 'run;'; put '%end;'; put '%else %do;'; put 'proc append base=&outds data=&out_ds force nowarn;'; put 'run;'; put '%end;'; put '%end;'; put '%else %do;'; put 'proc append base=&outds data=&out_ds;'; put 'run;'; put '%end;'; put '/* recursive call */'; put '%if &maxdepth>&level or &maxdepth=MAX %then %do;'; put 'data _null_;'; put 'set &out_ds;'; put 'where file_or_folder=''folder'';'; put '%if &showparent=YES and &level=0 %then %do;'; put 'if filepath ne directory;'; put '%end;'; put 'length code $10000;'; put 'code=cats(''%nrstr(%mp_dirlist(path='',filepath,",outds=&outds"'; put ',",getattrs=&getattrs,level=%eval(&level+1),maxdepth=&maxdepth))");'; put 'put code=;'; put 'call execute(code);'; put 'run;'; put '%end;'; put '/* tidy up */'; put 'proc sql;'; put 'drop table &out_ds;'; put '%mend mp_dirlist;'; put '%macro mf_getattrc('; put 'libds'; put ',attr'; put ')/*/STORE SOURCE*/;'; put '%local dsid rc;'; put '%let dsid=%sysfunc(open(&libds,is));'; put '%if &dsid = 0 %then %do;'; put '%put %str(WARN)ING: Cannot open %trim(&libds), system message below;'; put '%put %sysfunc(sysmsg());'; put '-1'; put '%end;'; put '%else %do;'; put '%sysfunc(attrc(&dsid,&attr))'; put '%let rc=%sysfunc(close(&dsid));'; put '%end;'; put '%mend mf_getattrc;'; put '%macro mp_lockfilecheck('; put 'libds'; put ')/*/STORE SOURCE*/;'; put 'data _null_;'; put 'if _n_=1 then putlog "&sysmacroname entry vars:";'; put 'set sashelp.vmacro;'; put 'where scope="&sysmacroname";'; put 'put name ''='' value;'; put 'run;'; put '%mp_abort(iftrue= (&syscc>0)'; put ',mac=checklock.sas'; put ',msg=Aborting with syscc=&syscc on entry.'; put ')'; put '%mp_abort(iftrue= ("&libds"="0")'; put ',mac=&sysmacroname'; put ',msg=%str(libds not provided)'; put ')'; put '%local msg lib ds;'; put '%let lib=%upcase(%scan(&libds,1,.));'; put '%let ds=%upcase(%scan(&libds,2,.));'; put '/* in DC, format catalogs are passed with a -FC suffix. No saslock here! */'; put '%if %scan(&libds,2,-)=FC %then %do;'; put '%put &sysmacroname: Format Catalog detected, no lockfile applied to &libds;'; put '%return;'; put '%end;'; put '/* do not proceed if no observations can be processed */'; put '%let msg=options obs = 0. syserrortext=%superq(syserrortext);'; put '%mp_abort(iftrue= (%sysfunc(getoption(OBS))=0)'; put ',mac=checklock.sas'; put ',msg=%superq(msg)'; put ')'; put 'data _null_;'; put 'putlog "Checking engine & member type";'; put 'run;'; put '%local engine memtype;'; put '%let memtype=%mf_getattrc(&libds,MTYPE);'; put '%let engine=%mf_getattrc(&libds,ENGINE);'; put '%if &engine ne V9 and &engine ne BASE %then %do;'; put 'data _null_;'; put 'putlog "Lib &lib is not assigned using BASE engine - uses &engine instead";'; put 'putlog "SAS lock check will not be performed";'; put 'run;'; put '%return;'; put '%end;'; put '%else %if &memtype ne DATA %then %do;'; put '%put NOTE: Cannot lock a VIEW!! Memtype=&memtype;'; put '%return;'; put '%end;'; put 'data _null_;'; put 'putlog "Engine = &engine, memtype=&memtype";'; put 'putlog "Attempting lock statement";'; put 'run;'; put 'lock &libds;'; put '%local abortme;'; put '%let abortme=0;'; put '%if &syscc>0 or &SYSLCKRC ne 0 %then %do;'; put '%let msg=Unable to apply lock on &libds (SYSLCKRC=&SYSLCKRC syscc=&syscc);'; put '%put %str(ERR)OR: &sysmacroname: &msg;'; put '%let abortme=1;'; put '%end;'; put 'lock &libds clear;'; put '%mp_abort(iftrue= (&abortme=1)'; put ',mac=&sysmacroname'; put ',msg=%superq(msg)'; put ')'; put '%mend mp_lockfilecheck;'; put '%macro mp_lockanytable('; put 'action'; put ',lib= WORK'; put ',ds=0'; put ',ref='; put ',ctl_ds=0'; put ',loops=25'; put ',loop_secs=1'; put ');'; put 'data _null_;'; put 'if _n_=1 then putlog "&sysmacroname entry vars:";'; put 'set sashelp.vmacro;'; put 'where scope="&sysmacroname";'; put 'put name ''='' value;'; put 'run;'; put '%mp_abort(iftrue= ("&ds"="0" and &action ne MAKETABLE)'; put ',mac=&sysmacroname'; put ',msg=%str(dataset was not provided)'; put ')'; put '%mp_abort(iftrue= (&ctl_ds=0)'; put ',mac=&sysmacroname'; put ',msg=%str(Control dataset was not provided)'; put ')'; put '/* set up lib & mac vars */'; put '%let lib=%upcase(&lib);'; put '%let ds=%upcase(&ds);'; put '%let action=%upcase(&action);'; put '%local user x trans msg abortme;'; put '%let user=%mf_getuser();'; put '%let abortme=0;'; put '%mp_abort(iftrue= (&action ne LOCK & &action ne UNLOCK & &action ne MAKETABLE)'; put ',mac=&sysmacroname'; put ',msg=%str(Invalid action (&action) provided)'; put ')'; put '/* if an err condition exists, exit before we even begin */'; put '%mp_abort(iftrue= (&syscc>0 and &action=LOCK)'; put ',mac=&sysmacroname'; put ',msg=%str(aborting due to syscc=&syscc on LOCK entry)'; put ')'; put '/* do not bother locking work tables (else may affect all WORK libraries) */'; put '%if (%upcase(&lib)=WORK or %str(&lib)=%str()) & &action ne MAKETABLE %then %do;'; put '%put NOTE: WORK libraries will not be registered in the locking system.;'; put '%return;'; put '%end;'; put '/* do not proceed if no observations can be processed */'; put '%mp_abort(iftrue= (%sysfunc(getoption(OBS))=0)'; put ',mac=&sysmacroname'; put ',msg=%str(cannot continue when options obs = 0)'; put ')'; put '%if &ACTION=LOCK %then %do;'; put '/* abort if a SAS lock is already in place, or cannot be applied */'; put '%mp_lockfilecheck(&lib..&ds)'; put '/* next, check there is a record for this table */'; put '%local record_exists_check;'; put 'proc sql noprint;'; put 'select count(*) into: record_exists_check from &ctl_ds'; put 'where LOCK_LIB ="&lib" and LOCK_DS="&ds";'; put 'quit;'; put '%if &syscc>0 %then %put syscc=&syscc sqlrc=&sqlrc;'; put '%if &record_exists_check=0 %then %do;'; put 'data _null_;'; put 'putlog "&sysmacroname: adding record to lock table..";'; put 'run;'; put 'data ;'; put 'if 0 then set &ctl_ds;'; put 'LOCK_LIB ="&lib";'; put 'LOCK_DS="&ds";'; put 'LOCK_STATUS_CD=''LOCKED'';'; put 'LOCK_START_DTTM="%sysfunc(datetime(),%mf_fmtdttm())"dt;'; put 'LOCK_USER_NM="&user";'; put 'LOCK_PID="&sysjobid";'; put 'LOCK_REF="&ref";'; put 'output;stop;'; put 'run;'; put '%let trans=&syslast;'; put 'proc append base=&ctl_ds data=&trans;'; put 'run;'; put '%end;'; put '/* if record does exist, perform lock attempts */'; put '%else %do x=1 %to &loops;'; put 'data _null_;'; put 'putlog "&sysmacroname: attempting lock (iteration &x) "@;'; put 'putlog "at %sysfunc(datetime(),datetime19.) ..";'; put 'run;'; put 'proc sql;'; put 'update &ctl_ds'; put 'set LOCK_STATUS_CD=''LOCKED'''; put ', LOCK_START_DTTM="%sysfunc(datetime(),%mf_fmtdttm())"dt'; put ', LOCK_USER_NM="&user"'; put ', LOCK_PID="&sysjobid"'; put ', LOCK_REF="&ref"'; put 'where LOCK_LIB ="&lib" and LOCK_DS="&ds";'; put 'quit;'; put '/**'; put '* NOTE - occasionally SQL server will return an err code (deadlocked'; put '* transaction). If so, ignore it, keep calm, and carry on..'; put '*/'; put '%if &syscc>0 %then %do;'; put 'data _null_;'; put 'putlog ''NOTE-'' / ''NOTE-'';'; put 'putlog "NOTE- &sysmacroname: Update failed. "@;'; put 'putlog "Resetting err conditions and re-attempting.";'; put 'putlog "NOTE- syscc=&syscc syserr=&syserr sqlrc=&sqlrc";'; put 'putlog ''NOTE-'' / ''NOTE-'';'; put 'run;'; put '%let syscc=0;'; put '%let sqlrc=0;'; put '%end;'; put '/* now check if the record was successfully updated */'; put '%local success_check;'; put 'proc sql noprint;'; put 'select count(*) into: success_check from &ctl_ds'; put 'where LOCK_LIB ="&lib" and LOCK_DS="&ds"'; put 'and LOCK_PID="&sysjobid" and LOCK_STATUS_CD=''LOCKED'';'; put 'quit;'; put '%if &success_check=0 %then %do;'; put '%if &x < &loops %then %do;'; put '/* pause before next check */'; put 'data _null_;'; put 'putlog ''NOTE-'' / ''NOTE-'';'; put 'putlog "NOTE- &sysmacroname: table locked, waiting "@;'; put 'putlog "%sysfunc(sleep(&loop_secs)) seconds.. ";'; put 'putlog "NOTE- (iteration &x of &loops)";'; put 'putlog ''NOTE-'' / ''NOTE-'';'; put 'run;'; put '%end;'; put '%else %do;'; put '%let msg=Unable to lock &lib..&ds via &ctl_ds after &loops attempts.\n'; put 'Please ask your administrator to investigate!;'; put '%let abortme=1;'; put '%end;'; put '%end;'; put '%else %do;'; put 'data _null_;'; put 'putlog ''NOTE-'' / ''NOTE-'';'; put 'putlog "NOTE- &sysmacroname: Table &lib..&ds locked at "@;'; put 'putlog " %sysfunc(datetime(),datetime19.) (iteration &x)"@;'; put 'putlog ''NOTE-'' / ''NOTE-'';'; put 'run;'; put '%if &syscc>0 %then %do;'; put '%put setting syscc(&syscc) back to 0;'; put '%let syscc=0;'; put '%end;'; put '%let x=&loops; /* no more iterations needed */'; put '%end;'; put '%end;'; put '%end;'; put '%else %if &ACTION=UNLOCK %then %do;'; put '%local status cnt;'; put '%let cnt=0;'; put 'proc sql noprint;'; put 'select count(*) into: cnt from &ctl_ds where LOCK_LIB ="&lib" & LOCK_DS="&ds";'; put '%if &cnt=0 %then %do;'; put '%put %str(WAR)NING: &lib..&ds was not previously locked in &ctl_ds!;'; put '%end;'; put '%else %do;'; put 'select LOCK_STATUS_CD into: status from &ctl_ds'; put 'where LOCK_LIB ="&lib" and LOCK_DS="&ds";'; put 'quit;'; put '%if &syscc>0 %then %put syscc=&syscc sqlrc=&sqlrc;'; put '%if &status=LOCKED %then %do;'; put 'data _null_;'; put 'putlog "&sysmacroname: unlocking &lib..&ds:";'; put 'run;'; put 'proc sql;'; put 'update &ctl_ds'; put 'set LOCK_STATUS_CD=''UNLOCKED'''; put ', LOCK_END_DTTM="%sysfunc(datetime(),%mf_fmtdttm())"dt'; put ', LOCK_USER_NM="&user"'; put ', LOCK_PID="&sysjobid"'; put ', LOCK_REF="&ref"'; put 'where LOCK_LIB ="&lib" and LOCK_DS="&ds";'; put 'quit;'; put '%end;'; put '%else %if &status=UNLOCKED %then %do;'; put '%put %str(WAR)NING: &lib..&ds is already unlocked!;'; put '%end;'; put '%else %do;'; put '%put NOTE: Unrecognised STATUS_CD (&status) in &ctl_ds;'; put '%let abortme=1;'; put '%end;'; put '%end;'; put '%end;'; put '%else %do;'; put '%let msg=lock_anytable given unsupported action (&action);'; put '%let abortme=1;'; put '%end;'; put '/* catch errs - mp_abort must be called outside of a logic block */'; put '%mp_abort(iftrue=(&abortme=1),'; put 'msg=%superq(msg),'; put 'mac=&sysmacroname'; put ')'; put '%exit_macro:'; put 'data _null_;'; put 'put "&sysmacroname: Exit vars: action=&action lib=&lib ds=&ds";'; put 'put " syscc=&syscc sqlrc=&sqlrc syserr=&syserr";'; put 'run;'; put '%mend mp_lockanytable;'; put '%macro mpe_loader('; put 'mperef= /* name of subfolder containing the staged data */'; put ',mDebug=0 /* set to 1 for development or debugging */'; put ',submitted_reason_txt= /* populates column of same name in sumo_approvals*/'; put ',approver= /* allows a userid to be provided for direct approval email */'; put ',url= /* optional - url for debugging */'; put ',dlm=%str(,)'; put ',termstr=crlf'; put ',dc_dttmtfmt=E8601DT26.6'; put ');'; put '%put entered mpe_loader from &=_program;'; put '%put &=url;'; put '%put &=termstr;'; put '%put &=dlm;'; put '/* determine full path to CSV directory */'; put '%local now;'; put '%let now=&dc_dttmtfmt;'; put '%put &=now;'; put '/**'; put '* get full path to package (only subdirectory passed through)'; put '*/'; put '%mp_abort('; put 'iftrue=(%mf_verifymacvars(mperef mpelocapprovals)=0)'; put ',mac=bitemporal_dataloader'; put ',msg=%str(Missing: mperef mpelocapprovals)'; put ')'; put '%let csv_dir=%trim(&mpelocapprovals/&mperef);'; put '/* exit if package has already been uploaded */'; put '%local check;'; put 'proc sql noprint;'; put 'select count(*) into: check'; put 'from &mpelib..mpe_loads'; put 'where csv_dir="&mperef";'; put '%if &check %then %do;'; put '%mp_abort(msg=Folder &mperef already has an entry in &mpelib..mpe_loads'; put ',mac=mpe_loader.sas);'; put '%return;'; put '%end;'; put '/* get CSV directory contents */'; put '%mp_dirlist(path=&csv_dir,outds=WORK.getfiles)'; put 'data WORK.csvs;'; put 'set WORK.getfiles;'; put 'if upcase(scan(filename,3,''.''))=''CSV'' then do;'; put 'lib=upcase(scan(filename,1,''.''));'; put 'ds=upcase(scan(filename,2,''.''));'; put 'output;'; put 'end;'; put 'run;'; put '/* get table attributes */'; put 'proc sql noprint;'; put 'create table WORK.sumo_tables as'; put 'select a.filename, b.*'; put 'from WORK.csvs a'; put 'left join &mpelib..mpe_tables b'; put 'on a.lib=b.libref'; put 'and a.ds=b.dsn'; put 'where b.tx_from le &now'; put 'and &now lt b.tx_to;'; put '/* define user as meta user if available */'; put '%local user;'; put '%let user=%mf_getuser();'; put '/* check if there is actually a table to load */'; put '%if %mf_getattrn(WORK.sumo_tables,NLOBS)=0 %then %do;'; put '%let msg=Table not registered in &mpelib..mpe_tables;'; put '%mpe_loadfail('; put 'status=&msg'; put ',now=&now'; put ',mperef=&mperef'; put ',dc_dttmtfmt=&dc_dttmtfmt.'; put ')'; put '%mp_abort(msg=&msg,mac=mpe_loader.sas);'; put '%return;'; put '%end;'; put 'proc sql;'; put 'insert into &mpelib..mpe_loads'; put 'set USER_NM="&user"'; put ',STATUS=''IN PROGRESS'''; put ',CSV_dir="&mperef"'; put ',PROCESSED_DTTM=&now;'; put '/* import CSV */'; put '%let droplist=;'; put '%let attrib=;'; put '%let droplist=;'; put '%let libref=;'; put '%let DS=;'; put '/* get table info */'; put 'data _null_;'; put 'set sumo_tables;'; put 'libds=upcase(cats(libref,''.'',dsn));'; put 'call symputx(''orig_libds'',libds);'; put 'is_fmt=0;'; put 'if substr(cats(reverse(dsn)),1,3)=:''CF-'' then do;'; put 'libds=scan(libds,1,''-'');'; put 'putlog "Format Catalog Captured";'; put 'libds=''work.fmtextract'';'; put 'is_fmt=1;'; put 'end;'; put 'call symputx(''is_fmt'',is_fmt);'; put 'call symputx(''libds'',libds);'; put 'call symputx(''FNAME'',filename);'; put 'call symputx(''LIBREF'',libref);'; put 'call symputx(''DS'',dsn);'; put 'call symputx(''LOADTYPE'',loadtype);'; put 'call symputx(''BUSKEY'',buskey);'; put 'call symputx(''VAR_TXFROM'',var_txfrom);'; put 'call symputx(''VAR_TXTO'',var_txto);'; put 'call symputx(''VAR_BUSFROM'',var_busfrom);'; put 'call symputx(''VAR_BUSTO'',var_busto);'; put 'call symputx(''VAR_PROCESSED'',var_processed);'; put 'call symputx(''RK_UNDERLYING'',RK_UNDERLYING);'; put 'call symputx(''POST_EDIT_HOOK'',POST_EDIT_HOOK);'; put 'call symputx(''NOTES'',NOTES);'; put 'call symputx(''PK'',coalescec(RK_UNDERLYING,buskey));'; put 'call symputx(''NUM_OF_APPROVALS_REQUIRED'',NUM_OF_APPROVALS_REQUIRED,''l'');'; put 'put (_all_)(=);'; put 'stop;'; put 'run;'; put '%if %length(&ds)=0 %then %do;'; put '%let msg=%str(ERR)OR: Unable to extract record from &mpelib..mpe_tables;'; put '%mpe_loadfail('; put 'status=FAILED'; put ',now=&now'; put ',mperef=&mperef'; put ',reason_txt=%quote(&msg)'; put ',dc_dttmtfmt=&dc_dttmtfmt.'; put ')'; put '%mp_abort(msg=&msg,mac=mpe_loader.sas);'; put '%return;'; put '%end;'; put '/* export format catalog */'; put '%mp_cntlout('; put 'iftrue=(&is_fmt=1)'; put ',libcat=&orig_libds'; put ',fmtlist=0'; put ',cntlout=work.fmtextract'; put ')'; put '/* user must have EDIT access to load a table */'; put '%mpe_accesscheck(&orig_libds'; put ',outds=work.sumo_access'; put ',user=&user'; put ',access_level=EDIT )'; put '%put exiting accesscheck;'; put '%if %mf_getattrn(work.sumo_access,NLOBS)=0 %then %do;'; put '%let msg=%str(ERR)OR: User is not authorised to edit &orig_libds!;'; put '%mpe_loadfail('; put 'status=UNAUTHORISED'; put ',now=&now'; put ',mperef=&mperef'; put ',reason_txt=%quote(&msg)'; put ',dc_dttmtfmt=&dc_dttmtfmt.'; put ')'; put '%mp_abort(msg=&msg,mac=mpe_loader.sas);'; put '%return;'; put '%end;'; put '%put now importing: "&csv_dir/&fname" termstr=&termstr;'; put '/* get the variables from the CSV */'; put 'data vars_csv1(index=(idxname=(varnum name)) drop=infile);'; put 'infile "&csv_dir/&fname" lrecl=32767 dsd termstr=&termstr encoding=''utf-8'';'; put 'input;'; put 'length infile $32767;'; put 'infile=compress(_infile_,''"'',);'; put 'infile=compress(infile,"''",);'; put 'format name $32.;'; put 'putlog ''received vars: '' infile;'; put 'call symputx(''received_vars'',infile,''l'');'; put 'do varnum=1 to countw(infile,"&dlm");'; put '/* keep writeable chars */'; put 'name=compress(upcase(scan(infile,varnum)),,''kw'');'; put 'if name ne "_____DELETE__THIS__RECORD_____" then output;'; put 'end;'; put 'stop;'; put 'run;'; put '%put received_vars = &received_vars;'; put '%dc_assignlib(WRITE,&libref)'; put '/* get list of variables and their formats */'; put 'proc contents noprint data=&libds'; put 'out=vars(keep=name type length varnum format:);'; put 'run;'; put 'data vars(keep=name type length varnum format);'; put 'set vars(rename=(format=format2 type=type2));'; put 'name=upcase(name);'; put 'format2=upcase(format2);'; put '/* not interested in transaction or processing dates'; put '(append table must be supplied without them) */'; put 'if name not in ("&VAR_TXFROM","&VAR_TXTO","&VAR_PROCESSED"'; put ',"_____DELETE__THIS__RECORD_____");'; put 'if type2 in (2,6) then do;'; put 'length format $49.;'; put 'if format2='''' then format=cats(''$'',length,''.'');'; put 'else format=cats(format2,max(formatl,length),''.'');'; put 'type=''char'';'; put 'end;'; put 'else do;'; put 'if format2='''' then format=cats(length,''.'');'; put 'else if format2=:''DATETIME'' or format2=:''E8601DT'' then do;'; put 'format=''DATETIME19.'';'; put 'end;'; put 'else if format2=:''DATE'' or format2=:''DDMMYY'''; put 'or format2=:''MMDDYY'' or format2=:''YYMMDD'''; put 'or format2=:''E8601DA'' or format2=:''B8601DA'''; put 'then do;'; put 'format=''DATE9.'';'; put 'end;'; put 'else if format2=''BEST'' & formatl=0 then format=cats(''BEST'',length,''.'');'; put '/*'; put 'else if format2=:''DATETIME'' or format2=:''DATE'' or format2=:''DDMMYY'''; put 'or format2=:''MMDDYY'' or format2=:''YYMMDD'' then do;'; put '*date or datetime format so use original ;'; put 'dsid=open("&libref..&ds");'; put 'vnum=varnum(dsid,name);'; put 'format=varfmt(dsid,vnum);'; put 'dsid=close(dsid);'; put 'end;'; put '*/'; put 'else do;'; put 'if formatl=0 then formatl=length;'; put 'format=cats(format2,formatl,''.'',formatd);'; put 'end;'; put 'type=''num'';'; put 'end;'; put 'put (_all_)(=);'; put 'run;'; put '/* build attrib statement */'; put 'data vars_attrib;'; put 'length attrib_statement $32767 type2 $20;'; put 'set vars end=lastobs;'; put 'retain attrib_statement;'; put 'if type=''char'' then type2=''$'';'; put 'str1=catx('' '',name,''length='',cats(type2,length));'; put 'attrib_statement=trim(attrib_statement)!!'' ''!!trim(str1);'; put 'if lastobs then call symputx(''ATTRIB'',attrib_statement,''L'');'; put 'run;'; put '/* build input statement - first get vars in right order'; put 'and join with target formats*/'; put 'proc sql noprint;'; put 'create table vars_csv2 as'; put 'select b.*'; put 'from vars_csv1 a'; put 'left join vars_attrib b'; put 'on a.name=b.name'; put 'order by a.varnum;'; put '/* make sure that the variables we are importing, actually'; put 'exist on the target table */'; put '/** edit - extra variables are now simply ignored'; put '%local very_bad_vars;'; put 'select name into: very_bad_vars separated by '' '''; put 'from vars_csv1'; put 'where name not in (select name from vars)'; put 'and name ne "_____DELETE__THIS__RECORD_____";'; put '%if %length(&very_bad_vars) > 0 %then %do;'; put '%let msg=%str(WARNING: The following vars are not defined in %trim('; put ')&libref..&ds, yet they exist in &csv_dir/&ds..csv: &very_bad_vars);'; put '%mpe_loadfail('; put 'status=FAILED'; put ',now=&now'; put ',mperef=&mperef'; put ',reason_txt=%quote(&msg)'; put ',dc_dttmtfmt=&dc_dttmtfmt.'; put ')'; put '%return;'; put '%end;'; put '**/'; put '/* now build input statement */'; put 'data final_check;'; put 'set vars_csv2 end=lastobs;'; put 'length input_statement $32767 type2 $20 droplist $32767;'; put 'retain input_statement droplist;'; put '/* Build input statement - CATCH EXCEPTIONS HERE!*/'; put 'if name in (''QUOTE_DTTM'') then do;'; put 'name=cats(name,''2'');'; put 'droplist=catx('' '',trim(droplist),name);'; put 'type2=''$20.'';/* converted below */'; put 'end;'; put 'else if type=''char'' then type2=cats(''$CHAR'', length,''.'');'; put 'else if format=''DATE9.'' then type2=''ANYDTDTE.'';'; put 'else if format=''DATETIME19.'' then type2=''ANYDTDTM.'';'; put 'else if format=:''TIME'' then type2=''ANYDTTME.'';'; put 'else if name='''' then do;/* additional vars in input data */'; put 'name=''_____DELETE__THIS__VARIABLE_____'';'; put 'droplist=catx('' '',trim(droplist),''_____DELETE__THIS__VARIABLE_____'');'; put 'type2=''$1.'';'; put 'end;'; put 'else type2=''best32.'';'; put '* else type2=cats(length,''.'');'; put 'input_statement=catx('' '',input_statement,name,'':'',type2);'; put 'if lastobs then do;'; put 'call symputx(''INPUT'', input_statement,''L'');'; put 'if trim(droplist) ne '''' then'; put 'call symputx(''droplist'',"drop "!!droplist!!'';'',''l'');'; put 'end;'; put 'run;'; put '%let mpeloadstop=0;'; put 'data work.STAGING_DS;'; put '&droplist;'; put 'infile "&csv_dir/&fname" dsd dlm="&dlm" lrecl=32767'; put 'firstobs=2 missover termstr=&termstr encoding=''utf-8'';'; put 'attrib &attrib ;'; put 'if _n_=1 then call missing (of _all_);'; put 'missing a b c d e f g h i j k l m n o p q r s t u v w x y z _;'; put 'input'; put '%if %scan(%quote(&received_vars),1)=_____DELETE__THIS__RECORD_____ %then %do;'; put '_____DELETE__THIS__RECORD_____: $3.'; put '%end;'; put '&input;'; put '%if %index(%quote(&attrib.),UNLIKELY_VAR ) %then %do;'; put '/*UNLIKELY_VAR=input(UNLIKELY_VAR2,ANYDTDTM21.);*/'; put '/* SPECIAL LOGIC FOR SPECIAL VARS */'; put '%end;'; put 'if _error_ ne 0 then do;'; put 'putlog _infile_;'; put 'call symputx(''mpeloadstop'',_n_);'; put 'stop;'; put 'end;'; put '/* remove all blank rows */'; put 'if compress(cats(of _all_),''.'')='' '' then delete;'; put 'run;'; put '%if &mpeloadstop>0 %then %do;'; put '%if %symexist(SYSPRINTTOLOG) %then %let logloc=&SYSPRINTTOLOG;'; put '%else %let logloc=%qsysfunc(getoption(LOG));'; put '%put redirecting log output to capture return message;'; put '%put currentloc=&logloc;'; put 'filename tmp temp;'; put 'proc printto log=tmp;run;'; put 'data _null_;'; put '&droplist;'; put 'infile "&csv_dir/&fname" dsd dlm="&dlm" lrecl=32767 firstobs=2'; put 'missover termstr=&termstr;'; put 'attrib &attrib ;'; put 'input'; put '%if %scan(%quote(&received_vars),1)=_____DELETE__THIS__RECORD_____'; put '%then %do;'; put '_____DELETE__THIS__RECORD_____: $3.'; put '%end;'; put '&input;'; put 'if _error_ then stop;'; put 'run;'; put '/* get log back */'; put 'proc printto log=&logloc;run;'; put 'data _null_; infile tmp; input; putlog _infile_;run;'; put '/* scan log for invalid data warning */'; put 'data _null_;'; put 'infile tmp;'; put 'input;'; put 'length msg1 msg2 msg3 msg4 msg5 msg url $32767;'; put 'if index(_infile_,''NOTE: Invalid data for'') then do;'; put 'msg1=_infile_;'; put 'input;'; put 'msg2=_infile_;'; put 'input;'; put 'msg3=_infile_;'; put 'input;'; put 'msg4=_infile_;'; put 'input;'; put 'msg5=_infile_;'; put 'url=symget(''url'');'; put 'msg=catx(''\n'',msg1,msg2,msg3,msg4,msg5,''\n'',url);'; put 'call symputx(''msg'',msg);'; put 'stop;'; put 'end;'; put 'run;'; put '%mpe_loadfail('; put 'status=FAILED'; put ',now=&now'; put ',mperef=&mperef'; put ',reason_txt=%superq(msg)'; put ',dc_dttmtfmt=&dc_dttmtfmt.'; put ')'; put '%return;'; put '%end;'; put '/* check that the table is unique on PK */'; put 'proc sort data=work.STAGING_DS dupout=work.MPE_DUPS (keep=&pk) nodupkey;'; put 'by &pk;'; put 'run;'; put '%if %mf_getattrn(work.MPE_DUPS,NLOBS)>0 %then %do;'; put '%local duplist;'; put 'data _null_;'; put 'set work.mpe_dups;'; put '%do i=1 %to %sysfunc(countw(&pk));'; put '%let iWord=%scan(&pk,&i);'; put 'call symputx(''duplist'',symget(''duplist'')!!'; put '" &iWord="!!cats(&iWord));'; put '%end;'; put 'run;'; put '%let msg=This upload contains duplicates on the Primary Key columns %trim('; put ')(&pk) \n Please remove the duplicates and try again. %trim('; put ')\n &duplist \n ;'; put '%mp_abort(msg=%superq(msg),mac=mpe_loader.sas);'; put '%return;'; put '%end;'; put '%if &syscc gt 4 %then %do;'; put '%let msg=SYSCC=&syscc prior to post edit hook (%superq(syserrortext));'; put '%mpe_loadfail('; put 'status=FAILED - &syscc'; put ',now=&now'; put ',mperef=&mperef'; put ',reason_txt=%superq(msg)'; put ',dc_dttmtfmt=&dc_dttmtfmt.'; put ')'; put '%return;'; put '%end;'; put '/* If a Complex Excel Upload, needs to have the load ref added to the table */'; put '%mpe_xlmapvalidate(&mperef,work.staging_ds,&mpelib,&orig_libds)'; put '/* Run the Post Edit Hook prior to creation of staging folder */'; put '%mpe_runhook(POST_EDIT_HOOK)'; put '/* stop if err */'; put '%if &syscc gt 4 %then %do;'; put '%let msg=ERR in post edit hook (&post_edit_hook);'; put '%mpe_loadfail('; put 'status=FAILED - &syscc'; put ',now=&now'; put ',mperef=&mperef'; put ',reason_txt=%quote(&msg)'; put ',dc_dttmtfmt=&dc_dttmtfmt.'; put ')'; put '%return;'; put '%end;'; put '/**'; put '* send to approve process'; put '*/'; put '/* create a dataset key (datetime plus 3 digit random number plus PID) */'; put '/* send dataset to approvals subfolder with same name as subfolder */'; put 'libname approval "&mpelocapprovals/&mperef";'; put 'data approval.&mperef;'; put 'set work.staging_ds;'; put 'run;'; put 'proc export data=approval.&mperef'; put 'outfile="&mpelocapprovals/&mperef/&mperef..csv"'; put 'dbms=csv'; put 'replace;'; put 'run;'; put '/* update the control dataset with relevant info */'; put 'data append_app;'; put 'if 0 then set &mpelib..mpe_submit;/* get formats */'; put 'call missing (of _all_);'; put 'TABLE_ID="&mperef";'; put 'submit_status_cd=''SUBMITTED'';'; put 'submitted_by_nm="%mf_getuser()";'; put 'base_lib="&libref";'; put 'base_ds="&ds";'; put 'submitted_on_dttm=&now;'; put 'submitted_reason_txt=symget(''submitted_reason_txt'');'; put 'input_vars=%mf_getattrn(approval.&mperef,NVARS);'; put 'input_obs=%mf_getattrn(approval.&mperef,NLOBS);'; put 'num_of_approvals_required=&NUM_OF_APPROVALS_REQUIRED;'; put 'num_of_approvals_remaining=&NUM_OF_APPROVALS_REQUIRED;'; put 'reviewed_by_nm='''';'; put 'reviewed_on_dttm=.;'; put 'run;'; put '%mp_lockanytable(LOCK,lib=&mpelib,ds=mpe_submit,'; put 'ref=%str(&mperef update in &_program),'; put 'ctl_ds=&mpelib..mpe_lockanytable'; put ')'; put 'proc append base= &mpelib..mpe_submit data=append_app;'; put 'run;'; put '%mp_lockanytable(UNLOCK,'; put 'lib=&mpelib,ds=mpe_submit,'; put 'ctl_ds=&mpelib..mpe_lockanytable'; put ')'; put '/* send email to REVIEW members */'; put '%put sending mpe_alerts;'; put '%mpe_alerts(alert_event=SUBMITTED'; put ', alert_lib=&libref'; put ', alert_ds=&ds'; put ', dsid=&mperef'; put ')'; put '/* DISABLE EMAIL FOR NOW'; put '%let b2=REASON: %quote(&submitted_reason_txt);'; put '%local URLNOTES;'; put '%if %length(¬es)>0 %then %let URLNOTES=%quote(%sysfunc(urlencode(¬es)));'; put '%let b3=%str(Click to review / approve: )%trim('; put ')%str(http://&_srvname:&_srvport&_url?_PROGRAM=/Web/approvals&)%trim('; put ')TABLEID=&dsid%str(&)BASETABLE=&libref..&ds%str(&)NOTES=&URLNOTES;'; put '%let b4=%str(Reference ID: &mperef);'; put '*/'; put '%put mpe_loader finishing up with syscc=&syscc;'; put '%if &syscc le 4 %then %do;'; put '%local dur;'; put 'data _null_;'; put 'now=symget(''now'');'; put 'dur=%sysfunc(datetime())-&now;'; put 'call symputx(''dur'',dur,''l'');'; put 'putlog ''Updating mpe_loads with the following query:'';'; put 'putlog "update &mpelib..mpe_loads set STATUS=''SUCCESS''";'; put 'putlog " , duration=" dur;'; put 'putlog " , processed_dttm=" now;'; put 'putlog " , approvals = ''&libref..&ds''";'; put 'putlog " where CSV_DIR=''&mperef'';";'; put 'run;'; put 'proc sql;'; put 'update &mpelib..mpe_loads set STATUS=''SUCCESS'''; put ', duration=&dur'; put ', processed_dttm=&now'; put ', approvals = "&libref..&ds"'; put 'where CSV_DIR="&mperef";'; put '%end;'; put '%else %do;'; put '%mpe_loadfail('; put 'status="FAILED - &syscc"'; put ',now=&now'; put ',approvals=&libref..&ds'; put ',mperef=&mperef'; put ',dc_dttmtfmt=&dc_dttmtfmt.'; put ')'; put '%return;'; put '%end;'; put '%mend mpe_loader;'; put '%macro mp_cleancsv(in=NOTPROVIDED,out=NOTPROVIDED,qchar=''22''x);'; put '%if "&in"="NOTPROVIDED" or "&out"="NOTPROVIDED" %then %do;'; put '%put %str(ERR)OR: Please provide valid input (&in) & output (&out) locations;'; put '%return;'; put '%end;'; put '/* presence of a period(.) indicates a physical location */'; put '%if %index(&in,.) %then %let in="&in";'; put '%if %index(&out,.) %then %let out="&out";'; put '/**'; put '* convert all cr and crlf within quotes to lf'; put '* convert all other cr or lf to crlf'; put '*/'; put 'data _null_;'; put 'infile &in recfm=n ;'; put 'file &out recfm=n;'; put 'retain isq iscrlf 0 qchar &qchar;'; put 'input inchar $char1. ;'; put 'if inchar=qchar then isq = mod(isq+1,2);'; put 'if isq then do;'; put '/* inside a quote change cr and crlf to lf */'; put 'if inchar=''0D''x then do;'; put 'put ''0A''x;'; put 'input inchar $char1.;'; put 'if inchar ne ''0A''x then do;'; put 'put inchar $char1.;'; put 'if inchar=qchar then isq = mod(isq+1,2);'; put 'end;'; put 'end;'; put 'else put inchar $char1.;'; put 'end;'; put 'else do;'; put '/* outside a quote, change cr and lf to crlf */'; put 'if inchar=''0D''x then do;'; put 'crblank:'; put 'put ''0D0A''x;'; put 'input inchar $char1.;'; put 'if inchar=''0D''x then do;'; put '/* multiple CR indicates CR formatted file with blank lines */'; put 'goto crblank;'; put 'end;'; put 'else if inchar ne ''0A''x then do;'; put 'put inchar $char1.;'; put 'if inchar=qchar then isq = mod(isq+1,2);'; put 'end;'; put 'end;'; put 'else if inchar=''0A''x then put ''0D0A''x;'; put 'else put inchar $char1.;'; put 'end;'; put 'run;'; put '%mend mp_cleancsv;'; put '/** @endcond */'; put '%macro mp_binarycopy('; put 'inloc= /* full path and filename of the object to be copied */'; put ',outloc= /* full path and filename of object to be created */'; put ',inref=____in /* override default to use own filerefs */'; put ',outref=____out /* override default to use own filerefs */'; put ',mode=CREATE'; put ',iftrue=%str(1=1)'; put ')/*/STORE SOURCE*/;'; put '%local mod;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%if &mode=APPEND %then %let mod=mod;'; put '/* these IN and OUT filerefs can point to anything */'; put '%if &inref = ____in %then %do;'; put 'filename &inref &inloc lrecl=1048576 ;'; put '%end;'; put '%if &outref=____out %then %do;'; put 'filename &outref &outloc lrecl=1048576 &mod;'; put '%end;'; put '/* copy the file byte-for-byte */'; put 'data _null_;'; put 'infile &inref lrecl=1 recfm=n;'; put 'file &outref &mod recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put '%if &inref = ____in %then %do;'; put 'filename &inref clear;'; put '%end;'; put '%if &outref=____out %then %do;'; put 'filename &outref clear;'; put '%end;'; put '%mend mp_binarycopy;'; put '* SAS Macros end;'; put '* SAS Includes start;'; put '* SAS Includes end;'; put '* Binary Files start;'; put '* Binary Files end;'; put '* ServiceInit start;'; put 'options noquotelenmax ps=max;'; put '/* create dummy macros to prevent stpbegin / stpend from corrupting sessions */'; put '%macro stpbegin();'; put '%put NOTE: the STPBEGIN macro should not be used for web apps!;'; put '%mend stpbegin;'; put '%macro stpend();'; put '%put NOTE: the STPEND macro should not be used for web apps!;'; put '%mend stpend;'; put '* ServiceInit end;'; put '* Service start;'; put '/**'; put '@file loadfile.sas'; put '@brief Loads a file'; put '@details'; put '

SAS Macros

'; put '@li mddl_sas_cntlout.sas'; put '@li mp_abort.sas'; put '@li mf_getplatform.sas'; put '@li mf_getuser.sas'; put '@li mf_getvarlist.sas'; put '@li mf_mkdir.sas'; put '@li mf_verifymacvars.sas'; put '@li mf_wordsinstr1butnotstr2.sas'; put '@li dc_assignlib.sas'; put '@li mpe_getgroups.sas'; put '@li mp_lockfilecheck.sas'; put '@li mpe_loader.sas'; put '@li mp_cleancsv.sas'; put '@li mp_binarycopy.sas'; put '@li mpeinit.sas'; put '@version 9.2'; put '@author 4GL Apps Ltd'; put '@copyright 4GL Apps Ltd. This code may only be used within Data Controller'; put 'and may not be re-distributed or re-sold without the express permission of'; put '4GL Apps Ltd.'; put '**/'; put '%global table dlm;'; put '%mpeinit(fetch=NO)'; put '%global _WEBIN_FILENAME1 _WEBIN_FILENAME2'; put '_WEBIN_FILEREF _WEBIN_FILEREF1 _WEBIN_FILEREF2;'; put '%macro load();'; put '%if %mf_getplatform()=SASVIYA %then %do;'; put '%global _webin_fileuri _webin_fileuri1 _webin_fileuri2;'; put '%let _webin_fileuri1=%sysfunc(coalescec(&_webin_fileuri1,&_webin_fileuri));'; put '%if "&_webin_fileuri1" ne "" %then %do;'; put '%put &=_webin_fileuri1;'; put 'filename sjfref1 filesrvc "&_webin_fileuri1";'; put '%let _WEBIN_FILEREF1=sjfref1;'; put '%end;'; put '%if "&_webin_fileuri2" ne "" %then %do;'; put '%put &=_webin_fileuri2;'; put 'filename sjfref2 filesrvc "&_webin_fileuri2";'; put '%let _WEBIN_FILEREF2=sjfref2;'; put '%end;'; put '%end;'; put '%mend load;'; put '%load()'; put '%let _WEBIN_FILENAME1=%sysfunc(coalescec(&_WEBIN_FILENAME1,&_WEBIN_FILENAME));'; put '%let _WEBIN_FILEREF1=%sysfunc(coalescec(&_WEBIN_FILEREF1,&_WEBIN_FILEREF));'; put '%let abort=0;'; put '/* we do not know if the excel file will be first or second fileref */'; put 'data _null_;'; put 'ext1=upcase(scan(symget(''_WEBIN_FILENAME1''),-1,''.''));'; put 'ext2=upcase(scan(symget(''_WEBIN_FILENAME2''),-1,''.''));'; put 'if ext1=''CSV'' then do;'; put 'csvname=symget(''_WEBIN_FILENAME1'');'; put 'csvref=symget(''_WEBIN_FILEREF1'');'; put 'xlsname=symget(''_WEBIN_FILENAME2'');'; put 'xlsref=symget(''_WEBIN_FILEREF2'');'; put 'end;'; put 'else if ext2=''CSV'' then do;'; put 'csvname=symget(''_WEBIN_FILENAME2'');'; put 'csvref=symget(''_WEBIN_FILEREF2'');'; put 'xlsname=symget(''_WEBIN_FILENAME1'');'; put 'xlsref=symget(''_WEBIN_FILEREF1'');'; put 'end;'; put 'else call symputx(''abort'',1);'; put 'call symputx(''csvname'',csvname);'; put 'call symputx(''csvref'',csvref);'; put 'call symputx(''xlsname'',xlsname);'; put 'call symputx(''xlsref'',coalescec(xlsref,''0''));'; put 'run;'; put '%mp_abort(iftrue= (&abort=1)'; put ',mac=&_program'; put ',msg=%str(File "&csvname" or "&xlsname" must be a CSV!'; put '(Comma separated with .csv extension))'; put ')'; put '%let user=%mf_getuser();'; put '%mp_abort('; put 'iftrue=(%mf_verifymacvars(table)=0)'; put ',mac=&_program'; put ',msg=%str(Missing: table)'; put ')'; put '%let table=%upcase(%trim(&table));'; put '/* load parameters */'; put 'data _null_;'; put 'libds=upcase(symget(''table''));'; put 'call symputx(''orig_libds'',libds);'; put 'call symputx(''orig_lib'',scan(libds,1,''.''));'; put 'call symputx(''orig_ds'',scan(libds,2,''.''));'; put 'is_fmt=0;'; put 'if substr(cats(reverse(libds)),1,3)=:''CF-'' then do;'; put 'libds=scan(libds,1,''-'');'; put 'putlog "Format Catalog Captured";'; put 'libds=''work.fmtextract'';'; put 'call symputx(''libds'',libds);'; put 'call execute(''%mddl_sas_cntlout(libds=work.fmtextract)'');'; put 'is_fmt=1;'; put 'end;'; put 'else call symputx(''libds'',libds);'; put 'call symputx(''is_fmt'',is_fmt);'; put 'putlog (_all_)(=);'; put 'run;'; put '/* check that the user has the requisite access */'; put '%mpe_getgroups(user=&user,outds=groups)'; put 'proc sql;'; put 'create table accesscheck as'; put 'select * from groups'; put 'where groupname="&mpeadmins"'; put 'or groupname in (select sas_group from &mpelib..mpe_security'; put 'where &dc_dttmtfmt. lt tx_to'; put 'and access_level="EDIT"'; put 'and ('; put '(libref="&orig_lib" and dsn="&orig_ds")'; put 'or (libref="&orig_lib" and dsn="*ALL*")'; put 'or (libref="*ALL*" and dsn="*ALL*")'; put 'or (libref="*ALL*" and dsn="&orig_ds")'; put '));'; put '%let nobs=;'; put 'select count(*) into: nobs from &syslast;'; put '%mp_abort(iftrue= (&nobs=0)'; put ',mac=&sysmacroname'; put ',msg=%str(&user not authorised to load &orig_libds per &mpelib..mpe_security)'; put ')'; put '%dc_assignlib(WRITE,&orig_lib)'; put '%mp_abort(iftrue= (&syscc ge 4)'; put ',mac=loadfile'; put ',msg=%str(Issue assigning library &orig_lib)'; put ')'; put '%global txfrom txto processed rk;'; put 'data _null_;'; put 'set &mpelib..MPE_TABLES;'; put 'where libref="&orig_lib" and dsn="&orig_ds";'; put 'call symputx(''txfrom'',var_txfrom);'; put 'call symputx(''txto'',var_txto);'; put 'call symputx(''processed'',var_processed);'; put 'if not missing(RK_UNDERLYING) then call symputx(''rk'',buskey);'; put 'run;'; put '%mp_lockfilecheck(libds=&orig_libds)'; put 'data compare;'; put 'set &libds(drop=&txfrom &txto &processed &rk);'; put 'stop;'; put 'run;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(syscc=&syscc line 80)'; put ')'; put '/* get line terminator, assume it''s the first cr, lf, or crlf */'; put 'data _null_;'; put 'length text $32767 term $4;'; put 'call missing (of _all_);'; put 'fid=fopen("&csvref",''I'',32767,''b'');'; put 'rc=fread(fid);'; put 'rc2=fget(fid,text,32767);'; put 'cr=find(text,''0D''x );'; put 'lf=find(text,''0A''x );'; put 'crlf=find(text,''0D0A''x);'; put 'rc=fclose(fid);'; put 'if crlf>0 & cr0 & crlf0 & cr>0 & lf0 then term=''LF'';'; put 'else term=''CR'';'; put 'call symputx(''termstr'',term);'; put 'run;'; put 'data _null_;'; put 'infile &csvref lrecl=32000 dsd termstr=&termstr;'; put 'input;'; put 'length incols_unsorted $32000 dlm $1;'; put 'incols_unsorted=compress(upcase(_infile_),"''"!!''"'');'; put '/* dlm has length 1 so will be the first non alpha / digit char */'; put '/* expectation is that there will not be any crazy characters in first col! */'; put 'dlm=compress(incols_unsorted,''_ '',''ad'');'; put 'incols_unsorted=compress(incols_unsorted,dlm!!''_'',''kado'');'; put 'incols_unsorted=tranwrd(incols_unsorted,dlm,'' '');'; put 'call symputx(''incols_unsorted'',incols_unsorted);'; put 'call symputx(''dlm'',dlm);'; put 'putlog incols_unsorted=;'; put 'putlog dlm=;'; put 'stop;'; put 'run;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(syscc=&syscc line 99)'; put ')'; put '%let basecols=%upcase(%mf_getvarlist(work.compare,dlm=%str( )));'; put '%let missing_cols=%trim('; put '%mf_wordsInStr1ButNotStr2('; put 'Str1=&basecols'; put ',Str2=&incols_unsorted'; put '));'; put '%let msg='; put 'Expected cols: &basecols'; put '
Received cols: &incols_unsorted'; put '
Missing cols: &missing_cols'; put ';'; put '%mp_abort(iftrue= (%length(%trim(&missing_cols)) > 1 or &syscc ne 0)'; put ',mac=mpestp_loadfile.sas'; put ',msg=%superq(msg)'; put ')'; put '%let msg=0;'; put 'PROC FORMAT;'; put 'picture yymmddhhmmss other=''%0Y%0m%0d_%0H%0M%0S'' (datatype=datetime);'; put 'RUN;'; put '/* create a dataset key (datetime plus 6 digit random number plus PID) */'; put '%let mperef=DC%left(%sysfunc(datetime(),B8601DT19.3))_%substr('; put '%sysfunc(ranuni(0)),3,6)_%substr(%str(&sysjobid ),1,4);'; put '/* Create package folder and redirect the log */'; put '%let dir=&mpelocapprovals/&mperef;'; put '%mf_mkdir(&dir)'; put '/* clean embedded line breaks and force CRLF line endings */'; put '%mp_cleancsv(in=&csvref, out=&dir/&orig_libds..csv)'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(issue in mp_cleancsv)'; put ')'; put '%put; %put; %put log is being redirected;'; put '%let url=_program=%substr(&_program'; put ',1,%length(&_program)-8)getlog%nrstr(&)table=&mperef;'; put '%put to retrieve, visit this url:; %put;%put;'; put '%put &url;'; put '%put;'; put '/* proc printto log="&dir/weblog.txt";run; */'; put 'libname approve "&dir";'; put 'options mprint;'; put '%put &=mperef;'; put '%put &=termstr;'; put '%put &=dlm;'; put '%mpe_loader(mperef=&mperef'; put ',submitted_reason_txt=%quote(File upload: %superq(csvname))'; put ',dlm=%superq(dlm)'; put ',url=%superq(url)'; put ',termstr=CRLF'; put ',dc_dttmtfmt=&dc_dttmtfmt'; put ')'; put '%mp_abort(mode=INCLUDE)'; put '%mp_abort('; put 'iftrue= (%sysfunc(fileexist(%sysfunc(pathname(work))/mf_abort.error)) ne 0)'; put ',mac=&_program'; put ',msg=%nrstr(Problem occurred in &sysmacroname (mf_abort.error file found))'; put ')'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=mpestp_loadfile.sas'; put ',msg=%str(syscc=&syscc)'; put ')'; put 'filename outref "&dir/BKP_&xlsname";'; put '%mp_binarycopy(iftrue=("&xlsref" ne "0"),inref=&xlsref,outref=outref)'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(syscc=&syscc when backing up source file &xlsname)'; put ')'; put 'data sasparams;'; put 'STATUS=''SUCCESS'';'; put 'DSID="&mperef";'; put 'run;'; put '%webout(OPEN)'; put '%webout(OBJ,sasparams)'; put '%webout(CLOSE)'; put '%mpeterm()'; put '* Service end;'; run; %mm_createwebservice(path=&appLoc/&path, name=&service, code=sascode, server=&serverName, replace=yes) filename sascode clear; %let service=stagedata; filename sascode temp lrecl=32767; data _null_; file sascode; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '/**'; put '@file mp_jsonout.sas'; put '@brief Writes JSON in SASjs format to a fileref'; put '@details This macro can be used to OPEN a JSON stream and send one or more'; put 'tables as arrays of rows, where each row can be an object or a nested array.'; put 'There are two engines available - DATASTEP or PROCJSON.'; put 'PROC JSON is fast but will produce errs like the ones below if'; put 'special chars are encountered.'; put '> (ERR)OR: Some code points did not transcode.'; put '> An object or array close is not valid at this point in the JSON text.'; put '> Date value out of range'; put 'If this happens, try running with ENGINE=DATASTEP.'; put 'The DATASTEP engine is used to handle special SAS missing numerics, and'; put 'can also convert entire datasets to formatted values. Output JSON is always'; put 'in UTF-8.'; put 'Usage:'; put 'filename tmp temp;'; put 'data class; set sashelp.class;run;'; put '%mp_jsonout(OPEN,jref=tmp)'; put '%mp_jsonout(OBJ,class,jref=tmp)'; put '%mp_jsonout(OBJ,class,dslabel=class2,jref=tmp,showmeta=Y)'; put '%mp_jsonout(CLOSE,jref=tmp)'; put 'data _null_;'; put 'infile tmp;'; put 'input;putlog _infile_;'; put 'run;'; put 'If you are building web apps with SAS then you are strongly encouraged to use'; put 'the mX_createwebservice macros in combination with the'; put '[sasjs adapter](https://github.com/sasjs/adapter).'; put 'For more information see https://sasjs.io'; put '@param [in] action Valid values:'; put '@li OPEN - opens the JSON'; put '@li OBJ - sends a table with each row as an object'; put '@li ARR - sends a table with each row in an array'; put '@li CLOSE - closes the JSON'; put '@param [in] ds The dataset to send. Must be a work table.'; put '@param [out] jref= (_webout) The fileref to which to send the JSON'; put '@param [out] dslabel= The name to give the table in the exported JSON'; put '@param [in] fmt= (Y) Whether to keep (Y) or strip (N) formats from the table'; put '@param [in] engine= (DATASTEP) Which engine to use to send the JSON. Options:'; put '@li PROCJSON (default)'; put '@li DATASTEP (more reliable when data has non standard characters)'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to JSON'; put '

Related Files

'; put '@li mp_ds2fmtds.sas'; put '@version 9.2'; put '@author Allan Bowe'; put '@source https://github.com/sasjs/core'; put '**/'; put '%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y'; put ',engine=DATASTEP'; put ',missing=NULL'; put ',showmeta=N'; put ',maxobs=MAX'; put ')/*/STORE SOURCE*/;'; put '%local tempds colinfo fmtds i numcols numobs stmt_obs lastobs optval'; put 'tmpds1 tmpds2 tmpds3 tmpds4;'; put '%let numcols=0;'; put '%if &maxobs ne MAX %then %let stmt_obs=%str(if _n_>&maxobs then stop;);'; put '%if &action=OPEN %then %do;'; put 'options nobomfile;'; put 'data _null_;file &jref encoding=''utf-8'' lrecl=200;'; put 'put ''{"PROCESSED_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''"'';'; put 'run;'; put '%end;'; put '%else %if (&action=ARR or &action=OBJ) %then %do;'; put '/* force variable names to always be uppercase in the JSON */'; put 'options validvarname=upcase;'; put '/* To avoid issues with _webout on EBI - such as encoding diffs and truncation'; put '(https://support.sas.com/kb/49/325.html) we use temporary files */'; put 'filename _sjs1 temp lrecl=200 ;'; put 'data _null_; file _sjs1 encoding=''utf-8'';'; put 'put ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":";'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjs1 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs1 clear;'; put '/* grab col defs */'; put 'proc contents noprint data=&ds'; put 'out=_data_(keep=name type length format formatl formatd varnum label);'; put 'run;'; put '%let colinfo=%scan(&syslast,2,.);'; put 'proc sort data=&colinfo;'; put 'by varnum;'; put 'run;'; put '/* move meta to mac vars */'; put 'data &colinfo;'; put 'if _n_=1 then call symputx(''numcols'',nobs,''l'');'; put 'set &colinfo end=last nobs=nobs;'; put 'name=upcase(name);'; put '/* fix formats */'; put 'if type=2 or type=6 then do;'; put 'typelong=''char'';'; put 'length fmt $49.;'; put 'if format='''' then fmt=cats(''$'',length,''.'');'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else fmt=cats(format,formatl,''.'');'; put 'end;'; put 'else do;'; put 'typelong=''num'';'; put 'if format='''' then fmt=''best.'';'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else if formatd=0 then fmt=cats(format,formatl,''.'');'; put 'else fmt=cats(format,formatl,''.'',formatd);'; put 'end;'; put '/* 32 char unique name */'; put 'newname=''sasjs''!!substr(cats(put(md5(name),$hex32.)),1,27);'; put 'call symputx(cats(''name'',_n_),name,''l'');'; put 'call symputx(cats(''newname'',_n_),newname,''l'');'; put 'call symputx(cats(''length'',_n_),length,''l'');'; put 'call symputx(cats(''fmt'',_n_),fmt,''l'');'; put 'call symputx(cats(''type'',_n_),type,''l'');'; put 'call symputx(cats(''typelong'',_n_),typelong,''l'');'; put 'call symputx(cats(''label'',_n_),coalescec(label,name),''l'');'; put '/* overwritten when fmt=Y and a custom format exists in catalog */'; put 'if typelong=''num'' then call symputx(cats(''fmtlen'',_n_),200,''l'');'; put 'else call symputx(cats(''fmtlen'',_n_),min(32767,ceil((length+10)*1.5)),''l'');'; put 'run;'; put '%let tempds=%substr(_%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql;'; put 'select count(*) into: lastobs from &ds;'; put '%if &maxobs ne MAX %then %let lastobs=%sysfunc(min(&lastobs,&maxobs));'; put '%if &engine=PROCJSON %then %do;'; put '%if &missing=STRING %then %do;'; put '%put &sysmacroname: Special Missings not supported in proc json.;'; put '%put &sysmacroname: Switching to DATASTEP engine;'; put '%goto datastep;'; put '%end;'; put 'data &tempds;'; put 'set &ds;'; put '&stmt_obs;'; put '%if &fmt=N %then format _numeric_ best32.;;'; put '/* PRETTY is necessary to avoid line truncation in large files */'; put 'filename _sjs2 temp lrecl=131068 encoding=''utf-8'';'; put 'proc json out=_sjs2 pretty'; put '%if &action=ARR %then nokeys ;'; put ';export &tempds / nosastags fmtnumeric;'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs2 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs2 clear;'; put '%end;'; put '%else %if &engine=DATASTEP %then %do;'; put '%datastep:'; put '%if %sysfunc(exist(&ds)) ne 1 & %sysfunc(exist(&ds,VIEW)) ne 1'; put '%then %do;'; put '%put &sysmacroname: &ds NOT FOUND!!!;'; put '%return;'; put '%end;'; put '%if &fmt=Y %then %do;'; put '/**'; put '* Extract format definitions'; put '* First, by getting library locations from dictionary.formats'; put '* Then, by exporting the width using proc format'; put '* Cannot use maxw from sashelp.vformat as not always populated'; put '* Cannot use fmtinfo() as not supported in all flavours'; put '*/'; put '%let tmpds1=%substr(fmtsum%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds2=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds3=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds4=%substr(col%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql noprint;'; put 'create table &tmpds1 as'; put 'select cats(libname,''.'',memname) as FMTCAT,'; put 'FMTNAME'; put 'from dictionary.formats'; put 'where fmttype=''F'' and libname is not null'; put 'and fmtname in (select format from &colinfo where format is not null)'; put 'order by 1;'; put 'create table &tmpds2('; put 'FMTNAME char(32),'; put 'LENGTH num'; put ');'; put '%local catlist cat fmtlist i;'; put 'select distinct fmtcat into: catlist separated by '' '' from &tmpds1;'; put '%do i=1 %to %sysfunc(countw(&catlist,%str( )));'; put '%let cat=%scan(&catlist,&i,%str( ));'; put 'proc sql;'; put 'select distinct fmtname into: fmtlist separated by '' '''; put 'from &tmpds1 where fmtcat="&cat";'; put 'proc format lib=&cat cntlout=&tmpds3(keep=fmtname length);'; put 'select &fmtlist;'; put 'run;'; put 'proc sql;'; put 'insert into &tmpds2 select distinct fmtname,length from &tmpds3;'; put '%end;'; put 'proc sql;'; put 'create table &tmpds4 as'; put 'select a.*, b.length as MAXW'; put 'from &colinfo a'; put 'left join &tmpds2 b'; put 'on cats(a.format)=cats(upcase(b.fmtname))'; put 'order by a.varnum;'; put 'data _null_;'; put 'set &tmpds4;'; put 'if not missing(maxw);'; put 'call symputx('; put 'cats(''fmtlen'',_n_),'; put '/* vars need extra padding due to JSON escaping of special chars */'; put 'min(32767,ceil((max(length,maxw)+10)*1.5))'; put ',''l'''; put ');'; put 'run;'; put '/* configure varlenchk - as we are explicitly shortening the variables */'; put '%let optval=%sysfunc(getoption(varlenchk));'; put 'options varlenchk=NOWARN;'; put 'data _data_(compress=char);'; put '/* shorten the new vars */'; put 'length'; put '%do i=1 %to &numcols;'; put '&&name&i $&&fmtlen&i'; put '%end;'; put ';'; put '/* rename on entry */'; put 'set &ds(rename=('; put '%do i=1 %to &numcols;'; put '&&name&i=&&newname&i'; put '%end;'; put '));'; put '&stmt_obs;'; put 'drop'; put '%do i=1 %to &numcols;'; put '&&newname&i'; put '%end;'; put ';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=num %then %do;'; put '&&name&i=cats(put(&&newname&i,&&fmt&i));'; put '%end;'; put '%else %do;'; put '&&name&i=put(&&newname&i,&&fmt&i);'; put '%end;'; put '%end;'; put 'if _error_ then do;'; put 'call symputx(''syscc'',1012);'; put 'stop;'; put 'end;'; put 'run;'; put '%let fmtds=&syslast;'; put 'options varlenchk=&optval;'; put '%end;'; put 'proc format; /* credit yabwon for special null removal */'; put 'value bart (default=40)'; put '%if &missing=NULL %then %do;'; put '._ - .z = null'; put '%end;'; put '%else %do;'; put '._ = [quote()]'; put '. = null'; put '.a - .z = [quote()]'; put '%end;'; put 'other = [best.];'; put 'data &tempds;'; put 'attrib _all_ label='''';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'length &&name&i $&&fmtlen&i...;'; put 'format &&name&i $&&fmtlen&i...;'; put '%end;'; put '%end;'; put '%if &fmt=Y %then %do;'; put 'set &fmtds;'; put '%end;'; put '%else %do;'; put 'set &ds;'; put '%end;'; put '&stmt_obs;'; put 'format _numeric_ bart.;'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'if findc(&&name&i,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put '&&name&i=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,&&name&i)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else &&name&i=quote(cats(&&name&i));'; put '%end;'; put '%end;'; put 'run;'; put 'filename _sjs3 temp lrecl=131068 ;'; put 'data _null_;'; put 'file _sjs3 encoding=''utf-8'';'; put 'if _n_=1 then put "[";'; put 'set &tempds;'; put 'if _n_>1 then put "," @; put'; put '%if &action=ARR %then "[" ; %else "{" ;'; put '%do i=1 %to &numcols;'; put '%if &i>1 %then "," ;'; put '%if &action=OBJ %then """&&name&i"":" ;'; put '"&&name&i"n /* name literal for reserved variable names */'; put '%end;'; put '%if &action=ARR %then "]" ; %else "}" ; ;'; put '/* close out the table */'; put 'data _null_;'; put 'file _sjs3 mod encoding=''utf-8'';'; put 'put '']'';'; put 'run;'; put 'data _null_;'; put 'infile _sjs3 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs3 clear;'; put '%end;'; put 'proc sql;'; put 'drop table &colinfo, &tempds;'; put '%if %substr(&showmeta,1,1)=Y %then %do;'; put 'filename _sjs4 temp lrecl=131068 encoding=''utf-8'';'; put 'data _null_;'; put 'file _sjs4;'; put 'length label $350;'; put 'put ", ""$%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":{""vars"":{";'; put 'do i=1 to &numcols;'; put 'name=quote(trim(symget(cats(''name'',i))));'; put 'format=quote(trim(symget(cats(''fmt'',i))));'; put 'label=quote(prxchange(''s/\\/\\\\/'',-1,trim(symget(cats(''label'',i)))));'; put 'length=quote(trim(symget(cats(''length'',i))));'; put 'type=quote(trim(symget(cats(''typelong'',i))));'; put 'if i>1 then put "," @@;'; put 'put name '':{"format":'' format '',"label":'' label'; put ''',"length":'' length '',"type":'' type ''}'';'; put 'end;'; put 'put ''}}'';'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs4 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs4 clear;'; put '%end;'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put 'data _null_; file &jref encoding=''utf-8'' mod ;'; put 'put "}";'; put 'run;'; put '%end;'; put '%mend mp_jsonout;'; put '/**'; put '@file mm_webout.sas'; put '@brief Send data to/from SAS Stored Processes'; put '@details This macro should be added to the start of each Stored Process,'; put '**immediately** followed by a call to:'; put '%mm_webout(FETCH)'; put 'This will read all the input data and create same-named SAS datasets in the'; put 'WORK library. You can then insert your code, and send data back using the'; put 'following syntax:'; put 'data some datasets; * make some data ;'; put 'retain some columns;'; put 'run;'; put '%mm_webout(OPEN)'; put '%mm_webout(ARR,some) * Array format, fast, suitable for large tables ;'; put '%mm_webout(OBJ,datasets) * Object format, easier to work with ;'; put 'Finally, wrap everything up send some helpful system variables too'; put '%mm_webout(CLOSE)'; put '@param [in] action Either FETCH, OPEN, ARR, OBJ or CLOSE'; put '@param [in] ds The dataset to send back to the frontend'; put '@param [out] dslabel= Value to use instead of table name for sending to JSON'; put '@param [in] fmt= (N) Setting Y converts all vars to their formatted values'; put '@param [out] fref= (_webout) The fileref to which to write the JSON'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] workobs= (0) When set to a positive integer, will create a new'; put 'output object (WORK) which contains this number of observations from all'; put 'tables in the WORK library.'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to output JSON'; put '

SAS Macros

'; put '@li mp_jsonout.sas'; put '

Related Macros

'; put '@li ms_webout.sas'; put '@li mv_webout.sas'; put '@version 9.3'; put '@author Allan Bowe'; put '**/'; put '%macro mm_webout(action,ds,dslabel=,fref=_webout,fmt=N,missing=NULL'; put ',showmeta=N,maxobs=MAX,workobs=0'; put ');'; put '%global _webin_file_count _webin_fileref1 _webin_name1 _program _debug'; put 'sasjs_tables;'; put '%local i tempds jsonengine;'; put '/* see https://github.com/sasjs/core/issues/41 */'; put '%if "%upcase(&SYSENCODING)" ne "UTF-8" %then %let jsonengine=PROCJSON;'; put '%else %let jsonengine=DATASTEP;'; put '%if &action=FETCH %then %do;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'options mprint notes mprintnest;'; put '%end;'; put '%let _webin_file_count=%eval(&_webin_file_count+0);'; put '/* now read in the data */'; put '%do i=1 %to &_webin_file_count;'; put '%if &_webin_file_count=1 %then %do;'; put '%let _webin_fileref1=&_webin_fileref;'; put '%let _webin_name1=&_webin_name;'; put '%end;'; put 'data _null_;'; put 'infile &&_webin_fileref&i termstr=crlf;'; put 'input;'; put 'call symputx(''input_statement'',_infile_);'; put 'putlog "&&_webin_name&i input statement: " _infile_;'; put 'stop;'; put 'data &&_webin_name&i;'; put 'infile &&_webin_fileref&i firstobs=2 dsd termstr=crlf encoding=''utf-8'';'; put 'input &input_statement;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'if _n_<20 then putlog _infile_;'; put '%end;'; put 'run;'; put '%let sasjs_tables=&sasjs_tables &&_webin_name&i;'; put '%end;'; put '%end;'; put '%else %if &action=OPEN %then %do;'; put '/* fix encoding */'; put 'OPTIONS NOBOMFILE;'; put '/**'; put '* check xengine type to avoid the below err message:'; put '* > Function is only valid for filerefs using the CACHE access method.'; put '*/'; put 'data _null_;'; put 'set sashelp.vextfl(where=(fileref="_WEBOUT"));'; put 'if xengine=''STREAM'' then do;'; put 'rc=stpsrv_header(''Content-type'',"text/html; encoding=utf-8");'; put 'end;'; put 'run;'; put '/* setup json */'; put 'data _null_;file &fref encoding=''utf-8'';'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutBEGIN<<'';'; put '%end;'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'run;'; put '%end;'; put '%else %if &action=ARR or &action=OBJ %then %do;'; put '%mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt,jref=&fref'; put ',engine=&jsonengine,missing=&missing,showmeta=&showmeta,maxobs=&maxobs'; put ')'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put '/* To avoid issues with _webout on EBI we use a temporary file */'; put 'filename _sjsref temp lrecl=131068;'; put '%if %str(&workobs) > 0 %then %do;'; put '/* if debug mode, send back first XX records of each work table also */'; put 'data;run;%let tempds=%scan(&syslast,2,.);'; put 'ods output Members=&tempds;'; put 'proc datasets library=WORK memtype=data;'; put '%local wtcnt;%let wtcnt=0;'; put 'data _null_;'; put 'set &tempds;'; put 'if not (upcase(name) =:"DATA"); /* ignore temp datasets */'; put 'i+1;'; put 'call symputx(cats(''wt'',i),name,''l'');'; put 'call symputx(''wtcnt'',i,''l'');'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put ",""WORK"":{";'; put '%do i=1 %to &wtcnt;'; put '%let wt=&&wt&i;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'dsid=open("WORK.&wt",''is'');'; put 'nlobs=attrn(dsid,''NLOBS'');'; put 'nvars=attrn(dsid,''NVARS'');'; put 'rc=close(dsid);'; put 'if &i>1 then put '',''@;'; put 'put " ""&wt"" : {";'; put 'put ''"nlobs":'' nlobs;'; put 'put '',"nvars":'' nvars;'; put '%mp_jsonout(OBJ,&wt,jref=_sjsref,dslabel=first10rows,showmeta=Y'; put ',maxobs=&workobs'; put ')'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put '%end;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put 'run;'; put '%end;'; put '/* close off json */'; put 'data _null_;file _sjsref mod encoding=''utf-8'';'; put 'length SYSPROCESSNAME syserrortext syswarningtext autoexec $512;'; put 'put ",""_DEBUG"" : ""&_debug"" ";'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'autoexec=quote(urlencode(trim(getoption(''autoexec''))));'; put 'put '',"AUTOEXEC" : '' autoexec;'; put 'put ",""MF_GETUSER"" : ""%mf_getuser()"" ";'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'put ",""SYSENCODING"" : ""&sysencoding"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSPROCESSID"" : ""&SYSPROCESSID"" ";'; put 'put ",""SYSPROCESSMODE"" : ""&SYSPROCESSMODE"" ";'; put 'SYSPROCESSNAME=quote(urlencode(cats(SYSPROCESSNAME)));'; put 'put ",""SYSPROCESSNAME"" : " SYSPROCESSNAME;'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put '',"SYSWARNINGTEXT" : '' syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'length memsize $32;'; put 'memsize="%sysfunc(INPUTN(%sysfunc(getoption(memsize)), best.),sizekmg.)";'; put 'memsize=quote(cats(memsize));'; put 'put '',"MEMSIZE" : '' memsize;'; put 'put "}" @;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutEND<<'';'; put '%end;'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjsref lrecl=1 recfm=n;'; put 'file &fref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjsref clear;'; put '%end;'; put '%mend mm_webout;'; put '%macro webout(action,ds,dslabel=,fmt=,missing=NULL,showmeta=NO,maxobs=MAX);'; put '%mm_webout(&action,ds=&ds,dslabel=&dslabel,fmt=&fmt'; put ',missing=&missing'; put ',showmeta=&showmeta'; put ',maxobs=&maxobs'; put ') %mend;'; put '/* provide additional debug info */'; put '%global _program;'; put '%put &=syscc;'; put '%put user=%mf_getuser();'; put '%put pgm=&_program;'; put '%put timestamp=%sysfunc(datetime(),datetime19.);'; put '* Service Variables start;'; put '* Service Variables end;'; put '* SAS Macros start;'; put '%macro mf_getapploc(pgm);'; put '%if "&pgm"="" %then %do;'; put '%if %symexist(_program) %then %let pgm=&_program;'; put '%else %do;'; put '%put &sysmacroname: No value provided and no _program variable available;'; put '%return;'; put '%end;'; put '%end;'; put '%local root;'; put '/**'; put '* First check we are not in the tests/macros folder (which has no subfolders)'; put '* or specifically in the testsetup or testteardown services'; put '*/'; put '%if %index(&pgm,/tests/macros/)'; put 'or %index(&pgm,/tests/testsetup)'; put 'or %index(&pgm,/tests/testteardown)'; put '%then %do;'; put '%let root=%substr(&pgm,1,%index(&pgm,/tests)-1);'; put '&root'; put '%return;'; put '%end;'; put '/**'; put '* Next, move up two levels to avoid matches on subfolder or service name'; put '*/'; put '%let root=%substr(&pgm,1,%length(&pgm)-%length(%scan(&pgm,-1,/))-1);'; put '%let root=%substr(&root,1,%length(&root)-%length(%scan(&root,-1,/))-1);'; put '%if %index(&root,/tests/) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/tests/)-1);'; put '%end;'; put '%else %if %index(&root,/services) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/services)-1);'; put '%end;'; put '%else %if %index(&root,/jobs) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/jobs)-1);'; put '%end;'; put '%else %put &sysmacroname: Could not find an app location from &pgm;'; put '&root'; put '%mend mf_getapploc ;'; put '%macro mf_getuniquefileref(prefix=_,maxtries=1000,lrecl=32767);'; put '%local rc fname;'; put '%if &prefix=0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%end;'; put '%else %do;'; put '%local x len;'; put '%let len=%eval(8-%length(&prefix));'; put '%let x=0;'; put '%do x=0 %to &maxtries;'; put '%let fname=&prefix%substr(%sysfunc(ranuni(0)),3,&len);'; put '%if %sysfunc(fileref(&fname)) > 0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%return;'; put '%end;'; put '%end;'; put '%put unable to find available fileref after &maxtries attempts;'; put '%end;'; put '%mend mf_getuniquefileref;'; put '%macro mp_abort(mac=mp_abort.sas, type=, msg=, iftrue=%str(1=1)'; put ', errds=work.mp_abort_errds'; put ', mode=REGULAR'; put ')/*/STORE SOURCE*/;'; put '%global sysprocessmode sysprocessname sasjs_stpsrv_header_loc sasjsprocessmode;'; put '%local fref fid i;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%put NOTE: /// mp_abort macro executing //;'; put '%if %length(&mac)>0 %then %put NOTE- called by &mac;'; put '%put NOTE - &msg;'; put '%if %symexist(_SYSINCLUDEFILEDEVICE)'; put '/* abort cancel FILE does not restart outside the INCLUDE on Viya 3.5 */'; put 'and %superq(SYSPROCESSNAME) ne %str(Compute Server)'; put '%then %do;'; put '%if "*&_SYSINCLUDEFILEDEVICE*" ne "**" %then %do;'; put 'data &errds;'; put 'iftrue=''1=1'';'; put 'length mac $100 msg $5000;'; put 'mac=symget(''mac'');'; put 'msg=symget(''msg'');'; put 'run;'; put 'data _null_;'; put 'abort cancel FILE;'; put 'run;'; put '%return;'; put '%end;'; put '%end;'; put '/* Web App Context */'; put '%if %symexist(_PROGRAM)'; put 'or %superq(SYSPROCESSNAME) = %str(Compute Server)'; put 'or &mode=INCLUDE'; put '%then %do;'; put 'options obs=max replace mprint;'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5"'; put '%then %do;'; put 'options nosyntaxcheck;'; put '%end;'; put '%if &mode=INCLUDE %then %do;'; put '%if %sysfunc(exist(&errds))=1 %then %do;'; put 'data _null_;'; put 'set &errds;'; put 'call symputx(''iftrue'',iftrue,''l'');'; put 'call symputx(''mac'',mac,''l'');'; put 'call symputx(''msg'',msg,''l'');'; put 'putlog (_all_)(=);'; put 'run;'; put '%if (&iftrue)=0 %then %return;'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: No include errors found;'; put '%return;'; put '%end;'; put '%end;'; put '/* extract log errs / warns, if exist */'; put '%local logloc logline;'; put '%global logmsg; /* capture global messages */'; put '%if %symexist(SYSPRINTTOLOG) %then %let logloc=&SYSPRINTTOLOG;'; put '%else %let logloc=%qsysfunc(getoption(LOG));'; put 'proc printto log=log;run;'; put '%let logline=0;'; put '%if %length(&logloc)>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input; putlog _infile_;'; put 'i=1;'; put 'retain logonce 0;'; put 'if ('; put '_infile_=:"%str(WARN)ING" or _infile_=:"%str(ERR)OR"'; put ') and logonce=0 then'; put 'do;'; put 'call symputx(''logline'',_n_);'; put 'logonce+1;'; put 'end;'; put 'run;'; put '/* capture log including lines BEFORE the err */'; put '%if &logline>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input;'; put 'i=1;'; put 'stoploop=0;'; put 'if _n_ ge &logline-15 and stoploop=0 then do until (i>22);'; put 'call symputx(''logmsg'',catx(''\n'',symget(''logmsg''),_infile_));'; put 'input;'; put 'i+1;'; put 'stoploop=1;'; put 'end;'; put 'if stoploop=1 then stop;'; put 'run;'; put '%end;'; put '%end;'; put '%if %symexist(SYS_JES_JOB_URI) %then %do;'; put '/* setup webout for Viya */'; put 'options nobomfile;'; put '%if "X&SYS_JES_JOB_URI.X"="XX" %then %do;'; put 'filename _webout temp lrecl=999999 mod;'; put '%end;'; put '%else %do;'; put 'filename _webout filesrvc parenturi="&SYS_JES_JOB_URI"'; put 'name="_webout.json" lrecl=999999 mod;'; put '%end;'; put '%end;'; put '%else %if %sysfunc(filename(fref,&sasjs_stpsrv_header_loc))=0 %then %do;'; put 'options nobomfile;'; put '/* set up http header for SASjs Server */'; put '%let fid=%sysfunc(fopen(&fref,A));'; put '%if &fid=0 %then %do;'; put '%put %str(ERR)OR: %sysfunc(sysmsg());'; put '%return;'; put '%end;'; put '%let rc=%sysfunc(fput(&fid,%str(Content-Type: application/json)));'; put '%let rc=%sysfunc(fwrite(&fid));'; put '%let rc=%sysfunc(fclose(&fid));'; put '%let rc=%sysfunc(filename(&fref));'; put '%end;'; put '/* send response in SASjs JSON format */'; put 'data _null_;'; put 'file _webout mod lrecl=32000 encoding=''utf-8'';'; put 'length msg syswarningtext syserrortext $32767 mode $10 ;'; put 'sasdatetime=datetime();'; put 'msg=symget(''msg'');'; put '%if &logline>0 %then %do;'; put 'msg=cats(msg,''\n\nLog Extract:\n'',symget(''logmsg''));'; put '%end;'; put '/* escape the escapes */'; put 'msg=tranwrd(msg,''\'',''\\'');'; put '/* escape the quotes */'; put 'msg=tranwrd(msg,''"'',''\"'');'; put '/* ditch the CRLFs as chrome complains */'; put 'msg=compress(msg,,''kw'');'; put '/* quote without quoting the quotes (which are escaped instead) */'; put 'msg=cats(''"'',msg,''"'');'; put 'if symexist(''_debug'') then debug=quote(trim(symget(''_debug'')));'; put 'else debug=''""'';'; put 'if symget(''sasjsprocessmode'')=''Stored Program'' then mode=''SASJS'';'; put 'if mode ne ''SASJS'' then put ''>>weboutBEGIN<<'';'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'put '',"sasjsAbort" : [{'';'; put 'put '' "MSG":'' msg ;'; put 'put '' ,"MAC": "'' "&mac" ''"}]'';'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'put '',"_DEBUG":'' debug ;'; put 'if symexist(''_metauser'') then do;'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put 'end;'; put 'if symexist(''SYS_JES_JOB_URI'') then do;'; put 'SYS_JES_JOB_URI=quote(trim(symget(''SYS_JES_JOB_URI'')));'; put 'put '',"SYS_JES_JOB_URI": '' SYS_JES_JOB_URI;'; put 'end;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put ",""SYSWARNINGTEXT"" : " syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'put "}" ;'; put 'if mode ne ''SASJS'' then put ''>>weboutEND<<'';'; put 'run;'; put '%put _all_;'; put '%if "&sysprocessmode " = "SAS Stored Process Server " %then %do;'; put 'data _null_;'; put 'putlog ''stpsrvset program err and syscc'';'; put 'rc=stpsrvset(''program error'', 0);'; put 'call symputx("syscc",0,"g");'; put 'run;'; put '%if &sysscp=WIN'; put 'and 1=0 /* deprecating this logic until we figure out a consistent abort */'; put 'and "%substr(%str(&sysvlong ),1,8)"="9.04.01M"'; put 'and "%substr(%str(&sysvlong ),9,1)">"5" %then %do;'; put '/* skip approach (below) does not work in windows m6+ envs */'; put 'endsas;'; put '%end;'; put '%else %do;'; put '/**'; put '* endsas kills 9.4m3 deployments by orphaning multibridges.'; put '* Abort variants are ungraceful (non zero return code)'; put '* This approach lets SAS run silently until the end :-)'; put '* Caution - fails when called within a %include within a macro'; put '* Use mp_include() to handle this.'; put '*/'; put 'filename skip temp;'; put 'data _null_;'; put 'file skip;'; put 'put ''%macro skip();'';'; put 'comment ''%mend skip; -> fix lint '';'; put 'put ''%macro skippy();'';'; put 'comment ''%mend skippy; -> fix lint '';'; put 'run;'; put '%inc skip;'; put '%end;'; put '%end;'; put '%else %if "&sysprocessmode " = "SAS Compute Server " %then %do;'; put '/* endsas kills the session making it harder to fetch results */'; put 'data _null_;'; put 'syswarningtext=symget(''syswarningtext'');'; put 'syserrortext=symget(''syserrortext'');'; put 'abort_msg=symget(''msg'');'; put 'syscc=symget(''syscc'');'; put 'sysuserid=symget(''sysuserid'');'; put 'iftrue=symget(''iftrue'');'; put 'put (_all_)(/=);'; put 'call symputx(''syscc'',0);'; put 'abort cancel nolist;'; put 'run;'; put '%end;'; put '%else %do;'; put '%abort cancel;'; put '%end;'; put '%end;'; put '%else %do;'; put '%put _all_;'; put '%abort cancel;'; put '%end;'; put '%mend mp_abort;'; put '/** @endcond */'; put '%macro mm_getstpcode('; put 'tree=/User Folders/sasdemo/somestp'; put ',name='; put ',outloc=0'; put ',outref=0'; put ',mDebug=1'; put ',showlog=NO'; put ');'; put '%local mD;'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing &sysmacroname..sas;'; put '%&mD.put _local_;'; put '%if %length(&name)>0 %then %let name=/&name;'; put '/* first, check if STP exists */'; put '%local tsuri;'; put '%let tsuri=stopifempty ;'; put 'data _null_;'; put 'format type uri tsuri value $200.;'; put 'call missing (of _all_);'; put 'path="&tree&name(StoredProcess)";'; put '/* first, find the STP ID */'; put 'if metadata_pathobj("",path,"StoredProcess",type,uri)>0 then do;'; put '/* get sourcecode */'; put 'cnt=1;'; put 'do while (metadata_getnasn(uri,"Notes",cnt,tsuri)>0);'; put 'rc=metadata_getattr(tsuri,"Name",value);'; put '&mD.put tsuri= value=;'; put 'if value="SourceCode" then do;'; put '/* found it! */'; put 'rc=metadata_getattr(tsuri,"Id",value);'; put 'call symputx(''tsuri'',value,''l'');'; put 'stop;'; put 'end;'; put 'cnt+1;'; put 'end;'; put 'end;'; put 'else put (_all_)(=);'; put 'run;'; put '%mp_abort(iftrue= (&tsuri=stopifempty)'; put ',mac=mm_getstpcode'; put ',msg=%str(&tree&name.(StoredProcess) not found!)'; put ')'; put '/**'; put '* Now we can extract the textstore'; put '*/'; put 'filename __getdoc temp lrecl=10000000;'; put 'proc metadata'; put 'in="$METAREPOSITORY'; put ''; put 'SAS1"'; put 'out=__getdoc ;'; put 'run;'; put '/* find the beginning of the text */'; put '%local start;'; put 'data _null_;'; put 'infile __getdoc lrecl=10000;'; put 'input;'; put 'start=index(_infile_,''StoredText="'');'; put 'if start then do;'; put 'call symputx("start",start+11);'; put '*putlog ''"'' _infile_ ''"'';'; put 'end;'; put 'stop;'; put '%local outeng;'; put '%if "&outloc"="0" %then %let outeng=TEMP;'; put '%else %let outeng="&outloc";'; put '%local fref;'; put '%if &outref=0 %then %let fref=%mf_getuniquefileref();'; put '%else %let fref=&outref;'; put '/* read the content, byte by byte, resolving escaped chars */'; put 'filename &fref &outeng lrecl=100000;'; put 'data _null_;'; put 'length filein 8 fileid 8;'; put 'filein = fopen("__getdoc","I",1,"B");'; put 'fileid = fopen("&fref","O",1,"B");'; put 'rec = "20"x;'; put 'length entity $6;'; put 'do while(fread(filein)=0);'; put 'x+1;'; put 'if x>&start then do;'; put 'rc = fget(filein,rec,1);'; put 'if rec=''"'' then leave;'; put 'else if rec="&" then do;'; put 'entity=rec;'; put 'do until (rec=";");'; put 'if fread(filein) ne 0 then goto getout;'; put 'rc = fget(filein,rec,1);'; put 'entity=cats(entity,rec);'; put 'end;'; put 'select (entity);'; put 'when (''&'' ) rec=''&'' ;'; put 'when (''<'' ) rec=''<'' ;'; put 'when (''>'' ) rec=''>'' ;'; put 'when (''''') rec="''" ;'; put 'when (''"'') rec=''"'' ;'; put 'when ('' '') rec=''0A''x;'; put 'when ('' '') rec=''0D''x;'; put 'when (''$'' ) rec=''$'' ;'; put 'when ('' '') rec=''09''x;'; put 'otherwise putlog "%str(WARN)ING: missing value for " entity=;'; put 'end;'; put 'rc =fput(fileid, substr(rec,1,1));'; put 'rc =fwrite(fileid);'; put 'end;'; put 'else do;'; put 'rc =fput(fileid,rec);'; put 'rc =fwrite(fileid);'; put 'end;'; put 'end;'; put 'end;'; put 'getout:'; put 'rc=fclose(filein);'; put 'rc=fclose(fileid);'; put 'run;'; put '%if &showlog=YES %then %do;'; put 'data _null_;'; put 'infile &fref lrecl=32767 end=last;'; put 'input;'; put 'if _n_=1 then putlog ''>>stpcodeBEGIN<<'';'; put 'putlog _infile_;'; put 'if last then putlog ''>>stpcodeEND<<'';'; put 'run;'; put '%end;'; put 'filename __getdoc clear;'; put '%if &outref=0 %then %do;'; put 'filename &fref clear;'; put '%end;'; put '%mend mm_getstpcode;'; put '%macro dc_getsettings();'; put '%global _program;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(syscc=&syscc on entry (&syswarningtext &syserrortext))'; put ')'; put '%if %length(&_PROGRAM)>1 %then %let root=&_program;'; put '%else %do;'; put '%global _metauser;'; put '%let _metauser=&sysuserid;'; put '/* to mimic a "real" _program we need to give a dummy role and stp name */'; put '%let root=/dummyRole/dummyName;'; put '%end;'; put '/* the DC precode is stored in the Admin folder in the root of'; put 'the project. Lets find that root. */'; put '%put &=root;'; put '%let root=%mf_getapploc();'; put '%put &=root;'; put '/* Now we know the root location we can retrieve the params */'; put '%let temploc=%sysfunc(pathname(work))/settings.txt;'; put '%mm_getstpcode(tree=&root/services/public'; put ',name=Data_Controller_Settings'; put ',outloc=&temploc'; put ')'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Unable to run getstpcode)'; put ')'; put 'filename _getsets "&temploc" lrecl=2000;'; put '/*'; put 'Do not use mp_include here - this puts a copy in every service, which creates'; put 'compilation problems when calling services from mp_include'; put '*/'; put '%inc _getsets/source2;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Problem running &sysmacroname (&syswarningtext &syserrortext))'; put ')'; put '%mend dc_getsettings;'; put '%macro mf_fmtdttm('; put ')/*/STORE SOURCE*/;'; put '%if "&sysver"="9.2" or "&sysver"="9.3"'; put 'or ("&sysver"="9.4" and "%substr(&SYSVLONG,9,1)" le "3")'; put 'or "%substr(&sysver,1,1)"="4"'; put 'or "%substr(&sysver,1,1)"="5"'; put '%then %do;DATETIME19.3%end;'; put '%else %do;E8601DT26.6%end;'; put '%mend mf_fmtdttm;'; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '%macro mp_init(prefix=SASJS'; put ')/*/STORE SOURCE*/;'; put '%if %symexist(SASJS_PREFIX) %then %return; /* only run once */'; put '%global'; put 'SASJS_PREFIX /* the ONLY hard-coded global macro variable in SASjs */'; put '&prefix._FUNCTIONS /* used in mcf_init() to track core function compilation */'; put '&prefix._INIT_NUM /* initialisation time as numeric */'; put '&prefix._INIT_DTTM /* initialisation time in E8601DT26.6 format */'; put '&prefix.WORK /* avoid typing %sysfunc(pathname(work)) every time */'; put ';'; put '%let sasjs_prefix=&prefix;'; put 'data _null_;'; put 'dttm=datetime();'; put 'call symputx("&sasjs_prefix._init_num",dttm,''g'');'; put 'call symputx("&sasjs_prefix._init_dttm",put(dttm,E8601DT26.6),''g'');'; put 'call symputx("&sasjs_prefix.work",pathname(''WORK''),''g'');'; put 'run;'; put 'options'; put 'compress=CHAR /* default is none so ensure we have something! */'; put 'datastmtchk=ALLKEYWORDS /* protection from overwriting input datasets */'; put 'errorcheck=STRICT /* catch errs in libname/filename statements */'; put 'fmterr /* ensure err when a format cannot be found */'; put 'mergenoby=%str(ERR)OR /* throw err when a merge has no BY variables */'; put 'missing=. /* changing this can cause hard to detect errs */'; put 'noquotelenmax /* avoid warnings for long strings */'; put 'noreplace /* avoid overwriting permanent datasets */'; put 'ps=max /* reduce log size slightly */'; put 'ls=max /* reduce log even more and avoid word truncation */'; put 'validmemname=COMPATIBLE /* avoid special characters etc in table names */'; put 'validvarname=V7 /* avoid special characters etc in variable names */'; put 'varinitchk=%str(ERR)OR /* avoid data mistakes from variable name typos */'; put 'varlenchk=%str(ERR)OR /* fail hard if truncation (data loss) can result */'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5" %then %do;'; put 'noautocorrect /* disallow misspelled procedure names */'; put 'dsoptions=note2err /* undocumented - convert bad NOTEs to ERRs */'; put '%end;'; put ';'; put '%mend mp_init;'; put '%macro mpeinit(fetch=YES);'; put '%global mpeinit'; put 'mpeadmins /* group with unrestricted Meditor access */'; put 'mpelocapprovals /* location for landing and staging files */'; put 'mpelib /* location of configuration tables for DC */'; put 'dc_repo_users /* location of user / group metadata */'; put 'dc_licence_key /* extracted in dc_getsettings */'; put 'dc_activation_key /* extracted in dc_getsettings */'; put 'dc_locale /* extracted in dc_getsettings */'; put 'dc_dttmtfmt /* can be overridden in dc_getsettings */'; put '_debug'; put ';'; put '%if &mpeinit=1 %then %return;'; put '%else %let mpeinit=1;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(Problem on service startup (&syswarningtext &syserrortext))'; put ')'; put '%mp_init()'; put '%if &fetch=YES %then %do;'; put '%webout(FETCH)'; put '%end;'; put '%global _CLIENTNAME;'; put '%mp_abort(iftrue= (&_CLIENTNAME=SAS Enterprise Guide)'; put ',mac=&_program..sas'; put ',msg=%str(Data Controller is a web app and should not be executed from EG)'; put ')'; put 'options urlencoding=utf8 nobomfile lrecl=32767;'; put '%let perf=%sysfunc(datetime());'; put '%put perfdiff: 0;'; put '%let dc_locale=SYSTEM; /* default if not set */'; put '/**'; put '* E8601DT26.6 has widest database support - but not all SAS flavours can'; put '* handle it. Override in the settings STP if needed.'; put '*/'; put 'data _null_;'; put 'dc_dttmtfmt=''"%sysfunc(datetime(),''!!"%mf_fmtdttm()"!!'')"dt'';'; put 'call symputx(''dc_dttmtfmt'',dc_dttmtfmt);'; put 'put dc_dttmtfmt=;'; put 'run;'; put '%put &=dc_dttmtfmt;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc prior to dc_getsettings)'; put ')'; put '%dc_getsettings()'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc after dc_getsettings)'; put ')'; put 'data _null_;'; put 'set &DC_LIBREF..mpe_config(where=('; put 'var_scope="DC"'; put 'and &dc_dttmtfmt lt tx_to'; put 'and var_active=1'; put '));'; put 'call symputx(var_name,var_value,''G'');'; put 'putlog var_name "=" var_value;'; put 'run;'; put '%let mpelib=&dc_libref;'; put '%let mpeadmins=&dc_admin_group;'; put '%let mpelocapprovals=&dc_staging_area;'; put '%let dc_repo_users=&dc_repo_users;'; put '%if &dc_locale ne SYSTEM %then %do;'; put 'options locale=&dc_locale;'; put '%end;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(Problem during compilation or with STP precode (&syswarningtext))'; put ')'; put '%mend mpeinit;'; put '%macro mf_mval(var);'; put '%if %symexist(&var) %then %do;'; put '%superq(&var)'; put '%end;'; put '%mend mf_mval;'; put '%macro mf_trimstr(basestr,trimstr);'; put '%local baselen trimlen trimval;'; put '/* return if basestr is shorter than trimstr (or 0) */'; put '%let baselen=%length(%superq(basestr));'; put '%let trimlen=%length(%superq(trimstr));'; put '%if &baselen < &trimlen or &baselen=0 %then %return;'; put '/* obtain the characters from the end of basestr */'; put '%let trimval=%qsubstr(%superq(basestr)'; put ',%length(%superq(basestr))-&trimlen+1'; put ',&trimlen);'; put '/* compare and if matching, chop it off! */'; put '%if %superq(basestr)=%superq(trimstr) %then %do;'; put '%return;'; put '%end;'; put '%else %if %superq(trimval)=%superq(trimstr) %then %do;'; put '%qsubstr(%superq(basestr),1,%length(%superq(basestr))-&trimlen)'; put '%end;'; put '%else %do;'; put '&basestr'; put '%end;'; put '%mend mf_trimstr;'; put '%macro mf_getplatform(switch'; put ')/*/STORE SOURCE*/;'; put '%local a b c;'; put '%if &switch.NONE=NONE %then %do;'; put '%if %symexist(sasjsprocessmode) %then %do;'; put '%if &sasjsprocessmode=Stored Program %then %do;'; put 'SASJS'; put '%return;'; put '%end;'; put '%end;'; put '%if %symexist(sysprocessmode) %then %do;'; put '%if "&sysprocessmode"="SAS Object Server"'; put 'or "&sysprocessmode"= "SAS Compute Server" %then %do;'; put 'SASVIYA'; put '%end;'; put '%else %if "&sysprocessmode"="SAS Stored Process Server"'; put 'or "&sysprocessmode"="SAS Workspace Server"'; put '%then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if %symexist(_metaport) or %symexist(_metauser) %then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if &switch=SASSTUDIO %then %do;'; put '/* return the version of SAS Studio else 0 */'; put '%if %mf_mval(_CLIENTAPP)=%str(SAS Studio) %then %do;'; put '%let a=%mf_mval(_CLIENTVERSION);'; put '%let b=%scan(&a,1,.);'; put '%if %eval(&b >2) %then %do;'; put '&b'; put '%end;'; put '%else 0;'; put '%end;'; put '%else 0;'; put '%end;'; put '%else %if &switch=VIYARESTAPI %then %do;'; put '%mf_trimstr(%sysfunc(getoption(servicesbaseurl)),/)'; put '%end;'; put '%mend mf_getplatform;'; put '%macro mpeterm();'; put '%local oldloc;'; put 'data _null_;'; put 'if symexist(''SYSPRINTTOLOG'') then oldloc=symget(''SYSPRINTTOLOG'');'; put 'else oldloc=getoption(''LOG'');'; put 'if subpad(oldloc,1,1) not in (''"'',"''",'' '') then oldloc=quote(cats(oldloc));'; put 'call symputx(''oldloc'',oldloc,''l'');'; put 'run;'; put '%if %length(&oldloc)>0 %then %do;'; put 'proc printto log=log;'; put 'run;'; put 'data _null_;'; put 'infile &oldloc;'; put 'input; putlog _infile_;'; put 'run;'; put '%end;'; put '%if %sysfunc(exist(&dc_libref..mpe_requests)) and %mf_getplatform() ne SASVIYA'; put '%then %do;'; put 'data ;'; put 'if 0 then set &dc_libref..mpe_requests;'; put 'request_dttm=%sysfunc(datetime());'; put 'request_user="%mf_getuser()";'; put 'request_service="%scan(&_program,-2,/)/%scan(&_program,-1,/)";'; put 'request_params='''';'; put 'output;stop;'; put 'proc append base=&dc_libref..mpe_requests data=&syslast force nowarn;'; put 'run;'; put '%end;'; put '%mend mpeterm;'; put '%macro mm_getGroups('; put 'user='; put ',outds=work.mm_getGroups'; put ',repo=foundation'; put ',mDebug=0'; put ')/*/STORE SOURCE*/;'; put '%local mD oldrepo;'; put '%let oldrepo=%sysfunc(getoption(metarepository));'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing mm_getGroups.sas;'; put '%&mD.put _local_;'; put '/* on some sites, user / group info is in a different metadata repo to the'; put 'default */'; put '%if &oldrepo ne &repo %then %do;'; put 'options metarepository=&repo;'; put '%end;'; put '%if %length(&user)=0 %then %do;'; put 'data &outds (keep=groupuri groupname groupdesc);'; put 'length groupuri groupname groupdesc group_or_role $256;'; put 'call missing(of _all_);'; put 'i+1;'; put 'do while'; put '(metadata_getnobj("omsobj:IdentityGroup?@Id contains ''.''",i,groupuri)>0);'; put 'rc=metadata_getattr(groupuri, "Name", groupname);'; put 'rc=metadata_getattr(groupuri, "Desc", groupdesc);'; put 'rc=metadata_getattr(groupuri,"PublicType",group_or_role);'; put 'if Group_or_Role = ''UserGroup'' then output;'; put 'i+1;'; put 'end;'; put 'run;'; put '%end;'; put '%else %do;'; put 'data &outds (keep=groupuri groupname groupdesc);'; put 'length uri groupuri groupname groupdesc group_or_role $256;'; put 'call missing(of _all_);'; put 'rc=metadata_getnobj("omsobj:Person?@Name=''&user''",1,uri);'; put 'if rc<=0 then do;'; put 'putlog "%str(WARN)ING: rc=" rc "&user not found "'; put '", or there was an issue reading the repository.";'; put 'stop;'; put 'end;'; put 'a=1;'; put 'grpassn=metadata_getnasn(uri,"IdentityGroups",a,groupuri);'; put 'if grpassn in (-3,-4) then do;'; put 'putlog "%str(WARN)ING: No metadata groups found for &user";'; put 'output;'; put 'end;'; put 'else do while (grpassn > 0);'; put 'rc=metadata_getattr(groupuri, "Name", groupname);'; put 'rc=metadata_getattr(groupuri, "Desc", groupdesc);'; put 'a+1;'; put 'rc=metadata_getattr(groupuri,"PublicType",group_or_role);'; put 'if Group_or_Role = ''UserGroup'' then output;'; put 'grpassn=metadata_getnasn(uri,"IdentityGroups",a,groupuri);'; put 'end;'; put 'run;'; put '%end;'; put '%if &oldrepo ne &repo %then %do;'; put 'options metarepository=&oldrepo;'; put '%end;'; put '%mend mm_getGroups;'; put '%macro dc_getusergroups(user=,outds=mm_getgroups);'; put '%global dc_repo_users;'; put '%if &dc_repo_users= %then %let dc_repo_users=foundation;'; put '%mm_getgroups(user=&user,outds=&outds,repo=&dc_repo_users)'; put '%mend dc_getusergroups;'; put '%macro mpe_getgroups(user=,outds=);'; put '%if not %symexist(dc_repo_users) %then %let dc_repo_users=foundation;'; put '%dc_getusergroups(user=&user,outds=&outds)'; put 'data;'; put 'length groupname groupdesc $256;'; put 'set &dc_libref..mpe_groups;'; put 'where &dc_dttmtfmt. lt tx_to;'; put 'where also upcase(user_name)="%upcase(&user)";'; put 'groupname=group_name;'; put 'groupdesc=group_desc;'; put 'keep groupname groupdesc;'; put 'run;'; put 'data &outds;'; put 'set &syslast &outds(keep=groupname groupdesc);'; put 'run;'; put '%mend mpe_getgroups;'; put '%macro mf_getuniquename(prefix=MC);'; put '&prefix.%substr(%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32-%length(&prefix))'; put '%mend mf_getuniquename;'; put '%macro mf_abort(mac=mf_abort.sas, msg=, iftrue=%str(1=1)'; put ')/des=''ungraceful abort'' /*STORE SOURCE*/;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%put NOTE: /// mf_abort macro executing //;'; put '%if %length(&mac)>0 %then %put NOTE- called by &mac;'; put '%put NOTE - &msg;'; put '%abort;'; put '%mend mf_abort;'; put '/** @endcond */'; put '%macro mf_verifymacvars('; put 'verifyVars /* list of macro variable NAMES */'; put ',makeUpcase=NO /* set to YES to make all the variable VALUES uppercase */'; put ',mAbort=SOFT'; put ')/*/STORE SOURCE*/;'; put '%local verifyIterator verifyVar abortmsg;'; put '%do verifyIterator=1 %to %sysfunc(countw(&verifyVars,%str( )));'; put '%let verifyVar=%qscan(&verifyVars,&verifyIterator,%str( ));'; put '%if not %symexist(&verifyvar) %then %do;'; put '%let abortmsg= Variable &verifyVar is MISSING;'; put '%goto exit_err;'; put '%end;'; put '%if %length(%trim(&&&verifyVar))=0 %then %do;'; put '%let abortmsg= Variable &verifyVar is EMPTY;'; put '%goto exit_err;'; put '%end;'; put '%if &makeupcase=YES %then %do;'; put '%let &verifyVar=%upcase(&&&verifyvar);'; put '%end;'; put '%end;'; put '%goto exit_success;'; put '%exit_err:'; put '%put &abortmsg;'; put '%mf_abort(iftrue=(&mabort ne SOFT),'; put 'mac=mf_verifymacvars,'; put 'msg=%str(&abortmsg)'; put ')'; put '0'; put '%return;'; put '%exit_success:'; put '1'; put '%mend mf_verifymacvars;'; put '%macro mpe_accesscheck('; put 'base_table'; put ',outds=med_accesscheck /* WORK table to contain access details */'; put ',user= /* metadata user to check for */'; put ',access_level=APPROVE'; put ',cntl_lib_var=MPELIB'; put ');'; put '%if &user= %then %let user=%mf_getuser();'; put '%mp_abort('; put 'iftrue=(%index(&outds,.)>0 and %upcase(%scan(&outds,1,.)) ne WORK)'; put ',mac=mpe_accesscheck'; put ',msg=%str(outds should be a WORK table)'; put ')'; put '%mp_abort('; put 'iftrue=(%mf_verifymacvars(base_table user access_level)=0)'; put ',mac=mpe_accesscheck'; put ',msg=%str(Missing base_table/user access_level variables)'; put ')'; put '/* make unique temp table vars */'; put '%local tempds1 tempds2;'; put '%let tempds1=%mf_getuniquename(prefix=usergroups);'; put '%let tempds2=%mf_getuniquename(prefix=tablegroups);'; put '/* get list of user groups */'; put '%mpe_getgroups(user=&user,outds=&tempds1)'; put '/* get list of groups with access for that table */'; put 'proc sql;'; put 'create table &tempds2 as'; put 'select distinct sas_group'; put 'from &&&cntl_lib_var...mpe_security'; put 'where &dc_dttmtfmt. lt tx_to'; put 'and access_level="&access_level"'; put 'and ('; put '(libref="%scan(&base_table,1,.)" and upcase(dsn)="%scan(&base_table,2,.)")'; put 'or (libref="%scan(&base_table,1,.)" and dsn="*ALL*")'; put 'or (libref="*ALL*")'; put ');'; put '%if &_debug ge 131 %then %do;'; put 'data _null_;'; put 'set &tempds1;'; put 'putlog (_all_)(=);'; put 'run;'; put 'data _null_;'; put 'set &tempds2;'; put 'putlog (_all_)(=);'; put 'run;'; put '%end;'; put 'proc sql;'; put 'create table &outds as'; put 'select * from &tempds1'; put 'where groupname="&mpeadmins"'; put 'or groupname in (select * from &tempds2);'; put '%put &sysmacroname: base_table=&base_table;'; put '%put &sysmacroname: access_level=&access_level;'; put '%mend mpe_accesscheck;'; put '%macro mf_getattrn('; put 'libds'; put ',attr'; put ')/*/STORE SOURCE*/;'; put '%local dsid rc;'; put '%let dsid=%sysfunc(open(&libds,is));'; put '%if &dsid = 0 %then %do;'; put '%put %str(WARN)ING: Cannot open %trim(&libds), system message below;'; put '%put %sysfunc(sysmsg());'; put '-1'; put '%end;'; put '%else %do;'; put '%sysfunc(attrn(&dsid,&attr))'; put '%let rc=%sysfunc(close(&dsid));'; put '%end;'; put '%mend mf_getattrn;'; put '%macro mf_existds(libds'; put ')/*/STORE SOURCE*/;'; put '%if %sysfunc(exist(&libds)) ne 1 & %sysfunc(exist(&libds,VIEW)) ne 1 %then 0;'; put '%else 1;'; put '%mend mf_existds;'; put '%macro mpe_alerts(alert_event='; put ', alert_lib='; put ', alert_ds='; put ', dsid='; put ');'; put '/* exit if not configured */'; put '%global DC_EMAIL_ALERTS;'; put '%if &DC_EMAIL_ALERTS ne YES %then %do;'; put '%put DCNOTE: Email alerts are not configured;'; put '%put DCNOTE: (dc_email_alerts=&dc_email_alerts in &mpelib..mpe_config);'; put '%return;'; put '%end;'; put '%let alert_event=%upcase(&alert_event);'; put '%let alert_lib=%upcase(&alert_lib);'; put '%let alert_ds=%upcase(&alert_ds);'; put '%let from_user=%mf_getuser();'; put '/* get users TO which the email should be sent */'; put 'proc sql noprint;'; put 'create table work.users as select distinct a.alert_user,'; put 'b.user_displayname,'; put 'b.user_email'; put 'from &mpelib..mpe_alerts'; put '(where=(&dc_dttmtfmt. lt tx_to)) a'; put 'left join &mpelib..mpe_emails'; put '(where=(&dc_dttmtfmt. lt tx_to)) b'; put 'on upcase(trim(a.alert_user))=upcase(trim(b.user_name))'; put 'where a.alert_event in ("&alert_event","*ALL*")'; put 'and a.alert_lib in ("&alert_lib","*ALL*")'; put 'and a.alert_ds in ("&alert_ds","*ALL*");'; put '/* ensure the submitter is included on the email */'; put '%local isThere userdisp user_eml;'; put '%let isThere=0;'; put 'select count(*) into: isThere from &syslast where alert_user="&from_user";'; put '%if &isThere=0 %then %do;'; put 'select user_displayname, user_email'; put 'into: userdisp trimmed, :user_eml trimmed'; put 'from &mpelib..mpe_emails'; put 'where &dc_dttmtfmt. lt tx_to'; put 'and user_name="&from_user";'; put 'insert into work.users'; put 'set alert_user="&from_user"'; put ',user_displayname="&userdisp"'; put ',user_email="&user_eml";'; put '%end;'; put '/* if no email / displayname is provided, then extract from metadata */'; put 'data work.emails;'; put 'set work.users;'; put 'length emailuri uri text $256; call missing(emailuri,uri); drop emailuri uri;'; put '/* get displayname */'; put 'text=cats("omsobj:Person?@Name=''",alert_user,"''");'; put 'if metadata_getnobj(text,1,uri)<=0 then do;'; put 'putlog "DCWARN: &from_user not found";'; put 'return;'; put 'end;'; put 'else if user_displayname = '''' then do;'; put 'if metadata_getattr(uri,''DisplayName'',user_displayname)<0 then do;'; put 'putlog ''DCWARN: strange err, no displayname attribute of user URI'';'; put 'end;'; put 'end;'; put 'if index(user_email,''@'') then return;'; put '/* get email from metadata if not in input table */'; put 'if metadata_getnasn(uri,"EmailAddresses",1,emailuri)<=0 then do;'; put 'putlog "DCWARN: " alert_user " has no emails in MPE_EMAILS or metadata!";'; put 'if metadata_getattr(emailuri,"Address",user_email)<0 then do;'; put 'putlog ''DCWARN: Unexpected error! Valid emailURI but no email. Weird.'';'; put 'end;'; put 'end;'; put '/* only keep valid emails */'; put 'if index(user_email,''@'') ;'; put '/* dump contents for debugging */'; put 'if _n_<21 then putlog (_all_)(=);'; put 'run;'; put '%local emails;'; put 'proc sql noprint;'; put 'select quote(trim(user_email)) into: emails separated by '' '' from work.emails;'; put '/* exit if nobody to email */'; put '%if %mf_getattrn(emails,NLOBS)=0 %then %do;'; put '%put NOTE: No alerts configured (mpe_alerts.sas);'; put '%return;'; put '%end;'; put '/* display email options */'; put 'data _null_;'; put 'set sashelp.voption(where=(group=''EMAIL''));'; put 'put optname ''='' setting;'; put 'run;'; put 'filename __out email (&emails)'; put 'subject="Table &alert_lib..&alert_ds has been &alert_event";'; put '%local SUBMITTED_TXT;'; put '%if &alert_event=SUBMITTED %then %do;'; put 'data _null_;'; put 'set &mpelib..mpe_submit;'; put 'where table_id="&dsid" and submit_status_cd=''SUBMITTED'';'; put 'call symputx(''SUBMITTED_TXT'',submitted_reason_txt,''l'');'; put 'run;'; put 'data _null_;'; put 'File __out lrecl=32000;'; put 'put ''Dear user,'';'; put 'put '' '';'; put 'put "Please be advised that a change to table &alert_lib..&alert_ds has "'; put '"been proposed by &from_user on the ''&syshostname'' SAS server.";'; put 'put " ";'; put 'length txt $2048;'; put 'txt=symget(''SUBMITTED_TXT'');'; put 'put "Reason provided: " txt;'; put 'put " ";'; put 'put "This is an automated email by Data Controller for SAS. For "'; put '"documentation, please visit https://docs.datacontroller.io";'; put 'run;'; put '%end;'; put '%else %if &alert_event=APPROVED %then %do;'; put '/* there is no approval message */'; put 'data _null_;'; put 'File __out lrecl=32000;'; put 'put ''Dear user,'';'; put 'put '' '';'; put 'put "Please be advised that a change to table &alert_lib..&alert_ds has "'; put '"been approved by &from_user on the ''&syshostname'' SAS server.";'; put 'put " ";'; put 'put "This is an automated email by Data Controller for SAS. For "'; put '"documentation, please visit https://docs.datacontroller.io";'; put 'run;'; put '%end;'; put '%else %if &alert_event=REJECTED %then %do;'; put 'data _null_;'; put 'set &mpelib..mpe_review;'; put 'where table_id="&dsid" and review_status_id=''REJECTED'';'; put 'call symputx(''REVIEW_REASON_TXT'',REVIEW_REASON_TXT,''l'');'; put 'run;'; put 'data _null_;'; put 'File __out lrecl=32000;'; put 'put ''Dear user,'';'; put 'put '' '';'; put 'put "Please be advised that a change to table &alert_lib..&alert_ds has "'; put '"been rejected by &from_user on the ''&syshostname'' SAS server.";'; put 'put " ";'; put 'length txt $2048;'; put 'txt=symget(''REVIEW_REASON_TXT'');'; put 'put "Reason provided: " txt;'; put 'put " ";'; put 'put "This is an automated email by Data Controller for SAS. For "'; put '"documentation, please visit https://docs.datacontroller.io";'; put 'run;'; put '%end;'; put 'filename __out clear;'; put '%mend mpe_alerts ;'; put '%macro mpe_xlmapvalidate(mperef,inds,dclib,tgtds);'; put '%local ismap;'; put 'proc sql noprint;'; put 'select count(*) into: ismap'; put 'from &dclib..mpe_xlmap_info'; put 'where XLMAP_TARGETLIBDS="&tgtds" and &dc_dttmtfmt. le TX_TO ;'; put '%if "&tgtds"="&dclib..MPE_XLMAP_DATA" or &ismap>0 %then %do;'; put 'data &inds;'; put 'set &inds;'; put 'LOAD_REF="&mperef";'; put 'run;'; put '%end;'; put '%mend mpe_xlmapvalidate;'; put '%macro mpe_loadfail('; put 'status=FAILED - &syscc'; put ',now=%sysfunc(datetime())'; put ',approvals='; put ',mperef='; put ',reason_txt='; put ',mac=mpe_loadfail.sas'; put ',dc_dttmtfmt=E8601DT26.6'; put ');'; put '/* do not perform duration calc in pass through */'; put '%local dur;'; put 'data _null_;'; put 'now=symget(''now'');'; put 'dur=%sysfunc(datetime())-&now;'; put 'call symputx(''dur'',dur,''l'');'; put 'run;'; put 'proc sql;'; put 'update &mpelib..mpe_loads'; put 'set STATUS=symget(''status'')'; put ', duration=&dur'; put ', processed_dttm=&dc_dttmtfmt.'; put ', approvals = symget(''approvals'')'; put ', reason_txt= symget(''reason_txt'')'; put 'where CSV_DIR="&mperef";'; put '%let syscc=666;'; put '%mp_abort(msg=%superq(status)\n%superq(reason_txt),mac=&mac)'; put '%mend mpe_loadfail;'; put '%macro dc_getservicecode(loc=,outref=);'; put '%mm_getstpcode(tree=&loc'; put ',outref=&outref'; put ')'; put '%mend dc_getservicecode;'; put '%macro mp_include(fileref'; put ',prefix=_'; put ',opts=SOURCE2'; put ',errds=work.mp_abort_errds'; put ')/*/STORE SOURCE*/;'; put '/* prepare precode */'; put '%local tempref;'; put '%let tempref=%mf_getuniquefileref();'; put 'data _null_;'; put 'file &tempref;'; put 'set sashelp.vextfl(where=(fileref="%upcase(&fileref)"));'; put 'put ''%let _SYSINCLUDEFILEDEVICE='' xengine '';'';'; put 'name=scan(xpath,-1,''/\'');'; put 'put ''%let _SYSINCLUDEFILENAME='' name '';'';'; put 'path=subpad(xpath,1,length(xpath)-length(name)-1);'; put 'put ''%let _SYSINCLUDEFILEDIR='' path '';'';'; put 'put ''%let _SYSINCLUDEFILEFILEREF='' "&fileref;";'; put 'run;'; put '/* prepare the errds */'; put 'data &errds;'; put 'length msg mac $1000;'; put 'call missing(msg,mac);'; put 'iftrue=''1=0'';'; put 'run;'; put '/* include the include */'; put '%inc &tempref &fileref/&opts;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=%str(&_SYSINCLUDEFILEDIR/&_SYSINCLUDEFILENAME)'; put ',msg=%str(syscc=&syscc after executing &_SYSINCLUDEFILENAME)'; put ')'; put 'filename &tempref clear;'; put '%mend mp_include;'; put '%macro mpe_runhook(hookvar);'; put '%local pgmloc pgmtype;'; put '%let pgmtype=0;'; put '%put &sysmacroname: &=hookvar;'; put '%if %length(&&&hookvar)>0 %then %do;'; put '%put &sysmacroname: Executing &&&hookvar;'; put 'data _null_;'; put 'rule_value=symget("&hookvar");'; put 'if scan(upcase(rule_value),-1,''.'')=''SAS'' then do;'; put 'call symputx(''pgmtype'',''PGM'');'; put 'call symputx(''pgmloc'',rule_value);'; put 'end;'; put 'else do;'; put 'apploc="%mf_getapploc()";'; put 'if substr(rule_value,1,1) ne ''/'''; put 'then rule_value=cats(apploc,''/'',rule_value);'; put 'call symputx(''pgmloc'',rule_value);'; put 'call symputx(''pgmtype'',''JOB'');'; put 'end;'; put 'run;'; put '%if &pgmtype=PGM %then %do;'; put 'filename sascode "&pgmloc";'; put '%end;'; put '%else %do;'; put '%dc_getservicecode(loc=&pgmloc'; put ',outref=sascode'; put ')'; put '%end;'; put '/* the below script will need to modify work.STAGING_DS */'; put '%local x; %let x=; /* legacy feature */'; put '%mp_include(sascode)'; put '%end;'; put '%mend mpe_runhook;'; put '%macro mm_assignlib('; put 'libref'; put ',mAbort=HARD'; put ')/*/STORE SOURCE*/;'; put '%local mp_abort msg;'; put '%let mp_abort=0;'; put '%if %sysfunc(libref(&libref)) %then %do;'; put 'data _null_;'; put 'length liburi LibName msg $200;'; put 'call missing(of _all_);'; put 'nobj=metadata_getnobj("omsobj:SASLibrary?@Libref=''&libref''",1,liburi);'; put 'if nobj=1 then do;'; put 'rc=metadata_getattr(liburi,"Name",LibName);'; put '/* now try and assign it */'; put 'if libname("&libref",,''meta'',cats(''liburi="'',liburi,''";'')) ne 0 then do;'; put 'putlog "&libref could not be assigned";'; put 'putlog liburi=;'; put '/**'; put '* Fetch the system message for display in the abort modal. This is'; put '* not always helpful though. One example, previously received:'; put '* NOTE: Libref XX refers to the same library metadata as libref XX.'; put '*/'; put 'msg=sysmsg();'; put 'if msg=:''ERROR: Libref SAVE is not assigned.'' then do;'; put 'msg=catx(" ",'; put '"Could not assign %upcase(&libref).",'; put '"Please check metadata permissions! Libname:",libname,'; put '"Liburi:",liburi'; put ');'; put 'end;'; put 'else if msg="ERROR: User does not have appropriate authorization "!!'; put '"level for library SAVE."'; put 'then do;'; put 'msg=catx(" ",'; put '"ERROR: User does not have appropriate authorization level",'; put '"for library %upcase(&libref), libname:",libname,'; put '"Liburi:",liburi'; put ');'; put 'end;'; put 'call symputx(''msg'',msg,''l'');'; put 'if "&mabort"=''HARD'' then call symputx(''mp_abort'',1,''l'');'; put 'end;'; put 'else do;'; put 'put (_all_)(=);'; put 'call symputx(''libname'',libname,''L'');'; put 'call symputx(''liburi'',liburi,''L'');'; put 'end;'; put 'end;'; put 'else if nobj>1 then do;'; put 'if "&mabort"=''HARD'' then call symputx(''mp_abort'',1);'; put 'call symputx(''msg'',"More than one library with libref=&libref");'; put 'end;'; put 'else do;'; put 'if "&mabort"=''HARD'' then call symputx(''mp_abort'',1);'; put 'call symputx(''msg'',"Library &libref not found in metadata");'; put 'end;'; put 'run;'; put '%put NOTE: &msg;'; put '%end;'; put '%else %do;'; put '%put NOTE: Library &libref is already assigned;'; put '%end;'; put '%mp_abort(iftrue= (&mp_abort=1)'; put ',mac=mm_assignlib.sas'; put ',msg=%superq(msg)'; put ')'; put '%mend mm_assignlib;'; put '/** @cond */'; put '%macro mf_getengine(libref'; put ')/*/STORE SOURCE*/;'; put '%local dsid engnum rc engine;'; put '/* in case the parameter is a libref.tablename, pull off just the libref */'; put '%let libref = %upcase(%scan(&libref, 1, %str(.)));'; put '%let dsid=%sysfunc('; put 'open(sashelp.vlibnam(where=(libname="%upcase(&libref)")),i)'; put ');'; put '%if (&dsid ^= 0) %then %do;'; put '%let engnum=%sysfunc(varnum(&dsid,ENGINE));'; put '%let rc=%sysfunc(fetch(&dsid));'; put '%let engine=%sysfunc(getvarc(&dsid,&engnum));'; put '%put &libref. ENGINE is &engine.;'; put '%let rc= %sysfunc(close(&dsid));'; put '%end;'; put '%upcase(&engine)'; put '%mend mf_getengine;'; put '/** @endcond */'; put '%macro mm_assigndirectlib('; put 'libref'; put ',open_passthrough='; put ',sql_options='; put ',mDebug=0'; put ',mAbort=0'; put ')/*/STORE SOURCE*/;'; put '%local mD;'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing mm_assigndirectlib.sas;'; put '%&mD.put _local_;'; put '%if &mAbort=1 %then %let mAbort=;'; put '%else %let mAbort=%str(*);'; put '%&mD.put NOTE: Creating direct (non META) connection to &libref library;'; put '%local cur_engine;'; put '%let cur_engine=%mf_getengine(&libref);'; put '%if &cur_engine ne META and &cur_engine ne %then %do;'; put '%put NOTE: &libref already has a direct (&cur_engine) libname connection;'; put '%return;'; put '%end;'; put '%else %if %upcase(&libref)=WORK %then %do;'; put '%put NOTE: We already have a direct connection to WORK :-) ;'; put '%return;'; put '%end;'; put '/* need to determine the library ENGINE first */'; put '%local engine;'; put 'data _null_;'; put 'length lib_uri engine $256;'; put 'call missing (of _all_);'; put '/* get URI for the particular library */'; put 'rc1=metadata_getnobj("omsobj:SASLibrary?@Libref =''&libref''",1,lib_uri);'; put '/* get the Engine attribute of the previous object */'; put 'rc2=metadata_getattr(lib_uri,''Engine'',engine);'; put 'putlog "mm_assigndirectlib for &libref:" rc1= lib_uri= rc2= engine=;'; put 'call symputx("liburi",lib_uri,''l'');'; put 'call symputx("engine",engine,''l'');'; put 'run;'; put '/* now obtain engine specific connection details */'; put '%if &engine=BASE %then %do;'; put '%&mD.put NOTE: Retrieving BASE library path;'; put 'data _null_;'; put 'length up_uri $256 path cat_path $1024;'; put 'retain cat_path;'; put 'call missing (of _all_);'; put '/* get all the filepaths of the UsingPackages association */'; put 'i=1;'; put 'rc3=metadata_getnasn("&liburi",''UsingPackages'',i,up_uri);'; put 'do while (rc3>0);'; put '/* get the DirectoryName attribute of the previous object */'; put 'rc4=metadata_getattr(up_uri,''DirectoryName'',path);'; put 'if i=1 then path = ''("''!!trim(path)!!''" '';'; put 'else path ='' "''!!trim(path)!!''" '';'; put 'cat_path = trim(cat_path) !! " " !! trim(path) ;'; put 'i+1;'; put 'rc3=metadata_getnasn("&liburi",''UsingPackages'',i,up_uri);'; put 'end;'; put 'cat_path = trim(cat_path) !! ")";'; put '&mD.putlog "NOTE: Getting physical path for &libref library";'; put '&mD.putlog rc3= up_uri= rc4= cat_path= path=;'; put '&mD.putlog "NOTE: Libname cmd will be:";'; put '&mD.putlog "libname &libref" cat_path;'; put 'call symputx("filepath",cat_path,''l'');'; put 'run;'; put '%if %sysevalf(&sysver<9.4) %then %do;'; put 'libname &libref &filepath;'; put '%end;'; put '%else %do;'; put '/* apply the new filelocks option to cater for temporary locks */'; put 'libname &libref &filepath filelockwait=5;'; put '%end;'; put '%end;'; put '%else %if &engine=REMOTE %then %do;'; put 'data x;'; put 'length rcCon rcProp rc k 3 uriCon uriProp PropertyValue PropertyName'; put 'Delimiter $256 properties $2048;'; put 'retain properties;'; put 'rcCon = metadata_getnasn("&liburi", "LibraryConnection", 1, uriCon);'; put 'rcProp = metadata_getnasn(uriCon, "Properties", 1, uriProp);'; put 'k = 1;'; put 'rcProp = metadata_getnasn(uriCon, "Properties", k, uriProp);'; put 'do while (rcProp > 0);'; put 'rc = metadata_getattr(uriProp , "DefaultValue",PropertyValue);'; put 'rc = metadata_getattr(uriProp , "PropertyName",PropertyName);'; put 'rc = metadata_getattr(uriProp , "Delimiter",Delimiter);'; put 'properties = trim(properties) !! " " !! trim(PropertyName)'; put '!! trim(Delimiter) !! trim(PropertyValue);'; put 'output;'; put 'k+1;'; put 'rcProp = metadata_getnasn(uriCon, "Properties", k, uriProp);'; put 'end;'; put '%&mD.put NOTE: Getting properties for REMOTE SHARE &libref library;'; put '&mD.put _all_;'; put '%&mD.put NOTE: Libname cmd will be:;'; put '%&mD.put libname &libref &engine &properties slibref=&libref;'; put 'call symputx ("properties",trim(properties),''l'');'; put 'run;'; put 'libname &libref &engine &properties slibref=&libref;'; put '%end;'; put '%else %if &engine=OLEDB %then %do;'; put '%&mD.put NOTE: Retrieving OLEDB connection details;'; put 'data _null_;'; put 'length domain datasource provider properties schema'; put 'connx_uri domain_uri conprop_uri lib_uri schema_uri value $256.;'; put 'call missing (of _all_);'; put '/* get source connection ID */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,connx_uri);'; put '/* get connection domain */'; put 'rc1=metadata_getnasn(connx_uri,''Domain'',1,domain_uri);'; put 'rc2=metadata_getattr(domain_uri,''Name'',domain);'; put '&mD.putlog / ''NOTE: '' // ''NOTE- connection id: '' connx_uri ;'; put '&mD.putlog ''NOTE- domain: '' domain;'; put '/* get DSN and PROVIDER from connection properties */'; put 'i=0;'; put 'do until (rc<0);'; put 'i+1;'; put 'rc=metadata_getnasn(connx_uri,''Properties'',i,conprop_uri);'; put 'rc2=metadata_getattr(conprop_uri,''Name'',value);'; put 'if value=''Connection.OLE.Property.DATASOURCE.Name.xmlKey.txt'' then do;'; put 'rc3=metadata_getattr(conprop_uri,''DefaultValue'',datasource);'; put 'end;'; put 'else if value=''Connection.OLE.Property.PROVIDER.Name.xmlKey.txt'' then do;'; put 'rc4=metadata_getattr(conprop_uri,''DefaultValue'',provider);'; put 'end;'; put 'else if value=''Connection.OLE.Property.PROPERTIES.Name.xmlKey.txt'' then'; put 'do;'; put 'rc5=metadata_getattr(conprop_uri,''DefaultValue'',properties);'; put 'end;'; put 'end;'; put '&mD.putlog ''NOTE- dsn/provider/properties: '' /'; put 'datasource provider properties;'; put '&mD.putlog ''NOTE- schema: '' schema // ''NOTE-'';'; put '/* get SCHEMA */'; put 'rc6=metadata_getnasn("&liburi",''UsingPackages'',1,lib_uri);'; put 'rc7=metadata_getattr(lib_uri,''SchemaName'',schema);'; put 'call symputx(''SQL_domain'',domain,''l'');'; put 'call symputx(''SQL_dsn'',datasource,''l'');'; put 'call symputx(''SQL_provider'',provider,''l'');'; put 'call symputx(''SQL_properties'',properties,''l'');'; put 'call symputx(''SQL_schema'',schema,''l'');'; put 'run;'; put '%if %length(&open_passthrough)>0 %then %do;'; put 'proc sql &sql_options;'; put 'connect to OLEDB as &open_passthrough(INSERT_SQL=YES'; put '/* need additional properties to make this work */'; put 'properties=(''Integrated Security''=SSPI'; put '''Persist Security Info''=True'; put '%sysfunc(compress(%str(&SQL_properties),%str(())))'; put ')'; put 'DATASOURCE=&sql_dsn PROMPT=NO'; put 'PROVIDER=&sql_provider SCHEMA=&sql_schema CONNECTION = GLOBAL);'; put '%end;'; put '%else %do;'; put 'LIBNAME &libref OLEDB PROPERTIES=&sql_properties'; put 'DATASOURCE=&sql_dsn PROVIDER=&sql_provider SCHEMA=&sql_schema'; put '%if %length(&sql_domain)>0 %then %do;'; put 'authdomain="&sql_domain"'; put '%end;'; put 'connection=shared;'; put '%end;'; put '%end;'; put '%else %if &engine=ODBC %then %do;'; put '%&mD.put NOTE: Retrieving ODBC connection details;'; put 'data _null_;'; put 'length connx_uri conprop_uri value datasource up_uri schema domprop_uri authdomain $256.;'; put 'call missing (of _all_);'; put '/* get source connection ID */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,connx_uri);'; put '/* get connection properties */'; put 'i=0;'; put 'do until (rc2<0);'; put 'i+1;'; put 'rc2=metadata_getnasn(connx_uri,''Properties'',i,conprop_uri);'; put 'rc3=metadata_getattr(conprop_uri,''Name'',value);'; put 'if value=''Connection.ODBC.Property.DATASRC.Name.xmlKey.txt'' then do;'; put 'rc4=metadata_getattr(conprop_uri,''DefaultValue'',datasource);'; put 'rc2=-1;'; put 'end;'; put 'end;'; put '/* get auth domain */'; put 'autrc=metadata_getnasn(connx_uri,"Domain",1,domprop_uri);'; put 'arc=metadata_getattr(domprop_uri,"Name",authdomain);'; put 'if not missing(authdomain) then authdomain=cats(''AUTHDOMAIN='',authdomain);'; put 'call symputx(''authdomain'',authdomain,''l'');'; put '/* get SCHEMA */'; put 'rc6=metadata_getnasn("&liburi",''UsingPackages'',1,up_uri);'; put 'rc7=metadata_getattr(up_uri,''SchemaName'',schema);'; put '&mD.put rc= connx_uri= rc2= conprop_uri= rc3= value= rc4= datasource='; put 'rc6= up_uri= rc7= schema=;'; put 'call symputx(''SQL_schema'',schema,''l'');'; put 'call symputx(''SQL_dsn'',datasource,''l'');'; put 'run;'; put '%if %length(&open_passthrough)>0 %then %do;'; put 'proc sql &sql_options;'; put 'connect to ODBC as &open_passthrough'; put '(INSERT_SQL=YES DATASRC=&sql_dsn. CONNECTION=global);'; put '%end;'; put '%else %do;'; put 'libname &libref ODBC DATASRC=&sql_dsn SCHEMA=&sql_schema &authdomain;'; put '%end;'; put '%end;'; put '%else %if &engine=POSTGRES %then %do;'; put '%put NOTE: Obtaining POSTGRES library details;'; put 'data _null_;'; put 'length database ignore_read_only_columns direct_exe preserve_col_names'; put 'preserve_tab_names server schema authdomain user password'; put 'prop name value uri urisrc $256.;'; put 'call missing (of _all_);'; put '/* get database value */'; put 'prop=''Connection.DBMS.Property.DB.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,database,"");'; put 'if database^='''' then database=''database=''!!quote(trim(database));'; put 'call symputx(''database'',database,''l'');'; put '/* get IGNORE_READ_ONLY_COLUMNS value */'; put 'prop=''Library.DBMS.Property.DBIROC.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,ignore_read_only_columns,"");'; put 'if ignore_read_only_columns^='''' then ignore_read_only_columns='; put '''ignore_read_only_columns=''!!ignore_read_only_columns;'; put 'call symputx(''ignore_read_only_columns'',ignore_read_only_columns,''l'');'; put '/* get DIRECT_EXE value */'; put 'prop=''Library.DBMS.Property.DirectExe.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,direct_exe,"");'; put 'if direct_exe^='''' then direct_exe=''direct_exe=''!!direct_exe;'; put 'call symputx(''direct_exe'',direct_exe,''l'');'; put '/* get PRESERVE_COL_NAMES value */'; put 'prop=''Library.DBMS.Property.PreserveColNames.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,preserve_col_names,"");'; put 'if preserve_col_names^='''' then preserve_col_names='; put '''preserve_col_names=''!!preserve_col_names;'; put 'call symputx(''preserve_col_names'',preserve_col_names,''l'');'; put '/* get PRESERVE_TAB_NAMES value */'; put '/* be careful with PRESERVE_TAB_NAMES=YES - it will mean your table will'; put 'become case sensitive!! */'; put 'prop=''Library.DBMS.Property.PreserveTabNames.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,preserve_tab_names,"");'; put 'if preserve_tab_names^='''' then preserve_tab_names='; put '''preserve_tab_names=''!!preserve_tab_names;'; put 'call symputx(''preserve_tab_names'',preserve_tab_names,''l'');'; put '/* get SERVER value */'; put 'if metadata_getnasn("&liburi","LibraryConnection",1,uri)>0 then do;'; put 'prop=''Connection.DBMS.Property.SERVER.Name.xmlKey.txt'';'; put 'rc=metadata_getprop(uri,prop,server,"");'; put 'end;'; put 'if server^='''' then server=''server=''!!quote(cats(server));'; put 'call symputx(''server'',server,''l'');'; put '/* get SCHEMA value */'; put 'if metadata_getnasn("&liburi","UsingPackages",1,uri)>0 then do;'; put 'rc=metadata_getattr(uri,"SchemaName",schema);'; put 'end;'; put 'if schema^='''' then schema=''schema=''!!schema;'; put 'call symputx(''schema'',schema,''l'');'; put '/* get AUTHDOMAIN value */'; put '/* this is only useful if the user account contains that auth domain'; put 'if metadata_getnasn("&liburi","DefaultLogin",1,uri)>0 then do;'; put 'rc=metadata_getnasn(uri,"Domain",1,urisrc);'; put 'rc=metadata_getattr(urisrc,"Name",authdomain);'; put 'end;'; put 'if authdomain^='''' then authdomain=''authdomain=''!!quote(trim(authdomain));'; put '*/'; put 'call symputx(''authdomain'',authdomain,''l'');'; put '/* get user & pass */'; put 'if authdomain='''' & metadata_getnasn("&liburi","DefaultLogin",1,uri)>0 then'; put 'do;'; put 'rc=metadata_getattr(uri,"UserID",user);'; put 'rc=metadata_getattr(uri,"Password",password);'; put 'end;'; put 'if user^='''' then do;'; put 'user=''user=''!!quote(trim(user));'; put 'password=''password=''!!quote(trim(password));'; put 'end;'; put 'call symputx(''user'',user,''l'');'; put 'call symputx(''password'',password,''l'');'; put '&md.put _all_;'; put 'run;'; put '%if %length(&open_passthrough)>0 %then %do;'; put '%put %str(WARN)ING: Passthrough option for postgres not yet supported;'; put '%return;'; put '%end;'; put '%else %do;'; put '%if &mdebug=1 %then %do;'; put '%put NOTE: Executing the following:/;'; put '%put NOTE- libname &libref POSTGRES &database &ignore_read_only_columns;'; put '%put NOTE- &direct_exe &preserve_col_names &preserve_tab_names;'; put '%put NOTE- &server &schema &authdomain &user &password //;'; put '%end;'; put 'libname &libref POSTGRES &database &ignore_read_only_columns &direct_exe'; put '&preserve_col_names &preserve_tab_names &server &schema &authdomain'; put '&user &password;'; put '%end;'; put '%end;'; put '%else %if &engine=ORACLE %then %do;'; put '%put NOTE: Obtaining &engine library details;'; put 'data _null_;'; put 'length assocuri1 assocuri2 assocuri3 authdomain path schema $256;'; put 'call missing (of _all_);'; put '/* get auth domain */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,assocuri1);'; put 'rc=metadata_getnasn(assocuri1,''Domain'',1,assocuri2);'; put 'rc=metadata_getattr(assocuri2,"Name",authdomain);'; put 'call symputx(''authdomain'',authdomain,''l'');'; put '/* path */'; put 'rc=metadata_getprop(assocuri1,'; put '''Connection.Oracle.Property.PATH.Name.xmlKey.txt'',path);'; put 'call symputx(''path'',path,''l'');'; put '/* schema */'; put 'rc=metadata_getnasn("&liburi",''UsingPackages'',1,assocuri3);'; put 'rc=metadata_getattr(assocuri3,''SchemaName'',schema);'; put 'call symputx(''schema'',schema,''l'');'; put 'run;'; put '%put NOTE: Executing the following:/; %put NOTE-;'; put '%put NOTE- libname &libref ORACLE path=&path schema=&schema;'; put '%put NOTE- authdomain=&authdomain;'; put '%put NOTE-;'; put 'libname &libref ORACLE path=&path schema=&schema authdomain=&authdomain;'; put '%end;'; put '%else %if &engine=SQLSVR %then %do;'; put '%put NOTE: Obtaining &engine library details;'; put 'data _null;'; put 'length assocuri1 assocuri2 assocuri3 authdomain path schema userid'; put 'passwd $256;'; put 'call missing (of _all_);'; put 'rc=metadata_getnasn("&liburi",''DefaultLogin'',1,assocuri1);'; put 'rc=metadata_getattr(assocuri1,"UserID",userid);'; put 'rc=metadata_getattr(assocuri1,"Password",passwd);'; put 'call symputx(''user'',userid,''l'');'; put 'call symputx(''pass'',passwd,''l'');'; put '/* path */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,assocuri2);'; put 'rc=metadata_getprop(assocuri2,'; put '''Connection.SQL.Property.Datasrc.Name.xmlKey.txt'',path);'; put 'call symputx(''path'',path,''l'');'; put '/* schema */'; put 'rc=metadata_getnasn("&liburi",''UsingPackages'',1,assocuri3);'; put 'rc=metadata_getattr(assocuri3,''SchemaName'',schema);'; put 'call symputx(''schema'',schema,''l'');'; put 'run;'; put '%put NOTE: Executing the following:/; %put NOTE-;'; put '%put NOTE- libname &libref SQLSVR datasrc=&path schema=&schema ;'; put '%put NOTE- user="&user" pass="XXX";'; put '%put NOTE-;'; put 'libname &libref SQLSVR datasrc=&path schema=&schema user="&user" pass="&pass";'; put '%end;'; put '%else %if &engine=TERADATA %then %do;'; put '%put NOTE: Obtaining &engine library details;'; put 'data _null;'; put 'length assocuri1 assocuri2 assocuri3 authdomain path schema userid'; put 'passwd $256;'; put 'call missing (of _all_);'; put '/* get auth domain */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,assocuri1);'; put 'rc=metadata_getnasn(assocuri1,''Domain'',1,assocuri2);'; put 'rc=metadata_getattr(assocuri2,"Name",authdomain);'; put 'call symputx(''authdomain'',authdomain,''l'');'; put '/*'; put 'rc=metadata_getnasn("&liburi",''DefaultLogin'',1,assocuri1);'; put 'rc=metadata_getattr(assocuri1,"UserID",userid);'; put 'rc=metadata_getattr(assocuri1,"Password",passwd);'; put 'call symputx(''user'',userid,''l'');'; put 'call symputx(''pass'',passwd,''l'');'; put '*/'; put '/* path */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,assocuri2);'; put 'rc=metadata_getprop(assocuri2,'; put '''Connection.Teradata.Property.SERVER.Name.xmlKey.txt'',path);'; put 'call symputx(''path'',path,''l'');'; put '/* schema */'; put 'rc=metadata_getnasn("&liburi",''UsingPackages'',1,assocuri3);'; put 'rc=metadata_getattr(assocuri3,''SchemaName'',schema);'; put 'call symputx(''schema'',schema,''l'');'; put 'run;'; put '%put NOTE: Executing the following:/; %put NOTE-;'; put '%put NOTE- libname &libref TERADATA server="&path" schema=&schema ;'; put '%put NOTe- authdomain=&authdomain;'; put '%put NOTE-;'; put 'libname &libref TERADATA server="&path" schema=&schema authdomain=&authdomain;'; put '%end;'; put '%else %if &engine= %then %do;'; put '%put NOTE: Libref &libref is not registered in metadata;'; put '%&mAbort.mp_abort('; put 'msg=%str(ERR)OR: Libref &libref is not registered in metadata'; put ',mac=mm_assigndirectlib.sas);'; put '%return;'; put '%end;'; put '%else %do;'; put '%put %str(WARN)ING: Engine &engine is currently unsupported;'; put '%put %str(WARN)ING- Please contact your support team.;'; put '%return;'; put '%end;'; put '%mend mm_assigndirectlib;'; put '%macro mm_getrepos('; put 'outds=work.mm_getrepos'; put ')/*/STORE SOURCE*/;'; put '* use a temporary fileref to hold the response;'; put 'filename response temp;'; put '/* get list of libraries */'; put 'proc metadata in='; put '"1"'; put 'out=response;'; put 'run;'; put '/* write the response to the log for debugging */'; put '/*'; put 'data _null_;'; put 'infile response lrecl=1048576;'; put 'input;'; put 'put _infile_;'; put 'run;'; put '*/'; put '/* create an XML map to read the response */'; put 'filename sxlemap temp;'; put 'data _null_;'; put 'file sxlemap;'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository";'; put 'put "";'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Id";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Name";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Desc";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@DefaultNS";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@RepositoryType";'; put 'put "characterstring20";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@RepositoryFormat";'; put 'put "characterstring10";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@Access";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@CurrentAccess";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@PauseState";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Path";'; put 'put "";'; put 'put "characterstring256";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Engine";'; put 'put "";'; put 'put "characterstring8";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Options";'; put 'put "";'; put 'put "characterstring32";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@MetadataCreated";'; put 'put "characterstring24";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@MetadataUpdated";'; put 'put "characterstring24";'; put 'put '''';'; put 'put ''
'';'; put 'run;'; put 'libname _XML_ xml xmlfileref=response xmlmap=sxlemap;'; put 'proc sort data= _XML_.SASRepos out=&outds;'; put 'by name;'; put 'run;'; put '/* clear references */'; put 'filename sxlemap clear;'; put 'filename response clear;'; put 'libname _XML_ clear;'; put '%mend mm_getrepos;'; put '%macro dc_assignlib(type,libref,passthru=);'; put '%put &sysmacroname entry vars:;'; put '%put _local_;'; put '/* take current repository */'; put '%local repo repocnt x;'; put '%let repo=%sysfunc(getoption(metarepository));'; put '/* get list of repositories and filter */'; put '%mm_getrepos(outds=repos)'; put '%let repocnt=0;'; put 'data repos;'; put 'set repos;'; put 'where repositorytype in(''CUSTOM'',''FOUNDATION'')'; put '& upcase(name) ne "%upcase(&repo)";'; put 'keep id name ;'; put 'call symputx(cats(''repo'',_n_),name,''l'');'; put 'call symputx(''repocnt'',_n_,''l'');'; put 'run;'; put '/* find out which of these repositories has the libref we are searching for */'; put '%local lib_uri;'; put '%let lib_uri=NOTFOUND;'; put 'data _null_; /* check default repo first */'; put 'length lib_uri $256;'; put 'call missing (of _all_);'; put 'rc=metadata_getnobj("omsobj:SASLibrary?@Libref =''&libref''",1,lib_uri);'; put 'call symputx(''lib_uri'',coalescec(lib_uri,''NOTFOUND''),''l'');'; put 'run;'; put '%if &lib_uri=NOTFOUND %then %do;'; put '%do x=1 %to &repocnt;'; put 'options metarepository=&&repo&x;'; put 'data _null_;'; put 'length lib_uri $256;'; put 'call missing (of _all_);'; put 'rc=metadata_getnobj("omsobj:SASLibrary?@Libref =''&libref''",1,lib_uri);'; put 'call symputx(''lib_uri'',coalescec(lib_uri,''NOTFOUND''),''l'');'; put 'run;'; put '%if &lib_uri ne NOTFOUND %then %let x=%eval(&repocnt+1);'; put '%end;'; put '%end;'; put '%if &type=READ %then %do;'; put '%mm_assignlib(&libref)'; put '%end;'; put '%else %if &type=WRITE %then %do;'; put '%mm_assigndirectlib(&libref,open_passthrough=&passthru)'; put '%end;'; put 'options metarepository=&repo;'; put '%mend dc_assignlib;'; put '%macro mf_mkdir(dir'; put ')/*/STORE SOURCE*/;'; put '%local lastchar child parent;'; put '%let lastchar = %substr(&dir, %length(&dir));'; put '%if (%bquote(&lastchar) eq %str(:)) %then %do;'; put '/* Cannot create drive mappings */'; put '%return;'; put '%end;'; put '%if (%bquote(&lastchar)=%str(/)) or (%bquote(&lastchar)=%str(\)) %then %do;'; put '/* last char is a slash */'; put '%if (%length(&dir) eq 1) %then %do;'; put '/* one single slash - root location is assumed to exist */'; put '%return;'; put '%end;'; put '%else %do;'; put '/* strip last slash */'; put '%let dir = %substr(&dir, 1, %length(&dir)-1);'; put '%end;'; put '%end;'; put '%if (%sysfunc(fileexist(%bquote(&dir))) = 0) %then %do;'; put '/* directory does not exist so prepare to create */'; put '/* first get the childmost directory */'; put '%let child = %scan(&dir, -1, %str(/\:));'; put '/*'; put 'If child name = path name then there are no parents to create. Else'; put 'they must be recursively scanned.'; put '*/'; put '%if (%length(&dir) gt %length(&child)) %then %do;'; put '%let parent = %substr(&dir, 1, %length(&dir)-%length(&child));'; put '%mf_mkdir(&parent)'; put '%end;'; put '/*'; put 'Now create the directory. Complain loudly of any errs.'; put '*/'; put '%let dname = %sysfunc(dcreate(&child, &parent));'; put '%if (%bquote(&dname) eq ) %then %do;'; put '%put %str(ERR)OR: could not create &parent + &child;'; put '%abort cancel;'; put '%end;'; put '%else %do;'; put '%put Directory created: &dir;'; put '%end;'; put '%end;'; put '/* exit quietly if directory did exist.*/'; put '%mend mf_mkdir;'; put '%macro mddl_sas_cntlout(libds=WORK.CNTLOUT);'; put 'proc sql;'; put 'create table &libds('; put 'TYPE char(1) label='; put '''Format Type: either N (num fmt), C (char fmt), I (num infmt) or J (char infmt)'''; put ',FMTNAME char(32) label=''Format name'''; put ',FMTROW num label='; put '''CALCULATED Position of record by FMTNAME (reqd for multilabel formats)'''; put ',START char(32767) label=''Starting value for format'''; put '/*'; put 'Keep lengths of START and END the same to avoid this err:'; put '"Start is greater than end: -<."'; put 'Similar usage note: https://support.sas.com/kb/69/330.html'; put '*/'; put ',END char(32767) label=''Ending value for format'''; put ',LABEL char(32767) label=''Format value label'''; put ',MIN num length=3 label=''Minimum length'''; put ',MAX num length=3 label=''Maximum length'''; put ',DEFAULT num length=3 label=''Default length'''; put ',LENGTH num length=3 label=''Format length'''; put ',FUZZ num label=''Fuzz value'''; put ',PREFIX char(2) label=''Prefix characters'''; put ',MULT num label=''Multiplier'''; put ',FILL char(1) label=''Fill character'''; put ',NOEDIT num length=3 label=''Is picture string noedit?'''; put ',SEXCL char(1) label=''Start exclusion'''; put ',EEXCL char(1) label=''End exclusion'''; put ',HLO char(13) label='; put '''More info: https://core.sasjs.io/mddl__sas__cntlout_8sas_source.html'''; put ',DECSEP char(1) label=''Decimal separator'''; put ',DIG3SEP char(1) label=''Three-digit separator'''; put ',DATATYPE char(8) label=''Date/time/datetime?'''; put ',LANGUAGE char(8) label=''Language for date strings'''; put ');'; put '%local lib;'; put '%let libds=%upcase(&libds);'; put '%if %index(&libds,.)=0 %then %let lib=WORK;'; put '%else %let lib=%scan(&libds,1,.);'; put 'proc datasets lib=&lib noprint;'; put 'modify %scan(&libds,-1,.);'; put 'index create'; put 'pk_cntlout=(type fmtname fmtrow)'; put '/nomiss unique;'; put 'quit;'; put '%mend mddl_sas_cntlout;'; put '%macro mp_aligndecimal(var,width=8);'; put '%local tmpvar;'; put '%let tmpvar=%mf_getuniquename(prefix=aligndp);'; put 'length &tmpvar $&width;'; put 'if index(&var,''.'') then do;'; put '&tmpvar=cats(scan(&var,1,''.''));'; put '&tmpvar=right(&tmpvar);'; put '&var=&tmpvar!!''.''!!cats(scan(&var,2,''.''));'; put 'end;'; put 'else do;'; put '&tmpvar=cats(&var);'; put '&tmpvar=right(&tmpvar);'; put '&var=&tmpvar;'; put 'end;'; put 'drop &tmpvar;'; put '%mend mp_aligndecimal;'; put '%macro mp_cntlout('; put 'iftrue=(1=1)'; put ',libcat='; put ',cntlout=work.fmtextract'; put ',fmtlist=0'; put ')/*/STORE SOURCE*/;'; put '%local ddlds cntlds i;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%let ddlds=%mf_getuniquename();'; put '%let cntlds=%mf_getuniquename();'; put '%mddl_sas_cntlout(libds=&ddlds)'; put '%if %index(&libcat,-)>0 and %scan(&libcat,2,-)=FC %then %do;'; put '%let libcat=%scan(&libcat,1,-);'; put '%end;'; put 'proc format lib=&libcat cntlout=&cntlds;'; put '%if "&fmtlist" ne "0" and "&fmtlist" ne "" %then %do;'; put 'select'; put '%do i=1 %to %sysfunc(countw(&fmtlist,%str( )));'; put '%scan(&fmtlist,&i,%str( ))'; put '%end;'; put ';'; put '%end;'; put 'run;'; put 'data &cntlout/nonote2err;'; put 'if 0 then set &ddlds;'; put 'set &cntlds;'; put 'by type fmtname notsorted;'; put '/* align the numeric values to avoid overlapping ranges */'; put 'if type in ("I","N") then do;'; put '%mp_aligndecimal(start,width=16)'; put '%mp_aligndecimal(end,width=16)'; put 'end;'; put '/* create row marker. Data cannot be sorted without it! */'; put 'if first.fmtname then fmtrow=1;'; put 'else fmtrow+1;'; put 'run;'; put 'proc sort;'; put 'by type fmtname fmtrow;'; put 'run;'; put 'proc sql;'; put 'drop table &ddlds,&cntlds;'; put '%mend mp_cntlout;'; put '/** @endcond */'; put '%macro mf_getvarlist(libds'; put ',dlm=%str( )'; put ',quote=no'; put ',typefilter=A'; put ')/*/STORE SOURCE*/;'; put '/* declare local vars */'; put '%local outvar dsid nvars x rc dlm q var vtype;'; put '/* credit Rowland Hale - byte34 is double quote, 39 is single quote */'; put '%if %upcase("e)=DOUBLE %then %let q=%qsysfunc(byte(34));'; put '%else %if %upcase("e)=SINGLE %then %let q=%qsysfunc(byte(39));'; put '/* open dataset in macro */'; put '%let dsid=%sysfunc(open(&libds));'; put '%if &dsid %then %do;'; put '%let nvars=%sysfunc(attrn(&dsid,NVARS));'; put '%if &nvars>0 %then %do;'; put '/* add variables with supplied delimeter */'; put '%do x=1 %to &nvars;'; put '/* get variable type */'; put '%let vtype=%sysfunc(vartype(&dsid,&x));'; put '%if &vtype=&typefilter or &typefilter=A %then %do;'; put '%let var=&q.%sysfunc(varname(&dsid,&x))&q.;'; put '%if &var=&q&q %then %do;'; put '%put &sysmacroname: Empty column found in &libds!;'; put '%let var=&q. &q.;'; put '%end;'; put '%if %quote(&outvar)=%quote() %then %let outvar=&var;'; put '%else %let outvar=&outvar.&dlm.&var.;'; put '%end;'; put '%end;'; put '%end;'; put '%let rc=%sysfunc(close(&dsid));'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: Unable to open &libds (rc=&dsid);'; put '%put &sysmacroname: SYSMSG= %sysfunc(sysmsg());'; put '%let rc=%sysfunc(close(&dsid));'; put '%end;'; put '%do;%unquote(&outvar)%end;'; put '%mend mf_getvarlist;'; put '%macro mf_wordsInStr1ButNotStr2('; put 'Str1= /* string containing words to extract */'; put ',Str2= /* used to compare with the extract string */'; put ')/*/STORE SOURCE*/;'; put '%local count_base count_extr i i2 extr_word base_word match outvar;'; put '%if %length(&str1)=0 or %length(&str2)=0 %then %do;'; put '%put base string (str1)= &str1;'; put '%put compare string (str2) = &str2;'; put '%return;'; put '%end;'; put '%let count_base=%sysfunc(countw(&Str2));'; put '%let count_extr=%sysfunc(countw(&Str1));'; put '%do i=1 %to &count_extr;'; put '%let extr_word=%scan(&Str1,&i,%str( ));'; put '%let match=0;'; put '%do i2=1 %to &count_base;'; put '%let base_word=%scan(&Str2,&i2,%str( ));'; put '%if &extr_word=&base_word %then %let match=1;'; put '%end;'; put '%if &match=0 %then %let outvar=&outvar &extr_word;'; put '%end;'; put '&outvar'; put '%mend mf_wordsInStr1ButNotStr2;'; put '%macro mf_isblank(param'; put ')/*/STORE SOURCE*/;'; put '%sysevalf(%superq(param)=,boolean)'; put '%mend mf_isblank;'; put '%macro mp_dropmembers('; put 'list /* space separated list of datasets / views */'; put ',libref=WORK /* can only drop from a single library at a time */'; put ',iftrue=%str(1=1)'; put ')/*/STORE SOURCE*/;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%if %mf_isblank(&list) %then %do;'; put '%put NOTE: nothing to drop!;'; put '%return;'; put '%end;'; put 'proc datasets lib=&libref nolist;'; put 'delete &list;'; put 'delete &list /mtype=view;'; put 'run;'; put '%mend mp_dropmembers;'; put '%macro mp_dirlist(path=%sysfunc(pathname(work))'; put ', fref=0'; put ', outds=work.mp_dirlist'; put ', getattrs=NO'; put ', showparent=NO'; put ', maxdepth=0'; put ', level=0 /* The level of recursion to perform. For internal use only. */'; put ')/*/STORE SOURCE*/;'; put '%let getattrs=%upcase(&getattrs)XX;'; put '/* temp table */'; put '%local out_ds;'; put 'data;run;'; put '%let out_ds=%str(&syslast);'; put '/* drop main (top) table if it exists */'; put '%if &level=0 %then %do;'; put '%mp_dropmembers(%scan(&outds,-1,.), libref=WORK)'; put '%end;'; put 'data &out_ds(compress=no'; put 'keep=file_or_folder filepath filename ext msg directory level'; put ');'; put 'length directory filepath $500 fref fref2 $8 file_or_folder $6 filename $80'; put 'ext $20 msg $200 foption $16;'; put 'if _n_=1 then call missing(of _all_);'; put 'retain level &level;'; put '%if &fref=0 %then %do;'; put 'rc = filename(fref, "&path");'; put '%end;'; put '%else %do;'; put 'fref="&fref";'; put 'rc=0;'; put '%end;'; put 'if rc = 0 then do;'; put 'did = dopen(fref);'; put 'if did=0 then do;'; put 'putlog "NOTE: This directory is empty, or does not exist - &path";'; put 'msg=sysmsg();'; put 'put (_all_)(=);'; put 'stop;'; put 'end;'; put '/* attribute is OS-dependent - could be "Directory" or "Directory Name" */'; put 'numopts=doptnum(did);'; put 'do i=1 to numopts;'; put 'foption=doptname(did,i);'; put 'if foption=:''Directory'' then i=numopts;'; put 'end;'; put 'directory=dinfo(did,foption);'; put 'rc = filename(fref);'; put 'end;'; put 'else do;'; put 'msg=sysmsg();'; put 'put _all_;'; put 'stop;'; put 'end;'; put 'dnum = dnum(did);'; put 'do i = 1 to dnum;'; put 'filename = dread(did, i);'; put 'filepath=cats(directory,''/'',filename);'; put 'rc = filename(fref2,filepath);'; put 'midd=dopen(fref2);'; put 'dmsg=sysmsg();'; put 'if did > 0 then file_or_folder=''folder'';'; put 'rc=dclose(midd);'; put 'midf=fopen(fref2);'; put 'fmsg=sysmsg();'; put 'if midf > 0 then file_or_folder=''file'';'; put 'rc=fclose(midf);'; put 'if index(fmsg,''File is in use'') or index(dmsg,''is not a directory'')'; put 'then file_or_folder=''file'';'; put 'else if index(fmsg,''Insufficient authorization'') then file_or_folder=''file'';'; put 'else if file_or_folder='''' then file_or_folder=''locked'';'; put 'if file_or_folder=''file'' then do;'; put 'ext = prxchange(''s/.*\.{1,1}(.*)/$1/'', 1, filename);'; put 'if filename = ext then ext = '' '';'; put 'end;'; put 'else do;'; put 'ext='''';'; put 'file_or_folder=''folder'';'; put 'end;'; put 'output;'; put 'end;'; put 'rc = dclose(did);'; put '%if &showparent=YES and &level=0 %then %do;'; put 'filepath=directory;'; put 'file_or_folder=''folder'';'; put 'ext='''';'; put 'filename=scan(directory,-1,''/\'');'; put 'msg='''';'; put 'level=&level;'; put 'output;'; put '%end;'; put 'stop;'; put 'run;'; put '%if %substr(&getattrs,1,1)=Y %then %do;'; put 'data &out_ds;'; put 'set &out_ds;'; put 'length infoname infoval $60 fref $8;'; put 'if _n_=1 then call missing(fref);'; put 'rc=filename(fref,filepath);'; put 'drop rc infoname fid i close fref;'; put 'if file_or_folder=''file'' then do;'; put 'fid=fopen(fref);'; put 'if fid le 0 then do;'; put 'msg=sysmsg();'; put 'putlog "Could not open file:" filepath fid= ;'; put 'sasname=''_MCNOTVALID_'';'; put 'output;'; put 'end;'; put 'else do i=1 to foptnum(fid);'; put 'infoname=foptname(fid,i);'; put 'infoval=finfo(fid,infoname);'; put 'sasname=compress(infoname, ''_'', ''adik'');'; put 'if anydigit(sasname)=1 then sasname=substr(sasname,anyalpha(sasname));'; put 'if upcase(sasname) ne ''FILENAME'' then output;'; put 'end;'; put 'close=fclose(fid);'; put 'end;'; put 'else do;'; put 'fid=dopen(fref);'; put 'if fid le 0 then do;'; put 'msg=sysmsg();'; put 'putlog "Could not open folder:" filepath fid= ;'; put 'sasname=''_MCNOTVALID_'';'; put 'output;'; put 'end;'; put 'else do i=1 to doptnum(fid);'; put 'infoname=doptname(fid,i);'; put 'infoval=dinfo(fid,infoname);'; put 'sasname=compress(infoname, ''_'', ''adik'');'; put 'if anydigit(sasname)=1 then sasname=substr(sasname,anyalpha(sasname));'; put 'if upcase(sasname) ne ''FILENAME'' then output;'; put 'end;'; put 'close=dclose(fid);'; put 'end;'; put 'run;'; put 'proc sort;'; put 'by filepath sasname;'; put 'proc transpose data=&out_ds out=&out_ds(drop=_:);'; put 'id sasname;'; put 'var infoval;'; put 'by filepath file_or_folder filename ext ;'; put 'run;'; put '%end;'; put 'data &out_ds;'; put 'set &out_ds(where=(filepath ne ''''));'; put 'run;'; put '/**'; put '* The above transpose can mean that some updates create additional columns.'; put '* This necessitates the occasional use of datastep over proc append.'; put '*/'; put '%if %mf_existds(&outds) %then %do;'; put '%local basevars appvars newvars;'; put '%let basevars=%mf_getvarlist(&outds);'; put '%let appvars=%mf_getvarlist(&out_ds);'; put '%let newvars=%length(%mf_wordsinstr1butnotstr2(Str1=&appvars,Str2=&basevars));'; put '%if &newvars>0 %then %do;'; put 'data &outds;'; put 'set &outds &out_ds;'; put 'run;'; put '%end;'; put '%else %do;'; put 'proc append base=&outds data=&out_ds force nowarn;'; put 'run;'; put '%end;'; put '%end;'; put '%else %do;'; put 'proc append base=&outds data=&out_ds;'; put 'run;'; put '%end;'; put '/* recursive call */'; put '%if &maxdepth>&level or &maxdepth=MAX %then %do;'; put 'data _null_;'; put 'set &out_ds;'; put 'where file_or_folder=''folder'';'; put '%if &showparent=YES and &level=0 %then %do;'; put 'if filepath ne directory;'; put '%end;'; put 'length code $10000;'; put 'code=cats(''%nrstr(%mp_dirlist(path='',filepath,",outds=&outds"'; put ',",getattrs=&getattrs,level=%eval(&level+1),maxdepth=&maxdepth))");'; put 'put code=;'; put 'call execute(code);'; put 'run;'; put '%end;'; put '/* tidy up */'; put 'proc sql;'; put 'drop table &out_ds;'; put '%mend mp_dirlist;'; put '%macro mf_getattrc('; put 'libds'; put ',attr'; put ')/*/STORE SOURCE*/;'; put '%local dsid rc;'; put '%let dsid=%sysfunc(open(&libds,is));'; put '%if &dsid = 0 %then %do;'; put '%put %str(WARN)ING: Cannot open %trim(&libds), system message below;'; put '%put %sysfunc(sysmsg());'; put '-1'; put '%end;'; put '%else %do;'; put '%sysfunc(attrc(&dsid,&attr))'; put '%let rc=%sysfunc(close(&dsid));'; put '%end;'; put '%mend mf_getattrc;'; put '%macro mp_lockfilecheck('; put 'libds'; put ')/*/STORE SOURCE*/;'; put 'data _null_;'; put 'if _n_=1 then putlog "&sysmacroname entry vars:";'; put 'set sashelp.vmacro;'; put 'where scope="&sysmacroname";'; put 'put name ''='' value;'; put 'run;'; put '%mp_abort(iftrue= (&syscc>0)'; put ',mac=checklock.sas'; put ',msg=Aborting with syscc=&syscc on entry.'; put ')'; put '%mp_abort(iftrue= ("&libds"="0")'; put ',mac=&sysmacroname'; put ',msg=%str(libds not provided)'; put ')'; put '%local msg lib ds;'; put '%let lib=%upcase(%scan(&libds,1,.));'; put '%let ds=%upcase(%scan(&libds,2,.));'; put '/* in DC, format catalogs are passed with a -FC suffix. No saslock here! */'; put '%if %scan(&libds,2,-)=FC %then %do;'; put '%put &sysmacroname: Format Catalog detected, no lockfile applied to &libds;'; put '%return;'; put '%end;'; put '/* do not proceed if no observations can be processed */'; put '%let msg=options obs = 0. syserrortext=%superq(syserrortext);'; put '%mp_abort(iftrue= (%sysfunc(getoption(OBS))=0)'; put ',mac=checklock.sas'; put ',msg=%superq(msg)'; put ')'; put 'data _null_;'; put 'putlog "Checking engine & member type";'; put 'run;'; put '%local engine memtype;'; put '%let memtype=%mf_getattrc(&libds,MTYPE);'; put '%let engine=%mf_getattrc(&libds,ENGINE);'; put '%if &engine ne V9 and &engine ne BASE %then %do;'; put 'data _null_;'; put 'putlog "Lib &lib is not assigned using BASE engine - uses &engine instead";'; put 'putlog "SAS lock check will not be performed";'; put 'run;'; put '%return;'; put '%end;'; put '%else %if &memtype ne DATA %then %do;'; put '%put NOTE: Cannot lock a VIEW!! Memtype=&memtype;'; put '%return;'; put '%end;'; put 'data _null_;'; put 'putlog "Engine = &engine, memtype=&memtype";'; put 'putlog "Attempting lock statement";'; put 'run;'; put 'lock &libds;'; put '%local abortme;'; put '%let abortme=0;'; put '%if &syscc>0 or &SYSLCKRC ne 0 %then %do;'; put '%let msg=Unable to apply lock on &libds (SYSLCKRC=&SYSLCKRC syscc=&syscc);'; put '%put %str(ERR)OR: &sysmacroname: &msg;'; put '%let abortme=1;'; put '%end;'; put 'lock &libds clear;'; put '%mp_abort(iftrue= (&abortme=1)'; put ',mac=&sysmacroname'; put ',msg=%superq(msg)'; put ')'; put '%mend mp_lockfilecheck;'; put '%macro mp_lockanytable('; put 'action'; put ',lib= WORK'; put ',ds=0'; put ',ref='; put ',ctl_ds=0'; put ',loops=25'; put ',loop_secs=1'; put ');'; put 'data _null_;'; put 'if _n_=1 then putlog "&sysmacroname entry vars:";'; put 'set sashelp.vmacro;'; put 'where scope="&sysmacroname";'; put 'put name ''='' value;'; put 'run;'; put '%mp_abort(iftrue= ("&ds"="0" and &action ne MAKETABLE)'; put ',mac=&sysmacroname'; put ',msg=%str(dataset was not provided)'; put ')'; put '%mp_abort(iftrue= (&ctl_ds=0)'; put ',mac=&sysmacroname'; put ',msg=%str(Control dataset was not provided)'; put ')'; put '/* set up lib & mac vars */'; put '%let lib=%upcase(&lib);'; put '%let ds=%upcase(&ds);'; put '%let action=%upcase(&action);'; put '%local user x trans msg abortme;'; put '%let user=%mf_getuser();'; put '%let abortme=0;'; put '%mp_abort(iftrue= (&action ne LOCK & &action ne UNLOCK & &action ne MAKETABLE)'; put ',mac=&sysmacroname'; put ',msg=%str(Invalid action (&action) provided)'; put ')'; put '/* if an err condition exists, exit before we even begin */'; put '%mp_abort(iftrue= (&syscc>0 and &action=LOCK)'; put ',mac=&sysmacroname'; put ',msg=%str(aborting due to syscc=&syscc on LOCK entry)'; put ')'; put '/* do not bother locking work tables (else may affect all WORK libraries) */'; put '%if (%upcase(&lib)=WORK or %str(&lib)=%str()) & &action ne MAKETABLE %then %do;'; put '%put NOTE: WORK libraries will not be registered in the locking system.;'; put '%return;'; put '%end;'; put '/* do not proceed if no observations can be processed */'; put '%mp_abort(iftrue= (%sysfunc(getoption(OBS))=0)'; put ',mac=&sysmacroname'; put ',msg=%str(cannot continue when options obs = 0)'; put ')'; put '%if &ACTION=LOCK %then %do;'; put '/* abort if a SAS lock is already in place, or cannot be applied */'; put '%mp_lockfilecheck(&lib..&ds)'; put '/* next, check there is a record for this table */'; put '%local record_exists_check;'; put 'proc sql noprint;'; put 'select count(*) into: record_exists_check from &ctl_ds'; put 'where LOCK_LIB ="&lib" and LOCK_DS="&ds";'; put 'quit;'; put '%if &syscc>0 %then %put syscc=&syscc sqlrc=&sqlrc;'; put '%if &record_exists_check=0 %then %do;'; put 'data _null_;'; put 'putlog "&sysmacroname: adding record to lock table..";'; put 'run;'; put 'data ;'; put 'if 0 then set &ctl_ds;'; put 'LOCK_LIB ="&lib";'; put 'LOCK_DS="&ds";'; put 'LOCK_STATUS_CD=''LOCKED'';'; put 'LOCK_START_DTTM="%sysfunc(datetime(),%mf_fmtdttm())"dt;'; put 'LOCK_USER_NM="&user";'; put 'LOCK_PID="&sysjobid";'; put 'LOCK_REF="&ref";'; put 'output;stop;'; put 'run;'; put '%let trans=&syslast;'; put 'proc append base=&ctl_ds data=&trans;'; put 'run;'; put '%end;'; put '/* if record does exist, perform lock attempts */'; put '%else %do x=1 %to &loops;'; put 'data _null_;'; put 'putlog "&sysmacroname: attempting lock (iteration &x) "@;'; put 'putlog "at %sysfunc(datetime(),datetime19.) ..";'; put 'run;'; put 'proc sql;'; put 'update &ctl_ds'; put 'set LOCK_STATUS_CD=''LOCKED'''; put ', LOCK_START_DTTM="%sysfunc(datetime(),%mf_fmtdttm())"dt'; put ', LOCK_USER_NM="&user"'; put ', LOCK_PID="&sysjobid"'; put ', LOCK_REF="&ref"'; put 'where LOCK_LIB ="&lib" and LOCK_DS="&ds";'; put 'quit;'; put '/**'; put '* NOTE - occasionally SQL server will return an err code (deadlocked'; put '* transaction). If so, ignore it, keep calm, and carry on..'; put '*/'; put '%if &syscc>0 %then %do;'; put 'data _null_;'; put 'putlog ''NOTE-'' / ''NOTE-'';'; put 'putlog "NOTE- &sysmacroname: Update failed. "@;'; put 'putlog "Resetting err conditions and re-attempting.";'; put 'putlog "NOTE- syscc=&syscc syserr=&syserr sqlrc=&sqlrc";'; put 'putlog ''NOTE-'' / ''NOTE-'';'; put 'run;'; put '%let syscc=0;'; put '%let sqlrc=0;'; put '%end;'; put '/* now check if the record was successfully updated */'; put '%local success_check;'; put 'proc sql noprint;'; put 'select count(*) into: success_check from &ctl_ds'; put 'where LOCK_LIB ="&lib" and LOCK_DS="&ds"'; put 'and LOCK_PID="&sysjobid" and LOCK_STATUS_CD=''LOCKED'';'; put 'quit;'; put '%if &success_check=0 %then %do;'; put '%if &x < &loops %then %do;'; put '/* pause before next check */'; put 'data _null_;'; put 'putlog ''NOTE-'' / ''NOTE-'';'; put 'putlog "NOTE- &sysmacroname: table locked, waiting "@;'; put 'putlog "%sysfunc(sleep(&loop_secs)) seconds.. ";'; put 'putlog "NOTE- (iteration &x of &loops)";'; put 'putlog ''NOTE-'' / ''NOTE-'';'; put 'run;'; put '%end;'; put '%else %do;'; put '%let msg=Unable to lock &lib..&ds via &ctl_ds after &loops attempts.\n'; put 'Please ask your administrator to investigate!;'; put '%let abortme=1;'; put '%end;'; put '%end;'; put '%else %do;'; put 'data _null_;'; put 'putlog ''NOTE-'' / ''NOTE-'';'; put 'putlog "NOTE- &sysmacroname: Table &lib..&ds locked at "@;'; put 'putlog " %sysfunc(datetime(),datetime19.) (iteration &x)"@;'; put 'putlog ''NOTE-'' / ''NOTE-'';'; put 'run;'; put '%if &syscc>0 %then %do;'; put '%put setting syscc(&syscc) back to 0;'; put '%let syscc=0;'; put '%end;'; put '%let x=&loops; /* no more iterations needed */'; put '%end;'; put '%end;'; put '%end;'; put '%else %if &ACTION=UNLOCK %then %do;'; put '%local status cnt;'; put '%let cnt=0;'; put 'proc sql noprint;'; put 'select count(*) into: cnt from &ctl_ds where LOCK_LIB ="&lib" & LOCK_DS="&ds";'; put '%if &cnt=0 %then %do;'; put '%put %str(WAR)NING: &lib..&ds was not previously locked in &ctl_ds!;'; put '%end;'; put '%else %do;'; put 'select LOCK_STATUS_CD into: status from &ctl_ds'; put 'where LOCK_LIB ="&lib" and LOCK_DS="&ds";'; put 'quit;'; put '%if &syscc>0 %then %put syscc=&syscc sqlrc=&sqlrc;'; put '%if &status=LOCKED %then %do;'; put 'data _null_;'; put 'putlog "&sysmacroname: unlocking &lib..&ds:";'; put 'run;'; put 'proc sql;'; put 'update &ctl_ds'; put 'set LOCK_STATUS_CD=''UNLOCKED'''; put ', LOCK_END_DTTM="%sysfunc(datetime(),%mf_fmtdttm())"dt'; put ', LOCK_USER_NM="&user"'; put ', LOCK_PID="&sysjobid"'; put ', LOCK_REF="&ref"'; put 'where LOCK_LIB ="&lib" and LOCK_DS="&ds";'; put 'quit;'; put '%end;'; put '%else %if &status=UNLOCKED %then %do;'; put '%put %str(WAR)NING: &lib..&ds is already unlocked!;'; put '%end;'; put '%else %do;'; put '%put NOTE: Unrecognised STATUS_CD (&status) in &ctl_ds;'; put '%let abortme=1;'; put '%end;'; put '%end;'; put '%end;'; put '%else %do;'; put '%let msg=lock_anytable given unsupported action (&action);'; put '%let abortme=1;'; put '%end;'; put '/* catch errs - mp_abort must be called outside of a logic block */'; put '%mp_abort(iftrue=(&abortme=1),'; put 'msg=%superq(msg),'; put 'mac=&sysmacroname'; put ')'; put '%exit_macro:'; put 'data _null_;'; put 'put "&sysmacroname: Exit vars: action=&action lib=&lib ds=&ds";'; put 'put " syscc=&syscc sqlrc=&sqlrc syserr=&syserr";'; put 'run;'; put '%mend mp_lockanytable;'; put '%macro mpe_loader('; put 'mperef= /* name of subfolder containing the staged data */'; put ',mDebug=0 /* set to 1 for development or debugging */'; put ',submitted_reason_txt= /* populates column of same name in sumo_approvals*/'; put ',approver= /* allows a userid to be provided for direct approval email */'; put ',url= /* optional - url for debugging */'; put ',dlm=%str(,)'; put ',termstr=crlf'; put ',dc_dttmtfmt=E8601DT26.6'; put ');'; put '%put entered mpe_loader from &=_program;'; put '%put &=url;'; put '%put &=termstr;'; put '%put &=dlm;'; put '/* determine full path to CSV directory */'; put '%local now;'; put '%let now=&dc_dttmtfmt;'; put '%put &=now;'; put '/**'; put '* get full path to package (only subdirectory passed through)'; put '*/'; put '%mp_abort('; put 'iftrue=(%mf_verifymacvars(mperef mpelocapprovals)=0)'; put ',mac=bitemporal_dataloader'; put ',msg=%str(Missing: mperef mpelocapprovals)'; put ')'; put '%let csv_dir=%trim(&mpelocapprovals/&mperef);'; put '/* exit if package has already been uploaded */'; put '%local check;'; put 'proc sql noprint;'; put 'select count(*) into: check'; put 'from &mpelib..mpe_loads'; put 'where csv_dir="&mperef";'; put '%if &check %then %do;'; put '%mp_abort(msg=Folder &mperef already has an entry in &mpelib..mpe_loads'; put ',mac=mpe_loader.sas);'; put '%return;'; put '%end;'; put '/* get CSV directory contents */'; put '%mp_dirlist(path=&csv_dir,outds=WORK.getfiles)'; put 'data WORK.csvs;'; put 'set WORK.getfiles;'; put 'if upcase(scan(filename,3,''.''))=''CSV'' then do;'; put 'lib=upcase(scan(filename,1,''.''));'; put 'ds=upcase(scan(filename,2,''.''));'; put 'output;'; put 'end;'; put 'run;'; put '/* get table attributes */'; put 'proc sql noprint;'; put 'create table WORK.sumo_tables as'; put 'select a.filename, b.*'; put 'from WORK.csvs a'; put 'left join &mpelib..mpe_tables b'; put 'on a.lib=b.libref'; put 'and a.ds=b.dsn'; put 'where b.tx_from le &now'; put 'and &now lt b.tx_to;'; put '/* define user as meta user if available */'; put '%local user;'; put '%let user=%mf_getuser();'; put '/* check if there is actually a table to load */'; put '%if %mf_getattrn(WORK.sumo_tables,NLOBS)=0 %then %do;'; put '%let msg=Table not registered in &mpelib..mpe_tables;'; put '%mpe_loadfail('; put 'status=&msg'; put ',now=&now'; put ',mperef=&mperef'; put ',dc_dttmtfmt=&dc_dttmtfmt.'; put ')'; put '%mp_abort(msg=&msg,mac=mpe_loader.sas);'; put '%return;'; put '%end;'; put 'proc sql;'; put 'insert into &mpelib..mpe_loads'; put 'set USER_NM="&user"'; put ',STATUS=''IN PROGRESS'''; put ',CSV_dir="&mperef"'; put ',PROCESSED_DTTM=&now;'; put '/* import CSV */'; put '%let droplist=;'; put '%let attrib=;'; put '%let droplist=;'; put '%let libref=;'; put '%let DS=;'; put '/* get table info */'; put 'data _null_;'; put 'set sumo_tables;'; put 'libds=upcase(cats(libref,''.'',dsn));'; put 'call symputx(''orig_libds'',libds);'; put 'is_fmt=0;'; put 'if substr(cats(reverse(dsn)),1,3)=:''CF-'' then do;'; put 'libds=scan(libds,1,''-'');'; put 'putlog "Format Catalog Captured";'; put 'libds=''work.fmtextract'';'; put 'is_fmt=1;'; put 'end;'; put 'call symputx(''is_fmt'',is_fmt);'; put 'call symputx(''libds'',libds);'; put 'call symputx(''FNAME'',filename);'; put 'call symputx(''LIBREF'',libref);'; put 'call symputx(''DS'',dsn);'; put 'call symputx(''LOADTYPE'',loadtype);'; put 'call symputx(''BUSKEY'',buskey);'; put 'call symputx(''VAR_TXFROM'',var_txfrom);'; put 'call symputx(''VAR_TXTO'',var_txto);'; put 'call symputx(''VAR_BUSFROM'',var_busfrom);'; put 'call symputx(''VAR_BUSTO'',var_busto);'; put 'call symputx(''VAR_PROCESSED'',var_processed);'; put 'call symputx(''RK_UNDERLYING'',RK_UNDERLYING);'; put 'call symputx(''POST_EDIT_HOOK'',POST_EDIT_HOOK);'; put 'call symputx(''NOTES'',NOTES);'; put 'call symputx(''PK'',coalescec(RK_UNDERLYING,buskey));'; put 'call symputx(''NUM_OF_APPROVALS_REQUIRED'',NUM_OF_APPROVALS_REQUIRED,''l'');'; put 'put (_all_)(=);'; put 'stop;'; put 'run;'; put '%if %length(&ds)=0 %then %do;'; put '%let msg=%str(ERR)OR: Unable to extract record from &mpelib..mpe_tables;'; put '%mpe_loadfail('; put 'status=FAILED'; put ',now=&now'; put ',mperef=&mperef'; put ',reason_txt=%quote(&msg)'; put ',dc_dttmtfmt=&dc_dttmtfmt.'; put ')'; put '%mp_abort(msg=&msg,mac=mpe_loader.sas);'; put '%return;'; put '%end;'; put '/* export format catalog */'; put '%mp_cntlout('; put 'iftrue=(&is_fmt=1)'; put ',libcat=&orig_libds'; put ',fmtlist=0'; put ',cntlout=work.fmtextract'; put ')'; put '/* user must have EDIT access to load a table */'; put '%mpe_accesscheck(&orig_libds'; put ',outds=work.sumo_access'; put ',user=&user'; put ',access_level=EDIT )'; put '%put exiting accesscheck;'; put '%if %mf_getattrn(work.sumo_access,NLOBS)=0 %then %do;'; put '%let msg=%str(ERR)OR: User is not authorised to edit &orig_libds!;'; put '%mpe_loadfail('; put 'status=UNAUTHORISED'; put ',now=&now'; put ',mperef=&mperef'; put ',reason_txt=%quote(&msg)'; put ',dc_dttmtfmt=&dc_dttmtfmt.'; put ')'; put '%mp_abort(msg=&msg,mac=mpe_loader.sas);'; put '%return;'; put '%end;'; put '%put now importing: "&csv_dir/&fname" termstr=&termstr;'; put '/* get the variables from the CSV */'; put 'data vars_csv1(index=(idxname=(varnum name)) drop=infile);'; put 'infile "&csv_dir/&fname" lrecl=32767 dsd termstr=&termstr encoding=''utf-8'';'; put 'input;'; put 'length infile $32767;'; put 'infile=compress(_infile_,''"'',);'; put 'infile=compress(infile,"''",);'; put 'format name $32.;'; put 'putlog ''received vars: '' infile;'; put 'call symputx(''received_vars'',infile,''l'');'; put 'do varnum=1 to countw(infile,"&dlm");'; put '/* keep writeable chars */'; put 'name=compress(upcase(scan(infile,varnum)),,''kw'');'; put 'if name ne "_____DELETE__THIS__RECORD_____" then output;'; put 'end;'; put 'stop;'; put 'run;'; put '%put received_vars = &received_vars;'; put '%dc_assignlib(WRITE,&libref)'; put '/* get list of variables and their formats */'; put 'proc contents noprint data=&libds'; put 'out=vars(keep=name type length varnum format:);'; put 'run;'; put 'data vars(keep=name type length varnum format);'; put 'set vars(rename=(format=format2 type=type2));'; put 'name=upcase(name);'; put 'format2=upcase(format2);'; put '/* not interested in transaction or processing dates'; put '(append table must be supplied without them) */'; put 'if name not in ("&VAR_TXFROM","&VAR_TXTO","&VAR_PROCESSED"'; put ',"_____DELETE__THIS__RECORD_____");'; put 'if type2 in (2,6) then do;'; put 'length format $49.;'; put 'if format2='''' then format=cats(''$'',length,''.'');'; put 'else format=cats(format2,max(formatl,length),''.'');'; put 'type=''char'';'; put 'end;'; put 'else do;'; put 'if format2='''' then format=cats(length,''.'');'; put 'else if format2=:''DATETIME'' or format2=:''E8601DT'' then do;'; put 'format=''DATETIME19.'';'; put 'end;'; put 'else if format2=:''DATE'' or format2=:''DDMMYY'''; put 'or format2=:''MMDDYY'' or format2=:''YYMMDD'''; put 'or format2=:''E8601DA'' or format2=:''B8601DA'''; put 'then do;'; put 'format=''DATE9.'';'; put 'end;'; put 'else if format2=''BEST'' & formatl=0 then format=cats(''BEST'',length,''.'');'; put '/*'; put 'else if format2=:''DATETIME'' or format2=:''DATE'' or format2=:''DDMMYY'''; put 'or format2=:''MMDDYY'' or format2=:''YYMMDD'' then do;'; put '*date or datetime format so use original ;'; put 'dsid=open("&libref..&ds");'; put 'vnum=varnum(dsid,name);'; put 'format=varfmt(dsid,vnum);'; put 'dsid=close(dsid);'; put 'end;'; put '*/'; put 'else do;'; put 'if formatl=0 then formatl=length;'; put 'format=cats(format2,formatl,''.'',formatd);'; put 'end;'; put 'type=''num'';'; put 'end;'; put 'put (_all_)(=);'; put 'run;'; put '/* build attrib statement */'; put 'data vars_attrib;'; put 'length attrib_statement $32767 type2 $20;'; put 'set vars end=lastobs;'; put 'retain attrib_statement;'; put 'if type=''char'' then type2=''$'';'; put 'str1=catx('' '',name,''length='',cats(type2,length));'; put 'attrib_statement=trim(attrib_statement)!!'' ''!!trim(str1);'; put 'if lastobs then call symputx(''ATTRIB'',attrib_statement,''L'');'; put 'run;'; put '/* build input statement - first get vars in right order'; put 'and join with target formats*/'; put 'proc sql noprint;'; put 'create table vars_csv2 as'; put 'select b.*'; put 'from vars_csv1 a'; put 'left join vars_attrib b'; put 'on a.name=b.name'; put 'order by a.varnum;'; put '/* make sure that the variables we are importing, actually'; put 'exist on the target table */'; put '/** edit - extra variables are now simply ignored'; put '%local very_bad_vars;'; put 'select name into: very_bad_vars separated by '' '''; put 'from vars_csv1'; put 'where name not in (select name from vars)'; put 'and name ne "_____DELETE__THIS__RECORD_____";'; put '%if %length(&very_bad_vars) > 0 %then %do;'; put '%let msg=%str(WARNING: The following vars are not defined in %trim('; put ')&libref..&ds, yet they exist in &csv_dir/&ds..csv: &very_bad_vars);'; put '%mpe_loadfail('; put 'status=FAILED'; put ',now=&now'; put ',mperef=&mperef'; put ',reason_txt=%quote(&msg)'; put ',dc_dttmtfmt=&dc_dttmtfmt.'; put ')'; put '%return;'; put '%end;'; put '**/'; put '/* now build input statement */'; put 'data final_check;'; put 'set vars_csv2 end=lastobs;'; put 'length input_statement $32767 type2 $20 droplist $32767;'; put 'retain input_statement droplist;'; put '/* Build input statement - CATCH EXCEPTIONS HERE!*/'; put 'if name in (''QUOTE_DTTM'') then do;'; put 'name=cats(name,''2'');'; put 'droplist=catx('' '',trim(droplist),name);'; put 'type2=''$20.'';/* converted below */'; put 'end;'; put 'else if type=''char'' then type2=cats(''$CHAR'', length,''.'');'; put 'else if format=''DATE9.'' then type2=''ANYDTDTE.'';'; put 'else if format=''DATETIME19.'' then type2=''ANYDTDTM.'';'; put 'else if format=:''TIME'' then type2=''ANYDTTME.'';'; put 'else if name='''' then do;/* additional vars in input data */'; put 'name=''_____DELETE__THIS__VARIABLE_____'';'; put 'droplist=catx('' '',trim(droplist),''_____DELETE__THIS__VARIABLE_____'');'; put 'type2=''$1.'';'; put 'end;'; put 'else type2=''best32.'';'; put '* else type2=cats(length,''.'');'; put 'input_statement=catx('' '',input_statement,name,'':'',type2);'; put 'if lastobs then do;'; put 'call symputx(''INPUT'', input_statement,''L'');'; put 'if trim(droplist) ne '''' then'; put 'call symputx(''droplist'',"drop "!!droplist!!'';'',''l'');'; put 'end;'; put 'run;'; put '%let mpeloadstop=0;'; put 'data work.STAGING_DS;'; put '&droplist;'; put 'infile "&csv_dir/&fname" dsd dlm="&dlm" lrecl=32767'; put 'firstobs=2 missover termstr=&termstr encoding=''utf-8'';'; put 'attrib &attrib ;'; put 'if _n_=1 then call missing (of _all_);'; put 'missing a b c d e f g h i j k l m n o p q r s t u v w x y z _;'; put 'input'; put '%if %scan(%quote(&received_vars),1)=_____DELETE__THIS__RECORD_____ %then %do;'; put '_____DELETE__THIS__RECORD_____: $3.'; put '%end;'; put '&input;'; put '%if %index(%quote(&attrib.),UNLIKELY_VAR ) %then %do;'; put '/*UNLIKELY_VAR=input(UNLIKELY_VAR2,ANYDTDTM21.);*/'; put '/* SPECIAL LOGIC FOR SPECIAL VARS */'; put '%end;'; put 'if _error_ ne 0 then do;'; put 'putlog _infile_;'; put 'call symputx(''mpeloadstop'',_n_);'; put 'stop;'; put 'end;'; put '/* remove all blank rows */'; put 'if compress(cats(of _all_),''.'')='' '' then delete;'; put 'run;'; put '%if &mpeloadstop>0 %then %do;'; put '%if %symexist(SYSPRINTTOLOG) %then %let logloc=&SYSPRINTTOLOG;'; put '%else %let logloc=%qsysfunc(getoption(LOG));'; put '%put redirecting log output to capture return message;'; put '%put currentloc=&logloc;'; put 'filename tmp temp;'; put 'proc printto log=tmp;run;'; put 'data _null_;'; put '&droplist;'; put 'infile "&csv_dir/&fname" dsd dlm="&dlm" lrecl=32767 firstobs=2'; put 'missover termstr=&termstr;'; put 'attrib &attrib ;'; put 'input'; put '%if %scan(%quote(&received_vars),1)=_____DELETE__THIS__RECORD_____'; put '%then %do;'; put '_____DELETE__THIS__RECORD_____: $3.'; put '%end;'; put '&input;'; put 'if _error_ then stop;'; put 'run;'; put '/* get log back */'; put 'proc printto log=&logloc;run;'; put 'data _null_; infile tmp; input; putlog _infile_;run;'; put '/* scan log for invalid data warning */'; put 'data _null_;'; put 'infile tmp;'; put 'input;'; put 'length msg1 msg2 msg3 msg4 msg5 msg url $32767;'; put 'if index(_infile_,''NOTE: Invalid data for'') then do;'; put 'msg1=_infile_;'; put 'input;'; put 'msg2=_infile_;'; put 'input;'; put 'msg3=_infile_;'; put 'input;'; put 'msg4=_infile_;'; put 'input;'; put 'msg5=_infile_;'; put 'url=symget(''url'');'; put 'msg=catx(''\n'',msg1,msg2,msg3,msg4,msg5,''\n'',url);'; put 'call symputx(''msg'',msg);'; put 'stop;'; put 'end;'; put 'run;'; put '%mpe_loadfail('; put 'status=FAILED'; put ',now=&now'; put ',mperef=&mperef'; put ',reason_txt=%superq(msg)'; put ',dc_dttmtfmt=&dc_dttmtfmt.'; put ')'; put '%return;'; put '%end;'; put '/* check that the table is unique on PK */'; put 'proc sort data=work.STAGING_DS dupout=work.MPE_DUPS (keep=&pk) nodupkey;'; put 'by &pk;'; put 'run;'; put '%if %mf_getattrn(work.MPE_DUPS,NLOBS)>0 %then %do;'; put '%local duplist;'; put 'data _null_;'; put 'set work.mpe_dups;'; put '%do i=1 %to %sysfunc(countw(&pk));'; put '%let iWord=%scan(&pk,&i);'; put 'call symputx(''duplist'',symget(''duplist'')!!'; put '" &iWord="!!cats(&iWord));'; put '%end;'; put 'run;'; put '%let msg=This upload contains duplicates on the Primary Key columns %trim('; put ')(&pk) \n Please remove the duplicates and try again. %trim('; put ')\n &duplist \n ;'; put '%mp_abort(msg=%superq(msg),mac=mpe_loader.sas);'; put '%return;'; put '%end;'; put '%if &syscc gt 4 %then %do;'; put '%let msg=SYSCC=&syscc prior to post edit hook (%superq(syserrortext));'; put '%mpe_loadfail('; put 'status=FAILED - &syscc'; put ',now=&now'; put ',mperef=&mperef'; put ',reason_txt=%superq(msg)'; put ',dc_dttmtfmt=&dc_dttmtfmt.'; put ')'; put '%return;'; put '%end;'; put '/* If a Complex Excel Upload, needs to have the load ref added to the table */'; put '%mpe_xlmapvalidate(&mperef,work.staging_ds,&mpelib,&orig_libds)'; put '/* Run the Post Edit Hook prior to creation of staging folder */'; put '%mpe_runhook(POST_EDIT_HOOK)'; put '/* stop if err */'; put '%if &syscc gt 4 %then %do;'; put '%let msg=ERR in post edit hook (&post_edit_hook);'; put '%mpe_loadfail('; put 'status=FAILED - &syscc'; put ',now=&now'; put ',mperef=&mperef'; put ',reason_txt=%quote(&msg)'; put ',dc_dttmtfmt=&dc_dttmtfmt.'; put ')'; put '%return;'; put '%end;'; put '/**'; put '* send to approve process'; put '*/'; put '/* create a dataset key (datetime plus 3 digit random number plus PID) */'; put '/* send dataset to approvals subfolder with same name as subfolder */'; put 'libname approval "&mpelocapprovals/&mperef";'; put 'data approval.&mperef;'; put 'set work.staging_ds;'; put 'run;'; put 'proc export data=approval.&mperef'; put 'outfile="&mpelocapprovals/&mperef/&mperef..csv"'; put 'dbms=csv'; put 'replace;'; put 'run;'; put '/* update the control dataset with relevant info */'; put 'data append_app;'; put 'if 0 then set &mpelib..mpe_submit;/* get formats */'; put 'call missing (of _all_);'; put 'TABLE_ID="&mperef";'; put 'submit_status_cd=''SUBMITTED'';'; put 'submitted_by_nm="%mf_getuser()";'; put 'base_lib="&libref";'; put 'base_ds="&ds";'; put 'submitted_on_dttm=&now;'; put 'submitted_reason_txt=symget(''submitted_reason_txt'');'; put 'input_vars=%mf_getattrn(approval.&mperef,NVARS);'; put 'input_obs=%mf_getattrn(approval.&mperef,NLOBS);'; put 'num_of_approvals_required=&NUM_OF_APPROVALS_REQUIRED;'; put 'num_of_approvals_remaining=&NUM_OF_APPROVALS_REQUIRED;'; put 'reviewed_by_nm='''';'; put 'reviewed_on_dttm=.;'; put 'run;'; put '%mp_lockanytable(LOCK,lib=&mpelib,ds=mpe_submit,'; put 'ref=%str(&mperef update in &_program),'; put 'ctl_ds=&mpelib..mpe_lockanytable'; put ')'; put 'proc append base= &mpelib..mpe_submit data=append_app;'; put 'run;'; put '%mp_lockanytable(UNLOCK,'; put 'lib=&mpelib,ds=mpe_submit,'; put 'ctl_ds=&mpelib..mpe_lockanytable'; put ')'; put '/* send email to REVIEW members */'; put '%put sending mpe_alerts;'; put '%mpe_alerts(alert_event=SUBMITTED'; put ', alert_lib=&libref'; put ', alert_ds=&ds'; put ', dsid=&mperef'; put ')'; put '/* DISABLE EMAIL FOR NOW'; put '%let b2=REASON: %quote(&submitted_reason_txt);'; put '%local URLNOTES;'; put '%if %length(¬es)>0 %then %let URLNOTES=%quote(%sysfunc(urlencode(¬es)));'; put '%let b3=%str(Click to review / approve: )%trim('; put ')%str(http://&_srvname:&_srvport&_url?_PROGRAM=/Web/approvals&)%trim('; put ')TABLEID=&dsid%str(&)BASETABLE=&libref..&ds%str(&)NOTES=&URLNOTES;'; put '%let b4=%str(Reference ID: &mperef);'; put '*/'; put '%put mpe_loader finishing up with syscc=&syscc;'; put '%if &syscc le 4 %then %do;'; put '%local dur;'; put 'data _null_;'; put 'now=symget(''now'');'; put 'dur=%sysfunc(datetime())-&now;'; put 'call symputx(''dur'',dur,''l'');'; put 'putlog ''Updating mpe_loads with the following query:'';'; put 'putlog "update &mpelib..mpe_loads set STATUS=''SUCCESS''";'; put 'putlog " , duration=" dur;'; put 'putlog " , processed_dttm=" now;'; put 'putlog " , approvals = ''&libref..&ds''";'; put 'putlog " where CSV_DIR=''&mperef'';";'; put 'run;'; put 'proc sql;'; put 'update &mpelib..mpe_loads set STATUS=''SUCCESS'''; put ', duration=&dur'; put ', processed_dttm=&now'; put ', approvals = "&libref..&ds"'; put 'where CSV_DIR="&mperef";'; put '%end;'; put '%else %do;'; put '%mpe_loadfail('; put 'status="FAILED - &syscc"'; put ',now=&now'; put ',approvals=&libref..&ds'; put ',mperef=&mperef'; put ',dc_dttmtfmt=&dc_dttmtfmt.'; put ')'; put '%return;'; put '%end;'; put '%mend mpe_loader;'; put '%macro mf_nobs(libds'; put ')/*/STORE SOURCE*/;'; put '%mf_getattrn(&libds,NLOBS)'; put '%mend mf_nobs;'; put '%macro mp_filtergenerate(inds,outref=filter);'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(syscc=&syscc - on macro entry)'; put ')'; put 'filename &outref temp;'; put '%if %mf_nobs(&inds)=0 %then %do;'; put '/* ensure we have a default filter */'; put 'data _null_;'; put 'file &outref;'; put 'put ''1=1'';'; put 'run;'; put '%end;'; put '%else %do;'; put 'proc sort data=&inds;'; put 'by SUBGROUP_ID;'; put 'run;'; put 'data _null_;'; put 'file &outref lrecl=32800;'; put 'set &inds end=last;'; put 'by SUBGROUP_ID;'; put 'if _n_=1 then put ''(('';'; put 'else if first.SUBGROUP_ID then put +1 GROUP_LOGIC ''('';'; put 'else put +2 SUBGROUP_LOGIC;'; put 'put +4 VARIABLE_NM OPERATOR_NM RAW_VALUE;'; put 'if last.SUBGROUP_ID then put '')''@;'; put 'if last then put '')'';'; put 'run;'; put '%end;'; put '%mend mp_filtergenerate;'; put '%macro mpe_filtermaster(mode,libds,'; put 'dclib=,'; put 'filter_rk=-1,'; put 'outref=0,'; put 'outds=work.query'; put ');'; put '%put &sysmacroname entry vars:;'; put '%put _local_;'; put '%let mode=%upcase(&mode);'; put '%let libds=%upcase(&libds);'; put '%mp_abort(iftrue= ('; put '&mode ne EDIT and &mode ne VIEW and &mode ne DLOAD and &mode ne ULOAD'; put ')'; put ',mac=&sysmacroname'; put ',msg=%str(Invalid MODE: &mode)'; put ')'; put '%mp_abort(iftrue= (&outref = 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Please provide a fileref!)'; put ')'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(syscc=&syscc)'; put ')'; put 'filename &outref temp;'; put '/* ensure outputs exist */'; put 'data _null_;'; put 'file &outref;'; put 'put '' '';'; put 'run;'; put 'data &outds;'; put 'set &dclib..mpe_filtersource;'; put 'stop;'; put 'run;'; put '/**'; put '* Deal with FILTER_RK first'; put '*/'; put '%if &filter_rk gt 0 %then %do;'; put 'data _null_;'; put 'file &outref;'; put 'put ''( ''@@;'; put 'set &dclib..mpe_filteranytable(where=(filter_rk=&filter_rk));'; put 'call symputx(''filter_hash'',filter_hash,''l'');'; put 'run;'; put 'proc sort data=&dclib..mpe_filtersource(where=(filter_hash="&filter_hash"))'; put 'out=&outds(drop=filter_hash filter_line processed_dttm);'; put 'by filter_line;'; put 'run;'; put '%mp_filtergenerate(&outds,outref=&outref)'; put '%end;'; put '/* Now filter for current records if the MODE is EDIT or DLOAD */'; put '%local varfrom varto;'; put '%let varfrom=0;'; put 'proc sql;'; put 'select coalescec(var_txfrom,''0''), var_txto into: varfrom,:varto'; put 'from &dclib..MPE_TABLES'; put 'where &dc_dttmtfmt. lt tx_to'; put 'and libref="%scan(&libds,1,.)" and dsn="%scan(&libds,2,.)";'; put '%put &=varfrom;'; put '%put &=varto;'; put '/**'; put '* Check if the date variables were mentioned in the query'; put '* This is a trigger for serving a historical view instead of current'; put '* we skip this part when checking an ULOAD as there are no date vars'; put '*/'; put '%if &varfrom ne 0 and (&mode=EDIT or &mode=DLOAD) %then %do;'; put '%local validityvars;'; put 'proc sql;'; put 'select count(*) into: validityvars'; put 'from &outds'; put 'where variable_nm in ("&varfrom","&varto");'; put '%if &validityvars=0 %then %do;'; put 'data _null_;'; put 'file &outref mod;'; put 'length filter_text $32767;'; put 'varfrom=symget(''varfrom'');'; put 'varto=symget(''varto'');'; put 'filter_text=catx('' '','; put '''("%sysfunc(datetime(),'',"%mf_fmtdttm()",'')"dt <'',varto,'')'''; put ');'; put 'if &filter_rk > 0 then put ''AND '' filter_text;'; put 'else put filter_text;'; put 'run;'; put '%end;'; put '%end;'; put '/**'; put '* Now do Row Level Security based on the MPE_ROW_LEVEL_SECURITY table'; put '*/'; put '/* first determine users group membership */'; put '%mpe_getgroups(user=%mf_getuser(),outds=work.groups)'; put '%local admin_check;'; put 'proc sql;'; put 'select count(*) into: admin_check'; put 'from work.groups'; put 'where groupname="&mpeadmins";'; put '%put &sysmacroname: &=admin_check &=mpeadmins;'; put '%if &admin_check=0 %then %do;'; put '%local scopeval;'; put '%if &mode=DLOAD %then %let scopeval=VIEW;'; put '%if &mode=ULOAD %then %let scopeval=EDIT;'; put '%else %let scopeval=&mode;'; put '/* extract relevant rows */'; put '%local rlsds;'; put '%let rlsds=%mf_getuniquename();'; put 'proc sql;'; put 'create table work.&rlsds as'; put 'select rls_group,'; put 'rls_group_logic as group_logic,'; put 'rls_subgroup_logic as subgroup_logic,'; put 'rls_subgroup_id as subgroup_id,'; put 'rls_variable_nm as variable_nm,'; put 'rls_operator_nm as operator_nm,'; put 'rls_raw_value as raw_value'; put 'from &mpelib..mpe_row_level_security'; put 'where &dc_dttmtfmt. lt tx_to'; put 'and rls_scope in ("&scopeval",''ALL'')'; put 'and upcase(rls_group) in (select upcase(groupname) from work.groups)'; put 'and rls_libref="%scan(&libds,1,.)"'; put 'and rls_table="%scan(&libds,2,.)"'; put 'and rls_active=1'; put 'order by rls_group,rls_subgroup_id;'; put '%if &sqlobs>0 %then %do;'; put '/* check if we currently have filter or not */'; put 'data ;'; put 'infile &outref end=eof;'; put 'input;'; put 'if _n_=1 and eof and cats(_infile_)='''' then newfilter=1;'; put 'output;'; put 'stop;'; put 'run;'; put 'data _null_;'; put 'set &syslast;'; put 'file &outref mod;'; put 'if newfilter=1 then put ''('';'; put 'else put ''AND ('';'; put 'run;'; put '/* loop through and apply filters for each group membership */'; put '%local fref ds;'; put '%let fref=%mf_getuniquefileref();'; put '%let ds=%mf_getuniquename();'; put 'proc sql noprint;'; put 'select distinct rls_group into : group1 -'; put 'from work.&rlsds;'; put '%do i=1 %to &sqlobs;'; put 'data work.&ds;'; put 'set work.&rlsds;'; put 'where rls_group="&&group&i";'; put 'drop rls_group;'; put 'run;'; put '%mp_filtergenerate(&ds,outref=&fref)'; put 'data _null_;'; put 'infile &fref;'; put 'file &outref mod;'; put 'input;'; put 'if &i>1 and _n_=1 then put '' OR '';'; put 'put _infile_;'; put 'run;'; put '%end;'; put 'data _null_;'; put 'file &outref mod;'; put 'put '')'';'; put 'run;'; put '%end; /* &sqlobs>0 */'; put '%else %do;'; put '%put &sysmacroname: no matching groups;'; put 'data _null_;'; put 'set work.groups;'; put 'putlog (_all_)(=);'; put 'run;'; put '%end;'; put '%mp_abort(iftrue= (&syscc>0)'; put ',mac=&sysmacroname'; put ',msg=%str(Row Level Security Generation Error)'; put ')'; put '%end; /* &admin_check=0 */'; put '%put leaving &sysmacroname with the following query:;'; put '%local empty;'; put '%let empty=0;'; put 'data _null_;'; put 'infile &outref end=eof;'; put 'input;'; put 'putlog _infile_;'; put 'if _n_=1 and eof and cats(_infile_)='''' then do;'; put 'put ''1=1'';'; put 'call symputx(''empty'',1,''l'');'; put 'end;'; put 'run;'; put '%if &empty=1 %then %do;'; put 'data _null_;'; put 'file &outref;'; put 'put ''1=1'';'; put 'run;'; put '%end;'; put '%mend mpe_filtermaster;'; put '%macro removecolsfromwork(col);'; put '/* only an issue if debug mode enabled */'; put '%global _debug;'; put '%if &_debug ge 131 %then %do;'; put '%let col=%upcase(&col);'; put '%local memlist;'; put 'proc sql noprint;'; put 'select distinct memname into: memlist'; put 'separated by '' '''; put 'from dictionary.columns'; put 'where libname=''WORK'' and upcase(name)="&col";'; put '%if %mf_isblank(&memlist) %then %return;'; put '%mp_dropmembers(list=&memlist)'; put '%end;'; put '%mend removecolsfromwork;'; put '%macro mp_binarycopy('; put 'inloc= /* full path and filename of the object to be copied */'; put ',outloc= /* full path and filename of object to be created */'; put ',inref=____in /* override default to use own filerefs */'; put ',outref=____out /* override default to use own filerefs */'; put ',mode=CREATE'; put ',iftrue=%str(1=1)'; put ')/*/STORE SOURCE*/;'; put '%local mod;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%if &mode=APPEND %then %let mod=mod;'; put '/* these IN and OUT filerefs can point to anything */'; put '%if &inref = ____in %then %do;'; put 'filename &inref &inloc lrecl=1048576 ;'; put '%end;'; put '%if &outref=____out %then %do;'; put 'filename &outref &outloc lrecl=1048576 &mod;'; put '%end;'; put '/* copy the file byte-for-byte */'; put 'data _null_;'; put 'infile &inref lrecl=1 recfm=n;'; put 'file &outref &mod recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put '%if &inref = ____in %then %do;'; put 'filename &inref clear;'; put '%end;'; put '%if &outref=____out %then %do;'; put 'filename &outref clear;'; put '%end;'; put '%mend mp_binarycopy;'; put '%macro mcf_init(func'; put ')/*/STORE SOURCE*/;'; put '%if not (%symexist(SASJS_PREFIX)) %then %do;'; put '%global SASJS_PREFIX;'; put '%let SASJS_PREFIX=SASJS;'; put '%end;'; put '%let func=%upcase(&func);'; put '/* the / character is just a seperator */'; put '%global &sasjs_prefix._FUNCTIONS;'; put '%if %index(&&&sasjs_prefix._FUNCTIONS,&func/)>0 %then %do;'; put '1'; put '%return;'; put '%end;'; put '%else %do;'; put '%let &sasjs_prefix._FUNCTIONS=&&&sasjs_prefix._FUNCTIONS &func/;'; put '0'; put '%end;'; put '%mend mcf_init;'; put '%macro mcf_getfmttype(wrap=NO'; put ',insert_cmplib=DEPRECATED'; put ',lib=WORK'; put ',cat=SASJS'; put ',pkg=UTILS'; put ')/*/STORE SOURCE*/;'; put '%local i var cmpval found;'; put '%if %mcf_init(mcf_getfmttype)=1 %then %return;'; put '%if &wrap=YES %then %do;'; put 'proc fcmp outlib=&lib..&cat..&pkg;'; put '%end;'; put 'function mcf_getfmttype(fmtnm $) $8;'; put 'if substr(fmtnm,1,1)=''$'' then return(''CHAR'');'; put 'else do;'; put '/* extract NAME */'; put 'length fmt $32;'; put 'fmt=scan(fmtnm,1,''.'');'; put 'do while ('; put 'substr(fmt,length(fmt),1) in (''1'',''2'',''3'',''4'',''5'',''6'',''7'',''8'',''9'',''0'')'; put ');'; put 'if length(fmt)=1 then fmt=''W'';'; put 'else fmt=substr(fmt,1,length(fmt)-1);'; put 'end;'; put '/* apply lookups */'; put 'if cats(fmt) in (''DATETIME'',''B8601DN'',''B8601DN'',''B8601DT'',''B8601DT'''; put ',''B8601DZ'',''B8601DZ'',''DATEAMPM'',''DTDATE'',''DTMONYY'',''DTWKDATX'',''DTYEAR'''; put ',''DTYYQC'',''E8601DN'',''E8601DN'',''E8601DT'',''E8601DT'',''E8601DZ'',''E8601DZ'')'; put 'then return(''DATETIME'');'; put 'else if fmt in (''DATE'',''YYMMDD'',''B8601DA'',''B8601DA'',''DAY'',''DDMMYY'''; put ',''DDMMYYB'',''DDMMYYC'',''DDMMYYD'',''DDMMYYN'',''DDMMYYP'',''DDMMYYS'',''DDMMYYx'''; put ',''DOWNAME'',''E8601DA'',''E8601DA'',''JULDAY'',''JULIAN'',''MMDDYY'',''MMDDYYB'''; put ',''MMDDYYC'',''MMDDYYD'',''MMDDYYN'',''MMDDYYP'',''MMDDYYS'',''MMDDYYx'',''MMYY'''; put ',''MMYYC'',''MMYYD'',''MMYYN'',''MMYYP'',''MMYYS'',''MMYYx'',''MONNAME'',''MONTH'''; put ',''MONYY'',''PDJULG'',''PDJULI'',''QTR'',''QTRR'',''WEEKDATE'',''WEEKDATX'',''WEEKDAY'''; put ',''WEEKU'',''WEEKV'',''WEEKW'',''WORDDATE'',''WORDDATX'',''YEAR'',''YYMM'',''YYMMC'''; put ',''YYMMD'',''YYMMDDB'',''YYMMDDC'',''YYMMDDD'',''YYMMDDN'',''YYMMDDP'',''YYMMDDS'''; put ',''YYMMDDx'',''YYMMN'',''YYMMP'',''YYMMS'',''YYMMx'',''YYMON'',''YYQ'',''YYQC'',''YYQD'''; put ',''YYQN'',''YYQP'',''YYQR'',''YYQRC'',''YYQRD'',''YYQRN'',''YYQRP'',''YYQRS'',''YYQRx'''; put ',''YYQS'',''YYQx'',''YYQZ'') then return(''DATE'');'; put 'else if fmt in (''TIME'',''B8601LZ'',''B8601LZ'',''B8601TM'',''B8601TM'',''B8601TZ'''; put ',''B8601TZ'',''E8601LZ'',''E8601LZ'',''E8601TM'',''E8601TM'',''E8601TZ'',''E8601TZ'''; put ',''HHMM'',''HOUR'',''MMSS'',''TIMEAMPM'',''TOD'') then return(''TIME'');'; put 'else return(''NUM'');'; put 'end;'; put 'endsub;'; put '%if &wrap=YES %then %do;'; put 'quit;'; put '%end;'; put '/* insert the CMPLIB if not already there */'; put '%let cmpval=%sysfunc(getoption(cmplib));'; put '%let found=0;'; put '%do i=1 %to %sysfunc(countw(&cmpval,%str( %(%))));'; put '%let var=%scan(&cmpval,&i,%str( %(%)));'; put '%if &var=&lib..&cat %then %let found=1;'; put '%end;'; put '%if &found=0 %then %do;'; put 'options insert=(CMPLIB=(&lib..&cat));'; put '%end;'; put '%mend mcf_getfmttype;'; put '%macro mf_getVarFormat(libds /* two level ds name */'; put ', var /* variable name from which to return the format */'; put ', force=0'; put ')/*/STORE SOURCE*/;'; put '%local dsid vnum vformat rc vlen vtype;'; put '/* Open dataset */'; put '%let dsid = %sysfunc(open(&libds));'; put '%if &dsid > 0 %then %do;'; put '/* Get variable number */'; put '%let vnum = %sysfunc(varnum(&dsid, &var));'; put '/* Get variable format */'; put '%if(&vnum > 0) %then %let vformat=%sysfunc(varfmt(&dsid, &vnum));'; put '%else %do;'; put '%put NOTE: Variable &var does not exist in &libds;'; put '%let rc = %sysfunc(close(&dsid));'; put '%return;'; put '%end;'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: dataset &libds not opened! (rc=&dsid);'; put '%put &sysmacroname: %sysfunc(sysmsg());'; put '%return;'; put '%end;'; put '/* supply a default if no format available */'; put '%if %length(&vformat)<2 & &force=1 %then %do;'; put '%let vlen = %sysfunc(varlen(&dsid, &vnum));'; put '%let vtype = %sysfunc(vartype(&dsid, &vnum.));'; put '%if &vtype=C %then %let vformat=$&vlen..;'; put '%else %let vformat=best.;'; put '%end;'; put '/* Close dataset */'; put '%let rc = %sysfunc(close(&dsid));'; put '/* Return variable format */'; put '&vformat'; put '%mend mf_getVarFormat;'; put '%macro mf_getvartype(libds /* two level name */'; put ', var /* variable name from which to return the type */'; put ')/*/STORE SOURCE*/;'; put '%local dsid vnum vtype rc;'; put '/* Open dataset */'; put '%let dsid = %sysfunc(open(&libds));'; put '%if &dsid. > 0 %then %do;'; put '/* Get variable number */'; put '%let vnum = %sysfunc(varnum(&dsid, &var));'; put '/* Get variable type (C/N) */'; put '%if(&vnum. > 0) %then %let vtype = %sysfunc(vartype(&dsid, &vnum.));'; put '%else %do;'; put '%put NOTE: Variable &var does not exist in &libds;'; put '%let vtype = %str( );'; put '%end;'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: dataset &libds not opened! (rc=&dsid);'; put '%put &sysmacroname: %sysfunc(sysmsg());'; put '%return;'; put '%end;'; put '/* Close dataset */'; put '%let rc = %sysfunc(close(&dsid));'; put '/* Return variable type */'; put '&vtype'; put '%mend mf_getvartype;'; put '%macro mp_ds2csv(ds'; put ',dlm=COMMA'; put ',outref=0'; put ',outfile='; put ',outencoding=0'; put ',headerformat=LABEL'; put ',termstr=CRLF'; put ')/*/STORE SOURCE*/;'; put '%local outloc delim i varlist var vcnt vat dsv vcom vmiss fmttype vfmt;'; put '%if not %sysfunc(exist(&ds)) %then %do;'; put '%put %str(WARN)ING: &ds does not exist;'; put '%return;'; put '%end;'; put '%if %index(&ds,.)=0 %then %let ds=WORK.&ds;'; put '%if &outencoding=0 %then %let outencoding=;'; put '%else %let outencoding=encoding=&outencoding;'; put '%if &outref=0 %then %let outloc=&outfile;'; put '%else %let outloc=&outref;'; put '%if &headerformat=SASJS %then %do;'; put '%let delim=",";'; put '%let termstr=CRLF;'; put '%mcf_getfmttype(wrap=YES)'; put '%end;'; put '%else %if &dlm=COMMA %then %let delim=",";'; put '%else %let delim=";";'; put '/* credit to mjsq - https://stackoverflow.com/a/55642267 */'; put '/* first get headers */'; put 'data _null_;'; put 'file &outloc &outencoding lrecl=32767 termstr=&termstr;'; put 'length header $ 2000 varnm vfmt $32 dlm $1 fmttype $8;'; put 'call missing(of _all_);'; put 'dsid=open("&ds.","i");'; put 'num=attrn(dsid,"nvars");'; put 'dlm=&delim;'; put 'do i=1 to num;'; put 'varnm=upcase(varname(dsid,i));'; put 'if i=num then dlm='''';'; put '%if &headerformat=NAME %then %do;'; put 'header=cats(varnm,dlm);'; put '%end;'; put '%else %if &headerformat=LABEL %then %do;'; put 'header = cats(coalescec(varlabel(dsid,i),varnm),dlm);'; put '%end;'; put '%else %if &headerformat=SASJS %then %do;'; put 'if vartype(dsid,i)=''C'' then header=cats(varnm,'':$char'',varlen(dsid,i),''.'');'; put 'else do;'; put 'vfmt=coalescec(varfmt(dsid,i),''0'');'; put 'fmttype=mcf_getfmttype(vfmt);'; put 'if fmttype=''DATE'' then header=cats(varnm,'':date9.'');'; put 'else if fmttype=''DATETIME'' then header=cats(varnm,'':E8601DT26.6'');'; put 'else if fmttype=''TIME'' then header=cats(varnm,'':TIME12.'');'; put 'else header=cats(varnm,'':best.'');'; put 'end;'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: Invalid headerformat value (&headerformat);'; put '%return;'; put '%end;'; put 'put header @;'; put 'end;'; put 'rc=close(dsid);'; put 'run;'; put '%let varlist=%mf_getvarlist(&ds);'; put '%let vcnt=%sysfunc(countw(&varlist));'; put '/**'; put '* The $quote modifier (without a width) will take the length from the variable'; put '* and increase by two. However this will lead to truncation where the value'; put '* contains double quotes (which are doubled up). To get around this, scan the'; put '* data to see the max number of double quotes, so that the appropriate width'; put '* can be applied in the subsequent step.'; put '*/'; put 'data _null_;'; put 'set &ds end=last;'; put '%do i=1 %to &vcnt;'; put '%let var=%scan(&varlist,&i);'; put '%if %mf_getvartype(&ds,&var)=C %then %do;'; put '%let dsv1=%mf_getuniquename(prefix=csvcol1_);'; put '%let dsv2=%mf_getuniquename(prefix=csvcol2_);'; put 'retain &dsv1 0;'; put '&dsv2=length(&var)+countc(&var,''"'');'; put 'if &dsv2>&dsv1 then &dsv1=&dsv2;'; put 'if last then call symputx('; put '"vlen&i"'; put '/* should be no shorter than varlen, and no longer than 32767 */'; put ',cats(''$quote'',min(&dsv1+2,32767),''.'')'; put ',''l'''; put ');'; put '%end;'; put '%end;'; put '%let vat=@;'; put '%let vcom=&delim;'; put '%let vmiss=%mf_getuniquename(prefix=csvcol3_);'; put '/* next, export data */'; put 'data _null_;'; put 'set &ds.;'; put 'file &outloc mod dlm=&delim dsd &outencoding lrecl=32767 termstr=&termstr;'; put 'if _n_=1 then &vmiss='' '';'; put '%do i=1 %to &vcnt;'; put '%let var=%scan(&varlist,&i);'; put '%if &i=&vcnt %then %do;'; put '%let vat=;'; put '%let vcom=;'; put '%end;'; put '%if %mf_getvartype(&ds,&var)=N %then %do;'; put '%if &headerformat = SASJS %then %do;'; put '%let vcom=&delim;'; put '%let fmttype=%sysfunc(mcf_getfmttype(%mf_getvarformat(&ds,&var)0));'; put '%if &fmttype=DATE %then %let vfmt=DATE9.;'; put '%else %if &fmttype=DATETIME %then %let vfmt=E8601DT26.6;'; put '%else %if &fmttype=TIME %then %let vfmt=TIME12.;'; put '%else %do;'; put '%let vfmt=;'; put '%let vcom=;'; put '%end;'; put '%end;'; put '%else %let vcom=;'; put '/* must use period - in order to work in both 9.4 and Viya 3.5 */'; put 'if missing(&var) and &var ne %sysfunc(getoption(MISSING)) then do;'; put '&vmiss=cats(''.'',&var);'; put 'put &vmiss &vat;'; put 'end;'; put 'else put &var &vfmt &vcom &vat;'; put '%end;'; put '%else %do;'; put '%if &i ne &vcnt %then %let vcom=&delim;'; put 'put &var &&vlen&i &vcom &vat;'; put '%end;'; put '%end;'; put 'run;'; put '%mend mp_ds2csv;'; put '* SAS Macros end;'; put '* SAS Includes start;'; put '* SAS Includes end;'; put '* Binary Files start;'; put '* Binary Files end;'; put '* ServiceInit start;'; put 'options noquotelenmax ps=max;'; put '/* create dummy macros to prevent stpbegin / stpend from corrupting sessions */'; put '%macro stpbegin();'; put '%put NOTE: the STPBEGIN macro should not be used for web apps!;'; put '%mend stpbegin;'; put '%macro stpend();'; put '%put NOTE: the STPEND macro should not be used for web apps!;'; put '%mend stpend;'; put '* ServiceInit end;'; put '* Service start;'; put '/**'; put '@file'; put '@brief Sends a changeset to staging area'; put '@details This is the service that is called when submitting a new edit.'; put '

Service Inputs

'; put '
jsdata
'; put 'This is the staged data table, plus an _____DELETE__THIS__RECORD_____ column'; put '
SASControlTable
'; put '|ACTION:$char4.|MESSAGE:$char1.|LIBDS:$char19.|'; put '|---|---|---|'; put '|LOAD|User-Provided message|LIBREF.DATASET_NAME|'; put '

SAS Macros

'; put '@li mf_getuser.sas'; put '@li mf_nobs.sas'; put '@li dc_assignlib.sas'; put '@li mf_verifymacvars.sas'; put '@li mf_mkdir.sas'; put '@li mf_getuniquefileref.sas'; put '@li mpe_loader.sas'; put '@li mpe_filtermaster.sas'; put '@li mp_abort.sas'; put '@li mp_binarycopy.sas'; put '@li mp_cntlout.sas'; put '@li mp_ds2csv.sas'; put '@li mf_getplatform.sas'; put '@li removecolsfromwork.sas'; put '@li mpeinit.sas'; put '@version 9.2'; put '@author 4GL Apps Ltd'; put '@copyright 4GL Apps Ltd. This code may only be used within Data Controller'; put 'and may not be re-distributed or re-sold without the express permission of'; put '4GL Apps Ltd.'; put '**/'; put '%mpeinit()'; put '%global approver; %let approver=;'; put '%global libref; %let libref=;'; put '%global dsn; %let dsn=;'; put '%global user; %let user=;'; put '%let user=%mf_getuser();'; put '/* load parameters */'; put 'data _null_;'; put 'set work.sascontroltable;'; put 'call symputx(''action'',action);'; put 'call symputx(''message'',message);'; put 'libds=upcase(libds);'; put 'call symputx(''orig_libds'',libds);'; put 'is_fmt=0;'; put 'if substr(cats(reverse(libds)),1,3)=:''CF-'' then do;'; put 'libds=scan(libds,1,''-'');'; put 'putlog "Format Catalog Captured";'; put 'libds=''work.fmtextract'';'; put 'call symputx(''libds'',libds);'; put 'is_fmt=1;'; put 'end;'; put 'else call symputx(''libds'',libds);'; put 'call symputx(''is_fmt'',is_fmt);'; put 'putlog (_all_)(=);'; put 'run;'; put '%mp_cntlout('; put 'iftrue=(&is_fmt=1)'; put ',libcat=&orig_libds'; put ',fmtlist=0'; put ',cntlout=work.fmtextract'; put ')'; put '/* stream back meta info, further jquery calls will return col metadata and'; put 'actual data */'; put '%let libref=%upcase(%scan(&libds,1,.));'; put '%let dsn=%upcase(%scan(&libds,2,.));'; put '%dc_assignlib(WRITE,&libref)'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc - unable to assign library &libref)'; put ')'; put '%mp_abort('; put 'iftrue=(%mf_verifymacvars(mpelocapprovals libds)=0)'; put ',mac=&_program'; put ',msg=%str(Missing: mpelocapprovals libds)'; put ')'; put '%put Verify that the upload does not violate Row Level Security checks:;'; put '%mpe_filtermaster(ULOAD,&libds,'; put 'dclib=&mpelib,'; put 'outref=filtref,'; put 'outds=work.query'; put ')'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc during filtering process)'; put ')'; put '/* prepare inverse query */'; put '%let tempref=%mf_getuniquefileref();'; put 'data _null_;'; put 'infile filtref end=eof;'; put 'file &tempref;'; put 'if _n_=1 then put ''where not('';'; put 'input;'; put 'put _infile_;'; put 'if eof then put '')'';'; put 'run;'; put '/* apply the query */'; put 'data work.badrecords;'; put 'set work.jsdata;'; put '%inc &tempref/source2;;'; put 'putlog (_all_)(=);'; put 'run;'; put '%mp_abort(iftrue= (%mf_nobs(work.badrecords)>0)'; put ',mac=&_program'; put ',msg=%str('; put 'Security Problem - %mf_nobs(work.badrecords) unauthorised records submitted'; put ')'; put ')'; put 'PROC FORMAT;'; put 'picture yymmddhhmmss other=''%0Y%0m%0d_%0H%0M%0S'' (datatype=datetime);'; put 'RUN;'; put '/**'; put '* Create package folder and redirect the log'; put '*/'; put '/* create a dataset key (datetime plus 6 digit random number plus PID) */'; put '%let mperef=DC%left(%sysfunc(datetime(),B8601DT19.3))_%substr('; put '%sysfunc(ranuni(0)),3,6)_%substr(%str(&sysjobid ),1,4);'; put '/* get web url */'; put '%global url;'; put '%let url=localhost/SASStoredProcess;'; put '%let platform=%mf_getplatform();'; put '%put &=platform;'; put 'data _null_;'; put 'length url $128;'; put '%macro stagedata();'; put '%if &platform=SASVIYA %then %do;'; put 'if symexist(''_baseurl'') then do;'; put 'url=symget(''_baseurl'');'; put 'if subpad(url,length(url)-9,9)=''SASStudio'''; put 'then url=substr(url,1,length(url)-11);'; put 'else url="&systcpiphostname/SASJobExecution";'; put 'end;'; put 'else url="&systcpiphostname/SASJobExecution";'; put '%end;'; put '%else %if &platform=SASMETA %then %do;'; put 'rc=METADATA_GETURI("Stored Process Web App",url);'; put '%end;'; put '%mend stagedata;'; put '%stagedata()'; put 'call symputx(''url'',url);'; put 'putlog url=;'; put 'run;'; put '/* Create package folder */'; put '%let dir=&mpelocapprovals/&mperef;'; put '%mf_mkdir(&dir)'; put '/* redirect the log */'; put '%put; %put; %put log is being redirected;'; put '%put to retrieve, visit this url:; %put;%put;'; put '%let url=&url?_program=%substr(&_program'; put ',1,%length(&_program)-9)getlog%str(&)table=&mperef;'; put '%put &url;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc prior to log redirection)'; put ')'; put 'proc printto log="&dir/weblog.txt";run;'; put 'options notes mprint;'; put 'libname approve "&dir";'; put '/* take copy of webin file */'; put 'data _null_;'; put 'if symexist(''_WEBIN_FILEREF1'') then ref=symget(''_WEBIN_FILEREF1'');'; put 'else if symexist(''sasjs_tables'') then ref=''0ref''; /* no fileref created */'; put 'else ref=''indata1'';'; put 'call symputx(''ref'',ref);'; put 'putlog ref=;'; put 'run;'; put '%mp_binarycopy(inref=&ref,outloc="&dir/_WEBIN_FILEREF1.txt",iftrue=&ref ne 0ref)'; put '/* take copy of macvars */'; put 'data _null_;'; put 'file "&dir/macvars.sas";'; put 'set sashelp.vmacro;'; put 'where scope=''GLOBAL'';'; put 'put ''%let '' name ''='' value '';'';'; put 'run;'; put 'data approve.jsdset;'; put 'length _____DELETE__THIS__RECORD_____ $3;'; put 'set jsdata;'; put 'run;'; put '/**'; put '* mf_getvarXXX functions will fail if the target is locked - so take a copy'; put '* and reference that (this will also explicitly throw the lock situation)'; put '*/'; put '%let dscopy=work.dscopy;'; put 'data &dscopy;'; put 'set &libds;'; put 'stop;'; put 'run;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(Issue getting lock on &libds)'; put ')'; put '%mp_ds2csv(approve.jsdset'; put ',dlm=COMMA'; put ',outfile="&dir/&orig_libds..csv"'; put ',outencoding="UTF-8"'; put ',headerformat=NAME'; put ',termstr=CRLF'; put ')'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc when writing the CSV)'; put ')'; put '%mpe_loader(mperef=&mperef'; put ',submitted_reason_txt=%superq(message)'; put ',approver=%quote(%trim(&approver))'; put ',url=%superq(url)'; put ',dc_dttmtfmt=&dc_dttmtfmt'; put ')'; put '%mp_abort(mode=INCLUDE)'; put '%mp_abort('; put 'iftrue=(%sysfunc(fileexist(%sysfunc(pathname(work))/mf_abort.error))=1)'; put ',mac=&_program..sas'; put ',msg=%str(mf_abort.error=1)'; put ')'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(syscc=&syscc)'; put ')'; put '/* send relevant SUCCESS values */'; put 'data sasparams;'; put 'STATUS=''SUCCESS'';'; put 'DSID="&mperef";'; put 'url="&url";'; put 'run;'; put '%removecolsfromwork(___TMP___MD5)'; put '%webout(OPEN)'; put '%webout(OBJ,sasparams)'; put '%webout(CLOSE)'; put '%mpeterm()'; put '* Service end;'; run; %mm_createwebservice(path=&appLoc/&path, name=&service, code=sascode, server=&serverName, replace=yes) filename sascode clear; %let path=services/hooks; %let service=mpe_column_level_security_postedit; filename sascode temp lrecl=32767; data _null_; file sascode; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '/**'; put '@file mp_jsonout.sas'; put '@brief Writes JSON in SASjs format to a fileref'; put '@details This macro can be used to OPEN a JSON stream and send one or more'; put 'tables as arrays of rows, where each row can be an object or a nested array.'; put 'There are two engines available - DATASTEP or PROCJSON.'; put 'PROC JSON is fast but will produce errs like the ones below if'; put 'special chars are encountered.'; put '> (ERR)OR: Some code points did not transcode.'; put '> An object or array close is not valid at this point in the JSON text.'; put '> Date value out of range'; put 'If this happens, try running with ENGINE=DATASTEP.'; put 'The DATASTEP engine is used to handle special SAS missing numerics, and'; put 'can also convert entire datasets to formatted values. Output JSON is always'; put 'in UTF-8.'; put 'Usage:'; put 'filename tmp temp;'; put 'data class; set sashelp.class;run;'; put '%mp_jsonout(OPEN,jref=tmp)'; put '%mp_jsonout(OBJ,class,jref=tmp)'; put '%mp_jsonout(OBJ,class,dslabel=class2,jref=tmp,showmeta=Y)'; put '%mp_jsonout(CLOSE,jref=tmp)'; put 'data _null_;'; put 'infile tmp;'; put 'input;putlog _infile_;'; put 'run;'; put 'If you are building web apps with SAS then you are strongly encouraged to use'; put 'the mX_createwebservice macros in combination with the'; put '[sasjs adapter](https://github.com/sasjs/adapter).'; put 'For more information see https://sasjs.io'; put '@param [in] action Valid values:'; put '@li OPEN - opens the JSON'; put '@li OBJ - sends a table with each row as an object'; put '@li ARR - sends a table with each row in an array'; put '@li CLOSE - closes the JSON'; put '@param [in] ds The dataset to send. Must be a work table.'; put '@param [out] jref= (_webout) The fileref to which to send the JSON'; put '@param [out] dslabel= The name to give the table in the exported JSON'; put '@param [in] fmt= (Y) Whether to keep (Y) or strip (N) formats from the table'; put '@param [in] engine= (DATASTEP) Which engine to use to send the JSON. Options:'; put '@li PROCJSON (default)'; put '@li DATASTEP (more reliable when data has non standard characters)'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to JSON'; put '

Related Files

'; put '@li mp_ds2fmtds.sas'; put '@version 9.2'; put '@author Allan Bowe'; put '@source https://github.com/sasjs/core'; put '**/'; put '%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y'; put ',engine=DATASTEP'; put ',missing=NULL'; put ',showmeta=N'; put ',maxobs=MAX'; put ')/*/STORE SOURCE*/;'; put '%local tempds colinfo fmtds i numcols numobs stmt_obs lastobs optval'; put 'tmpds1 tmpds2 tmpds3 tmpds4;'; put '%let numcols=0;'; put '%if &maxobs ne MAX %then %let stmt_obs=%str(if _n_>&maxobs then stop;);'; put '%if &action=OPEN %then %do;'; put 'options nobomfile;'; put 'data _null_;file &jref encoding=''utf-8'' lrecl=200;'; put 'put ''{"PROCESSED_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''"'';'; put 'run;'; put '%end;'; put '%else %if (&action=ARR or &action=OBJ) %then %do;'; put '/* force variable names to always be uppercase in the JSON */'; put 'options validvarname=upcase;'; put '/* To avoid issues with _webout on EBI - such as encoding diffs and truncation'; put '(https://support.sas.com/kb/49/325.html) we use temporary files */'; put 'filename _sjs1 temp lrecl=200 ;'; put 'data _null_; file _sjs1 encoding=''utf-8'';'; put 'put ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":";'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjs1 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs1 clear;'; put '/* grab col defs */'; put 'proc contents noprint data=&ds'; put 'out=_data_(keep=name type length format formatl formatd varnum label);'; put 'run;'; put '%let colinfo=%scan(&syslast,2,.);'; put 'proc sort data=&colinfo;'; put 'by varnum;'; put 'run;'; put '/* move meta to mac vars */'; put 'data &colinfo;'; put 'if _n_=1 then call symputx(''numcols'',nobs,''l'');'; put 'set &colinfo end=last nobs=nobs;'; put 'name=upcase(name);'; put '/* fix formats */'; put 'if type=2 or type=6 then do;'; put 'typelong=''char'';'; put 'length fmt $49.;'; put 'if format='''' then fmt=cats(''$'',length,''.'');'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else fmt=cats(format,formatl,''.'');'; put 'end;'; put 'else do;'; put 'typelong=''num'';'; put 'if format='''' then fmt=''best.'';'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else if formatd=0 then fmt=cats(format,formatl,''.'');'; put 'else fmt=cats(format,formatl,''.'',formatd);'; put 'end;'; put '/* 32 char unique name */'; put 'newname=''sasjs''!!substr(cats(put(md5(name),$hex32.)),1,27);'; put 'call symputx(cats(''name'',_n_),name,''l'');'; put 'call symputx(cats(''newname'',_n_),newname,''l'');'; put 'call symputx(cats(''length'',_n_),length,''l'');'; put 'call symputx(cats(''fmt'',_n_),fmt,''l'');'; put 'call symputx(cats(''type'',_n_),type,''l'');'; put 'call symputx(cats(''typelong'',_n_),typelong,''l'');'; put 'call symputx(cats(''label'',_n_),coalescec(label,name),''l'');'; put '/* overwritten when fmt=Y and a custom format exists in catalog */'; put 'if typelong=''num'' then call symputx(cats(''fmtlen'',_n_),200,''l'');'; put 'else call symputx(cats(''fmtlen'',_n_),min(32767,ceil((length+10)*1.5)),''l'');'; put 'run;'; put '%let tempds=%substr(_%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql;'; put 'select count(*) into: lastobs from &ds;'; put '%if &maxobs ne MAX %then %let lastobs=%sysfunc(min(&lastobs,&maxobs));'; put '%if &engine=PROCJSON %then %do;'; put '%if &missing=STRING %then %do;'; put '%put &sysmacroname: Special Missings not supported in proc json.;'; put '%put &sysmacroname: Switching to DATASTEP engine;'; put '%goto datastep;'; put '%end;'; put 'data &tempds;'; put 'set &ds;'; put '&stmt_obs;'; put '%if &fmt=N %then format _numeric_ best32.;;'; put '/* PRETTY is necessary to avoid line truncation in large files */'; put 'filename _sjs2 temp lrecl=131068 encoding=''utf-8'';'; put 'proc json out=_sjs2 pretty'; put '%if &action=ARR %then nokeys ;'; put ';export &tempds / nosastags fmtnumeric;'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs2 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs2 clear;'; put '%end;'; put '%else %if &engine=DATASTEP %then %do;'; put '%datastep:'; put '%if %sysfunc(exist(&ds)) ne 1 & %sysfunc(exist(&ds,VIEW)) ne 1'; put '%then %do;'; put '%put &sysmacroname: &ds NOT FOUND!!!;'; put '%return;'; put '%end;'; put '%if &fmt=Y %then %do;'; put '/**'; put '* Extract format definitions'; put '* First, by getting library locations from dictionary.formats'; put '* Then, by exporting the width using proc format'; put '* Cannot use maxw from sashelp.vformat as not always populated'; put '* Cannot use fmtinfo() as not supported in all flavours'; put '*/'; put '%let tmpds1=%substr(fmtsum%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds2=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds3=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds4=%substr(col%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql noprint;'; put 'create table &tmpds1 as'; put 'select cats(libname,''.'',memname) as FMTCAT,'; put 'FMTNAME'; put 'from dictionary.formats'; put 'where fmttype=''F'' and libname is not null'; put 'and fmtname in (select format from &colinfo where format is not null)'; put 'order by 1;'; put 'create table &tmpds2('; put 'FMTNAME char(32),'; put 'LENGTH num'; put ');'; put '%local catlist cat fmtlist i;'; put 'select distinct fmtcat into: catlist separated by '' '' from &tmpds1;'; put '%do i=1 %to %sysfunc(countw(&catlist,%str( )));'; put '%let cat=%scan(&catlist,&i,%str( ));'; put 'proc sql;'; put 'select distinct fmtname into: fmtlist separated by '' '''; put 'from &tmpds1 where fmtcat="&cat";'; put 'proc format lib=&cat cntlout=&tmpds3(keep=fmtname length);'; put 'select &fmtlist;'; put 'run;'; put 'proc sql;'; put 'insert into &tmpds2 select distinct fmtname,length from &tmpds3;'; put '%end;'; put 'proc sql;'; put 'create table &tmpds4 as'; put 'select a.*, b.length as MAXW'; put 'from &colinfo a'; put 'left join &tmpds2 b'; put 'on cats(a.format)=cats(upcase(b.fmtname))'; put 'order by a.varnum;'; put 'data _null_;'; put 'set &tmpds4;'; put 'if not missing(maxw);'; put 'call symputx('; put 'cats(''fmtlen'',_n_),'; put '/* vars need extra padding due to JSON escaping of special chars */'; put 'min(32767,ceil((max(length,maxw)+10)*1.5))'; put ',''l'''; put ');'; put 'run;'; put '/* configure varlenchk - as we are explicitly shortening the variables */'; put '%let optval=%sysfunc(getoption(varlenchk));'; put 'options varlenchk=NOWARN;'; put 'data _data_(compress=char);'; put '/* shorten the new vars */'; put 'length'; put '%do i=1 %to &numcols;'; put '&&name&i $&&fmtlen&i'; put '%end;'; put ';'; put '/* rename on entry */'; put 'set &ds(rename=('; put '%do i=1 %to &numcols;'; put '&&name&i=&&newname&i'; put '%end;'; put '));'; put '&stmt_obs;'; put 'drop'; put '%do i=1 %to &numcols;'; put '&&newname&i'; put '%end;'; put ';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=num %then %do;'; put '&&name&i=cats(put(&&newname&i,&&fmt&i));'; put '%end;'; put '%else %do;'; put '&&name&i=put(&&newname&i,&&fmt&i);'; put '%end;'; put '%end;'; put 'if _error_ then do;'; put 'call symputx(''syscc'',1012);'; put 'stop;'; put 'end;'; put 'run;'; put '%let fmtds=&syslast;'; put 'options varlenchk=&optval;'; put '%end;'; put 'proc format; /* credit yabwon for special null removal */'; put 'value bart (default=40)'; put '%if &missing=NULL %then %do;'; put '._ - .z = null'; put '%end;'; put '%else %do;'; put '._ = [quote()]'; put '. = null'; put '.a - .z = [quote()]'; put '%end;'; put 'other = [best.];'; put 'data &tempds;'; put 'attrib _all_ label='''';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'length &&name&i $&&fmtlen&i...;'; put 'format &&name&i $&&fmtlen&i...;'; put '%end;'; put '%end;'; put '%if &fmt=Y %then %do;'; put 'set &fmtds;'; put '%end;'; put '%else %do;'; put 'set &ds;'; put '%end;'; put '&stmt_obs;'; put 'format _numeric_ bart.;'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'if findc(&&name&i,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put '&&name&i=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,&&name&i)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else &&name&i=quote(cats(&&name&i));'; put '%end;'; put '%end;'; put 'run;'; put 'filename _sjs3 temp lrecl=131068 ;'; put 'data _null_;'; put 'file _sjs3 encoding=''utf-8'';'; put 'if _n_=1 then put "[";'; put 'set &tempds;'; put 'if _n_>1 then put "," @; put'; put '%if &action=ARR %then "[" ; %else "{" ;'; put '%do i=1 %to &numcols;'; put '%if &i>1 %then "," ;'; put '%if &action=OBJ %then """&&name&i"":" ;'; put '"&&name&i"n /* name literal for reserved variable names */'; put '%end;'; put '%if &action=ARR %then "]" ; %else "}" ; ;'; put '/* close out the table */'; put 'data _null_;'; put 'file _sjs3 mod encoding=''utf-8'';'; put 'put '']'';'; put 'run;'; put 'data _null_;'; put 'infile _sjs3 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs3 clear;'; put '%end;'; put 'proc sql;'; put 'drop table &colinfo, &tempds;'; put '%if %substr(&showmeta,1,1)=Y %then %do;'; put 'filename _sjs4 temp lrecl=131068 encoding=''utf-8'';'; put 'data _null_;'; put 'file _sjs4;'; put 'length label $350;'; put 'put ", ""$%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":{""vars"":{";'; put 'do i=1 to &numcols;'; put 'name=quote(trim(symget(cats(''name'',i))));'; put 'format=quote(trim(symget(cats(''fmt'',i))));'; put 'label=quote(prxchange(''s/\\/\\\\/'',-1,trim(symget(cats(''label'',i)))));'; put 'length=quote(trim(symget(cats(''length'',i))));'; put 'type=quote(trim(symget(cats(''typelong'',i))));'; put 'if i>1 then put "," @@;'; put 'put name '':{"format":'' format '',"label":'' label'; put ''',"length":'' length '',"type":'' type ''}'';'; put 'end;'; put 'put ''}}'';'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs4 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs4 clear;'; put '%end;'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put 'data _null_; file &jref encoding=''utf-8'' mod ;'; put 'put "}";'; put 'run;'; put '%end;'; put '%mend mp_jsonout;'; put '/**'; put '@file mm_webout.sas'; put '@brief Send data to/from SAS Stored Processes'; put '@details This macro should be added to the start of each Stored Process,'; put '**immediately** followed by a call to:'; put '%mm_webout(FETCH)'; put 'This will read all the input data and create same-named SAS datasets in the'; put 'WORK library. You can then insert your code, and send data back using the'; put 'following syntax:'; put 'data some datasets; * make some data ;'; put 'retain some columns;'; put 'run;'; put '%mm_webout(OPEN)'; put '%mm_webout(ARR,some) * Array format, fast, suitable for large tables ;'; put '%mm_webout(OBJ,datasets) * Object format, easier to work with ;'; put 'Finally, wrap everything up send some helpful system variables too'; put '%mm_webout(CLOSE)'; put '@param [in] action Either FETCH, OPEN, ARR, OBJ or CLOSE'; put '@param [in] ds The dataset to send back to the frontend'; put '@param [out] dslabel= Value to use instead of table name for sending to JSON'; put '@param [in] fmt= (N) Setting Y converts all vars to their formatted values'; put '@param [out] fref= (_webout) The fileref to which to write the JSON'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] workobs= (0) When set to a positive integer, will create a new'; put 'output object (WORK) which contains this number of observations from all'; put 'tables in the WORK library.'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to output JSON'; put '

SAS Macros

'; put '@li mp_jsonout.sas'; put '

Related Macros

'; put '@li ms_webout.sas'; put '@li mv_webout.sas'; put '@version 9.3'; put '@author Allan Bowe'; put '**/'; put '%macro mm_webout(action,ds,dslabel=,fref=_webout,fmt=N,missing=NULL'; put ',showmeta=N,maxobs=MAX,workobs=0'; put ');'; put '%global _webin_file_count _webin_fileref1 _webin_name1 _program _debug'; put 'sasjs_tables;'; put '%local i tempds jsonengine;'; put '/* see https://github.com/sasjs/core/issues/41 */'; put '%if "%upcase(&SYSENCODING)" ne "UTF-8" %then %let jsonengine=PROCJSON;'; put '%else %let jsonengine=DATASTEP;'; put '%if &action=FETCH %then %do;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'options mprint notes mprintnest;'; put '%end;'; put '%let _webin_file_count=%eval(&_webin_file_count+0);'; put '/* now read in the data */'; put '%do i=1 %to &_webin_file_count;'; put '%if &_webin_file_count=1 %then %do;'; put '%let _webin_fileref1=&_webin_fileref;'; put '%let _webin_name1=&_webin_name;'; put '%end;'; put 'data _null_;'; put 'infile &&_webin_fileref&i termstr=crlf;'; put 'input;'; put 'call symputx(''input_statement'',_infile_);'; put 'putlog "&&_webin_name&i input statement: " _infile_;'; put 'stop;'; put 'data &&_webin_name&i;'; put 'infile &&_webin_fileref&i firstobs=2 dsd termstr=crlf encoding=''utf-8'';'; put 'input &input_statement;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'if _n_<20 then putlog _infile_;'; put '%end;'; put 'run;'; put '%let sasjs_tables=&sasjs_tables &&_webin_name&i;'; put '%end;'; put '%end;'; put '%else %if &action=OPEN %then %do;'; put '/* fix encoding */'; put 'OPTIONS NOBOMFILE;'; put '/**'; put '* check xengine type to avoid the below err message:'; put '* > Function is only valid for filerefs using the CACHE access method.'; put '*/'; put 'data _null_;'; put 'set sashelp.vextfl(where=(fileref="_WEBOUT"));'; put 'if xengine=''STREAM'' then do;'; put 'rc=stpsrv_header(''Content-type'',"text/html; encoding=utf-8");'; put 'end;'; put 'run;'; put '/* setup json */'; put 'data _null_;file &fref encoding=''utf-8'';'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutBEGIN<<'';'; put '%end;'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'run;'; put '%end;'; put '%else %if &action=ARR or &action=OBJ %then %do;'; put '%mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt,jref=&fref'; put ',engine=&jsonengine,missing=&missing,showmeta=&showmeta,maxobs=&maxobs'; put ')'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put '/* To avoid issues with _webout on EBI we use a temporary file */'; put 'filename _sjsref temp lrecl=131068;'; put '%if %str(&workobs) > 0 %then %do;'; put '/* if debug mode, send back first XX records of each work table also */'; put 'data;run;%let tempds=%scan(&syslast,2,.);'; put 'ods output Members=&tempds;'; put 'proc datasets library=WORK memtype=data;'; put '%local wtcnt;%let wtcnt=0;'; put 'data _null_;'; put 'set &tempds;'; put 'if not (upcase(name) =:"DATA"); /* ignore temp datasets */'; put 'i+1;'; put 'call symputx(cats(''wt'',i),name,''l'');'; put 'call symputx(''wtcnt'',i,''l'');'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put ",""WORK"":{";'; put '%do i=1 %to &wtcnt;'; put '%let wt=&&wt&i;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'dsid=open("WORK.&wt",''is'');'; put 'nlobs=attrn(dsid,''NLOBS'');'; put 'nvars=attrn(dsid,''NVARS'');'; put 'rc=close(dsid);'; put 'if &i>1 then put '',''@;'; put 'put " ""&wt"" : {";'; put 'put ''"nlobs":'' nlobs;'; put 'put '',"nvars":'' nvars;'; put '%mp_jsonout(OBJ,&wt,jref=_sjsref,dslabel=first10rows,showmeta=Y'; put ',maxobs=&workobs'; put ')'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put '%end;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put 'run;'; put '%end;'; put '/* close off json */'; put 'data _null_;file _sjsref mod encoding=''utf-8'';'; put 'length SYSPROCESSNAME syserrortext syswarningtext autoexec $512;'; put 'put ",""_DEBUG"" : ""&_debug"" ";'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'autoexec=quote(urlencode(trim(getoption(''autoexec''))));'; put 'put '',"AUTOEXEC" : '' autoexec;'; put 'put ",""MF_GETUSER"" : ""%mf_getuser()"" ";'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'put ",""SYSENCODING"" : ""&sysencoding"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSPROCESSID"" : ""&SYSPROCESSID"" ";'; put 'put ",""SYSPROCESSMODE"" : ""&SYSPROCESSMODE"" ";'; put 'SYSPROCESSNAME=quote(urlencode(cats(SYSPROCESSNAME)));'; put 'put ",""SYSPROCESSNAME"" : " SYSPROCESSNAME;'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put '',"SYSWARNINGTEXT" : '' syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'length memsize $32;'; put 'memsize="%sysfunc(INPUTN(%sysfunc(getoption(memsize)), best.),sizekmg.)";'; put 'memsize=quote(cats(memsize));'; put 'put '',"MEMSIZE" : '' memsize;'; put 'put "}" @;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutEND<<'';'; put '%end;'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjsref lrecl=1 recfm=n;'; put 'file &fref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjsref clear;'; put '%end;'; put '%mend mm_webout;'; put '%macro webout(action,ds,dslabel=,fmt=,missing=NULL,showmeta=NO,maxobs=MAX);'; put '%mm_webout(&action,ds=&ds,dslabel=&dslabel,fmt=&fmt'; put ',missing=&missing'; put ',showmeta=&showmeta'; put ',maxobs=&maxobs'; put ') %mend;'; put '/* provide additional debug info */'; put '%global _program;'; put '%put &=syscc;'; put '%put user=%mf_getuser();'; put '%put pgm=&_program;'; put '%put timestamp=%sysfunc(datetime(),datetime19.);'; put '* Service Variables start;'; put '* Service Variables end;'; put '* SAS Macros start;'; put '%macro mf_getapploc(pgm);'; put '%if "&pgm"="" %then %do;'; put '%if %symexist(_program) %then %let pgm=&_program;'; put '%else %do;'; put '%put &sysmacroname: No value provided and no _program variable available;'; put '%return;'; put '%end;'; put '%end;'; put '%local root;'; put '/**'; put '* First check we are not in the tests/macros folder (which has no subfolders)'; put '* or specifically in the testsetup or testteardown services'; put '*/'; put '%if %index(&pgm,/tests/macros/)'; put 'or %index(&pgm,/tests/testsetup)'; put 'or %index(&pgm,/tests/testteardown)'; put '%then %do;'; put '%let root=%substr(&pgm,1,%index(&pgm,/tests)-1);'; put '&root'; put '%return;'; put '%end;'; put '/**'; put '* Next, move up two levels to avoid matches on subfolder or service name'; put '*/'; put '%let root=%substr(&pgm,1,%length(&pgm)-%length(%scan(&pgm,-1,/))-1);'; put '%let root=%substr(&root,1,%length(&root)-%length(%scan(&root,-1,/))-1);'; put '%if %index(&root,/tests/) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/tests/)-1);'; put '%end;'; put '%else %if %index(&root,/services) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/services)-1);'; put '%end;'; put '%else %if %index(&root,/jobs) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/jobs)-1);'; put '%end;'; put '%else %put &sysmacroname: Could not find an app location from &pgm;'; put '&root'; put '%mend mf_getapploc ;'; put '%macro mf_getuniquefileref(prefix=_,maxtries=1000,lrecl=32767);'; put '%local rc fname;'; put '%if &prefix=0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%end;'; put '%else %do;'; put '%local x len;'; put '%let len=%eval(8-%length(&prefix));'; put '%let x=0;'; put '%do x=0 %to &maxtries;'; put '%let fname=&prefix%substr(%sysfunc(ranuni(0)),3,&len);'; put '%if %sysfunc(fileref(&fname)) > 0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%return;'; put '%end;'; put '%end;'; put '%put unable to find available fileref after &maxtries attempts;'; put '%end;'; put '%mend mf_getuniquefileref;'; put '%macro mp_abort(mac=mp_abort.sas, type=, msg=, iftrue=%str(1=1)'; put ', errds=work.mp_abort_errds'; put ', mode=REGULAR'; put ')/*/STORE SOURCE*/;'; put '%global sysprocessmode sysprocessname sasjs_stpsrv_header_loc sasjsprocessmode;'; put '%local fref fid i;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%put NOTE: /// mp_abort macro executing //;'; put '%if %length(&mac)>0 %then %put NOTE- called by &mac;'; put '%put NOTE - &msg;'; put '%if %symexist(_SYSINCLUDEFILEDEVICE)'; put '/* abort cancel FILE does not restart outside the INCLUDE on Viya 3.5 */'; put 'and %superq(SYSPROCESSNAME) ne %str(Compute Server)'; put '%then %do;'; put '%if "*&_SYSINCLUDEFILEDEVICE*" ne "**" %then %do;'; put 'data &errds;'; put 'iftrue=''1=1'';'; put 'length mac $100 msg $5000;'; put 'mac=symget(''mac'');'; put 'msg=symget(''msg'');'; put 'run;'; put 'data _null_;'; put 'abort cancel FILE;'; put 'run;'; put '%return;'; put '%end;'; put '%end;'; put '/* Web App Context */'; put '%if %symexist(_PROGRAM)'; put 'or %superq(SYSPROCESSNAME) = %str(Compute Server)'; put 'or &mode=INCLUDE'; put '%then %do;'; put 'options obs=max replace mprint;'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5"'; put '%then %do;'; put 'options nosyntaxcheck;'; put '%end;'; put '%if &mode=INCLUDE %then %do;'; put '%if %sysfunc(exist(&errds))=1 %then %do;'; put 'data _null_;'; put 'set &errds;'; put 'call symputx(''iftrue'',iftrue,''l'');'; put 'call symputx(''mac'',mac,''l'');'; put 'call symputx(''msg'',msg,''l'');'; put 'putlog (_all_)(=);'; put 'run;'; put '%if (&iftrue)=0 %then %return;'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: No include errors found;'; put '%return;'; put '%end;'; put '%end;'; put '/* extract log errs / warns, if exist */'; put '%local logloc logline;'; put '%global logmsg; /* capture global messages */'; put '%if %symexist(SYSPRINTTOLOG) %then %let logloc=&SYSPRINTTOLOG;'; put '%else %let logloc=%qsysfunc(getoption(LOG));'; put 'proc printto log=log;run;'; put '%let logline=0;'; put '%if %length(&logloc)>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input; putlog _infile_;'; put 'i=1;'; put 'retain logonce 0;'; put 'if ('; put '_infile_=:"%str(WARN)ING" or _infile_=:"%str(ERR)OR"'; put ') and logonce=0 then'; put 'do;'; put 'call symputx(''logline'',_n_);'; put 'logonce+1;'; put 'end;'; put 'run;'; put '/* capture log including lines BEFORE the err */'; put '%if &logline>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input;'; put 'i=1;'; put 'stoploop=0;'; put 'if _n_ ge &logline-15 and stoploop=0 then do until (i>22);'; put 'call symputx(''logmsg'',catx(''\n'',symget(''logmsg''),_infile_));'; put 'input;'; put 'i+1;'; put 'stoploop=1;'; put 'end;'; put 'if stoploop=1 then stop;'; put 'run;'; put '%end;'; put '%end;'; put '%if %symexist(SYS_JES_JOB_URI) %then %do;'; put '/* setup webout for Viya */'; put 'options nobomfile;'; put '%if "X&SYS_JES_JOB_URI.X"="XX" %then %do;'; put 'filename _webout temp lrecl=999999 mod;'; put '%end;'; put '%else %do;'; put 'filename _webout filesrvc parenturi="&SYS_JES_JOB_URI"'; put 'name="_webout.json" lrecl=999999 mod;'; put '%end;'; put '%end;'; put '%else %if %sysfunc(filename(fref,&sasjs_stpsrv_header_loc))=0 %then %do;'; put 'options nobomfile;'; put '/* set up http header for SASjs Server */'; put '%let fid=%sysfunc(fopen(&fref,A));'; put '%if &fid=0 %then %do;'; put '%put %str(ERR)OR: %sysfunc(sysmsg());'; put '%return;'; put '%end;'; put '%let rc=%sysfunc(fput(&fid,%str(Content-Type: application/json)));'; put '%let rc=%sysfunc(fwrite(&fid));'; put '%let rc=%sysfunc(fclose(&fid));'; put '%let rc=%sysfunc(filename(&fref));'; put '%end;'; put '/* send response in SASjs JSON format */'; put 'data _null_;'; put 'file _webout mod lrecl=32000 encoding=''utf-8'';'; put 'length msg syswarningtext syserrortext $32767 mode $10 ;'; put 'sasdatetime=datetime();'; put 'msg=symget(''msg'');'; put '%if &logline>0 %then %do;'; put 'msg=cats(msg,''\n\nLog Extract:\n'',symget(''logmsg''));'; put '%end;'; put '/* escape the escapes */'; put 'msg=tranwrd(msg,''\'',''\\'');'; put '/* escape the quotes */'; put 'msg=tranwrd(msg,''"'',''\"'');'; put '/* ditch the CRLFs as chrome complains */'; put 'msg=compress(msg,,''kw'');'; put '/* quote without quoting the quotes (which are escaped instead) */'; put 'msg=cats(''"'',msg,''"'');'; put 'if symexist(''_debug'') then debug=quote(trim(symget(''_debug'')));'; put 'else debug=''""'';'; put 'if symget(''sasjsprocessmode'')=''Stored Program'' then mode=''SASJS'';'; put 'if mode ne ''SASJS'' then put ''>>weboutBEGIN<<'';'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'put '',"sasjsAbort" : [{'';'; put 'put '' "MSG":'' msg ;'; put 'put '' ,"MAC": "'' "&mac" ''"}]'';'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'put '',"_DEBUG":'' debug ;'; put 'if symexist(''_metauser'') then do;'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put 'end;'; put 'if symexist(''SYS_JES_JOB_URI'') then do;'; put 'SYS_JES_JOB_URI=quote(trim(symget(''SYS_JES_JOB_URI'')));'; put 'put '',"SYS_JES_JOB_URI": '' SYS_JES_JOB_URI;'; put 'end;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put ",""SYSWARNINGTEXT"" : " syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'put "}" ;'; put 'if mode ne ''SASJS'' then put ''>>weboutEND<<'';'; put 'run;'; put '%put _all_;'; put '%if "&sysprocessmode " = "SAS Stored Process Server " %then %do;'; put 'data _null_;'; put 'putlog ''stpsrvset program err and syscc'';'; put 'rc=stpsrvset(''program error'', 0);'; put 'call symputx("syscc",0,"g");'; put 'run;'; put '%if &sysscp=WIN'; put 'and 1=0 /* deprecating this logic until we figure out a consistent abort */'; put 'and "%substr(%str(&sysvlong ),1,8)"="9.04.01M"'; put 'and "%substr(%str(&sysvlong ),9,1)">"5" %then %do;'; put '/* skip approach (below) does not work in windows m6+ envs */'; put 'endsas;'; put '%end;'; put '%else %do;'; put '/**'; put '* endsas kills 9.4m3 deployments by orphaning multibridges.'; put '* Abort variants are ungraceful (non zero return code)'; put '* This approach lets SAS run silently until the end :-)'; put '* Caution - fails when called within a %include within a macro'; put '* Use mp_include() to handle this.'; put '*/'; put 'filename skip temp;'; put 'data _null_;'; put 'file skip;'; put 'put ''%macro skip();'';'; put 'comment ''%mend skip; -> fix lint '';'; put 'put ''%macro skippy();'';'; put 'comment ''%mend skippy; -> fix lint '';'; put 'run;'; put '%inc skip;'; put '%end;'; put '%end;'; put '%else %if "&sysprocessmode " = "SAS Compute Server " %then %do;'; put '/* endsas kills the session making it harder to fetch results */'; put 'data _null_;'; put 'syswarningtext=symget(''syswarningtext'');'; put 'syserrortext=symget(''syserrortext'');'; put 'abort_msg=symget(''msg'');'; put 'syscc=symget(''syscc'');'; put 'sysuserid=symget(''sysuserid'');'; put 'iftrue=symget(''iftrue'');'; put 'put (_all_)(/=);'; put 'call symputx(''syscc'',0);'; put 'abort cancel nolist;'; put 'run;'; put '%end;'; put '%else %do;'; put '%abort cancel;'; put '%end;'; put '%end;'; put '%else %do;'; put '%put _all_;'; put '%abort cancel;'; put '%end;'; put '%mend mp_abort;'; put '/** @endcond */'; put '%macro mm_getstpcode('; put 'tree=/User Folders/sasdemo/somestp'; put ',name='; put ',outloc=0'; put ',outref=0'; put ',mDebug=1'; put ',showlog=NO'; put ');'; put '%local mD;'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing &sysmacroname..sas;'; put '%&mD.put _local_;'; put '%if %length(&name)>0 %then %let name=/&name;'; put '/* first, check if STP exists */'; put '%local tsuri;'; put '%let tsuri=stopifempty ;'; put 'data _null_;'; put 'format type uri tsuri value $200.;'; put 'call missing (of _all_);'; put 'path="&tree&name(StoredProcess)";'; put '/* first, find the STP ID */'; put 'if metadata_pathobj("",path,"StoredProcess",type,uri)>0 then do;'; put '/* get sourcecode */'; put 'cnt=1;'; put 'do while (metadata_getnasn(uri,"Notes",cnt,tsuri)>0);'; put 'rc=metadata_getattr(tsuri,"Name",value);'; put '&mD.put tsuri= value=;'; put 'if value="SourceCode" then do;'; put '/* found it! */'; put 'rc=metadata_getattr(tsuri,"Id",value);'; put 'call symputx(''tsuri'',value,''l'');'; put 'stop;'; put 'end;'; put 'cnt+1;'; put 'end;'; put 'end;'; put 'else put (_all_)(=);'; put 'run;'; put '%mp_abort(iftrue= (&tsuri=stopifempty)'; put ',mac=mm_getstpcode'; put ',msg=%str(&tree&name.(StoredProcess) not found!)'; put ')'; put '/**'; put '* Now we can extract the textstore'; put '*/'; put 'filename __getdoc temp lrecl=10000000;'; put 'proc metadata'; put 'in="$METAREPOSITORY'; put ''; put 'SAS1"'; put 'out=__getdoc ;'; put 'run;'; put '/* find the beginning of the text */'; put '%local start;'; put 'data _null_;'; put 'infile __getdoc lrecl=10000;'; put 'input;'; put 'start=index(_infile_,''StoredText="'');'; put 'if start then do;'; put 'call symputx("start",start+11);'; put '*putlog ''"'' _infile_ ''"'';'; put 'end;'; put 'stop;'; put '%local outeng;'; put '%if "&outloc"="0" %then %let outeng=TEMP;'; put '%else %let outeng="&outloc";'; put '%local fref;'; put '%if &outref=0 %then %let fref=%mf_getuniquefileref();'; put '%else %let fref=&outref;'; put '/* read the content, byte by byte, resolving escaped chars */'; put 'filename &fref &outeng lrecl=100000;'; put 'data _null_;'; put 'length filein 8 fileid 8;'; put 'filein = fopen("__getdoc","I",1,"B");'; put 'fileid = fopen("&fref","O",1,"B");'; put 'rec = "20"x;'; put 'length entity $6;'; put 'do while(fread(filein)=0);'; put 'x+1;'; put 'if x>&start then do;'; put 'rc = fget(filein,rec,1);'; put 'if rec=''"'' then leave;'; put 'else if rec="&" then do;'; put 'entity=rec;'; put 'do until (rec=";");'; put 'if fread(filein) ne 0 then goto getout;'; put 'rc = fget(filein,rec,1);'; put 'entity=cats(entity,rec);'; put 'end;'; put 'select (entity);'; put 'when (''&'' ) rec=''&'' ;'; put 'when (''<'' ) rec=''<'' ;'; put 'when (''>'' ) rec=''>'' ;'; put 'when (''''') rec="''" ;'; put 'when (''"'') rec=''"'' ;'; put 'when ('' '') rec=''0A''x;'; put 'when ('' '') rec=''0D''x;'; put 'when (''$'' ) rec=''$'' ;'; put 'when ('' '') rec=''09''x;'; put 'otherwise putlog "%str(WARN)ING: missing value for " entity=;'; put 'end;'; put 'rc =fput(fileid, substr(rec,1,1));'; put 'rc =fwrite(fileid);'; put 'end;'; put 'else do;'; put 'rc =fput(fileid,rec);'; put 'rc =fwrite(fileid);'; put 'end;'; put 'end;'; put 'end;'; put 'getout:'; put 'rc=fclose(filein);'; put 'rc=fclose(fileid);'; put 'run;'; put '%if &showlog=YES %then %do;'; put 'data _null_;'; put 'infile &fref lrecl=32767 end=last;'; put 'input;'; put 'if _n_=1 then putlog ''>>stpcodeBEGIN<<'';'; put 'putlog _infile_;'; put 'if last then putlog ''>>stpcodeEND<<'';'; put 'run;'; put '%end;'; put 'filename __getdoc clear;'; put '%if &outref=0 %then %do;'; put 'filename &fref clear;'; put '%end;'; put '%mend mm_getstpcode;'; put '%macro dc_getsettings();'; put '%global _program;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(syscc=&syscc on entry (&syswarningtext &syserrortext))'; put ')'; put '%if %length(&_PROGRAM)>1 %then %let root=&_program;'; put '%else %do;'; put '%global _metauser;'; put '%let _metauser=&sysuserid;'; put '/* to mimic a "real" _program we need to give a dummy role and stp name */'; put '%let root=/dummyRole/dummyName;'; put '%end;'; put '/* the DC precode is stored in the Admin folder in the root of'; put 'the project. Lets find that root. */'; put '%put &=root;'; put '%let root=%mf_getapploc();'; put '%put &=root;'; put '/* Now we know the root location we can retrieve the params */'; put '%let temploc=%sysfunc(pathname(work))/settings.txt;'; put '%mm_getstpcode(tree=&root/services/public'; put ',name=Data_Controller_Settings'; put ',outloc=&temploc'; put ')'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Unable to run getstpcode)'; put ')'; put 'filename _getsets "&temploc" lrecl=2000;'; put '/*'; put 'Do not use mp_include here - this puts a copy in every service, which creates'; put 'compilation problems when calling services from mp_include'; put '*/'; put '%inc _getsets/source2;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Problem running &sysmacroname (&syswarningtext &syserrortext))'; put ')'; put '%mend dc_getsettings;'; put '%macro mf_fmtdttm('; put ')/*/STORE SOURCE*/;'; put '%if "&sysver"="9.2" or "&sysver"="9.3"'; put 'or ("&sysver"="9.4" and "%substr(&SYSVLONG,9,1)" le "3")'; put 'or "%substr(&sysver,1,1)"="4"'; put 'or "%substr(&sysver,1,1)"="5"'; put '%then %do;DATETIME19.3%end;'; put '%else %do;E8601DT26.6%end;'; put '%mend mf_fmtdttm;'; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '%macro mp_init(prefix=SASJS'; put ')/*/STORE SOURCE*/;'; put '%if %symexist(SASJS_PREFIX) %then %return; /* only run once */'; put '%global'; put 'SASJS_PREFIX /* the ONLY hard-coded global macro variable in SASjs */'; put '&prefix._FUNCTIONS /* used in mcf_init() to track core function compilation */'; put '&prefix._INIT_NUM /* initialisation time as numeric */'; put '&prefix._INIT_DTTM /* initialisation time in E8601DT26.6 format */'; put '&prefix.WORK /* avoid typing %sysfunc(pathname(work)) every time */'; put ';'; put '%let sasjs_prefix=&prefix;'; put 'data _null_;'; put 'dttm=datetime();'; put 'call symputx("&sasjs_prefix._init_num",dttm,''g'');'; put 'call symputx("&sasjs_prefix._init_dttm",put(dttm,E8601DT26.6),''g'');'; put 'call symputx("&sasjs_prefix.work",pathname(''WORK''),''g'');'; put 'run;'; put 'options'; put 'compress=CHAR /* default is none so ensure we have something! */'; put 'datastmtchk=ALLKEYWORDS /* protection from overwriting input datasets */'; put 'errorcheck=STRICT /* catch errs in libname/filename statements */'; put 'fmterr /* ensure err when a format cannot be found */'; put 'mergenoby=%str(ERR)OR /* throw err when a merge has no BY variables */'; put 'missing=. /* changing this can cause hard to detect errs */'; put 'noquotelenmax /* avoid warnings for long strings */'; put 'noreplace /* avoid overwriting permanent datasets */'; put 'ps=max /* reduce log size slightly */'; put 'ls=max /* reduce log even more and avoid word truncation */'; put 'validmemname=COMPATIBLE /* avoid special characters etc in table names */'; put 'validvarname=V7 /* avoid special characters etc in variable names */'; put 'varinitchk=%str(ERR)OR /* avoid data mistakes from variable name typos */'; put 'varlenchk=%str(ERR)OR /* fail hard if truncation (data loss) can result */'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5" %then %do;'; put 'noautocorrect /* disallow misspelled procedure names */'; put 'dsoptions=note2err /* undocumented - convert bad NOTEs to ERRs */'; put '%end;'; put ';'; put '%mend mp_init;'; put '%macro mpeinit(fetch=YES);'; put '%global mpeinit'; put 'mpeadmins /* group with unrestricted Meditor access */'; put 'mpelocapprovals /* location for landing and staging files */'; put 'mpelib /* location of configuration tables for DC */'; put 'dc_repo_users /* location of user / group metadata */'; put 'dc_licence_key /* extracted in dc_getsettings */'; put 'dc_activation_key /* extracted in dc_getsettings */'; put 'dc_locale /* extracted in dc_getsettings */'; put 'dc_dttmtfmt /* can be overridden in dc_getsettings */'; put '_debug'; put ';'; put '%if &mpeinit=1 %then %return;'; put '%else %let mpeinit=1;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(Problem on service startup (&syswarningtext &syserrortext))'; put ')'; put '%mp_init()'; put '%if &fetch=YES %then %do;'; put '%webout(FETCH)'; put '%end;'; put '%global _CLIENTNAME;'; put '%mp_abort(iftrue= (&_CLIENTNAME=SAS Enterprise Guide)'; put ',mac=&_program..sas'; put ',msg=%str(Data Controller is a web app and should not be executed from EG)'; put ')'; put 'options urlencoding=utf8 nobomfile lrecl=32767;'; put '%let perf=%sysfunc(datetime());'; put '%put perfdiff: 0;'; put '%let dc_locale=SYSTEM; /* default if not set */'; put '/**'; put '* E8601DT26.6 has widest database support - but not all SAS flavours can'; put '* handle it. Override in the settings STP if needed.'; put '*/'; put 'data _null_;'; put 'dc_dttmtfmt=''"%sysfunc(datetime(),''!!"%mf_fmtdttm()"!!'')"dt'';'; put 'call symputx(''dc_dttmtfmt'',dc_dttmtfmt);'; put 'put dc_dttmtfmt=;'; put 'run;'; put '%put &=dc_dttmtfmt;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc prior to dc_getsettings)'; put ')'; put '%dc_getsettings()'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc after dc_getsettings)'; put ')'; put 'data _null_;'; put 'set &DC_LIBREF..mpe_config(where=('; put 'var_scope="DC"'; put 'and &dc_dttmtfmt lt tx_to'; put 'and var_active=1'; put '));'; put 'call symputx(var_name,var_value,''G'');'; put 'putlog var_name "=" var_value;'; put 'run;'; put '%let mpelib=&dc_libref;'; put '%let mpeadmins=&dc_admin_group;'; put '%let mpelocapprovals=&dc_staging_area;'; put '%let dc_repo_users=&dc_repo_users;'; put '%if &dc_locale ne SYSTEM %then %do;'; put 'options locale=&dc_locale;'; put '%end;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(Problem during compilation or with STP precode (&syswarningtext))'; put ')'; put '%mend mpeinit;'; put '%macro mf_mval(var);'; put '%if %symexist(&var) %then %do;'; put '%superq(&var)'; put '%end;'; put '%mend mf_mval;'; put '%macro mf_trimstr(basestr,trimstr);'; put '%local baselen trimlen trimval;'; put '/* return if basestr is shorter than trimstr (or 0) */'; put '%let baselen=%length(%superq(basestr));'; put '%let trimlen=%length(%superq(trimstr));'; put '%if &baselen < &trimlen or &baselen=0 %then %return;'; put '/* obtain the characters from the end of basestr */'; put '%let trimval=%qsubstr(%superq(basestr)'; put ',%length(%superq(basestr))-&trimlen+1'; put ',&trimlen);'; put '/* compare and if matching, chop it off! */'; put '%if %superq(basestr)=%superq(trimstr) %then %do;'; put '%return;'; put '%end;'; put '%else %if %superq(trimval)=%superq(trimstr) %then %do;'; put '%qsubstr(%superq(basestr),1,%length(%superq(basestr))-&trimlen)'; put '%end;'; put '%else %do;'; put '&basestr'; put '%end;'; put '%mend mf_trimstr;'; put '%macro mf_getplatform(switch'; put ')/*/STORE SOURCE*/;'; put '%local a b c;'; put '%if &switch.NONE=NONE %then %do;'; put '%if %symexist(sasjsprocessmode) %then %do;'; put '%if &sasjsprocessmode=Stored Program %then %do;'; put 'SASJS'; put '%return;'; put '%end;'; put '%end;'; put '%if %symexist(sysprocessmode) %then %do;'; put '%if "&sysprocessmode"="SAS Object Server"'; put 'or "&sysprocessmode"= "SAS Compute Server" %then %do;'; put 'SASVIYA'; put '%end;'; put '%else %if "&sysprocessmode"="SAS Stored Process Server"'; put 'or "&sysprocessmode"="SAS Workspace Server"'; put '%then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if %symexist(_metaport) or %symexist(_metauser) %then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if &switch=SASSTUDIO %then %do;'; put '/* return the version of SAS Studio else 0 */'; put '%if %mf_mval(_CLIENTAPP)=%str(SAS Studio) %then %do;'; put '%let a=%mf_mval(_CLIENTVERSION);'; put '%let b=%scan(&a,1,.);'; put '%if %eval(&b >2) %then %do;'; put '&b'; put '%end;'; put '%else 0;'; put '%end;'; put '%else 0;'; put '%end;'; put '%else %if &switch=VIYARESTAPI %then %do;'; put '%mf_trimstr(%sysfunc(getoption(servicesbaseurl)),/)'; put '%end;'; put '%mend mf_getplatform;'; put '%macro mpeterm();'; put '%local oldloc;'; put 'data _null_;'; put 'if symexist(''SYSPRINTTOLOG'') then oldloc=symget(''SYSPRINTTOLOG'');'; put 'else oldloc=getoption(''LOG'');'; put 'if subpad(oldloc,1,1) not in (''"'',"''",'' '') then oldloc=quote(cats(oldloc));'; put 'call symputx(''oldloc'',oldloc,''l'');'; put 'run;'; put '%if %length(&oldloc)>0 %then %do;'; put 'proc printto log=log;'; put 'run;'; put 'data _null_;'; put 'infile &oldloc;'; put 'input; putlog _infile_;'; put 'run;'; put '%end;'; put '%if %sysfunc(exist(&dc_libref..mpe_requests)) and %mf_getplatform() ne SASVIYA'; put '%then %do;'; put 'data ;'; put 'if 0 then set &dc_libref..mpe_requests;'; put 'request_dttm=%sysfunc(datetime());'; put 'request_user="%mf_getuser()";'; put 'request_service="%scan(&_program,-2,/)/%scan(&_program,-1,/)";'; put 'request_params='''';'; put 'output;stop;'; put 'proc append base=&dc_libref..mpe_requests data=&syslast force nowarn;'; put 'run;'; put '%end;'; put '%mend mpeterm;'; put '* SAS Macros end;'; put '* SAS Includes start;'; put '* SAS Includes end;'; put '* Binary Files start;'; put '* Binary Files end;'; put '* ServiceInit start;'; put 'options noquotelenmax ps=max;'; put '/* create dummy macros to prevent stpbegin / stpend from corrupting sessions */'; put '%macro stpbegin();'; put '%put NOTE: the STPBEGIN macro should not be used for web apps!;'; put '%mend stpbegin;'; put '%macro stpend();'; put '%put NOTE: the STPEND macro should not be used for web apps!;'; put '%mend stpend;'; put '* ServiceInit end;'; put '* Service start;'; put '/**'; put '@file'; put '@brief Post Edit Hook script for the MPE_COLUMN_LEVEL_SECURITY table'; put '@details Post edit hooks provide additional backend validation for user'; put 'provided data. The incoming dataset is named `work.staging_ds` and is'; put 'provided in mpe_loader.sas.'; put 'Available macro variables:'; put '@li DC_LIBREF - The DC control library'; put '@li LIBREF - The library of the dataset being edited (is assigned)'; put '@li DS - The dataset being edited'; put 'This validation checks the incoming column_level_security settings to ensure'; put 'each individual filter is valid'; put '**/'; put '/* check scope values and ensure uppercasing */'; put '%let errflag=0;'; put '%let errmsg=;'; put 'data work.staging_ds;'; put 'set work.staging_ds;'; put 'cls_scope=upcase(cls_scope);'; put 'CLS_LIBREF=upcase(CLS_LIBREF);'; put 'cls_table=upcase(CLS_TABLE);'; put 'CLS_VARIABLE_NM=upcase(CLS_VARIABLE_NM);'; put 'if cls_scope not in (''ALL'',''VIEW'',''EDIT'') then do;'; put 'call symputx(''errflag'',1);'; put 'call symputx(''errmsg'',"Invalid scope: "!!cls_scope);'; put 'stop;'; put 'end;'; put 'if cls_hide<1 then cls_hide=0;'; put 'else cls_hide=1;'; put 'if CLS_ACTIVE<1 then CLS_ACTIVE=0;'; put 'else CLS_ACTIVE=1;'; put 'run;'; put '%mp_abort(iftrue=(&errflag=1)'; put ',mac=mpe_column_level_security_postedit'; put ',msg=%superq(errmsg)'; put ')'; put '* Service end;'; run; %mm_createwebservice(path=&appLoc/&path, name=&service, code=sascode, server=&serverName, replace=yes) filename sascode clear; %let service=mpe_row_level_security_postedit; filename sascode temp lrecl=32767; data _null_; file sascode; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '/**'; put '@file mp_jsonout.sas'; put '@brief Writes JSON in SASjs format to a fileref'; put '@details This macro can be used to OPEN a JSON stream and send one or more'; put 'tables as arrays of rows, where each row can be an object or a nested array.'; put 'There are two engines available - DATASTEP or PROCJSON.'; put 'PROC JSON is fast but will produce errs like the ones below if'; put 'special chars are encountered.'; put '> (ERR)OR: Some code points did not transcode.'; put '> An object or array close is not valid at this point in the JSON text.'; put '> Date value out of range'; put 'If this happens, try running with ENGINE=DATASTEP.'; put 'The DATASTEP engine is used to handle special SAS missing numerics, and'; put 'can also convert entire datasets to formatted values. Output JSON is always'; put 'in UTF-8.'; put 'Usage:'; put 'filename tmp temp;'; put 'data class; set sashelp.class;run;'; put '%mp_jsonout(OPEN,jref=tmp)'; put '%mp_jsonout(OBJ,class,jref=tmp)'; put '%mp_jsonout(OBJ,class,dslabel=class2,jref=tmp,showmeta=Y)'; put '%mp_jsonout(CLOSE,jref=tmp)'; put 'data _null_;'; put 'infile tmp;'; put 'input;putlog _infile_;'; put 'run;'; put 'If you are building web apps with SAS then you are strongly encouraged to use'; put 'the mX_createwebservice macros in combination with the'; put '[sasjs adapter](https://github.com/sasjs/adapter).'; put 'For more information see https://sasjs.io'; put '@param [in] action Valid values:'; put '@li OPEN - opens the JSON'; put '@li OBJ - sends a table with each row as an object'; put '@li ARR - sends a table with each row in an array'; put '@li CLOSE - closes the JSON'; put '@param [in] ds The dataset to send. Must be a work table.'; put '@param [out] jref= (_webout) The fileref to which to send the JSON'; put '@param [out] dslabel= The name to give the table in the exported JSON'; put '@param [in] fmt= (Y) Whether to keep (Y) or strip (N) formats from the table'; put '@param [in] engine= (DATASTEP) Which engine to use to send the JSON. Options:'; put '@li PROCJSON (default)'; put '@li DATASTEP (more reliable when data has non standard characters)'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to JSON'; put '

Related Files

'; put '@li mp_ds2fmtds.sas'; put '@version 9.2'; put '@author Allan Bowe'; put '@source https://github.com/sasjs/core'; put '**/'; put '%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y'; put ',engine=DATASTEP'; put ',missing=NULL'; put ',showmeta=N'; put ',maxobs=MAX'; put ')/*/STORE SOURCE*/;'; put '%local tempds colinfo fmtds i numcols numobs stmt_obs lastobs optval'; put 'tmpds1 tmpds2 tmpds3 tmpds4;'; put '%let numcols=0;'; put '%if &maxobs ne MAX %then %let stmt_obs=%str(if _n_>&maxobs then stop;);'; put '%if &action=OPEN %then %do;'; put 'options nobomfile;'; put 'data _null_;file &jref encoding=''utf-8'' lrecl=200;'; put 'put ''{"PROCESSED_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''"'';'; put 'run;'; put '%end;'; put '%else %if (&action=ARR or &action=OBJ) %then %do;'; put '/* force variable names to always be uppercase in the JSON */'; put 'options validvarname=upcase;'; put '/* To avoid issues with _webout on EBI - such as encoding diffs and truncation'; put '(https://support.sas.com/kb/49/325.html) we use temporary files */'; put 'filename _sjs1 temp lrecl=200 ;'; put 'data _null_; file _sjs1 encoding=''utf-8'';'; put 'put ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":";'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjs1 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs1 clear;'; put '/* grab col defs */'; put 'proc contents noprint data=&ds'; put 'out=_data_(keep=name type length format formatl formatd varnum label);'; put 'run;'; put '%let colinfo=%scan(&syslast,2,.);'; put 'proc sort data=&colinfo;'; put 'by varnum;'; put 'run;'; put '/* move meta to mac vars */'; put 'data &colinfo;'; put 'if _n_=1 then call symputx(''numcols'',nobs,''l'');'; put 'set &colinfo end=last nobs=nobs;'; put 'name=upcase(name);'; put '/* fix formats */'; put 'if type=2 or type=6 then do;'; put 'typelong=''char'';'; put 'length fmt $49.;'; put 'if format='''' then fmt=cats(''$'',length,''.'');'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else fmt=cats(format,formatl,''.'');'; put 'end;'; put 'else do;'; put 'typelong=''num'';'; put 'if format='''' then fmt=''best.'';'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else if formatd=0 then fmt=cats(format,formatl,''.'');'; put 'else fmt=cats(format,formatl,''.'',formatd);'; put 'end;'; put '/* 32 char unique name */'; put 'newname=''sasjs''!!substr(cats(put(md5(name),$hex32.)),1,27);'; put 'call symputx(cats(''name'',_n_),name,''l'');'; put 'call symputx(cats(''newname'',_n_),newname,''l'');'; put 'call symputx(cats(''length'',_n_),length,''l'');'; put 'call symputx(cats(''fmt'',_n_),fmt,''l'');'; put 'call symputx(cats(''type'',_n_),type,''l'');'; put 'call symputx(cats(''typelong'',_n_),typelong,''l'');'; put 'call symputx(cats(''label'',_n_),coalescec(label,name),''l'');'; put '/* overwritten when fmt=Y and a custom format exists in catalog */'; put 'if typelong=''num'' then call symputx(cats(''fmtlen'',_n_),200,''l'');'; put 'else call symputx(cats(''fmtlen'',_n_),min(32767,ceil((length+10)*1.5)),''l'');'; put 'run;'; put '%let tempds=%substr(_%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql;'; put 'select count(*) into: lastobs from &ds;'; put '%if &maxobs ne MAX %then %let lastobs=%sysfunc(min(&lastobs,&maxobs));'; put '%if &engine=PROCJSON %then %do;'; put '%if &missing=STRING %then %do;'; put '%put &sysmacroname: Special Missings not supported in proc json.;'; put '%put &sysmacroname: Switching to DATASTEP engine;'; put '%goto datastep;'; put '%end;'; put 'data &tempds;'; put 'set &ds;'; put '&stmt_obs;'; put '%if &fmt=N %then format _numeric_ best32.;;'; put '/* PRETTY is necessary to avoid line truncation in large files */'; put 'filename _sjs2 temp lrecl=131068 encoding=''utf-8'';'; put 'proc json out=_sjs2 pretty'; put '%if &action=ARR %then nokeys ;'; put ';export &tempds / nosastags fmtnumeric;'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs2 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs2 clear;'; put '%end;'; put '%else %if &engine=DATASTEP %then %do;'; put '%datastep:'; put '%if %sysfunc(exist(&ds)) ne 1 & %sysfunc(exist(&ds,VIEW)) ne 1'; put '%then %do;'; put '%put &sysmacroname: &ds NOT FOUND!!!;'; put '%return;'; put '%end;'; put '%if &fmt=Y %then %do;'; put '/**'; put '* Extract format definitions'; put '* First, by getting library locations from dictionary.formats'; put '* Then, by exporting the width using proc format'; put '* Cannot use maxw from sashelp.vformat as not always populated'; put '* Cannot use fmtinfo() as not supported in all flavours'; put '*/'; put '%let tmpds1=%substr(fmtsum%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds2=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds3=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds4=%substr(col%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql noprint;'; put 'create table &tmpds1 as'; put 'select cats(libname,''.'',memname) as FMTCAT,'; put 'FMTNAME'; put 'from dictionary.formats'; put 'where fmttype=''F'' and libname is not null'; put 'and fmtname in (select format from &colinfo where format is not null)'; put 'order by 1;'; put 'create table &tmpds2('; put 'FMTNAME char(32),'; put 'LENGTH num'; put ');'; put '%local catlist cat fmtlist i;'; put 'select distinct fmtcat into: catlist separated by '' '' from &tmpds1;'; put '%do i=1 %to %sysfunc(countw(&catlist,%str( )));'; put '%let cat=%scan(&catlist,&i,%str( ));'; put 'proc sql;'; put 'select distinct fmtname into: fmtlist separated by '' '''; put 'from &tmpds1 where fmtcat="&cat";'; put 'proc format lib=&cat cntlout=&tmpds3(keep=fmtname length);'; put 'select &fmtlist;'; put 'run;'; put 'proc sql;'; put 'insert into &tmpds2 select distinct fmtname,length from &tmpds3;'; put '%end;'; put 'proc sql;'; put 'create table &tmpds4 as'; put 'select a.*, b.length as MAXW'; put 'from &colinfo a'; put 'left join &tmpds2 b'; put 'on cats(a.format)=cats(upcase(b.fmtname))'; put 'order by a.varnum;'; put 'data _null_;'; put 'set &tmpds4;'; put 'if not missing(maxw);'; put 'call symputx('; put 'cats(''fmtlen'',_n_),'; put '/* vars need extra padding due to JSON escaping of special chars */'; put 'min(32767,ceil((max(length,maxw)+10)*1.5))'; put ',''l'''; put ');'; put 'run;'; put '/* configure varlenchk - as we are explicitly shortening the variables */'; put '%let optval=%sysfunc(getoption(varlenchk));'; put 'options varlenchk=NOWARN;'; put 'data _data_(compress=char);'; put '/* shorten the new vars */'; put 'length'; put '%do i=1 %to &numcols;'; put '&&name&i $&&fmtlen&i'; put '%end;'; put ';'; put '/* rename on entry */'; put 'set &ds(rename=('; put '%do i=1 %to &numcols;'; put '&&name&i=&&newname&i'; put '%end;'; put '));'; put '&stmt_obs;'; put 'drop'; put '%do i=1 %to &numcols;'; put '&&newname&i'; put '%end;'; put ';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=num %then %do;'; put '&&name&i=cats(put(&&newname&i,&&fmt&i));'; put '%end;'; put '%else %do;'; put '&&name&i=put(&&newname&i,&&fmt&i);'; put '%end;'; put '%end;'; put 'if _error_ then do;'; put 'call symputx(''syscc'',1012);'; put 'stop;'; put 'end;'; put 'run;'; put '%let fmtds=&syslast;'; put 'options varlenchk=&optval;'; put '%end;'; put 'proc format; /* credit yabwon for special null removal */'; put 'value bart (default=40)'; put '%if &missing=NULL %then %do;'; put '._ - .z = null'; put '%end;'; put '%else %do;'; put '._ = [quote()]'; put '. = null'; put '.a - .z = [quote()]'; put '%end;'; put 'other = [best.];'; put 'data &tempds;'; put 'attrib _all_ label='''';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'length &&name&i $&&fmtlen&i...;'; put 'format &&name&i $&&fmtlen&i...;'; put '%end;'; put '%end;'; put '%if &fmt=Y %then %do;'; put 'set &fmtds;'; put '%end;'; put '%else %do;'; put 'set &ds;'; put '%end;'; put '&stmt_obs;'; put 'format _numeric_ bart.;'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'if findc(&&name&i,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put '&&name&i=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,&&name&i)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else &&name&i=quote(cats(&&name&i));'; put '%end;'; put '%end;'; put 'run;'; put 'filename _sjs3 temp lrecl=131068 ;'; put 'data _null_;'; put 'file _sjs3 encoding=''utf-8'';'; put 'if _n_=1 then put "[";'; put 'set &tempds;'; put 'if _n_>1 then put "," @; put'; put '%if &action=ARR %then "[" ; %else "{" ;'; put '%do i=1 %to &numcols;'; put '%if &i>1 %then "," ;'; put '%if &action=OBJ %then """&&name&i"":" ;'; put '"&&name&i"n /* name literal for reserved variable names */'; put '%end;'; put '%if &action=ARR %then "]" ; %else "}" ; ;'; put '/* close out the table */'; put 'data _null_;'; put 'file _sjs3 mod encoding=''utf-8'';'; put 'put '']'';'; put 'run;'; put 'data _null_;'; put 'infile _sjs3 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs3 clear;'; put '%end;'; put 'proc sql;'; put 'drop table &colinfo, &tempds;'; put '%if %substr(&showmeta,1,1)=Y %then %do;'; put 'filename _sjs4 temp lrecl=131068 encoding=''utf-8'';'; put 'data _null_;'; put 'file _sjs4;'; put 'length label $350;'; put 'put ", ""$%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":{""vars"":{";'; put 'do i=1 to &numcols;'; put 'name=quote(trim(symget(cats(''name'',i))));'; put 'format=quote(trim(symget(cats(''fmt'',i))));'; put 'label=quote(prxchange(''s/\\/\\\\/'',-1,trim(symget(cats(''label'',i)))));'; put 'length=quote(trim(symget(cats(''length'',i))));'; put 'type=quote(trim(symget(cats(''typelong'',i))));'; put 'if i>1 then put "," @@;'; put 'put name '':{"format":'' format '',"label":'' label'; put ''',"length":'' length '',"type":'' type ''}'';'; put 'end;'; put 'put ''}}'';'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs4 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs4 clear;'; put '%end;'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put 'data _null_; file &jref encoding=''utf-8'' mod ;'; put 'put "}";'; put 'run;'; put '%end;'; put '%mend mp_jsonout;'; put '/**'; put '@file mm_webout.sas'; put '@brief Send data to/from SAS Stored Processes'; put '@details This macro should be added to the start of each Stored Process,'; put '**immediately** followed by a call to:'; put '%mm_webout(FETCH)'; put 'This will read all the input data and create same-named SAS datasets in the'; put 'WORK library. You can then insert your code, and send data back using the'; put 'following syntax:'; put 'data some datasets; * make some data ;'; put 'retain some columns;'; put 'run;'; put '%mm_webout(OPEN)'; put '%mm_webout(ARR,some) * Array format, fast, suitable for large tables ;'; put '%mm_webout(OBJ,datasets) * Object format, easier to work with ;'; put 'Finally, wrap everything up send some helpful system variables too'; put '%mm_webout(CLOSE)'; put '@param [in] action Either FETCH, OPEN, ARR, OBJ or CLOSE'; put '@param [in] ds The dataset to send back to the frontend'; put '@param [out] dslabel= Value to use instead of table name for sending to JSON'; put '@param [in] fmt= (N) Setting Y converts all vars to their formatted values'; put '@param [out] fref= (_webout) The fileref to which to write the JSON'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] workobs= (0) When set to a positive integer, will create a new'; put 'output object (WORK) which contains this number of observations from all'; put 'tables in the WORK library.'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to output JSON'; put '

SAS Macros

'; put '@li mp_jsonout.sas'; put '

Related Macros

'; put '@li ms_webout.sas'; put '@li mv_webout.sas'; put '@version 9.3'; put '@author Allan Bowe'; put '**/'; put '%macro mm_webout(action,ds,dslabel=,fref=_webout,fmt=N,missing=NULL'; put ',showmeta=N,maxobs=MAX,workobs=0'; put ');'; put '%global _webin_file_count _webin_fileref1 _webin_name1 _program _debug'; put 'sasjs_tables;'; put '%local i tempds jsonengine;'; put '/* see https://github.com/sasjs/core/issues/41 */'; put '%if "%upcase(&SYSENCODING)" ne "UTF-8" %then %let jsonengine=PROCJSON;'; put '%else %let jsonengine=DATASTEP;'; put '%if &action=FETCH %then %do;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'options mprint notes mprintnest;'; put '%end;'; put '%let _webin_file_count=%eval(&_webin_file_count+0);'; put '/* now read in the data */'; put '%do i=1 %to &_webin_file_count;'; put '%if &_webin_file_count=1 %then %do;'; put '%let _webin_fileref1=&_webin_fileref;'; put '%let _webin_name1=&_webin_name;'; put '%end;'; put 'data _null_;'; put 'infile &&_webin_fileref&i termstr=crlf;'; put 'input;'; put 'call symputx(''input_statement'',_infile_);'; put 'putlog "&&_webin_name&i input statement: " _infile_;'; put 'stop;'; put 'data &&_webin_name&i;'; put 'infile &&_webin_fileref&i firstobs=2 dsd termstr=crlf encoding=''utf-8'';'; put 'input &input_statement;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'if _n_<20 then putlog _infile_;'; put '%end;'; put 'run;'; put '%let sasjs_tables=&sasjs_tables &&_webin_name&i;'; put '%end;'; put '%end;'; put '%else %if &action=OPEN %then %do;'; put '/* fix encoding */'; put 'OPTIONS NOBOMFILE;'; put '/**'; put '* check xengine type to avoid the below err message:'; put '* > Function is only valid for filerefs using the CACHE access method.'; put '*/'; put 'data _null_;'; put 'set sashelp.vextfl(where=(fileref="_WEBOUT"));'; put 'if xengine=''STREAM'' then do;'; put 'rc=stpsrv_header(''Content-type'',"text/html; encoding=utf-8");'; put 'end;'; put 'run;'; put '/* setup json */'; put 'data _null_;file &fref encoding=''utf-8'';'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutBEGIN<<'';'; put '%end;'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'run;'; put '%end;'; put '%else %if &action=ARR or &action=OBJ %then %do;'; put '%mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt,jref=&fref'; put ',engine=&jsonengine,missing=&missing,showmeta=&showmeta,maxobs=&maxobs'; put ')'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put '/* To avoid issues with _webout on EBI we use a temporary file */'; put 'filename _sjsref temp lrecl=131068;'; put '%if %str(&workobs) > 0 %then %do;'; put '/* if debug mode, send back first XX records of each work table also */'; put 'data;run;%let tempds=%scan(&syslast,2,.);'; put 'ods output Members=&tempds;'; put 'proc datasets library=WORK memtype=data;'; put '%local wtcnt;%let wtcnt=0;'; put 'data _null_;'; put 'set &tempds;'; put 'if not (upcase(name) =:"DATA"); /* ignore temp datasets */'; put 'i+1;'; put 'call symputx(cats(''wt'',i),name,''l'');'; put 'call symputx(''wtcnt'',i,''l'');'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put ",""WORK"":{";'; put '%do i=1 %to &wtcnt;'; put '%let wt=&&wt&i;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'dsid=open("WORK.&wt",''is'');'; put 'nlobs=attrn(dsid,''NLOBS'');'; put 'nvars=attrn(dsid,''NVARS'');'; put 'rc=close(dsid);'; put 'if &i>1 then put '',''@;'; put 'put " ""&wt"" : {";'; put 'put ''"nlobs":'' nlobs;'; put 'put '',"nvars":'' nvars;'; put '%mp_jsonout(OBJ,&wt,jref=_sjsref,dslabel=first10rows,showmeta=Y'; put ',maxobs=&workobs'; put ')'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put '%end;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put 'run;'; put '%end;'; put '/* close off json */'; put 'data _null_;file _sjsref mod encoding=''utf-8'';'; put 'length SYSPROCESSNAME syserrortext syswarningtext autoexec $512;'; put 'put ",""_DEBUG"" : ""&_debug"" ";'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'autoexec=quote(urlencode(trim(getoption(''autoexec''))));'; put 'put '',"AUTOEXEC" : '' autoexec;'; put 'put ",""MF_GETUSER"" : ""%mf_getuser()"" ";'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'put ",""SYSENCODING"" : ""&sysencoding"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSPROCESSID"" : ""&SYSPROCESSID"" ";'; put 'put ",""SYSPROCESSMODE"" : ""&SYSPROCESSMODE"" ";'; put 'SYSPROCESSNAME=quote(urlencode(cats(SYSPROCESSNAME)));'; put 'put ",""SYSPROCESSNAME"" : " SYSPROCESSNAME;'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put '',"SYSWARNINGTEXT" : '' syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'length memsize $32;'; put 'memsize="%sysfunc(INPUTN(%sysfunc(getoption(memsize)), best.),sizekmg.)";'; put 'memsize=quote(cats(memsize));'; put 'put '',"MEMSIZE" : '' memsize;'; put 'put "}" @;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutEND<<'';'; put '%end;'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjsref lrecl=1 recfm=n;'; put 'file &fref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjsref clear;'; put '%end;'; put '%mend mm_webout;'; put '%macro webout(action,ds,dslabel=,fmt=,missing=NULL,showmeta=NO,maxobs=MAX);'; put '%mm_webout(&action,ds=&ds,dslabel=&dslabel,fmt=&fmt'; put ',missing=&missing'; put ',showmeta=&showmeta'; put ',maxobs=&maxobs'; put ') %mend;'; put '/* provide additional debug info */'; put '%global _program;'; put '%put &=syscc;'; put '%put user=%mf_getuser();'; put '%put pgm=&_program;'; put '%put timestamp=%sysfunc(datetime(),datetime19.);'; put '* Service Variables start;'; put '* Service Variables end;'; put '* SAS Macros start;'; put '%macro mf_getapploc(pgm);'; put '%if "&pgm"="" %then %do;'; put '%if %symexist(_program) %then %let pgm=&_program;'; put '%else %do;'; put '%put &sysmacroname: No value provided and no _program variable available;'; put '%return;'; put '%end;'; put '%end;'; put '%local root;'; put '/**'; put '* First check we are not in the tests/macros folder (which has no subfolders)'; put '* or specifically in the testsetup or testteardown services'; put '*/'; put '%if %index(&pgm,/tests/macros/)'; put 'or %index(&pgm,/tests/testsetup)'; put 'or %index(&pgm,/tests/testteardown)'; put '%then %do;'; put '%let root=%substr(&pgm,1,%index(&pgm,/tests)-1);'; put '&root'; put '%return;'; put '%end;'; put '/**'; put '* Next, move up two levels to avoid matches on subfolder or service name'; put '*/'; put '%let root=%substr(&pgm,1,%length(&pgm)-%length(%scan(&pgm,-1,/))-1);'; put '%let root=%substr(&root,1,%length(&root)-%length(%scan(&root,-1,/))-1);'; put '%if %index(&root,/tests/) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/tests/)-1);'; put '%end;'; put '%else %if %index(&root,/services) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/services)-1);'; put '%end;'; put '%else %if %index(&root,/jobs) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/jobs)-1);'; put '%end;'; put '%else %put &sysmacroname: Could not find an app location from &pgm;'; put '&root'; put '%mend mf_getapploc ;'; put '%macro mf_getuniquefileref(prefix=_,maxtries=1000,lrecl=32767);'; put '%local rc fname;'; put '%if &prefix=0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%end;'; put '%else %do;'; put '%local x len;'; put '%let len=%eval(8-%length(&prefix));'; put '%let x=0;'; put '%do x=0 %to &maxtries;'; put '%let fname=&prefix%substr(%sysfunc(ranuni(0)),3,&len);'; put '%if %sysfunc(fileref(&fname)) > 0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%return;'; put '%end;'; put '%end;'; put '%put unable to find available fileref after &maxtries attempts;'; put '%end;'; put '%mend mf_getuniquefileref;'; put '%macro mp_abort(mac=mp_abort.sas, type=, msg=, iftrue=%str(1=1)'; put ', errds=work.mp_abort_errds'; put ', mode=REGULAR'; put ')/*/STORE SOURCE*/;'; put '%global sysprocessmode sysprocessname sasjs_stpsrv_header_loc sasjsprocessmode;'; put '%local fref fid i;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%put NOTE: /// mp_abort macro executing //;'; put '%if %length(&mac)>0 %then %put NOTE- called by &mac;'; put '%put NOTE - &msg;'; put '%if %symexist(_SYSINCLUDEFILEDEVICE)'; put '/* abort cancel FILE does not restart outside the INCLUDE on Viya 3.5 */'; put 'and %superq(SYSPROCESSNAME) ne %str(Compute Server)'; put '%then %do;'; put '%if "*&_SYSINCLUDEFILEDEVICE*" ne "**" %then %do;'; put 'data &errds;'; put 'iftrue=''1=1'';'; put 'length mac $100 msg $5000;'; put 'mac=symget(''mac'');'; put 'msg=symget(''msg'');'; put 'run;'; put 'data _null_;'; put 'abort cancel FILE;'; put 'run;'; put '%return;'; put '%end;'; put '%end;'; put '/* Web App Context */'; put '%if %symexist(_PROGRAM)'; put 'or %superq(SYSPROCESSNAME) = %str(Compute Server)'; put 'or &mode=INCLUDE'; put '%then %do;'; put 'options obs=max replace mprint;'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5"'; put '%then %do;'; put 'options nosyntaxcheck;'; put '%end;'; put '%if &mode=INCLUDE %then %do;'; put '%if %sysfunc(exist(&errds))=1 %then %do;'; put 'data _null_;'; put 'set &errds;'; put 'call symputx(''iftrue'',iftrue,''l'');'; put 'call symputx(''mac'',mac,''l'');'; put 'call symputx(''msg'',msg,''l'');'; put 'putlog (_all_)(=);'; put 'run;'; put '%if (&iftrue)=0 %then %return;'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: No include errors found;'; put '%return;'; put '%end;'; put '%end;'; put '/* extract log errs / warns, if exist */'; put '%local logloc logline;'; put '%global logmsg; /* capture global messages */'; put '%if %symexist(SYSPRINTTOLOG) %then %let logloc=&SYSPRINTTOLOG;'; put '%else %let logloc=%qsysfunc(getoption(LOG));'; put 'proc printto log=log;run;'; put '%let logline=0;'; put '%if %length(&logloc)>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input; putlog _infile_;'; put 'i=1;'; put 'retain logonce 0;'; put 'if ('; put '_infile_=:"%str(WARN)ING" or _infile_=:"%str(ERR)OR"'; put ') and logonce=0 then'; put 'do;'; put 'call symputx(''logline'',_n_);'; put 'logonce+1;'; put 'end;'; put 'run;'; put '/* capture log including lines BEFORE the err */'; put '%if &logline>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input;'; put 'i=1;'; put 'stoploop=0;'; put 'if _n_ ge &logline-15 and stoploop=0 then do until (i>22);'; put 'call symputx(''logmsg'',catx(''\n'',symget(''logmsg''),_infile_));'; put 'input;'; put 'i+1;'; put 'stoploop=1;'; put 'end;'; put 'if stoploop=1 then stop;'; put 'run;'; put '%end;'; put '%end;'; put '%if %symexist(SYS_JES_JOB_URI) %then %do;'; put '/* setup webout for Viya */'; put 'options nobomfile;'; put '%if "X&SYS_JES_JOB_URI.X"="XX" %then %do;'; put 'filename _webout temp lrecl=999999 mod;'; put '%end;'; put '%else %do;'; put 'filename _webout filesrvc parenturi="&SYS_JES_JOB_URI"'; put 'name="_webout.json" lrecl=999999 mod;'; put '%end;'; put '%end;'; put '%else %if %sysfunc(filename(fref,&sasjs_stpsrv_header_loc))=0 %then %do;'; put 'options nobomfile;'; put '/* set up http header for SASjs Server */'; put '%let fid=%sysfunc(fopen(&fref,A));'; put '%if &fid=0 %then %do;'; put '%put %str(ERR)OR: %sysfunc(sysmsg());'; put '%return;'; put '%end;'; put '%let rc=%sysfunc(fput(&fid,%str(Content-Type: application/json)));'; put '%let rc=%sysfunc(fwrite(&fid));'; put '%let rc=%sysfunc(fclose(&fid));'; put '%let rc=%sysfunc(filename(&fref));'; put '%end;'; put '/* send response in SASjs JSON format */'; put 'data _null_;'; put 'file _webout mod lrecl=32000 encoding=''utf-8'';'; put 'length msg syswarningtext syserrortext $32767 mode $10 ;'; put 'sasdatetime=datetime();'; put 'msg=symget(''msg'');'; put '%if &logline>0 %then %do;'; put 'msg=cats(msg,''\n\nLog Extract:\n'',symget(''logmsg''));'; put '%end;'; put '/* escape the escapes */'; put 'msg=tranwrd(msg,''\'',''\\'');'; put '/* escape the quotes */'; put 'msg=tranwrd(msg,''"'',''\"'');'; put '/* ditch the CRLFs as chrome complains */'; put 'msg=compress(msg,,''kw'');'; put '/* quote without quoting the quotes (which are escaped instead) */'; put 'msg=cats(''"'',msg,''"'');'; put 'if symexist(''_debug'') then debug=quote(trim(symget(''_debug'')));'; put 'else debug=''""'';'; put 'if symget(''sasjsprocessmode'')=''Stored Program'' then mode=''SASJS'';'; put 'if mode ne ''SASJS'' then put ''>>weboutBEGIN<<'';'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'put '',"sasjsAbort" : [{'';'; put 'put '' "MSG":'' msg ;'; put 'put '' ,"MAC": "'' "&mac" ''"}]'';'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'put '',"_DEBUG":'' debug ;'; put 'if symexist(''_metauser'') then do;'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put 'end;'; put 'if symexist(''SYS_JES_JOB_URI'') then do;'; put 'SYS_JES_JOB_URI=quote(trim(symget(''SYS_JES_JOB_URI'')));'; put 'put '',"SYS_JES_JOB_URI": '' SYS_JES_JOB_URI;'; put 'end;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put ",""SYSWARNINGTEXT"" : " syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'put "}" ;'; put 'if mode ne ''SASJS'' then put ''>>weboutEND<<'';'; put 'run;'; put '%put _all_;'; put '%if "&sysprocessmode " = "SAS Stored Process Server " %then %do;'; put 'data _null_;'; put 'putlog ''stpsrvset program err and syscc'';'; put 'rc=stpsrvset(''program error'', 0);'; put 'call symputx("syscc",0,"g");'; put 'run;'; put '%if &sysscp=WIN'; put 'and 1=0 /* deprecating this logic until we figure out a consistent abort */'; put 'and "%substr(%str(&sysvlong ),1,8)"="9.04.01M"'; put 'and "%substr(%str(&sysvlong ),9,1)">"5" %then %do;'; put '/* skip approach (below) does not work in windows m6+ envs */'; put 'endsas;'; put '%end;'; put '%else %do;'; put '/**'; put '* endsas kills 9.4m3 deployments by orphaning multibridges.'; put '* Abort variants are ungraceful (non zero return code)'; put '* This approach lets SAS run silently until the end :-)'; put '* Caution - fails when called within a %include within a macro'; put '* Use mp_include() to handle this.'; put '*/'; put 'filename skip temp;'; put 'data _null_;'; put 'file skip;'; put 'put ''%macro skip();'';'; put 'comment ''%mend skip; -> fix lint '';'; put 'put ''%macro skippy();'';'; put 'comment ''%mend skippy; -> fix lint '';'; put 'run;'; put '%inc skip;'; put '%end;'; put '%end;'; put '%else %if "&sysprocessmode " = "SAS Compute Server " %then %do;'; put '/* endsas kills the session making it harder to fetch results */'; put 'data _null_;'; put 'syswarningtext=symget(''syswarningtext'');'; put 'syserrortext=symget(''syserrortext'');'; put 'abort_msg=symget(''msg'');'; put 'syscc=symget(''syscc'');'; put 'sysuserid=symget(''sysuserid'');'; put 'iftrue=symget(''iftrue'');'; put 'put (_all_)(/=);'; put 'call symputx(''syscc'',0);'; put 'abort cancel nolist;'; put 'run;'; put '%end;'; put '%else %do;'; put '%abort cancel;'; put '%end;'; put '%end;'; put '%else %do;'; put '%put _all_;'; put '%abort cancel;'; put '%end;'; put '%mend mp_abort;'; put '/** @endcond */'; put '%macro mm_getstpcode('; put 'tree=/User Folders/sasdemo/somestp'; put ',name='; put ',outloc=0'; put ',outref=0'; put ',mDebug=1'; put ',showlog=NO'; put ');'; put '%local mD;'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing &sysmacroname..sas;'; put '%&mD.put _local_;'; put '%if %length(&name)>0 %then %let name=/&name;'; put '/* first, check if STP exists */'; put '%local tsuri;'; put '%let tsuri=stopifempty ;'; put 'data _null_;'; put 'format type uri tsuri value $200.;'; put 'call missing (of _all_);'; put 'path="&tree&name(StoredProcess)";'; put '/* first, find the STP ID */'; put 'if metadata_pathobj("",path,"StoredProcess",type,uri)>0 then do;'; put '/* get sourcecode */'; put 'cnt=1;'; put 'do while (metadata_getnasn(uri,"Notes",cnt,tsuri)>0);'; put 'rc=metadata_getattr(tsuri,"Name",value);'; put '&mD.put tsuri= value=;'; put 'if value="SourceCode" then do;'; put '/* found it! */'; put 'rc=metadata_getattr(tsuri,"Id",value);'; put 'call symputx(''tsuri'',value,''l'');'; put 'stop;'; put 'end;'; put 'cnt+1;'; put 'end;'; put 'end;'; put 'else put (_all_)(=);'; put 'run;'; put '%mp_abort(iftrue= (&tsuri=stopifempty)'; put ',mac=mm_getstpcode'; put ',msg=%str(&tree&name.(StoredProcess) not found!)'; put ')'; put '/**'; put '* Now we can extract the textstore'; put '*/'; put 'filename __getdoc temp lrecl=10000000;'; put 'proc metadata'; put 'in="$METAREPOSITORY'; put ''; put 'SAS1"'; put 'out=__getdoc ;'; put 'run;'; put '/* find the beginning of the text */'; put '%local start;'; put 'data _null_;'; put 'infile __getdoc lrecl=10000;'; put 'input;'; put 'start=index(_infile_,''StoredText="'');'; put 'if start then do;'; put 'call symputx("start",start+11);'; put '*putlog ''"'' _infile_ ''"'';'; put 'end;'; put 'stop;'; put '%local outeng;'; put '%if "&outloc"="0" %then %let outeng=TEMP;'; put '%else %let outeng="&outloc";'; put '%local fref;'; put '%if &outref=0 %then %let fref=%mf_getuniquefileref();'; put '%else %let fref=&outref;'; put '/* read the content, byte by byte, resolving escaped chars */'; put 'filename &fref &outeng lrecl=100000;'; put 'data _null_;'; put 'length filein 8 fileid 8;'; put 'filein = fopen("__getdoc","I",1,"B");'; put 'fileid = fopen("&fref","O",1,"B");'; put 'rec = "20"x;'; put 'length entity $6;'; put 'do while(fread(filein)=0);'; put 'x+1;'; put 'if x>&start then do;'; put 'rc = fget(filein,rec,1);'; put 'if rec=''"'' then leave;'; put 'else if rec="&" then do;'; put 'entity=rec;'; put 'do until (rec=";");'; put 'if fread(filein) ne 0 then goto getout;'; put 'rc = fget(filein,rec,1);'; put 'entity=cats(entity,rec);'; put 'end;'; put 'select (entity);'; put 'when (''&'' ) rec=''&'' ;'; put 'when (''<'' ) rec=''<'' ;'; put 'when (''>'' ) rec=''>'' ;'; put 'when (''''') rec="''" ;'; put 'when (''"'') rec=''"'' ;'; put 'when ('' '') rec=''0A''x;'; put 'when ('' '') rec=''0D''x;'; put 'when (''$'' ) rec=''$'' ;'; put 'when ('' '') rec=''09''x;'; put 'otherwise putlog "%str(WARN)ING: missing value for " entity=;'; put 'end;'; put 'rc =fput(fileid, substr(rec,1,1));'; put 'rc =fwrite(fileid);'; put 'end;'; put 'else do;'; put 'rc =fput(fileid,rec);'; put 'rc =fwrite(fileid);'; put 'end;'; put 'end;'; put 'end;'; put 'getout:'; put 'rc=fclose(filein);'; put 'rc=fclose(fileid);'; put 'run;'; put '%if &showlog=YES %then %do;'; put 'data _null_;'; put 'infile &fref lrecl=32767 end=last;'; put 'input;'; put 'if _n_=1 then putlog ''>>stpcodeBEGIN<<'';'; put 'putlog _infile_;'; put 'if last then putlog ''>>stpcodeEND<<'';'; put 'run;'; put '%end;'; put 'filename __getdoc clear;'; put '%if &outref=0 %then %do;'; put 'filename &fref clear;'; put '%end;'; put '%mend mm_getstpcode;'; put '%macro dc_getsettings();'; put '%global _program;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(syscc=&syscc on entry (&syswarningtext &syserrortext))'; put ')'; put '%if %length(&_PROGRAM)>1 %then %let root=&_program;'; put '%else %do;'; put '%global _metauser;'; put '%let _metauser=&sysuserid;'; put '/* to mimic a "real" _program we need to give a dummy role and stp name */'; put '%let root=/dummyRole/dummyName;'; put '%end;'; put '/* the DC precode is stored in the Admin folder in the root of'; put 'the project. Lets find that root. */'; put '%put &=root;'; put '%let root=%mf_getapploc();'; put '%put &=root;'; put '/* Now we know the root location we can retrieve the params */'; put '%let temploc=%sysfunc(pathname(work))/settings.txt;'; put '%mm_getstpcode(tree=&root/services/public'; put ',name=Data_Controller_Settings'; put ',outloc=&temploc'; put ')'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Unable to run getstpcode)'; put ')'; put 'filename _getsets "&temploc" lrecl=2000;'; put '/*'; put 'Do not use mp_include here - this puts a copy in every service, which creates'; put 'compilation problems when calling services from mp_include'; put '*/'; put '%inc _getsets/source2;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Problem running &sysmacroname (&syswarningtext &syserrortext))'; put ')'; put '%mend dc_getsettings;'; put '%macro mf_fmtdttm('; put ')/*/STORE SOURCE*/;'; put '%if "&sysver"="9.2" or "&sysver"="9.3"'; put 'or ("&sysver"="9.4" and "%substr(&SYSVLONG,9,1)" le "3")'; put 'or "%substr(&sysver,1,1)"="4"'; put 'or "%substr(&sysver,1,1)"="5"'; put '%then %do;DATETIME19.3%end;'; put '%else %do;E8601DT26.6%end;'; put '%mend mf_fmtdttm;'; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '%macro mp_init(prefix=SASJS'; put ')/*/STORE SOURCE*/;'; put '%if %symexist(SASJS_PREFIX) %then %return; /* only run once */'; put '%global'; put 'SASJS_PREFIX /* the ONLY hard-coded global macro variable in SASjs */'; put '&prefix._FUNCTIONS /* used in mcf_init() to track core function compilation */'; put '&prefix._INIT_NUM /* initialisation time as numeric */'; put '&prefix._INIT_DTTM /* initialisation time in E8601DT26.6 format */'; put '&prefix.WORK /* avoid typing %sysfunc(pathname(work)) every time */'; put ';'; put '%let sasjs_prefix=&prefix;'; put 'data _null_;'; put 'dttm=datetime();'; put 'call symputx("&sasjs_prefix._init_num",dttm,''g'');'; put 'call symputx("&sasjs_prefix._init_dttm",put(dttm,E8601DT26.6),''g'');'; put 'call symputx("&sasjs_prefix.work",pathname(''WORK''),''g'');'; put 'run;'; put 'options'; put 'compress=CHAR /* default is none so ensure we have something! */'; put 'datastmtchk=ALLKEYWORDS /* protection from overwriting input datasets */'; put 'errorcheck=STRICT /* catch errs in libname/filename statements */'; put 'fmterr /* ensure err when a format cannot be found */'; put 'mergenoby=%str(ERR)OR /* throw err when a merge has no BY variables */'; put 'missing=. /* changing this can cause hard to detect errs */'; put 'noquotelenmax /* avoid warnings for long strings */'; put 'noreplace /* avoid overwriting permanent datasets */'; put 'ps=max /* reduce log size slightly */'; put 'ls=max /* reduce log even more and avoid word truncation */'; put 'validmemname=COMPATIBLE /* avoid special characters etc in table names */'; put 'validvarname=V7 /* avoid special characters etc in variable names */'; put 'varinitchk=%str(ERR)OR /* avoid data mistakes from variable name typos */'; put 'varlenchk=%str(ERR)OR /* fail hard if truncation (data loss) can result */'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5" %then %do;'; put 'noautocorrect /* disallow misspelled procedure names */'; put 'dsoptions=note2err /* undocumented - convert bad NOTEs to ERRs */'; put '%end;'; put ';'; put '%mend mp_init;'; put '%macro mpeinit(fetch=YES);'; put '%global mpeinit'; put 'mpeadmins /* group with unrestricted Meditor access */'; put 'mpelocapprovals /* location for landing and staging files */'; put 'mpelib /* location of configuration tables for DC */'; put 'dc_repo_users /* location of user / group metadata */'; put 'dc_licence_key /* extracted in dc_getsettings */'; put 'dc_activation_key /* extracted in dc_getsettings */'; put 'dc_locale /* extracted in dc_getsettings */'; put 'dc_dttmtfmt /* can be overridden in dc_getsettings */'; put '_debug'; put ';'; put '%if &mpeinit=1 %then %return;'; put '%else %let mpeinit=1;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(Problem on service startup (&syswarningtext &syserrortext))'; put ')'; put '%mp_init()'; put '%if &fetch=YES %then %do;'; put '%webout(FETCH)'; put '%end;'; put '%global _CLIENTNAME;'; put '%mp_abort(iftrue= (&_CLIENTNAME=SAS Enterprise Guide)'; put ',mac=&_program..sas'; put ',msg=%str(Data Controller is a web app and should not be executed from EG)'; put ')'; put 'options urlencoding=utf8 nobomfile lrecl=32767;'; put '%let perf=%sysfunc(datetime());'; put '%put perfdiff: 0;'; put '%let dc_locale=SYSTEM; /* default if not set */'; put '/**'; put '* E8601DT26.6 has widest database support - but not all SAS flavours can'; put '* handle it. Override in the settings STP if needed.'; put '*/'; put 'data _null_;'; put 'dc_dttmtfmt=''"%sysfunc(datetime(),''!!"%mf_fmtdttm()"!!'')"dt'';'; put 'call symputx(''dc_dttmtfmt'',dc_dttmtfmt);'; put 'put dc_dttmtfmt=;'; put 'run;'; put '%put &=dc_dttmtfmt;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc prior to dc_getsettings)'; put ')'; put '%dc_getsettings()'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc after dc_getsettings)'; put ')'; put 'data _null_;'; put 'set &DC_LIBREF..mpe_config(where=('; put 'var_scope="DC"'; put 'and &dc_dttmtfmt lt tx_to'; put 'and var_active=1'; put '));'; put 'call symputx(var_name,var_value,''G'');'; put 'putlog var_name "=" var_value;'; put 'run;'; put '%let mpelib=&dc_libref;'; put '%let mpeadmins=&dc_admin_group;'; put '%let mpelocapprovals=&dc_staging_area;'; put '%let dc_repo_users=&dc_repo_users;'; put '%if &dc_locale ne SYSTEM %then %do;'; put 'options locale=&dc_locale;'; put '%end;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(Problem during compilation or with STP precode (&syswarningtext))'; put ')'; put '%mend mpeinit;'; put '%macro mf_mval(var);'; put '%if %symexist(&var) %then %do;'; put '%superq(&var)'; put '%end;'; put '%mend mf_mval;'; put '%macro mf_trimstr(basestr,trimstr);'; put '%local baselen trimlen trimval;'; put '/* return if basestr is shorter than trimstr (or 0) */'; put '%let baselen=%length(%superq(basestr));'; put '%let trimlen=%length(%superq(trimstr));'; put '%if &baselen < &trimlen or &baselen=0 %then %return;'; put '/* obtain the characters from the end of basestr */'; put '%let trimval=%qsubstr(%superq(basestr)'; put ',%length(%superq(basestr))-&trimlen+1'; put ',&trimlen);'; put '/* compare and if matching, chop it off! */'; put '%if %superq(basestr)=%superq(trimstr) %then %do;'; put '%return;'; put '%end;'; put '%else %if %superq(trimval)=%superq(trimstr) %then %do;'; put '%qsubstr(%superq(basestr),1,%length(%superq(basestr))-&trimlen)'; put '%end;'; put '%else %do;'; put '&basestr'; put '%end;'; put '%mend mf_trimstr;'; put '%macro mf_getplatform(switch'; put ')/*/STORE SOURCE*/;'; put '%local a b c;'; put '%if &switch.NONE=NONE %then %do;'; put '%if %symexist(sasjsprocessmode) %then %do;'; put '%if &sasjsprocessmode=Stored Program %then %do;'; put 'SASJS'; put '%return;'; put '%end;'; put '%end;'; put '%if %symexist(sysprocessmode) %then %do;'; put '%if "&sysprocessmode"="SAS Object Server"'; put 'or "&sysprocessmode"= "SAS Compute Server" %then %do;'; put 'SASVIYA'; put '%end;'; put '%else %if "&sysprocessmode"="SAS Stored Process Server"'; put 'or "&sysprocessmode"="SAS Workspace Server"'; put '%then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if %symexist(_metaport) or %symexist(_metauser) %then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if &switch=SASSTUDIO %then %do;'; put '/* return the version of SAS Studio else 0 */'; put '%if %mf_mval(_CLIENTAPP)=%str(SAS Studio) %then %do;'; put '%let a=%mf_mval(_CLIENTVERSION);'; put '%let b=%scan(&a,1,.);'; put '%if %eval(&b >2) %then %do;'; put '&b'; put '%end;'; put '%else 0;'; put '%end;'; put '%else 0;'; put '%end;'; put '%else %if &switch=VIYARESTAPI %then %do;'; put '%mf_trimstr(%sysfunc(getoption(servicesbaseurl)),/)'; put '%end;'; put '%mend mf_getplatform;'; put '%macro mpeterm();'; put '%local oldloc;'; put 'data _null_;'; put 'if symexist(''SYSPRINTTOLOG'') then oldloc=symget(''SYSPRINTTOLOG'');'; put 'else oldloc=getoption(''LOG'');'; put 'if subpad(oldloc,1,1) not in (''"'',"''",'' '') then oldloc=quote(cats(oldloc));'; put 'call symputx(''oldloc'',oldloc,''l'');'; put 'run;'; put '%if %length(&oldloc)>0 %then %do;'; put 'proc printto log=log;'; put 'run;'; put 'data _null_;'; put 'infile &oldloc;'; put 'input; putlog _infile_;'; put 'run;'; put '%end;'; put '%if %sysfunc(exist(&dc_libref..mpe_requests)) and %mf_getplatform() ne SASVIYA'; put '%then %do;'; put 'data ;'; put 'if 0 then set &dc_libref..mpe_requests;'; put 'request_dttm=%sysfunc(datetime());'; put 'request_user="%mf_getuser()";'; put 'request_service="%scan(&_program,-2,/)/%scan(&_program,-1,/)";'; put 'request_params='''';'; put 'output;stop;'; put 'proc append base=&dc_libref..mpe_requests data=&syslast force nowarn;'; put 'run;'; put '%end;'; put '%mend mpeterm;'; put '%macro mm_assignlib('; put 'libref'; put ',mAbort=HARD'; put ')/*/STORE SOURCE*/;'; put '%local mp_abort msg;'; put '%let mp_abort=0;'; put '%if %sysfunc(libref(&libref)) %then %do;'; put 'data _null_;'; put 'length liburi LibName msg $200;'; put 'call missing(of _all_);'; put 'nobj=metadata_getnobj("omsobj:SASLibrary?@Libref=''&libref''",1,liburi);'; put 'if nobj=1 then do;'; put 'rc=metadata_getattr(liburi,"Name",LibName);'; put '/* now try and assign it */'; put 'if libname("&libref",,''meta'',cats(''liburi="'',liburi,''";'')) ne 0 then do;'; put 'putlog "&libref could not be assigned";'; put 'putlog liburi=;'; put '/**'; put '* Fetch the system message for display in the abort modal. This is'; put '* not always helpful though. One example, previously received:'; put '* NOTE: Libref XX refers to the same library metadata as libref XX.'; put '*/'; put 'msg=sysmsg();'; put 'if msg=:''ERROR: Libref SAVE is not assigned.'' then do;'; put 'msg=catx(" ",'; put '"Could not assign %upcase(&libref).",'; put '"Please check metadata permissions! Libname:",libname,'; put '"Liburi:",liburi'; put ');'; put 'end;'; put 'else if msg="ERROR: User does not have appropriate authorization "!!'; put '"level for library SAVE."'; put 'then do;'; put 'msg=catx(" ",'; put '"ERROR: User does not have appropriate authorization level",'; put '"for library %upcase(&libref), libname:",libname,'; put '"Liburi:",liburi'; put ');'; put 'end;'; put 'call symputx(''msg'',msg,''l'');'; put 'if "&mabort"=''HARD'' then call symputx(''mp_abort'',1,''l'');'; put 'end;'; put 'else do;'; put 'put (_all_)(=);'; put 'call symputx(''libname'',libname,''L'');'; put 'call symputx(''liburi'',liburi,''L'');'; put 'end;'; put 'end;'; put 'else if nobj>1 then do;'; put 'if "&mabort"=''HARD'' then call symputx(''mp_abort'',1);'; put 'call symputx(''msg'',"More than one library with libref=&libref");'; put 'end;'; put 'else do;'; put 'if "&mabort"=''HARD'' then call symputx(''mp_abort'',1);'; put 'call symputx(''msg'',"Library &libref not found in metadata");'; put 'end;'; put 'run;'; put '%put NOTE: &msg;'; put '%end;'; put '%else %do;'; put '%put NOTE: Library &libref is already assigned;'; put '%end;'; put '%mp_abort(iftrue= (&mp_abort=1)'; put ',mac=mm_assignlib.sas'; put ',msg=%superq(msg)'; put ')'; put '%mend mm_assignlib;'; put '/** @cond */'; put '%macro mf_getengine(libref'; put ')/*/STORE SOURCE*/;'; put '%local dsid engnum rc engine;'; put '/* in case the parameter is a libref.tablename, pull off just the libref */'; put '%let libref = %upcase(%scan(&libref, 1, %str(.)));'; put '%let dsid=%sysfunc('; put 'open(sashelp.vlibnam(where=(libname="%upcase(&libref)")),i)'; put ');'; put '%if (&dsid ^= 0) %then %do;'; put '%let engnum=%sysfunc(varnum(&dsid,ENGINE));'; put '%let rc=%sysfunc(fetch(&dsid));'; put '%let engine=%sysfunc(getvarc(&dsid,&engnum));'; put '%put &libref. ENGINE is &engine.;'; put '%let rc= %sysfunc(close(&dsid));'; put '%end;'; put '%upcase(&engine)'; put '%mend mf_getengine;'; put '/** @endcond */'; put '%macro mm_assigndirectlib('; put 'libref'; put ',open_passthrough='; put ',sql_options='; put ',mDebug=0'; put ',mAbort=0'; put ')/*/STORE SOURCE*/;'; put '%local mD;'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing mm_assigndirectlib.sas;'; put '%&mD.put _local_;'; put '%if &mAbort=1 %then %let mAbort=;'; put '%else %let mAbort=%str(*);'; put '%&mD.put NOTE: Creating direct (non META) connection to &libref library;'; put '%local cur_engine;'; put '%let cur_engine=%mf_getengine(&libref);'; put '%if &cur_engine ne META and &cur_engine ne %then %do;'; put '%put NOTE: &libref already has a direct (&cur_engine) libname connection;'; put '%return;'; put '%end;'; put '%else %if %upcase(&libref)=WORK %then %do;'; put '%put NOTE: We already have a direct connection to WORK :-) ;'; put '%return;'; put '%end;'; put '/* need to determine the library ENGINE first */'; put '%local engine;'; put 'data _null_;'; put 'length lib_uri engine $256;'; put 'call missing (of _all_);'; put '/* get URI for the particular library */'; put 'rc1=metadata_getnobj("omsobj:SASLibrary?@Libref =''&libref''",1,lib_uri);'; put '/* get the Engine attribute of the previous object */'; put 'rc2=metadata_getattr(lib_uri,''Engine'',engine);'; put 'putlog "mm_assigndirectlib for &libref:" rc1= lib_uri= rc2= engine=;'; put 'call symputx("liburi",lib_uri,''l'');'; put 'call symputx("engine",engine,''l'');'; put 'run;'; put '/* now obtain engine specific connection details */'; put '%if &engine=BASE %then %do;'; put '%&mD.put NOTE: Retrieving BASE library path;'; put 'data _null_;'; put 'length up_uri $256 path cat_path $1024;'; put 'retain cat_path;'; put 'call missing (of _all_);'; put '/* get all the filepaths of the UsingPackages association */'; put 'i=1;'; put 'rc3=metadata_getnasn("&liburi",''UsingPackages'',i,up_uri);'; put 'do while (rc3>0);'; put '/* get the DirectoryName attribute of the previous object */'; put 'rc4=metadata_getattr(up_uri,''DirectoryName'',path);'; put 'if i=1 then path = ''("''!!trim(path)!!''" '';'; put 'else path ='' "''!!trim(path)!!''" '';'; put 'cat_path = trim(cat_path) !! " " !! trim(path) ;'; put 'i+1;'; put 'rc3=metadata_getnasn("&liburi",''UsingPackages'',i,up_uri);'; put 'end;'; put 'cat_path = trim(cat_path) !! ")";'; put '&mD.putlog "NOTE: Getting physical path for &libref library";'; put '&mD.putlog rc3= up_uri= rc4= cat_path= path=;'; put '&mD.putlog "NOTE: Libname cmd will be:";'; put '&mD.putlog "libname &libref" cat_path;'; put 'call symputx("filepath",cat_path,''l'');'; put 'run;'; put '%if %sysevalf(&sysver<9.4) %then %do;'; put 'libname &libref &filepath;'; put '%end;'; put '%else %do;'; put '/* apply the new filelocks option to cater for temporary locks */'; put 'libname &libref &filepath filelockwait=5;'; put '%end;'; put '%end;'; put '%else %if &engine=REMOTE %then %do;'; put 'data x;'; put 'length rcCon rcProp rc k 3 uriCon uriProp PropertyValue PropertyName'; put 'Delimiter $256 properties $2048;'; put 'retain properties;'; put 'rcCon = metadata_getnasn("&liburi", "LibraryConnection", 1, uriCon);'; put 'rcProp = metadata_getnasn(uriCon, "Properties", 1, uriProp);'; put 'k = 1;'; put 'rcProp = metadata_getnasn(uriCon, "Properties", k, uriProp);'; put 'do while (rcProp > 0);'; put 'rc = metadata_getattr(uriProp , "DefaultValue",PropertyValue);'; put 'rc = metadata_getattr(uriProp , "PropertyName",PropertyName);'; put 'rc = metadata_getattr(uriProp , "Delimiter",Delimiter);'; put 'properties = trim(properties) !! " " !! trim(PropertyName)'; put '!! trim(Delimiter) !! trim(PropertyValue);'; put 'output;'; put 'k+1;'; put 'rcProp = metadata_getnasn(uriCon, "Properties", k, uriProp);'; put 'end;'; put '%&mD.put NOTE: Getting properties for REMOTE SHARE &libref library;'; put '&mD.put _all_;'; put '%&mD.put NOTE: Libname cmd will be:;'; put '%&mD.put libname &libref &engine &properties slibref=&libref;'; put 'call symputx ("properties",trim(properties),''l'');'; put 'run;'; put 'libname &libref &engine &properties slibref=&libref;'; put '%end;'; put '%else %if &engine=OLEDB %then %do;'; put '%&mD.put NOTE: Retrieving OLEDB connection details;'; put 'data _null_;'; put 'length domain datasource provider properties schema'; put 'connx_uri domain_uri conprop_uri lib_uri schema_uri value $256.;'; put 'call missing (of _all_);'; put '/* get source connection ID */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,connx_uri);'; put '/* get connection domain */'; put 'rc1=metadata_getnasn(connx_uri,''Domain'',1,domain_uri);'; put 'rc2=metadata_getattr(domain_uri,''Name'',domain);'; put '&mD.putlog / ''NOTE: '' // ''NOTE- connection id: '' connx_uri ;'; put '&mD.putlog ''NOTE- domain: '' domain;'; put '/* get DSN and PROVIDER from connection properties */'; put 'i=0;'; put 'do until (rc<0);'; put 'i+1;'; put 'rc=metadata_getnasn(connx_uri,''Properties'',i,conprop_uri);'; put 'rc2=metadata_getattr(conprop_uri,''Name'',value);'; put 'if value=''Connection.OLE.Property.DATASOURCE.Name.xmlKey.txt'' then do;'; put 'rc3=metadata_getattr(conprop_uri,''DefaultValue'',datasource);'; put 'end;'; put 'else if value=''Connection.OLE.Property.PROVIDER.Name.xmlKey.txt'' then do;'; put 'rc4=metadata_getattr(conprop_uri,''DefaultValue'',provider);'; put 'end;'; put 'else if value=''Connection.OLE.Property.PROPERTIES.Name.xmlKey.txt'' then'; put 'do;'; put 'rc5=metadata_getattr(conprop_uri,''DefaultValue'',properties);'; put 'end;'; put 'end;'; put '&mD.putlog ''NOTE- dsn/provider/properties: '' /'; put 'datasource provider properties;'; put '&mD.putlog ''NOTE- schema: '' schema // ''NOTE-'';'; put '/* get SCHEMA */'; put 'rc6=metadata_getnasn("&liburi",''UsingPackages'',1,lib_uri);'; put 'rc7=metadata_getattr(lib_uri,''SchemaName'',schema);'; put 'call symputx(''SQL_domain'',domain,''l'');'; put 'call symputx(''SQL_dsn'',datasource,''l'');'; put 'call symputx(''SQL_provider'',provider,''l'');'; put 'call symputx(''SQL_properties'',properties,''l'');'; put 'call symputx(''SQL_schema'',schema,''l'');'; put 'run;'; put '%if %length(&open_passthrough)>0 %then %do;'; put 'proc sql &sql_options;'; put 'connect to OLEDB as &open_passthrough(INSERT_SQL=YES'; put '/* need additional properties to make this work */'; put 'properties=(''Integrated Security''=SSPI'; put '''Persist Security Info''=True'; put '%sysfunc(compress(%str(&SQL_properties),%str(())))'; put ')'; put 'DATASOURCE=&sql_dsn PROMPT=NO'; put 'PROVIDER=&sql_provider SCHEMA=&sql_schema CONNECTION = GLOBAL);'; put '%end;'; put '%else %do;'; put 'LIBNAME &libref OLEDB PROPERTIES=&sql_properties'; put 'DATASOURCE=&sql_dsn PROVIDER=&sql_provider SCHEMA=&sql_schema'; put '%if %length(&sql_domain)>0 %then %do;'; put 'authdomain="&sql_domain"'; put '%end;'; put 'connection=shared;'; put '%end;'; put '%end;'; put '%else %if &engine=ODBC %then %do;'; put '%&mD.put NOTE: Retrieving ODBC connection details;'; put 'data _null_;'; put 'length connx_uri conprop_uri value datasource up_uri schema domprop_uri authdomain $256.;'; put 'call missing (of _all_);'; put '/* get source connection ID */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,connx_uri);'; put '/* get connection properties */'; put 'i=0;'; put 'do until (rc2<0);'; put 'i+1;'; put 'rc2=metadata_getnasn(connx_uri,''Properties'',i,conprop_uri);'; put 'rc3=metadata_getattr(conprop_uri,''Name'',value);'; put 'if value=''Connection.ODBC.Property.DATASRC.Name.xmlKey.txt'' then do;'; put 'rc4=metadata_getattr(conprop_uri,''DefaultValue'',datasource);'; put 'rc2=-1;'; put 'end;'; put 'end;'; put '/* get auth domain */'; put 'autrc=metadata_getnasn(connx_uri,"Domain",1,domprop_uri);'; put 'arc=metadata_getattr(domprop_uri,"Name",authdomain);'; put 'if not missing(authdomain) then authdomain=cats(''AUTHDOMAIN='',authdomain);'; put 'call symputx(''authdomain'',authdomain,''l'');'; put '/* get SCHEMA */'; put 'rc6=metadata_getnasn("&liburi",''UsingPackages'',1,up_uri);'; put 'rc7=metadata_getattr(up_uri,''SchemaName'',schema);'; put '&mD.put rc= connx_uri= rc2= conprop_uri= rc3= value= rc4= datasource='; put 'rc6= up_uri= rc7= schema=;'; put 'call symputx(''SQL_schema'',schema,''l'');'; put 'call symputx(''SQL_dsn'',datasource,''l'');'; put 'run;'; put '%if %length(&open_passthrough)>0 %then %do;'; put 'proc sql &sql_options;'; put 'connect to ODBC as &open_passthrough'; put '(INSERT_SQL=YES DATASRC=&sql_dsn. CONNECTION=global);'; put '%end;'; put '%else %do;'; put 'libname &libref ODBC DATASRC=&sql_dsn SCHEMA=&sql_schema &authdomain;'; put '%end;'; put '%end;'; put '%else %if &engine=POSTGRES %then %do;'; put '%put NOTE: Obtaining POSTGRES library details;'; put 'data _null_;'; put 'length database ignore_read_only_columns direct_exe preserve_col_names'; put 'preserve_tab_names server schema authdomain user password'; put 'prop name value uri urisrc $256.;'; put 'call missing (of _all_);'; put '/* get database value */'; put 'prop=''Connection.DBMS.Property.DB.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,database,"");'; put 'if database^='''' then database=''database=''!!quote(trim(database));'; put 'call symputx(''database'',database,''l'');'; put '/* get IGNORE_READ_ONLY_COLUMNS value */'; put 'prop=''Library.DBMS.Property.DBIROC.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,ignore_read_only_columns,"");'; put 'if ignore_read_only_columns^='''' then ignore_read_only_columns='; put '''ignore_read_only_columns=''!!ignore_read_only_columns;'; put 'call symputx(''ignore_read_only_columns'',ignore_read_only_columns,''l'');'; put '/* get DIRECT_EXE value */'; put 'prop=''Library.DBMS.Property.DirectExe.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,direct_exe,"");'; put 'if direct_exe^='''' then direct_exe=''direct_exe=''!!direct_exe;'; put 'call symputx(''direct_exe'',direct_exe,''l'');'; put '/* get PRESERVE_COL_NAMES value */'; put 'prop=''Library.DBMS.Property.PreserveColNames.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,preserve_col_names,"");'; put 'if preserve_col_names^='''' then preserve_col_names='; put '''preserve_col_names=''!!preserve_col_names;'; put 'call symputx(''preserve_col_names'',preserve_col_names,''l'');'; put '/* get PRESERVE_TAB_NAMES value */'; put '/* be careful with PRESERVE_TAB_NAMES=YES - it will mean your table will'; put 'become case sensitive!! */'; put 'prop=''Library.DBMS.Property.PreserveTabNames.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,preserve_tab_names,"");'; put 'if preserve_tab_names^='''' then preserve_tab_names='; put '''preserve_tab_names=''!!preserve_tab_names;'; put 'call symputx(''preserve_tab_names'',preserve_tab_names,''l'');'; put '/* get SERVER value */'; put 'if metadata_getnasn("&liburi","LibraryConnection",1,uri)>0 then do;'; put 'prop=''Connection.DBMS.Property.SERVER.Name.xmlKey.txt'';'; put 'rc=metadata_getprop(uri,prop,server,"");'; put 'end;'; put 'if server^='''' then server=''server=''!!quote(cats(server));'; put 'call symputx(''server'',server,''l'');'; put '/* get SCHEMA value */'; put 'if metadata_getnasn("&liburi","UsingPackages",1,uri)>0 then do;'; put 'rc=metadata_getattr(uri,"SchemaName",schema);'; put 'end;'; put 'if schema^='''' then schema=''schema=''!!schema;'; put 'call symputx(''schema'',schema,''l'');'; put '/* get AUTHDOMAIN value */'; put '/* this is only useful if the user account contains that auth domain'; put 'if metadata_getnasn("&liburi","DefaultLogin",1,uri)>0 then do;'; put 'rc=metadata_getnasn(uri,"Domain",1,urisrc);'; put 'rc=metadata_getattr(urisrc,"Name",authdomain);'; put 'end;'; put 'if authdomain^='''' then authdomain=''authdomain=''!!quote(trim(authdomain));'; put '*/'; put 'call symputx(''authdomain'',authdomain,''l'');'; put '/* get user & pass */'; put 'if authdomain='''' & metadata_getnasn("&liburi","DefaultLogin",1,uri)>0 then'; put 'do;'; put 'rc=metadata_getattr(uri,"UserID",user);'; put 'rc=metadata_getattr(uri,"Password",password);'; put 'end;'; put 'if user^='''' then do;'; put 'user=''user=''!!quote(trim(user));'; put 'password=''password=''!!quote(trim(password));'; put 'end;'; put 'call symputx(''user'',user,''l'');'; put 'call symputx(''password'',password,''l'');'; put '&md.put _all_;'; put 'run;'; put '%if %length(&open_passthrough)>0 %then %do;'; put '%put %str(WARN)ING: Passthrough option for postgres not yet supported;'; put '%return;'; put '%end;'; put '%else %do;'; put '%if &mdebug=1 %then %do;'; put '%put NOTE: Executing the following:/;'; put '%put NOTE- libname &libref POSTGRES &database &ignore_read_only_columns;'; put '%put NOTE- &direct_exe &preserve_col_names &preserve_tab_names;'; put '%put NOTE- &server &schema &authdomain &user &password //;'; put '%end;'; put 'libname &libref POSTGRES &database &ignore_read_only_columns &direct_exe'; put '&preserve_col_names &preserve_tab_names &server &schema &authdomain'; put '&user &password;'; put '%end;'; put '%end;'; put '%else %if &engine=ORACLE %then %do;'; put '%put NOTE: Obtaining &engine library details;'; put 'data _null_;'; put 'length assocuri1 assocuri2 assocuri3 authdomain path schema $256;'; put 'call missing (of _all_);'; put '/* get auth domain */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,assocuri1);'; put 'rc=metadata_getnasn(assocuri1,''Domain'',1,assocuri2);'; put 'rc=metadata_getattr(assocuri2,"Name",authdomain);'; put 'call symputx(''authdomain'',authdomain,''l'');'; put '/* path */'; put 'rc=metadata_getprop(assocuri1,'; put '''Connection.Oracle.Property.PATH.Name.xmlKey.txt'',path);'; put 'call symputx(''path'',path,''l'');'; put '/* schema */'; put 'rc=metadata_getnasn("&liburi",''UsingPackages'',1,assocuri3);'; put 'rc=metadata_getattr(assocuri3,''SchemaName'',schema);'; put 'call symputx(''schema'',schema,''l'');'; put 'run;'; put '%put NOTE: Executing the following:/; %put NOTE-;'; put '%put NOTE- libname &libref ORACLE path=&path schema=&schema;'; put '%put NOTE- authdomain=&authdomain;'; put '%put NOTE-;'; put 'libname &libref ORACLE path=&path schema=&schema authdomain=&authdomain;'; put '%end;'; put '%else %if &engine=SQLSVR %then %do;'; put '%put NOTE: Obtaining &engine library details;'; put 'data _null;'; put 'length assocuri1 assocuri2 assocuri3 authdomain path schema userid'; put 'passwd $256;'; put 'call missing (of _all_);'; put 'rc=metadata_getnasn("&liburi",''DefaultLogin'',1,assocuri1);'; put 'rc=metadata_getattr(assocuri1,"UserID",userid);'; put 'rc=metadata_getattr(assocuri1,"Password",passwd);'; put 'call symputx(''user'',userid,''l'');'; put 'call symputx(''pass'',passwd,''l'');'; put '/* path */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,assocuri2);'; put 'rc=metadata_getprop(assocuri2,'; put '''Connection.SQL.Property.Datasrc.Name.xmlKey.txt'',path);'; put 'call symputx(''path'',path,''l'');'; put '/* schema */'; put 'rc=metadata_getnasn("&liburi",''UsingPackages'',1,assocuri3);'; put 'rc=metadata_getattr(assocuri3,''SchemaName'',schema);'; put 'call symputx(''schema'',schema,''l'');'; put 'run;'; put '%put NOTE: Executing the following:/; %put NOTE-;'; put '%put NOTE- libname &libref SQLSVR datasrc=&path schema=&schema ;'; put '%put NOTE- user="&user" pass="XXX";'; put '%put NOTE-;'; put 'libname &libref SQLSVR datasrc=&path schema=&schema user="&user" pass="&pass";'; put '%end;'; put '%else %if &engine=TERADATA %then %do;'; put '%put NOTE: Obtaining &engine library details;'; put 'data _null;'; put 'length assocuri1 assocuri2 assocuri3 authdomain path schema userid'; put 'passwd $256;'; put 'call missing (of _all_);'; put '/* get auth domain */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,assocuri1);'; put 'rc=metadata_getnasn(assocuri1,''Domain'',1,assocuri2);'; put 'rc=metadata_getattr(assocuri2,"Name",authdomain);'; put 'call symputx(''authdomain'',authdomain,''l'');'; put '/*'; put 'rc=metadata_getnasn("&liburi",''DefaultLogin'',1,assocuri1);'; put 'rc=metadata_getattr(assocuri1,"UserID",userid);'; put 'rc=metadata_getattr(assocuri1,"Password",passwd);'; put 'call symputx(''user'',userid,''l'');'; put 'call symputx(''pass'',passwd,''l'');'; put '*/'; put '/* path */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,assocuri2);'; put 'rc=metadata_getprop(assocuri2,'; put '''Connection.Teradata.Property.SERVER.Name.xmlKey.txt'',path);'; put 'call symputx(''path'',path,''l'');'; put '/* schema */'; put 'rc=metadata_getnasn("&liburi",''UsingPackages'',1,assocuri3);'; put 'rc=metadata_getattr(assocuri3,''SchemaName'',schema);'; put 'call symputx(''schema'',schema,''l'');'; put 'run;'; put '%put NOTE: Executing the following:/; %put NOTE-;'; put '%put NOTE- libname &libref TERADATA server="&path" schema=&schema ;'; put '%put NOTe- authdomain=&authdomain;'; put '%put NOTE-;'; put 'libname &libref TERADATA server="&path" schema=&schema authdomain=&authdomain;'; put '%end;'; put '%else %if &engine= %then %do;'; put '%put NOTE: Libref &libref is not registered in metadata;'; put '%&mAbort.mp_abort('; put 'msg=%str(ERR)OR: Libref &libref is not registered in metadata'; put ',mac=mm_assigndirectlib.sas);'; put '%return;'; put '%end;'; put '%else %do;'; put '%put %str(WARN)ING: Engine &engine is currently unsupported;'; put '%put %str(WARN)ING- Please contact your support team.;'; put '%return;'; put '%end;'; put '%mend mm_assigndirectlib;'; put '%macro mm_getrepos('; put 'outds=work.mm_getrepos'; put ')/*/STORE SOURCE*/;'; put '* use a temporary fileref to hold the response;'; put 'filename response temp;'; put '/* get list of libraries */'; put 'proc metadata in='; put '"1"'; put 'out=response;'; put 'run;'; put '/* write the response to the log for debugging */'; put '/*'; put 'data _null_;'; put 'infile response lrecl=1048576;'; put 'input;'; put 'put _infile_;'; put 'run;'; put '*/'; put '/* create an XML map to read the response */'; put 'filename sxlemap temp;'; put 'data _null_;'; put 'file sxlemap;'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository";'; put 'put "";'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Id";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Name";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Desc";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@DefaultNS";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@RepositoryType";'; put 'put "characterstring20";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@RepositoryFormat";'; put 'put "characterstring10";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@Access";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@CurrentAccess";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@PauseState";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Path";'; put 'put "";'; put 'put "characterstring256";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Engine";'; put 'put "";'; put 'put "characterstring8";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Options";'; put 'put "";'; put 'put "characterstring32";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@MetadataCreated";'; put 'put "characterstring24";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@MetadataUpdated";'; put 'put "characterstring24";'; put 'put '''';'; put 'put ''
'';'; put 'run;'; put 'libname _XML_ xml xmlfileref=response xmlmap=sxlemap;'; put 'proc sort data= _XML_.SASRepos out=&outds;'; put 'by name;'; put 'run;'; put '/* clear references */'; put 'filename sxlemap clear;'; put 'filename response clear;'; put 'libname _XML_ clear;'; put '%mend mm_getrepos;'; put '%macro dc_assignlib(type,libref,passthru=);'; put '%put &sysmacroname entry vars:;'; put '%put _local_;'; put '/* take current repository */'; put '%local repo repocnt x;'; put '%let repo=%sysfunc(getoption(metarepository));'; put '/* get list of repositories and filter */'; put '%mm_getrepos(outds=repos)'; put '%let repocnt=0;'; put 'data repos;'; put 'set repos;'; put 'where repositorytype in(''CUSTOM'',''FOUNDATION'')'; put '& upcase(name) ne "%upcase(&repo)";'; put 'keep id name ;'; put 'call symputx(cats(''repo'',_n_),name,''l'');'; put 'call symputx(''repocnt'',_n_,''l'');'; put 'run;'; put '/* find out which of these repositories has the libref we are searching for */'; put '%local lib_uri;'; put '%let lib_uri=NOTFOUND;'; put 'data _null_; /* check default repo first */'; put 'length lib_uri $256;'; put 'call missing (of _all_);'; put 'rc=metadata_getnobj("omsobj:SASLibrary?@Libref =''&libref''",1,lib_uri);'; put 'call symputx(''lib_uri'',coalescec(lib_uri,''NOTFOUND''),''l'');'; put 'run;'; put '%if &lib_uri=NOTFOUND %then %do;'; put '%do x=1 %to &repocnt;'; put 'options metarepository=&&repo&x;'; put 'data _null_;'; put 'length lib_uri $256;'; put 'call missing (of _all_);'; put 'rc=metadata_getnobj("omsobj:SASLibrary?@Libref =''&libref''",1,lib_uri);'; put 'call symputx(''lib_uri'',coalescec(lib_uri,''NOTFOUND''),''l'');'; put 'run;'; put '%if &lib_uri ne NOTFOUND %then %let x=%eval(&repocnt+1);'; put '%end;'; put '%end;'; put '%if &type=READ %then %do;'; put '%mm_assignlib(&libref)'; put '%end;'; put '%else %if &type=WRITE %then %do;'; put '%mm_assigndirectlib(&libref,open_passthrough=&passthru)'; put '%end;'; put 'options metarepository=&repo;'; put '%mend dc_assignlib;'; put '%macro mf_getvarlist(libds'; put ',dlm=%str( )'; put ',quote=no'; put ',typefilter=A'; put ')/*/STORE SOURCE*/;'; put '/* declare local vars */'; put '%local outvar dsid nvars x rc dlm q var vtype;'; put '/* credit Rowland Hale - byte34 is double quote, 39 is single quote */'; put '%if %upcase("e)=DOUBLE %then %let q=%qsysfunc(byte(34));'; put '%else %if %upcase("e)=SINGLE %then %let q=%qsysfunc(byte(39));'; put '/* open dataset in macro */'; put '%let dsid=%sysfunc(open(&libds));'; put '%if &dsid %then %do;'; put '%let nvars=%sysfunc(attrn(&dsid,NVARS));'; put '%if &nvars>0 %then %do;'; put '/* add variables with supplied delimeter */'; put '%do x=1 %to &nvars;'; put '/* get variable type */'; put '%let vtype=%sysfunc(vartype(&dsid,&x));'; put '%if &vtype=&typefilter or &typefilter=A %then %do;'; put '%let var=&q.%sysfunc(varname(&dsid,&x))&q.;'; put '%if &var=&q&q %then %do;'; put '%put &sysmacroname: Empty column found in &libds!;'; put '%let var=&q. &q.;'; put '%end;'; put '%if %quote(&outvar)=%quote() %then %let outvar=&var;'; put '%else %let outvar=&outvar.&dlm.&var.;'; put '%end;'; put '%end;'; put '%end;'; put '%let rc=%sysfunc(close(&dsid));'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: Unable to open &libds (rc=&dsid);'; put '%put &sysmacroname: SYSMSG= %sysfunc(sysmsg());'; put '%let rc=%sysfunc(close(&dsid));'; put '%end;'; put '%do;%unquote(&outvar)%end;'; put '%mend mf_getvarlist;'; put '%macro mf_getvartype(libds /* two level name */'; put ', var /* variable name from which to return the type */'; put ')/*/STORE SOURCE*/;'; put '%local dsid vnum vtype rc;'; put '/* Open dataset */'; put '%let dsid = %sysfunc(open(&libds));'; put '%if &dsid. > 0 %then %do;'; put '/* Get variable number */'; put '%let vnum = %sysfunc(varnum(&dsid, &var));'; put '/* Get variable type (C/N) */'; put '%if(&vnum. > 0) %then %let vtype = %sysfunc(vartype(&dsid, &vnum.));'; put '%else %do;'; put '%put NOTE: Variable &var does not exist in &libds;'; put '%let vtype = %str( );'; put '%end;'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: dataset &libds not opened! (rc=&dsid);'; put '%put &sysmacroname: %sysfunc(sysmsg());'; put '%return;'; put '%end;'; put '/* Close dataset */'; put '%let rc = %sysfunc(close(&dsid));'; put '/* Return variable type */'; put '&vtype'; put '%mend mf_getvartype;'; put '%macro mf_getattrn('; put 'libds'; put ',attr'; put ')/*/STORE SOURCE*/;'; put '%local dsid rc;'; put '%let dsid=%sysfunc(open(&libds,is));'; put '%if &dsid = 0 %then %do;'; put '%put %str(WARN)ING: Cannot open %trim(&libds), system message below;'; put '%put %sysfunc(sysmsg());'; put '-1'; put '%end;'; put '%else %do;'; put '%sysfunc(attrn(&dsid,&attr))'; put '%let rc=%sysfunc(close(&dsid));'; put '%end;'; put '%mend mf_getattrn;'; put '%macro mf_nobs(libds'; put ')/*/STORE SOURCE*/;'; put '%mf_getattrn(&libds,NLOBS)'; put '%mend mf_nobs;'; put '%macro mp_filtergenerate(inds,outref=filter);'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(syscc=&syscc - on macro entry)'; put ')'; put 'filename &outref temp;'; put '%if %mf_nobs(&inds)=0 %then %do;'; put '/* ensure we have a default filter */'; put 'data _null_;'; put 'file &outref;'; put 'put ''1=1'';'; put 'run;'; put '%end;'; put '%else %do;'; put 'proc sort data=&inds;'; put 'by SUBGROUP_ID;'; put 'run;'; put 'data _null_;'; put 'file &outref lrecl=32800;'; put 'set &inds end=last;'; put 'by SUBGROUP_ID;'; put 'if _n_=1 then put ''(('';'; put 'else if first.SUBGROUP_ID then put +1 GROUP_LOGIC ''('';'; put 'else put +2 SUBGROUP_LOGIC;'; put 'put +4 VARIABLE_NM OPERATOR_NM RAW_VALUE;'; put 'if last.SUBGROUP_ID then put '')''@;'; put 'if last then put '')'';'; put 'run;'; put '%end;'; put '%mend mp_filtergenerate;'; put '%macro mp_filtervalidate(inref,targetds,abort=YES,outds=work.mp_filtervalidate);'; put '%mp_abort(iftrue= (&syscc ne 0 or &syserr ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(syscc=&syscc / syserr=&syserr - on macro entry)'; put ')'; put '%local fref1;'; put '%let fref1=%mf_getuniquefileref();'; put 'data _null_;'; put 'file &fref1;'; put 'infile &inref end=eof;'; put 'if _n_=1 then do;'; put 'put "proc sql;";'; put 'put "validate select * from &targetds";'; put 'put "where " ;'; put 'end;'; put 'input;'; put 'put _infile_;'; put 'putlog _infile_;'; put 'if eof then put ";quit;";'; put 'run;'; put '%inc &fref1;'; put 'data &outds;'; put 'if &sqlrc or &syscc or &syserr then do;'; put 'REASON_CD=''VALIDATION_ERR''!!''OR: ''!!'; put 'coalescec(symget(''SYSERRORTEXT''),symget(''SYSWARNINGTEXT''));'; put 'output;'; put 'end;'; put 'else stop;'; put 'run;'; put 'filename &fref1 clear;'; put '%if %mf_nobs(&outds)>0 %then %do;'; put '%if &abort=YES %then %do;'; put 'data _null_;'; put 'set &outds;'; put 'call symputx(''REASON_CD'',reason_cd,''l'');'; put 'stop;'; put 'run;'; put '%mp_abort('; put 'mac=&sysmacroname,'; put 'msg=%str(Filter validation issues.)'; put ')'; put '%end;'; put '%let syscc=1008;'; put '%end;'; put '%mend mp_filtervalidate;'; put '%macro mp_filtercheck(inds,targetds=,outds=work.badrecords,abort=YES);'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(syscc=&syscc - on macro entry)'; put ')'; put '/* Validate input column */'; put '%local vtype;'; put '%let vtype=%mf_getvartype(&inds,RAW_VALUE);'; put '%mp_abort(iftrue=(&abort=YES and &vtype ne C),'; put 'mac=&sysmacroname,'; put 'msg=%str(%str(ERR)OR: RAW_VALUE must be character)'; put ')'; put '%if &vtype ne C %then %do;'; put '%put &sysmacroname: RAW_VALUE must be character;'; put '%let syscc=42;'; put '%return;'; put '%end;'; put '/**'; put '* Sanitise the values based on valid value lists, then strip out'; put '* quotes, commas, periods and spaces.'; put '*/'; put '%local reason_cd nobs;'; put '%let nobs=0;'; put 'data &outds;'; put '/*length GROUP_LOGIC SUBGROUP_LOGIC $3 SUBGROUP_ID 8 VARIABLE_NM $32'; put 'OPERATOR_NM $10 RAW_VALUE $4000;*/'; put 'set &inds end=last;'; put 'length reason_cd $4032 vtype vtype2 $1 vnum dsid 8 tmp $4000;'; put 'drop tmp;'; put '/* quick check to ensure column exists */'; put 'if upcase(VARIABLE_NM) not in'; put '(%upcase(%mf_getvarlist(&targetds,dlm=%str(,),quote=SINGLE)))'; put 'then do;'; put 'REASON_CD="Variable "!!cats(variable_nm)!!" not in &targetds";'; put 'putlog REASON_CD= VARIABLE_NM=;'; put 'call symputx(''reason_cd'',reason_cd,''l'');'; put 'call symputx(''nobs'',_n_,''l'');'; put 'output;'; put 'return;'; put 'end;'; put '/* need to open the dataset to get the column type */'; put 'retain dsid;'; put 'if _n_=1 then dsid=open("&targetds","i");'; put 'if dsid>0 then do;'; put 'vnum=varnum(dsid,VARIABLE_NM);'; put 'if vnum<1 then do;'; put '/* should not happen as was also tested for above */'; put 'REASON_CD=cats("Variable (",VARIABLE_NM,") not found in &targetds");'; put 'putlog REASON_CD= dsid=;'; put 'call symputx(''reason_cd'',reason_cd,''l'');'; put 'call symputx(''nobs'',_n_,''l'');'; put 'output;'; put 'goto endstep;'; put 'end;'; put '/* now we can get the type */'; put 'else vtype=vartype(dsid,vnum);'; put 'end;'; put 'else do;'; put 'REASON_CD=cats("Could not open &targetds");'; put 'putlog REASON_CD= dsid=;'; put 'call symputx(''reason_cd'',reason_cd,''l'');'; put 'call symputx(''nobs'',_n_,''l'');'; put 'output;'; put 'stop;'; put 'end;'; put '/* closed list checks */'; put 'if GROUP_LOGIC not in (''AND'',''OR'') then do;'; put 'REASON_CD=''GROUP_LOGIC should be AND/OR, not:''!!cats(GROUP_LOGIC);'; put 'putlog REASON_CD= GROUP_LOGIC=;'; put 'call symputx(''reason_cd'',reason_cd,''l'');'; put 'call symputx(''nobs'',_n_,''l'');'; put 'output;'; put 'end;'; put 'if SUBGROUP_LOGIC not in (''AND'',''OR'') then do;'; put 'REASON_CD=''SUBGROUP_LOGIC should be AND/OR, not:''!!cats(SUBGROUP_LOGIC);'; put 'putlog REASON_CD= SUBGROUP_LOGIC=;'; put 'call symputx(''reason_cd'',reason_cd,''l'');'; put 'call symputx(''nobs'',_n_,''l'');'; put 'output;'; put 'end;'; put 'if mod(SUBGROUP_ID,1) ne 0 then do;'; put 'REASON_CD=''SUBGROUP_ID should be integer, not ''!!cats(subgroup_id);'; put 'putlog REASON_CD= SUBGROUP_ID=;'; put 'call symputx(''reason_cd'',reason_cd,''l'');'; put 'call symputx(''nobs'',_n_,''l'');'; put 'output;'; put 'end;'; put 'if OPERATOR_NM not in'; put '(''='',''>'',''<'',''<='',''>='',''NE'',''GE'',''LE'',''BETWEEN'',''IN'',''NOT IN'',''CONTAINS'')'; put 'then do;'; put 'REASON_CD=''Invalid OPERATOR_NM: ''!!cats(OPERATOR_NM);'; put 'putlog REASON_CD= OPERATOR_NM=;'; put 'call symputx(''reason_cd'',reason_cd,''l'');'; put 'call symputx(''nobs'',_n_,''l'');'; put 'output;'; put 'end;'; put '/* special missing logic */'; put 'if vtype=''N'' & OPERATOR_NM in (''='',''>'',''<'',''<='',''>='',''NE'',''GE'',''LE'') then do;'; put 'if cats(upcase(raw_value)) in ('; put '''.'',''.A'',''.B'',''.C'',''.D'',''.E'',''.F'',''.G'',''.H'',''.I'',''.J'',''.K'',''.L'',''.M'',''.N'''; put '''.N'',''.O'',''.P'',''.Q'',''.R'',''.S'',''.T'',''.U'',''.V'',''.W'',''.X'',''.Y'',''.Z'',''._'''; put ')'; put 'then do;'; put '/* valid numeric - exit data step loop */'; put 'return;'; put 'end;'; put 'else if subpad(upcase(raw_value),1,1) in ('; put '''A'',''B'',''C'',''D'',''E'',''F'',''G'',''H'',''I'',''J'',''K'',''L'',''M'',''N'''; put '''N'',''O'',''P'',''Q'',''R'',''S'',''T'',''U'',''V'',''W'',''X'',''Y'',''Z'',''_'''; put ')'; put 'then do;'; put '/* check if the raw_value contains a valid variable NAME */'; put 'vnum=varnum(dsid,subpad(raw_value,1,32));'; put 'if vnum>0 then do;'; put '/* now we can get the type */'; put 'vtype2=vartype(dsid,vnum);'; put '/* check type matches */'; put 'if vtype2=vtype then do;'; put '/* valid target var - exit loop */'; put 'return;'; put 'end;'; put 'else do;'; put 'REASON_CD=cats("Compared Type (",vtype2,") is not (",vtype,")");'; put 'putlog REASON_CD= dsid=;'; put 'call symputx(''reason_cd'',reason_cd,''l'');'; put 'call symputx(''nobs'',_n_,''l'');'; put 'output;'; put 'goto endstep;'; put 'end;'; put 'end;'; put 'end;'; put 'end;'; put '/* special logic */'; put 'if OPERATOR_NM in (''IN'',''NOT IN'',''BETWEEN'') then do;'; put 'if OPERATOR_NM=''BETWEEN'' then raw_value1=tranwrd(raw_value,'' AND '','','');'; put 'else do;'; put 'if substr(raw_value,1,1) ne ''('''; put 'or substr(cats(reverse(raw_value)),1,1) ne '')'''; put 'then do;'; put 'REASON_CD=''Missing start/end bracket in RAW_VALUE'';'; put 'putlog REASON_CD= OPERATOR_NM= raw_value= raw_value1= ;'; put 'call symputx(''reason_cd'',reason_cd,''l'');'; put 'call symputx(''nobs'',_n_,''l'');'; put 'output;'; put 'end;'; put 'else raw_value1=substr(raw_value,2,max(length(raw_value)-2,0));'; put 'end;'; put '/* we now have a comma seperated list of values */'; put 'if vtype=''N'' then do i=1 to countc(raw_value1, '','')+1;'; put 'tmp=scan(raw_value1,i,'','');'; put 'if cats(tmp) ne ''.'' and input(tmp, ?? 8.) eq . then do;'; put 'if OPERATOR_NM =''BETWEEN'' and subpad(upcase(tmp),1,1) in ('; put '''A'',''B'',''C'',''D'',''E'',''F'',''G'',''H'',''I'',''J'',''K'',''L'',''M'',''N'''; put '''N'',''O'',''P'',''Q'',''R'',''S'',''T'',''U'',''V'',''W'',''X'',''Y'',''Z'',''_'''; put ')'; put 'then do;'; put '/* check if the raw_value contains a valid variable NAME */'; put '/* is not valid syntax for IN or NOT IN */'; put 'vnum=varnum(dsid,subpad(tmp,1,32));'; put 'if vnum>0 then do;'; put '/* now we can get the type */'; put 'vtype2=vartype(dsid,vnum);'; put '/* check type matches */'; put 'if vtype2=vtype then do;'; put '/* valid target var - exit loop */'; put 'return;'; put 'end;'; put 'else do;'; put 'REASON_CD=cats("Compared Type (",vtype2,") is not (",vtype,")");'; put 'putlog REASON_CD= dsid=;'; put 'call symputx(''reason_cd'',reason_cd,''l'');'; put 'call symputx(''nobs'',_n_,''l'');'; put 'output;'; put 'goto endstep;'; put 'end;'; put 'end;'; put 'end;'; put 'REASON_CD=''Non Numeric value provided'';'; put 'putlog REASON_CD= OPERATOR_NM= raw_value= raw_value1= ;'; put 'call symputx(''reason_cd'',reason_cd,''l'');'; put 'call symputx(''nobs'',_n_,''l'');'; put 'output;'; put 'end;'; put 'return;'; put 'end;'; put 'end;'; put 'else raw_value1=raw_value;'; put '/* remove nested literals eg '''' */'; put 'raw_value1=tranwrd(raw_value1,"''''",'''');'; put '/* now match string literals (always single quotes) */'; put 'raw_value2=raw_value1;'; put 'regex = prxparse("s/(\'').*?(\'')//");'; put 'call prxchange(regex,-1,raw_value2);'; put '/* remove commas and periods*/'; put 'raw_value3=compress(raw_value2,'',.'');'; put '/* output records that contain values other than digits and spaces */'; put 'if notdigit(compress(raw_value3,'' ''))>0 then do;'; put 'if vtype=''C'' and subpad(upcase(raw_value),1,1) in ('; put '''A'',''B'',''C'',''D'',''E'',''F'',''G'',''H'',''I'',''J'',''K'',''L'',''M'',''N'''; put '''N'',''O'',''P'',''Q'',''R'',''S'',''T'',''U'',''V'',''W'',''X'',''Y'',''Z'',''_'''; put ')'; put 'then do;'; put '/* check if the raw_value contains a valid variable NAME */'; put 'vnum=varnum(dsid,subpad(raw_value,1,32));'; put 'if vnum>0 then do;'; put '/* now we can get the type */'; put 'vtype2=vartype(dsid,vnum);'; put '/* check type matches */'; put 'if vtype2=vtype then do;'; put '/* valid target var - exit loop */'; put 'return;'; put 'end;'; put 'else do;'; put 'REASON_CD=cats("Compared Char Type (",vtype2,") is not (",vtype,")");'; put 'putlog REASON_CD= dsid=;'; put 'call symputx(''reason_cd'',reason_cd,''l'');'; put 'call symputx(''nobs'',_n_,''l'');'; put 'output;'; put 'goto endstep;'; put 'end;'; put 'end;'; put 'end;'; put 'putlog raw_value3= $hex32.;'; put 'REASON_CD=cats(''Invalid RAW_VALUE:'',raw_value);'; put 'putlog (_all_)(=);'; put 'call symputx(''reason_cd'',reason_cd,''l'');'; put 'call symputx(''nobs'',_n_,''l'');'; put 'output;'; put 'end;'; put 'endstep:'; put 'if last then rc=close(dsid);'; put 'run;'; put 'data _null_;'; put 'set &outds end=last;'; put 'putlog (_all_)(=);'; put 'run;'; put '%mp_abort(iftrue=(&abort=YES and &nobs>0),'; put 'mac=&sysmacroname,'; put 'msg=%str(Data issue: %superq(reason_cd))'; put ')'; put '%if &nobs>0 %then %do;'; put '%let syscc=1008;'; put '%return;'; put '%end;'; put '/**'; put '* syntax checking passed but it does not mean the filter is valid'; put '* for that we can run a proc sql validate query'; put '*/'; put '%local fref1;'; put '%let fref1=%mf_getuniquefileref();'; put '%mp_filtergenerate(&inds,outref=&fref1)'; put '/* this macro will also set syscc to 1008 if any issues found */'; put '%mp_filtervalidate(&fref1,&targetds,outds=&outds,abort=&abort)'; put '%mend mp_filtercheck;'; put '* SAS Macros end;'; put '* SAS Includes start;'; put '* SAS Includes end;'; put '* Binary Files start;'; put '* Binary Files end;'; put '* ServiceInit start;'; put 'options noquotelenmax ps=max;'; put '/* create dummy macros to prevent stpbegin / stpend from corrupting sessions */'; put '%macro stpbegin();'; put '%put NOTE: the STPBEGIN macro should not be used for web apps!;'; put '%mend stpbegin;'; put '%macro stpend();'; put '%put NOTE: the STPEND macro should not be used for web apps!;'; put '%mend stpend;'; put '* ServiceInit end;'; put '* Service start;'; put '/**'; put '@file'; put '@brief Post Edit Hook script for the MPE_ROW_LEVEL_SECURITY table'; put '@details Post edit hooks provide additional backend validation for user'; put 'provided data. The incoming dataset is named `work.staging_ds` and is'; put 'provided in mpe_loader.sas.'; put 'Available macro variables:'; put '@li DC_LIBREF - The DC control library'; put '@li LIBREF - The library of the dataset being edited (is assigned)'; put '@li DS - The dataset being edited'; put 'This validation checks the incoming row_level_security settings to ensure'; put 'each individual filter is'; put '

SAS Macros

'; put '@li dc_assignlib.sas'; put '@li mp_filtercheck.sas'; put '

Related Macros

'; put '@li mpe_loader.sas'; put '**/'; put '/* ignore scope and group for validation */'; put 'proc sql;'; put 'create table work.batches as'; put 'select distinct upcase(rls_libref) as rls_libref,'; put 'upcase(rls_table) as rls_table,'; put 'rls_group_logic as group_logic,'; put 'rls_subgroup_logic as subgroup_logic,'; put 'rls_subgroup_id as subgroup_id,'; put 'rls_variable_nm as variable_nm,'; put 'rls_operator_nm as operator_nm,'; put 'rls_raw_value as raw_value'; put 'from work.staging_ds'; put 'where rls_active=1'; put 'order by rls_libref, rls_table;'; put '%let cnt=0;'; put 'data _null_;'; put 'set work.batches;'; put 'by rls_libref rls_table;'; put 'putlog (_all_)(=);'; put 'if last.rls_table then do;'; put 'x+1;'; put 'call symputx(cats(''libds'',x),cats(rls_libref,''.'',rls_table));'; put 'call symputx(''cnt'',x);'; put 'end;'; put 'run;'; put '%macro quickloop();'; put '%do i=1 %to &cnt;'; put 'data work.inds&i;'; put 'set work.batches;'; put 'if cats(rls_libref,''.'',rls_table)="&&libds&i";'; put 'keep group_logic subgroup_logic subgroup_id variable_nm operator_nm'; put 'raw_value;'; put 'run;'; put '%dc_assignlib(READ,%scan(&&libds&i,1,.))'; put '%mp_filtercheck(work.inds&i'; put ',targetds=&&libds&i'; put ',outds=work.badrecords'; put ',abort=YES'; put ')'; put '%end;'; put '%mend quickloop;'; put '%quickloop()'; put '* Service end;'; run; %mm_createwebservice(path=&appLoc/&path, name=&service, code=sascode, server=&serverName, replace=yes) filename sascode clear; %let service=mpe_security_postedit; filename sascode temp lrecl=32767; data _null_; file sascode; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '/**'; put '@file mp_jsonout.sas'; put '@brief Writes JSON in SASjs format to a fileref'; put '@details This macro can be used to OPEN a JSON stream and send one or more'; put 'tables as arrays of rows, where each row can be an object or a nested array.'; put 'There are two engines available - DATASTEP or PROCJSON.'; put 'PROC JSON is fast but will produce errs like the ones below if'; put 'special chars are encountered.'; put '> (ERR)OR: Some code points did not transcode.'; put '> An object or array close is not valid at this point in the JSON text.'; put '> Date value out of range'; put 'If this happens, try running with ENGINE=DATASTEP.'; put 'The DATASTEP engine is used to handle special SAS missing numerics, and'; put 'can also convert entire datasets to formatted values. Output JSON is always'; put 'in UTF-8.'; put 'Usage:'; put 'filename tmp temp;'; put 'data class; set sashelp.class;run;'; put '%mp_jsonout(OPEN,jref=tmp)'; put '%mp_jsonout(OBJ,class,jref=tmp)'; put '%mp_jsonout(OBJ,class,dslabel=class2,jref=tmp,showmeta=Y)'; put '%mp_jsonout(CLOSE,jref=tmp)'; put 'data _null_;'; put 'infile tmp;'; put 'input;putlog _infile_;'; put 'run;'; put 'If you are building web apps with SAS then you are strongly encouraged to use'; put 'the mX_createwebservice macros in combination with the'; put '[sasjs adapter](https://github.com/sasjs/adapter).'; put 'For more information see https://sasjs.io'; put '@param [in] action Valid values:'; put '@li OPEN - opens the JSON'; put '@li OBJ - sends a table with each row as an object'; put '@li ARR - sends a table with each row in an array'; put '@li CLOSE - closes the JSON'; put '@param [in] ds The dataset to send. Must be a work table.'; put '@param [out] jref= (_webout) The fileref to which to send the JSON'; put '@param [out] dslabel= The name to give the table in the exported JSON'; put '@param [in] fmt= (Y) Whether to keep (Y) or strip (N) formats from the table'; put '@param [in] engine= (DATASTEP) Which engine to use to send the JSON. Options:'; put '@li PROCJSON (default)'; put '@li DATASTEP (more reliable when data has non standard characters)'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to JSON'; put '

Related Files

'; put '@li mp_ds2fmtds.sas'; put '@version 9.2'; put '@author Allan Bowe'; put '@source https://github.com/sasjs/core'; put '**/'; put '%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y'; put ',engine=DATASTEP'; put ',missing=NULL'; put ',showmeta=N'; put ',maxobs=MAX'; put ')/*/STORE SOURCE*/;'; put '%local tempds colinfo fmtds i numcols numobs stmt_obs lastobs optval'; put 'tmpds1 tmpds2 tmpds3 tmpds4;'; put '%let numcols=0;'; put '%if &maxobs ne MAX %then %let stmt_obs=%str(if _n_>&maxobs then stop;);'; put '%if &action=OPEN %then %do;'; put 'options nobomfile;'; put 'data _null_;file &jref encoding=''utf-8'' lrecl=200;'; put 'put ''{"PROCESSED_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''"'';'; put 'run;'; put '%end;'; put '%else %if (&action=ARR or &action=OBJ) %then %do;'; put '/* force variable names to always be uppercase in the JSON */'; put 'options validvarname=upcase;'; put '/* To avoid issues with _webout on EBI - such as encoding diffs and truncation'; put '(https://support.sas.com/kb/49/325.html) we use temporary files */'; put 'filename _sjs1 temp lrecl=200 ;'; put 'data _null_; file _sjs1 encoding=''utf-8'';'; put 'put ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":";'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjs1 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs1 clear;'; put '/* grab col defs */'; put 'proc contents noprint data=&ds'; put 'out=_data_(keep=name type length format formatl formatd varnum label);'; put 'run;'; put '%let colinfo=%scan(&syslast,2,.);'; put 'proc sort data=&colinfo;'; put 'by varnum;'; put 'run;'; put '/* move meta to mac vars */'; put 'data &colinfo;'; put 'if _n_=1 then call symputx(''numcols'',nobs,''l'');'; put 'set &colinfo end=last nobs=nobs;'; put 'name=upcase(name);'; put '/* fix formats */'; put 'if type=2 or type=6 then do;'; put 'typelong=''char'';'; put 'length fmt $49.;'; put 'if format='''' then fmt=cats(''$'',length,''.'');'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else fmt=cats(format,formatl,''.'');'; put 'end;'; put 'else do;'; put 'typelong=''num'';'; put 'if format='''' then fmt=''best.'';'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else if formatd=0 then fmt=cats(format,formatl,''.'');'; put 'else fmt=cats(format,formatl,''.'',formatd);'; put 'end;'; put '/* 32 char unique name */'; put 'newname=''sasjs''!!substr(cats(put(md5(name),$hex32.)),1,27);'; put 'call symputx(cats(''name'',_n_),name,''l'');'; put 'call symputx(cats(''newname'',_n_),newname,''l'');'; put 'call symputx(cats(''length'',_n_),length,''l'');'; put 'call symputx(cats(''fmt'',_n_),fmt,''l'');'; put 'call symputx(cats(''type'',_n_),type,''l'');'; put 'call symputx(cats(''typelong'',_n_),typelong,''l'');'; put 'call symputx(cats(''label'',_n_),coalescec(label,name),''l'');'; put '/* overwritten when fmt=Y and a custom format exists in catalog */'; put 'if typelong=''num'' then call symputx(cats(''fmtlen'',_n_),200,''l'');'; put 'else call symputx(cats(''fmtlen'',_n_),min(32767,ceil((length+10)*1.5)),''l'');'; put 'run;'; put '%let tempds=%substr(_%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql;'; put 'select count(*) into: lastobs from &ds;'; put '%if &maxobs ne MAX %then %let lastobs=%sysfunc(min(&lastobs,&maxobs));'; put '%if &engine=PROCJSON %then %do;'; put '%if &missing=STRING %then %do;'; put '%put &sysmacroname: Special Missings not supported in proc json.;'; put '%put &sysmacroname: Switching to DATASTEP engine;'; put '%goto datastep;'; put '%end;'; put 'data &tempds;'; put 'set &ds;'; put '&stmt_obs;'; put '%if &fmt=N %then format _numeric_ best32.;;'; put '/* PRETTY is necessary to avoid line truncation in large files */'; put 'filename _sjs2 temp lrecl=131068 encoding=''utf-8'';'; put 'proc json out=_sjs2 pretty'; put '%if &action=ARR %then nokeys ;'; put ';export &tempds / nosastags fmtnumeric;'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs2 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs2 clear;'; put '%end;'; put '%else %if &engine=DATASTEP %then %do;'; put '%datastep:'; put '%if %sysfunc(exist(&ds)) ne 1 & %sysfunc(exist(&ds,VIEW)) ne 1'; put '%then %do;'; put '%put &sysmacroname: &ds NOT FOUND!!!;'; put '%return;'; put '%end;'; put '%if &fmt=Y %then %do;'; put '/**'; put '* Extract format definitions'; put '* First, by getting library locations from dictionary.formats'; put '* Then, by exporting the width using proc format'; put '* Cannot use maxw from sashelp.vformat as not always populated'; put '* Cannot use fmtinfo() as not supported in all flavours'; put '*/'; put '%let tmpds1=%substr(fmtsum%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds2=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds3=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds4=%substr(col%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql noprint;'; put 'create table &tmpds1 as'; put 'select cats(libname,''.'',memname) as FMTCAT,'; put 'FMTNAME'; put 'from dictionary.formats'; put 'where fmttype=''F'' and libname is not null'; put 'and fmtname in (select format from &colinfo where format is not null)'; put 'order by 1;'; put 'create table &tmpds2('; put 'FMTNAME char(32),'; put 'LENGTH num'; put ');'; put '%local catlist cat fmtlist i;'; put 'select distinct fmtcat into: catlist separated by '' '' from &tmpds1;'; put '%do i=1 %to %sysfunc(countw(&catlist,%str( )));'; put '%let cat=%scan(&catlist,&i,%str( ));'; put 'proc sql;'; put 'select distinct fmtname into: fmtlist separated by '' '''; put 'from &tmpds1 where fmtcat="&cat";'; put 'proc format lib=&cat cntlout=&tmpds3(keep=fmtname length);'; put 'select &fmtlist;'; put 'run;'; put 'proc sql;'; put 'insert into &tmpds2 select distinct fmtname,length from &tmpds3;'; put '%end;'; put 'proc sql;'; put 'create table &tmpds4 as'; put 'select a.*, b.length as MAXW'; put 'from &colinfo a'; put 'left join &tmpds2 b'; put 'on cats(a.format)=cats(upcase(b.fmtname))'; put 'order by a.varnum;'; put 'data _null_;'; put 'set &tmpds4;'; put 'if not missing(maxw);'; put 'call symputx('; put 'cats(''fmtlen'',_n_),'; put '/* vars need extra padding due to JSON escaping of special chars */'; put 'min(32767,ceil((max(length,maxw)+10)*1.5))'; put ',''l'''; put ');'; put 'run;'; put '/* configure varlenchk - as we are explicitly shortening the variables */'; put '%let optval=%sysfunc(getoption(varlenchk));'; put 'options varlenchk=NOWARN;'; put 'data _data_(compress=char);'; put '/* shorten the new vars */'; put 'length'; put '%do i=1 %to &numcols;'; put '&&name&i $&&fmtlen&i'; put '%end;'; put ';'; put '/* rename on entry */'; put 'set &ds(rename=('; put '%do i=1 %to &numcols;'; put '&&name&i=&&newname&i'; put '%end;'; put '));'; put '&stmt_obs;'; put 'drop'; put '%do i=1 %to &numcols;'; put '&&newname&i'; put '%end;'; put ';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=num %then %do;'; put '&&name&i=cats(put(&&newname&i,&&fmt&i));'; put '%end;'; put '%else %do;'; put '&&name&i=put(&&newname&i,&&fmt&i);'; put '%end;'; put '%end;'; put 'if _error_ then do;'; put 'call symputx(''syscc'',1012);'; put 'stop;'; put 'end;'; put 'run;'; put '%let fmtds=&syslast;'; put 'options varlenchk=&optval;'; put '%end;'; put 'proc format; /* credit yabwon for special null removal */'; put 'value bart (default=40)'; put '%if &missing=NULL %then %do;'; put '._ - .z = null'; put '%end;'; put '%else %do;'; put '._ = [quote()]'; put '. = null'; put '.a - .z = [quote()]'; put '%end;'; put 'other = [best.];'; put 'data &tempds;'; put 'attrib _all_ label='''';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'length &&name&i $&&fmtlen&i...;'; put 'format &&name&i $&&fmtlen&i...;'; put '%end;'; put '%end;'; put '%if &fmt=Y %then %do;'; put 'set &fmtds;'; put '%end;'; put '%else %do;'; put 'set &ds;'; put '%end;'; put '&stmt_obs;'; put 'format _numeric_ bart.;'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'if findc(&&name&i,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put '&&name&i=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,&&name&i)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else &&name&i=quote(cats(&&name&i));'; put '%end;'; put '%end;'; put 'run;'; put 'filename _sjs3 temp lrecl=131068 ;'; put 'data _null_;'; put 'file _sjs3 encoding=''utf-8'';'; put 'if _n_=1 then put "[";'; put 'set &tempds;'; put 'if _n_>1 then put "," @; put'; put '%if &action=ARR %then "[" ; %else "{" ;'; put '%do i=1 %to &numcols;'; put '%if &i>1 %then "," ;'; put '%if &action=OBJ %then """&&name&i"":" ;'; put '"&&name&i"n /* name literal for reserved variable names */'; put '%end;'; put '%if &action=ARR %then "]" ; %else "}" ; ;'; put '/* close out the table */'; put 'data _null_;'; put 'file _sjs3 mod encoding=''utf-8'';'; put 'put '']'';'; put 'run;'; put 'data _null_;'; put 'infile _sjs3 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs3 clear;'; put '%end;'; put 'proc sql;'; put 'drop table &colinfo, &tempds;'; put '%if %substr(&showmeta,1,1)=Y %then %do;'; put 'filename _sjs4 temp lrecl=131068 encoding=''utf-8'';'; put 'data _null_;'; put 'file _sjs4;'; put 'length label $350;'; put 'put ", ""$%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":{""vars"":{";'; put 'do i=1 to &numcols;'; put 'name=quote(trim(symget(cats(''name'',i))));'; put 'format=quote(trim(symget(cats(''fmt'',i))));'; put 'label=quote(prxchange(''s/\\/\\\\/'',-1,trim(symget(cats(''label'',i)))));'; put 'length=quote(trim(symget(cats(''length'',i))));'; put 'type=quote(trim(symget(cats(''typelong'',i))));'; put 'if i>1 then put "," @@;'; put 'put name '':{"format":'' format '',"label":'' label'; put ''',"length":'' length '',"type":'' type ''}'';'; put 'end;'; put 'put ''}}'';'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs4 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs4 clear;'; put '%end;'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put 'data _null_; file &jref encoding=''utf-8'' mod ;'; put 'put "}";'; put 'run;'; put '%end;'; put '%mend mp_jsonout;'; put '/**'; put '@file mm_webout.sas'; put '@brief Send data to/from SAS Stored Processes'; put '@details This macro should be added to the start of each Stored Process,'; put '**immediately** followed by a call to:'; put '%mm_webout(FETCH)'; put 'This will read all the input data and create same-named SAS datasets in the'; put 'WORK library. You can then insert your code, and send data back using the'; put 'following syntax:'; put 'data some datasets; * make some data ;'; put 'retain some columns;'; put 'run;'; put '%mm_webout(OPEN)'; put '%mm_webout(ARR,some) * Array format, fast, suitable for large tables ;'; put '%mm_webout(OBJ,datasets) * Object format, easier to work with ;'; put 'Finally, wrap everything up send some helpful system variables too'; put '%mm_webout(CLOSE)'; put '@param [in] action Either FETCH, OPEN, ARR, OBJ or CLOSE'; put '@param [in] ds The dataset to send back to the frontend'; put '@param [out] dslabel= Value to use instead of table name for sending to JSON'; put '@param [in] fmt= (N) Setting Y converts all vars to their formatted values'; put '@param [out] fref= (_webout) The fileref to which to write the JSON'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] workobs= (0) When set to a positive integer, will create a new'; put 'output object (WORK) which contains this number of observations from all'; put 'tables in the WORK library.'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to output JSON'; put '

SAS Macros

'; put '@li mp_jsonout.sas'; put '

Related Macros

'; put '@li ms_webout.sas'; put '@li mv_webout.sas'; put '@version 9.3'; put '@author Allan Bowe'; put '**/'; put '%macro mm_webout(action,ds,dslabel=,fref=_webout,fmt=N,missing=NULL'; put ',showmeta=N,maxobs=MAX,workobs=0'; put ');'; put '%global _webin_file_count _webin_fileref1 _webin_name1 _program _debug'; put 'sasjs_tables;'; put '%local i tempds jsonengine;'; put '/* see https://github.com/sasjs/core/issues/41 */'; put '%if "%upcase(&SYSENCODING)" ne "UTF-8" %then %let jsonengine=PROCJSON;'; put '%else %let jsonengine=DATASTEP;'; put '%if &action=FETCH %then %do;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'options mprint notes mprintnest;'; put '%end;'; put '%let _webin_file_count=%eval(&_webin_file_count+0);'; put '/* now read in the data */'; put '%do i=1 %to &_webin_file_count;'; put '%if &_webin_file_count=1 %then %do;'; put '%let _webin_fileref1=&_webin_fileref;'; put '%let _webin_name1=&_webin_name;'; put '%end;'; put 'data _null_;'; put 'infile &&_webin_fileref&i termstr=crlf;'; put 'input;'; put 'call symputx(''input_statement'',_infile_);'; put 'putlog "&&_webin_name&i input statement: " _infile_;'; put 'stop;'; put 'data &&_webin_name&i;'; put 'infile &&_webin_fileref&i firstobs=2 dsd termstr=crlf encoding=''utf-8'';'; put 'input &input_statement;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'if _n_<20 then putlog _infile_;'; put '%end;'; put 'run;'; put '%let sasjs_tables=&sasjs_tables &&_webin_name&i;'; put '%end;'; put '%end;'; put '%else %if &action=OPEN %then %do;'; put '/* fix encoding */'; put 'OPTIONS NOBOMFILE;'; put '/**'; put '* check xengine type to avoid the below err message:'; put '* > Function is only valid for filerefs using the CACHE access method.'; put '*/'; put 'data _null_;'; put 'set sashelp.vextfl(where=(fileref="_WEBOUT"));'; put 'if xengine=''STREAM'' then do;'; put 'rc=stpsrv_header(''Content-type'',"text/html; encoding=utf-8");'; put 'end;'; put 'run;'; put '/* setup json */'; put 'data _null_;file &fref encoding=''utf-8'';'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutBEGIN<<'';'; put '%end;'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'run;'; put '%end;'; put '%else %if &action=ARR or &action=OBJ %then %do;'; put '%mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt,jref=&fref'; put ',engine=&jsonengine,missing=&missing,showmeta=&showmeta,maxobs=&maxobs'; put ')'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put '/* To avoid issues with _webout on EBI we use a temporary file */'; put 'filename _sjsref temp lrecl=131068;'; put '%if %str(&workobs) > 0 %then %do;'; put '/* if debug mode, send back first XX records of each work table also */'; put 'data;run;%let tempds=%scan(&syslast,2,.);'; put 'ods output Members=&tempds;'; put 'proc datasets library=WORK memtype=data;'; put '%local wtcnt;%let wtcnt=0;'; put 'data _null_;'; put 'set &tempds;'; put 'if not (upcase(name) =:"DATA"); /* ignore temp datasets */'; put 'i+1;'; put 'call symputx(cats(''wt'',i),name,''l'');'; put 'call symputx(''wtcnt'',i,''l'');'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put ",""WORK"":{";'; put '%do i=1 %to &wtcnt;'; put '%let wt=&&wt&i;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'dsid=open("WORK.&wt",''is'');'; put 'nlobs=attrn(dsid,''NLOBS'');'; put 'nvars=attrn(dsid,''NVARS'');'; put 'rc=close(dsid);'; put 'if &i>1 then put '',''@;'; put 'put " ""&wt"" : {";'; put 'put ''"nlobs":'' nlobs;'; put 'put '',"nvars":'' nvars;'; put '%mp_jsonout(OBJ,&wt,jref=_sjsref,dslabel=first10rows,showmeta=Y'; put ',maxobs=&workobs'; put ')'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put '%end;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put 'run;'; put '%end;'; put '/* close off json */'; put 'data _null_;file _sjsref mod encoding=''utf-8'';'; put 'length SYSPROCESSNAME syserrortext syswarningtext autoexec $512;'; put 'put ",""_DEBUG"" : ""&_debug"" ";'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'autoexec=quote(urlencode(trim(getoption(''autoexec''))));'; put 'put '',"AUTOEXEC" : '' autoexec;'; put 'put ",""MF_GETUSER"" : ""%mf_getuser()"" ";'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'put ",""SYSENCODING"" : ""&sysencoding"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSPROCESSID"" : ""&SYSPROCESSID"" ";'; put 'put ",""SYSPROCESSMODE"" : ""&SYSPROCESSMODE"" ";'; put 'SYSPROCESSNAME=quote(urlencode(cats(SYSPROCESSNAME)));'; put 'put ",""SYSPROCESSNAME"" : " SYSPROCESSNAME;'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put '',"SYSWARNINGTEXT" : '' syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'length memsize $32;'; put 'memsize="%sysfunc(INPUTN(%sysfunc(getoption(memsize)), best.),sizekmg.)";'; put 'memsize=quote(cats(memsize));'; put 'put '',"MEMSIZE" : '' memsize;'; put 'put "}" @;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutEND<<'';'; put '%end;'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjsref lrecl=1 recfm=n;'; put 'file &fref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjsref clear;'; put '%end;'; put '%mend mm_webout;'; put '%macro webout(action,ds,dslabel=,fmt=,missing=NULL,showmeta=NO,maxobs=MAX);'; put '%mm_webout(&action,ds=&ds,dslabel=&dslabel,fmt=&fmt'; put ',missing=&missing'; put ',showmeta=&showmeta'; put ',maxobs=&maxobs'; put ') %mend;'; put '/* provide additional debug info */'; put '%global _program;'; put '%put &=syscc;'; put '%put user=%mf_getuser();'; put '%put pgm=&_program;'; put '%put timestamp=%sysfunc(datetime(),datetime19.);'; put '* Service Variables start;'; put '* Service Variables end;'; put '* SAS Macros start;'; put '%macro mf_getapploc(pgm);'; put '%if "&pgm"="" %then %do;'; put '%if %symexist(_program) %then %let pgm=&_program;'; put '%else %do;'; put '%put &sysmacroname: No value provided and no _program variable available;'; put '%return;'; put '%end;'; put '%end;'; put '%local root;'; put '/**'; put '* First check we are not in the tests/macros folder (which has no subfolders)'; put '* or specifically in the testsetup or testteardown services'; put '*/'; put '%if %index(&pgm,/tests/macros/)'; put 'or %index(&pgm,/tests/testsetup)'; put 'or %index(&pgm,/tests/testteardown)'; put '%then %do;'; put '%let root=%substr(&pgm,1,%index(&pgm,/tests)-1);'; put '&root'; put '%return;'; put '%end;'; put '/**'; put '* Next, move up two levels to avoid matches on subfolder or service name'; put '*/'; put '%let root=%substr(&pgm,1,%length(&pgm)-%length(%scan(&pgm,-1,/))-1);'; put '%let root=%substr(&root,1,%length(&root)-%length(%scan(&root,-1,/))-1);'; put '%if %index(&root,/tests/) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/tests/)-1);'; put '%end;'; put '%else %if %index(&root,/services) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/services)-1);'; put '%end;'; put '%else %if %index(&root,/jobs) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/jobs)-1);'; put '%end;'; put '%else %put &sysmacroname: Could not find an app location from &pgm;'; put '&root'; put '%mend mf_getapploc ;'; put '%macro mf_getuniquefileref(prefix=_,maxtries=1000,lrecl=32767);'; put '%local rc fname;'; put '%if &prefix=0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%end;'; put '%else %do;'; put '%local x len;'; put '%let len=%eval(8-%length(&prefix));'; put '%let x=0;'; put '%do x=0 %to &maxtries;'; put '%let fname=&prefix%substr(%sysfunc(ranuni(0)),3,&len);'; put '%if %sysfunc(fileref(&fname)) > 0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%return;'; put '%end;'; put '%end;'; put '%put unable to find available fileref after &maxtries attempts;'; put '%end;'; put '%mend mf_getuniquefileref;'; put '%macro mp_abort(mac=mp_abort.sas, type=, msg=, iftrue=%str(1=1)'; put ', errds=work.mp_abort_errds'; put ', mode=REGULAR'; put ')/*/STORE SOURCE*/;'; put '%global sysprocessmode sysprocessname sasjs_stpsrv_header_loc sasjsprocessmode;'; put '%local fref fid i;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%put NOTE: /// mp_abort macro executing //;'; put '%if %length(&mac)>0 %then %put NOTE- called by &mac;'; put '%put NOTE - &msg;'; put '%if %symexist(_SYSINCLUDEFILEDEVICE)'; put '/* abort cancel FILE does not restart outside the INCLUDE on Viya 3.5 */'; put 'and %superq(SYSPROCESSNAME) ne %str(Compute Server)'; put '%then %do;'; put '%if "*&_SYSINCLUDEFILEDEVICE*" ne "**" %then %do;'; put 'data &errds;'; put 'iftrue=''1=1'';'; put 'length mac $100 msg $5000;'; put 'mac=symget(''mac'');'; put 'msg=symget(''msg'');'; put 'run;'; put 'data _null_;'; put 'abort cancel FILE;'; put 'run;'; put '%return;'; put '%end;'; put '%end;'; put '/* Web App Context */'; put '%if %symexist(_PROGRAM)'; put 'or %superq(SYSPROCESSNAME) = %str(Compute Server)'; put 'or &mode=INCLUDE'; put '%then %do;'; put 'options obs=max replace mprint;'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5"'; put '%then %do;'; put 'options nosyntaxcheck;'; put '%end;'; put '%if &mode=INCLUDE %then %do;'; put '%if %sysfunc(exist(&errds))=1 %then %do;'; put 'data _null_;'; put 'set &errds;'; put 'call symputx(''iftrue'',iftrue,''l'');'; put 'call symputx(''mac'',mac,''l'');'; put 'call symputx(''msg'',msg,''l'');'; put 'putlog (_all_)(=);'; put 'run;'; put '%if (&iftrue)=0 %then %return;'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: No include errors found;'; put '%return;'; put '%end;'; put '%end;'; put '/* extract log errs / warns, if exist */'; put '%local logloc logline;'; put '%global logmsg; /* capture global messages */'; put '%if %symexist(SYSPRINTTOLOG) %then %let logloc=&SYSPRINTTOLOG;'; put '%else %let logloc=%qsysfunc(getoption(LOG));'; put 'proc printto log=log;run;'; put '%let logline=0;'; put '%if %length(&logloc)>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input; putlog _infile_;'; put 'i=1;'; put 'retain logonce 0;'; put 'if ('; put '_infile_=:"%str(WARN)ING" or _infile_=:"%str(ERR)OR"'; put ') and logonce=0 then'; put 'do;'; put 'call symputx(''logline'',_n_);'; put 'logonce+1;'; put 'end;'; put 'run;'; put '/* capture log including lines BEFORE the err */'; put '%if &logline>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input;'; put 'i=1;'; put 'stoploop=0;'; put 'if _n_ ge &logline-15 and stoploop=0 then do until (i>22);'; put 'call symputx(''logmsg'',catx(''\n'',symget(''logmsg''),_infile_));'; put 'input;'; put 'i+1;'; put 'stoploop=1;'; put 'end;'; put 'if stoploop=1 then stop;'; put 'run;'; put '%end;'; put '%end;'; put '%if %symexist(SYS_JES_JOB_URI) %then %do;'; put '/* setup webout for Viya */'; put 'options nobomfile;'; put '%if "X&SYS_JES_JOB_URI.X"="XX" %then %do;'; put 'filename _webout temp lrecl=999999 mod;'; put '%end;'; put '%else %do;'; put 'filename _webout filesrvc parenturi="&SYS_JES_JOB_URI"'; put 'name="_webout.json" lrecl=999999 mod;'; put '%end;'; put '%end;'; put '%else %if %sysfunc(filename(fref,&sasjs_stpsrv_header_loc))=0 %then %do;'; put 'options nobomfile;'; put '/* set up http header for SASjs Server */'; put '%let fid=%sysfunc(fopen(&fref,A));'; put '%if &fid=0 %then %do;'; put '%put %str(ERR)OR: %sysfunc(sysmsg());'; put '%return;'; put '%end;'; put '%let rc=%sysfunc(fput(&fid,%str(Content-Type: application/json)));'; put '%let rc=%sysfunc(fwrite(&fid));'; put '%let rc=%sysfunc(fclose(&fid));'; put '%let rc=%sysfunc(filename(&fref));'; put '%end;'; put '/* send response in SASjs JSON format */'; put 'data _null_;'; put 'file _webout mod lrecl=32000 encoding=''utf-8'';'; put 'length msg syswarningtext syserrortext $32767 mode $10 ;'; put 'sasdatetime=datetime();'; put 'msg=symget(''msg'');'; put '%if &logline>0 %then %do;'; put 'msg=cats(msg,''\n\nLog Extract:\n'',symget(''logmsg''));'; put '%end;'; put '/* escape the escapes */'; put 'msg=tranwrd(msg,''\'',''\\'');'; put '/* escape the quotes */'; put 'msg=tranwrd(msg,''"'',''\"'');'; put '/* ditch the CRLFs as chrome complains */'; put 'msg=compress(msg,,''kw'');'; put '/* quote without quoting the quotes (which are escaped instead) */'; put 'msg=cats(''"'',msg,''"'');'; put 'if symexist(''_debug'') then debug=quote(trim(symget(''_debug'')));'; put 'else debug=''""'';'; put 'if symget(''sasjsprocessmode'')=''Stored Program'' then mode=''SASJS'';'; put 'if mode ne ''SASJS'' then put ''>>weboutBEGIN<<'';'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'put '',"sasjsAbort" : [{'';'; put 'put '' "MSG":'' msg ;'; put 'put '' ,"MAC": "'' "&mac" ''"}]'';'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'put '',"_DEBUG":'' debug ;'; put 'if symexist(''_metauser'') then do;'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put 'end;'; put 'if symexist(''SYS_JES_JOB_URI'') then do;'; put 'SYS_JES_JOB_URI=quote(trim(symget(''SYS_JES_JOB_URI'')));'; put 'put '',"SYS_JES_JOB_URI": '' SYS_JES_JOB_URI;'; put 'end;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put ",""SYSWARNINGTEXT"" : " syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'put "}" ;'; put 'if mode ne ''SASJS'' then put ''>>weboutEND<<'';'; put 'run;'; put '%put _all_;'; put '%if "&sysprocessmode " = "SAS Stored Process Server " %then %do;'; put 'data _null_;'; put 'putlog ''stpsrvset program err and syscc'';'; put 'rc=stpsrvset(''program error'', 0);'; put 'call symputx("syscc",0,"g");'; put 'run;'; put '%if &sysscp=WIN'; put 'and 1=0 /* deprecating this logic until we figure out a consistent abort */'; put 'and "%substr(%str(&sysvlong ),1,8)"="9.04.01M"'; put 'and "%substr(%str(&sysvlong ),9,1)">"5" %then %do;'; put '/* skip approach (below) does not work in windows m6+ envs */'; put 'endsas;'; put '%end;'; put '%else %do;'; put '/**'; put '* endsas kills 9.4m3 deployments by orphaning multibridges.'; put '* Abort variants are ungraceful (non zero return code)'; put '* This approach lets SAS run silently until the end :-)'; put '* Caution - fails when called within a %include within a macro'; put '* Use mp_include() to handle this.'; put '*/'; put 'filename skip temp;'; put 'data _null_;'; put 'file skip;'; put 'put ''%macro skip();'';'; put 'comment ''%mend skip; -> fix lint '';'; put 'put ''%macro skippy();'';'; put 'comment ''%mend skippy; -> fix lint '';'; put 'run;'; put '%inc skip;'; put '%end;'; put '%end;'; put '%else %if "&sysprocessmode " = "SAS Compute Server " %then %do;'; put '/* endsas kills the session making it harder to fetch results */'; put 'data _null_;'; put 'syswarningtext=symget(''syswarningtext'');'; put 'syserrortext=symget(''syserrortext'');'; put 'abort_msg=symget(''msg'');'; put 'syscc=symget(''syscc'');'; put 'sysuserid=symget(''sysuserid'');'; put 'iftrue=symget(''iftrue'');'; put 'put (_all_)(/=);'; put 'call symputx(''syscc'',0);'; put 'abort cancel nolist;'; put 'run;'; put '%end;'; put '%else %do;'; put '%abort cancel;'; put '%end;'; put '%end;'; put '%else %do;'; put '%put _all_;'; put '%abort cancel;'; put '%end;'; put '%mend mp_abort;'; put '/** @endcond */'; put '%macro mm_getstpcode('; put 'tree=/User Folders/sasdemo/somestp'; put ',name='; put ',outloc=0'; put ',outref=0'; put ',mDebug=1'; put ',showlog=NO'; put ');'; put '%local mD;'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing &sysmacroname..sas;'; put '%&mD.put _local_;'; put '%if %length(&name)>0 %then %let name=/&name;'; put '/* first, check if STP exists */'; put '%local tsuri;'; put '%let tsuri=stopifempty ;'; put 'data _null_;'; put 'format type uri tsuri value $200.;'; put 'call missing (of _all_);'; put 'path="&tree&name(StoredProcess)";'; put '/* first, find the STP ID */'; put 'if metadata_pathobj("",path,"StoredProcess",type,uri)>0 then do;'; put '/* get sourcecode */'; put 'cnt=1;'; put 'do while (metadata_getnasn(uri,"Notes",cnt,tsuri)>0);'; put 'rc=metadata_getattr(tsuri,"Name",value);'; put '&mD.put tsuri= value=;'; put 'if value="SourceCode" then do;'; put '/* found it! */'; put 'rc=metadata_getattr(tsuri,"Id",value);'; put 'call symputx(''tsuri'',value,''l'');'; put 'stop;'; put 'end;'; put 'cnt+1;'; put 'end;'; put 'end;'; put 'else put (_all_)(=);'; put 'run;'; put '%mp_abort(iftrue= (&tsuri=stopifempty)'; put ',mac=mm_getstpcode'; put ',msg=%str(&tree&name.(StoredProcess) not found!)'; put ')'; put '/**'; put '* Now we can extract the textstore'; put '*/'; put 'filename __getdoc temp lrecl=10000000;'; put 'proc metadata'; put 'in="$METAREPOSITORY'; put ''; put 'SAS1"'; put 'out=__getdoc ;'; put 'run;'; put '/* find the beginning of the text */'; put '%local start;'; put 'data _null_;'; put 'infile __getdoc lrecl=10000;'; put 'input;'; put 'start=index(_infile_,''StoredText="'');'; put 'if start then do;'; put 'call symputx("start",start+11);'; put '*putlog ''"'' _infile_ ''"'';'; put 'end;'; put 'stop;'; put '%local outeng;'; put '%if "&outloc"="0" %then %let outeng=TEMP;'; put '%else %let outeng="&outloc";'; put '%local fref;'; put '%if &outref=0 %then %let fref=%mf_getuniquefileref();'; put '%else %let fref=&outref;'; put '/* read the content, byte by byte, resolving escaped chars */'; put 'filename &fref &outeng lrecl=100000;'; put 'data _null_;'; put 'length filein 8 fileid 8;'; put 'filein = fopen("__getdoc","I",1,"B");'; put 'fileid = fopen("&fref","O",1,"B");'; put 'rec = "20"x;'; put 'length entity $6;'; put 'do while(fread(filein)=0);'; put 'x+1;'; put 'if x>&start then do;'; put 'rc = fget(filein,rec,1);'; put 'if rec=''"'' then leave;'; put 'else if rec="&" then do;'; put 'entity=rec;'; put 'do until (rec=";");'; put 'if fread(filein) ne 0 then goto getout;'; put 'rc = fget(filein,rec,1);'; put 'entity=cats(entity,rec);'; put 'end;'; put 'select (entity);'; put 'when (''&'' ) rec=''&'' ;'; put 'when (''<'' ) rec=''<'' ;'; put 'when (''>'' ) rec=''>'' ;'; put 'when (''''') rec="''" ;'; put 'when (''"'') rec=''"'' ;'; put 'when ('' '') rec=''0A''x;'; put 'when ('' '') rec=''0D''x;'; put 'when (''$'' ) rec=''$'' ;'; put 'when ('' '') rec=''09''x;'; put 'otherwise putlog "%str(WARN)ING: missing value for " entity=;'; put 'end;'; put 'rc =fput(fileid, substr(rec,1,1));'; put 'rc =fwrite(fileid);'; put 'end;'; put 'else do;'; put 'rc =fput(fileid,rec);'; put 'rc =fwrite(fileid);'; put 'end;'; put 'end;'; put 'end;'; put 'getout:'; put 'rc=fclose(filein);'; put 'rc=fclose(fileid);'; put 'run;'; put '%if &showlog=YES %then %do;'; put 'data _null_;'; put 'infile &fref lrecl=32767 end=last;'; put 'input;'; put 'if _n_=1 then putlog ''>>stpcodeBEGIN<<'';'; put 'putlog _infile_;'; put 'if last then putlog ''>>stpcodeEND<<'';'; put 'run;'; put '%end;'; put 'filename __getdoc clear;'; put '%if &outref=0 %then %do;'; put 'filename &fref clear;'; put '%end;'; put '%mend mm_getstpcode;'; put '%macro dc_getsettings();'; put '%global _program;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(syscc=&syscc on entry (&syswarningtext &syserrortext))'; put ')'; put '%if %length(&_PROGRAM)>1 %then %let root=&_program;'; put '%else %do;'; put '%global _metauser;'; put '%let _metauser=&sysuserid;'; put '/* to mimic a "real" _program we need to give a dummy role and stp name */'; put '%let root=/dummyRole/dummyName;'; put '%end;'; put '/* the DC precode is stored in the Admin folder in the root of'; put 'the project. Lets find that root. */'; put '%put &=root;'; put '%let root=%mf_getapploc();'; put '%put &=root;'; put '/* Now we know the root location we can retrieve the params */'; put '%let temploc=%sysfunc(pathname(work))/settings.txt;'; put '%mm_getstpcode(tree=&root/services/public'; put ',name=Data_Controller_Settings'; put ',outloc=&temploc'; put ')'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Unable to run getstpcode)'; put ')'; put 'filename _getsets "&temploc" lrecl=2000;'; put '/*'; put 'Do not use mp_include here - this puts a copy in every service, which creates'; put 'compilation problems when calling services from mp_include'; put '*/'; put '%inc _getsets/source2;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Problem running &sysmacroname (&syswarningtext &syserrortext))'; put ')'; put '%mend dc_getsettings;'; put '%macro mf_fmtdttm('; put ')/*/STORE SOURCE*/;'; put '%if "&sysver"="9.2" or "&sysver"="9.3"'; put 'or ("&sysver"="9.4" and "%substr(&SYSVLONG,9,1)" le "3")'; put 'or "%substr(&sysver,1,1)"="4"'; put 'or "%substr(&sysver,1,1)"="5"'; put '%then %do;DATETIME19.3%end;'; put '%else %do;E8601DT26.6%end;'; put '%mend mf_fmtdttm;'; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '%macro mp_init(prefix=SASJS'; put ')/*/STORE SOURCE*/;'; put '%if %symexist(SASJS_PREFIX) %then %return; /* only run once */'; put '%global'; put 'SASJS_PREFIX /* the ONLY hard-coded global macro variable in SASjs */'; put '&prefix._FUNCTIONS /* used in mcf_init() to track core function compilation */'; put '&prefix._INIT_NUM /* initialisation time as numeric */'; put '&prefix._INIT_DTTM /* initialisation time in E8601DT26.6 format */'; put '&prefix.WORK /* avoid typing %sysfunc(pathname(work)) every time */'; put ';'; put '%let sasjs_prefix=&prefix;'; put 'data _null_;'; put 'dttm=datetime();'; put 'call symputx("&sasjs_prefix._init_num",dttm,''g'');'; put 'call symputx("&sasjs_prefix._init_dttm",put(dttm,E8601DT26.6),''g'');'; put 'call symputx("&sasjs_prefix.work",pathname(''WORK''),''g'');'; put 'run;'; put 'options'; put 'compress=CHAR /* default is none so ensure we have something! */'; put 'datastmtchk=ALLKEYWORDS /* protection from overwriting input datasets */'; put 'errorcheck=STRICT /* catch errs in libname/filename statements */'; put 'fmterr /* ensure err when a format cannot be found */'; put 'mergenoby=%str(ERR)OR /* throw err when a merge has no BY variables */'; put 'missing=. /* changing this can cause hard to detect errs */'; put 'noquotelenmax /* avoid warnings for long strings */'; put 'noreplace /* avoid overwriting permanent datasets */'; put 'ps=max /* reduce log size slightly */'; put 'ls=max /* reduce log even more and avoid word truncation */'; put 'validmemname=COMPATIBLE /* avoid special characters etc in table names */'; put 'validvarname=V7 /* avoid special characters etc in variable names */'; put 'varinitchk=%str(ERR)OR /* avoid data mistakes from variable name typos */'; put 'varlenchk=%str(ERR)OR /* fail hard if truncation (data loss) can result */'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5" %then %do;'; put 'noautocorrect /* disallow misspelled procedure names */'; put 'dsoptions=note2err /* undocumented - convert bad NOTEs to ERRs */'; put '%end;'; put ';'; put '%mend mp_init;'; put '%macro mpeinit(fetch=YES);'; put '%global mpeinit'; put 'mpeadmins /* group with unrestricted Meditor access */'; put 'mpelocapprovals /* location for landing and staging files */'; put 'mpelib /* location of configuration tables for DC */'; put 'dc_repo_users /* location of user / group metadata */'; put 'dc_licence_key /* extracted in dc_getsettings */'; put 'dc_activation_key /* extracted in dc_getsettings */'; put 'dc_locale /* extracted in dc_getsettings */'; put 'dc_dttmtfmt /* can be overridden in dc_getsettings */'; put '_debug'; put ';'; put '%if &mpeinit=1 %then %return;'; put '%else %let mpeinit=1;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(Problem on service startup (&syswarningtext &syserrortext))'; put ')'; put '%mp_init()'; put '%if &fetch=YES %then %do;'; put '%webout(FETCH)'; put '%end;'; put '%global _CLIENTNAME;'; put '%mp_abort(iftrue= (&_CLIENTNAME=SAS Enterprise Guide)'; put ',mac=&_program..sas'; put ',msg=%str(Data Controller is a web app and should not be executed from EG)'; put ')'; put 'options urlencoding=utf8 nobomfile lrecl=32767;'; put '%let perf=%sysfunc(datetime());'; put '%put perfdiff: 0;'; put '%let dc_locale=SYSTEM; /* default if not set */'; put '/**'; put '* E8601DT26.6 has widest database support - but not all SAS flavours can'; put '* handle it. Override in the settings STP if needed.'; put '*/'; put 'data _null_;'; put 'dc_dttmtfmt=''"%sysfunc(datetime(),''!!"%mf_fmtdttm()"!!'')"dt'';'; put 'call symputx(''dc_dttmtfmt'',dc_dttmtfmt);'; put 'put dc_dttmtfmt=;'; put 'run;'; put '%put &=dc_dttmtfmt;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc prior to dc_getsettings)'; put ')'; put '%dc_getsettings()'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc after dc_getsettings)'; put ')'; put 'data _null_;'; put 'set &DC_LIBREF..mpe_config(where=('; put 'var_scope="DC"'; put 'and &dc_dttmtfmt lt tx_to'; put 'and var_active=1'; put '));'; put 'call symputx(var_name,var_value,''G'');'; put 'putlog var_name "=" var_value;'; put 'run;'; put '%let mpelib=&dc_libref;'; put '%let mpeadmins=&dc_admin_group;'; put '%let mpelocapprovals=&dc_staging_area;'; put '%let dc_repo_users=&dc_repo_users;'; put '%if &dc_locale ne SYSTEM %then %do;'; put 'options locale=&dc_locale;'; put '%end;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(Problem during compilation or with STP precode (&syswarningtext))'; put ')'; put '%mend mpeinit;'; put '%macro mf_mval(var);'; put '%if %symexist(&var) %then %do;'; put '%superq(&var)'; put '%end;'; put '%mend mf_mval;'; put '%macro mf_trimstr(basestr,trimstr);'; put '%local baselen trimlen trimval;'; put '/* return if basestr is shorter than trimstr (or 0) */'; put '%let baselen=%length(%superq(basestr));'; put '%let trimlen=%length(%superq(trimstr));'; put '%if &baselen < &trimlen or &baselen=0 %then %return;'; put '/* obtain the characters from the end of basestr */'; put '%let trimval=%qsubstr(%superq(basestr)'; put ',%length(%superq(basestr))-&trimlen+1'; put ',&trimlen);'; put '/* compare and if matching, chop it off! */'; put '%if %superq(basestr)=%superq(trimstr) %then %do;'; put '%return;'; put '%end;'; put '%else %if %superq(trimval)=%superq(trimstr) %then %do;'; put '%qsubstr(%superq(basestr),1,%length(%superq(basestr))-&trimlen)'; put '%end;'; put '%else %do;'; put '&basestr'; put '%end;'; put '%mend mf_trimstr;'; put '%macro mf_getplatform(switch'; put ')/*/STORE SOURCE*/;'; put '%local a b c;'; put '%if &switch.NONE=NONE %then %do;'; put '%if %symexist(sasjsprocessmode) %then %do;'; put '%if &sasjsprocessmode=Stored Program %then %do;'; put 'SASJS'; put '%return;'; put '%end;'; put '%end;'; put '%if %symexist(sysprocessmode) %then %do;'; put '%if "&sysprocessmode"="SAS Object Server"'; put 'or "&sysprocessmode"= "SAS Compute Server" %then %do;'; put 'SASVIYA'; put '%end;'; put '%else %if "&sysprocessmode"="SAS Stored Process Server"'; put 'or "&sysprocessmode"="SAS Workspace Server"'; put '%then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if %symexist(_metaport) or %symexist(_metauser) %then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if &switch=SASSTUDIO %then %do;'; put '/* return the version of SAS Studio else 0 */'; put '%if %mf_mval(_CLIENTAPP)=%str(SAS Studio) %then %do;'; put '%let a=%mf_mval(_CLIENTVERSION);'; put '%let b=%scan(&a,1,.);'; put '%if %eval(&b >2) %then %do;'; put '&b'; put '%end;'; put '%else 0;'; put '%end;'; put '%else 0;'; put '%end;'; put '%else %if &switch=VIYARESTAPI %then %do;'; put '%mf_trimstr(%sysfunc(getoption(servicesbaseurl)),/)'; put '%end;'; put '%mend mf_getplatform;'; put '%macro mpeterm();'; put '%local oldloc;'; put 'data _null_;'; put 'if symexist(''SYSPRINTTOLOG'') then oldloc=symget(''SYSPRINTTOLOG'');'; put 'else oldloc=getoption(''LOG'');'; put 'if subpad(oldloc,1,1) not in (''"'',"''",'' '') then oldloc=quote(cats(oldloc));'; put 'call symputx(''oldloc'',oldloc,''l'');'; put 'run;'; put '%if %length(&oldloc)>0 %then %do;'; put 'proc printto log=log;'; put 'run;'; put 'data _null_;'; put 'infile &oldloc;'; put 'input; putlog _infile_;'; put 'run;'; put '%end;'; put '%if %sysfunc(exist(&dc_libref..mpe_requests)) and %mf_getplatform() ne SASVIYA'; put '%then %do;'; put 'data ;'; put 'if 0 then set &dc_libref..mpe_requests;'; put 'request_dttm=%sysfunc(datetime());'; put 'request_user="%mf_getuser()";'; put 'request_service="%scan(&_program,-2,/)/%scan(&_program,-1,/)";'; put 'request_params='''';'; put 'output;stop;'; put 'proc append base=&dc_libref..mpe_requests data=&syslast force nowarn;'; put 'run;'; put '%end;'; put '%mend mpeterm;'; put '* SAS Macros end;'; put '* SAS Includes start;'; put '* SAS Includes end;'; put '* Binary Files start;'; put '* Binary Files end;'; put '* ServiceInit start;'; put 'options noquotelenmax ps=max;'; put '/* create dummy macros to prevent stpbegin / stpend from corrupting sessions */'; put '%macro stpbegin();'; put '%put NOTE: the STPBEGIN macro should not be used for web apps!;'; put '%mend stpbegin;'; put '%macro stpend();'; put '%put NOTE: the STPEND macro should not be used for web apps!;'; put '%mend stpend;'; put '* ServiceInit end;'; put '* Service start;'; put '/**'; put '@file'; put '@brief Post Edit Hook script for the MPE_SECURITY table'; put '@details Post edit hooks provide additional backend validation against'; put 'user-sourced data. The incoming dataset is always `work.staging_ds` and this'; put 'file is included from the mpe_loader.sas macro.'; put 'Available (at runtime) macro variables:'; put '@li DC_LIBREF - The DC control library for your site'; put '@li LIBREF - The library of the dataset being edited (is assigned)'; put '@li DS - The dataset being edited'; put '**/'; put '/* ensure upcase and check access level values*/'; put '%let errval=0;'; put '%let errmsg=;'; put 'data work.staging_ds;'; put 'set work.staging_ds;'; put 'LIBREF=upcase(LIBREF);'; put 'DSN=upcase(DSN);'; put 'ACCESS_LEVEL=upcase(ACCESS_LEVEL);'; put 'if ACCESS_LEVEL not in (''EDIT'',''APPROVE'',''VIEW'',''SIGNOFF'',''AUDIT'') then do;'; put 'putlog "ERR" +(-1) "OR: invalid ACCESS_LEVEL - " access_level;'; put 'call symputx(''errval'',1);'; put 'call symputx(''errmsg'',"Invalid ACCESS_LEVEL: "!!access_level);'; put 'end;'; put 'run;'; put '%mp_abort(iftrue=(&errval=1)'; put ',mac=mpe_security_postedit.sas'; put ',msg=%str(&errmsg)'; put ')'; put '* Service end;'; run; %mm_createwebservice(path=&appLoc/&path, name=&service, code=sascode, server=&serverName, replace=yes) filename sascode clear; %let service=mpe_tables_postedit; filename sascode temp lrecl=32767; data _null_; file sascode; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '/**'; put '@file mp_jsonout.sas'; put '@brief Writes JSON in SASjs format to a fileref'; put '@details This macro can be used to OPEN a JSON stream and send one or more'; put 'tables as arrays of rows, where each row can be an object or a nested array.'; put 'There are two engines available - DATASTEP or PROCJSON.'; put 'PROC JSON is fast but will produce errs like the ones below if'; put 'special chars are encountered.'; put '> (ERR)OR: Some code points did not transcode.'; put '> An object or array close is not valid at this point in the JSON text.'; put '> Date value out of range'; put 'If this happens, try running with ENGINE=DATASTEP.'; put 'The DATASTEP engine is used to handle special SAS missing numerics, and'; put 'can also convert entire datasets to formatted values. Output JSON is always'; put 'in UTF-8.'; put 'Usage:'; put 'filename tmp temp;'; put 'data class; set sashelp.class;run;'; put '%mp_jsonout(OPEN,jref=tmp)'; put '%mp_jsonout(OBJ,class,jref=tmp)'; put '%mp_jsonout(OBJ,class,dslabel=class2,jref=tmp,showmeta=Y)'; put '%mp_jsonout(CLOSE,jref=tmp)'; put 'data _null_;'; put 'infile tmp;'; put 'input;putlog _infile_;'; put 'run;'; put 'If you are building web apps with SAS then you are strongly encouraged to use'; put 'the mX_createwebservice macros in combination with the'; put '[sasjs adapter](https://github.com/sasjs/adapter).'; put 'For more information see https://sasjs.io'; put '@param [in] action Valid values:'; put '@li OPEN - opens the JSON'; put '@li OBJ - sends a table with each row as an object'; put '@li ARR - sends a table with each row in an array'; put '@li CLOSE - closes the JSON'; put '@param [in] ds The dataset to send. Must be a work table.'; put '@param [out] jref= (_webout) The fileref to which to send the JSON'; put '@param [out] dslabel= The name to give the table in the exported JSON'; put '@param [in] fmt= (Y) Whether to keep (Y) or strip (N) formats from the table'; put '@param [in] engine= (DATASTEP) Which engine to use to send the JSON. Options:'; put '@li PROCJSON (default)'; put '@li DATASTEP (more reliable when data has non standard characters)'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to JSON'; put '

Related Files

'; put '@li mp_ds2fmtds.sas'; put '@version 9.2'; put '@author Allan Bowe'; put '@source https://github.com/sasjs/core'; put '**/'; put '%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y'; put ',engine=DATASTEP'; put ',missing=NULL'; put ',showmeta=N'; put ',maxobs=MAX'; put ')/*/STORE SOURCE*/;'; put '%local tempds colinfo fmtds i numcols numobs stmt_obs lastobs optval'; put 'tmpds1 tmpds2 tmpds3 tmpds4;'; put '%let numcols=0;'; put '%if &maxobs ne MAX %then %let stmt_obs=%str(if _n_>&maxobs then stop;);'; put '%if &action=OPEN %then %do;'; put 'options nobomfile;'; put 'data _null_;file &jref encoding=''utf-8'' lrecl=200;'; put 'put ''{"PROCESSED_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''"'';'; put 'run;'; put '%end;'; put '%else %if (&action=ARR or &action=OBJ) %then %do;'; put '/* force variable names to always be uppercase in the JSON */'; put 'options validvarname=upcase;'; put '/* To avoid issues with _webout on EBI - such as encoding diffs and truncation'; put '(https://support.sas.com/kb/49/325.html) we use temporary files */'; put 'filename _sjs1 temp lrecl=200 ;'; put 'data _null_; file _sjs1 encoding=''utf-8'';'; put 'put ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":";'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjs1 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs1 clear;'; put '/* grab col defs */'; put 'proc contents noprint data=&ds'; put 'out=_data_(keep=name type length format formatl formatd varnum label);'; put 'run;'; put '%let colinfo=%scan(&syslast,2,.);'; put 'proc sort data=&colinfo;'; put 'by varnum;'; put 'run;'; put '/* move meta to mac vars */'; put 'data &colinfo;'; put 'if _n_=1 then call symputx(''numcols'',nobs,''l'');'; put 'set &colinfo end=last nobs=nobs;'; put 'name=upcase(name);'; put '/* fix formats */'; put 'if type=2 or type=6 then do;'; put 'typelong=''char'';'; put 'length fmt $49.;'; put 'if format='''' then fmt=cats(''$'',length,''.'');'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else fmt=cats(format,formatl,''.'');'; put 'end;'; put 'else do;'; put 'typelong=''num'';'; put 'if format='''' then fmt=''best.'';'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else if formatd=0 then fmt=cats(format,formatl,''.'');'; put 'else fmt=cats(format,formatl,''.'',formatd);'; put 'end;'; put '/* 32 char unique name */'; put 'newname=''sasjs''!!substr(cats(put(md5(name),$hex32.)),1,27);'; put 'call symputx(cats(''name'',_n_),name,''l'');'; put 'call symputx(cats(''newname'',_n_),newname,''l'');'; put 'call symputx(cats(''length'',_n_),length,''l'');'; put 'call symputx(cats(''fmt'',_n_),fmt,''l'');'; put 'call symputx(cats(''type'',_n_),type,''l'');'; put 'call symputx(cats(''typelong'',_n_),typelong,''l'');'; put 'call symputx(cats(''label'',_n_),coalescec(label,name),''l'');'; put '/* overwritten when fmt=Y and a custom format exists in catalog */'; put 'if typelong=''num'' then call symputx(cats(''fmtlen'',_n_),200,''l'');'; put 'else call symputx(cats(''fmtlen'',_n_),min(32767,ceil((length+10)*1.5)),''l'');'; put 'run;'; put '%let tempds=%substr(_%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql;'; put 'select count(*) into: lastobs from &ds;'; put '%if &maxobs ne MAX %then %let lastobs=%sysfunc(min(&lastobs,&maxobs));'; put '%if &engine=PROCJSON %then %do;'; put '%if &missing=STRING %then %do;'; put '%put &sysmacroname: Special Missings not supported in proc json.;'; put '%put &sysmacroname: Switching to DATASTEP engine;'; put '%goto datastep;'; put '%end;'; put 'data &tempds;'; put 'set &ds;'; put '&stmt_obs;'; put '%if &fmt=N %then format _numeric_ best32.;;'; put '/* PRETTY is necessary to avoid line truncation in large files */'; put 'filename _sjs2 temp lrecl=131068 encoding=''utf-8'';'; put 'proc json out=_sjs2 pretty'; put '%if &action=ARR %then nokeys ;'; put ';export &tempds / nosastags fmtnumeric;'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs2 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs2 clear;'; put '%end;'; put '%else %if &engine=DATASTEP %then %do;'; put '%datastep:'; put '%if %sysfunc(exist(&ds)) ne 1 & %sysfunc(exist(&ds,VIEW)) ne 1'; put '%then %do;'; put '%put &sysmacroname: &ds NOT FOUND!!!;'; put '%return;'; put '%end;'; put '%if &fmt=Y %then %do;'; put '/**'; put '* Extract format definitions'; put '* First, by getting library locations from dictionary.formats'; put '* Then, by exporting the width using proc format'; put '* Cannot use maxw from sashelp.vformat as not always populated'; put '* Cannot use fmtinfo() as not supported in all flavours'; put '*/'; put '%let tmpds1=%substr(fmtsum%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds2=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds3=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds4=%substr(col%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql noprint;'; put 'create table &tmpds1 as'; put 'select cats(libname,''.'',memname) as FMTCAT,'; put 'FMTNAME'; put 'from dictionary.formats'; put 'where fmttype=''F'' and libname is not null'; put 'and fmtname in (select format from &colinfo where format is not null)'; put 'order by 1;'; put 'create table &tmpds2('; put 'FMTNAME char(32),'; put 'LENGTH num'; put ');'; put '%local catlist cat fmtlist i;'; put 'select distinct fmtcat into: catlist separated by '' '' from &tmpds1;'; put '%do i=1 %to %sysfunc(countw(&catlist,%str( )));'; put '%let cat=%scan(&catlist,&i,%str( ));'; put 'proc sql;'; put 'select distinct fmtname into: fmtlist separated by '' '''; put 'from &tmpds1 where fmtcat="&cat";'; put 'proc format lib=&cat cntlout=&tmpds3(keep=fmtname length);'; put 'select &fmtlist;'; put 'run;'; put 'proc sql;'; put 'insert into &tmpds2 select distinct fmtname,length from &tmpds3;'; put '%end;'; put 'proc sql;'; put 'create table &tmpds4 as'; put 'select a.*, b.length as MAXW'; put 'from &colinfo a'; put 'left join &tmpds2 b'; put 'on cats(a.format)=cats(upcase(b.fmtname))'; put 'order by a.varnum;'; put 'data _null_;'; put 'set &tmpds4;'; put 'if not missing(maxw);'; put 'call symputx('; put 'cats(''fmtlen'',_n_),'; put '/* vars need extra padding due to JSON escaping of special chars */'; put 'min(32767,ceil((max(length,maxw)+10)*1.5))'; put ',''l'''; put ');'; put 'run;'; put '/* configure varlenchk - as we are explicitly shortening the variables */'; put '%let optval=%sysfunc(getoption(varlenchk));'; put 'options varlenchk=NOWARN;'; put 'data _data_(compress=char);'; put '/* shorten the new vars */'; put 'length'; put '%do i=1 %to &numcols;'; put '&&name&i $&&fmtlen&i'; put '%end;'; put ';'; put '/* rename on entry */'; put 'set &ds(rename=('; put '%do i=1 %to &numcols;'; put '&&name&i=&&newname&i'; put '%end;'; put '));'; put '&stmt_obs;'; put 'drop'; put '%do i=1 %to &numcols;'; put '&&newname&i'; put '%end;'; put ';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=num %then %do;'; put '&&name&i=cats(put(&&newname&i,&&fmt&i));'; put '%end;'; put '%else %do;'; put '&&name&i=put(&&newname&i,&&fmt&i);'; put '%end;'; put '%end;'; put 'if _error_ then do;'; put 'call symputx(''syscc'',1012);'; put 'stop;'; put 'end;'; put 'run;'; put '%let fmtds=&syslast;'; put 'options varlenchk=&optval;'; put '%end;'; put 'proc format; /* credit yabwon for special null removal */'; put 'value bart (default=40)'; put '%if &missing=NULL %then %do;'; put '._ - .z = null'; put '%end;'; put '%else %do;'; put '._ = [quote()]'; put '. = null'; put '.a - .z = [quote()]'; put '%end;'; put 'other = [best.];'; put 'data &tempds;'; put 'attrib _all_ label='''';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'length &&name&i $&&fmtlen&i...;'; put 'format &&name&i $&&fmtlen&i...;'; put '%end;'; put '%end;'; put '%if &fmt=Y %then %do;'; put 'set &fmtds;'; put '%end;'; put '%else %do;'; put 'set &ds;'; put '%end;'; put '&stmt_obs;'; put 'format _numeric_ bart.;'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'if findc(&&name&i,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put '&&name&i=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,&&name&i)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else &&name&i=quote(cats(&&name&i));'; put '%end;'; put '%end;'; put 'run;'; put 'filename _sjs3 temp lrecl=131068 ;'; put 'data _null_;'; put 'file _sjs3 encoding=''utf-8'';'; put 'if _n_=1 then put "[";'; put 'set &tempds;'; put 'if _n_>1 then put "," @; put'; put '%if &action=ARR %then "[" ; %else "{" ;'; put '%do i=1 %to &numcols;'; put '%if &i>1 %then "," ;'; put '%if &action=OBJ %then """&&name&i"":" ;'; put '"&&name&i"n /* name literal for reserved variable names */'; put '%end;'; put '%if &action=ARR %then "]" ; %else "}" ; ;'; put '/* close out the table */'; put 'data _null_;'; put 'file _sjs3 mod encoding=''utf-8'';'; put 'put '']'';'; put 'run;'; put 'data _null_;'; put 'infile _sjs3 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs3 clear;'; put '%end;'; put 'proc sql;'; put 'drop table &colinfo, &tempds;'; put '%if %substr(&showmeta,1,1)=Y %then %do;'; put 'filename _sjs4 temp lrecl=131068 encoding=''utf-8'';'; put 'data _null_;'; put 'file _sjs4;'; put 'length label $350;'; put 'put ", ""$%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":{""vars"":{";'; put 'do i=1 to &numcols;'; put 'name=quote(trim(symget(cats(''name'',i))));'; put 'format=quote(trim(symget(cats(''fmt'',i))));'; put 'label=quote(prxchange(''s/\\/\\\\/'',-1,trim(symget(cats(''label'',i)))));'; put 'length=quote(trim(symget(cats(''length'',i))));'; put 'type=quote(trim(symget(cats(''typelong'',i))));'; put 'if i>1 then put "," @@;'; put 'put name '':{"format":'' format '',"label":'' label'; put ''',"length":'' length '',"type":'' type ''}'';'; put 'end;'; put 'put ''}}'';'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs4 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs4 clear;'; put '%end;'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put 'data _null_; file &jref encoding=''utf-8'' mod ;'; put 'put "}";'; put 'run;'; put '%end;'; put '%mend mp_jsonout;'; put '/**'; put '@file mm_webout.sas'; put '@brief Send data to/from SAS Stored Processes'; put '@details This macro should be added to the start of each Stored Process,'; put '**immediately** followed by a call to:'; put '%mm_webout(FETCH)'; put 'This will read all the input data and create same-named SAS datasets in the'; put 'WORK library. You can then insert your code, and send data back using the'; put 'following syntax:'; put 'data some datasets; * make some data ;'; put 'retain some columns;'; put 'run;'; put '%mm_webout(OPEN)'; put '%mm_webout(ARR,some) * Array format, fast, suitable for large tables ;'; put '%mm_webout(OBJ,datasets) * Object format, easier to work with ;'; put 'Finally, wrap everything up send some helpful system variables too'; put '%mm_webout(CLOSE)'; put '@param [in] action Either FETCH, OPEN, ARR, OBJ or CLOSE'; put '@param [in] ds The dataset to send back to the frontend'; put '@param [out] dslabel= Value to use instead of table name for sending to JSON'; put '@param [in] fmt= (N) Setting Y converts all vars to their formatted values'; put '@param [out] fref= (_webout) The fileref to which to write the JSON'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] workobs= (0) When set to a positive integer, will create a new'; put 'output object (WORK) which contains this number of observations from all'; put 'tables in the WORK library.'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to output JSON'; put '

SAS Macros

'; put '@li mp_jsonout.sas'; put '

Related Macros

'; put '@li ms_webout.sas'; put '@li mv_webout.sas'; put '@version 9.3'; put '@author Allan Bowe'; put '**/'; put '%macro mm_webout(action,ds,dslabel=,fref=_webout,fmt=N,missing=NULL'; put ',showmeta=N,maxobs=MAX,workobs=0'; put ');'; put '%global _webin_file_count _webin_fileref1 _webin_name1 _program _debug'; put 'sasjs_tables;'; put '%local i tempds jsonengine;'; put '/* see https://github.com/sasjs/core/issues/41 */'; put '%if "%upcase(&SYSENCODING)" ne "UTF-8" %then %let jsonengine=PROCJSON;'; put '%else %let jsonengine=DATASTEP;'; put '%if &action=FETCH %then %do;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'options mprint notes mprintnest;'; put '%end;'; put '%let _webin_file_count=%eval(&_webin_file_count+0);'; put '/* now read in the data */'; put '%do i=1 %to &_webin_file_count;'; put '%if &_webin_file_count=1 %then %do;'; put '%let _webin_fileref1=&_webin_fileref;'; put '%let _webin_name1=&_webin_name;'; put '%end;'; put 'data _null_;'; put 'infile &&_webin_fileref&i termstr=crlf;'; put 'input;'; put 'call symputx(''input_statement'',_infile_);'; put 'putlog "&&_webin_name&i input statement: " _infile_;'; put 'stop;'; put 'data &&_webin_name&i;'; put 'infile &&_webin_fileref&i firstobs=2 dsd termstr=crlf encoding=''utf-8'';'; put 'input &input_statement;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'if _n_<20 then putlog _infile_;'; put '%end;'; put 'run;'; put '%let sasjs_tables=&sasjs_tables &&_webin_name&i;'; put '%end;'; put '%end;'; put '%else %if &action=OPEN %then %do;'; put '/* fix encoding */'; put 'OPTIONS NOBOMFILE;'; put '/**'; put '* check xengine type to avoid the below err message:'; put '* > Function is only valid for filerefs using the CACHE access method.'; put '*/'; put 'data _null_;'; put 'set sashelp.vextfl(where=(fileref="_WEBOUT"));'; put 'if xengine=''STREAM'' then do;'; put 'rc=stpsrv_header(''Content-type'',"text/html; encoding=utf-8");'; put 'end;'; put 'run;'; put '/* setup json */'; put 'data _null_;file &fref encoding=''utf-8'';'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutBEGIN<<'';'; put '%end;'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'run;'; put '%end;'; put '%else %if &action=ARR or &action=OBJ %then %do;'; put '%mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt,jref=&fref'; put ',engine=&jsonengine,missing=&missing,showmeta=&showmeta,maxobs=&maxobs'; put ')'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put '/* To avoid issues with _webout on EBI we use a temporary file */'; put 'filename _sjsref temp lrecl=131068;'; put '%if %str(&workobs) > 0 %then %do;'; put '/* if debug mode, send back first XX records of each work table also */'; put 'data;run;%let tempds=%scan(&syslast,2,.);'; put 'ods output Members=&tempds;'; put 'proc datasets library=WORK memtype=data;'; put '%local wtcnt;%let wtcnt=0;'; put 'data _null_;'; put 'set &tempds;'; put 'if not (upcase(name) =:"DATA"); /* ignore temp datasets */'; put 'i+1;'; put 'call symputx(cats(''wt'',i),name,''l'');'; put 'call symputx(''wtcnt'',i,''l'');'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put ",""WORK"":{";'; put '%do i=1 %to &wtcnt;'; put '%let wt=&&wt&i;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'dsid=open("WORK.&wt",''is'');'; put 'nlobs=attrn(dsid,''NLOBS'');'; put 'nvars=attrn(dsid,''NVARS'');'; put 'rc=close(dsid);'; put 'if &i>1 then put '',''@;'; put 'put " ""&wt"" : {";'; put 'put ''"nlobs":'' nlobs;'; put 'put '',"nvars":'' nvars;'; put '%mp_jsonout(OBJ,&wt,jref=_sjsref,dslabel=first10rows,showmeta=Y'; put ',maxobs=&workobs'; put ')'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put '%end;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put 'run;'; put '%end;'; put '/* close off json */'; put 'data _null_;file _sjsref mod encoding=''utf-8'';'; put 'length SYSPROCESSNAME syserrortext syswarningtext autoexec $512;'; put 'put ",""_DEBUG"" : ""&_debug"" ";'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'autoexec=quote(urlencode(trim(getoption(''autoexec''))));'; put 'put '',"AUTOEXEC" : '' autoexec;'; put 'put ",""MF_GETUSER"" : ""%mf_getuser()"" ";'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'put ",""SYSENCODING"" : ""&sysencoding"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSPROCESSID"" : ""&SYSPROCESSID"" ";'; put 'put ",""SYSPROCESSMODE"" : ""&SYSPROCESSMODE"" ";'; put 'SYSPROCESSNAME=quote(urlencode(cats(SYSPROCESSNAME)));'; put 'put ",""SYSPROCESSNAME"" : " SYSPROCESSNAME;'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put '',"SYSWARNINGTEXT" : '' syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'length memsize $32;'; put 'memsize="%sysfunc(INPUTN(%sysfunc(getoption(memsize)), best.),sizekmg.)";'; put 'memsize=quote(cats(memsize));'; put 'put '',"MEMSIZE" : '' memsize;'; put 'put "}" @;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutEND<<'';'; put '%end;'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjsref lrecl=1 recfm=n;'; put 'file &fref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjsref clear;'; put '%end;'; put '%mend mm_webout;'; put '%macro webout(action,ds,dslabel=,fmt=,missing=NULL,showmeta=NO,maxobs=MAX);'; put '%mm_webout(&action,ds=&ds,dslabel=&dslabel,fmt=&fmt'; put ',missing=&missing'; put ',showmeta=&showmeta'; put ',maxobs=&maxobs'; put ') %mend;'; put '/* provide additional debug info */'; put '%global _program;'; put '%put &=syscc;'; put '%put user=%mf_getuser();'; put '%put pgm=&_program;'; put '%put timestamp=%sysfunc(datetime(),datetime19.);'; put '* Service Variables start;'; put '* Service Variables end;'; put '* SAS Macros start;'; put '%macro mf_getapploc(pgm);'; put '%if "&pgm"="" %then %do;'; put '%if %symexist(_program) %then %let pgm=&_program;'; put '%else %do;'; put '%put &sysmacroname: No value provided and no _program variable available;'; put '%return;'; put '%end;'; put '%end;'; put '%local root;'; put '/**'; put '* First check we are not in the tests/macros folder (which has no subfolders)'; put '* or specifically in the testsetup or testteardown services'; put '*/'; put '%if %index(&pgm,/tests/macros/)'; put 'or %index(&pgm,/tests/testsetup)'; put 'or %index(&pgm,/tests/testteardown)'; put '%then %do;'; put '%let root=%substr(&pgm,1,%index(&pgm,/tests)-1);'; put '&root'; put '%return;'; put '%end;'; put '/**'; put '* Next, move up two levels to avoid matches on subfolder or service name'; put '*/'; put '%let root=%substr(&pgm,1,%length(&pgm)-%length(%scan(&pgm,-1,/))-1);'; put '%let root=%substr(&root,1,%length(&root)-%length(%scan(&root,-1,/))-1);'; put '%if %index(&root,/tests/) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/tests/)-1);'; put '%end;'; put '%else %if %index(&root,/services) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/services)-1);'; put '%end;'; put '%else %if %index(&root,/jobs) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/jobs)-1);'; put '%end;'; put '%else %put &sysmacroname: Could not find an app location from &pgm;'; put '&root'; put '%mend mf_getapploc ;'; put '%macro mf_getuniquefileref(prefix=_,maxtries=1000,lrecl=32767);'; put '%local rc fname;'; put '%if &prefix=0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%end;'; put '%else %do;'; put '%local x len;'; put '%let len=%eval(8-%length(&prefix));'; put '%let x=0;'; put '%do x=0 %to &maxtries;'; put '%let fname=&prefix%substr(%sysfunc(ranuni(0)),3,&len);'; put '%if %sysfunc(fileref(&fname)) > 0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%return;'; put '%end;'; put '%end;'; put '%put unable to find available fileref after &maxtries attempts;'; put '%end;'; put '%mend mf_getuniquefileref;'; put '%macro mp_abort(mac=mp_abort.sas, type=, msg=, iftrue=%str(1=1)'; put ', errds=work.mp_abort_errds'; put ', mode=REGULAR'; put ')/*/STORE SOURCE*/;'; put '%global sysprocessmode sysprocessname sasjs_stpsrv_header_loc sasjsprocessmode;'; put '%local fref fid i;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%put NOTE: /// mp_abort macro executing //;'; put '%if %length(&mac)>0 %then %put NOTE- called by &mac;'; put '%put NOTE - &msg;'; put '%if %symexist(_SYSINCLUDEFILEDEVICE)'; put '/* abort cancel FILE does not restart outside the INCLUDE on Viya 3.5 */'; put 'and %superq(SYSPROCESSNAME) ne %str(Compute Server)'; put '%then %do;'; put '%if "*&_SYSINCLUDEFILEDEVICE*" ne "**" %then %do;'; put 'data &errds;'; put 'iftrue=''1=1'';'; put 'length mac $100 msg $5000;'; put 'mac=symget(''mac'');'; put 'msg=symget(''msg'');'; put 'run;'; put 'data _null_;'; put 'abort cancel FILE;'; put 'run;'; put '%return;'; put '%end;'; put '%end;'; put '/* Web App Context */'; put '%if %symexist(_PROGRAM)'; put 'or %superq(SYSPROCESSNAME) = %str(Compute Server)'; put 'or &mode=INCLUDE'; put '%then %do;'; put 'options obs=max replace mprint;'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5"'; put '%then %do;'; put 'options nosyntaxcheck;'; put '%end;'; put '%if &mode=INCLUDE %then %do;'; put '%if %sysfunc(exist(&errds))=1 %then %do;'; put 'data _null_;'; put 'set &errds;'; put 'call symputx(''iftrue'',iftrue,''l'');'; put 'call symputx(''mac'',mac,''l'');'; put 'call symputx(''msg'',msg,''l'');'; put 'putlog (_all_)(=);'; put 'run;'; put '%if (&iftrue)=0 %then %return;'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: No include errors found;'; put '%return;'; put '%end;'; put '%end;'; put '/* extract log errs / warns, if exist */'; put '%local logloc logline;'; put '%global logmsg; /* capture global messages */'; put '%if %symexist(SYSPRINTTOLOG) %then %let logloc=&SYSPRINTTOLOG;'; put '%else %let logloc=%qsysfunc(getoption(LOG));'; put 'proc printto log=log;run;'; put '%let logline=0;'; put '%if %length(&logloc)>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input; putlog _infile_;'; put 'i=1;'; put 'retain logonce 0;'; put 'if ('; put '_infile_=:"%str(WARN)ING" or _infile_=:"%str(ERR)OR"'; put ') and logonce=0 then'; put 'do;'; put 'call symputx(''logline'',_n_);'; put 'logonce+1;'; put 'end;'; put 'run;'; put '/* capture log including lines BEFORE the err */'; put '%if &logline>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input;'; put 'i=1;'; put 'stoploop=0;'; put 'if _n_ ge &logline-15 and stoploop=0 then do until (i>22);'; put 'call symputx(''logmsg'',catx(''\n'',symget(''logmsg''),_infile_));'; put 'input;'; put 'i+1;'; put 'stoploop=1;'; put 'end;'; put 'if stoploop=1 then stop;'; put 'run;'; put '%end;'; put '%end;'; put '%if %symexist(SYS_JES_JOB_URI) %then %do;'; put '/* setup webout for Viya */'; put 'options nobomfile;'; put '%if "X&SYS_JES_JOB_URI.X"="XX" %then %do;'; put 'filename _webout temp lrecl=999999 mod;'; put '%end;'; put '%else %do;'; put 'filename _webout filesrvc parenturi="&SYS_JES_JOB_URI"'; put 'name="_webout.json" lrecl=999999 mod;'; put '%end;'; put '%end;'; put '%else %if %sysfunc(filename(fref,&sasjs_stpsrv_header_loc))=0 %then %do;'; put 'options nobomfile;'; put '/* set up http header for SASjs Server */'; put '%let fid=%sysfunc(fopen(&fref,A));'; put '%if &fid=0 %then %do;'; put '%put %str(ERR)OR: %sysfunc(sysmsg());'; put '%return;'; put '%end;'; put '%let rc=%sysfunc(fput(&fid,%str(Content-Type: application/json)));'; put '%let rc=%sysfunc(fwrite(&fid));'; put '%let rc=%sysfunc(fclose(&fid));'; put '%let rc=%sysfunc(filename(&fref));'; put '%end;'; put '/* send response in SASjs JSON format */'; put 'data _null_;'; put 'file _webout mod lrecl=32000 encoding=''utf-8'';'; put 'length msg syswarningtext syserrortext $32767 mode $10 ;'; put 'sasdatetime=datetime();'; put 'msg=symget(''msg'');'; put '%if &logline>0 %then %do;'; put 'msg=cats(msg,''\n\nLog Extract:\n'',symget(''logmsg''));'; put '%end;'; put '/* escape the escapes */'; put 'msg=tranwrd(msg,''\'',''\\'');'; put '/* escape the quotes */'; put 'msg=tranwrd(msg,''"'',''\"'');'; put '/* ditch the CRLFs as chrome complains */'; put 'msg=compress(msg,,''kw'');'; put '/* quote without quoting the quotes (which are escaped instead) */'; put 'msg=cats(''"'',msg,''"'');'; put 'if symexist(''_debug'') then debug=quote(trim(symget(''_debug'')));'; put 'else debug=''""'';'; put 'if symget(''sasjsprocessmode'')=''Stored Program'' then mode=''SASJS'';'; put 'if mode ne ''SASJS'' then put ''>>weboutBEGIN<<'';'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'put '',"sasjsAbort" : [{'';'; put 'put '' "MSG":'' msg ;'; put 'put '' ,"MAC": "'' "&mac" ''"}]'';'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'put '',"_DEBUG":'' debug ;'; put 'if symexist(''_metauser'') then do;'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put 'end;'; put 'if symexist(''SYS_JES_JOB_URI'') then do;'; put 'SYS_JES_JOB_URI=quote(trim(symget(''SYS_JES_JOB_URI'')));'; put 'put '',"SYS_JES_JOB_URI": '' SYS_JES_JOB_URI;'; put 'end;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put ",""SYSWARNINGTEXT"" : " syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'put "}" ;'; put 'if mode ne ''SASJS'' then put ''>>weboutEND<<'';'; put 'run;'; put '%put _all_;'; put '%if "&sysprocessmode " = "SAS Stored Process Server " %then %do;'; put 'data _null_;'; put 'putlog ''stpsrvset program err and syscc'';'; put 'rc=stpsrvset(''program error'', 0);'; put 'call symputx("syscc",0,"g");'; put 'run;'; put '%if &sysscp=WIN'; put 'and 1=0 /* deprecating this logic until we figure out a consistent abort */'; put 'and "%substr(%str(&sysvlong ),1,8)"="9.04.01M"'; put 'and "%substr(%str(&sysvlong ),9,1)">"5" %then %do;'; put '/* skip approach (below) does not work in windows m6+ envs */'; put 'endsas;'; put '%end;'; put '%else %do;'; put '/**'; put '* endsas kills 9.4m3 deployments by orphaning multibridges.'; put '* Abort variants are ungraceful (non zero return code)'; put '* This approach lets SAS run silently until the end :-)'; put '* Caution - fails when called within a %include within a macro'; put '* Use mp_include() to handle this.'; put '*/'; put 'filename skip temp;'; put 'data _null_;'; put 'file skip;'; put 'put ''%macro skip();'';'; put 'comment ''%mend skip; -> fix lint '';'; put 'put ''%macro skippy();'';'; put 'comment ''%mend skippy; -> fix lint '';'; put 'run;'; put '%inc skip;'; put '%end;'; put '%end;'; put '%else %if "&sysprocessmode " = "SAS Compute Server " %then %do;'; put '/* endsas kills the session making it harder to fetch results */'; put 'data _null_;'; put 'syswarningtext=symget(''syswarningtext'');'; put 'syserrortext=symget(''syserrortext'');'; put 'abort_msg=symget(''msg'');'; put 'syscc=symget(''syscc'');'; put 'sysuserid=symget(''sysuserid'');'; put 'iftrue=symget(''iftrue'');'; put 'put (_all_)(/=);'; put 'call symputx(''syscc'',0);'; put 'abort cancel nolist;'; put 'run;'; put '%end;'; put '%else %do;'; put '%abort cancel;'; put '%end;'; put '%end;'; put '%else %do;'; put '%put _all_;'; put '%abort cancel;'; put '%end;'; put '%mend mp_abort;'; put '/** @endcond */'; put '%macro mm_getstpcode('; put 'tree=/User Folders/sasdemo/somestp'; put ',name='; put ',outloc=0'; put ',outref=0'; put ',mDebug=1'; put ',showlog=NO'; put ');'; put '%local mD;'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing &sysmacroname..sas;'; put '%&mD.put _local_;'; put '%if %length(&name)>0 %then %let name=/&name;'; put '/* first, check if STP exists */'; put '%local tsuri;'; put '%let tsuri=stopifempty ;'; put 'data _null_;'; put 'format type uri tsuri value $200.;'; put 'call missing (of _all_);'; put 'path="&tree&name(StoredProcess)";'; put '/* first, find the STP ID */'; put 'if metadata_pathobj("",path,"StoredProcess",type,uri)>0 then do;'; put '/* get sourcecode */'; put 'cnt=1;'; put 'do while (metadata_getnasn(uri,"Notes",cnt,tsuri)>0);'; put 'rc=metadata_getattr(tsuri,"Name",value);'; put '&mD.put tsuri= value=;'; put 'if value="SourceCode" then do;'; put '/* found it! */'; put 'rc=metadata_getattr(tsuri,"Id",value);'; put 'call symputx(''tsuri'',value,''l'');'; put 'stop;'; put 'end;'; put 'cnt+1;'; put 'end;'; put 'end;'; put 'else put (_all_)(=);'; put 'run;'; put '%mp_abort(iftrue= (&tsuri=stopifempty)'; put ',mac=mm_getstpcode'; put ',msg=%str(&tree&name.(StoredProcess) not found!)'; put ')'; put '/**'; put '* Now we can extract the textstore'; put '*/'; put 'filename __getdoc temp lrecl=10000000;'; put 'proc metadata'; put 'in="$METAREPOSITORY'; put ''; put 'SAS1"'; put 'out=__getdoc ;'; put 'run;'; put '/* find the beginning of the text */'; put '%local start;'; put 'data _null_;'; put 'infile __getdoc lrecl=10000;'; put 'input;'; put 'start=index(_infile_,''StoredText="'');'; put 'if start then do;'; put 'call symputx("start",start+11);'; put '*putlog ''"'' _infile_ ''"'';'; put 'end;'; put 'stop;'; put '%local outeng;'; put '%if "&outloc"="0" %then %let outeng=TEMP;'; put '%else %let outeng="&outloc";'; put '%local fref;'; put '%if &outref=0 %then %let fref=%mf_getuniquefileref();'; put '%else %let fref=&outref;'; put '/* read the content, byte by byte, resolving escaped chars */'; put 'filename &fref &outeng lrecl=100000;'; put 'data _null_;'; put 'length filein 8 fileid 8;'; put 'filein = fopen("__getdoc","I",1,"B");'; put 'fileid = fopen("&fref","O",1,"B");'; put 'rec = "20"x;'; put 'length entity $6;'; put 'do while(fread(filein)=0);'; put 'x+1;'; put 'if x>&start then do;'; put 'rc = fget(filein,rec,1);'; put 'if rec=''"'' then leave;'; put 'else if rec="&" then do;'; put 'entity=rec;'; put 'do until (rec=";");'; put 'if fread(filein) ne 0 then goto getout;'; put 'rc = fget(filein,rec,1);'; put 'entity=cats(entity,rec);'; put 'end;'; put 'select (entity);'; put 'when (''&'' ) rec=''&'' ;'; put 'when (''<'' ) rec=''<'' ;'; put 'when (''>'' ) rec=''>'' ;'; put 'when (''''') rec="''" ;'; put 'when (''"'') rec=''"'' ;'; put 'when ('' '') rec=''0A''x;'; put 'when ('' '') rec=''0D''x;'; put 'when (''$'' ) rec=''$'' ;'; put 'when ('' '') rec=''09''x;'; put 'otherwise putlog "%str(WARN)ING: missing value for " entity=;'; put 'end;'; put 'rc =fput(fileid, substr(rec,1,1));'; put 'rc =fwrite(fileid);'; put 'end;'; put 'else do;'; put 'rc =fput(fileid,rec);'; put 'rc =fwrite(fileid);'; put 'end;'; put 'end;'; put 'end;'; put 'getout:'; put 'rc=fclose(filein);'; put 'rc=fclose(fileid);'; put 'run;'; put '%if &showlog=YES %then %do;'; put 'data _null_;'; put 'infile &fref lrecl=32767 end=last;'; put 'input;'; put 'if _n_=1 then putlog ''>>stpcodeBEGIN<<'';'; put 'putlog _infile_;'; put 'if last then putlog ''>>stpcodeEND<<'';'; put 'run;'; put '%end;'; put 'filename __getdoc clear;'; put '%if &outref=0 %then %do;'; put 'filename &fref clear;'; put '%end;'; put '%mend mm_getstpcode;'; put '%macro dc_getsettings();'; put '%global _program;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(syscc=&syscc on entry (&syswarningtext &syserrortext))'; put ')'; put '%if %length(&_PROGRAM)>1 %then %let root=&_program;'; put '%else %do;'; put '%global _metauser;'; put '%let _metauser=&sysuserid;'; put '/* to mimic a "real" _program we need to give a dummy role and stp name */'; put '%let root=/dummyRole/dummyName;'; put '%end;'; put '/* the DC precode is stored in the Admin folder in the root of'; put 'the project. Lets find that root. */'; put '%put &=root;'; put '%let root=%mf_getapploc();'; put '%put &=root;'; put '/* Now we know the root location we can retrieve the params */'; put '%let temploc=%sysfunc(pathname(work))/settings.txt;'; put '%mm_getstpcode(tree=&root/services/public'; put ',name=Data_Controller_Settings'; put ',outloc=&temploc'; put ')'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Unable to run getstpcode)'; put ')'; put 'filename _getsets "&temploc" lrecl=2000;'; put '/*'; put 'Do not use mp_include here - this puts a copy in every service, which creates'; put 'compilation problems when calling services from mp_include'; put '*/'; put '%inc _getsets/source2;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Problem running &sysmacroname (&syswarningtext &syserrortext))'; put ')'; put '%mend dc_getsettings;'; put '%macro mf_fmtdttm('; put ')/*/STORE SOURCE*/;'; put '%if "&sysver"="9.2" or "&sysver"="9.3"'; put 'or ("&sysver"="9.4" and "%substr(&SYSVLONG,9,1)" le "3")'; put 'or "%substr(&sysver,1,1)"="4"'; put 'or "%substr(&sysver,1,1)"="5"'; put '%then %do;DATETIME19.3%end;'; put '%else %do;E8601DT26.6%end;'; put '%mend mf_fmtdttm;'; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '%macro mp_init(prefix=SASJS'; put ')/*/STORE SOURCE*/;'; put '%if %symexist(SASJS_PREFIX) %then %return; /* only run once */'; put '%global'; put 'SASJS_PREFIX /* the ONLY hard-coded global macro variable in SASjs */'; put '&prefix._FUNCTIONS /* used in mcf_init() to track core function compilation */'; put '&prefix._INIT_NUM /* initialisation time as numeric */'; put '&prefix._INIT_DTTM /* initialisation time in E8601DT26.6 format */'; put '&prefix.WORK /* avoid typing %sysfunc(pathname(work)) every time */'; put ';'; put '%let sasjs_prefix=&prefix;'; put 'data _null_;'; put 'dttm=datetime();'; put 'call symputx("&sasjs_prefix._init_num",dttm,''g'');'; put 'call symputx("&sasjs_prefix._init_dttm",put(dttm,E8601DT26.6),''g'');'; put 'call symputx("&sasjs_prefix.work",pathname(''WORK''),''g'');'; put 'run;'; put 'options'; put 'compress=CHAR /* default is none so ensure we have something! */'; put 'datastmtchk=ALLKEYWORDS /* protection from overwriting input datasets */'; put 'errorcheck=STRICT /* catch errs in libname/filename statements */'; put 'fmterr /* ensure err when a format cannot be found */'; put 'mergenoby=%str(ERR)OR /* throw err when a merge has no BY variables */'; put 'missing=. /* changing this can cause hard to detect errs */'; put 'noquotelenmax /* avoid warnings for long strings */'; put 'noreplace /* avoid overwriting permanent datasets */'; put 'ps=max /* reduce log size slightly */'; put 'ls=max /* reduce log even more and avoid word truncation */'; put 'validmemname=COMPATIBLE /* avoid special characters etc in table names */'; put 'validvarname=V7 /* avoid special characters etc in variable names */'; put 'varinitchk=%str(ERR)OR /* avoid data mistakes from variable name typos */'; put 'varlenchk=%str(ERR)OR /* fail hard if truncation (data loss) can result */'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5" %then %do;'; put 'noautocorrect /* disallow misspelled procedure names */'; put 'dsoptions=note2err /* undocumented - convert bad NOTEs to ERRs */'; put '%end;'; put ';'; put '%mend mp_init;'; put '%macro mpeinit(fetch=YES);'; put '%global mpeinit'; put 'mpeadmins /* group with unrestricted Meditor access */'; put 'mpelocapprovals /* location for landing and staging files */'; put 'mpelib /* location of configuration tables for DC */'; put 'dc_repo_users /* location of user / group metadata */'; put 'dc_licence_key /* extracted in dc_getsettings */'; put 'dc_activation_key /* extracted in dc_getsettings */'; put 'dc_locale /* extracted in dc_getsettings */'; put 'dc_dttmtfmt /* can be overridden in dc_getsettings */'; put '_debug'; put ';'; put '%if &mpeinit=1 %then %return;'; put '%else %let mpeinit=1;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(Problem on service startup (&syswarningtext &syserrortext))'; put ')'; put '%mp_init()'; put '%if &fetch=YES %then %do;'; put '%webout(FETCH)'; put '%end;'; put '%global _CLIENTNAME;'; put '%mp_abort(iftrue= (&_CLIENTNAME=SAS Enterprise Guide)'; put ',mac=&_program..sas'; put ',msg=%str(Data Controller is a web app and should not be executed from EG)'; put ')'; put 'options urlencoding=utf8 nobomfile lrecl=32767;'; put '%let perf=%sysfunc(datetime());'; put '%put perfdiff: 0;'; put '%let dc_locale=SYSTEM; /* default if not set */'; put '/**'; put '* E8601DT26.6 has widest database support - but not all SAS flavours can'; put '* handle it. Override in the settings STP if needed.'; put '*/'; put 'data _null_;'; put 'dc_dttmtfmt=''"%sysfunc(datetime(),''!!"%mf_fmtdttm()"!!'')"dt'';'; put 'call symputx(''dc_dttmtfmt'',dc_dttmtfmt);'; put 'put dc_dttmtfmt=;'; put 'run;'; put '%put &=dc_dttmtfmt;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc prior to dc_getsettings)'; put ')'; put '%dc_getsettings()'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc after dc_getsettings)'; put ')'; put 'data _null_;'; put 'set &DC_LIBREF..mpe_config(where=('; put 'var_scope="DC"'; put 'and &dc_dttmtfmt lt tx_to'; put 'and var_active=1'; put '));'; put 'call symputx(var_name,var_value,''G'');'; put 'putlog var_name "=" var_value;'; put 'run;'; put '%let mpelib=&dc_libref;'; put '%let mpeadmins=&dc_admin_group;'; put '%let mpelocapprovals=&dc_staging_area;'; put '%let dc_repo_users=&dc_repo_users;'; put '%if &dc_locale ne SYSTEM %then %do;'; put 'options locale=&dc_locale;'; put '%end;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(Problem during compilation or with STP precode (&syswarningtext))'; put ')'; put '%mend mpeinit;'; put '%macro mf_mval(var);'; put '%if %symexist(&var) %then %do;'; put '%superq(&var)'; put '%end;'; put '%mend mf_mval;'; put '%macro mf_trimstr(basestr,trimstr);'; put '%local baselen trimlen trimval;'; put '/* return if basestr is shorter than trimstr (or 0) */'; put '%let baselen=%length(%superq(basestr));'; put '%let trimlen=%length(%superq(trimstr));'; put '%if &baselen < &trimlen or &baselen=0 %then %return;'; put '/* obtain the characters from the end of basestr */'; put '%let trimval=%qsubstr(%superq(basestr)'; put ',%length(%superq(basestr))-&trimlen+1'; put ',&trimlen);'; put '/* compare and if matching, chop it off! */'; put '%if %superq(basestr)=%superq(trimstr) %then %do;'; put '%return;'; put '%end;'; put '%else %if %superq(trimval)=%superq(trimstr) %then %do;'; put '%qsubstr(%superq(basestr),1,%length(%superq(basestr))-&trimlen)'; put '%end;'; put '%else %do;'; put '&basestr'; put '%end;'; put '%mend mf_trimstr;'; put '%macro mf_getplatform(switch'; put ')/*/STORE SOURCE*/;'; put '%local a b c;'; put '%if &switch.NONE=NONE %then %do;'; put '%if %symexist(sasjsprocessmode) %then %do;'; put '%if &sasjsprocessmode=Stored Program %then %do;'; put 'SASJS'; put '%return;'; put '%end;'; put '%end;'; put '%if %symexist(sysprocessmode) %then %do;'; put '%if "&sysprocessmode"="SAS Object Server"'; put 'or "&sysprocessmode"= "SAS Compute Server" %then %do;'; put 'SASVIYA'; put '%end;'; put '%else %if "&sysprocessmode"="SAS Stored Process Server"'; put 'or "&sysprocessmode"="SAS Workspace Server"'; put '%then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if %symexist(_metaport) or %symexist(_metauser) %then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if &switch=SASSTUDIO %then %do;'; put '/* return the version of SAS Studio else 0 */'; put '%if %mf_mval(_CLIENTAPP)=%str(SAS Studio) %then %do;'; put '%let a=%mf_mval(_CLIENTVERSION);'; put '%let b=%scan(&a,1,.);'; put '%if %eval(&b >2) %then %do;'; put '&b'; put '%end;'; put '%else 0;'; put '%end;'; put '%else 0;'; put '%end;'; put '%else %if &switch=VIYARESTAPI %then %do;'; put '%mf_trimstr(%sysfunc(getoption(servicesbaseurl)),/)'; put '%end;'; put '%mend mf_getplatform;'; put '%macro mpeterm();'; put '%local oldloc;'; put 'data _null_;'; put 'if symexist(''SYSPRINTTOLOG'') then oldloc=symget(''SYSPRINTTOLOG'');'; put 'else oldloc=getoption(''LOG'');'; put 'if subpad(oldloc,1,1) not in (''"'',"''",'' '') then oldloc=quote(cats(oldloc));'; put 'call symputx(''oldloc'',oldloc,''l'');'; put 'run;'; put '%if %length(&oldloc)>0 %then %do;'; put 'proc printto log=log;'; put 'run;'; put 'data _null_;'; put 'infile &oldloc;'; put 'input; putlog _infile_;'; put 'run;'; put '%end;'; put '%if %sysfunc(exist(&dc_libref..mpe_requests)) and %mf_getplatform() ne SASVIYA'; put '%then %do;'; put 'data ;'; put 'if 0 then set &dc_libref..mpe_requests;'; put 'request_dttm=%sysfunc(datetime());'; put 'request_user="%mf_getuser()";'; put 'request_service="%scan(&_program,-2,/)/%scan(&_program,-1,/)";'; put 'request_params='''';'; put 'output;stop;'; put 'proc append base=&dc_libref..mpe_requests data=&syslast force nowarn;'; put 'run;'; put '%end;'; put '%mend mpeterm;'; put '* SAS Macros end;'; put '* SAS Includes start;'; put '* SAS Includes end;'; put '* Binary Files start;'; put '* Binary Files end;'; put '* ServiceInit start;'; put 'options noquotelenmax ps=max;'; put '/* create dummy macros to prevent stpbegin / stpend from corrupting sessions */'; put '%macro stpbegin();'; put '%put NOTE: the STPBEGIN macro should not be used for web apps!;'; put '%mend stpbegin;'; put '%macro stpend();'; put '%put NOTE: the STPEND macro should not be used for web apps!;'; put '%mend stpend;'; put '* ServiceInit end;'; put '* Service start;'; put '/**'; put '@file'; put '@brief Post Edit Hook script for the MPE_TABLES table'; put '@details Post edit hooks provide additional backend validation for user'; put 'provided data. The incoming dataset is named `work.staging_ds` and is'; put 'provided in mpe_loader.sas.'; put 'Available macro variables:'; put '@li DC_LIBREF - The DC control library'; put '@li LIBREF - The library of the dataset being edited (is assigned)'; put '@li DS - The dataset being edited'; put 'This validation checks MPE_TABLES to ensure modified / added records are'; put 'valid. If a non-default AUDIT_LIBDS is being used, there is also a check'; put 'to ensure that this table already exists.'; put '**/'; put '%let errmsg=;'; put '%let errflag=0;'; put '/* ensure uppercasing */'; put 'data work.staging_ds;'; put 'set work.staging_ds;'; put 'LIBREF=upcase(LIBREF);'; put 'DSN=upcase(DSN);'; put 'loadtype=upcase(loadtype);'; put 'buskey=upcase(buskey);'; put 'var_txfrom=upcase(var_txfrom);'; put 'var_txto=upcase(var_txto);'; put 'var_busfrom=upcase(var_busfrom);'; put 'var_busto=upcase(var_busto);'; put 'var_processed=upcase(var_processed);'; put 'close_vars=upcase(close_vars);'; put 'audit_libds=upcase(audit_libds);'; put 'rk_underlying=upcase(rk_underlying);'; put '/* check for valid loadtype */'; put 'if LOADTYPE not in (''UPDATE'',''TXTEMPORAL'',''FORMAT_CAT'',''BITEMPORAL'',''REPLACE'')'; put 'then do;'; put 'call symputx(''errmsg'',"Invalid LOADTYPE: "!!LOADTYPE);'; put 'call symputx(''errflag'',1);'; put 'end;'; put '/* force correct BUSKEY and DSN when loading format catalogs */'; put 'if LOADTYPE=''FORMAT_CAT'' then do;'; put 'BUSKEY=''TYPE FMTNAME FMTROW'';'; put 'DSN=scan(dsn,1,''-'')!!''-FC'';'; put 'end;'; put '/* convert tabs into spaces */'; put 'buskey=translate(buskey," ","09"x);'; put 'rk_underlying=translate(rk_underlying," ","09"x);'; put 'run;'; put '%mp_abort(iftrue=(&errflag=1)'; put ',mac=mpe_tables_postedit'; put ',msg=%superq(errmsg)'; put ')'; put '/* get distinct list of audit libs */'; put 'proc sql;'; put 'create table work.liblist as'; put 'select distinct audit_libds'; put 'from work.staging_ds'; put 'where audit_libds not in ('''',''0'', "&dc_libref..MPE_AUDIT")'; put 'and upcase(_____DELETE__THIS__RECORD_____) ne "YES";'; put '/* assign the libs */'; put 'data _null_;'; put 'set work.liblist;'; put 'call symputx(cats(''lib'',_n_),audit_libds);'; put 'libref=scan(audit_libds,1,''.'');'; put 'call execute(''%dc_assignlib(WRITE,''!!libref!!'')'');'; put 'run;'; put '/* check the audit tables exist */'; put 'data _null_;'; put 'set work.liblist;'; put 'if exist(audit_libds,"DATA")=0 then do;'; put 'call symputx(''errmsg'','; put '"Audit Table "!!audit_libds!!" does not exist, or could not be assigned."'; put ');'; put 'call symputx(''errflag'',1);'; put 'end;'; put 'run;'; put '%mp_abort(iftrue=(&errflag=1)'; put ',mac=mpe_tables_postedit'; put ',msg=%superq(errmsg)'; put ')'; put '* Service end;'; run; %mm_createwebservice(path=&appLoc/&path, name=&service, code=sascode, server=&serverName, replace=yes) filename sascode clear; %let service=mpe_validations_postedit; filename sascode temp lrecl=32767; data _null_; file sascode; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '/**'; put '@file mp_jsonout.sas'; put '@brief Writes JSON in SASjs format to a fileref'; put '@details This macro can be used to OPEN a JSON stream and send one or more'; put 'tables as arrays of rows, where each row can be an object or a nested array.'; put 'There are two engines available - DATASTEP or PROCJSON.'; put 'PROC JSON is fast but will produce errs like the ones below if'; put 'special chars are encountered.'; put '> (ERR)OR: Some code points did not transcode.'; put '> An object or array close is not valid at this point in the JSON text.'; put '> Date value out of range'; put 'If this happens, try running with ENGINE=DATASTEP.'; put 'The DATASTEP engine is used to handle special SAS missing numerics, and'; put 'can also convert entire datasets to formatted values. Output JSON is always'; put 'in UTF-8.'; put 'Usage:'; put 'filename tmp temp;'; put 'data class; set sashelp.class;run;'; put '%mp_jsonout(OPEN,jref=tmp)'; put '%mp_jsonout(OBJ,class,jref=tmp)'; put '%mp_jsonout(OBJ,class,dslabel=class2,jref=tmp,showmeta=Y)'; put '%mp_jsonout(CLOSE,jref=tmp)'; put 'data _null_;'; put 'infile tmp;'; put 'input;putlog _infile_;'; put 'run;'; put 'If you are building web apps with SAS then you are strongly encouraged to use'; put 'the mX_createwebservice macros in combination with the'; put '[sasjs adapter](https://github.com/sasjs/adapter).'; put 'For more information see https://sasjs.io'; put '@param [in] action Valid values:'; put '@li OPEN - opens the JSON'; put '@li OBJ - sends a table with each row as an object'; put '@li ARR - sends a table with each row in an array'; put '@li CLOSE - closes the JSON'; put '@param [in] ds The dataset to send. Must be a work table.'; put '@param [out] jref= (_webout) The fileref to which to send the JSON'; put '@param [out] dslabel= The name to give the table in the exported JSON'; put '@param [in] fmt= (Y) Whether to keep (Y) or strip (N) formats from the table'; put '@param [in] engine= (DATASTEP) Which engine to use to send the JSON. Options:'; put '@li PROCJSON (default)'; put '@li DATASTEP (more reliable when data has non standard characters)'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to JSON'; put '

Related Files

'; put '@li mp_ds2fmtds.sas'; put '@version 9.2'; put '@author Allan Bowe'; put '@source https://github.com/sasjs/core'; put '**/'; put '%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y'; put ',engine=DATASTEP'; put ',missing=NULL'; put ',showmeta=N'; put ',maxobs=MAX'; put ')/*/STORE SOURCE*/;'; put '%local tempds colinfo fmtds i numcols numobs stmt_obs lastobs optval'; put 'tmpds1 tmpds2 tmpds3 tmpds4;'; put '%let numcols=0;'; put '%if &maxobs ne MAX %then %let stmt_obs=%str(if _n_>&maxobs then stop;);'; put '%if &action=OPEN %then %do;'; put 'options nobomfile;'; put 'data _null_;file &jref encoding=''utf-8'' lrecl=200;'; put 'put ''{"PROCESSED_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''"'';'; put 'run;'; put '%end;'; put '%else %if (&action=ARR or &action=OBJ) %then %do;'; put '/* force variable names to always be uppercase in the JSON */'; put 'options validvarname=upcase;'; put '/* To avoid issues with _webout on EBI - such as encoding diffs and truncation'; put '(https://support.sas.com/kb/49/325.html) we use temporary files */'; put 'filename _sjs1 temp lrecl=200 ;'; put 'data _null_; file _sjs1 encoding=''utf-8'';'; put 'put ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":";'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjs1 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs1 clear;'; put '/* grab col defs */'; put 'proc contents noprint data=&ds'; put 'out=_data_(keep=name type length format formatl formatd varnum label);'; put 'run;'; put '%let colinfo=%scan(&syslast,2,.);'; put 'proc sort data=&colinfo;'; put 'by varnum;'; put 'run;'; put '/* move meta to mac vars */'; put 'data &colinfo;'; put 'if _n_=1 then call symputx(''numcols'',nobs,''l'');'; put 'set &colinfo end=last nobs=nobs;'; put 'name=upcase(name);'; put '/* fix formats */'; put 'if type=2 or type=6 then do;'; put 'typelong=''char'';'; put 'length fmt $49.;'; put 'if format='''' then fmt=cats(''$'',length,''.'');'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else fmt=cats(format,formatl,''.'');'; put 'end;'; put 'else do;'; put 'typelong=''num'';'; put 'if format='''' then fmt=''best.'';'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else if formatd=0 then fmt=cats(format,formatl,''.'');'; put 'else fmt=cats(format,formatl,''.'',formatd);'; put 'end;'; put '/* 32 char unique name */'; put 'newname=''sasjs''!!substr(cats(put(md5(name),$hex32.)),1,27);'; put 'call symputx(cats(''name'',_n_),name,''l'');'; put 'call symputx(cats(''newname'',_n_),newname,''l'');'; put 'call symputx(cats(''length'',_n_),length,''l'');'; put 'call symputx(cats(''fmt'',_n_),fmt,''l'');'; put 'call symputx(cats(''type'',_n_),type,''l'');'; put 'call symputx(cats(''typelong'',_n_),typelong,''l'');'; put 'call symputx(cats(''label'',_n_),coalescec(label,name),''l'');'; put '/* overwritten when fmt=Y and a custom format exists in catalog */'; put 'if typelong=''num'' then call symputx(cats(''fmtlen'',_n_),200,''l'');'; put 'else call symputx(cats(''fmtlen'',_n_),min(32767,ceil((length+10)*1.5)),''l'');'; put 'run;'; put '%let tempds=%substr(_%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql;'; put 'select count(*) into: lastobs from &ds;'; put '%if &maxobs ne MAX %then %let lastobs=%sysfunc(min(&lastobs,&maxobs));'; put '%if &engine=PROCJSON %then %do;'; put '%if &missing=STRING %then %do;'; put '%put &sysmacroname: Special Missings not supported in proc json.;'; put '%put &sysmacroname: Switching to DATASTEP engine;'; put '%goto datastep;'; put '%end;'; put 'data &tempds;'; put 'set &ds;'; put '&stmt_obs;'; put '%if &fmt=N %then format _numeric_ best32.;;'; put '/* PRETTY is necessary to avoid line truncation in large files */'; put 'filename _sjs2 temp lrecl=131068 encoding=''utf-8'';'; put 'proc json out=_sjs2 pretty'; put '%if &action=ARR %then nokeys ;'; put ';export &tempds / nosastags fmtnumeric;'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs2 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs2 clear;'; put '%end;'; put '%else %if &engine=DATASTEP %then %do;'; put '%datastep:'; put '%if %sysfunc(exist(&ds)) ne 1 & %sysfunc(exist(&ds,VIEW)) ne 1'; put '%then %do;'; put '%put &sysmacroname: &ds NOT FOUND!!!;'; put '%return;'; put '%end;'; put '%if &fmt=Y %then %do;'; put '/**'; put '* Extract format definitions'; put '* First, by getting library locations from dictionary.formats'; put '* Then, by exporting the width using proc format'; put '* Cannot use maxw from sashelp.vformat as not always populated'; put '* Cannot use fmtinfo() as not supported in all flavours'; put '*/'; put '%let tmpds1=%substr(fmtsum%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds2=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds3=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds4=%substr(col%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql noprint;'; put 'create table &tmpds1 as'; put 'select cats(libname,''.'',memname) as FMTCAT,'; put 'FMTNAME'; put 'from dictionary.formats'; put 'where fmttype=''F'' and libname is not null'; put 'and fmtname in (select format from &colinfo where format is not null)'; put 'order by 1;'; put 'create table &tmpds2('; put 'FMTNAME char(32),'; put 'LENGTH num'; put ');'; put '%local catlist cat fmtlist i;'; put 'select distinct fmtcat into: catlist separated by '' '' from &tmpds1;'; put '%do i=1 %to %sysfunc(countw(&catlist,%str( )));'; put '%let cat=%scan(&catlist,&i,%str( ));'; put 'proc sql;'; put 'select distinct fmtname into: fmtlist separated by '' '''; put 'from &tmpds1 where fmtcat="&cat";'; put 'proc format lib=&cat cntlout=&tmpds3(keep=fmtname length);'; put 'select &fmtlist;'; put 'run;'; put 'proc sql;'; put 'insert into &tmpds2 select distinct fmtname,length from &tmpds3;'; put '%end;'; put 'proc sql;'; put 'create table &tmpds4 as'; put 'select a.*, b.length as MAXW'; put 'from &colinfo a'; put 'left join &tmpds2 b'; put 'on cats(a.format)=cats(upcase(b.fmtname))'; put 'order by a.varnum;'; put 'data _null_;'; put 'set &tmpds4;'; put 'if not missing(maxw);'; put 'call symputx('; put 'cats(''fmtlen'',_n_),'; put '/* vars need extra padding due to JSON escaping of special chars */'; put 'min(32767,ceil((max(length,maxw)+10)*1.5))'; put ',''l'''; put ');'; put 'run;'; put '/* configure varlenchk - as we are explicitly shortening the variables */'; put '%let optval=%sysfunc(getoption(varlenchk));'; put 'options varlenchk=NOWARN;'; put 'data _data_(compress=char);'; put '/* shorten the new vars */'; put 'length'; put '%do i=1 %to &numcols;'; put '&&name&i $&&fmtlen&i'; put '%end;'; put ';'; put '/* rename on entry */'; put 'set &ds(rename=('; put '%do i=1 %to &numcols;'; put '&&name&i=&&newname&i'; put '%end;'; put '));'; put '&stmt_obs;'; put 'drop'; put '%do i=1 %to &numcols;'; put '&&newname&i'; put '%end;'; put ';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=num %then %do;'; put '&&name&i=cats(put(&&newname&i,&&fmt&i));'; put '%end;'; put '%else %do;'; put '&&name&i=put(&&newname&i,&&fmt&i);'; put '%end;'; put '%end;'; put 'if _error_ then do;'; put 'call symputx(''syscc'',1012);'; put 'stop;'; put 'end;'; put 'run;'; put '%let fmtds=&syslast;'; put 'options varlenchk=&optval;'; put '%end;'; put 'proc format; /* credit yabwon for special null removal */'; put 'value bart (default=40)'; put '%if &missing=NULL %then %do;'; put '._ - .z = null'; put '%end;'; put '%else %do;'; put '._ = [quote()]'; put '. = null'; put '.a - .z = [quote()]'; put '%end;'; put 'other = [best.];'; put 'data &tempds;'; put 'attrib _all_ label='''';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'length &&name&i $&&fmtlen&i...;'; put 'format &&name&i $&&fmtlen&i...;'; put '%end;'; put '%end;'; put '%if &fmt=Y %then %do;'; put 'set &fmtds;'; put '%end;'; put '%else %do;'; put 'set &ds;'; put '%end;'; put '&stmt_obs;'; put 'format _numeric_ bart.;'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'if findc(&&name&i,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put '&&name&i=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,&&name&i)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else &&name&i=quote(cats(&&name&i));'; put '%end;'; put '%end;'; put 'run;'; put 'filename _sjs3 temp lrecl=131068 ;'; put 'data _null_;'; put 'file _sjs3 encoding=''utf-8'';'; put 'if _n_=1 then put "[";'; put 'set &tempds;'; put 'if _n_>1 then put "," @; put'; put '%if &action=ARR %then "[" ; %else "{" ;'; put '%do i=1 %to &numcols;'; put '%if &i>1 %then "," ;'; put '%if &action=OBJ %then """&&name&i"":" ;'; put '"&&name&i"n /* name literal for reserved variable names */'; put '%end;'; put '%if &action=ARR %then "]" ; %else "}" ; ;'; put '/* close out the table */'; put 'data _null_;'; put 'file _sjs3 mod encoding=''utf-8'';'; put 'put '']'';'; put 'run;'; put 'data _null_;'; put 'infile _sjs3 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs3 clear;'; put '%end;'; put 'proc sql;'; put 'drop table &colinfo, &tempds;'; put '%if %substr(&showmeta,1,1)=Y %then %do;'; put 'filename _sjs4 temp lrecl=131068 encoding=''utf-8'';'; put 'data _null_;'; put 'file _sjs4;'; put 'length label $350;'; put 'put ", ""$%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":{""vars"":{";'; put 'do i=1 to &numcols;'; put 'name=quote(trim(symget(cats(''name'',i))));'; put 'format=quote(trim(symget(cats(''fmt'',i))));'; put 'label=quote(prxchange(''s/\\/\\\\/'',-1,trim(symget(cats(''label'',i)))));'; put 'length=quote(trim(symget(cats(''length'',i))));'; put 'type=quote(trim(symget(cats(''typelong'',i))));'; put 'if i>1 then put "," @@;'; put 'put name '':{"format":'' format '',"label":'' label'; put ''',"length":'' length '',"type":'' type ''}'';'; put 'end;'; put 'put ''}}'';'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs4 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs4 clear;'; put '%end;'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put 'data _null_; file &jref encoding=''utf-8'' mod ;'; put 'put "}";'; put 'run;'; put '%end;'; put '%mend mp_jsonout;'; put '/**'; put '@file mm_webout.sas'; put '@brief Send data to/from SAS Stored Processes'; put '@details This macro should be added to the start of each Stored Process,'; put '**immediately** followed by a call to:'; put '%mm_webout(FETCH)'; put 'This will read all the input data and create same-named SAS datasets in the'; put 'WORK library. You can then insert your code, and send data back using the'; put 'following syntax:'; put 'data some datasets; * make some data ;'; put 'retain some columns;'; put 'run;'; put '%mm_webout(OPEN)'; put '%mm_webout(ARR,some) * Array format, fast, suitable for large tables ;'; put '%mm_webout(OBJ,datasets) * Object format, easier to work with ;'; put 'Finally, wrap everything up send some helpful system variables too'; put '%mm_webout(CLOSE)'; put '@param [in] action Either FETCH, OPEN, ARR, OBJ or CLOSE'; put '@param [in] ds The dataset to send back to the frontend'; put '@param [out] dslabel= Value to use instead of table name for sending to JSON'; put '@param [in] fmt= (N) Setting Y converts all vars to their formatted values'; put '@param [out] fref= (_webout) The fileref to which to write the JSON'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] workobs= (0) When set to a positive integer, will create a new'; put 'output object (WORK) which contains this number of observations from all'; put 'tables in the WORK library.'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to output JSON'; put '

SAS Macros

'; put '@li mp_jsonout.sas'; put '

Related Macros

'; put '@li ms_webout.sas'; put '@li mv_webout.sas'; put '@version 9.3'; put '@author Allan Bowe'; put '**/'; put '%macro mm_webout(action,ds,dslabel=,fref=_webout,fmt=N,missing=NULL'; put ',showmeta=N,maxobs=MAX,workobs=0'; put ');'; put '%global _webin_file_count _webin_fileref1 _webin_name1 _program _debug'; put 'sasjs_tables;'; put '%local i tempds jsonengine;'; put '/* see https://github.com/sasjs/core/issues/41 */'; put '%if "%upcase(&SYSENCODING)" ne "UTF-8" %then %let jsonengine=PROCJSON;'; put '%else %let jsonengine=DATASTEP;'; put '%if &action=FETCH %then %do;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'options mprint notes mprintnest;'; put '%end;'; put '%let _webin_file_count=%eval(&_webin_file_count+0);'; put '/* now read in the data */'; put '%do i=1 %to &_webin_file_count;'; put '%if &_webin_file_count=1 %then %do;'; put '%let _webin_fileref1=&_webin_fileref;'; put '%let _webin_name1=&_webin_name;'; put '%end;'; put 'data _null_;'; put 'infile &&_webin_fileref&i termstr=crlf;'; put 'input;'; put 'call symputx(''input_statement'',_infile_);'; put 'putlog "&&_webin_name&i input statement: " _infile_;'; put 'stop;'; put 'data &&_webin_name&i;'; put 'infile &&_webin_fileref&i firstobs=2 dsd termstr=crlf encoding=''utf-8'';'; put 'input &input_statement;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'if _n_<20 then putlog _infile_;'; put '%end;'; put 'run;'; put '%let sasjs_tables=&sasjs_tables &&_webin_name&i;'; put '%end;'; put '%end;'; put '%else %if &action=OPEN %then %do;'; put '/* fix encoding */'; put 'OPTIONS NOBOMFILE;'; put '/**'; put '* check xengine type to avoid the below err message:'; put '* > Function is only valid for filerefs using the CACHE access method.'; put '*/'; put 'data _null_;'; put 'set sashelp.vextfl(where=(fileref="_WEBOUT"));'; put 'if xengine=''STREAM'' then do;'; put 'rc=stpsrv_header(''Content-type'',"text/html; encoding=utf-8");'; put 'end;'; put 'run;'; put '/* setup json */'; put 'data _null_;file &fref encoding=''utf-8'';'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutBEGIN<<'';'; put '%end;'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'run;'; put '%end;'; put '%else %if &action=ARR or &action=OBJ %then %do;'; put '%mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt,jref=&fref'; put ',engine=&jsonengine,missing=&missing,showmeta=&showmeta,maxobs=&maxobs'; put ')'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put '/* To avoid issues with _webout on EBI we use a temporary file */'; put 'filename _sjsref temp lrecl=131068;'; put '%if %str(&workobs) > 0 %then %do;'; put '/* if debug mode, send back first XX records of each work table also */'; put 'data;run;%let tempds=%scan(&syslast,2,.);'; put 'ods output Members=&tempds;'; put 'proc datasets library=WORK memtype=data;'; put '%local wtcnt;%let wtcnt=0;'; put 'data _null_;'; put 'set &tempds;'; put 'if not (upcase(name) =:"DATA"); /* ignore temp datasets */'; put 'i+1;'; put 'call symputx(cats(''wt'',i),name,''l'');'; put 'call symputx(''wtcnt'',i,''l'');'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put ",""WORK"":{";'; put '%do i=1 %to &wtcnt;'; put '%let wt=&&wt&i;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'dsid=open("WORK.&wt",''is'');'; put 'nlobs=attrn(dsid,''NLOBS'');'; put 'nvars=attrn(dsid,''NVARS'');'; put 'rc=close(dsid);'; put 'if &i>1 then put '',''@;'; put 'put " ""&wt"" : {";'; put 'put ''"nlobs":'' nlobs;'; put 'put '',"nvars":'' nvars;'; put '%mp_jsonout(OBJ,&wt,jref=_sjsref,dslabel=first10rows,showmeta=Y'; put ',maxobs=&workobs'; put ')'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put '%end;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put 'run;'; put '%end;'; put '/* close off json */'; put 'data _null_;file _sjsref mod encoding=''utf-8'';'; put 'length SYSPROCESSNAME syserrortext syswarningtext autoexec $512;'; put 'put ",""_DEBUG"" : ""&_debug"" ";'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'autoexec=quote(urlencode(trim(getoption(''autoexec''))));'; put 'put '',"AUTOEXEC" : '' autoexec;'; put 'put ",""MF_GETUSER"" : ""%mf_getuser()"" ";'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'put ",""SYSENCODING"" : ""&sysencoding"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSPROCESSID"" : ""&SYSPROCESSID"" ";'; put 'put ",""SYSPROCESSMODE"" : ""&SYSPROCESSMODE"" ";'; put 'SYSPROCESSNAME=quote(urlencode(cats(SYSPROCESSNAME)));'; put 'put ",""SYSPROCESSNAME"" : " SYSPROCESSNAME;'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put '',"SYSWARNINGTEXT" : '' syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'length memsize $32;'; put 'memsize="%sysfunc(INPUTN(%sysfunc(getoption(memsize)), best.),sizekmg.)";'; put 'memsize=quote(cats(memsize));'; put 'put '',"MEMSIZE" : '' memsize;'; put 'put "}" @;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutEND<<'';'; put '%end;'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjsref lrecl=1 recfm=n;'; put 'file &fref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjsref clear;'; put '%end;'; put '%mend mm_webout;'; put '%macro webout(action,ds,dslabel=,fmt=,missing=NULL,showmeta=NO,maxobs=MAX);'; put '%mm_webout(&action,ds=&ds,dslabel=&dslabel,fmt=&fmt'; put ',missing=&missing'; put ',showmeta=&showmeta'; put ',maxobs=&maxobs'; put ') %mend;'; put '/* provide additional debug info */'; put '%global _program;'; put '%put &=syscc;'; put '%put user=%mf_getuser();'; put '%put pgm=&_program;'; put '%put timestamp=%sysfunc(datetime(),datetime19.);'; put '* Service Variables start;'; put '* Service Variables end;'; put '* SAS Macros start;'; put '%macro mf_getapploc(pgm);'; put '%if "&pgm"="" %then %do;'; put '%if %symexist(_program) %then %let pgm=&_program;'; put '%else %do;'; put '%put &sysmacroname: No value provided and no _program variable available;'; put '%return;'; put '%end;'; put '%end;'; put '%local root;'; put '/**'; put '* First check we are not in the tests/macros folder (which has no subfolders)'; put '* or specifically in the testsetup or testteardown services'; put '*/'; put '%if %index(&pgm,/tests/macros/)'; put 'or %index(&pgm,/tests/testsetup)'; put 'or %index(&pgm,/tests/testteardown)'; put '%then %do;'; put '%let root=%substr(&pgm,1,%index(&pgm,/tests)-1);'; put '&root'; put '%return;'; put '%end;'; put '/**'; put '* Next, move up two levels to avoid matches on subfolder or service name'; put '*/'; put '%let root=%substr(&pgm,1,%length(&pgm)-%length(%scan(&pgm,-1,/))-1);'; put '%let root=%substr(&root,1,%length(&root)-%length(%scan(&root,-1,/))-1);'; put '%if %index(&root,/tests/) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/tests/)-1);'; put '%end;'; put '%else %if %index(&root,/services) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/services)-1);'; put '%end;'; put '%else %if %index(&root,/jobs) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/jobs)-1);'; put '%end;'; put '%else %put &sysmacroname: Could not find an app location from &pgm;'; put '&root'; put '%mend mf_getapploc ;'; put '%macro mf_getuniquefileref(prefix=_,maxtries=1000,lrecl=32767);'; put '%local rc fname;'; put '%if &prefix=0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%end;'; put '%else %do;'; put '%local x len;'; put '%let len=%eval(8-%length(&prefix));'; put '%let x=0;'; put '%do x=0 %to &maxtries;'; put '%let fname=&prefix%substr(%sysfunc(ranuni(0)),3,&len);'; put '%if %sysfunc(fileref(&fname)) > 0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%return;'; put '%end;'; put '%end;'; put '%put unable to find available fileref after &maxtries attempts;'; put '%end;'; put '%mend mf_getuniquefileref;'; put '%macro mp_abort(mac=mp_abort.sas, type=, msg=, iftrue=%str(1=1)'; put ', errds=work.mp_abort_errds'; put ', mode=REGULAR'; put ')/*/STORE SOURCE*/;'; put '%global sysprocessmode sysprocessname sasjs_stpsrv_header_loc sasjsprocessmode;'; put '%local fref fid i;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%put NOTE: /// mp_abort macro executing //;'; put '%if %length(&mac)>0 %then %put NOTE- called by &mac;'; put '%put NOTE - &msg;'; put '%if %symexist(_SYSINCLUDEFILEDEVICE)'; put '/* abort cancel FILE does not restart outside the INCLUDE on Viya 3.5 */'; put 'and %superq(SYSPROCESSNAME) ne %str(Compute Server)'; put '%then %do;'; put '%if "*&_SYSINCLUDEFILEDEVICE*" ne "**" %then %do;'; put 'data &errds;'; put 'iftrue=''1=1'';'; put 'length mac $100 msg $5000;'; put 'mac=symget(''mac'');'; put 'msg=symget(''msg'');'; put 'run;'; put 'data _null_;'; put 'abort cancel FILE;'; put 'run;'; put '%return;'; put '%end;'; put '%end;'; put '/* Web App Context */'; put '%if %symexist(_PROGRAM)'; put 'or %superq(SYSPROCESSNAME) = %str(Compute Server)'; put 'or &mode=INCLUDE'; put '%then %do;'; put 'options obs=max replace mprint;'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5"'; put '%then %do;'; put 'options nosyntaxcheck;'; put '%end;'; put '%if &mode=INCLUDE %then %do;'; put '%if %sysfunc(exist(&errds))=1 %then %do;'; put 'data _null_;'; put 'set &errds;'; put 'call symputx(''iftrue'',iftrue,''l'');'; put 'call symputx(''mac'',mac,''l'');'; put 'call symputx(''msg'',msg,''l'');'; put 'putlog (_all_)(=);'; put 'run;'; put '%if (&iftrue)=0 %then %return;'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: No include errors found;'; put '%return;'; put '%end;'; put '%end;'; put '/* extract log errs / warns, if exist */'; put '%local logloc logline;'; put '%global logmsg; /* capture global messages */'; put '%if %symexist(SYSPRINTTOLOG) %then %let logloc=&SYSPRINTTOLOG;'; put '%else %let logloc=%qsysfunc(getoption(LOG));'; put 'proc printto log=log;run;'; put '%let logline=0;'; put '%if %length(&logloc)>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input; putlog _infile_;'; put 'i=1;'; put 'retain logonce 0;'; put 'if ('; put '_infile_=:"%str(WARN)ING" or _infile_=:"%str(ERR)OR"'; put ') and logonce=0 then'; put 'do;'; put 'call symputx(''logline'',_n_);'; put 'logonce+1;'; put 'end;'; put 'run;'; put '/* capture log including lines BEFORE the err */'; put '%if &logline>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input;'; put 'i=1;'; put 'stoploop=0;'; put 'if _n_ ge &logline-15 and stoploop=0 then do until (i>22);'; put 'call symputx(''logmsg'',catx(''\n'',symget(''logmsg''),_infile_));'; put 'input;'; put 'i+1;'; put 'stoploop=1;'; put 'end;'; put 'if stoploop=1 then stop;'; put 'run;'; put '%end;'; put '%end;'; put '%if %symexist(SYS_JES_JOB_URI) %then %do;'; put '/* setup webout for Viya */'; put 'options nobomfile;'; put '%if "X&SYS_JES_JOB_URI.X"="XX" %then %do;'; put 'filename _webout temp lrecl=999999 mod;'; put '%end;'; put '%else %do;'; put 'filename _webout filesrvc parenturi="&SYS_JES_JOB_URI"'; put 'name="_webout.json" lrecl=999999 mod;'; put '%end;'; put '%end;'; put '%else %if %sysfunc(filename(fref,&sasjs_stpsrv_header_loc))=0 %then %do;'; put 'options nobomfile;'; put '/* set up http header for SASjs Server */'; put '%let fid=%sysfunc(fopen(&fref,A));'; put '%if &fid=0 %then %do;'; put '%put %str(ERR)OR: %sysfunc(sysmsg());'; put '%return;'; put '%end;'; put '%let rc=%sysfunc(fput(&fid,%str(Content-Type: application/json)));'; put '%let rc=%sysfunc(fwrite(&fid));'; put '%let rc=%sysfunc(fclose(&fid));'; put '%let rc=%sysfunc(filename(&fref));'; put '%end;'; put '/* send response in SASjs JSON format */'; put 'data _null_;'; put 'file _webout mod lrecl=32000 encoding=''utf-8'';'; put 'length msg syswarningtext syserrortext $32767 mode $10 ;'; put 'sasdatetime=datetime();'; put 'msg=symget(''msg'');'; put '%if &logline>0 %then %do;'; put 'msg=cats(msg,''\n\nLog Extract:\n'',symget(''logmsg''));'; put '%end;'; put '/* escape the escapes */'; put 'msg=tranwrd(msg,''\'',''\\'');'; put '/* escape the quotes */'; put 'msg=tranwrd(msg,''"'',''\"'');'; put '/* ditch the CRLFs as chrome complains */'; put 'msg=compress(msg,,''kw'');'; put '/* quote without quoting the quotes (which are escaped instead) */'; put 'msg=cats(''"'',msg,''"'');'; put 'if symexist(''_debug'') then debug=quote(trim(symget(''_debug'')));'; put 'else debug=''""'';'; put 'if symget(''sasjsprocessmode'')=''Stored Program'' then mode=''SASJS'';'; put 'if mode ne ''SASJS'' then put ''>>weboutBEGIN<<'';'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'put '',"sasjsAbort" : [{'';'; put 'put '' "MSG":'' msg ;'; put 'put '' ,"MAC": "'' "&mac" ''"}]'';'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'put '',"_DEBUG":'' debug ;'; put 'if symexist(''_metauser'') then do;'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put 'end;'; put 'if symexist(''SYS_JES_JOB_URI'') then do;'; put 'SYS_JES_JOB_URI=quote(trim(symget(''SYS_JES_JOB_URI'')));'; put 'put '',"SYS_JES_JOB_URI": '' SYS_JES_JOB_URI;'; put 'end;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put ",""SYSWARNINGTEXT"" : " syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'put "}" ;'; put 'if mode ne ''SASJS'' then put ''>>weboutEND<<'';'; put 'run;'; put '%put _all_;'; put '%if "&sysprocessmode " = "SAS Stored Process Server " %then %do;'; put 'data _null_;'; put 'putlog ''stpsrvset program err and syscc'';'; put 'rc=stpsrvset(''program error'', 0);'; put 'call symputx("syscc",0,"g");'; put 'run;'; put '%if &sysscp=WIN'; put 'and 1=0 /* deprecating this logic until we figure out a consistent abort */'; put 'and "%substr(%str(&sysvlong ),1,8)"="9.04.01M"'; put 'and "%substr(%str(&sysvlong ),9,1)">"5" %then %do;'; put '/* skip approach (below) does not work in windows m6+ envs */'; put 'endsas;'; put '%end;'; put '%else %do;'; put '/**'; put '* endsas kills 9.4m3 deployments by orphaning multibridges.'; put '* Abort variants are ungraceful (non zero return code)'; put '* This approach lets SAS run silently until the end :-)'; put '* Caution - fails when called within a %include within a macro'; put '* Use mp_include() to handle this.'; put '*/'; put 'filename skip temp;'; put 'data _null_;'; put 'file skip;'; put 'put ''%macro skip();'';'; put 'comment ''%mend skip; -> fix lint '';'; put 'put ''%macro skippy();'';'; put 'comment ''%mend skippy; -> fix lint '';'; put 'run;'; put '%inc skip;'; put '%end;'; put '%end;'; put '%else %if "&sysprocessmode " = "SAS Compute Server " %then %do;'; put '/* endsas kills the session making it harder to fetch results */'; put 'data _null_;'; put 'syswarningtext=symget(''syswarningtext'');'; put 'syserrortext=symget(''syserrortext'');'; put 'abort_msg=symget(''msg'');'; put 'syscc=symget(''syscc'');'; put 'sysuserid=symget(''sysuserid'');'; put 'iftrue=symget(''iftrue'');'; put 'put (_all_)(/=);'; put 'call symputx(''syscc'',0);'; put 'abort cancel nolist;'; put 'run;'; put '%end;'; put '%else %do;'; put '%abort cancel;'; put '%end;'; put '%end;'; put '%else %do;'; put '%put _all_;'; put '%abort cancel;'; put '%end;'; put '%mend mp_abort;'; put '/** @endcond */'; put '%macro mm_getstpcode('; put 'tree=/User Folders/sasdemo/somestp'; put ',name='; put ',outloc=0'; put ',outref=0'; put ',mDebug=1'; put ',showlog=NO'; put ');'; put '%local mD;'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing &sysmacroname..sas;'; put '%&mD.put _local_;'; put '%if %length(&name)>0 %then %let name=/&name;'; put '/* first, check if STP exists */'; put '%local tsuri;'; put '%let tsuri=stopifempty ;'; put 'data _null_;'; put 'format type uri tsuri value $200.;'; put 'call missing (of _all_);'; put 'path="&tree&name(StoredProcess)";'; put '/* first, find the STP ID */'; put 'if metadata_pathobj("",path,"StoredProcess",type,uri)>0 then do;'; put '/* get sourcecode */'; put 'cnt=1;'; put 'do while (metadata_getnasn(uri,"Notes",cnt,tsuri)>0);'; put 'rc=metadata_getattr(tsuri,"Name",value);'; put '&mD.put tsuri= value=;'; put 'if value="SourceCode" then do;'; put '/* found it! */'; put 'rc=metadata_getattr(tsuri,"Id",value);'; put 'call symputx(''tsuri'',value,''l'');'; put 'stop;'; put 'end;'; put 'cnt+1;'; put 'end;'; put 'end;'; put 'else put (_all_)(=);'; put 'run;'; put '%mp_abort(iftrue= (&tsuri=stopifempty)'; put ',mac=mm_getstpcode'; put ',msg=%str(&tree&name.(StoredProcess) not found!)'; put ')'; put '/**'; put '* Now we can extract the textstore'; put '*/'; put 'filename __getdoc temp lrecl=10000000;'; put 'proc metadata'; put 'in="$METAREPOSITORY'; put ''; put 'SAS1"'; put 'out=__getdoc ;'; put 'run;'; put '/* find the beginning of the text */'; put '%local start;'; put 'data _null_;'; put 'infile __getdoc lrecl=10000;'; put 'input;'; put 'start=index(_infile_,''StoredText="'');'; put 'if start then do;'; put 'call symputx("start",start+11);'; put '*putlog ''"'' _infile_ ''"'';'; put 'end;'; put 'stop;'; put '%local outeng;'; put '%if "&outloc"="0" %then %let outeng=TEMP;'; put '%else %let outeng="&outloc";'; put '%local fref;'; put '%if &outref=0 %then %let fref=%mf_getuniquefileref();'; put '%else %let fref=&outref;'; put '/* read the content, byte by byte, resolving escaped chars */'; put 'filename &fref &outeng lrecl=100000;'; put 'data _null_;'; put 'length filein 8 fileid 8;'; put 'filein = fopen("__getdoc","I",1,"B");'; put 'fileid = fopen("&fref","O",1,"B");'; put 'rec = "20"x;'; put 'length entity $6;'; put 'do while(fread(filein)=0);'; put 'x+1;'; put 'if x>&start then do;'; put 'rc = fget(filein,rec,1);'; put 'if rec=''"'' then leave;'; put 'else if rec="&" then do;'; put 'entity=rec;'; put 'do until (rec=";");'; put 'if fread(filein) ne 0 then goto getout;'; put 'rc = fget(filein,rec,1);'; put 'entity=cats(entity,rec);'; put 'end;'; put 'select (entity);'; put 'when (''&'' ) rec=''&'' ;'; put 'when (''<'' ) rec=''<'' ;'; put 'when (''>'' ) rec=''>'' ;'; put 'when (''''') rec="''" ;'; put 'when (''"'') rec=''"'' ;'; put 'when ('' '') rec=''0A''x;'; put 'when ('' '') rec=''0D''x;'; put 'when (''$'' ) rec=''$'' ;'; put 'when ('' '') rec=''09''x;'; put 'otherwise putlog "%str(WARN)ING: missing value for " entity=;'; put 'end;'; put 'rc =fput(fileid, substr(rec,1,1));'; put 'rc =fwrite(fileid);'; put 'end;'; put 'else do;'; put 'rc =fput(fileid,rec);'; put 'rc =fwrite(fileid);'; put 'end;'; put 'end;'; put 'end;'; put 'getout:'; put 'rc=fclose(filein);'; put 'rc=fclose(fileid);'; put 'run;'; put '%if &showlog=YES %then %do;'; put 'data _null_;'; put 'infile &fref lrecl=32767 end=last;'; put 'input;'; put 'if _n_=1 then putlog ''>>stpcodeBEGIN<<'';'; put 'putlog _infile_;'; put 'if last then putlog ''>>stpcodeEND<<'';'; put 'run;'; put '%end;'; put 'filename __getdoc clear;'; put '%if &outref=0 %then %do;'; put 'filename &fref clear;'; put '%end;'; put '%mend mm_getstpcode;'; put '%macro dc_getsettings();'; put '%global _program;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(syscc=&syscc on entry (&syswarningtext &syserrortext))'; put ')'; put '%if %length(&_PROGRAM)>1 %then %let root=&_program;'; put '%else %do;'; put '%global _metauser;'; put '%let _metauser=&sysuserid;'; put '/* to mimic a "real" _program we need to give a dummy role and stp name */'; put '%let root=/dummyRole/dummyName;'; put '%end;'; put '/* the DC precode is stored in the Admin folder in the root of'; put 'the project. Lets find that root. */'; put '%put &=root;'; put '%let root=%mf_getapploc();'; put '%put &=root;'; put '/* Now we know the root location we can retrieve the params */'; put '%let temploc=%sysfunc(pathname(work))/settings.txt;'; put '%mm_getstpcode(tree=&root/services/public'; put ',name=Data_Controller_Settings'; put ',outloc=&temploc'; put ')'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Unable to run getstpcode)'; put ')'; put 'filename _getsets "&temploc" lrecl=2000;'; put '/*'; put 'Do not use mp_include here - this puts a copy in every service, which creates'; put 'compilation problems when calling services from mp_include'; put '*/'; put '%inc _getsets/source2;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Problem running &sysmacroname (&syswarningtext &syserrortext))'; put ')'; put '%mend dc_getsettings;'; put '%macro mf_fmtdttm('; put ')/*/STORE SOURCE*/;'; put '%if "&sysver"="9.2" or "&sysver"="9.3"'; put 'or ("&sysver"="9.4" and "%substr(&SYSVLONG,9,1)" le "3")'; put 'or "%substr(&sysver,1,1)"="4"'; put 'or "%substr(&sysver,1,1)"="5"'; put '%then %do;DATETIME19.3%end;'; put '%else %do;E8601DT26.6%end;'; put '%mend mf_fmtdttm;'; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '%macro mp_init(prefix=SASJS'; put ')/*/STORE SOURCE*/;'; put '%if %symexist(SASJS_PREFIX) %then %return; /* only run once */'; put '%global'; put 'SASJS_PREFIX /* the ONLY hard-coded global macro variable in SASjs */'; put '&prefix._FUNCTIONS /* used in mcf_init() to track core function compilation */'; put '&prefix._INIT_NUM /* initialisation time as numeric */'; put '&prefix._INIT_DTTM /* initialisation time in E8601DT26.6 format */'; put '&prefix.WORK /* avoid typing %sysfunc(pathname(work)) every time */'; put ';'; put '%let sasjs_prefix=&prefix;'; put 'data _null_;'; put 'dttm=datetime();'; put 'call symputx("&sasjs_prefix._init_num",dttm,''g'');'; put 'call symputx("&sasjs_prefix._init_dttm",put(dttm,E8601DT26.6),''g'');'; put 'call symputx("&sasjs_prefix.work",pathname(''WORK''),''g'');'; put 'run;'; put 'options'; put 'compress=CHAR /* default is none so ensure we have something! */'; put 'datastmtchk=ALLKEYWORDS /* protection from overwriting input datasets */'; put 'errorcheck=STRICT /* catch errs in libname/filename statements */'; put 'fmterr /* ensure err when a format cannot be found */'; put 'mergenoby=%str(ERR)OR /* throw err when a merge has no BY variables */'; put 'missing=. /* changing this can cause hard to detect errs */'; put 'noquotelenmax /* avoid warnings for long strings */'; put 'noreplace /* avoid overwriting permanent datasets */'; put 'ps=max /* reduce log size slightly */'; put 'ls=max /* reduce log even more and avoid word truncation */'; put 'validmemname=COMPATIBLE /* avoid special characters etc in table names */'; put 'validvarname=V7 /* avoid special characters etc in variable names */'; put 'varinitchk=%str(ERR)OR /* avoid data mistakes from variable name typos */'; put 'varlenchk=%str(ERR)OR /* fail hard if truncation (data loss) can result */'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5" %then %do;'; put 'noautocorrect /* disallow misspelled procedure names */'; put 'dsoptions=note2err /* undocumented - convert bad NOTEs to ERRs */'; put '%end;'; put ';'; put '%mend mp_init;'; put '%macro mpeinit(fetch=YES);'; put '%global mpeinit'; put 'mpeadmins /* group with unrestricted Meditor access */'; put 'mpelocapprovals /* location for landing and staging files */'; put 'mpelib /* location of configuration tables for DC */'; put 'dc_repo_users /* location of user / group metadata */'; put 'dc_licence_key /* extracted in dc_getsettings */'; put 'dc_activation_key /* extracted in dc_getsettings */'; put 'dc_locale /* extracted in dc_getsettings */'; put 'dc_dttmtfmt /* can be overridden in dc_getsettings */'; put '_debug'; put ';'; put '%if &mpeinit=1 %then %return;'; put '%else %let mpeinit=1;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(Problem on service startup (&syswarningtext &syserrortext))'; put ')'; put '%mp_init()'; put '%if &fetch=YES %then %do;'; put '%webout(FETCH)'; put '%end;'; put '%global _CLIENTNAME;'; put '%mp_abort(iftrue= (&_CLIENTNAME=SAS Enterprise Guide)'; put ',mac=&_program..sas'; put ',msg=%str(Data Controller is a web app and should not be executed from EG)'; put ')'; put 'options urlencoding=utf8 nobomfile lrecl=32767;'; put '%let perf=%sysfunc(datetime());'; put '%put perfdiff: 0;'; put '%let dc_locale=SYSTEM; /* default if not set */'; put '/**'; put '* E8601DT26.6 has widest database support - but not all SAS flavours can'; put '* handle it. Override in the settings STP if needed.'; put '*/'; put 'data _null_;'; put 'dc_dttmtfmt=''"%sysfunc(datetime(),''!!"%mf_fmtdttm()"!!'')"dt'';'; put 'call symputx(''dc_dttmtfmt'',dc_dttmtfmt);'; put 'put dc_dttmtfmt=;'; put 'run;'; put '%put &=dc_dttmtfmt;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc prior to dc_getsettings)'; put ')'; put '%dc_getsettings()'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc after dc_getsettings)'; put ')'; put 'data _null_;'; put 'set &DC_LIBREF..mpe_config(where=('; put 'var_scope="DC"'; put 'and &dc_dttmtfmt lt tx_to'; put 'and var_active=1'; put '));'; put 'call symputx(var_name,var_value,''G'');'; put 'putlog var_name "=" var_value;'; put 'run;'; put '%let mpelib=&dc_libref;'; put '%let mpeadmins=&dc_admin_group;'; put '%let mpelocapprovals=&dc_staging_area;'; put '%let dc_repo_users=&dc_repo_users;'; put '%if &dc_locale ne SYSTEM %then %do;'; put 'options locale=&dc_locale;'; put '%end;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(Problem during compilation or with STP precode (&syswarningtext))'; put ')'; put '%mend mpeinit;'; put '%macro mf_mval(var);'; put '%if %symexist(&var) %then %do;'; put '%superq(&var)'; put '%end;'; put '%mend mf_mval;'; put '%macro mf_trimstr(basestr,trimstr);'; put '%local baselen trimlen trimval;'; put '/* return if basestr is shorter than trimstr (or 0) */'; put '%let baselen=%length(%superq(basestr));'; put '%let trimlen=%length(%superq(trimstr));'; put '%if &baselen < &trimlen or &baselen=0 %then %return;'; put '/* obtain the characters from the end of basestr */'; put '%let trimval=%qsubstr(%superq(basestr)'; put ',%length(%superq(basestr))-&trimlen+1'; put ',&trimlen);'; put '/* compare and if matching, chop it off! */'; put '%if %superq(basestr)=%superq(trimstr) %then %do;'; put '%return;'; put '%end;'; put '%else %if %superq(trimval)=%superq(trimstr) %then %do;'; put '%qsubstr(%superq(basestr),1,%length(%superq(basestr))-&trimlen)'; put '%end;'; put '%else %do;'; put '&basestr'; put '%end;'; put '%mend mf_trimstr;'; put '%macro mf_getplatform(switch'; put ')/*/STORE SOURCE*/;'; put '%local a b c;'; put '%if &switch.NONE=NONE %then %do;'; put '%if %symexist(sasjsprocessmode) %then %do;'; put '%if &sasjsprocessmode=Stored Program %then %do;'; put 'SASJS'; put '%return;'; put '%end;'; put '%end;'; put '%if %symexist(sysprocessmode) %then %do;'; put '%if "&sysprocessmode"="SAS Object Server"'; put 'or "&sysprocessmode"= "SAS Compute Server" %then %do;'; put 'SASVIYA'; put '%end;'; put '%else %if "&sysprocessmode"="SAS Stored Process Server"'; put 'or "&sysprocessmode"="SAS Workspace Server"'; put '%then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if %symexist(_metaport) or %symexist(_metauser) %then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if &switch=SASSTUDIO %then %do;'; put '/* return the version of SAS Studio else 0 */'; put '%if %mf_mval(_CLIENTAPP)=%str(SAS Studio) %then %do;'; put '%let a=%mf_mval(_CLIENTVERSION);'; put '%let b=%scan(&a,1,.);'; put '%if %eval(&b >2) %then %do;'; put '&b'; put '%end;'; put '%else 0;'; put '%end;'; put '%else 0;'; put '%end;'; put '%else %if &switch=VIYARESTAPI %then %do;'; put '%mf_trimstr(%sysfunc(getoption(servicesbaseurl)),/)'; put '%end;'; put '%mend mf_getplatform;'; put '%macro mpeterm();'; put '%local oldloc;'; put 'data _null_;'; put 'if symexist(''SYSPRINTTOLOG'') then oldloc=symget(''SYSPRINTTOLOG'');'; put 'else oldloc=getoption(''LOG'');'; put 'if subpad(oldloc,1,1) not in (''"'',"''",'' '') then oldloc=quote(cats(oldloc));'; put 'call symputx(''oldloc'',oldloc,''l'');'; put 'run;'; put '%if %length(&oldloc)>0 %then %do;'; put 'proc printto log=log;'; put 'run;'; put 'data _null_;'; put 'infile &oldloc;'; put 'input; putlog _infile_;'; put 'run;'; put '%end;'; put '%if %sysfunc(exist(&dc_libref..mpe_requests)) and %mf_getplatform() ne SASVIYA'; put '%then %do;'; put 'data ;'; put 'if 0 then set &dc_libref..mpe_requests;'; put 'request_dttm=%sysfunc(datetime());'; put 'request_user="%mf_getuser()";'; put 'request_service="%scan(&_program,-2,/)/%scan(&_program,-1,/)";'; put 'request_params='''';'; put 'output;stop;'; put 'proc append base=&dc_libref..mpe_requests data=&syslast force nowarn;'; put 'run;'; put '%end;'; put '%mend mpeterm;'; put '%macro mf_getattrn('; put 'libds'; put ',attr'; put ')/*/STORE SOURCE*/;'; put '%local dsid rc;'; put '%let dsid=%sysfunc(open(&libds,is));'; put '%if &dsid = 0 %then %do;'; put '%put %str(WARN)ING: Cannot open %trim(&libds), system message below;'; put '%put %sysfunc(sysmsg());'; put '-1'; put '%end;'; put '%else %do;'; put '%sysfunc(attrn(&dsid,&attr))'; put '%let rc=%sysfunc(close(&dsid));'; put '%end;'; put '%mend mf_getattrn;'; put '%macro mf_nobs(libds'; put ')/*/STORE SOURCE*/;'; put '%mf_getattrn(&libds,NLOBS)'; put '%mend mf_nobs;'; put '* SAS Macros end;'; put '* SAS Includes start;'; put '* SAS Includes end;'; put '* Binary Files start;'; put '* Binary Files end;'; put '* ServiceInit start;'; put 'options noquotelenmax ps=max;'; put '/* create dummy macros to prevent stpbegin / stpend from corrupting sessions */'; put '%macro stpbegin();'; put '%put NOTE: the STPBEGIN macro should not be used for web apps!;'; put '%mend stpbegin;'; put '%macro stpend();'; put '%put NOTE: the STPEND macro should not be used for web apps!;'; put '%mend stpend;'; put '* ServiceInit end;'; put '* Service start;'; put '/**'; put '@file'; put '@brief Post Edit Hook script for the MPE_VALIDATIONS table'; put '@details Post edit hooks provide additional backend validation for user'; put 'provided data. The incoming dataset is named `work.staging_ds` and is'; put 'provided in mpe_loader.sas.'; put 'Available macro variables:'; put '@li DC_LIBREF - The DC control library'; put '@li LIBREF - The library of the dataset being edited (is assigned)'; put '@li DS - The dataset being edited'; put 'This validation checks the incoming mpe_validations settings to ensure'; put 'there are no columns that have both HARDSELECT_HOOK and SOFTSELECT_HOOK.'; put '

SAS Macros

'; put '@li mf_nobs.sas'; put '

Related Macros

'; put '@li mpe_loader.sas'; put '**/'; put '/** check to avoid a colum having both HARDSELECT_HOOK and SOFTSELECT_HOOK */'; put '/* need to merge with base table in the case of a single row being added */'; put '%global src_list1 src_list2;'; put '%let src_list1='''';'; put 'proc sql noprint;'; put 'create table work.check1 as'; put 'select quote(catx(''.'',base_lib,base_ds,base_col)) as source'; put ',rule_type'; put 'from work.staging_ds'; put 'where rule_type in (''SOFTSELECT_HOOK'',''HARDSELECT_HOOK'')'; put 'and upcase(_____DELETE__THIS__RECORD_____) ne "YES";'; put 'select distinct cats(source) into: src_list1 separated by '','''; put 'from work.check1;'; put 'create table work.check2 as'; put 'select quote(catx(''.'',base_lib,base_ds,base_col)) as source'; put ',rule_type'; put 'from &DC_LIBREF..MPE_VALIDATIONS'; put 'where rule_type in (''SOFTSELECT_HOOK'',''HARDSELECT_HOOK'')'; put 'and &dc_dttmtfmt. lt tx_to'; put 'and catx(''.'',base_lib,base_ds,base_col) in (&src_list1);'; put 'create table work.check3 as'; put 'select * from work.check1'; put 'union'; put 'select * from work.check2;'; put 'create table work.validation_checker as'; put 'select source'; put ',count(*) as cnt'; put 'from work.check3'; put 'group by 1'; put 'having cnt>1;'; put 'select distinct source into: src_list2 from work.validation_checker;'; put 'data _null_;'; put 'set work.validation_checker;'; put 'putlog (_all_)(=);'; put 'run;'; put '%mp_abort(iftrue= (%mf_nobs(work.validation_checker)>0)'; put ',mac=mpe_validations_postedit'; put ',msg=%str(The following vars have duplicate HOOKS: &src_list2)'; put ')'; put '* Service end;'; run; %mm_createwebservice(path=&appLoc/&path, name=&service, code=sascode, server=&serverName, replace=yes) filename sascode clear; %let service=mpe_xlmap_info_postedit; filename sascode temp lrecl=32767; data _null_; file sascode; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '/**'; put '@file mp_jsonout.sas'; put '@brief Writes JSON in SASjs format to a fileref'; put '@details This macro can be used to OPEN a JSON stream and send one or more'; put 'tables as arrays of rows, where each row can be an object or a nested array.'; put 'There are two engines available - DATASTEP or PROCJSON.'; put 'PROC JSON is fast but will produce errs like the ones below if'; put 'special chars are encountered.'; put '> (ERR)OR: Some code points did not transcode.'; put '> An object or array close is not valid at this point in the JSON text.'; put '> Date value out of range'; put 'If this happens, try running with ENGINE=DATASTEP.'; put 'The DATASTEP engine is used to handle special SAS missing numerics, and'; put 'can also convert entire datasets to formatted values. Output JSON is always'; put 'in UTF-8.'; put 'Usage:'; put 'filename tmp temp;'; put 'data class; set sashelp.class;run;'; put '%mp_jsonout(OPEN,jref=tmp)'; put '%mp_jsonout(OBJ,class,jref=tmp)'; put '%mp_jsonout(OBJ,class,dslabel=class2,jref=tmp,showmeta=Y)'; put '%mp_jsonout(CLOSE,jref=tmp)'; put 'data _null_;'; put 'infile tmp;'; put 'input;putlog _infile_;'; put 'run;'; put 'If you are building web apps with SAS then you are strongly encouraged to use'; put 'the mX_createwebservice macros in combination with the'; put '[sasjs adapter](https://github.com/sasjs/adapter).'; put 'For more information see https://sasjs.io'; put '@param [in] action Valid values:'; put '@li OPEN - opens the JSON'; put '@li OBJ - sends a table with each row as an object'; put '@li ARR - sends a table with each row in an array'; put '@li CLOSE - closes the JSON'; put '@param [in] ds The dataset to send. Must be a work table.'; put '@param [out] jref= (_webout) The fileref to which to send the JSON'; put '@param [out] dslabel= The name to give the table in the exported JSON'; put '@param [in] fmt= (Y) Whether to keep (Y) or strip (N) formats from the table'; put '@param [in] engine= (DATASTEP) Which engine to use to send the JSON. Options:'; put '@li PROCJSON (default)'; put '@li DATASTEP (more reliable when data has non standard characters)'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to JSON'; put '

Related Files

'; put '@li mp_ds2fmtds.sas'; put '@version 9.2'; put '@author Allan Bowe'; put '@source https://github.com/sasjs/core'; put '**/'; put '%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y'; put ',engine=DATASTEP'; put ',missing=NULL'; put ',showmeta=N'; put ',maxobs=MAX'; put ')/*/STORE SOURCE*/;'; put '%local tempds colinfo fmtds i numcols numobs stmt_obs lastobs optval'; put 'tmpds1 tmpds2 tmpds3 tmpds4;'; put '%let numcols=0;'; put '%if &maxobs ne MAX %then %let stmt_obs=%str(if _n_>&maxobs then stop;);'; put '%if &action=OPEN %then %do;'; put 'options nobomfile;'; put 'data _null_;file &jref encoding=''utf-8'' lrecl=200;'; put 'put ''{"PROCESSED_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''"'';'; put 'run;'; put '%end;'; put '%else %if (&action=ARR or &action=OBJ) %then %do;'; put '/* force variable names to always be uppercase in the JSON */'; put 'options validvarname=upcase;'; put '/* To avoid issues with _webout on EBI - such as encoding diffs and truncation'; put '(https://support.sas.com/kb/49/325.html) we use temporary files */'; put 'filename _sjs1 temp lrecl=200 ;'; put 'data _null_; file _sjs1 encoding=''utf-8'';'; put 'put ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":";'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjs1 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs1 clear;'; put '/* grab col defs */'; put 'proc contents noprint data=&ds'; put 'out=_data_(keep=name type length format formatl formatd varnum label);'; put 'run;'; put '%let colinfo=%scan(&syslast,2,.);'; put 'proc sort data=&colinfo;'; put 'by varnum;'; put 'run;'; put '/* move meta to mac vars */'; put 'data &colinfo;'; put 'if _n_=1 then call symputx(''numcols'',nobs,''l'');'; put 'set &colinfo end=last nobs=nobs;'; put 'name=upcase(name);'; put '/* fix formats */'; put 'if type=2 or type=6 then do;'; put 'typelong=''char'';'; put 'length fmt $49.;'; put 'if format='''' then fmt=cats(''$'',length,''.'');'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else fmt=cats(format,formatl,''.'');'; put 'end;'; put 'else do;'; put 'typelong=''num'';'; put 'if format='''' then fmt=''best.'';'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else if formatd=0 then fmt=cats(format,formatl,''.'');'; put 'else fmt=cats(format,formatl,''.'',formatd);'; put 'end;'; put '/* 32 char unique name */'; put 'newname=''sasjs''!!substr(cats(put(md5(name),$hex32.)),1,27);'; put 'call symputx(cats(''name'',_n_),name,''l'');'; put 'call symputx(cats(''newname'',_n_),newname,''l'');'; put 'call symputx(cats(''length'',_n_),length,''l'');'; put 'call symputx(cats(''fmt'',_n_),fmt,''l'');'; put 'call symputx(cats(''type'',_n_),type,''l'');'; put 'call symputx(cats(''typelong'',_n_),typelong,''l'');'; put 'call symputx(cats(''label'',_n_),coalescec(label,name),''l'');'; put '/* overwritten when fmt=Y and a custom format exists in catalog */'; put 'if typelong=''num'' then call symputx(cats(''fmtlen'',_n_),200,''l'');'; put 'else call symputx(cats(''fmtlen'',_n_),min(32767,ceil((length+10)*1.5)),''l'');'; put 'run;'; put '%let tempds=%substr(_%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql;'; put 'select count(*) into: lastobs from &ds;'; put '%if &maxobs ne MAX %then %let lastobs=%sysfunc(min(&lastobs,&maxobs));'; put '%if &engine=PROCJSON %then %do;'; put '%if &missing=STRING %then %do;'; put '%put &sysmacroname: Special Missings not supported in proc json.;'; put '%put &sysmacroname: Switching to DATASTEP engine;'; put '%goto datastep;'; put '%end;'; put 'data &tempds;'; put 'set &ds;'; put '&stmt_obs;'; put '%if &fmt=N %then format _numeric_ best32.;;'; put '/* PRETTY is necessary to avoid line truncation in large files */'; put 'filename _sjs2 temp lrecl=131068 encoding=''utf-8'';'; put 'proc json out=_sjs2 pretty'; put '%if &action=ARR %then nokeys ;'; put ';export &tempds / nosastags fmtnumeric;'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs2 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs2 clear;'; put '%end;'; put '%else %if &engine=DATASTEP %then %do;'; put '%datastep:'; put '%if %sysfunc(exist(&ds)) ne 1 & %sysfunc(exist(&ds,VIEW)) ne 1'; put '%then %do;'; put '%put &sysmacroname: &ds NOT FOUND!!!;'; put '%return;'; put '%end;'; put '%if &fmt=Y %then %do;'; put '/**'; put '* Extract format definitions'; put '* First, by getting library locations from dictionary.formats'; put '* Then, by exporting the width using proc format'; put '* Cannot use maxw from sashelp.vformat as not always populated'; put '* Cannot use fmtinfo() as not supported in all flavours'; put '*/'; put '%let tmpds1=%substr(fmtsum%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds2=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds3=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds4=%substr(col%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql noprint;'; put 'create table &tmpds1 as'; put 'select cats(libname,''.'',memname) as FMTCAT,'; put 'FMTNAME'; put 'from dictionary.formats'; put 'where fmttype=''F'' and libname is not null'; put 'and fmtname in (select format from &colinfo where format is not null)'; put 'order by 1;'; put 'create table &tmpds2('; put 'FMTNAME char(32),'; put 'LENGTH num'; put ');'; put '%local catlist cat fmtlist i;'; put 'select distinct fmtcat into: catlist separated by '' '' from &tmpds1;'; put '%do i=1 %to %sysfunc(countw(&catlist,%str( )));'; put '%let cat=%scan(&catlist,&i,%str( ));'; put 'proc sql;'; put 'select distinct fmtname into: fmtlist separated by '' '''; put 'from &tmpds1 where fmtcat="&cat";'; put 'proc format lib=&cat cntlout=&tmpds3(keep=fmtname length);'; put 'select &fmtlist;'; put 'run;'; put 'proc sql;'; put 'insert into &tmpds2 select distinct fmtname,length from &tmpds3;'; put '%end;'; put 'proc sql;'; put 'create table &tmpds4 as'; put 'select a.*, b.length as MAXW'; put 'from &colinfo a'; put 'left join &tmpds2 b'; put 'on cats(a.format)=cats(upcase(b.fmtname))'; put 'order by a.varnum;'; put 'data _null_;'; put 'set &tmpds4;'; put 'if not missing(maxw);'; put 'call symputx('; put 'cats(''fmtlen'',_n_),'; put '/* vars need extra padding due to JSON escaping of special chars */'; put 'min(32767,ceil((max(length,maxw)+10)*1.5))'; put ',''l'''; put ');'; put 'run;'; put '/* configure varlenchk - as we are explicitly shortening the variables */'; put '%let optval=%sysfunc(getoption(varlenchk));'; put 'options varlenchk=NOWARN;'; put 'data _data_(compress=char);'; put '/* shorten the new vars */'; put 'length'; put '%do i=1 %to &numcols;'; put '&&name&i $&&fmtlen&i'; put '%end;'; put ';'; put '/* rename on entry */'; put 'set &ds(rename=('; put '%do i=1 %to &numcols;'; put '&&name&i=&&newname&i'; put '%end;'; put '));'; put '&stmt_obs;'; put 'drop'; put '%do i=1 %to &numcols;'; put '&&newname&i'; put '%end;'; put ';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=num %then %do;'; put '&&name&i=cats(put(&&newname&i,&&fmt&i));'; put '%end;'; put '%else %do;'; put '&&name&i=put(&&newname&i,&&fmt&i);'; put '%end;'; put '%end;'; put 'if _error_ then do;'; put 'call symputx(''syscc'',1012);'; put 'stop;'; put 'end;'; put 'run;'; put '%let fmtds=&syslast;'; put 'options varlenchk=&optval;'; put '%end;'; put 'proc format; /* credit yabwon for special null removal */'; put 'value bart (default=40)'; put '%if &missing=NULL %then %do;'; put '._ - .z = null'; put '%end;'; put '%else %do;'; put '._ = [quote()]'; put '. = null'; put '.a - .z = [quote()]'; put '%end;'; put 'other = [best.];'; put 'data &tempds;'; put 'attrib _all_ label='''';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'length &&name&i $&&fmtlen&i...;'; put 'format &&name&i $&&fmtlen&i...;'; put '%end;'; put '%end;'; put '%if &fmt=Y %then %do;'; put 'set &fmtds;'; put '%end;'; put '%else %do;'; put 'set &ds;'; put '%end;'; put '&stmt_obs;'; put 'format _numeric_ bart.;'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'if findc(&&name&i,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put '&&name&i=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,&&name&i)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else &&name&i=quote(cats(&&name&i));'; put '%end;'; put '%end;'; put 'run;'; put 'filename _sjs3 temp lrecl=131068 ;'; put 'data _null_;'; put 'file _sjs3 encoding=''utf-8'';'; put 'if _n_=1 then put "[";'; put 'set &tempds;'; put 'if _n_>1 then put "," @; put'; put '%if &action=ARR %then "[" ; %else "{" ;'; put '%do i=1 %to &numcols;'; put '%if &i>1 %then "," ;'; put '%if &action=OBJ %then """&&name&i"":" ;'; put '"&&name&i"n /* name literal for reserved variable names */'; put '%end;'; put '%if &action=ARR %then "]" ; %else "}" ; ;'; put '/* close out the table */'; put 'data _null_;'; put 'file _sjs3 mod encoding=''utf-8'';'; put 'put '']'';'; put 'run;'; put 'data _null_;'; put 'infile _sjs3 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs3 clear;'; put '%end;'; put 'proc sql;'; put 'drop table &colinfo, &tempds;'; put '%if %substr(&showmeta,1,1)=Y %then %do;'; put 'filename _sjs4 temp lrecl=131068 encoding=''utf-8'';'; put 'data _null_;'; put 'file _sjs4;'; put 'length label $350;'; put 'put ", ""$%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":{""vars"":{";'; put 'do i=1 to &numcols;'; put 'name=quote(trim(symget(cats(''name'',i))));'; put 'format=quote(trim(symget(cats(''fmt'',i))));'; put 'label=quote(prxchange(''s/\\/\\\\/'',-1,trim(symget(cats(''label'',i)))));'; put 'length=quote(trim(symget(cats(''length'',i))));'; put 'type=quote(trim(symget(cats(''typelong'',i))));'; put 'if i>1 then put "," @@;'; put 'put name '':{"format":'' format '',"label":'' label'; put ''',"length":'' length '',"type":'' type ''}'';'; put 'end;'; put 'put ''}}'';'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs4 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs4 clear;'; put '%end;'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put 'data _null_; file &jref encoding=''utf-8'' mod ;'; put 'put "}";'; put 'run;'; put '%end;'; put '%mend mp_jsonout;'; put '/**'; put '@file mm_webout.sas'; put '@brief Send data to/from SAS Stored Processes'; put '@details This macro should be added to the start of each Stored Process,'; put '**immediately** followed by a call to:'; put '%mm_webout(FETCH)'; put 'This will read all the input data and create same-named SAS datasets in the'; put 'WORK library. You can then insert your code, and send data back using the'; put 'following syntax:'; put 'data some datasets; * make some data ;'; put 'retain some columns;'; put 'run;'; put '%mm_webout(OPEN)'; put '%mm_webout(ARR,some) * Array format, fast, suitable for large tables ;'; put '%mm_webout(OBJ,datasets) * Object format, easier to work with ;'; put 'Finally, wrap everything up send some helpful system variables too'; put '%mm_webout(CLOSE)'; put '@param [in] action Either FETCH, OPEN, ARR, OBJ or CLOSE'; put '@param [in] ds The dataset to send back to the frontend'; put '@param [out] dslabel= Value to use instead of table name for sending to JSON'; put '@param [in] fmt= (N) Setting Y converts all vars to their formatted values'; put '@param [out] fref= (_webout) The fileref to which to write the JSON'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] workobs= (0) When set to a positive integer, will create a new'; put 'output object (WORK) which contains this number of observations from all'; put 'tables in the WORK library.'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to output JSON'; put '

SAS Macros

'; put '@li mp_jsonout.sas'; put '

Related Macros

'; put '@li ms_webout.sas'; put '@li mv_webout.sas'; put '@version 9.3'; put '@author Allan Bowe'; put '**/'; put '%macro mm_webout(action,ds,dslabel=,fref=_webout,fmt=N,missing=NULL'; put ',showmeta=N,maxobs=MAX,workobs=0'; put ');'; put '%global _webin_file_count _webin_fileref1 _webin_name1 _program _debug'; put 'sasjs_tables;'; put '%local i tempds jsonengine;'; put '/* see https://github.com/sasjs/core/issues/41 */'; put '%if "%upcase(&SYSENCODING)" ne "UTF-8" %then %let jsonengine=PROCJSON;'; put '%else %let jsonengine=DATASTEP;'; put '%if &action=FETCH %then %do;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'options mprint notes mprintnest;'; put '%end;'; put '%let _webin_file_count=%eval(&_webin_file_count+0);'; put '/* now read in the data */'; put '%do i=1 %to &_webin_file_count;'; put '%if &_webin_file_count=1 %then %do;'; put '%let _webin_fileref1=&_webin_fileref;'; put '%let _webin_name1=&_webin_name;'; put '%end;'; put 'data _null_;'; put 'infile &&_webin_fileref&i termstr=crlf;'; put 'input;'; put 'call symputx(''input_statement'',_infile_);'; put 'putlog "&&_webin_name&i input statement: " _infile_;'; put 'stop;'; put 'data &&_webin_name&i;'; put 'infile &&_webin_fileref&i firstobs=2 dsd termstr=crlf encoding=''utf-8'';'; put 'input &input_statement;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'if _n_<20 then putlog _infile_;'; put '%end;'; put 'run;'; put '%let sasjs_tables=&sasjs_tables &&_webin_name&i;'; put '%end;'; put '%end;'; put '%else %if &action=OPEN %then %do;'; put '/* fix encoding */'; put 'OPTIONS NOBOMFILE;'; put '/**'; put '* check xengine type to avoid the below err message:'; put '* > Function is only valid for filerefs using the CACHE access method.'; put '*/'; put 'data _null_;'; put 'set sashelp.vextfl(where=(fileref="_WEBOUT"));'; put 'if xengine=''STREAM'' then do;'; put 'rc=stpsrv_header(''Content-type'',"text/html; encoding=utf-8");'; put 'end;'; put 'run;'; put '/* setup json */'; put 'data _null_;file &fref encoding=''utf-8'';'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutBEGIN<<'';'; put '%end;'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'run;'; put '%end;'; put '%else %if &action=ARR or &action=OBJ %then %do;'; put '%mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt,jref=&fref'; put ',engine=&jsonengine,missing=&missing,showmeta=&showmeta,maxobs=&maxobs'; put ')'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put '/* To avoid issues with _webout on EBI we use a temporary file */'; put 'filename _sjsref temp lrecl=131068;'; put '%if %str(&workobs) > 0 %then %do;'; put '/* if debug mode, send back first XX records of each work table also */'; put 'data;run;%let tempds=%scan(&syslast,2,.);'; put 'ods output Members=&tempds;'; put 'proc datasets library=WORK memtype=data;'; put '%local wtcnt;%let wtcnt=0;'; put 'data _null_;'; put 'set &tempds;'; put 'if not (upcase(name) =:"DATA"); /* ignore temp datasets */'; put 'i+1;'; put 'call symputx(cats(''wt'',i),name,''l'');'; put 'call symputx(''wtcnt'',i,''l'');'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put ",""WORK"":{";'; put '%do i=1 %to &wtcnt;'; put '%let wt=&&wt&i;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'dsid=open("WORK.&wt",''is'');'; put 'nlobs=attrn(dsid,''NLOBS'');'; put 'nvars=attrn(dsid,''NVARS'');'; put 'rc=close(dsid);'; put 'if &i>1 then put '',''@;'; put 'put " ""&wt"" : {";'; put 'put ''"nlobs":'' nlobs;'; put 'put '',"nvars":'' nvars;'; put '%mp_jsonout(OBJ,&wt,jref=_sjsref,dslabel=first10rows,showmeta=Y'; put ',maxobs=&workobs'; put ')'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put '%end;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put 'run;'; put '%end;'; put '/* close off json */'; put 'data _null_;file _sjsref mod encoding=''utf-8'';'; put 'length SYSPROCESSNAME syserrortext syswarningtext autoexec $512;'; put 'put ",""_DEBUG"" : ""&_debug"" ";'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'autoexec=quote(urlencode(trim(getoption(''autoexec''))));'; put 'put '',"AUTOEXEC" : '' autoexec;'; put 'put ",""MF_GETUSER"" : ""%mf_getuser()"" ";'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'put ",""SYSENCODING"" : ""&sysencoding"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSPROCESSID"" : ""&SYSPROCESSID"" ";'; put 'put ",""SYSPROCESSMODE"" : ""&SYSPROCESSMODE"" ";'; put 'SYSPROCESSNAME=quote(urlencode(cats(SYSPROCESSNAME)));'; put 'put ",""SYSPROCESSNAME"" : " SYSPROCESSNAME;'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put '',"SYSWARNINGTEXT" : '' syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'length memsize $32;'; put 'memsize="%sysfunc(INPUTN(%sysfunc(getoption(memsize)), best.),sizekmg.)";'; put 'memsize=quote(cats(memsize));'; put 'put '',"MEMSIZE" : '' memsize;'; put 'put "}" @;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutEND<<'';'; put '%end;'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjsref lrecl=1 recfm=n;'; put 'file &fref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjsref clear;'; put '%end;'; put '%mend mm_webout;'; put '%macro webout(action,ds,dslabel=,fmt=,missing=NULL,showmeta=NO,maxobs=MAX);'; put '%mm_webout(&action,ds=&ds,dslabel=&dslabel,fmt=&fmt'; put ',missing=&missing'; put ',showmeta=&showmeta'; put ',maxobs=&maxobs'; put ') %mend;'; put '/* provide additional debug info */'; put '%global _program;'; put '%put &=syscc;'; put '%put user=%mf_getuser();'; put '%put pgm=&_program;'; put '%put timestamp=%sysfunc(datetime(),datetime19.);'; put '* Service Variables start;'; put '* Service Variables end;'; put '* SAS Macros start;'; put '%macro mf_getapploc(pgm);'; put '%if "&pgm"="" %then %do;'; put '%if %symexist(_program) %then %let pgm=&_program;'; put '%else %do;'; put '%put &sysmacroname: No value provided and no _program variable available;'; put '%return;'; put '%end;'; put '%end;'; put '%local root;'; put '/**'; put '* First check we are not in the tests/macros folder (which has no subfolders)'; put '* or specifically in the testsetup or testteardown services'; put '*/'; put '%if %index(&pgm,/tests/macros/)'; put 'or %index(&pgm,/tests/testsetup)'; put 'or %index(&pgm,/tests/testteardown)'; put '%then %do;'; put '%let root=%substr(&pgm,1,%index(&pgm,/tests)-1);'; put '&root'; put '%return;'; put '%end;'; put '/**'; put '* Next, move up two levels to avoid matches on subfolder or service name'; put '*/'; put '%let root=%substr(&pgm,1,%length(&pgm)-%length(%scan(&pgm,-1,/))-1);'; put '%let root=%substr(&root,1,%length(&root)-%length(%scan(&root,-1,/))-1);'; put '%if %index(&root,/tests/) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/tests/)-1);'; put '%end;'; put '%else %if %index(&root,/services) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/services)-1);'; put '%end;'; put '%else %if %index(&root,/jobs) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/jobs)-1);'; put '%end;'; put '%else %put &sysmacroname: Could not find an app location from &pgm;'; put '&root'; put '%mend mf_getapploc ;'; put '%macro mf_getuniquefileref(prefix=_,maxtries=1000,lrecl=32767);'; put '%local rc fname;'; put '%if &prefix=0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%end;'; put '%else %do;'; put '%local x len;'; put '%let len=%eval(8-%length(&prefix));'; put '%let x=0;'; put '%do x=0 %to &maxtries;'; put '%let fname=&prefix%substr(%sysfunc(ranuni(0)),3,&len);'; put '%if %sysfunc(fileref(&fname)) > 0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%return;'; put '%end;'; put '%end;'; put '%put unable to find available fileref after &maxtries attempts;'; put '%end;'; put '%mend mf_getuniquefileref;'; put '%macro mp_abort(mac=mp_abort.sas, type=, msg=, iftrue=%str(1=1)'; put ', errds=work.mp_abort_errds'; put ', mode=REGULAR'; put ')/*/STORE SOURCE*/;'; put '%global sysprocessmode sysprocessname sasjs_stpsrv_header_loc sasjsprocessmode;'; put '%local fref fid i;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%put NOTE: /// mp_abort macro executing //;'; put '%if %length(&mac)>0 %then %put NOTE- called by &mac;'; put '%put NOTE - &msg;'; put '%if %symexist(_SYSINCLUDEFILEDEVICE)'; put '/* abort cancel FILE does not restart outside the INCLUDE on Viya 3.5 */'; put 'and %superq(SYSPROCESSNAME) ne %str(Compute Server)'; put '%then %do;'; put '%if "*&_SYSINCLUDEFILEDEVICE*" ne "**" %then %do;'; put 'data &errds;'; put 'iftrue=''1=1'';'; put 'length mac $100 msg $5000;'; put 'mac=symget(''mac'');'; put 'msg=symget(''msg'');'; put 'run;'; put 'data _null_;'; put 'abort cancel FILE;'; put 'run;'; put '%return;'; put '%end;'; put '%end;'; put '/* Web App Context */'; put '%if %symexist(_PROGRAM)'; put 'or %superq(SYSPROCESSNAME) = %str(Compute Server)'; put 'or &mode=INCLUDE'; put '%then %do;'; put 'options obs=max replace mprint;'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5"'; put '%then %do;'; put 'options nosyntaxcheck;'; put '%end;'; put '%if &mode=INCLUDE %then %do;'; put '%if %sysfunc(exist(&errds))=1 %then %do;'; put 'data _null_;'; put 'set &errds;'; put 'call symputx(''iftrue'',iftrue,''l'');'; put 'call symputx(''mac'',mac,''l'');'; put 'call symputx(''msg'',msg,''l'');'; put 'putlog (_all_)(=);'; put 'run;'; put '%if (&iftrue)=0 %then %return;'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: No include errors found;'; put '%return;'; put '%end;'; put '%end;'; put '/* extract log errs / warns, if exist */'; put '%local logloc logline;'; put '%global logmsg; /* capture global messages */'; put '%if %symexist(SYSPRINTTOLOG) %then %let logloc=&SYSPRINTTOLOG;'; put '%else %let logloc=%qsysfunc(getoption(LOG));'; put 'proc printto log=log;run;'; put '%let logline=0;'; put '%if %length(&logloc)>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input; putlog _infile_;'; put 'i=1;'; put 'retain logonce 0;'; put 'if ('; put '_infile_=:"%str(WARN)ING" or _infile_=:"%str(ERR)OR"'; put ') and logonce=0 then'; put 'do;'; put 'call symputx(''logline'',_n_);'; put 'logonce+1;'; put 'end;'; put 'run;'; put '/* capture log including lines BEFORE the err */'; put '%if &logline>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input;'; put 'i=1;'; put 'stoploop=0;'; put 'if _n_ ge &logline-15 and stoploop=0 then do until (i>22);'; put 'call symputx(''logmsg'',catx(''\n'',symget(''logmsg''),_infile_));'; put 'input;'; put 'i+1;'; put 'stoploop=1;'; put 'end;'; put 'if stoploop=1 then stop;'; put 'run;'; put '%end;'; put '%end;'; put '%if %symexist(SYS_JES_JOB_URI) %then %do;'; put '/* setup webout for Viya */'; put 'options nobomfile;'; put '%if "X&SYS_JES_JOB_URI.X"="XX" %then %do;'; put 'filename _webout temp lrecl=999999 mod;'; put '%end;'; put '%else %do;'; put 'filename _webout filesrvc parenturi="&SYS_JES_JOB_URI"'; put 'name="_webout.json" lrecl=999999 mod;'; put '%end;'; put '%end;'; put '%else %if %sysfunc(filename(fref,&sasjs_stpsrv_header_loc))=0 %then %do;'; put 'options nobomfile;'; put '/* set up http header for SASjs Server */'; put '%let fid=%sysfunc(fopen(&fref,A));'; put '%if &fid=0 %then %do;'; put '%put %str(ERR)OR: %sysfunc(sysmsg());'; put '%return;'; put '%end;'; put '%let rc=%sysfunc(fput(&fid,%str(Content-Type: application/json)));'; put '%let rc=%sysfunc(fwrite(&fid));'; put '%let rc=%sysfunc(fclose(&fid));'; put '%let rc=%sysfunc(filename(&fref));'; put '%end;'; put '/* send response in SASjs JSON format */'; put 'data _null_;'; put 'file _webout mod lrecl=32000 encoding=''utf-8'';'; put 'length msg syswarningtext syserrortext $32767 mode $10 ;'; put 'sasdatetime=datetime();'; put 'msg=symget(''msg'');'; put '%if &logline>0 %then %do;'; put 'msg=cats(msg,''\n\nLog Extract:\n'',symget(''logmsg''));'; put '%end;'; put '/* escape the escapes */'; put 'msg=tranwrd(msg,''\'',''\\'');'; put '/* escape the quotes */'; put 'msg=tranwrd(msg,''"'',''\"'');'; put '/* ditch the CRLFs as chrome complains */'; put 'msg=compress(msg,,''kw'');'; put '/* quote without quoting the quotes (which are escaped instead) */'; put 'msg=cats(''"'',msg,''"'');'; put 'if symexist(''_debug'') then debug=quote(trim(symget(''_debug'')));'; put 'else debug=''""'';'; put 'if symget(''sasjsprocessmode'')=''Stored Program'' then mode=''SASJS'';'; put 'if mode ne ''SASJS'' then put ''>>weboutBEGIN<<'';'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'put '',"sasjsAbort" : [{'';'; put 'put '' "MSG":'' msg ;'; put 'put '' ,"MAC": "'' "&mac" ''"}]'';'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'put '',"_DEBUG":'' debug ;'; put 'if symexist(''_metauser'') then do;'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put 'end;'; put 'if symexist(''SYS_JES_JOB_URI'') then do;'; put 'SYS_JES_JOB_URI=quote(trim(symget(''SYS_JES_JOB_URI'')));'; put 'put '',"SYS_JES_JOB_URI": '' SYS_JES_JOB_URI;'; put 'end;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put ",""SYSWARNINGTEXT"" : " syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'put "}" ;'; put 'if mode ne ''SASJS'' then put ''>>weboutEND<<'';'; put 'run;'; put '%put _all_;'; put '%if "&sysprocessmode " = "SAS Stored Process Server " %then %do;'; put 'data _null_;'; put 'putlog ''stpsrvset program err and syscc'';'; put 'rc=stpsrvset(''program error'', 0);'; put 'call symputx("syscc",0,"g");'; put 'run;'; put '%if &sysscp=WIN'; put 'and 1=0 /* deprecating this logic until we figure out a consistent abort */'; put 'and "%substr(%str(&sysvlong ),1,8)"="9.04.01M"'; put 'and "%substr(%str(&sysvlong ),9,1)">"5" %then %do;'; put '/* skip approach (below) does not work in windows m6+ envs */'; put 'endsas;'; put '%end;'; put '%else %do;'; put '/**'; put '* endsas kills 9.4m3 deployments by orphaning multibridges.'; put '* Abort variants are ungraceful (non zero return code)'; put '* This approach lets SAS run silently until the end :-)'; put '* Caution - fails when called within a %include within a macro'; put '* Use mp_include() to handle this.'; put '*/'; put 'filename skip temp;'; put 'data _null_;'; put 'file skip;'; put 'put ''%macro skip();'';'; put 'comment ''%mend skip; -> fix lint '';'; put 'put ''%macro skippy();'';'; put 'comment ''%mend skippy; -> fix lint '';'; put 'run;'; put '%inc skip;'; put '%end;'; put '%end;'; put '%else %if "&sysprocessmode " = "SAS Compute Server " %then %do;'; put '/* endsas kills the session making it harder to fetch results */'; put 'data _null_;'; put 'syswarningtext=symget(''syswarningtext'');'; put 'syserrortext=symget(''syserrortext'');'; put 'abort_msg=symget(''msg'');'; put 'syscc=symget(''syscc'');'; put 'sysuserid=symget(''sysuserid'');'; put 'iftrue=symget(''iftrue'');'; put 'put (_all_)(/=);'; put 'call symputx(''syscc'',0);'; put 'abort cancel nolist;'; put 'run;'; put '%end;'; put '%else %do;'; put '%abort cancel;'; put '%end;'; put '%end;'; put '%else %do;'; put '%put _all_;'; put '%abort cancel;'; put '%end;'; put '%mend mp_abort;'; put '/** @endcond */'; put '%macro mm_getstpcode('; put 'tree=/User Folders/sasdemo/somestp'; put ',name='; put ',outloc=0'; put ',outref=0'; put ',mDebug=1'; put ',showlog=NO'; put ');'; put '%local mD;'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing &sysmacroname..sas;'; put '%&mD.put _local_;'; put '%if %length(&name)>0 %then %let name=/&name;'; put '/* first, check if STP exists */'; put '%local tsuri;'; put '%let tsuri=stopifempty ;'; put 'data _null_;'; put 'format type uri tsuri value $200.;'; put 'call missing (of _all_);'; put 'path="&tree&name(StoredProcess)";'; put '/* first, find the STP ID */'; put 'if metadata_pathobj("",path,"StoredProcess",type,uri)>0 then do;'; put '/* get sourcecode */'; put 'cnt=1;'; put 'do while (metadata_getnasn(uri,"Notes",cnt,tsuri)>0);'; put 'rc=metadata_getattr(tsuri,"Name",value);'; put '&mD.put tsuri= value=;'; put 'if value="SourceCode" then do;'; put '/* found it! */'; put 'rc=metadata_getattr(tsuri,"Id",value);'; put 'call symputx(''tsuri'',value,''l'');'; put 'stop;'; put 'end;'; put 'cnt+1;'; put 'end;'; put 'end;'; put 'else put (_all_)(=);'; put 'run;'; put '%mp_abort(iftrue= (&tsuri=stopifempty)'; put ',mac=mm_getstpcode'; put ',msg=%str(&tree&name.(StoredProcess) not found!)'; put ')'; put '/**'; put '* Now we can extract the textstore'; put '*/'; put 'filename __getdoc temp lrecl=10000000;'; put 'proc metadata'; put 'in="$METAREPOSITORY'; put ''; put 'SAS1"'; put 'out=__getdoc ;'; put 'run;'; put '/* find the beginning of the text */'; put '%local start;'; put 'data _null_;'; put 'infile __getdoc lrecl=10000;'; put 'input;'; put 'start=index(_infile_,''StoredText="'');'; put 'if start then do;'; put 'call symputx("start",start+11);'; put '*putlog ''"'' _infile_ ''"'';'; put 'end;'; put 'stop;'; put '%local outeng;'; put '%if "&outloc"="0" %then %let outeng=TEMP;'; put '%else %let outeng="&outloc";'; put '%local fref;'; put '%if &outref=0 %then %let fref=%mf_getuniquefileref();'; put '%else %let fref=&outref;'; put '/* read the content, byte by byte, resolving escaped chars */'; put 'filename &fref &outeng lrecl=100000;'; put 'data _null_;'; put 'length filein 8 fileid 8;'; put 'filein = fopen("__getdoc","I",1,"B");'; put 'fileid = fopen("&fref","O",1,"B");'; put 'rec = "20"x;'; put 'length entity $6;'; put 'do while(fread(filein)=0);'; put 'x+1;'; put 'if x>&start then do;'; put 'rc = fget(filein,rec,1);'; put 'if rec=''"'' then leave;'; put 'else if rec="&" then do;'; put 'entity=rec;'; put 'do until (rec=";");'; put 'if fread(filein) ne 0 then goto getout;'; put 'rc = fget(filein,rec,1);'; put 'entity=cats(entity,rec);'; put 'end;'; put 'select (entity);'; put 'when (''&'' ) rec=''&'' ;'; put 'when (''<'' ) rec=''<'' ;'; put 'when (''>'' ) rec=''>'' ;'; put 'when (''''') rec="''" ;'; put 'when (''"'') rec=''"'' ;'; put 'when ('' '') rec=''0A''x;'; put 'when ('' '') rec=''0D''x;'; put 'when (''$'' ) rec=''$'' ;'; put 'when ('' '') rec=''09''x;'; put 'otherwise putlog "%str(WARN)ING: missing value for " entity=;'; put 'end;'; put 'rc =fput(fileid, substr(rec,1,1));'; put 'rc =fwrite(fileid);'; put 'end;'; put 'else do;'; put 'rc =fput(fileid,rec);'; put 'rc =fwrite(fileid);'; put 'end;'; put 'end;'; put 'end;'; put 'getout:'; put 'rc=fclose(filein);'; put 'rc=fclose(fileid);'; put 'run;'; put '%if &showlog=YES %then %do;'; put 'data _null_;'; put 'infile &fref lrecl=32767 end=last;'; put 'input;'; put 'if _n_=1 then putlog ''>>stpcodeBEGIN<<'';'; put 'putlog _infile_;'; put 'if last then putlog ''>>stpcodeEND<<'';'; put 'run;'; put '%end;'; put 'filename __getdoc clear;'; put '%if &outref=0 %then %do;'; put 'filename &fref clear;'; put '%end;'; put '%mend mm_getstpcode;'; put '%macro dc_getsettings();'; put '%global _program;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(syscc=&syscc on entry (&syswarningtext &syserrortext))'; put ')'; put '%if %length(&_PROGRAM)>1 %then %let root=&_program;'; put '%else %do;'; put '%global _metauser;'; put '%let _metauser=&sysuserid;'; put '/* to mimic a "real" _program we need to give a dummy role and stp name */'; put '%let root=/dummyRole/dummyName;'; put '%end;'; put '/* the DC precode is stored in the Admin folder in the root of'; put 'the project. Lets find that root. */'; put '%put &=root;'; put '%let root=%mf_getapploc();'; put '%put &=root;'; put '/* Now we know the root location we can retrieve the params */'; put '%let temploc=%sysfunc(pathname(work))/settings.txt;'; put '%mm_getstpcode(tree=&root/services/public'; put ',name=Data_Controller_Settings'; put ',outloc=&temploc'; put ')'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Unable to run getstpcode)'; put ')'; put 'filename _getsets "&temploc" lrecl=2000;'; put '/*'; put 'Do not use mp_include here - this puts a copy in every service, which creates'; put 'compilation problems when calling services from mp_include'; put '*/'; put '%inc _getsets/source2;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Problem running &sysmacroname (&syswarningtext &syserrortext))'; put ')'; put '%mend dc_getsettings;'; put '%macro mf_fmtdttm('; put ')/*/STORE SOURCE*/;'; put '%if "&sysver"="9.2" or "&sysver"="9.3"'; put 'or ("&sysver"="9.4" and "%substr(&SYSVLONG,9,1)" le "3")'; put 'or "%substr(&sysver,1,1)"="4"'; put 'or "%substr(&sysver,1,1)"="5"'; put '%then %do;DATETIME19.3%end;'; put '%else %do;E8601DT26.6%end;'; put '%mend mf_fmtdttm;'; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '%macro mp_init(prefix=SASJS'; put ')/*/STORE SOURCE*/;'; put '%if %symexist(SASJS_PREFIX) %then %return; /* only run once */'; put '%global'; put 'SASJS_PREFIX /* the ONLY hard-coded global macro variable in SASjs */'; put '&prefix._FUNCTIONS /* used in mcf_init() to track core function compilation */'; put '&prefix._INIT_NUM /* initialisation time as numeric */'; put '&prefix._INIT_DTTM /* initialisation time in E8601DT26.6 format */'; put '&prefix.WORK /* avoid typing %sysfunc(pathname(work)) every time */'; put ';'; put '%let sasjs_prefix=&prefix;'; put 'data _null_;'; put 'dttm=datetime();'; put 'call symputx("&sasjs_prefix._init_num",dttm,''g'');'; put 'call symputx("&sasjs_prefix._init_dttm",put(dttm,E8601DT26.6),''g'');'; put 'call symputx("&sasjs_prefix.work",pathname(''WORK''),''g'');'; put 'run;'; put 'options'; put 'compress=CHAR /* default is none so ensure we have something! */'; put 'datastmtchk=ALLKEYWORDS /* protection from overwriting input datasets */'; put 'errorcheck=STRICT /* catch errs in libname/filename statements */'; put 'fmterr /* ensure err when a format cannot be found */'; put 'mergenoby=%str(ERR)OR /* throw err when a merge has no BY variables */'; put 'missing=. /* changing this can cause hard to detect errs */'; put 'noquotelenmax /* avoid warnings for long strings */'; put 'noreplace /* avoid overwriting permanent datasets */'; put 'ps=max /* reduce log size slightly */'; put 'ls=max /* reduce log even more and avoid word truncation */'; put 'validmemname=COMPATIBLE /* avoid special characters etc in table names */'; put 'validvarname=V7 /* avoid special characters etc in variable names */'; put 'varinitchk=%str(ERR)OR /* avoid data mistakes from variable name typos */'; put 'varlenchk=%str(ERR)OR /* fail hard if truncation (data loss) can result */'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5" %then %do;'; put 'noautocorrect /* disallow misspelled procedure names */'; put 'dsoptions=note2err /* undocumented - convert bad NOTEs to ERRs */'; put '%end;'; put ';'; put '%mend mp_init;'; put '%macro mpeinit(fetch=YES);'; put '%global mpeinit'; put 'mpeadmins /* group with unrestricted Meditor access */'; put 'mpelocapprovals /* location for landing and staging files */'; put 'mpelib /* location of configuration tables for DC */'; put 'dc_repo_users /* location of user / group metadata */'; put 'dc_licence_key /* extracted in dc_getsettings */'; put 'dc_activation_key /* extracted in dc_getsettings */'; put 'dc_locale /* extracted in dc_getsettings */'; put 'dc_dttmtfmt /* can be overridden in dc_getsettings */'; put '_debug'; put ';'; put '%if &mpeinit=1 %then %return;'; put '%else %let mpeinit=1;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(Problem on service startup (&syswarningtext &syserrortext))'; put ')'; put '%mp_init()'; put '%if &fetch=YES %then %do;'; put '%webout(FETCH)'; put '%end;'; put '%global _CLIENTNAME;'; put '%mp_abort(iftrue= (&_CLIENTNAME=SAS Enterprise Guide)'; put ',mac=&_program..sas'; put ',msg=%str(Data Controller is a web app and should not be executed from EG)'; put ')'; put 'options urlencoding=utf8 nobomfile lrecl=32767;'; put '%let perf=%sysfunc(datetime());'; put '%put perfdiff: 0;'; put '%let dc_locale=SYSTEM; /* default if not set */'; put '/**'; put '* E8601DT26.6 has widest database support - but not all SAS flavours can'; put '* handle it. Override in the settings STP if needed.'; put '*/'; put 'data _null_;'; put 'dc_dttmtfmt=''"%sysfunc(datetime(),''!!"%mf_fmtdttm()"!!'')"dt'';'; put 'call symputx(''dc_dttmtfmt'',dc_dttmtfmt);'; put 'put dc_dttmtfmt=;'; put 'run;'; put '%put &=dc_dttmtfmt;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc prior to dc_getsettings)'; put ')'; put '%dc_getsettings()'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc after dc_getsettings)'; put ')'; put 'data _null_;'; put 'set &DC_LIBREF..mpe_config(where=('; put 'var_scope="DC"'; put 'and &dc_dttmtfmt lt tx_to'; put 'and var_active=1'; put '));'; put 'call symputx(var_name,var_value,''G'');'; put 'putlog var_name "=" var_value;'; put 'run;'; put '%let mpelib=&dc_libref;'; put '%let mpeadmins=&dc_admin_group;'; put '%let mpelocapprovals=&dc_staging_area;'; put '%let dc_repo_users=&dc_repo_users;'; put '%if &dc_locale ne SYSTEM %then %do;'; put 'options locale=&dc_locale;'; put '%end;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(Problem during compilation or with STP precode (&syswarningtext))'; put ')'; put '%mend mpeinit;'; put '%macro mf_mval(var);'; put '%if %symexist(&var) %then %do;'; put '%superq(&var)'; put '%end;'; put '%mend mf_mval;'; put '%macro mf_trimstr(basestr,trimstr);'; put '%local baselen trimlen trimval;'; put '/* return if basestr is shorter than trimstr (or 0) */'; put '%let baselen=%length(%superq(basestr));'; put '%let trimlen=%length(%superq(trimstr));'; put '%if &baselen < &trimlen or &baselen=0 %then %return;'; put '/* obtain the characters from the end of basestr */'; put '%let trimval=%qsubstr(%superq(basestr)'; put ',%length(%superq(basestr))-&trimlen+1'; put ',&trimlen);'; put '/* compare and if matching, chop it off! */'; put '%if %superq(basestr)=%superq(trimstr) %then %do;'; put '%return;'; put '%end;'; put '%else %if %superq(trimval)=%superq(trimstr) %then %do;'; put '%qsubstr(%superq(basestr),1,%length(%superq(basestr))-&trimlen)'; put '%end;'; put '%else %do;'; put '&basestr'; put '%end;'; put '%mend mf_trimstr;'; put '%macro mf_getplatform(switch'; put ')/*/STORE SOURCE*/;'; put '%local a b c;'; put '%if &switch.NONE=NONE %then %do;'; put '%if %symexist(sasjsprocessmode) %then %do;'; put '%if &sasjsprocessmode=Stored Program %then %do;'; put 'SASJS'; put '%return;'; put '%end;'; put '%end;'; put '%if %symexist(sysprocessmode) %then %do;'; put '%if "&sysprocessmode"="SAS Object Server"'; put 'or "&sysprocessmode"= "SAS Compute Server" %then %do;'; put 'SASVIYA'; put '%end;'; put '%else %if "&sysprocessmode"="SAS Stored Process Server"'; put 'or "&sysprocessmode"="SAS Workspace Server"'; put '%then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if %symexist(_metaport) or %symexist(_metauser) %then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if &switch=SASSTUDIO %then %do;'; put '/* return the version of SAS Studio else 0 */'; put '%if %mf_mval(_CLIENTAPP)=%str(SAS Studio) %then %do;'; put '%let a=%mf_mval(_CLIENTVERSION);'; put '%let b=%scan(&a,1,.);'; put '%if %eval(&b >2) %then %do;'; put '&b'; put '%end;'; put '%else 0;'; put '%end;'; put '%else 0;'; put '%end;'; put '%else %if &switch=VIYARESTAPI %then %do;'; put '%mf_trimstr(%sysfunc(getoption(servicesbaseurl)),/)'; put '%end;'; put '%mend mf_getplatform;'; put '%macro mpeterm();'; put '%local oldloc;'; put 'data _null_;'; put 'if symexist(''SYSPRINTTOLOG'') then oldloc=symget(''SYSPRINTTOLOG'');'; put 'else oldloc=getoption(''LOG'');'; put 'if subpad(oldloc,1,1) not in (''"'',"''",'' '') then oldloc=quote(cats(oldloc));'; put 'call symputx(''oldloc'',oldloc,''l'');'; put 'run;'; put '%if %length(&oldloc)>0 %then %do;'; put 'proc printto log=log;'; put 'run;'; put 'data _null_;'; put 'infile &oldloc;'; put 'input; putlog _infile_;'; put 'run;'; put '%end;'; put '%if %sysfunc(exist(&dc_libref..mpe_requests)) and %mf_getplatform() ne SASVIYA'; put '%then %do;'; put 'data ;'; put 'if 0 then set &dc_libref..mpe_requests;'; put 'request_dttm=%sysfunc(datetime());'; put 'request_user="%mf_getuser()";'; put 'request_service="%scan(&_program,-2,/)/%scan(&_program,-1,/)";'; put 'request_params='''';'; put 'output;stop;'; put 'proc append base=&dc_libref..mpe_requests data=&syslast force nowarn;'; put 'run;'; put '%end;'; put '%mend mpeterm;'; put '%macro mm_assignlib('; put 'libref'; put ',mAbort=HARD'; put ')/*/STORE SOURCE*/;'; put '%local mp_abort msg;'; put '%let mp_abort=0;'; put '%if %sysfunc(libref(&libref)) %then %do;'; put 'data _null_;'; put 'length liburi LibName msg $200;'; put 'call missing(of _all_);'; put 'nobj=metadata_getnobj("omsobj:SASLibrary?@Libref=''&libref''",1,liburi);'; put 'if nobj=1 then do;'; put 'rc=metadata_getattr(liburi,"Name",LibName);'; put '/* now try and assign it */'; put 'if libname("&libref",,''meta'',cats(''liburi="'',liburi,''";'')) ne 0 then do;'; put 'putlog "&libref could not be assigned";'; put 'putlog liburi=;'; put '/**'; put '* Fetch the system message for display in the abort modal. This is'; put '* not always helpful though. One example, previously received:'; put '* NOTE: Libref XX refers to the same library metadata as libref XX.'; put '*/'; put 'msg=sysmsg();'; put 'if msg=:''ERROR: Libref SAVE is not assigned.'' then do;'; put 'msg=catx(" ",'; put '"Could not assign %upcase(&libref).",'; put '"Please check metadata permissions! Libname:",libname,'; put '"Liburi:",liburi'; put ');'; put 'end;'; put 'else if msg="ERROR: User does not have appropriate authorization "!!'; put '"level for library SAVE."'; put 'then do;'; put 'msg=catx(" ",'; put '"ERROR: User does not have appropriate authorization level",'; put '"for library %upcase(&libref), libname:",libname,'; put '"Liburi:",liburi'; put ');'; put 'end;'; put 'call symputx(''msg'',msg,''l'');'; put 'if "&mabort"=''HARD'' then call symputx(''mp_abort'',1,''l'');'; put 'end;'; put 'else do;'; put 'put (_all_)(=);'; put 'call symputx(''libname'',libname,''L'');'; put 'call symputx(''liburi'',liburi,''L'');'; put 'end;'; put 'end;'; put 'else if nobj>1 then do;'; put 'if "&mabort"=''HARD'' then call symputx(''mp_abort'',1);'; put 'call symputx(''msg'',"More than one library with libref=&libref");'; put 'end;'; put 'else do;'; put 'if "&mabort"=''HARD'' then call symputx(''mp_abort'',1);'; put 'call symputx(''msg'',"Library &libref not found in metadata");'; put 'end;'; put 'run;'; put '%put NOTE: &msg;'; put '%end;'; put '%else %do;'; put '%put NOTE: Library &libref is already assigned;'; put '%end;'; put '%mp_abort(iftrue= (&mp_abort=1)'; put ',mac=mm_assignlib.sas'; put ',msg=%superq(msg)'; put ')'; put '%mend mm_assignlib;'; put '/** @cond */'; put '%macro mf_getengine(libref'; put ')/*/STORE SOURCE*/;'; put '%local dsid engnum rc engine;'; put '/* in case the parameter is a libref.tablename, pull off just the libref */'; put '%let libref = %upcase(%scan(&libref, 1, %str(.)));'; put '%let dsid=%sysfunc('; put 'open(sashelp.vlibnam(where=(libname="%upcase(&libref)")),i)'; put ');'; put '%if (&dsid ^= 0) %then %do;'; put '%let engnum=%sysfunc(varnum(&dsid,ENGINE));'; put '%let rc=%sysfunc(fetch(&dsid));'; put '%let engine=%sysfunc(getvarc(&dsid,&engnum));'; put '%put &libref. ENGINE is &engine.;'; put '%let rc= %sysfunc(close(&dsid));'; put '%end;'; put '%upcase(&engine)'; put '%mend mf_getengine;'; put '/** @endcond */'; put '%macro mm_assigndirectlib('; put 'libref'; put ',open_passthrough='; put ',sql_options='; put ',mDebug=0'; put ',mAbort=0'; put ')/*/STORE SOURCE*/;'; put '%local mD;'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing mm_assigndirectlib.sas;'; put '%&mD.put _local_;'; put '%if &mAbort=1 %then %let mAbort=;'; put '%else %let mAbort=%str(*);'; put '%&mD.put NOTE: Creating direct (non META) connection to &libref library;'; put '%local cur_engine;'; put '%let cur_engine=%mf_getengine(&libref);'; put '%if &cur_engine ne META and &cur_engine ne %then %do;'; put '%put NOTE: &libref already has a direct (&cur_engine) libname connection;'; put '%return;'; put '%end;'; put '%else %if %upcase(&libref)=WORK %then %do;'; put '%put NOTE: We already have a direct connection to WORK :-) ;'; put '%return;'; put '%end;'; put '/* need to determine the library ENGINE first */'; put '%local engine;'; put 'data _null_;'; put 'length lib_uri engine $256;'; put 'call missing (of _all_);'; put '/* get URI for the particular library */'; put 'rc1=metadata_getnobj("omsobj:SASLibrary?@Libref =''&libref''",1,lib_uri);'; put '/* get the Engine attribute of the previous object */'; put 'rc2=metadata_getattr(lib_uri,''Engine'',engine);'; put 'putlog "mm_assigndirectlib for &libref:" rc1= lib_uri= rc2= engine=;'; put 'call symputx("liburi",lib_uri,''l'');'; put 'call symputx("engine",engine,''l'');'; put 'run;'; put '/* now obtain engine specific connection details */'; put '%if &engine=BASE %then %do;'; put '%&mD.put NOTE: Retrieving BASE library path;'; put 'data _null_;'; put 'length up_uri $256 path cat_path $1024;'; put 'retain cat_path;'; put 'call missing (of _all_);'; put '/* get all the filepaths of the UsingPackages association */'; put 'i=1;'; put 'rc3=metadata_getnasn("&liburi",''UsingPackages'',i,up_uri);'; put 'do while (rc3>0);'; put '/* get the DirectoryName attribute of the previous object */'; put 'rc4=metadata_getattr(up_uri,''DirectoryName'',path);'; put 'if i=1 then path = ''("''!!trim(path)!!''" '';'; put 'else path ='' "''!!trim(path)!!''" '';'; put 'cat_path = trim(cat_path) !! " " !! trim(path) ;'; put 'i+1;'; put 'rc3=metadata_getnasn("&liburi",''UsingPackages'',i,up_uri);'; put 'end;'; put 'cat_path = trim(cat_path) !! ")";'; put '&mD.putlog "NOTE: Getting physical path for &libref library";'; put '&mD.putlog rc3= up_uri= rc4= cat_path= path=;'; put '&mD.putlog "NOTE: Libname cmd will be:";'; put '&mD.putlog "libname &libref" cat_path;'; put 'call symputx("filepath",cat_path,''l'');'; put 'run;'; put '%if %sysevalf(&sysver<9.4) %then %do;'; put 'libname &libref &filepath;'; put '%end;'; put '%else %do;'; put '/* apply the new filelocks option to cater for temporary locks */'; put 'libname &libref &filepath filelockwait=5;'; put '%end;'; put '%end;'; put '%else %if &engine=REMOTE %then %do;'; put 'data x;'; put 'length rcCon rcProp rc k 3 uriCon uriProp PropertyValue PropertyName'; put 'Delimiter $256 properties $2048;'; put 'retain properties;'; put 'rcCon = metadata_getnasn("&liburi", "LibraryConnection", 1, uriCon);'; put 'rcProp = metadata_getnasn(uriCon, "Properties", 1, uriProp);'; put 'k = 1;'; put 'rcProp = metadata_getnasn(uriCon, "Properties", k, uriProp);'; put 'do while (rcProp > 0);'; put 'rc = metadata_getattr(uriProp , "DefaultValue",PropertyValue);'; put 'rc = metadata_getattr(uriProp , "PropertyName",PropertyName);'; put 'rc = metadata_getattr(uriProp , "Delimiter",Delimiter);'; put 'properties = trim(properties) !! " " !! trim(PropertyName)'; put '!! trim(Delimiter) !! trim(PropertyValue);'; put 'output;'; put 'k+1;'; put 'rcProp = metadata_getnasn(uriCon, "Properties", k, uriProp);'; put 'end;'; put '%&mD.put NOTE: Getting properties for REMOTE SHARE &libref library;'; put '&mD.put _all_;'; put '%&mD.put NOTE: Libname cmd will be:;'; put '%&mD.put libname &libref &engine &properties slibref=&libref;'; put 'call symputx ("properties",trim(properties),''l'');'; put 'run;'; put 'libname &libref &engine &properties slibref=&libref;'; put '%end;'; put '%else %if &engine=OLEDB %then %do;'; put '%&mD.put NOTE: Retrieving OLEDB connection details;'; put 'data _null_;'; put 'length domain datasource provider properties schema'; put 'connx_uri domain_uri conprop_uri lib_uri schema_uri value $256.;'; put 'call missing (of _all_);'; put '/* get source connection ID */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,connx_uri);'; put '/* get connection domain */'; put 'rc1=metadata_getnasn(connx_uri,''Domain'',1,domain_uri);'; put 'rc2=metadata_getattr(domain_uri,''Name'',domain);'; put '&mD.putlog / ''NOTE: '' // ''NOTE- connection id: '' connx_uri ;'; put '&mD.putlog ''NOTE- domain: '' domain;'; put '/* get DSN and PROVIDER from connection properties */'; put 'i=0;'; put 'do until (rc<0);'; put 'i+1;'; put 'rc=metadata_getnasn(connx_uri,''Properties'',i,conprop_uri);'; put 'rc2=metadata_getattr(conprop_uri,''Name'',value);'; put 'if value=''Connection.OLE.Property.DATASOURCE.Name.xmlKey.txt'' then do;'; put 'rc3=metadata_getattr(conprop_uri,''DefaultValue'',datasource);'; put 'end;'; put 'else if value=''Connection.OLE.Property.PROVIDER.Name.xmlKey.txt'' then do;'; put 'rc4=metadata_getattr(conprop_uri,''DefaultValue'',provider);'; put 'end;'; put 'else if value=''Connection.OLE.Property.PROPERTIES.Name.xmlKey.txt'' then'; put 'do;'; put 'rc5=metadata_getattr(conprop_uri,''DefaultValue'',properties);'; put 'end;'; put 'end;'; put '&mD.putlog ''NOTE- dsn/provider/properties: '' /'; put 'datasource provider properties;'; put '&mD.putlog ''NOTE- schema: '' schema // ''NOTE-'';'; put '/* get SCHEMA */'; put 'rc6=metadata_getnasn("&liburi",''UsingPackages'',1,lib_uri);'; put 'rc7=metadata_getattr(lib_uri,''SchemaName'',schema);'; put 'call symputx(''SQL_domain'',domain,''l'');'; put 'call symputx(''SQL_dsn'',datasource,''l'');'; put 'call symputx(''SQL_provider'',provider,''l'');'; put 'call symputx(''SQL_properties'',properties,''l'');'; put 'call symputx(''SQL_schema'',schema,''l'');'; put 'run;'; put '%if %length(&open_passthrough)>0 %then %do;'; put 'proc sql &sql_options;'; put 'connect to OLEDB as &open_passthrough(INSERT_SQL=YES'; put '/* need additional properties to make this work */'; put 'properties=(''Integrated Security''=SSPI'; put '''Persist Security Info''=True'; put '%sysfunc(compress(%str(&SQL_properties),%str(())))'; put ')'; put 'DATASOURCE=&sql_dsn PROMPT=NO'; put 'PROVIDER=&sql_provider SCHEMA=&sql_schema CONNECTION = GLOBAL);'; put '%end;'; put '%else %do;'; put 'LIBNAME &libref OLEDB PROPERTIES=&sql_properties'; put 'DATASOURCE=&sql_dsn PROVIDER=&sql_provider SCHEMA=&sql_schema'; put '%if %length(&sql_domain)>0 %then %do;'; put 'authdomain="&sql_domain"'; put '%end;'; put 'connection=shared;'; put '%end;'; put '%end;'; put '%else %if &engine=ODBC %then %do;'; put '%&mD.put NOTE: Retrieving ODBC connection details;'; put 'data _null_;'; put 'length connx_uri conprop_uri value datasource up_uri schema domprop_uri authdomain $256.;'; put 'call missing (of _all_);'; put '/* get source connection ID */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,connx_uri);'; put '/* get connection properties */'; put 'i=0;'; put 'do until (rc2<0);'; put 'i+1;'; put 'rc2=metadata_getnasn(connx_uri,''Properties'',i,conprop_uri);'; put 'rc3=metadata_getattr(conprop_uri,''Name'',value);'; put 'if value=''Connection.ODBC.Property.DATASRC.Name.xmlKey.txt'' then do;'; put 'rc4=metadata_getattr(conprop_uri,''DefaultValue'',datasource);'; put 'rc2=-1;'; put 'end;'; put 'end;'; put '/* get auth domain */'; put 'autrc=metadata_getnasn(connx_uri,"Domain",1,domprop_uri);'; put 'arc=metadata_getattr(domprop_uri,"Name",authdomain);'; put 'if not missing(authdomain) then authdomain=cats(''AUTHDOMAIN='',authdomain);'; put 'call symputx(''authdomain'',authdomain,''l'');'; put '/* get SCHEMA */'; put 'rc6=metadata_getnasn("&liburi",''UsingPackages'',1,up_uri);'; put 'rc7=metadata_getattr(up_uri,''SchemaName'',schema);'; put '&mD.put rc= connx_uri= rc2= conprop_uri= rc3= value= rc4= datasource='; put 'rc6= up_uri= rc7= schema=;'; put 'call symputx(''SQL_schema'',schema,''l'');'; put 'call symputx(''SQL_dsn'',datasource,''l'');'; put 'run;'; put '%if %length(&open_passthrough)>0 %then %do;'; put 'proc sql &sql_options;'; put 'connect to ODBC as &open_passthrough'; put '(INSERT_SQL=YES DATASRC=&sql_dsn. CONNECTION=global);'; put '%end;'; put '%else %do;'; put 'libname &libref ODBC DATASRC=&sql_dsn SCHEMA=&sql_schema &authdomain;'; put '%end;'; put '%end;'; put '%else %if &engine=POSTGRES %then %do;'; put '%put NOTE: Obtaining POSTGRES library details;'; put 'data _null_;'; put 'length database ignore_read_only_columns direct_exe preserve_col_names'; put 'preserve_tab_names server schema authdomain user password'; put 'prop name value uri urisrc $256.;'; put 'call missing (of _all_);'; put '/* get database value */'; put 'prop=''Connection.DBMS.Property.DB.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,database,"");'; put 'if database^='''' then database=''database=''!!quote(trim(database));'; put 'call symputx(''database'',database,''l'');'; put '/* get IGNORE_READ_ONLY_COLUMNS value */'; put 'prop=''Library.DBMS.Property.DBIROC.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,ignore_read_only_columns,"");'; put 'if ignore_read_only_columns^='''' then ignore_read_only_columns='; put '''ignore_read_only_columns=''!!ignore_read_only_columns;'; put 'call symputx(''ignore_read_only_columns'',ignore_read_only_columns,''l'');'; put '/* get DIRECT_EXE value */'; put 'prop=''Library.DBMS.Property.DirectExe.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,direct_exe,"");'; put 'if direct_exe^='''' then direct_exe=''direct_exe=''!!direct_exe;'; put 'call symputx(''direct_exe'',direct_exe,''l'');'; put '/* get PRESERVE_COL_NAMES value */'; put 'prop=''Library.DBMS.Property.PreserveColNames.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,preserve_col_names,"");'; put 'if preserve_col_names^='''' then preserve_col_names='; put '''preserve_col_names=''!!preserve_col_names;'; put 'call symputx(''preserve_col_names'',preserve_col_names,''l'');'; put '/* get PRESERVE_TAB_NAMES value */'; put '/* be careful with PRESERVE_TAB_NAMES=YES - it will mean your table will'; put 'become case sensitive!! */'; put 'prop=''Library.DBMS.Property.PreserveTabNames.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,preserve_tab_names,"");'; put 'if preserve_tab_names^='''' then preserve_tab_names='; put '''preserve_tab_names=''!!preserve_tab_names;'; put 'call symputx(''preserve_tab_names'',preserve_tab_names,''l'');'; put '/* get SERVER value */'; put 'if metadata_getnasn("&liburi","LibraryConnection",1,uri)>0 then do;'; put 'prop=''Connection.DBMS.Property.SERVER.Name.xmlKey.txt'';'; put 'rc=metadata_getprop(uri,prop,server,"");'; put 'end;'; put 'if server^='''' then server=''server=''!!quote(cats(server));'; put 'call symputx(''server'',server,''l'');'; put '/* get SCHEMA value */'; put 'if metadata_getnasn("&liburi","UsingPackages",1,uri)>0 then do;'; put 'rc=metadata_getattr(uri,"SchemaName",schema);'; put 'end;'; put 'if schema^='''' then schema=''schema=''!!schema;'; put 'call symputx(''schema'',schema,''l'');'; put '/* get AUTHDOMAIN value */'; put '/* this is only useful if the user account contains that auth domain'; put 'if metadata_getnasn("&liburi","DefaultLogin",1,uri)>0 then do;'; put 'rc=metadata_getnasn(uri,"Domain",1,urisrc);'; put 'rc=metadata_getattr(urisrc,"Name",authdomain);'; put 'end;'; put 'if authdomain^='''' then authdomain=''authdomain=''!!quote(trim(authdomain));'; put '*/'; put 'call symputx(''authdomain'',authdomain,''l'');'; put '/* get user & pass */'; put 'if authdomain='''' & metadata_getnasn("&liburi","DefaultLogin",1,uri)>0 then'; put 'do;'; put 'rc=metadata_getattr(uri,"UserID",user);'; put 'rc=metadata_getattr(uri,"Password",password);'; put 'end;'; put 'if user^='''' then do;'; put 'user=''user=''!!quote(trim(user));'; put 'password=''password=''!!quote(trim(password));'; put 'end;'; put 'call symputx(''user'',user,''l'');'; put 'call symputx(''password'',password,''l'');'; put '&md.put _all_;'; put 'run;'; put '%if %length(&open_passthrough)>0 %then %do;'; put '%put %str(WARN)ING: Passthrough option for postgres not yet supported;'; put '%return;'; put '%end;'; put '%else %do;'; put '%if &mdebug=1 %then %do;'; put '%put NOTE: Executing the following:/;'; put '%put NOTE- libname &libref POSTGRES &database &ignore_read_only_columns;'; put '%put NOTE- &direct_exe &preserve_col_names &preserve_tab_names;'; put '%put NOTE- &server &schema &authdomain &user &password //;'; put '%end;'; put 'libname &libref POSTGRES &database &ignore_read_only_columns &direct_exe'; put '&preserve_col_names &preserve_tab_names &server &schema &authdomain'; put '&user &password;'; put '%end;'; put '%end;'; put '%else %if &engine=ORACLE %then %do;'; put '%put NOTE: Obtaining &engine library details;'; put 'data _null_;'; put 'length assocuri1 assocuri2 assocuri3 authdomain path schema $256;'; put 'call missing (of _all_);'; put '/* get auth domain */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,assocuri1);'; put 'rc=metadata_getnasn(assocuri1,''Domain'',1,assocuri2);'; put 'rc=metadata_getattr(assocuri2,"Name",authdomain);'; put 'call symputx(''authdomain'',authdomain,''l'');'; put '/* path */'; put 'rc=metadata_getprop(assocuri1,'; put '''Connection.Oracle.Property.PATH.Name.xmlKey.txt'',path);'; put 'call symputx(''path'',path,''l'');'; put '/* schema */'; put 'rc=metadata_getnasn("&liburi",''UsingPackages'',1,assocuri3);'; put 'rc=metadata_getattr(assocuri3,''SchemaName'',schema);'; put 'call symputx(''schema'',schema,''l'');'; put 'run;'; put '%put NOTE: Executing the following:/; %put NOTE-;'; put '%put NOTE- libname &libref ORACLE path=&path schema=&schema;'; put '%put NOTE- authdomain=&authdomain;'; put '%put NOTE-;'; put 'libname &libref ORACLE path=&path schema=&schema authdomain=&authdomain;'; put '%end;'; put '%else %if &engine=SQLSVR %then %do;'; put '%put NOTE: Obtaining &engine library details;'; put 'data _null;'; put 'length assocuri1 assocuri2 assocuri3 authdomain path schema userid'; put 'passwd $256;'; put 'call missing (of _all_);'; put 'rc=metadata_getnasn("&liburi",''DefaultLogin'',1,assocuri1);'; put 'rc=metadata_getattr(assocuri1,"UserID",userid);'; put 'rc=metadata_getattr(assocuri1,"Password",passwd);'; put 'call symputx(''user'',userid,''l'');'; put 'call symputx(''pass'',passwd,''l'');'; put '/* path */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,assocuri2);'; put 'rc=metadata_getprop(assocuri2,'; put '''Connection.SQL.Property.Datasrc.Name.xmlKey.txt'',path);'; put 'call symputx(''path'',path,''l'');'; put '/* schema */'; put 'rc=metadata_getnasn("&liburi",''UsingPackages'',1,assocuri3);'; put 'rc=metadata_getattr(assocuri3,''SchemaName'',schema);'; put 'call symputx(''schema'',schema,''l'');'; put 'run;'; put '%put NOTE: Executing the following:/; %put NOTE-;'; put '%put NOTE- libname &libref SQLSVR datasrc=&path schema=&schema ;'; put '%put NOTE- user="&user" pass="XXX";'; put '%put NOTE-;'; put 'libname &libref SQLSVR datasrc=&path schema=&schema user="&user" pass="&pass";'; put '%end;'; put '%else %if &engine=TERADATA %then %do;'; put '%put NOTE: Obtaining &engine library details;'; put 'data _null;'; put 'length assocuri1 assocuri2 assocuri3 authdomain path schema userid'; put 'passwd $256;'; put 'call missing (of _all_);'; put '/* get auth domain */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,assocuri1);'; put 'rc=metadata_getnasn(assocuri1,''Domain'',1,assocuri2);'; put 'rc=metadata_getattr(assocuri2,"Name",authdomain);'; put 'call symputx(''authdomain'',authdomain,''l'');'; put '/*'; put 'rc=metadata_getnasn("&liburi",''DefaultLogin'',1,assocuri1);'; put 'rc=metadata_getattr(assocuri1,"UserID",userid);'; put 'rc=metadata_getattr(assocuri1,"Password",passwd);'; put 'call symputx(''user'',userid,''l'');'; put 'call symputx(''pass'',passwd,''l'');'; put '*/'; put '/* path */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,assocuri2);'; put 'rc=metadata_getprop(assocuri2,'; put '''Connection.Teradata.Property.SERVER.Name.xmlKey.txt'',path);'; put 'call symputx(''path'',path,''l'');'; put '/* schema */'; put 'rc=metadata_getnasn("&liburi",''UsingPackages'',1,assocuri3);'; put 'rc=metadata_getattr(assocuri3,''SchemaName'',schema);'; put 'call symputx(''schema'',schema,''l'');'; put 'run;'; put '%put NOTE: Executing the following:/; %put NOTE-;'; put '%put NOTE- libname &libref TERADATA server="&path" schema=&schema ;'; put '%put NOTe- authdomain=&authdomain;'; put '%put NOTE-;'; put 'libname &libref TERADATA server="&path" schema=&schema authdomain=&authdomain;'; put '%end;'; put '%else %if &engine= %then %do;'; put '%put NOTE: Libref &libref is not registered in metadata;'; put '%&mAbort.mp_abort('; put 'msg=%str(ERR)OR: Libref &libref is not registered in metadata'; put ',mac=mm_assigndirectlib.sas);'; put '%return;'; put '%end;'; put '%else %do;'; put '%put %str(WARN)ING: Engine &engine is currently unsupported;'; put '%put %str(WARN)ING- Please contact your support team.;'; put '%return;'; put '%end;'; put '%mend mm_assigndirectlib;'; put '%macro mm_getrepos('; put 'outds=work.mm_getrepos'; put ')/*/STORE SOURCE*/;'; put '* use a temporary fileref to hold the response;'; put 'filename response temp;'; put '/* get list of libraries */'; put 'proc metadata in='; put '"1"'; put 'out=response;'; put 'run;'; put '/* write the response to the log for debugging */'; put '/*'; put 'data _null_;'; put 'infile response lrecl=1048576;'; put 'input;'; put 'put _infile_;'; put 'run;'; put '*/'; put '/* create an XML map to read the response */'; put 'filename sxlemap temp;'; put 'data _null_;'; put 'file sxlemap;'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository";'; put 'put "";'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Id";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Name";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Desc";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@DefaultNS";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@RepositoryType";'; put 'put "characterstring20";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@RepositoryFormat";'; put 'put "characterstring10";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@Access";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@CurrentAccess";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@PauseState";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Path";'; put 'put "";'; put 'put "characterstring256";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Engine";'; put 'put "";'; put 'put "characterstring8";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Options";'; put 'put "";'; put 'put "characterstring32";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@MetadataCreated";'; put 'put "characterstring24";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@MetadataUpdated";'; put 'put "characterstring24";'; put 'put '''';'; put 'put ''
'';'; put 'run;'; put 'libname _XML_ xml xmlfileref=response xmlmap=sxlemap;'; put 'proc sort data= _XML_.SASRepos out=&outds;'; put 'by name;'; put 'run;'; put '/* clear references */'; put 'filename sxlemap clear;'; put 'filename response clear;'; put 'libname _XML_ clear;'; put '%mend mm_getrepos;'; put '%macro dc_assignlib(type,libref,passthru=);'; put '%put &sysmacroname entry vars:;'; put '%put _local_;'; put '/* take current repository */'; put '%local repo repocnt x;'; put '%let repo=%sysfunc(getoption(metarepository));'; put '/* get list of repositories and filter */'; put '%mm_getrepos(outds=repos)'; put '%let repocnt=0;'; put 'data repos;'; put 'set repos;'; put 'where repositorytype in(''CUSTOM'',''FOUNDATION'')'; put '& upcase(name) ne "%upcase(&repo)";'; put 'keep id name ;'; put 'call symputx(cats(''repo'',_n_),name,''l'');'; put 'call symputx(''repocnt'',_n_,''l'');'; put 'run;'; put '/* find out which of these repositories has the libref we are searching for */'; put '%local lib_uri;'; put '%let lib_uri=NOTFOUND;'; put 'data _null_; /* check default repo first */'; put 'length lib_uri $256;'; put 'call missing (of _all_);'; put 'rc=metadata_getnobj("omsobj:SASLibrary?@Libref =''&libref''",1,lib_uri);'; put 'call symputx(''lib_uri'',coalescec(lib_uri,''NOTFOUND''),''l'');'; put 'run;'; put '%if &lib_uri=NOTFOUND %then %do;'; put '%do x=1 %to &repocnt;'; put 'options metarepository=&&repo&x;'; put 'data _null_;'; put 'length lib_uri $256;'; put 'call missing (of _all_);'; put 'rc=metadata_getnobj("omsobj:SASLibrary?@Libref =''&libref''",1,lib_uri);'; put 'call symputx(''lib_uri'',coalescec(lib_uri,''NOTFOUND''),''l'');'; put 'run;'; put '%if &lib_uri ne NOTFOUND %then %let x=%eval(&repocnt+1);'; put '%end;'; put '%end;'; put '%if &type=READ %then %do;'; put '%mm_assignlib(&libref)'; put '%end;'; put '%else %if &type=WRITE %then %do;'; put '%mm_assigndirectlib(&libref,open_passthrough=&passthru)'; put '%end;'; put 'options metarepository=&repo;'; put '%mend dc_assignlib;'; put '%macro mf_existds(libds'; put ')/*/STORE SOURCE*/;'; put '%if %sysfunc(exist(&libds)) ne 1 & %sysfunc(exist(&libds,VIEW)) ne 1 %then 0;'; put '%else 1;'; put '%mend mf_existds;'; put '%macro mf_getvarlist(libds'; put ',dlm=%str( )'; put ',quote=no'; put ',typefilter=A'; put ')/*/STORE SOURCE*/;'; put '/* declare local vars */'; put '%local outvar dsid nvars x rc dlm q var vtype;'; put '/* credit Rowland Hale - byte34 is double quote, 39 is single quote */'; put '%if %upcase("e)=DOUBLE %then %let q=%qsysfunc(byte(34));'; put '%else %if %upcase("e)=SINGLE %then %let q=%qsysfunc(byte(39));'; put '/* open dataset in macro */'; put '%let dsid=%sysfunc(open(&libds));'; put '%if &dsid %then %do;'; put '%let nvars=%sysfunc(attrn(&dsid,NVARS));'; put '%if &nvars>0 %then %do;'; put '/* add variables with supplied delimeter */'; put '%do x=1 %to &nvars;'; put '/* get variable type */'; put '%let vtype=%sysfunc(vartype(&dsid,&x));'; put '%if &vtype=&typefilter or &typefilter=A %then %do;'; put '%let var=&q.%sysfunc(varname(&dsid,&x))&q.;'; put '%if &var=&q&q %then %do;'; put '%put &sysmacroname: Empty column found in &libds!;'; put '%let var=&q. &q.;'; put '%end;'; put '%if %quote(&outvar)=%quote() %then %let outvar=&var;'; put '%else %let outvar=&outvar.&dlm.&var.;'; put '%end;'; put '%end;'; put '%end;'; put '%let rc=%sysfunc(close(&dsid));'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: Unable to open &libds (rc=&dsid);'; put '%put &sysmacroname: SYSMSG= %sysfunc(sysmsg());'; put '%let rc=%sysfunc(close(&dsid));'; put '%end;'; put '%do;%unquote(&outvar)%end;'; put '%mend mf_getvarlist;'; put '%macro mf_wordsInStr1ButNotStr2('; put 'Str1= /* string containing words to extract */'; put ',Str2= /* used to compare with the extract string */'; put ')/*/STORE SOURCE*/;'; put '%local count_base count_extr i i2 extr_word base_word match outvar;'; put '%if %length(&str1)=0 or %length(&str2)=0 %then %do;'; put '%put base string (str1)= &str1;'; put '%put compare string (str2) = &str2;'; put '%return;'; put '%end;'; put '%let count_base=%sysfunc(countw(&Str2));'; put '%let count_extr=%sysfunc(countw(&Str1));'; put '%do i=1 %to &count_extr;'; put '%let extr_word=%scan(&Str1,&i,%str( ));'; put '%let match=0;'; put '%do i2=1 %to &count_base;'; put '%let base_word=%scan(&Str2,&i2,%str( ));'; put '%if &extr_word=&base_word %then %let match=1;'; put '%end;'; put '%if &match=0 %then %let outvar=&outvar &extr_word;'; put '%end;'; put '&outvar'; put '%mend mf_wordsInStr1ButNotStr2;'; put '%macro mf_getuniquename(prefix=MC);'; put '&prefix.%substr(%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32-%length(&prefix))'; put '%mend mf_getuniquename;'; put '%macro mp_validatecol(incol,rule,outcol);'; put '/* tempcol is given a unique name with every invocation */'; put '%local tempcol;'; put '%let tempcol=%mf_getuniquename();'; put '%if &rule=ISINT %then %do;'; put '&outcol=0;'; put 'if not missing(&incol) then do;'; put '&tempcol=input(&incol,?? best32.);'; put 'if not missing(&tempcol) then if mod(&tempcol,1)=0 then &outcol=1;'; put 'end;'; put 'drop &tempcol;'; put '%end;'; put '%else %if &rule=ISNUM %then %do;'; put '/*'; put 'credit SOREN LASSEN'; put 'https://sasmacro.blogspot.com/2009/06/welcome-isnum-macro.html'; put '*/'; put '&tempcol=input(&incol,?? best32.);'; put 'if missing(&tempcol) then &outcol=0;'; put 'else &outcol=1;'; put 'drop &tempcol;'; put '%end;'; put '%else %if &rule=LIBDS %then %do;'; put '/* match libref.dataset */'; put 'if _n_=1 then do;'; put 'retain &tempcol;'; put '&tempcol=prxparse(''/^[_a-z]\w{0,7}\.[_a-z]\w{0,31}$/i'');'; put 'if missing(&tempcol) then do;'; put 'putlog ''ERR'' +(-1) "OR: Invalid expression for LIBDS";'; put 'stop;'; put 'end;'; put 'drop &tempcol;'; put 'end;'; put 'if prxmatch(&tempcol, trim(&incol)) then &outcol=1;'; put 'else &outcol=0;'; put '%end;'; put '%else %if &rule=FORMAT %then %do;'; put '/* match valid format - regex could probably be improved */'; put 'if _n_=1 then do;'; put 'retain &tempcol;'; put '&tempcol=prxparse(''/^[_a-z\$]\w{0,31}\.[0-9]*$/i'');'; put 'if missing(&tempcol) then do;'; put 'putlog ''ERR'' +(-1) "OR: Invalid expression for FORMAT";'; put 'stop;'; put 'end;'; put 'drop &tempcol;'; put 'end;'; put 'if prxmatch(&tempcol, trim(&incol)) then &outcol=1;'; put 'else &outcol=0;'; put '%end;'; put '%mend mp_validatecol;'; put '* SAS Macros end;'; put '* SAS Includes start;'; put '* SAS Includes end;'; put '* Binary Files start;'; put '* Binary Files end;'; put '* ServiceInit start;'; put 'options noquotelenmax ps=max;'; put '/* create dummy macros to prevent stpbegin / stpend from corrupting sessions */'; put '%macro stpbegin();'; put '%put NOTE: the STPBEGIN macro should not be used for web apps!;'; put '%mend stpbegin;'; put '%macro stpend();'; put '%put NOTE: the STPEND macro should not be used for web apps!;'; put '%mend stpend;'; put '* ServiceInit end;'; put '* Service start;'; put '/**'; put '@file'; put '@brief Post Edit Hook script for the MPE_XLMAP_INFO table'; put '@details Post edit hooks provide additional backend validation for user'; put 'provided data. The incoming dataset is named `work.staging_ds` and is'; put 'provided in mpe_loader.sas.'; put 'Available macro variables:'; put '@li DC_LIBREF - The DC control library'; put '@li LIBREF - The library of the dataset being edited (is assigned)'; put '@li DS - The dataset being edited'; put '

SAS Macros

'; put '@li mf_existds.sas'; put '@li mf_getvarlist.sas'; put '@li mf_wordsinstr1butnotstr2.sas'; put '@li dc_assignlib.sas'; put '@li mp_validatecol.sas'; put '**/'; put 'data work.staging_ds;'; put 'set work.staging_ds;'; put '/* apply the first excel map to all cells */'; put 'length tgtds $41;'; put 'retain tgtds;'; put 'drop tgtds is_libds;'; put 'if _n_=1 then do;'; put 'if missing(XLMAP_TARGETLIBDS) then tgtds="&dc_libref..MPE_XLMAP_DATA";'; put 'else tgtds=upcase(XLMAP_TARGETLIBDS);'; put '%mp_validatecol(XLMAP_TARGETLIBDS,LIBDS,is_libds)'; put 'call symputx(''tgtds'',tgtds);'; put 'call symputx(''is_libds'',is_libds);'; put 'end;'; put 'XLMAP_TARGETLIBDS=tgtds;'; put 'run;'; put '%mp_abort(iftrue=(&is_libds ne 1)'; put ',mac=mpe_xlmap_info_postedit'; put ',msg=Invalid target dataset (&tgtds)'; put ')'; put '/**'; put '* make sure that the supplied target dataset exists and'; put '* has the necessary columns'; put '*/'; put '%dc_assignlib(READ,%scan(&tgtds,1,.))'; put '%mp_abort(iftrue=(%mf_existds(libds=&tgtds) ne 1)'; put ',mac=mpe_xlmap_info_postedit'; put ',msg=Target dataset (&tgtds) could not be opened'; put ')'; put '%let tgtvars=%upcase(%mf_getvarlist(&tgtds));'; put '%let srcvars=%upcase(%mf_getvarlist(&dc_libref..MPE_XLMAP_DATA));'; put '%let badvars1=%mf_wordsInStr1ButNotStr2(Str1=&srcvars,Str2=&tgtvars);'; put '%let badvars2=%mf_wordsInStr1ButNotStr2(Str1=&tgtvars,Str2=&srcvars);'; put '%mp_abort(iftrue=(%length(&badvars1.X)>1)'; put ',mac=mpe_xlmap_info_postedit'; put ',msg=%str(Target dataset (&tgtds) has missing vars: &badvars1)'; put ')'; put '%mp_abort(iftrue=(%length(&badvars2.X)>1)'; put ',mac=mpe_xlmap_info_postedit'; put ',msg=%str(Target dataset (&tgtds) has unrecognised vars: &badvars2)'; put ')'; put '* Service end;'; run; %mm_createwebservice(path=&appLoc/&path, name=&service, code=sascode, server=&serverName, replace=yes) filename sascode clear; %let service=mpe_xlmap_rules_postedit; filename sascode temp lrecl=32767; data _null_; file sascode; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '/**'; put '@file mp_jsonout.sas'; put '@brief Writes JSON in SASjs format to a fileref'; put '@details This macro can be used to OPEN a JSON stream and send one or more'; put 'tables as arrays of rows, where each row can be an object or a nested array.'; put 'There are two engines available - DATASTEP or PROCJSON.'; put 'PROC JSON is fast but will produce errs like the ones below if'; put 'special chars are encountered.'; put '> (ERR)OR: Some code points did not transcode.'; put '> An object or array close is not valid at this point in the JSON text.'; put '> Date value out of range'; put 'If this happens, try running with ENGINE=DATASTEP.'; put 'The DATASTEP engine is used to handle special SAS missing numerics, and'; put 'can also convert entire datasets to formatted values. Output JSON is always'; put 'in UTF-8.'; put 'Usage:'; put 'filename tmp temp;'; put 'data class; set sashelp.class;run;'; put '%mp_jsonout(OPEN,jref=tmp)'; put '%mp_jsonout(OBJ,class,jref=tmp)'; put '%mp_jsonout(OBJ,class,dslabel=class2,jref=tmp,showmeta=Y)'; put '%mp_jsonout(CLOSE,jref=tmp)'; put 'data _null_;'; put 'infile tmp;'; put 'input;putlog _infile_;'; put 'run;'; put 'If you are building web apps with SAS then you are strongly encouraged to use'; put 'the mX_createwebservice macros in combination with the'; put '[sasjs adapter](https://github.com/sasjs/adapter).'; put 'For more information see https://sasjs.io'; put '@param [in] action Valid values:'; put '@li OPEN - opens the JSON'; put '@li OBJ - sends a table with each row as an object'; put '@li ARR - sends a table with each row in an array'; put '@li CLOSE - closes the JSON'; put '@param [in] ds The dataset to send. Must be a work table.'; put '@param [out] jref= (_webout) The fileref to which to send the JSON'; put '@param [out] dslabel= The name to give the table in the exported JSON'; put '@param [in] fmt= (Y) Whether to keep (Y) or strip (N) formats from the table'; put '@param [in] engine= (DATASTEP) Which engine to use to send the JSON. Options:'; put '@li PROCJSON (default)'; put '@li DATASTEP (more reliable when data has non standard characters)'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to JSON'; put '

Related Files

'; put '@li mp_ds2fmtds.sas'; put '@version 9.2'; put '@author Allan Bowe'; put '@source https://github.com/sasjs/core'; put '**/'; put '%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y'; put ',engine=DATASTEP'; put ',missing=NULL'; put ',showmeta=N'; put ',maxobs=MAX'; put ')/*/STORE SOURCE*/;'; put '%local tempds colinfo fmtds i numcols numobs stmt_obs lastobs optval'; put 'tmpds1 tmpds2 tmpds3 tmpds4;'; put '%let numcols=0;'; put '%if &maxobs ne MAX %then %let stmt_obs=%str(if _n_>&maxobs then stop;);'; put '%if &action=OPEN %then %do;'; put 'options nobomfile;'; put 'data _null_;file &jref encoding=''utf-8'' lrecl=200;'; put 'put ''{"PROCESSED_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''"'';'; put 'run;'; put '%end;'; put '%else %if (&action=ARR or &action=OBJ) %then %do;'; put '/* force variable names to always be uppercase in the JSON */'; put 'options validvarname=upcase;'; put '/* To avoid issues with _webout on EBI - such as encoding diffs and truncation'; put '(https://support.sas.com/kb/49/325.html) we use temporary files */'; put 'filename _sjs1 temp lrecl=200 ;'; put 'data _null_; file _sjs1 encoding=''utf-8'';'; put 'put ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":";'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjs1 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs1 clear;'; put '/* grab col defs */'; put 'proc contents noprint data=&ds'; put 'out=_data_(keep=name type length format formatl formatd varnum label);'; put 'run;'; put '%let colinfo=%scan(&syslast,2,.);'; put 'proc sort data=&colinfo;'; put 'by varnum;'; put 'run;'; put '/* move meta to mac vars */'; put 'data &colinfo;'; put 'if _n_=1 then call symputx(''numcols'',nobs,''l'');'; put 'set &colinfo end=last nobs=nobs;'; put 'name=upcase(name);'; put '/* fix formats */'; put 'if type=2 or type=6 then do;'; put 'typelong=''char'';'; put 'length fmt $49.;'; put 'if format='''' then fmt=cats(''$'',length,''.'');'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else fmt=cats(format,formatl,''.'');'; put 'end;'; put 'else do;'; put 'typelong=''num'';'; put 'if format='''' then fmt=''best.'';'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else if formatd=0 then fmt=cats(format,formatl,''.'');'; put 'else fmt=cats(format,formatl,''.'',formatd);'; put 'end;'; put '/* 32 char unique name */'; put 'newname=''sasjs''!!substr(cats(put(md5(name),$hex32.)),1,27);'; put 'call symputx(cats(''name'',_n_),name,''l'');'; put 'call symputx(cats(''newname'',_n_),newname,''l'');'; put 'call symputx(cats(''length'',_n_),length,''l'');'; put 'call symputx(cats(''fmt'',_n_),fmt,''l'');'; put 'call symputx(cats(''type'',_n_),type,''l'');'; put 'call symputx(cats(''typelong'',_n_),typelong,''l'');'; put 'call symputx(cats(''label'',_n_),coalescec(label,name),''l'');'; put '/* overwritten when fmt=Y and a custom format exists in catalog */'; put 'if typelong=''num'' then call symputx(cats(''fmtlen'',_n_),200,''l'');'; put 'else call symputx(cats(''fmtlen'',_n_),min(32767,ceil((length+10)*1.5)),''l'');'; put 'run;'; put '%let tempds=%substr(_%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql;'; put 'select count(*) into: lastobs from &ds;'; put '%if &maxobs ne MAX %then %let lastobs=%sysfunc(min(&lastobs,&maxobs));'; put '%if &engine=PROCJSON %then %do;'; put '%if &missing=STRING %then %do;'; put '%put &sysmacroname: Special Missings not supported in proc json.;'; put '%put &sysmacroname: Switching to DATASTEP engine;'; put '%goto datastep;'; put '%end;'; put 'data &tempds;'; put 'set &ds;'; put '&stmt_obs;'; put '%if &fmt=N %then format _numeric_ best32.;;'; put '/* PRETTY is necessary to avoid line truncation in large files */'; put 'filename _sjs2 temp lrecl=131068 encoding=''utf-8'';'; put 'proc json out=_sjs2 pretty'; put '%if &action=ARR %then nokeys ;'; put ';export &tempds / nosastags fmtnumeric;'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs2 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs2 clear;'; put '%end;'; put '%else %if &engine=DATASTEP %then %do;'; put '%datastep:'; put '%if %sysfunc(exist(&ds)) ne 1 & %sysfunc(exist(&ds,VIEW)) ne 1'; put '%then %do;'; put '%put &sysmacroname: &ds NOT FOUND!!!;'; put '%return;'; put '%end;'; put '%if &fmt=Y %then %do;'; put '/**'; put '* Extract format definitions'; put '* First, by getting library locations from dictionary.formats'; put '* Then, by exporting the width using proc format'; put '* Cannot use maxw from sashelp.vformat as not always populated'; put '* Cannot use fmtinfo() as not supported in all flavours'; put '*/'; put '%let tmpds1=%substr(fmtsum%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds2=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds3=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds4=%substr(col%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql noprint;'; put 'create table &tmpds1 as'; put 'select cats(libname,''.'',memname) as FMTCAT,'; put 'FMTNAME'; put 'from dictionary.formats'; put 'where fmttype=''F'' and libname is not null'; put 'and fmtname in (select format from &colinfo where format is not null)'; put 'order by 1;'; put 'create table &tmpds2('; put 'FMTNAME char(32),'; put 'LENGTH num'; put ');'; put '%local catlist cat fmtlist i;'; put 'select distinct fmtcat into: catlist separated by '' '' from &tmpds1;'; put '%do i=1 %to %sysfunc(countw(&catlist,%str( )));'; put '%let cat=%scan(&catlist,&i,%str( ));'; put 'proc sql;'; put 'select distinct fmtname into: fmtlist separated by '' '''; put 'from &tmpds1 where fmtcat="&cat";'; put 'proc format lib=&cat cntlout=&tmpds3(keep=fmtname length);'; put 'select &fmtlist;'; put 'run;'; put 'proc sql;'; put 'insert into &tmpds2 select distinct fmtname,length from &tmpds3;'; put '%end;'; put 'proc sql;'; put 'create table &tmpds4 as'; put 'select a.*, b.length as MAXW'; put 'from &colinfo a'; put 'left join &tmpds2 b'; put 'on cats(a.format)=cats(upcase(b.fmtname))'; put 'order by a.varnum;'; put 'data _null_;'; put 'set &tmpds4;'; put 'if not missing(maxw);'; put 'call symputx('; put 'cats(''fmtlen'',_n_),'; put '/* vars need extra padding due to JSON escaping of special chars */'; put 'min(32767,ceil((max(length,maxw)+10)*1.5))'; put ',''l'''; put ');'; put 'run;'; put '/* configure varlenchk - as we are explicitly shortening the variables */'; put '%let optval=%sysfunc(getoption(varlenchk));'; put 'options varlenchk=NOWARN;'; put 'data _data_(compress=char);'; put '/* shorten the new vars */'; put 'length'; put '%do i=1 %to &numcols;'; put '&&name&i $&&fmtlen&i'; put '%end;'; put ';'; put '/* rename on entry */'; put 'set &ds(rename=('; put '%do i=1 %to &numcols;'; put '&&name&i=&&newname&i'; put '%end;'; put '));'; put '&stmt_obs;'; put 'drop'; put '%do i=1 %to &numcols;'; put '&&newname&i'; put '%end;'; put ';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=num %then %do;'; put '&&name&i=cats(put(&&newname&i,&&fmt&i));'; put '%end;'; put '%else %do;'; put '&&name&i=put(&&newname&i,&&fmt&i);'; put '%end;'; put '%end;'; put 'if _error_ then do;'; put 'call symputx(''syscc'',1012);'; put 'stop;'; put 'end;'; put 'run;'; put '%let fmtds=&syslast;'; put 'options varlenchk=&optval;'; put '%end;'; put 'proc format; /* credit yabwon for special null removal */'; put 'value bart (default=40)'; put '%if &missing=NULL %then %do;'; put '._ - .z = null'; put '%end;'; put '%else %do;'; put '._ = [quote()]'; put '. = null'; put '.a - .z = [quote()]'; put '%end;'; put 'other = [best.];'; put 'data &tempds;'; put 'attrib _all_ label='''';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'length &&name&i $&&fmtlen&i...;'; put 'format &&name&i $&&fmtlen&i...;'; put '%end;'; put '%end;'; put '%if &fmt=Y %then %do;'; put 'set &fmtds;'; put '%end;'; put '%else %do;'; put 'set &ds;'; put '%end;'; put '&stmt_obs;'; put 'format _numeric_ bart.;'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'if findc(&&name&i,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put '&&name&i=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,&&name&i)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else &&name&i=quote(cats(&&name&i));'; put '%end;'; put '%end;'; put 'run;'; put 'filename _sjs3 temp lrecl=131068 ;'; put 'data _null_;'; put 'file _sjs3 encoding=''utf-8'';'; put 'if _n_=1 then put "[";'; put 'set &tempds;'; put 'if _n_>1 then put "," @; put'; put '%if &action=ARR %then "[" ; %else "{" ;'; put '%do i=1 %to &numcols;'; put '%if &i>1 %then "," ;'; put '%if &action=OBJ %then """&&name&i"":" ;'; put '"&&name&i"n /* name literal for reserved variable names */'; put '%end;'; put '%if &action=ARR %then "]" ; %else "}" ; ;'; put '/* close out the table */'; put 'data _null_;'; put 'file _sjs3 mod encoding=''utf-8'';'; put 'put '']'';'; put 'run;'; put 'data _null_;'; put 'infile _sjs3 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs3 clear;'; put '%end;'; put 'proc sql;'; put 'drop table &colinfo, &tempds;'; put '%if %substr(&showmeta,1,1)=Y %then %do;'; put 'filename _sjs4 temp lrecl=131068 encoding=''utf-8'';'; put 'data _null_;'; put 'file _sjs4;'; put 'length label $350;'; put 'put ", ""$%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":{""vars"":{";'; put 'do i=1 to &numcols;'; put 'name=quote(trim(symget(cats(''name'',i))));'; put 'format=quote(trim(symget(cats(''fmt'',i))));'; put 'label=quote(prxchange(''s/\\/\\\\/'',-1,trim(symget(cats(''label'',i)))));'; put 'length=quote(trim(symget(cats(''length'',i))));'; put 'type=quote(trim(symget(cats(''typelong'',i))));'; put 'if i>1 then put "," @@;'; put 'put name '':{"format":'' format '',"label":'' label'; put ''',"length":'' length '',"type":'' type ''}'';'; put 'end;'; put 'put ''}}'';'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs4 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs4 clear;'; put '%end;'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put 'data _null_; file &jref encoding=''utf-8'' mod ;'; put 'put "}";'; put 'run;'; put '%end;'; put '%mend mp_jsonout;'; put '/**'; put '@file mm_webout.sas'; put '@brief Send data to/from SAS Stored Processes'; put '@details This macro should be added to the start of each Stored Process,'; put '**immediately** followed by a call to:'; put '%mm_webout(FETCH)'; put 'This will read all the input data and create same-named SAS datasets in the'; put 'WORK library. You can then insert your code, and send data back using the'; put 'following syntax:'; put 'data some datasets; * make some data ;'; put 'retain some columns;'; put 'run;'; put '%mm_webout(OPEN)'; put '%mm_webout(ARR,some) * Array format, fast, suitable for large tables ;'; put '%mm_webout(OBJ,datasets) * Object format, easier to work with ;'; put 'Finally, wrap everything up send some helpful system variables too'; put '%mm_webout(CLOSE)'; put '@param [in] action Either FETCH, OPEN, ARR, OBJ or CLOSE'; put '@param [in] ds The dataset to send back to the frontend'; put '@param [out] dslabel= Value to use instead of table name for sending to JSON'; put '@param [in] fmt= (N) Setting Y converts all vars to their formatted values'; put '@param [out] fref= (_webout) The fileref to which to write the JSON'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] workobs= (0) When set to a positive integer, will create a new'; put 'output object (WORK) which contains this number of observations from all'; put 'tables in the WORK library.'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to output JSON'; put '

SAS Macros

'; put '@li mp_jsonout.sas'; put '

Related Macros

'; put '@li ms_webout.sas'; put '@li mv_webout.sas'; put '@version 9.3'; put '@author Allan Bowe'; put '**/'; put '%macro mm_webout(action,ds,dslabel=,fref=_webout,fmt=N,missing=NULL'; put ',showmeta=N,maxobs=MAX,workobs=0'; put ');'; put '%global _webin_file_count _webin_fileref1 _webin_name1 _program _debug'; put 'sasjs_tables;'; put '%local i tempds jsonengine;'; put '/* see https://github.com/sasjs/core/issues/41 */'; put '%if "%upcase(&SYSENCODING)" ne "UTF-8" %then %let jsonengine=PROCJSON;'; put '%else %let jsonengine=DATASTEP;'; put '%if &action=FETCH %then %do;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'options mprint notes mprintnest;'; put '%end;'; put '%let _webin_file_count=%eval(&_webin_file_count+0);'; put '/* now read in the data */'; put '%do i=1 %to &_webin_file_count;'; put '%if &_webin_file_count=1 %then %do;'; put '%let _webin_fileref1=&_webin_fileref;'; put '%let _webin_name1=&_webin_name;'; put '%end;'; put 'data _null_;'; put 'infile &&_webin_fileref&i termstr=crlf;'; put 'input;'; put 'call symputx(''input_statement'',_infile_);'; put 'putlog "&&_webin_name&i input statement: " _infile_;'; put 'stop;'; put 'data &&_webin_name&i;'; put 'infile &&_webin_fileref&i firstobs=2 dsd termstr=crlf encoding=''utf-8'';'; put 'input &input_statement;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'if _n_<20 then putlog _infile_;'; put '%end;'; put 'run;'; put '%let sasjs_tables=&sasjs_tables &&_webin_name&i;'; put '%end;'; put '%end;'; put '%else %if &action=OPEN %then %do;'; put '/* fix encoding */'; put 'OPTIONS NOBOMFILE;'; put '/**'; put '* check xengine type to avoid the below err message:'; put '* > Function is only valid for filerefs using the CACHE access method.'; put '*/'; put 'data _null_;'; put 'set sashelp.vextfl(where=(fileref="_WEBOUT"));'; put 'if xengine=''STREAM'' then do;'; put 'rc=stpsrv_header(''Content-type'',"text/html; encoding=utf-8");'; put 'end;'; put 'run;'; put '/* setup json */'; put 'data _null_;file &fref encoding=''utf-8'';'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutBEGIN<<'';'; put '%end;'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'run;'; put '%end;'; put '%else %if &action=ARR or &action=OBJ %then %do;'; put '%mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt,jref=&fref'; put ',engine=&jsonengine,missing=&missing,showmeta=&showmeta,maxobs=&maxobs'; put ')'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put '/* To avoid issues with _webout on EBI we use a temporary file */'; put 'filename _sjsref temp lrecl=131068;'; put '%if %str(&workobs) > 0 %then %do;'; put '/* if debug mode, send back first XX records of each work table also */'; put 'data;run;%let tempds=%scan(&syslast,2,.);'; put 'ods output Members=&tempds;'; put 'proc datasets library=WORK memtype=data;'; put '%local wtcnt;%let wtcnt=0;'; put 'data _null_;'; put 'set &tempds;'; put 'if not (upcase(name) =:"DATA"); /* ignore temp datasets */'; put 'i+1;'; put 'call symputx(cats(''wt'',i),name,''l'');'; put 'call symputx(''wtcnt'',i,''l'');'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put ",""WORK"":{";'; put '%do i=1 %to &wtcnt;'; put '%let wt=&&wt&i;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'dsid=open("WORK.&wt",''is'');'; put 'nlobs=attrn(dsid,''NLOBS'');'; put 'nvars=attrn(dsid,''NVARS'');'; put 'rc=close(dsid);'; put 'if &i>1 then put '',''@;'; put 'put " ""&wt"" : {";'; put 'put ''"nlobs":'' nlobs;'; put 'put '',"nvars":'' nvars;'; put '%mp_jsonout(OBJ,&wt,jref=_sjsref,dslabel=first10rows,showmeta=Y'; put ',maxobs=&workobs'; put ')'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put '%end;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put 'run;'; put '%end;'; put '/* close off json */'; put 'data _null_;file _sjsref mod encoding=''utf-8'';'; put 'length SYSPROCESSNAME syserrortext syswarningtext autoexec $512;'; put 'put ",""_DEBUG"" : ""&_debug"" ";'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'autoexec=quote(urlencode(trim(getoption(''autoexec''))));'; put 'put '',"AUTOEXEC" : '' autoexec;'; put 'put ",""MF_GETUSER"" : ""%mf_getuser()"" ";'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'put ",""SYSENCODING"" : ""&sysencoding"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSPROCESSID"" : ""&SYSPROCESSID"" ";'; put 'put ",""SYSPROCESSMODE"" : ""&SYSPROCESSMODE"" ";'; put 'SYSPROCESSNAME=quote(urlencode(cats(SYSPROCESSNAME)));'; put 'put ",""SYSPROCESSNAME"" : " SYSPROCESSNAME;'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put '',"SYSWARNINGTEXT" : '' syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'length memsize $32;'; put 'memsize="%sysfunc(INPUTN(%sysfunc(getoption(memsize)), best.),sizekmg.)";'; put 'memsize=quote(cats(memsize));'; put 'put '',"MEMSIZE" : '' memsize;'; put 'put "}" @;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutEND<<'';'; put '%end;'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjsref lrecl=1 recfm=n;'; put 'file &fref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjsref clear;'; put '%end;'; put '%mend mm_webout;'; put '%macro webout(action,ds,dslabel=,fmt=,missing=NULL,showmeta=NO,maxobs=MAX);'; put '%mm_webout(&action,ds=&ds,dslabel=&dslabel,fmt=&fmt'; put ',missing=&missing'; put ',showmeta=&showmeta'; put ',maxobs=&maxobs'; put ') %mend;'; put '/* provide additional debug info */'; put '%global _program;'; put '%put &=syscc;'; put '%put user=%mf_getuser();'; put '%put pgm=&_program;'; put '%put timestamp=%sysfunc(datetime(),datetime19.);'; put '* Service Variables start;'; put '* Service Variables end;'; put '* SAS Macros start;'; put '%macro mf_getapploc(pgm);'; put '%if "&pgm"="" %then %do;'; put '%if %symexist(_program) %then %let pgm=&_program;'; put '%else %do;'; put '%put &sysmacroname: No value provided and no _program variable available;'; put '%return;'; put '%end;'; put '%end;'; put '%local root;'; put '/**'; put '* First check we are not in the tests/macros folder (which has no subfolders)'; put '* or specifically in the testsetup or testteardown services'; put '*/'; put '%if %index(&pgm,/tests/macros/)'; put 'or %index(&pgm,/tests/testsetup)'; put 'or %index(&pgm,/tests/testteardown)'; put '%then %do;'; put '%let root=%substr(&pgm,1,%index(&pgm,/tests)-1);'; put '&root'; put '%return;'; put '%end;'; put '/**'; put '* Next, move up two levels to avoid matches on subfolder or service name'; put '*/'; put '%let root=%substr(&pgm,1,%length(&pgm)-%length(%scan(&pgm,-1,/))-1);'; put '%let root=%substr(&root,1,%length(&root)-%length(%scan(&root,-1,/))-1);'; put '%if %index(&root,/tests/) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/tests/)-1);'; put '%end;'; put '%else %if %index(&root,/services) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/services)-1);'; put '%end;'; put '%else %if %index(&root,/jobs) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/jobs)-1);'; put '%end;'; put '%else %put &sysmacroname: Could not find an app location from &pgm;'; put '&root'; put '%mend mf_getapploc ;'; put '%macro mf_getuniquefileref(prefix=_,maxtries=1000,lrecl=32767);'; put '%local rc fname;'; put '%if &prefix=0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%end;'; put '%else %do;'; put '%local x len;'; put '%let len=%eval(8-%length(&prefix));'; put '%let x=0;'; put '%do x=0 %to &maxtries;'; put '%let fname=&prefix%substr(%sysfunc(ranuni(0)),3,&len);'; put '%if %sysfunc(fileref(&fname)) > 0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%return;'; put '%end;'; put '%end;'; put '%put unable to find available fileref after &maxtries attempts;'; put '%end;'; put '%mend mf_getuniquefileref;'; put '%macro mp_abort(mac=mp_abort.sas, type=, msg=, iftrue=%str(1=1)'; put ', errds=work.mp_abort_errds'; put ', mode=REGULAR'; put ')/*/STORE SOURCE*/;'; put '%global sysprocessmode sysprocessname sasjs_stpsrv_header_loc sasjsprocessmode;'; put '%local fref fid i;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%put NOTE: /// mp_abort macro executing //;'; put '%if %length(&mac)>0 %then %put NOTE- called by &mac;'; put '%put NOTE - &msg;'; put '%if %symexist(_SYSINCLUDEFILEDEVICE)'; put '/* abort cancel FILE does not restart outside the INCLUDE on Viya 3.5 */'; put 'and %superq(SYSPROCESSNAME) ne %str(Compute Server)'; put '%then %do;'; put '%if "*&_SYSINCLUDEFILEDEVICE*" ne "**" %then %do;'; put 'data &errds;'; put 'iftrue=''1=1'';'; put 'length mac $100 msg $5000;'; put 'mac=symget(''mac'');'; put 'msg=symget(''msg'');'; put 'run;'; put 'data _null_;'; put 'abort cancel FILE;'; put 'run;'; put '%return;'; put '%end;'; put '%end;'; put '/* Web App Context */'; put '%if %symexist(_PROGRAM)'; put 'or %superq(SYSPROCESSNAME) = %str(Compute Server)'; put 'or &mode=INCLUDE'; put '%then %do;'; put 'options obs=max replace mprint;'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5"'; put '%then %do;'; put 'options nosyntaxcheck;'; put '%end;'; put '%if &mode=INCLUDE %then %do;'; put '%if %sysfunc(exist(&errds))=1 %then %do;'; put 'data _null_;'; put 'set &errds;'; put 'call symputx(''iftrue'',iftrue,''l'');'; put 'call symputx(''mac'',mac,''l'');'; put 'call symputx(''msg'',msg,''l'');'; put 'putlog (_all_)(=);'; put 'run;'; put '%if (&iftrue)=0 %then %return;'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: No include errors found;'; put '%return;'; put '%end;'; put '%end;'; put '/* extract log errs / warns, if exist */'; put '%local logloc logline;'; put '%global logmsg; /* capture global messages */'; put '%if %symexist(SYSPRINTTOLOG) %then %let logloc=&SYSPRINTTOLOG;'; put '%else %let logloc=%qsysfunc(getoption(LOG));'; put 'proc printto log=log;run;'; put '%let logline=0;'; put '%if %length(&logloc)>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input; putlog _infile_;'; put 'i=1;'; put 'retain logonce 0;'; put 'if ('; put '_infile_=:"%str(WARN)ING" or _infile_=:"%str(ERR)OR"'; put ') and logonce=0 then'; put 'do;'; put 'call symputx(''logline'',_n_);'; put 'logonce+1;'; put 'end;'; put 'run;'; put '/* capture log including lines BEFORE the err */'; put '%if &logline>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input;'; put 'i=1;'; put 'stoploop=0;'; put 'if _n_ ge &logline-15 and stoploop=0 then do until (i>22);'; put 'call symputx(''logmsg'',catx(''\n'',symget(''logmsg''),_infile_));'; put 'input;'; put 'i+1;'; put 'stoploop=1;'; put 'end;'; put 'if stoploop=1 then stop;'; put 'run;'; put '%end;'; put '%end;'; put '%if %symexist(SYS_JES_JOB_URI) %then %do;'; put '/* setup webout for Viya */'; put 'options nobomfile;'; put '%if "X&SYS_JES_JOB_URI.X"="XX" %then %do;'; put 'filename _webout temp lrecl=999999 mod;'; put '%end;'; put '%else %do;'; put 'filename _webout filesrvc parenturi="&SYS_JES_JOB_URI"'; put 'name="_webout.json" lrecl=999999 mod;'; put '%end;'; put '%end;'; put '%else %if %sysfunc(filename(fref,&sasjs_stpsrv_header_loc))=0 %then %do;'; put 'options nobomfile;'; put '/* set up http header for SASjs Server */'; put '%let fid=%sysfunc(fopen(&fref,A));'; put '%if &fid=0 %then %do;'; put '%put %str(ERR)OR: %sysfunc(sysmsg());'; put '%return;'; put '%end;'; put '%let rc=%sysfunc(fput(&fid,%str(Content-Type: application/json)));'; put '%let rc=%sysfunc(fwrite(&fid));'; put '%let rc=%sysfunc(fclose(&fid));'; put '%let rc=%sysfunc(filename(&fref));'; put '%end;'; put '/* send response in SASjs JSON format */'; put 'data _null_;'; put 'file _webout mod lrecl=32000 encoding=''utf-8'';'; put 'length msg syswarningtext syserrortext $32767 mode $10 ;'; put 'sasdatetime=datetime();'; put 'msg=symget(''msg'');'; put '%if &logline>0 %then %do;'; put 'msg=cats(msg,''\n\nLog Extract:\n'',symget(''logmsg''));'; put '%end;'; put '/* escape the escapes */'; put 'msg=tranwrd(msg,''\'',''\\'');'; put '/* escape the quotes */'; put 'msg=tranwrd(msg,''"'',''\"'');'; put '/* ditch the CRLFs as chrome complains */'; put 'msg=compress(msg,,''kw'');'; put '/* quote without quoting the quotes (which are escaped instead) */'; put 'msg=cats(''"'',msg,''"'');'; put 'if symexist(''_debug'') then debug=quote(trim(symget(''_debug'')));'; put 'else debug=''""'';'; put 'if symget(''sasjsprocessmode'')=''Stored Program'' then mode=''SASJS'';'; put 'if mode ne ''SASJS'' then put ''>>weboutBEGIN<<'';'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'put '',"sasjsAbort" : [{'';'; put 'put '' "MSG":'' msg ;'; put 'put '' ,"MAC": "'' "&mac" ''"}]'';'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'put '',"_DEBUG":'' debug ;'; put 'if symexist(''_metauser'') then do;'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put 'end;'; put 'if symexist(''SYS_JES_JOB_URI'') then do;'; put 'SYS_JES_JOB_URI=quote(trim(symget(''SYS_JES_JOB_URI'')));'; put 'put '',"SYS_JES_JOB_URI": '' SYS_JES_JOB_URI;'; put 'end;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put ",""SYSWARNINGTEXT"" : " syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'put "}" ;'; put 'if mode ne ''SASJS'' then put ''>>weboutEND<<'';'; put 'run;'; put '%put _all_;'; put '%if "&sysprocessmode " = "SAS Stored Process Server " %then %do;'; put 'data _null_;'; put 'putlog ''stpsrvset program err and syscc'';'; put 'rc=stpsrvset(''program error'', 0);'; put 'call symputx("syscc",0,"g");'; put 'run;'; put '%if &sysscp=WIN'; put 'and 1=0 /* deprecating this logic until we figure out a consistent abort */'; put 'and "%substr(%str(&sysvlong ),1,8)"="9.04.01M"'; put 'and "%substr(%str(&sysvlong ),9,1)">"5" %then %do;'; put '/* skip approach (below) does not work in windows m6+ envs */'; put 'endsas;'; put '%end;'; put '%else %do;'; put '/**'; put '* endsas kills 9.4m3 deployments by orphaning multibridges.'; put '* Abort variants are ungraceful (non zero return code)'; put '* This approach lets SAS run silently until the end :-)'; put '* Caution - fails when called within a %include within a macro'; put '* Use mp_include() to handle this.'; put '*/'; put 'filename skip temp;'; put 'data _null_;'; put 'file skip;'; put 'put ''%macro skip();'';'; put 'comment ''%mend skip; -> fix lint '';'; put 'put ''%macro skippy();'';'; put 'comment ''%mend skippy; -> fix lint '';'; put 'run;'; put '%inc skip;'; put '%end;'; put '%end;'; put '%else %if "&sysprocessmode " = "SAS Compute Server " %then %do;'; put '/* endsas kills the session making it harder to fetch results */'; put 'data _null_;'; put 'syswarningtext=symget(''syswarningtext'');'; put 'syserrortext=symget(''syserrortext'');'; put 'abort_msg=symget(''msg'');'; put 'syscc=symget(''syscc'');'; put 'sysuserid=symget(''sysuserid'');'; put 'iftrue=symget(''iftrue'');'; put 'put (_all_)(/=);'; put 'call symputx(''syscc'',0);'; put 'abort cancel nolist;'; put 'run;'; put '%end;'; put '%else %do;'; put '%abort cancel;'; put '%end;'; put '%end;'; put '%else %do;'; put '%put _all_;'; put '%abort cancel;'; put '%end;'; put '%mend mp_abort;'; put '/** @endcond */'; put '%macro mm_getstpcode('; put 'tree=/User Folders/sasdemo/somestp'; put ',name='; put ',outloc=0'; put ',outref=0'; put ',mDebug=1'; put ',showlog=NO'; put ');'; put '%local mD;'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing &sysmacroname..sas;'; put '%&mD.put _local_;'; put '%if %length(&name)>0 %then %let name=/&name;'; put '/* first, check if STP exists */'; put '%local tsuri;'; put '%let tsuri=stopifempty ;'; put 'data _null_;'; put 'format type uri tsuri value $200.;'; put 'call missing (of _all_);'; put 'path="&tree&name(StoredProcess)";'; put '/* first, find the STP ID */'; put 'if metadata_pathobj("",path,"StoredProcess",type,uri)>0 then do;'; put '/* get sourcecode */'; put 'cnt=1;'; put 'do while (metadata_getnasn(uri,"Notes",cnt,tsuri)>0);'; put 'rc=metadata_getattr(tsuri,"Name",value);'; put '&mD.put tsuri= value=;'; put 'if value="SourceCode" then do;'; put '/* found it! */'; put 'rc=metadata_getattr(tsuri,"Id",value);'; put 'call symputx(''tsuri'',value,''l'');'; put 'stop;'; put 'end;'; put 'cnt+1;'; put 'end;'; put 'end;'; put 'else put (_all_)(=);'; put 'run;'; put '%mp_abort(iftrue= (&tsuri=stopifempty)'; put ',mac=mm_getstpcode'; put ',msg=%str(&tree&name.(StoredProcess) not found!)'; put ')'; put '/**'; put '* Now we can extract the textstore'; put '*/'; put 'filename __getdoc temp lrecl=10000000;'; put 'proc metadata'; put 'in="$METAREPOSITORY'; put ''; put 'SAS1"'; put 'out=__getdoc ;'; put 'run;'; put '/* find the beginning of the text */'; put '%local start;'; put 'data _null_;'; put 'infile __getdoc lrecl=10000;'; put 'input;'; put 'start=index(_infile_,''StoredText="'');'; put 'if start then do;'; put 'call symputx("start",start+11);'; put '*putlog ''"'' _infile_ ''"'';'; put 'end;'; put 'stop;'; put '%local outeng;'; put '%if "&outloc"="0" %then %let outeng=TEMP;'; put '%else %let outeng="&outloc";'; put '%local fref;'; put '%if &outref=0 %then %let fref=%mf_getuniquefileref();'; put '%else %let fref=&outref;'; put '/* read the content, byte by byte, resolving escaped chars */'; put 'filename &fref &outeng lrecl=100000;'; put 'data _null_;'; put 'length filein 8 fileid 8;'; put 'filein = fopen("__getdoc","I",1,"B");'; put 'fileid = fopen("&fref","O",1,"B");'; put 'rec = "20"x;'; put 'length entity $6;'; put 'do while(fread(filein)=0);'; put 'x+1;'; put 'if x>&start then do;'; put 'rc = fget(filein,rec,1);'; put 'if rec=''"'' then leave;'; put 'else if rec="&" then do;'; put 'entity=rec;'; put 'do until (rec=";");'; put 'if fread(filein) ne 0 then goto getout;'; put 'rc = fget(filein,rec,1);'; put 'entity=cats(entity,rec);'; put 'end;'; put 'select (entity);'; put 'when (''&'' ) rec=''&'' ;'; put 'when (''<'' ) rec=''<'' ;'; put 'when (''>'' ) rec=''>'' ;'; put 'when (''''') rec="''" ;'; put 'when (''"'') rec=''"'' ;'; put 'when ('' '') rec=''0A''x;'; put 'when ('' '') rec=''0D''x;'; put 'when (''$'' ) rec=''$'' ;'; put 'when ('' '') rec=''09''x;'; put 'otherwise putlog "%str(WARN)ING: missing value for " entity=;'; put 'end;'; put 'rc =fput(fileid, substr(rec,1,1));'; put 'rc =fwrite(fileid);'; put 'end;'; put 'else do;'; put 'rc =fput(fileid,rec);'; put 'rc =fwrite(fileid);'; put 'end;'; put 'end;'; put 'end;'; put 'getout:'; put 'rc=fclose(filein);'; put 'rc=fclose(fileid);'; put 'run;'; put '%if &showlog=YES %then %do;'; put 'data _null_;'; put 'infile &fref lrecl=32767 end=last;'; put 'input;'; put 'if _n_=1 then putlog ''>>stpcodeBEGIN<<'';'; put 'putlog _infile_;'; put 'if last then putlog ''>>stpcodeEND<<'';'; put 'run;'; put '%end;'; put 'filename __getdoc clear;'; put '%if &outref=0 %then %do;'; put 'filename &fref clear;'; put '%end;'; put '%mend mm_getstpcode;'; put '%macro dc_getsettings();'; put '%global _program;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(syscc=&syscc on entry (&syswarningtext &syserrortext))'; put ')'; put '%if %length(&_PROGRAM)>1 %then %let root=&_program;'; put '%else %do;'; put '%global _metauser;'; put '%let _metauser=&sysuserid;'; put '/* to mimic a "real" _program we need to give a dummy role and stp name */'; put '%let root=/dummyRole/dummyName;'; put '%end;'; put '/* the DC precode is stored in the Admin folder in the root of'; put 'the project. Lets find that root. */'; put '%put &=root;'; put '%let root=%mf_getapploc();'; put '%put &=root;'; put '/* Now we know the root location we can retrieve the params */'; put '%let temploc=%sysfunc(pathname(work))/settings.txt;'; put '%mm_getstpcode(tree=&root/services/public'; put ',name=Data_Controller_Settings'; put ',outloc=&temploc'; put ')'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Unable to run getstpcode)'; put ')'; put 'filename _getsets "&temploc" lrecl=2000;'; put '/*'; put 'Do not use mp_include here - this puts a copy in every service, which creates'; put 'compilation problems when calling services from mp_include'; put '*/'; put '%inc _getsets/source2;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Problem running &sysmacroname (&syswarningtext &syserrortext))'; put ')'; put '%mend dc_getsettings;'; put '%macro mf_fmtdttm('; put ')/*/STORE SOURCE*/;'; put '%if "&sysver"="9.2" or "&sysver"="9.3"'; put 'or ("&sysver"="9.4" and "%substr(&SYSVLONG,9,1)" le "3")'; put 'or "%substr(&sysver,1,1)"="4"'; put 'or "%substr(&sysver,1,1)"="5"'; put '%then %do;DATETIME19.3%end;'; put '%else %do;E8601DT26.6%end;'; put '%mend mf_fmtdttm;'; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '%macro mp_init(prefix=SASJS'; put ')/*/STORE SOURCE*/;'; put '%if %symexist(SASJS_PREFIX) %then %return; /* only run once */'; put '%global'; put 'SASJS_PREFIX /* the ONLY hard-coded global macro variable in SASjs */'; put '&prefix._FUNCTIONS /* used in mcf_init() to track core function compilation */'; put '&prefix._INIT_NUM /* initialisation time as numeric */'; put '&prefix._INIT_DTTM /* initialisation time in E8601DT26.6 format */'; put '&prefix.WORK /* avoid typing %sysfunc(pathname(work)) every time */'; put ';'; put '%let sasjs_prefix=&prefix;'; put 'data _null_;'; put 'dttm=datetime();'; put 'call symputx("&sasjs_prefix._init_num",dttm,''g'');'; put 'call symputx("&sasjs_prefix._init_dttm",put(dttm,E8601DT26.6),''g'');'; put 'call symputx("&sasjs_prefix.work",pathname(''WORK''),''g'');'; put 'run;'; put 'options'; put 'compress=CHAR /* default is none so ensure we have something! */'; put 'datastmtchk=ALLKEYWORDS /* protection from overwriting input datasets */'; put 'errorcheck=STRICT /* catch errs in libname/filename statements */'; put 'fmterr /* ensure err when a format cannot be found */'; put 'mergenoby=%str(ERR)OR /* throw err when a merge has no BY variables */'; put 'missing=. /* changing this can cause hard to detect errs */'; put 'noquotelenmax /* avoid warnings for long strings */'; put 'noreplace /* avoid overwriting permanent datasets */'; put 'ps=max /* reduce log size slightly */'; put 'ls=max /* reduce log even more and avoid word truncation */'; put 'validmemname=COMPATIBLE /* avoid special characters etc in table names */'; put 'validvarname=V7 /* avoid special characters etc in variable names */'; put 'varinitchk=%str(ERR)OR /* avoid data mistakes from variable name typos */'; put 'varlenchk=%str(ERR)OR /* fail hard if truncation (data loss) can result */'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5" %then %do;'; put 'noautocorrect /* disallow misspelled procedure names */'; put 'dsoptions=note2err /* undocumented - convert bad NOTEs to ERRs */'; put '%end;'; put ';'; put '%mend mp_init;'; put '%macro mpeinit(fetch=YES);'; put '%global mpeinit'; put 'mpeadmins /* group with unrestricted Meditor access */'; put 'mpelocapprovals /* location for landing and staging files */'; put 'mpelib /* location of configuration tables for DC */'; put 'dc_repo_users /* location of user / group metadata */'; put 'dc_licence_key /* extracted in dc_getsettings */'; put 'dc_activation_key /* extracted in dc_getsettings */'; put 'dc_locale /* extracted in dc_getsettings */'; put 'dc_dttmtfmt /* can be overridden in dc_getsettings */'; put '_debug'; put ';'; put '%if &mpeinit=1 %then %return;'; put '%else %let mpeinit=1;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(Problem on service startup (&syswarningtext &syserrortext))'; put ')'; put '%mp_init()'; put '%if &fetch=YES %then %do;'; put '%webout(FETCH)'; put '%end;'; put '%global _CLIENTNAME;'; put '%mp_abort(iftrue= (&_CLIENTNAME=SAS Enterprise Guide)'; put ',mac=&_program..sas'; put ',msg=%str(Data Controller is a web app and should not be executed from EG)'; put ')'; put 'options urlencoding=utf8 nobomfile lrecl=32767;'; put '%let perf=%sysfunc(datetime());'; put '%put perfdiff: 0;'; put '%let dc_locale=SYSTEM; /* default if not set */'; put '/**'; put '* E8601DT26.6 has widest database support - but not all SAS flavours can'; put '* handle it. Override in the settings STP if needed.'; put '*/'; put 'data _null_;'; put 'dc_dttmtfmt=''"%sysfunc(datetime(),''!!"%mf_fmtdttm()"!!'')"dt'';'; put 'call symputx(''dc_dttmtfmt'',dc_dttmtfmt);'; put 'put dc_dttmtfmt=;'; put 'run;'; put '%put &=dc_dttmtfmt;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc prior to dc_getsettings)'; put ')'; put '%dc_getsettings()'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc after dc_getsettings)'; put ')'; put 'data _null_;'; put 'set &DC_LIBREF..mpe_config(where=('; put 'var_scope="DC"'; put 'and &dc_dttmtfmt lt tx_to'; put 'and var_active=1'; put '));'; put 'call symputx(var_name,var_value,''G'');'; put 'putlog var_name "=" var_value;'; put 'run;'; put '%let mpelib=&dc_libref;'; put '%let mpeadmins=&dc_admin_group;'; put '%let mpelocapprovals=&dc_staging_area;'; put '%let dc_repo_users=&dc_repo_users;'; put '%if &dc_locale ne SYSTEM %then %do;'; put 'options locale=&dc_locale;'; put '%end;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(Problem during compilation or with STP precode (&syswarningtext))'; put ')'; put '%mend mpeinit;'; put '%macro mf_mval(var);'; put '%if %symexist(&var) %then %do;'; put '%superq(&var)'; put '%end;'; put '%mend mf_mval;'; put '%macro mf_trimstr(basestr,trimstr);'; put '%local baselen trimlen trimval;'; put '/* return if basestr is shorter than trimstr (or 0) */'; put '%let baselen=%length(%superq(basestr));'; put '%let trimlen=%length(%superq(trimstr));'; put '%if &baselen < &trimlen or &baselen=0 %then %return;'; put '/* obtain the characters from the end of basestr */'; put '%let trimval=%qsubstr(%superq(basestr)'; put ',%length(%superq(basestr))-&trimlen+1'; put ',&trimlen);'; put '/* compare and if matching, chop it off! */'; put '%if %superq(basestr)=%superq(trimstr) %then %do;'; put '%return;'; put '%end;'; put '%else %if %superq(trimval)=%superq(trimstr) %then %do;'; put '%qsubstr(%superq(basestr),1,%length(%superq(basestr))-&trimlen)'; put '%end;'; put '%else %do;'; put '&basestr'; put '%end;'; put '%mend mf_trimstr;'; put '%macro mf_getplatform(switch'; put ')/*/STORE SOURCE*/;'; put '%local a b c;'; put '%if &switch.NONE=NONE %then %do;'; put '%if %symexist(sasjsprocessmode) %then %do;'; put '%if &sasjsprocessmode=Stored Program %then %do;'; put 'SASJS'; put '%return;'; put '%end;'; put '%end;'; put '%if %symexist(sysprocessmode) %then %do;'; put '%if "&sysprocessmode"="SAS Object Server"'; put 'or "&sysprocessmode"= "SAS Compute Server" %then %do;'; put 'SASVIYA'; put '%end;'; put '%else %if "&sysprocessmode"="SAS Stored Process Server"'; put 'or "&sysprocessmode"="SAS Workspace Server"'; put '%then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if %symexist(_metaport) or %symexist(_metauser) %then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if &switch=SASSTUDIO %then %do;'; put '/* return the version of SAS Studio else 0 */'; put '%if %mf_mval(_CLIENTAPP)=%str(SAS Studio) %then %do;'; put '%let a=%mf_mval(_CLIENTVERSION);'; put '%let b=%scan(&a,1,.);'; put '%if %eval(&b >2) %then %do;'; put '&b'; put '%end;'; put '%else 0;'; put '%end;'; put '%else 0;'; put '%end;'; put '%else %if &switch=VIYARESTAPI %then %do;'; put '%mf_trimstr(%sysfunc(getoption(servicesbaseurl)),/)'; put '%end;'; put '%mend mf_getplatform;'; put '%macro mpeterm();'; put '%local oldloc;'; put 'data _null_;'; put 'if symexist(''SYSPRINTTOLOG'') then oldloc=symget(''SYSPRINTTOLOG'');'; put 'else oldloc=getoption(''LOG'');'; put 'if subpad(oldloc,1,1) not in (''"'',"''",'' '') then oldloc=quote(cats(oldloc));'; put 'call symputx(''oldloc'',oldloc,''l'');'; put 'run;'; put '%if %length(&oldloc)>0 %then %do;'; put 'proc printto log=log;'; put 'run;'; put 'data _null_;'; put 'infile &oldloc;'; put 'input; putlog _infile_;'; put 'run;'; put '%end;'; put '%if %sysfunc(exist(&dc_libref..mpe_requests)) and %mf_getplatform() ne SASVIYA'; put '%then %do;'; put 'data ;'; put 'if 0 then set &dc_libref..mpe_requests;'; put 'request_dttm=%sysfunc(datetime());'; put 'request_user="%mf_getuser()";'; put 'request_service="%scan(&_program,-2,/)/%scan(&_program,-1,/)";'; put 'request_params='''';'; put 'output;stop;'; put 'proc append base=&dc_libref..mpe_requests data=&syslast force nowarn;'; put 'run;'; put '%end;'; put '%mend mpeterm;'; put '* SAS Macros end;'; put '* SAS Includes start;'; put '* SAS Includes end;'; put '* Binary Files start;'; put '* Binary Files end;'; put '* ServiceInit start;'; put 'options noquotelenmax ps=max;'; put '/* create dummy macros to prevent stpbegin / stpend from corrupting sessions */'; put '%macro stpbegin();'; put '%put NOTE: the STPBEGIN macro should not be used for web apps!;'; put '%mend stpbegin;'; put '%macro stpend();'; put '%put NOTE: the STPEND macro should not be used for web apps!;'; put '%mend stpend;'; put '* ServiceInit end;'; put '* Service start;'; put '/**'; put '@file'; put '@brief Post Edit Hook script for the MPE_XLMAP_RULES table'; put '@details Post edit hooks provide additional backend validation for user'; put 'provided data. The incoming dataset is named `work.staging_ds` and is'; put 'provided in mpe_loader.sas.'; put 'Available macro variables:'; put '@li DC_LIBREF - The DC control library'; put '@li LIBREF - The library of the dataset being edited (is assigned)'; put '@li DS - The dataset being edited'; put '**/'; put 'data work.staging_ds;'; put 'set work.staging_ds;'; put '/* ensure uppercasing */'; put 'XLMAP_ID=upcase(XLMAP_ID);'; put 'run;'; put '* Service end;'; run; %mm_createwebservice(path=&appLoc/&path, name=&service, code=sascode, server=&serverName, replace=yes) filename sascode clear; %let service=sample_xlmap_data_postapprove; filename sascode temp lrecl=32767; data _null_; file sascode; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '/**'; put '@file mp_jsonout.sas'; put '@brief Writes JSON in SASjs format to a fileref'; put '@details This macro can be used to OPEN a JSON stream and send one or more'; put 'tables as arrays of rows, where each row can be an object or a nested array.'; put 'There are two engines available - DATASTEP or PROCJSON.'; put 'PROC JSON is fast but will produce errs like the ones below if'; put 'special chars are encountered.'; put '> (ERR)OR: Some code points did not transcode.'; put '> An object or array close is not valid at this point in the JSON text.'; put '> Date value out of range'; put 'If this happens, try running with ENGINE=DATASTEP.'; put 'The DATASTEP engine is used to handle special SAS missing numerics, and'; put 'can also convert entire datasets to formatted values. Output JSON is always'; put 'in UTF-8.'; put 'Usage:'; put 'filename tmp temp;'; put 'data class; set sashelp.class;run;'; put '%mp_jsonout(OPEN,jref=tmp)'; put '%mp_jsonout(OBJ,class,jref=tmp)'; put '%mp_jsonout(OBJ,class,dslabel=class2,jref=tmp,showmeta=Y)'; put '%mp_jsonout(CLOSE,jref=tmp)'; put 'data _null_;'; put 'infile tmp;'; put 'input;putlog _infile_;'; put 'run;'; put 'If you are building web apps with SAS then you are strongly encouraged to use'; put 'the mX_createwebservice macros in combination with the'; put '[sasjs adapter](https://github.com/sasjs/adapter).'; put 'For more information see https://sasjs.io'; put '@param [in] action Valid values:'; put '@li OPEN - opens the JSON'; put '@li OBJ - sends a table with each row as an object'; put '@li ARR - sends a table with each row in an array'; put '@li CLOSE - closes the JSON'; put '@param [in] ds The dataset to send. Must be a work table.'; put '@param [out] jref= (_webout) The fileref to which to send the JSON'; put '@param [out] dslabel= The name to give the table in the exported JSON'; put '@param [in] fmt= (Y) Whether to keep (Y) or strip (N) formats from the table'; put '@param [in] engine= (DATASTEP) Which engine to use to send the JSON. Options:'; put '@li PROCJSON (default)'; put '@li DATASTEP (more reliable when data has non standard characters)'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to JSON'; put '

Related Files

'; put '@li mp_ds2fmtds.sas'; put '@version 9.2'; put '@author Allan Bowe'; put '@source https://github.com/sasjs/core'; put '**/'; put '%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y'; put ',engine=DATASTEP'; put ',missing=NULL'; put ',showmeta=N'; put ',maxobs=MAX'; put ')/*/STORE SOURCE*/;'; put '%local tempds colinfo fmtds i numcols numobs stmt_obs lastobs optval'; put 'tmpds1 tmpds2 tmpds3 tmpds4;'; put '%let numcols=0;'; put '%if &maxobs ne MAX %then %let stmt_obs=%str(if _n_>&maxobs then stop;);'; put '%if &action=OPEN %then %do;'; put 'options nobomfile;'; put 'data _null_;file &jref encoding=''utf-8'' lrecl=200;'; put 'put ''{"PROCESSED_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''"'';'; put 'run;'; put '%end;'; put '%else %if (&action=ARR or &action=OBJ) %then %do;'; put '/* force variable names to always be uppercase in the JSON */'; put 'options validvarname=upcase;'; put '/* To avoid issues with _webout on EBI - such as encoding diffs and truncation'; put '(https://support.sas.com/kb/49/325.html) we use temporary files */'; put 'filename _sjs1 temp lrecl=200 ;'; put 'data _null_; file _sjs1 encoding=''utf-8'';'; put 'put ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":";'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjs1 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs1 clear;'; put '/* grab col defs */'; put 'proc contents noprint data=&ds'; put 'out=_data_(keep=name type length format formatl formatd varnum label);'; put 'run;'; put '%let colinfo=%scan(&syslast,2,.);'; put 'proc sort data=&colinfo;'; put 'by varnum;'; put 'run;'; put '/* move meta to mac vars */'; put 'data &colinfo;'; put 'if _n_=1 then call symputx(''numcols'',nobs,''l'');'; put 'set &colinfo end=last nobs=nobs;'; put 'name=upcase(name);'; put '/* fix formats */'; put 'if type=2 or type=6 then do;'; put 'typelong=''char'';'; put 'length fmt $49.;'; put 'if format='''' then fmt=cats(''$'',length,''.'');'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else fmt=cats(format,formatl,''.'');'; put 'end;'; put 'else do;'; put 'typelong=''num'';'; put 'if format='''' then fmt=''best.'';'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else if formatd=0 then fmt=cats(format,formatl,''.'');'; put 'else fmt=cats(format,formatl,''.'',formatd);'; put 'end;'; put '/* 32 char unique name */'; put 'newname=''sasjs''!!substr(cats(put(md5(name),$hex32.)),1,27);'; put 'call symputx(cats(''name'',_n_),name,''l'');'; put 'call symputx(cats(''newname'',_n_),newname,''l'');'; put 'call symputx(cats(''length'',_n_),length,''l'');'; put 'call symputx(cats(''fmt'',_n_),fmt,''l'');'; put 'call symputx(cats(''type'',_n_),type,''l'');'; put 'call symputx(cats(''typelong'',_n_),typelong,''l'');'; put 'call symputx(cats(''label'',_n_),coalescec(label,name),''l'');'; put '/* overwritten when fmt=Y and a custom format exists in catalog */'; put 'if typelong=''num'' then call symputx(cats(''fmtlen'',_n_),200,''l'');'; put 'else call symputx(cats(''fmtlen'',_n_),min(32767,ceil((length+10)*1.5)),''l'');'; put 'run;'; put '%let tempds=%substr(_%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql;'; put 'select count(*) into: lastobs from &ds;'; put '%if &maxobs ne MAX %then %let lastobs=%sysfunc(min(&lastobs,&maxobs));'; put '%if &engine=PROCJSON %then %do;'; put '%if &missing=STRING %then %do;'; put '%put &sysmacroname: Special Missings not supported in proc json.;'; put '%put &sysmacroname: Switching to DATASTEP engine;'; put '%goto datastep;'; put '%end;'; put 'data &tempds;'; put 'set &ds;'; put '&stmt_obs;'; put '%if &fmt=N %then format _numeric_ best32.;;'; put '/* PRETTY is necessary to avoid line truncation in large files */'; put 'filename _sjs2 temp lrecl=131068 encoding=''utf-8'';'; put 'proc json out=_sjs2 pretty'; put '%if &action=ARR %then nokeys ;'; put ';export &tempds / nosastags fmtnumeric;'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs2 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs2 clear;'; put '%end;'; put '%else %if &engine=DATASTEP %then %do;'; put '%datastep:'; put '%if %sysfunc(exist(&ds)) ne 1 & %sysfunc(exist(&ds,VIEW)) ne 1'; put '%then %do;'; put '%put &sysmacroname: &ds NOT FOUND!!!;'; put '%return;'; put '%end;'; put '%if &fmt=Y %then %do;'; put '/**'; put '* Extract format definitions'; put '* First, by getting library locations from dictionary.formats'; put '* Then, by exporting the width using proc format'; put '* Cannot use maxw from sashelp.vformat as not always populated'; put '* Cannot use fmtinfo() as not supported in all flavours'; put '*/'; put '%let tmpds1=%substr(fmtsum%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds2=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds3=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds4=%substr(col%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql noprint;'; put 'create table &tmpds1 as'; put 'select cats(libname,''.'',memname) as FMTCAT,'; put 'FMTNAME'; put 'from dictionary.formats'; put 'where fmttype=''F'' and libname is not null'; put 'and fmtname in (select format from &colinfo where format is not null)'; put 'order by 1;'; put 'create table &tmpds2('; put 'FMTNAME char(32),'; put 'LENGTH num'; put ');'; put '%local catlist cat fmtlist i;'; put 'select distinct fmtcat into: catlist separated by '' '' from &tmpds1;'; put '%do i=1 %to %sysfunc(countw(&catlist,%str( )));'; put '%let cat=%scan(&catlist,&i,%str( ));'; put 'proc sql;'; put 'select distinct fmtname into: fmtlist separated by '' '''; put 'from &tmpds1 where fmtcat="&cat";'; put 'proc format lib=&cat cntlout=&tmpds3(keep=fmtname length);'; put 'select &fmtlist;'; put 'run;'; put 'proc sql;'; put 'insert into &tmpds2 select distinct fmtname,length from &tmpds3;'; put '%end;'; put 'proc sql;'; put 'create table &tmpds4 as'; put 'select a.*, b.length as MAXW'; put 'from &colinfo a'; put 'left join &tmpds2 b'; put 'on cats(a.format)=cats(upcase(b.fmtname))'; put 'order by a.varnum;'; put 'data _null_;'; put 'set &tmpds4;'; put 'if not missing(maxw);'; put 'call symputx('; put 'cats(''fmtlen'',_n_),'; put '/* vars need extra padding due to JSON escaping of special chars */'; put 'min(32767,ceil((max(length,maxw)+10)*1.5))'; put ',''l'''; put ');'; put 'run;'; put '/* configure varlenchk - as we are explicitly shortening the variables */'; put '%let optval=%sysfunc(getoption(varlenchk));'; put 'options varlenchk=NOWARN;'; put 'data _data_(compress=char);'; put '/* shorten the new vars */'; put 'length'; put '%do i=1 %to &numcols;'; put '&&name&i $&&fmtlen&i'; put '%end;'; put ';'; put '/* rename on entry */'; put 'set &ds(rename=('; put '%do i=1 %to &numcols;'; put '&&name&i=&&newname&i'; put '%end;'; put '));'; put '&stmt_obs;'; put 'drop'; put '%do i=1 %to &numcols;'; put '&&newname&i'; put '%end;'; put ';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=num %then %do;'; put '&&name&i=cats(put(&&newname&i,&&fmt&i));'; put '%end;'; put '%else %do;'; put '&&name&i=put(&&newname&i,&&fmt&i);'; put '%end;'; put '%end;'; put 'if _error_ then do;'; put 'call symputx(''syscc'',1012);'; put 'stop;'; put 'end;'; put 'run;'; put '%let fmtds=&syslast;'; put 'options varlenchk=&optval;'; put '%end;'; put 'proc format; /* credit yabwon for special null removal */'; put 'value bart (default=40)'; put '%if &missing=NULL %then %do;'; put '._ - .z = null'; put '%end;'; put '%else %do;'; put '._ = [quote()]'; put '. = null'; put '.a - .z = [quote()]'; put '%end;'; put 'other = [best.];'; put 'data &tempds;'; put 'attrib _all_ label='''';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'length &&name&i $&&fmtlen&i...;'; put 'format &&name&i $&&fmtlen&i...;'; put '%end;'; put '%end;'; put '%if &fmt=Y %then %do;'; put 'set &fmtds;'; put '%end;'; put '%else %do;'; put 'set &ds;'; put '%end;'; put '&stmt_obs;'; put 'format _numeric_ bart.;'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'if findc(&&name&i,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put '&&name&i=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,&&name&i)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else &&name&i=quote(cats(&&name&i));'; put '%end;'; put '%end;'; put 'run;'; put 'filename _sjs3 temp lrecl=131068 ;'; put 'data _null_;'; put 'file _sjs3 encoding=''utf-8'';'; put 'if _n_=1 then put "[";'; put 'set &tempds;'; put 'if _n_>1 then put "," @; put'; put '%if &action=ARR %then "[" ; %else "{" ;'; put '%do i=1 %to &numcols;'; put '%if &i>1 %then "," ;'; put '%if &action=OBJ %then """&&name&i"":" ;'; put '"&&name&i"n /* name literal for reserved variable names */'; put '%end;'; put '%if &action=ARR %then "]" ; %else "}" ; ;'; put '/* close out the table */'; put 'data _null_;'; put 'file _sjs3 mod encoding=''utf-8'';'; put 'put '']'';'; put 'run;'; put 'data _null_;'; put 'infile _sjs3 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs3 clear;'; put '%end;'; put 'proc sql;'; put 'drop table &colinfo, &tempds;'; put '%if %substr(&showmeta,1,1)=Y %then %do;'; put 'filename _sjs4 temp lrecl=131068 encoding=''utf-8'';'; put 'data _null_;'; put 'file _sjs4;'; put 'length label $350;'; put 'put ", ""$%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":{""vars"":{";'; put 'do i=1 to &numcols;'; put 'name=quote(trim(symget(cats(''name'',i))));'; put 'format=quote(trim(symget(cats(''fmt'',i))));'; put 'label=quote(prxchange(''s/\\/\\\\/'',-1,trim(symget(cats(''label'',i)))));'; put 'length=quote(trim(symget(cats(''length'',i))));'; put 'type=quote(trim(symget(cats(''typelong'',i))));'; put 'if i>1 then put "," @@;'; put 'put name '':{"format":'' format '',"label":'' label'; put ''',"length":'' length '',"type":'' type ''}'';'; put 'end;'; put 'put ''}}'';'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs4 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs4 clear;'; put '%end;'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put 'data _null_; file &jref encoding=''utf-8'' mod ;'; put 'put "}";'; put 'run;'; put '%end;'; put '%mend mp_jsonout;'; put '/**'; put '@file mm_webout.sas'; put '@brief Send data to/from SAS Stored Processes'; put '@details This macro should be added to the start of each Stored Process,'; put '**immediately** followed by a call to:'; put '%mm_webout(FETCH)'; put 'This will read all the input data and create same-named SAS datasets in the'; put 'WORK library. You can then insert your code, and send data back using the'; put 'following syntax:'; put 'data some datasets; * make some data ;'; put 'retain some columns;'; put 'run;'; put '%mm_webout(OPEN)'; put '%mm_webout(ARR,some) * Array format, fast, suitable for large tables ;'; put '%mm_webout(OBJ,datasets) * Object format, easier to work with ;'; put 'Finally, wrap everything up send some helpful system variables too'; put '%mm_webout(CLOSE)'; put '@param [in] action Either FETCH, OPEN, ARR, OBJ or CLOSE'; put '@param [in] ds The dataset to send back to the frontend'; put '@param [out] dslabel= Value to use instead of table name for sending to JSON'; put '@param [in] fmt= (N) Setting Y converts all vars to their formatted values'; put '@param [out] fref= (_webout) The fileref to which to write the JSON'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] workobs= (0) When set to a positive integer, will create a new'; put 'output object (WORK) which contains this number of observations from all'; put 'tables in the WORK library.'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to output JSON'; put '

SAS Macros

'; put '@li mp_jsonout.sas'; put '

Related Macros

'; put '@li ms_webout.sas'; put '@li mv_webout.sas'; put '@version 9.3'; put '@author Allan Bowe'; put '**/'; put '%macro mm_webout(action,ds,dslabel=,fref=_webout,fmt=N,missing=NULL'; put ',showmeta=N,maxobs=MAX,workobs=0'; put ');'; put '%global _webin_file_count _webin_fileref1 _webin_name1 _program _debug'; put 'sasjs_tables;'; put '%local i tempds jsonengine;'; put '/* see https://github.com/sasjs/core/issues/41 */'; put '%if "%upcase(&SYSENCODING)" ne "UTF-8" %then %let jsonengine=PROCJSON;'; put '%else %let jsonengine=DATASTEP;'; put '%if &action=FETCH %then %do;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'options mprint notes mprintnest;'; put '%end;'; put '%let _webin_file_count=%eval(&_webin_file_count+0);'; put '/* now read in the data */'; put '%do i=1 %to &_webin_file_count;'; put '%if &_webin_file_count=1 %then %do;'; put '%let _webin_fileref1=&_webin_fileref;'; put '%let _webin_name1=&_webin_name;'; put '%end;'; put 'data _null_;'; put 'infile &&_webin_fileref&i termstr=crlf;'; put 'input;'; put 'call symputx(''input_statement'',_infile_);'; put 'putlog "&&_webin_name&i input statement: " _infile_;'; put 'stop;'; put 'data &&_webin_name&i;'; put 'infile &&_webin_fileref&i firstobs=2 dsd termstr=crlf encoding=''utf-8'';'; put 'input &input_statement;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'if _n_<20 then putlog _infile_;'; put '%end;'; put 'run;'; put '%let sasjs_tables=&sasjs_tables &&_webin_name&i;'; put '%end;'; put '%end;'; put '%else %if &action=OPEN %then %do;'; put '/* fix encoding */'; put 'OPTIONS NOBOMFILE;'; put '/**'; put '* check xengine type to avoid the below err message:'; put '* > Function is only valid for filerefs using the CACHE access method.'; put '*/'; put 'data _null_;'; put 'set sashelp.vextfl(where=(fileref="_WEBOUT"));'; put 'if xengine=''STREAM'' then do;'; put 'rc=stpsrv_header(''Content-type'',"text/html; encoding=utf-8");'; put 'end;'; put 'run;'; put '/* setup json */'; put 'data _null_;file &fref encoding=''utf-8'';'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutBEGIN<<'';'; put '%end;'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'run;'; put '%end;'; put '%else %if &action=ARR or &action=OBJ %then %do;'; put '%mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt,jref=&fref'; put ',engine=&jsonengine,missing=&missing,showmeta=&showmeta,maxobs=&maxobs'; put ')'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put '/* To avoid issues with _webout on EBI we use a temporary file */'; put 'filename _sjsref temp lrecl=131068;'; put '%if %str(&workobs) > 0 %then %do;'; put '/* if debug mode, send back first XX records of each work table also */'; put 'data;run;%let tempds=%scan(&syslast,2,.);'; put 'ods output Members=&tempds;'; put 'proc datasets library=WORK memtype=data;'; put '%local wtcnt;%let wtcnt=0;'; put 'data _null_;'; put 'set &tempds;'; put 'if not (upcase(name) =:"DATA"); /* ignore temp datasets */'; put 'i+1;'; put 'call symputx(cats(''wt'',i),name,''l'');'; put 'call symputx(''wtcnt'',i,''l'');'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put ",""WORK"":{";'; put '%do i=1 %to &wtcnt;'; put '%let wt=&&wt&i;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'dsid=open("WORK.&wt",''is'');'; put 'nlobs=attrn(dsid,''NLOBS'');'; put 'nvars=attrn(dsid,''NVARS'');'; put 'rc=close(dsid);'; put 'if &i>1 then put '',''@;'; put 'put " ""&wt"" : {";'; put 'put ''"nlobs":'' nlobs;'; put 'put '',"nvars":'' nvars;'; put '%mp_jsonout(OBJ,&wt,jref=_sjsref,dslabel=first10rows,showmeta=Y'; put ',maxobs=&workobs'; put ')'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put '%end;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put 'run;'; put '%end;'; put '/* close off json */'; put 'data _null_;file _sjsref mod encoding=''utf-8'';'; put 'length SYSPROCESSNAME syserrortext syswarningtext autoexec $512;'; put 'put ",""_DEBUG"" : ""&_debug"" ";'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'autoexec=quote(urlencode(trim(getoption(''autoexec''))));'; put 'put '',"AUTOEXEC" : '' autoexec;'; put 'put ",""MF_GETUSER"" : ""%mf_getuser()"" ";'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'put ",""SYSENCODING"" : ""&sysencoding"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSPROCESSID"" : ""&SYSPROCESSID"" ";'; put 'put ",""SYSPROCESSMODE"" : ""&SYSPROCESSMODE"" ";'; put 'SYSPROCESSNAME=quote(urlencode(cats(SYSPROCESSNAME)));'; put 'put ",""SYSPROCESSNAME"" : " SYSPROCESSNAME;'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put '',"SYSWARNINGTEXT" : '' syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'length memsize $32;'; put 'memsize="%sysfunc(INPUTN(%sysfunc(getoption(memsize)), best.),sizekmg.)";'; put 'memsize=quote(cats(memsize));'; put 'put '',"MEMSIZE" : '' memsize;'; put 'put "}" @;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutEND<<'';'; put '%end;'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjsref lrecl=1 recfm=n;'; put 'file &fref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjsref clear;'; put '%end;'; put '%mend mm_webout;'; put '%macro webout(action,ds,dslabel=,fmt=,missing=NULL,showmeta=NO,maxobs=MAX);'; put '%mm_webout(&action,ds=&ds,dslabel=&dslabel,fmt=&fmt'; put ',missing=&missing'; put ',showmeta=&showmeta'; put ',maxobs=&maxobs'; put ') %mend;'; put '/* provide additional debug info */'; put '%global _program;'; put '%put &=syscc;'; put '%put user=%mf_getuser();'; put '%put pgm=&_program;'; put '%put timestamp=%sysfunc(datetime(),datetime19.);'; put '* Service Variables start;'; put '* Service Variables end;'; put '* SAS Macros start;'; put '%macro mf_getapploc(pgm);'; put '%if "&pgm"="" %then %do;'; put '%if %symexist(_program) %then %let pgm=&_program;'; put '%else %do;'; put '%put &sysmacroname: No value provided and no _program variable available;'; put '%return;'; put '%end;'; put '%end;'; put '%local root;'; put '/**'; put '* First check we are not in the tests/macros folder (which has no subfolders)'; put '* or specifically in the testsetup or testteardown services'; put '*/'; put '%if %index(&pgm,/tests/macros/)'; put 'or %index(&pgm,/tests/testsetup)'; put 'or %index(&pgm,/tests/testteardown)'; put '%then %do;'; put '%let root=%substr(&pgm,1,%index(&pgm,/tests)-1);'; put '&root'; put '%return;'; put '%end;'; put '/**'; put '* Next, move up two levels to avoid matches on subfolder or service name'; put '*/'; put '%let root=%substr(&pgm,1,%length(&pgm)-%length(%scan(&pgm,-1,/))-1);'; put '%let root=%substr(&root,1,%length(&root)-%length(%scan(&root,-1,/))-1);'; put '%if %index(&root,/tests/) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/tests/)-1);'; put '%end;'; put '%else %if %index(&root,/services) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/services)-1);'; put '%end;'; put '%else %if %index(&root,/jobs) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/jobs)-1);'; put '%end;'; put '%else %put &sysmacroname: Could not find an app location from &pgm;'; put '&root'; put '%mend mf_getapploc ;'; put '%macro mf_getuniquefileref(prefix=_,maxtries=1000,lrecl=32767);'; put '%local rc fname;'; put '%if &prefix=0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%end;'; put '%else %do;'; put '%local x len;'; put '%let len=%eval(8-%length(&prefix));'; put '%let x=0;'; put '%do x=0 %to &maxtries;'; put '%let fname=&prefix%substr(%sysfunc(ranuni(0)),3,&len);'; put '%if %sysfunc(fileref(&fname)) > 0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%return;'; put '%end;'; put '%end;'; put '%put unable to find available fileref after &maxtries attempts;'; put '%end;'; put '%mend mf_getuniquefileref;'; put '%macro mp_abort(mac=mp_abort.sas, type=, msg=, iftrue=%str(1=1)'; put ', errds=work.mp_abort_errds'; put ', mode=REGULAR'; put ')/*/STORE SOURCE*/;'; put '%global sysprocessmode sysprocessname sasjs_stpsrv_header_loc sasjsprocessmode;'; put '%local fref fid i;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%put NOTE: /// mp_abort macro executing //;'; put '%if %length(&mac)>0 %then %put NOTE- called by &mac;'; put '%put NOTE - &msg;'; put '%if %symexist(_SYSINCLUDEFILEDEVICE)'; put '/* abort cancel FILE does not restart outside the INCLUDE on Viya 3.5 */'; put 'and %superq(SYSPROCESSNAME) ne %str(Compute Server)'; put '%then %do;'; put '%if "*&_SYSINCLUDEFILEDEVICE*" ne "**" %then %do;'; put 'data &errds;'; put 'iftrue=''1=1'';'; put 'length mac $100 msg $5000;'; put 'mac=symget(''mac'');'; put 'msg=symget(''msg'');'; put 'run;'; put 'data _null_;'; put 'abort cancel FILE;'; put 'run;'; put '%return;'; put '%end;'; put '%end;'; put '/* Web App Context */'; put '%if %symexist(_PROGRAM)'; put 'or %superq(SYSPROCESSNAME) = %str(Compute Server)'; put 'or &mode=INCLUDE'; put '%then %do;'; put 'options obs=max replace mprint;'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5"'; put '%then %do;'; put 'options nosyntaxcheck;'; put '%end;'; put '%if &mode=INCLUDE %then %do;'; put '%if %sysfunc(exist(&errds))=1 %then %do;'; put 'data _null_;'; put 'set &errds;'; put 'call symputx(''iftrue'',iftrue,''l'');'; put 'call symputx(''mac'',mac,''l'');'; put 'call symputx(''msg'',msg,''l'');'; put 'putlog (_all_)(=);'; put 'run;'; put '%if (&iftrue)=0 %then %return;'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: No include errors found;'; put '%return;'; put '%end;'; put '%end;'; put '/* extract log errs / warns, if exist */'; put '%local logloc logline;'; put '%global logmsg; /* capture global messages */'; put '%if %symexist(SYSPRINTTOLOG) %then %let logloc=&SYSPRINTTOLOG;'; put '%else %let logloc=%qsysfunc(getoption(LOG));'; put 'proc printto log=log;run;'; put '%let logline=0;'; put '%if %length(&logloc)>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input; putlog _infile_;'; put 'i=1;'; put 'retain logonce 0;'; put 'if ('; put '_infile_=:"%str(WARN)ING" or _infile_=:"%str(ERR)OR"'; put ') and logonce=0 then'; put 'do;'; put 'call symputx(''logline'',_n_);'; put 'logonce+1;'; put 'end;'; put 'run;'; put '/* capture log including lines BEFORE the err */'; put '%if &logline>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input;'; put 'i=1;'; put 'stoploop=0;'; put 'if _n_ ge &logline-15 and stoploop=0 then do until (i>22);'; put 'call symputx(''logmsg'',catx(''\n'',symget(''logmsg''),_infile_));'; put 'input;'; put 'i+1;'; put 'stoploop=1;'; put 'end;'; put 'if stoploop=1 then stop;'; put 'run;'; put '%end;'; put '%end;'; put '%if %symexist(SYS_JES_JOB_URI) %then %do;'; put '/* setup webout for Viya */'; put 'options nobomfile;'; put '%if "X&SYS_JES_JOB_URI.X"="XX" %then %do;'; put 'filename _webout temp lrecl=999999 mod;'; put '%end;'; put '%else %do;'; put 'filename _webout filesrvc parenturi="&SYS_JES_JOB_URI"'; put 'name="_webout.json" lrecl=999999 mod;'; put '%end;'; put '%end;'; put '%else %if %sysfunc(filename(fref,&sasjs_stpsrv_header_loc))=0 %then %do;'; put 'options nobomfile;'; put '/* set up http header for SASjs Server */'; put '%let fid=%sysfunc(fopen(&fref,A));'; put '%if &fid=0 %then %do;'; put '%put %str(ERR)OR: %sysfunc(sysmsg());'; put '%return;'; put '%end;'; put '%let rc=%sysfunc(fput(&fid,%str(Content-Type: application/json)));'; put '%let rc=%sysfunc(fwrite(&fid));'; put '%let rc=%sysfunc(fclose(&fid));'; put '%let rc=%sysfunc(filename(&fref));'; put '%end;'; put '/* send response in SASjs JSON format */'; put 'data _null_;'; put 'file _webout mod lrecl=32000 encoding=''utf-8'';'; put 'length msg syswarningtext syserrortext $32767 mode $10 ;'; put 'sasdatetime=datetime();'; put 'msg=symget(''msg'');'; put '%if &logline>0 %then %do;'; put 'msg=cats(msg,''\n\nLog Extract:\n'',symget(''logmsg''));'; put '%end;'; put '/* escape the escapes */'; put 'msg=tranwrd(msg,''\'',''\\'');'; put '/* escape the quotes */'; put 'msg=tranwrd(msg,''"'',''\"'');'; put '/* ditch the CRLFs as chrome complains */'; put 'msg=compress(msg,,''kw'');'; put '/* quote without quoting the quotes (which are escaped instead) */'; put 'msg=cats(''"'',msg,''"'');'; put 'if symexist(''_debug'') then debug=quote(trim(symget(''_debug'')));'; put 'else debug=''""'';'; put 'if symget(''sasjsprocessmode'')=''Stored Program'' then mode=''SASJS'';'; put 'if mode ne ''SASJS'' then put ''>>weboutBEGIN<<'';'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'put '',"sasjsAbort" : [{'';'; put 'put '' "MSG":'' msg ;'; put 'put '' ,"MAC": "'' "&mac" ''"}]'';'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'put '',"_DEBUG":'' debug ;'; put 'if symexist(''_metauser'') then do;'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put 'end;'; put 'if symexist(''SYS_JES_JOB_URI'') then do;'; put 'SYS_JES_JOB_URI=quote(trim(symget(''SYS_JES_JOB_URI'')));'; put 'put '',"SYS_JES_JOB_URI": '' SYS_JES_JOB_URI;'; put 'end;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put ",""SYSWARNINGTEXT"" : " syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'put "}" ;'; put 'if mode ne ''SASJS'' then put ''>>weboutEND<<'';'; put 'run;'; put '%put _all_;'; put '%if "&sysprocessmode " = "SAS Stored Process Server " %then %do;'; put 'data _null_;'; put 'putlog ''stpsrvset program err and syscc'';'; put 'rc=stpsrvset(''program error'', 0);'; put 'call symputx("syscc",0,"g");'; put 'run;'; put '%if &sysscp=WIN'; put 'and 1=0 /* deprecating this logic until we figure out a consistent abort */'; put 'and "%substr(%str(&sysvlong ),1,8)"="9.04.01M"'; put 'and "%substr(%str(&sysvlong ),9,1)">"5" %then %do;'; put '/* skip approach (below) does not work in windows m6+ envs */'; put 'endsas;'; put '%end;'; put '%else %do;'; put '/**'; put '* endsas kills 9.4m3 deployments by orphaning multibridges.'; put '* Abort variants are ungraceful (non zero return code)'; put '* This approach lets SAS run silently until the end :-)'; put '* Caution - fails when called within a %include within a macro'; put '* Use mp_include() to handle this.'; put '*/'; put 'filename skip temp;'; put 'data _null_;'; put 'file skip;'; put 'put ''%macro skip();'';'; put 'comment ''%mend skip; -> fix lint '';'; put 'put ''%macro skippy();'';'; put 'comment ''%mend skippy; -> fix lint '';'; put 'run;'; put '%inc skip;'; put '%end;'; put '%end;'; put '%else %if "&sysprocessmode " = "SAS Compute Server " %then %do;'; put '/* endsas kills the session making it harder to fetch results */'; put 'data _null_;'; put 'syswarningtext=symget(''syswarningtext'');'; put 'syserrortext=symget(''syserrortext'');'; put 'abort_msg=symget(''msg'');'; put 'syscc=symget(''syscc'');'; put 'sysuserid=symget(''sysuserid'');'; put 'iftrue=symget(''iftrue'');'; put 'put (_all_)(/=);'; put 'call symputx(''syscc'',0);'; put 'abort cancel nolist;'; put 'run;'; put '%end;'; put '%else %do;'; put '%abort cancel;'; put '%end;'; put '%end;'; put '%else %do;'; put '%put _all_;'; put '%abort cancel;'; put '%end;'; put '%mend mp_abort;'; put '/** @endcond */'; put '%macro mm_getstpcode('; put 'tree=/User Folders/sasdemo/somestp'; put ',name='; put ',outloc=0'; put ',outref=0'; put ',mDebug=1'; put ',showlog=NO'; put ');'; put '%local mD;'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing &sysmacroname..sas;'; put '%&mD.put _local_;'; put '%if %length(&name)>0 %then %let name=/&name;'; put '/* first, check if STP exists */'; put '%local tsuri;'; put '%let tsuri=stopifempty ;'; put 'data _null_;'; put 'format type uri tsuri value $200.;'; put 'call missing (of _all_);'; put 'path="&tree&name(StoredProcess)";'; put '/* first, find the STP ID */'; put 'if metadata_pathobj("",path,"StoredProcess",type,uri)>0 then do;'; put '/* get sourcecode */'; put 'cnt=1;'; put 'do while (metadata_getnasn(uri,"Notes",cnt,tsuri)>0);'; put 'rc=metadata_getattr(tsuri,"Name",value);'; put '&mD.put tsuri= value=;'; put 'if value="SourceCode" then do;'; put '/* found it! */'; put 'rc=metadata_getattr(tsuri,"Id",value);'; put 'call symputx(''tsuri'',value,''l'');'; put 'stop;'; put 'end;'; put 'cnt+1;'; put 'end;'; put 'end;'; put 'else put (_all_)(=);'; put 'run;'; put '%mp_abort(iftrue= (&tsuri=stopifempty)'; put ',mac=mm_getstpcode'; put ',msg=%str(&tree&name.(StoredProcess) not found!)'; put ')'; put '/**'; put '* Now we can extract the textstore'; put '*/'; put 'filename __getdoc temp lrecl=10000000;'; put 'proc metadata'; put 'in="$METAREPOSITORY'; put ''; put 'SAS1"'; put 'out=__getdoc ;'; put 'run;'; put '/* find the beginning of the text */'; put '%local start;'; put 'data _null_;'; put 'infile __getdoc lrecl=10000;'; put 'input;'; put 'start=index(_infile_,''StoredText="'');'; put 'if start then do;'; put 'call symputx("start",start+11);'; put '*putlog ''"'' _infile_ ''"'';'; put 'end;'; put 'stop;'; put '%local outeng;'; put '%if "&outloc"="0" %then %let outeng=TEMP;'; put '%else %let outeng="&outloc";'; put '%local fref;'; put '%if &outref=0 %then %let fref=%mf_getuniquefileref();'; put '%else %let fref=&outref;'; put '/* read the content, byte by byte, resolving escaped chars */'; put 'filename &fref &outeng lrecl=100000;'; put 'data _null_;'; put 'length filein 8 fileid 8;'; put 'filein = fopen("__getdoc","I",1,"B");'; put 'fileid = fopen("&fref","O",1,"B");'; put 'rec = "20"x;'; put 'length entity $6;'; put 'do while(fread(filein)=0);'; put 'x+1;'; put 'if x>&start then do;'; put 'rc = fget(filein,rec,1);'; put 'if rec=''"'' then leave;'; put 'else if rec="&" then do;'; put 'entity=rec;'; put 'do until (rec=";");'; put 'if fread(filein) ne 0 then goto getout;'; put 'rc = fget(filein,rec,1);'; put 'entity=cats(entity,rec);'; put 'end;'; put 'select (entity);'; put 'when (''&'' ) rec=''&'' ;'; put 'when (''<'' ) rec=''<'' ;'; put 'when (''>'' ) rec=''>'' ;'; put 'when (''''') rec="''" ;'; put 'when (''"'') rec=''"'' ;'; put 'when ('' '') rec=''0A''x;'; put 'when ('' '') rec=''0D''x;'; put 'when (''$'' ) rec=''$'' ;'; put 'when ('' '') rec=''09''x;'; put 'otherwise putlog "%str(WARN)ING: missing value for " entity=;'; put 'end;'; put 'rc =fput(fileid, substr(rec,1,1));'; put 'rc =fwrite(fileid);'; put 'end;'; put 'else do;'; put 'rc =fput(fileid,rec);'; put 'rc =fwrite(fileid);'; put 'end;'; put 'end;'; put 'end;'; put 'getout:'; put 'rc=fclose(filein);'; put 'rc=fclose(fileid);'; put 'run;'; put '%if &showlog=YES %then %do;'; put 'data _null_;'; put 'infile &fref lrecl=32767 end=last;'; put 'input;'; put 'if _n_=1 then putlog ''>>stpcodeBEGIN<<'';'; put 'putlog _infile_;'; put 'if last then putlog ''>>stpcodeEND<<'';'; put 'run;'; put '%end;'; put 'filename __getdoc clear;'; put '%if &outref=0 %then %do;'; put 'filename &fref clear;'; put '%end;'; put '%mend mm_getstpcode;'; put '%macro dc_getsettings();'; put '%global _program;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(syscc=&syscc on entry (&syswarningtext &syserrortext))'; put ')'; put '%if %length(&_PROGRAM)>1 %then %let root=&_program;'; put '%else %do;'; put '%global _metauser;'; put '%let _metauser=&sysuserid;'; put '/* to mimic a "real" _program we need to give a dummy role and stp name */'; put '%let root=/dummyRole/dummyName;'; put '%end;'; put '/* the DC precode is stored in the Admin folder in the root of'; put 'the project. Lets find that root. */'; put '%put &=root;'; put '%let root=%mf_getapploc();'; put '%put &=root;'; put '/* Now we know the root location we can retrieve the params */'; put '%let temploc=%sysfunc(pathname(work))/settings.txt;'; put '%mm_getstpcode(tree=&root/services/public'; put ',name=Data_Controller_Settings'; put ',outloc=&temploc'; put ')'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Unable to run getstpcode)'; put ')'; put 'filename _getsets "&temploc" lrecl=2000;'; put '/*'; put 'Do not use mp_include here - this puts a copy in every service, which creates'; put 'compilation problems when calling services from mp_include'; put '*/'; put '%inc _getsets/source2;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Problem running &sysmacroname (&syswarningtext &syserrortext))'; put ')'; put '%mend dc_getsettings;'; put '%macro mf_fmtdttm('; put ')/*/STORE SOURCE*/;'; put '%if "&sysver"="9.2" or "&sysver"="9.3"'; put 'or ("&sysver"="9.4" and "%substr(&SYSVLONG,9,1)" le "3")'; put 'or "%substr(&sysver,1,1)"="4"'; put 'or "%substr(&sysver,1,1)"="5"'; put '%then %do;DATETIME19.3%end;'; put '%else %do;E8601DT26.6%end;'; put '%mend mf_fmtdttm;'; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '%macro mp_init(prefix=SASJS'; put ')/*/STORE SOURCE*/;'; put '%if %symexist(SASJS_PREFIX) %then %return; /* only run once */'; put '%global'; put 'SASJS_PREFIX /* the ONLY hard-coded global macro variable in SASjs */'; put '&prefix._FUNCTIONS /* used in mcf_init() to track core function compilation */'; put '&prefix._INIT_NUM /* initialisation time as numeric */'; put '&prefix._INIT_DTTM /* initialisation time in E8601DT26.6 format */'; put '&prefix.WORK /* avoid typing %sysfunc(pathname(work)) every time */'; put ';'; put '%let sasjs_prefix=&prefix;'; put 'data _null_;'; put 'dttm=datetime();'; put 'call symputx("&sasjs_prefix._init_num",dttm,''g'');'; put 'call symputx("&sasjs_prefix._init_dttm",put(dttm,E8601DT26.6),''g'');'; put 'call symputx("&sasjs_prefix.work",pathname(''WORK''),''g'');'; put 'run;'; put 'options'; put 'compress=CHAR /* default is none so ensure we have something! */'; put 'datastmtchk=ALLKEYWORDS /* protection from overwriting input datasets */'; put 'errorcheck=STRICT /* catch errs in libname/filename statements */'; put 'fmterr /* ensure err when a format cannot be found */'; put 'mergenoby=%str(ERR)OR /* throw err when a merge has no BY variables */'; put 'missing=. /* changing this can cause hard to detect errs */'; put 'noquotelenmax /* avoid warnings for long strings */'; put 'noreplace /* avoid overwriting permanent datasets */'; put 'ps=max /* reduce log size slightly */'; put 'ls=max /* reduce log even more and avoid word truncation */'; put 'validmemname=COMPATIBLE /* avoid special characters etc in table names */'; put 'validvarname=V7 /* avoid special characters etc in variable names */'; put 'varinitchk=%str(ERR)OR /* avoid data mistakes from variable name typos */'; put 'varlenchk=%str(ERR)OR /* fail hard if truncation (data loss) can result */'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5" %then %do;'; put 'noautocorrect /* disallow misspelled procedure names */'; put 'dsoptions=note2err /* undocumented - convert bad NOTEs to ERRs */'; put '%end;'; put ';'; put '%mend mp_init;'; put '%macro mpeinit(fetch=YES);'; put '%global mpeinit'; put 'mpeadmins /* group with unrestricted Meditor access */'; put 'mpelocapprovals /* location for landing and staging files */'; put 'mpelib /* location of configuration tables for DC */'; put 'dc_repo_users /* location of user / group metadata */'; put 'dc_licence_key /* extracted in dc_getsettings */'; put 'dc_activation_key /* extracted in dc_getsettings */'; put 'dc_locale /* extracted in dc_getsettings */'; put 'dc_dttmtfmt /* can be overridden in dc_getsettings */'; put '_debug'; put ';'; put '%if &mpeinit=1 %then %return;'; put '%else %let mpeinit=1;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(Problem on service startup (&syswarningtext &syserrortext))'; put ')'; put '%mp_init()'; put '%if &fetch=YES %then %do;'; put '%webout(FETCH)'; put '%end;'; put '%global _CLIENTNAME;'; put '%mp_abort(iftrue= (&_CLIENTNAME=SAS Enterprise Guide)'; put ',mac=&_program..sas'; put ',msg=%str(Data Controller is a web app and should not be executed from EG)'; put ')'; put 'options urlencoding=utf8 nobomfile lrecl=32767;'; put '%let perf=%sysfunc(datetime());'; put '%put perfdiff: 0;'; put '%let dc_locale=SYSTEM; /* default if not set */'; put '/**'; put '* E8601DT26.6 has widest database support - but not all SAS flavours can'; put '* handle it. Override in the settings STP if needed.'; put '*/'; put 'data _null_;'; put 'dc_dttmtfmt=''"%sysfunc(datetime(),''!!"%mf_fmtdttm()"!!'')"dt'';'; put 'call symputx(''dc_dttmtfmt'',dc_dttmtfmt);'; put 'put dc_dttmtfmt=;'; put 'run;'; put '%put &=dc_dttmtfmt;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc prior to dc_getsettings)'; put ')'; put '%dc_getsettings()'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc after dc_getsettings)'; put ')'; put 'data _null_;'; put 'set &DC_LIBREF..mpe_config(where=('; put 'var_scope="DC"'; put 'and &dc_dttmtfmt lt tx_to'; put 'and var_active=1'; put '));'; put 'call symputx(var_name,var_value,''G'');'; put 'putlog var_name "=" var_value;'; put 'run;'; put '%let mpelib=&dc_libref;'; put '%let mpeadmins=&dc_admin_group;'; put '%let mpelocapprovals=&dc_staging_area;'; put '%let dc_repo_users=&dc_repo_users;'; put '%if &dc_locale ne SYSTEM %then %do;'; put 'options locale=&dc_locale;'; put '%end;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(Problem during compilation or with STP precode (&syswarningtext))'; put ')'; put '%mend mpeinit;'; put '%macro mf_mval(var);'; put '%if %symexist(&var) %then %do;'; put '%superq(&var)'; put '%end;'; put '%mend mf_mval;'; put '%macro mf_trimstr(basestr,trimstr);'; put '%local baselen trimlen trimval;'; put '/* return if basestr is shorter than trimstr (or 0) */'; put '%let baselen=%length(%superq(basestr));'; put '%let trimlen=%length(%superq(trimstr));'; put '%if &baselen < &trimlen or &baselen=0 %then %return;'; put '/* obtain the characters from the end of basestr */'; put '%let trimval=%qsubstr(%superq(basestr)'; put ',%length(%superq(basestr))-&trimlen+1'; put ',&trimlen);'; put '/* compare and if matching, chop it off! */'; put '%if %superq(basestr)=%superq(trimstr) %then %do;'; put '%return;'; put '%end;'; put '%else %if %superq(trimval)=%superq(trimstr) %then %do;'; put '%qsubstr(%superq(basestr),1,%length(%superq(basestr))-&trimlen)'; put '%end;'; put '%else %do;'; put '&basestr'; put '%end;'; put '%mend mf_trimstr;'; put '%macro mf_getplatform(switch'; put ')/*/STORE SOURCE*/;'; put '%local a b c;'; put '%if &switch.NONE=NONE %then %do;'; put '%if %symexist(sasjsprocessmode) %then %do;'; put '%if &sasjsprocessmode=Stored Program %then %do;'; put 'SASJS'; put '%return;'; put '%end;'; put '%end;'; put '%if %symexist(sysprocessmode) %then %do;'; put '%if "&sysprocessmode"="SAS Object Server"'; put 'or "&sysprocessmode"= "SAS Compute Server" %then %do;'; put 'SASVIYA'; put '%end;'; put '%else %if "&sysprocessmode"="SAS Stored Process Server"'; put 'or "&sysprocessmode"="SAS Workspace Server"'; put '%then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if %symexist(_metaport) or %symexist(_metauser) %then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if &switch=SASSTUDIO %then %do;'; put '/* return the version of SAS Studio else 0 */'; put '%if %mf_mval(_CLIENTAPP)=%str(SAS Studio) %then %do;'; put '%let a=%mf_mval(_CLIENTVERSION);'; put '%let b=%scan(&a,1,.);'; put '%if %eval(&b >2) %then %do;'; put '&b'; put '%end;'; put '%else 0;'; put '%end;'; put '%else 0;'; put '%end;'; put '%else %if &switch=VIYARESTAPI %then %do;'; put '%mf_trimstr(%sysfunc(getoption(servicesbaseurl)),/)'; put '%end;'; put '%mend mf_getplatform;'; put '%macro mpeterm();'; put '%local oldloc;'; put 'data _null_;'; put 'if symexist(''SYSPRINTTOLOG'') then oldloc=symget(''SYSPRINTTOLOG'');'; put 'else oldloc=getoption(''LOG'');'; put 'if subpad(oldloc,1,1) not in (''"'',"''",'' '') then oldloc=quote(cats(oldloc));'; put 'call symputx(''oldloc'',oldloc,''l'');'; put 'run;'; put '%if %length(&oldloc)>0 %then %do;'; put 'proc printto log=log;'; put 'run;'; put 'data _null_;'; put 'infile &oldloc;'; put 'input; putlog _infile_;'; put 'run;'; put '%end;'; put '%if %sysfunc(exist(&dc_libref..mpe_requests)) and %mf_getplatform() ne SASVIYA'; put '%then %do;'; put 'data ;'; put 'if 0 then set &dc_libref..mpe_requests;'; put 'request_dttm=%sysfunc(datetime());'; put 'request_user="%mf_getuser()";'; put 'request_service="%scan(&_program,-2,/)/%scan(&_program,-1,/)";'; put 'request_params='''';'; put 'output;stop;'; put 'proc append base=&dc_libref..mpe_requests data=&syslast force nowarn;'; put 'run;'; put '%end;'; put '%mend mpeterm;'; put '* SAS Macros end;'; put '* SAS Includes start;'; put '* SAS Includes end;'; put '* Binary Files start;'; put '* Binary Files end;'; put '* ServiceInit start;'; put 'options noquotelenmax ps=max;'; put '/* create dummy macros to prevent stpbegin / stpend from corrupting sessions */'; put '%macro stpbegin();'; put '%put NOTE: the STPBEGIN macro should not be used for web apps!;'; put '%mend stpbegin;'; put '%macro stpend();'; put '%put NOTE: the STPEND macro should not be used for web apps!;'; put '%mend stpend;'; put '* ServiceInit end;'; put '* Service start;'; put '/**'; put '@file'; put '@brief Sample XLMAP Data hook program (sample_xlmap_data_postapprove)'; put '@details This hook script should NOT be modified in place, as the changes'; put 'would be lost in your next Data Controller deployment.'; put 'Instead, create a copy of this hook script and place it OUTSIDE the'; put 'Data Controller metadata folder.'; put 'Available macro variables:'; put '@li LOAD_REF - The Load Reference (unique upload id)'; put '@li ORIG_LIBDS - The target library.dataset that was just loaded'; put '**/'; put 'data _null_;'; put 'set work.staging_ds;'; put 'putlog ''load ref is in the staged data: '' load_ref;'; put 'stop;'; put 'run;'; put '%put the unique identifier (LOAD_REF) is also a macro variable: &LOAD_REF;'; put '* Service end;'; run; %mm_createwebservice(path=&appLoc/&path, name=&service, code=sascode, server=&serverName, replace=yes) filename sascode clear; %let service=sample_xlmap_data_postedit; filename sascode temp lrecl=32767; data _null_; file sascode; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '/**'; put '@file mp_jsonout.sas'; put '@brief Writes JSON in SASjs format to a fileref'; put '@details This macro can be used to OPEN a JSON stream and send one or more'; put 'tables as arrays of rows, where each row can be an object or a nested array.'; put 'There are two engines available - DATASTEP or PROCJSON.'; put 'PROC JSON is fast but will produce errs like the ones below if'; put 'special chars are encountered.'; put '> (ERR)OR: Some code points did not transcode.'; put '> An object or array close is not valid at this point in the JSON text.'; put '> Date value out of range'; put 'If this happens, try running with ENGINE=DATASTEP.'; put 'The DATASTEP engine is used to handle special SAS missing numerics, and'; put 'can also convert entire datasets to formatted values. Output JSON is always'; put 'in UTF-8.'; put 'Usage:'; put 'filename tmp temp;'; put 'data class; set sashelp.class;run;'; put '%mp_jsonout(OPEN,jref=tmp)'; put '%mp_jsonout(OBJ,class,jref=tmp)'; put '%mp_jsonout(OBJ,class,dslabel=class2,jref=tmp,showmeta=Y)'; put '%mp_jsonout(CLOSE,jref=tmp)'; put 'data _null_;'; put 'infile tmp;'; put 'input;putlog _infile_;'; put 'run;'; put 'If you are building web apps with SAS then you are strongly encouraged to use'; put 'the mX_createwebservice macros in combination with the'; put '[sasjs adapter](https://github.com/sasjs/adapter).'; put 'For more information see https://sasjs.io'; put '@param [in] action Valid values:'; put '@li OPEN - opens the JSON'; put '@li OBJ - sends a table with each row as an object'; put '@li ARR - sends a table with each row in an array'; put '@li CLOSE - closes the JSON'; put '@param [in] ds The dataset to send. Must be a work table.'; put '@param [out] jref= (_webout) The fileref to which to send the JSON'; put '@param [out] dslabel= The name to give the table in the exported JSON'; put '@param [in] fmt= (Y) Whether to keep (Y) or strip (N) formats from the table'; put '@param [in] engine= (DATASTEP) Which engine to use to send the JSON. Options:'; put '@li PROCJSON (default)'; put '@li DATASTEP (more reliable when data has non standard characters)'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to JSON'; put '

Related Files

'; put '@li mp_ds2fmtds.sas'; put '@version 9.2'; put '@author Allan Bowe'; put '@source https://github.com/sasjs/core'; put '**/'; put '%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y'; put ',engine=DATASTEP'; put ',missing=NULL'; put ',showmeta=N'; put ',maxobs=MAX'; put ')/*/STORE SOURCE*/;'; put '%local tempds colinfo fmtds i numcols numobs stmt_obs lastobs optval'; put 'tmpds1 tmpds2 tmpds3 tmpds4;'; put '%let numcols=0;'; put '%if &maxobs ne MAX %then %let stmt_obs=%str(if _n_>&maxobs then stop;);'; put '%if &action=OPEN %then %do;'; put 'options nobomfile;'; put 'data _null_;file &jref encoding=''utf-8'' lrecl=200;'; put 'put ''{"PROCESSED_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''"'';'; put 'run;'; put '%end;'; put '%else %if (&action=ARR or &action=OBJ) %then %do;'; put '/* force variable names to always be uppercase in the JSON */'; put 'options validvarname=upcase;'; put '/* To avoid issues with _webout on EBI - such as encoding diffs and truncation'; put '(https://support.sas.com/kb/49/325.html) we use temporary files */'; put 'filename _sjs1 temp lrecl=200 ;'; put 'data _null_; file _sjs1 encoding=''utf-8'';'; put 'put ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":";'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjs1 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs1 clear;'; put '/* grab col defs */'; put 'proc contents noprint data=&ds'; put 'out=_data_(keep=name type length format formatl formatd varnum label);'; put 'run;'; put '%let colinfo=%scan(&syslast,2,.);'; put 'proc sort data=&colinfo;'; put 'by varnum;'; put 'run;'; put '/* move meta to mac vars */'; put 'data &colinfo;'; put 'if _n_=1 then call symputx(''numcols'',nobs,''l'');'; put 'set &colinfo end=last nobs=nobs;'; put 'name=upcase(name);'; put '/* fix formats */'; put 'if type=2 or type=6 then do;'; put 'typelong=''char'';'; put 'length fmt $49.;'; put 'if format='''' then fmt=cats(''$'',length,''.'');'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else fmt=cats(format,formatl,''.'');'; put 'end;'; put 'else do;'; put 'typelong=''num'';'; put 'if format='''' then fmt=''best.'';'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else if formatd=0 then fmt=cats(format,formatl,''.'');'; put 'else fmt=cats(format,formatl,''.'',formatd);'; put 'end;'; put '/* 32 char unique name */'; put 'newname=''sasjs''!!substr(cats(put(md5(name),$hex32.)),1,27);'; put 'call symputx(cats(''name'',_n_),name,''l'');'; put 'call symputx(cats(''newname'',_n_),newname,''l'');'; put 'call symputx(cats(''length'',_n_),length,''l'');'; put 'call symputx(cats(''fmt'',_n_),fmt,''l'');'; put 'call symputx(cats(''type'',_n_),type,''l'');'; put 'call symputx(cats(''typelong'',_n_),typelong,''l'');'; put 'call symputx(cats(''label'',_n_),coalescec(label,name),''l'');'; put '/* overwritten when fmt=Y and a custom format exists in catalog */'; put 'if typelong=''num'' then call symputx(cats(''fmtlen'',_n_),200,''l'');'; put 'else call symputx(cats(''fmtlen'',_n_),min(32767,ceil((length+10)*1.5)),''l'');'; put 'run;'; put '%let tempds=%substr(_%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql;'; put 'select count(*) into: lastobs from &ds;'; put '%if &maxobs ne MAX %then %let lastobs=%sysfunc(min(&lastobs,&maxobs));'; put '%if &engine=PROCJSON %then %do;'; put '%if &missing=STRING %then %do;'; put '%put &sysmacroname: Special Missings not supported in proc json.;'; put '%put &sysmacroname: Switching to DATASTEP engine;'; put '%goto datastep;'; put '%end;'; put 'data &tempds;'; put 'set &ds;'; put '&stmt_obs;'; put '%if &fmt=N %then format _numeric_ best32.;;'; put '/* PRETTY is necessary to avoid line truncation in large files */'; put 'filename _sjs2 temp lrecl=131068 encoding=''utf-8'';'; put 'proc json out=_sjs2 pretty'; put '%if &action=ARR %then nokeys ;'; put ';export &tempds / nosastags fmtnumeric;'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs2 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs2 clear;'; put '%end;'; put '%else %if &engine=DATASTEP %then %do;'; put '%datastep:'; put '%if %sysfunc(exist(&ds)) ne 1 & %sysfunc(exist(&ds,VIEW)) ne 1'; put '%then %do;'; put '%put &sysmacroname: &ds NOT FOUND!!!;'; put '%return;'; put '%end;'; put '%if &fmt=Y %then %do;'; put '/**'; put '* Extract format definitions'; put '* First, by getting library locations from dictionary.formats'; put '* Then, by exporting the width using proc format'; put '* Cannot use maxw from sashelp.vformat as not always populated'; put '* Cannot use fmtinfo() as not supported in all flavours'; put '*/'; put '%let tmpds1=%substr(fmtsum%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds2=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds3=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds4=%substr(col%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql noprint;'; put 'create table &tmpds1 as'; put 'select cats(libname,''.'',memname) as FMTCAT,'; put 'FMTNAME'; put 'from dictionary.formats'; put 'where fmttype=''F'' and libname is not null'; put 'and fmtname in (select format from &colinfo where format is not null)'; put 'order by 1;'; put 'create table &tmpds2('; put 'FMTNAME char(32),'; put 'LENGTH num'; put ');'; put '%local catlist cat fmtlist i;'; put 'select distinct fmtcat into: catlist separated by '' '' from &tmpds1;'; put '%do i=1 %to %sysfunc(countw(&catlist,%str( )));'; put '%let cat=%scan(&catlist,&i,%str( ));'; put 'proc sql;'; put 'select distinct fmtname into: fmtlist separated by '' '''; put 'from &tmpds1 where fmtcat="&cat";'; put 'proc format lib=&cat cntlout=&tmpds3(keep=fmtname length);'; put 'select &fmtlist;'; put 'run;'; put 'proc sql;'; put 'insert into &tmpds2 select distinct fmtname,length from &tmpds3;'; put '%end;'; put 'proc sql;'; put 'create table &tmpds4 as'; put 'select a.*, b.length as MAXW'; put 'from &colinfo a'; put 'left join &tmpds2 b'; put 'on cats(a.format)=cats(upcase(b.fmtname))'; put 'order by a.varnum;'; put 'data _null_;'; put 'set &tmpds4;'; put 'if not missing(maxw);'; put 'call symputx('; put 'cats(''fmtlen'',_n_),'; put '/* vars need extra padding due to JSON escaping of special chars */'; put 'min(32767,ceil((max(length,maxw)+10)*1.5))'; put ',''l'''; put ');'; put 'run;'; put '/* configure varlenchk - as we are explicitly shortening the variables */'; put '%let optval=%sysfunc(getoption(varlenchk));'; put 'options varlenchk=NOWARN;'; put 'data _data_(compress=char);'; put '/* shorten the new vars */'; put 'length'; put '%do i=1 %to &numcols;'; put '&&name&i $&&fmtlen&i'; put '%end;'; put ';'; put '/* rename on entry */'; put 'set &ds(rename=('; put '%do i=1 %to &numcols;'; put '&&name&i=&&newname&i'; put '%end;'; put '));'; put '&stmt_obs;'; put 'drop'; put '%do i=1 %to &numcols;'; put '&&newname&i'; put '%end;'; put ';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=num %then %do;'; put '&&name&i=cats(put(&&newname&i,&&fmt&i));'; put '%end;'; put '%else %do;'; put '&&name&i=put(&&newname&i,&&fmt&i);'; put '%end;'; put '%end;'; put 'if _error_ then do;'; put 'call symputx(''syscc'',1012);'; put 'stop;'; put 'end;'; put 'run;'; put '%let fmtds=&syslast;'; put 'options varlenchk=&optval;'; put '%end;'; put 'proc format; /* credit yabwon for special null removal */'; put 'value bart (default=40)'; put '%if &missing=NULL %then %do;'; put '._ - .z = null'; put '%end;'; put '%else %do;'; put '._ = [quote()]'; put '. = null'; put '.a - .z = [quote()]'; put '%end;'; put 'other = [best.];'; put 'data &tempds;'; put 'attrib _all_ label='''';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'length &&name&i $&&fmtlen&i...;'; put 'format &&name&i $&&fmtlen&i...;'; put '%end;'; put '%end;'; put '%if &fmt=Y %then %do;'; put 'set &fmtds;'; put '%end;'; put '%else %do;'; put 'set &ds;'; put '%end;'; put '&stmt_obs;'; put 'format _numeric_ bart.;'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'if findc(&&name&i,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put '&&name&i=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,&&name&i)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else &&name&i=quote(cats(&&name&i));'; put '%end;'; put '%end;'; put 'run;'; put 'filename _sjs3 temp lrecl=131068 ;'; put 'data _null_;'; put 'file _sjs3 encoding=''utf-8'';'; put 'if _n_=1 then put "[";'; put 'set &tempds;'; put 'if _n_>1 then put "," @; put'; put '%if &action=ARR %then "[" ; %else "{" ;'; put '%do i=1 %to &numcols;'; put '%if &i>1 %then "," ;'; put '%if &action=OBJ %then """&&name&i"":" ;'; put '"&&name&i"n /* name literal for reserved variable names */'; put '%end;'; put '%if &action=ARR %then "]" ; %else "}" ; ;'; put '/* close out the table */'; put 'data _null_;'; put 'file _sjs3 mod encoding=''utf-8'';'; put 'put '']'';'; put 'run;'; put 'data _null_;'; put 'infile _sjs3 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs3 clear;'; put '%end;'; put 'proc sql;'; put 'drop table &colinfo, &tempds;'; put '%if %substr(&showmeta,1,1)=Y %then %do;'; put 'filename _sjs4 temp lrecl=131068 encoding=''utf-8'';'; put 'data _null_;'; put 'file _sjs4;'; put 'length label $350;'; put 'put ", ""$%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":{""vars"":{";'; put 'do i=1 to &numcols;'; put 'name=quote(trim(symget(cats(''name'',i))));'; put 'format=quote(trim(symget(cats(''fmt'',i))));'; put 'label=quote(prxchange(''s/\\/\\\\/'',-1,trim(symget(cats(''label'',i)))));'; put 'length=quote(trim(symget(cats(''length'',i))));'; put 'type=quote(trim(symget(cats(''typelong'',i))));'; put 'if i>1 then put "," @@;'; put 'put name '':{"format":'' format '',"label":'' label'; put ''',"length":'' length '',"type":'' type ''}'';'; put 'end;'; put 'put ''}}'';'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs4 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs4 clear;'; put '%end;'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put 'data _null_; file &jref encoding=''utf-8'' mod ;'; put 'put "}";'; put 'run;'; put '%end;'; put '%mend mp_jsonout;'; put '/**'; put '@file mm_webout.sas'; put '@brief Send data to/from SAS Stored Processes'; put '@details This macro should be added to the start of each Stored Process,'; put '**immediately** followed by a call to:'; put '%mm_webout(FETCH)'; put 'This will read all the input data and create same-named SAS datasets in the'; put 'WORK library. You can then insert your code, and send data back using the'; put 'following syntax:'; put 'data some datasets; * make some data ;'; put 'retain some columns;'; put 'run;'; put '%mm_webout(OPEN)'; put '%mm_webout(ARR,some) * Array format, fast, suitable for large tables ;'; put '%mm_webout(OBJ,datasets) * Object format, easier to work with ;'; put 'Finally, wrap everything up send some helpful system variables too'; put '%mm_webout(CLOSE)'; put '@param [in] action Either FETCH, OPEN, ARR, OBJ or CLOSE'; put '@param [in] ds The dataset to send back to the frontend'; put '@param [out] dslabel= Value to use instead of table name for sending to JSON'; put '@param [in] fmt= (N) Setting Y converts all vars to their formatted values'; put '@param [out] fref= (_webout) The fileref to which to write the JSON'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] workobs= (0) When set to a positive integer, will create a new'; put 'output object (WORK) which contains this number of observations from all'; put 'tables in the WORK library.'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to output JSON'; put '

SAS Macros

'; put '@li mp_jsonout.sas'; put '

Related Macros

'; put '@li ms_webout.sas'; put '@li mv_webout.sas'; put '@version 9.3'; put '@author Allan Bowe'; put '**/'; put '%macro mm_webout(action,ds,dslabel=,fref=_webout,fmt=N,missing=NULL'; put ',showmeta=N,maxobs=MAX,workobs=0'; put ');'; put '%global _webin_file_count _webin_fileref1 _webin_name1 _program _debug'; put 'sasjs_tables;'; put '%local i tempds jsonengine;'; put '/* see https://github.com/sasjs/core/issues/41 */'; put '%if "%upcase(&SYSENCODING)" ne "UTF-8" %then %let jsonengine=PROCJSON;'; put '%else %let jsonengine=DATASTEP;'; put '%if &action=FETCH %then %do;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'options mprint notes mprintnest;'; put '%end;'; put '%let _webin_file_count=%eval(&_webin_file_count+0);'; put '/* now read in the data */'; put '%do i=1 %to &_webin_file_count;'; put '%if &_webin_file_count=1 %then %do;'; put '%let _webin_fileref1=&_webin_fileref;'; put '%let _webin_name1=&_webin_name;'; put '%end;'; put 'data _null_;'; put 'infile &&_webin_fileref&i termstr=crlf;'; put 'input;'; put 'call symputx(''input_statement'',_infile_);'; put 'putlog "&&_webin_name&i input statement: " _infile_;'; put 'stop;'; put 'data &&_webin_name&i;'; put 'infile &&_webin_fileref&i firstobs=2 dsd termstr=crlf encoding=''utf-8'';'; put 'input &input_statement;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'if _n_<20 then putlog _infile_;'; put '%end;'; put 'run;'; put '%let sasjs_tables=&sasjs_tables &&_webin_name&i;'; put '%end;'; put '%end;'; put '%else %if &action=OPEN %then %do;'; put '/* fix encoding */'; put 'OPTIONS NOBOMFILE;'; put '/**'; put '* check xengine type to avoid the below err message:'; put '* > Function is only valid for filerefs using the CACHE access method.'; put '*/'; put 'data _null_;'; put 'set sashelp.vextfl(where=(fileref="_WEBOUT"));'; put 'if xengine=''STREAM'' then do;'; put 'rc=stpsrv_header(''Content-type'',"text/html; encoding=utf-8");'; put 'end;'; put 'run;'; put '/* setup json */'; put 'data _null_;file &fref encoding=''utf-8'';'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutBEGIN<<'';'; put '%end;'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'run;'; put '%end;'; put '%else %if &action=ARR or &action=OBJ %then %do;'; put '%mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt,jref=&fref'; put ',engine=&jsonengine,missing=&missing,showmeta=&showmeta,maxobs=&maxobs'; put ')'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put '/* To avoid issues with _webout on EBI we use a temporary file */'; put 'filename _sjsref temp lrecl=131068;'; put '%if %str(&workobs) > 0 %then %do;'; put '/* if debug mode, send back first XX records of each work table also */'; put 'data;run;%let tempds=%scan(&syslast,2,.);'; put 'ods output Members=&tempds;'; put 'proc datasets library=WORK memtype=data;'; put '%local wtcnt;%let wtcnt=0;'; put 'data _null_;'; put 'set &tempds;'; put 'if not (upcase(name) =:"DATA"); /* ignore temp datasets */'; put 'i+1;'; put 'call symputx(cats(''wt'',i),name,''l'');'; put 'call symputx(''wtcnt'',i,''l'');'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put ",""WORK"":{";'; put '%do i=1 %to &wtcnt;'; put '%let wt=&&wt&i;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'dsid=open("WORK.&wt",''is'');'; put 'nlobs=attrn(dsid,''NLOBS'');'; put 'nvars=attrn(dsid,''NVARS'');'; put 'rc=close(dsid);'; put 'if &i>1 then put '',''@;'; put 'put " ""&wt"" : {";'; put 'put ''"nlobs":'' nlobs;'; put 'put '',"nvars":'' nvars;'; put '%mp_jsonout(OBJ,&wt,jref=_sjsref,dslabel=first10rows,showmeta=Y'; put ',maxobs=&workobs'; put ')'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put '%end;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put 'run;'; put '%end;'; put '/* close off json */'; put 'data _null_;file _sjsref mod encoding=''utf-8'';'; put 'length SYSPROCESSNAME syserrortext syswarningtext autoexec $512;'; put 'put ",""_DEBUG"" : ""&_debug"" ";'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'autoexec=quote(urlencode(trim(getoption(''autoexec''))));'; put 'put '',"AUTOEXEC" : '' autoexec;'; put 'put ",""MF_GETUSER"" : ""%mf_getuser()"" ";'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'put ",""SYSENCODING"" : ""&sysencoding"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSPROCESSID"" : ""&SYSPROCESSID"" ";'; put 'put ",""SYSPROCESSMODE"" : ""&SYSPROCESSMODE"" ";'; put 'SYSPROCESSNAME=quote(urlencode(cats(SYSPROCESSNAME)));'; put 'put ",""SYSPROCESSNAME"" : " SYSPROCESSNAME;'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put '',"SYSWARNINGTEXT" : '' syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'length memsize $32;'; put 'memsize="%sysfunc(INPUTN(%sysfunc(getoption(memsize)), best.),sizekmg.)";'; put 'memsize=quote(cats(memsize));'; put 'put '',"MEMSIZE" : '' memsize;'; put 'put "}" @;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutEND<<'';'; put '%end;'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjsref lrecl=1 recfm=n;'; put 'file &fref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjsref clear;'; put '%end;'; put '%mend mm_webout;'; put '%macro webout(action,ds,dslabel=,fmt=,missing=NULL,showmeta=NO,maxobs=MAX);'; put '%mm_webout(&action,ds=&ds,dslabel=&dslabel,fmt=&fmt'; put ',missing=&missing'; put ',showmeta=&showmeta'; put ',maxobs=&maxobs'; put ') %mend;'; put '/* provide additional debug info */'; put '%global _program;'; put '%put &=syscc;'; put '%put user=%mf_getuser();'; put '%put pgm=&_program;'; put '%put timestamp=%sysfunc(datetime(),datetime19.);'; put '* Service Variables start;'; put '* Service Variables end;'; put '* SAS Macros start;'; put '%macro mf_getapploc(pgm);'; put '%if "&pgm"="" %then %do;'; put '%if %symexist(_program) %then %let pgm=&_program;'; put '%else %do;'; put '%put &sysmacroname: No value provided and no _program variable available;'; put '%return;'; put '%end;'; put '%end;'; put '%local root;'; put '/**'; put '* First check we are not in the tests/macros folder (which has no subfolders)'; put '* or specifically in the testsetup or testteardown services'; put '*/'; put '%if %index(&pgm,/tests/macros/)'; put 'or %index(&pgm,/tests/testsetup)'; put 'or %index(&pgm,/tests/testteardown)'; put '%then %do;'; put '%let root=%substr(&pgm,1,%index(&pgm,/tests)-1);'; put '&root'; put '%return;'; put '%end;'; put '/**'; put '* Next, move up two levels to avoid matches on subfolder or service name'; put '*/'; put '%let root=%substr(&pgm,1,%length(&pgm)-%length(%scan(&pgm,-1,/))-1);'; put '%let root=%substr(&root,1,%length(&root)-%length(%scan(&root,-1,/))-1);'; put '%if %index(&root,/tests/) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/tests/)-1);'; put '%end;'; put '%else %if %index(&root,/services) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/services)-1);'; put '%end;'; put '%else %if %index(&root,/jobs) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/jobs)-1);'; put '%end;'; put '%else %put &sysmacroname: Could not find an app location from &pgm;'; put '&root'; put '%mend mf_getapploc ;'; put '%macro mf_getuniquefileref(prefix=_,maxtries=1000,lrecl=32767);'; put '%local rc fname;'; put '%if &prefix=0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%end;'; put '%else %do;'; put '%local x len;'; put '%let len=%eval(8-%length(&prefix));'; put '%let x=0;'; put '%do x=0 %to &maxtries;'; put '%let fname=&prefix%substr(%sysfunc(ranuni(0)),3,&len);'; put '%if %sysfunc(fileref(&fname)) > 0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%return;'; put '%end;'; put '%end;'; put '%put unable to find available fileref after &maxtries attempts;'; put '%end;'; put '%mend mf_getuniquefileref;'; put '%macro mp_abort(mac=mp_abort.sas, type=, msg=, iftrue=%str(1=1)'; put ', errds=work.mp_abort_errds'; put ', mode=REGULAR'; put ')/*/STORE SOURCE*/;'; put '%global sysprocessmode sysprocessname sasjs_stpsrv_header_loc sasjsprocessmode;'; put '%local fref fid i;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%put NOTE: /// mp_abort macro executing //;'; put '%if %length(&mac)>0 %then %put NOTE- called by &mac;'; put '%put NOTE - &msg;'; put '%if %symexist(_SYSINCLUDEFILEDEVICE)'; put '/* abort cancel FILE does not restart outside the INCLUDE on Viya 3.5 */'; put 'and %superq(SYSPROCESSNAME) ne %str(Compute Server)'; put '%then %do;'; put '%if "*&_SYSINCLUDEFILEDEVICE*" ne "**" %then %do;'; put 'data &errds;'; put 'iftrue=''1=1'';'; put 'length mac $100 msg $5000;'; put 'mac=symget(''mac'');'; put 'msg=symget(''msg'');'; put 'run;'; put 'data _null_;'; put 'abort cancel FILE;'; put 'run;'; put '%return;'; put '%end;'; put '%end;'; put '/* Web App Context */'; put '%if %symexist(_PROGRAM)'; put 'or %superq(SYSPROCESSNAME) = %str(Compute Server)'; put 'or &mode=INCLUDE'; put '%then %do;'; put 'options obs=max replace mprint;'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5"'; put '%then %do;'; put 'options nosyntaxcheck;'; put '%end;'; put '%if &mode=INCLUDE %then %do;'; put '%if %sysfunc(exist(&errds))=1 %then %do;'; put 'data _null_;'; put 'set &errds;'; put 'call symputx(''iftrue'',iftrue,''l'');'; put 'call symputx(''mac'',mac,''l'');'; put 'call symputx(''msg'',msg,''l'');'; put 'putlog (_all_)(=);'; put 'run;'; put '%if (&iftrue)=0 %then %return;'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: No include errors found;'; put '%return;'; put '%end;'; put '%end;'; put '/* extract log errs / warns, if exist */'; put '%local logloc logline;'; put '%global logmsg; /* capture global messages */'; put '%if %symexist(SYSPRINTTOLOG) %then %let logloc=&SYSPRINTTOLOG;'; put '%else %let logloc=%qsysfunc(getoption(LOG));'; put 'proc printto log=log;run;'; put '%let logline=0;'; put '%if %length(&logloc)>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input; putlog _infile_;'; put 'i=1;'; put 'retain logonce 0;'; put 'if ('; put '_infile_=:"%str(WARN)ING" or _infile_=:"%str(ERR)OR"'; put ') and logonce=0 then'; put 'do;'; put 'call symputx(''logline'',_n_);'; put 'logonce+1;'; put 'end;'; put 'run;'; put '/* capture log including lines BEFORE the err */'; put '%if &logline>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input;'; put 'i=1;'; put 'stoploop=0;'; put 'if _n_ ge &logline-15 and stoploop=0 then do until (i>22);'; put 'call symputx(''logmsg'',catx(''\n'',symget(''logmsg''),_infile_));'; put 'input;'; put 'i+1;'; put 'stoploop=1;'; put 'end;'; put 'if stoploop=1 then stop;'; put 'run;'; put '%end;'; put '%end;'; put '%if %symexist(SYS_JES_JOB_URI) %then %do;'; put '/* setup webout for Viya */'; put 'options nobomfile;'; put '%if "X&SYS_JES_JOB_URI.X"="XX" %then %do;'; put 'filename _webout temp lrecl=999999 mod;'; put '%end;'; put '%else %do;'; put 'filename _webout filesrvc parenturi="&SYS_JES_JOB_URI"'; put 'name="_webout.json" lrecl=999999 mod;'; put '%end;'; put '%end;'; put '%else %if %sysfunc(filename(fref,&sasjs_stpsrv_header_loc))=0 %then %do;'; put 'options nobomfile;'; put '/* set up http header for SASjs Server */'; put '%let fid=%sysfunc(fopen(&fref,A));'; put '%if &fid=0 %then %do;'; put '%put %str(ERR)OR: %sysfunc(sysmsg());'; put '%return;'; put '%end;'; put '%let rc=%sysfunc(fput(&fid,%str(Content-Type: application/json)));'; put '%let rc=%sysfunc(fwrite(&fid));'; put '%let rc=%sysfunc(fclose(&fid));'; put '%let rc=%sysfunc(filename(&fref));'; put '%end;'; put '/* send response in SASjs JSON format */'; put 'data _null_;'; put 'file _webout mod lrecl=32000 encoding=''utf-8'';'; put 'length msg syswarningtext syserrortext $32767 mode $10 ;'; put 'sasdatetime=datetime();'; put 'msg=symget(''msg'');'; put '%if &logline>0 %then %do;'; put 'msg=cats(msg,''\n\nLog Extract:\n'',symget(''logmsg''));'; put '%end;'; put '/* escape the escapes */'; put 'msg=tranwrd(msg,''\'',''\\'');'; put '/* escape the quotes */'; put 'msg=tranwrd(msg,''"'',''\"'');'; put '/* ditch the CRLFs as chrome complains */'; put 'msg=compress(msg,,''kw'');'; put '/* quote without quoting the quotes (which are escaped instead) */'; put 'msg=cats(''"'',msg,''"'');'; put 'if symexist(''_debug'') then debug=quote(trim(symget(''_debug'')));'; put 'else debug=''""'';'; put 'if symget(''sasjsprocessmode'')=''Stored Program'' then mode=''SASJS'';'; put 'if mode ne ''SASJS'' then put ''>>weboutBEGIN<<'';'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'put '',"sasjsAbort" : [{'';'; put 'put '' "MSG":'' msg ;'; put 'put '' ,"MAC": "'' "&mac" ''"}]'';'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'put '',"_DEBUG":'' debug ;'; put 'if symexist(''_metauser'') then do;'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put 'end;'; put 'if symexist(''SYS_JES_JOB_URI'') then do;'; put 'SYS_JES_JOB_URI=quote(trim(symget(''SYS_JES_JOB_URI'')));'; put 'put '',"SYS_JES_JOB_URI": '' SYS_JES_JOB_URI;'; put 'end;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put ",""SYSWARNINGTEXT"" : " syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'put "}" ;'; put 'if mode ne ''SASJS'' then put ''>>weboutEND<<'';'; put 'run;'; put '%put _all_;'; put '%if "&sysprocessmode " = "SAS Stored Process Server " %then %do;'; put 'data _null_;'; put 'putlog ''stpsrvset program err and syscc'';'; put 'rc=stpsrvset(''program error'', 0);'; put 'call symputx("syscc",0,"g");'; put 'run;'; put '%if &sysscp=WIN'; put 'and 1=0 /* deprecating this logic until we figure out a consistent abort */'; put 'and "%substr(%str(&sysvlong ),1,8)"="9.04.01M"'; put 'and "%substr(%str(&sysvlong ),9,1)">"5" %then %do;'; put '/* skip approach (below) does not work in windows m6+ envs */'; put 'endsas;'; put '%end;'; put '%else %do;'; put '/**'; put '* endsas kills 9.4m3 deployments by orphaning multibridges.'; put '* Abort variants are ungraceful (non zero return code)'; put '* This approach lets SAS run silently until the end :-)'; put '* Caution - fails when called within a %include within a macro'; put '* Use mp_include() to handle this.'; put '*/'; put 'filename skip temp;'; put 'data _null_;'; put 'file skip;'; put 'put ''%macro skip();'';'; put 'comment ''%mend skip; -> fix lint '';'; put 'put ''%macro skippy();'';'; put 'comment ''%mend skippy; -> fix lint '';'; put 'run;'; put '%inc skip;'; put '%end;'; put '%end;'; put '%else %if "&sysprocessmode " = "SAS Compute Server " %then %do;'; put '/* endsas kills the session making it harder to fetch results */'; put 'data _null_;'; put 'syswarningtext=symget(''syswarningtext'');'; put 'syserrortext=symget(''syserrortext'');'; put 'abort_msg=symget(''msg'');'; put 'syscc=symget(''syscc'');'; put 'sysuserid=symget(''sysuserid'');'; put 'iftrue=symget(''iftrue'');'; put 'put (_all_)(/=);'; put 'call symputx(''syscc'',0);'; put 'abort cancel nolist;'; put 'run;'; put '%end;'; put '%else %do;'; put '%abort cancel;'; put '%end;'; put '%end;'; put '%else %do;'; put '%put _all_;'; put '%abort cancel;'; put '%end;'; put '%mend mp_abort;'; put '/** @endcond */'; put '%macro mm_getstpcode('; put 'tree=/User Folders/sasdemo/somestp'; put ',name='; put ',outloc=0'; put ',outref=0'; put ',mDebug=1'; put ',showlog=NO'; put ');'; put '%local mD;'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing &sysmacroname..sas;'; put '%&mD.put _local_;'; put '%if %length(&name)>0 %then %let name=/&name;'; put '/* first, check if STP exists */'; put '%local tsuri;'; put '%let tsuri=stopifempty ;'; put 'data _null_;'; put 'format type uri tsuri value $200.;'; put 'call missing (of _all_);'; put 'path="&tree&name(StoredProcess)";'; put '/* first, find the STP ID */'; put 'if metadata_pathobj("",path,"StoredProcess",type,uri)>0 then do;'; put '/* get sourcecode */'; put 'cnt=1;'; put 'do while (metadata_getnasn(uri,"Notes",cnt,tsuri)>0);'; put 'rc=metadata_getattr(tsuri,"Name",value);'; put '&mD.put tsuri= value=;'; put 'if value="SourceCode" then do;'; put '/* found it! */'; put 'rc=metadata_getattr(tsuri,"Id",value);'; put 'call symputx(''tsuri'',value,''l'');'; put 'stop;'; put 'end;'; put 'cnt+1;'; put 'end;'; put 'end;'; put 'else put (_all_)(=);'; put 'run;'; put '%mp_abort(iftrue= (&tsuri=stopifempty)'; put ',mac=mm_getstpcode'; put ',msg=%str(&tree&name.(StoredProcess) not found!)'; put ')'; put '/**'; put '* Now we can extract the textstore'; put '*/'; put 'filename __getdoc temp lrecl=10000000;'; put 'proc metadata'; put 'in="$METAREPOSITORY'; put ''; put 'SAS1"'; put 'out=__getdoc ;'; put 'run;'; put '/* find the beginning of the text */'; put '%local start;'; put 'data _null_;'; put 'infile __getdoc lrecl=10000;'; put 'input;'; put 'start=index(_infile_,''StoredText="'');'; put 'if start then do;'; put 'call symputx("start",start+11);'; put '*putlog ''"'' _infile_ ''"'';'; put 'end;'; put 'stop;'; put '%local outeng;'; put '%if "&outloc"="0" %then %let outeng=TEMP;'; put '%else %let outeng="&outloc";'; put '%local fref;'; put '%if &outref=0 %then %let fref=%mf_getuniquefileref();'; put '%else %let fref=&outref;'; put '/* read the content, byte by byte, resolving escaped chars */'; put 'filename &fref &outeng lrecl=100000;'; put 'data _null_;'; put 'length filein 8 fileid 8;'; put 'filein = fopen("__getdoc","I",1,"B");'; put 'fileid = fopen("&fref","O",1,"B");'; put 'rec = "20"x;'; put 'length entity $6;'; put 'do while(fread(filein)=0);'; put 'x+1;'; put 'if x>&start then do;'; put 'rc = fget(filein,rec,1);'; put 'if rec=''"'' then leave;'; put 'else if rec="&" then do;'; put 'entity=rec;'; put 'do until (rec=";");'; put 'if fread(filein) ne 0 then goto getout;'; put 'rc = fget(filein,rec,1);'; put 'entity=cats(entity,rec);'; put 'end;'; put 'select (entity);'; put 'when (''&'' ) rec=''&'' ;'; put 'when (''<'' ) rec=''<'' ;'; put 'when (''>'' ) rec=''>'' ;'; put 'when (''''') rec="''" ;'; put 'when (''"'') rec=''"'' ;'; put 'when ('' '') rec=''0A''x;'; put 'when ('' '') rec=''0D''x;'; put 'when (''$'' ) rec=''$'' ;'; put 'when ('' '') rec=''09''x;'; put 'otherwise putlog "%str(WARN)ING: missing value for " entity=;'; put 'end;'; put 'rc =fput(fileid, substr(rec,1,1));'; put 'rc =fwrite(fileid);'; put 'end;'; put 'else do;'; put 'rc =fput(fileid,rec);'; put 'rc =fwrite(fileid);'; put 'end;'; put 'end;'; put 'end;'; put 'getout:'; put 'rc=fclose(filein);'; put 'rc=fclose(fileid);'; put 'run;'; put '%if &showlog=YES %then %do;'; put 'data _null_;'; put 'infile &fref lrecl=32767 end=last;'; put 'input;'; put 'if _n_=1 then putlog ''>>stpcodeBEGIN<<'';'; put 'putlog _infile_;'; put 'if last then putlog ''>>stpcodeEND<<'';'; put 'run;'; put '%end;'; put 'filename __getdoc clear;'; put '%if &outref=0 %then %do;'; put 'filename &fref clear;'; put '%end;'; put '%mend mm_getstpcode;'; put '%macro dc_getsettings();'; put '%global _program;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(syscc=&syscc on entry (&syswarningtext &syserrortext))'; put ')'; put '%if %length(&_PROGRAM)>1 %then %let root=&_program;'; put '%else %do;'; put '%global _metauser;'; put '%let _metauser=&sysuserid;'; put '/* to mimic a "real" _program we need to give a dummy role and stp name */'; put '%let root=/dummyRole/dummyName;'; put '%end;'; put '/* the DC precode is stored in the Admin folder in the root of'; put 'the project. Lets find that root. */'; put '%put &=root;'; put '%let root=%mf_getapploc();'; put '%put &=root;'; put '/* Now we know the root location we can retrieve the params */'; put '%let temploc=%sysfunc(pathname(work))/settings.txt;'; put '%mm_getstpcode(tree=&root/services/public'; put ',name=Data_Controller_Settings'; put ',outloc=&temploc'; put ')'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Unable to run getstpcode)'; put ')'; put 'filename _getsets "&temploc" lrecl=2000;'; put '/*'; put 'Do not use mp_include here - this puts a copy in every service, which creates'; put 'compilation problems when calling services from mp_include'; put '*/'; put '%inc _getsets/source2;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Problem running &sysmacroname (&syswarningtext &syserrortext))'; put ')'; put '%mend dc_getsettings;'; put '%macro mf_fmtdttm('; put ')/*/STORE SOURCE*/;'; put '%if "&sysver"="9.2" or "&sysver"="9.3"'; put 'or ("&sysver"="9.4" and "%substr(&SYSVLONG,9,1)" le "3")'; put 'or "%substr(&sysver,1,1)"="4"'; put 'or "%substr(&sysver,1,1)"="5"'; put '%then %do;DATETIME19.3%end;'; put '%else %do;E8601DT26.6%end;'; put '%mend mf_fmtdttm;'; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '%macro mp_init(prefix=SASJS'; put ')/*/STORE SOURCE*/;'; put '%if %symexist(SASJS_PREFIX) %then %return; /* only run once */'; put '%global'; put 'SASJS_PREFIX /* the ONLY hard-coded global macro variable in SASjs */'; put '&prefix._FUNCTIONS /* used in mcf_init() to track core function compilation */'; put '&prefix._INIT_NUM /* initialisation time as numeric */'; put '&prefix._INIT_DTTM /* initialisation time in E8601DT26.6 format */'; put '&prefix.WORK /* avoid typing %sysfunc(pathname(work)) every time */'; put ';'; put '%let sasjs_prefix=&prefix;'; put 'data _null_;'; put 'dttm=datetime();'; put 'call symputx("&sasjs_prefix._init_num",dttm,''g'');'; put 'call symputx("&sasjs_prefix._init_dttm",put(dttm,E8601DT26.6),''g'');'; put 'call symputx("&sasjs_prefix.work",pathname(''WORK''),''g'');'; put 'run;'; put 'options'; put 'compress=CHAR /* default is none so ensure we have something! */'; put 'datastmtchk=ALLKEYWORDS /* protection from overwriting input datasets */'; put 'errorcheck=STRICT /* catch errs in libname/filename statements */'; put 'fmterr /* ensure err when a format cannot be found */'; put 'mergenoby=%str(ERR)OR /* throw err when a merge has no BY variables */'; put 'missing=. /* changing this can cause hard to detect errs */'; put 'noquotelenmax /* avoid warnings for long strings */'; put 'noreplace /* avoid overwriting permanent datasets */'; put 'ps=max /* reduce log size slightly */'; put 'ls=max /* reduce log even more and avoid word truncation */'; put 'validmemname=COMPATIBLE /* avoid special characters etc in table names */'; put 'validvarname=V7 /* avoid special characters etc in variable names */'; put 'varinitchk=%str(ERR)OR /* avoid data mistakes from variable name typos */'; put 'varlenchk=%str(ERR)OR /* fail hard if truncation (data loss) can result */'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5" %then %do;'; put 'noautocorrect /* disallow misspelled procedure names */'; put 'dsoptions=note2err /* undocumented - convert bad NOTEs to ERRs */'; put '%end;'; put ';'; put '%mend mp_init;'; put '%macro mpeinit(fetch=YES);'; put '%global mpeinit'; put 'mpeadmins /* group with unrestricted Meditor access */'; put 'mpelocapprovals /* location for landing and staging files */'; put 'mpelib /* location of configuration tables for DC */'; put 'dc_repo_users /* location of user / group metadata */'; put 'dc_licence_key /* extracted in dc_getsettings */'; put 'dc_activation_key /* extracted in dc_getsettings */'; put 'dc_locale /* extracted in dc_getsettings */'; put 'dc_dttmtfmt /* can be overridden in dc_getsettings */'; put '_debug'; put ';'; put '%if &mpeinit=1 %then %return;'; put '%else %let mpeinit=1;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(Problem on service startup (&syswarningtext &syserrortext))'; put ')'; put '%mp_init()'; put '%if &fetch=YES %then %do;'; put '%webout(FETCH)'; put '%end;'; put '%global _CLIENTNAME;'; put '%mp_abort(iftrue= (&_CLIENTNAME=SAS Enterprise Guide)'; put ',mac=&_program..sas'; put ',msg=%str(Data Controller is a web app and should not be executed from EG)'; put ')'; put 'options urlencoding=utf8 nobomfile lrecl=32767;'; put '%let perf=%sysfunc(datetime());'; put '%put perfdiff: 0;'; put '%let dc_locale=SYSTEM; /* default if not set */'; put '/**'; put '* E8601DT26.6 has widest database support - but not all SAS flavours can'; put '* handle it. Override in the settings STP if needed.'; put '*/'; put 'data _null_;'; put 'dc_dttmtfmt=''"%sysfunc(datetime(),''!!"%mf_fmtdttm()"!!'')"dt'';'; put 'call symputx(''dc_dttmtfmt'',dc_dttmtfmt);'; put 'put dc_dttmtfmt=;'; put 'run;'; put '%put &=dc_dttmtfmt;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc prior to dc_getsettings)'; put ')'; put '%dc_getsettings()'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc after dc_getsettings)'; put ')'; put 'data _null_;'; put 'set &DC_LIBREF..mpe_config(where=('; put 'var_scope="DC"'; put 'and &dc_dttmtfmt lt tx_to'; put 'and var_active=1'; put '));'; put 'call symputx(var_name,var_value,''G'');'; put 'putlog var_name "=" var_value;'; put 'run;'; put '%let mpelib=&dc_libref;'; put '%let mpeadmins=&dc_admin_group;'; put '%let mpelocapprovals=&dc_staging_area;'; put '%let dc_repo_users=&dc_repo_users;'; put '%if &dc_locale ne SYSTEM %then %do;'; put 'options locale=&dc_locale;'; put '%end;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(Problem during compilation or with STP precode (&syswarningtext))'; put ')'; put '%mend mpeinit;'; put '%macro mf_mval(var);'; put '%if %symexist(&var) %then %do;'; put '%superq(&var)'; put '%end;'; put '%mend mf_mval;'; put '%macro mf_trimstr(basestr,trimstr);'; put '%local baselen trimlen trimval;'; put '/* return if basestr is shorter than trimstr (or 0) */'; put '%let baselen=%length(%superq(basestr));'; put '%let trimlen=%length(%superq(trimstr));'; put '%if &baselen < &trimlen or &baselen=0 %then %return;'; put '/* obtain the characters from the end of basestr */'; put '%let trimval=%qsubstr(%superq(basestr)'; put ',%length(%superq(basestr))-&trimlen+1'; put ',&trimlen);'; put '/* compare and if matching, chop it off! */'; put '%if %superq(basestr)=%superq(trimstr) %then %do;'; put '%return;'; put '%end;'; put '%else %if %superq(trimval)=%superq(trimstr) %then %do;'; put '%qsubstr(%superq(basestr),1,%length(%superq(basestr))-&trimlen)'; put '%end;'; put '%else %do;'; put '&basestr'; put '%end;'; put '%mend mf_trimstr;'; put '%macro mf_getplatform(switch'; put ')/*/STORE SOURCE*/;'; put '%local a b c;'; put '%if &switch.NONE=NONE %then %do;'; put '%if %symexist(sasjsprocessmode) %then %do;'; put '%if &sasjsprocessmode=Stored Program %then %do;'; put 'SASJS'; put '%return;'; put '%end;'; put '%end;'; put '%if %symexist(sysprocessmode) %then %do;'; put '%if "&sysprocessmode"="SAS Object Server"'; put 'or "&sysprocessmode"= "SAS Compute Server" %then %do;'; put 'SASVIYA'; put '%end;'; put '%else %if "&sysprocessmode"="SAS Stored Process Server"'; put 'or "&sysprocessmode"="SAS Workspace Server"'; put '%then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if %symexist(_metaport) or %symexist(_metauser) %then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if &switch=SASSTUDIO %then %do;'; put '/* return the version of SAS Studio else 0 */'; put '%if %mf_mval(_CLIENTAPP)=%str(SAS Studio) %then %do;'; put '%let a=%mf_mval(_CLIENTVERSION);'; put '%let b=%scan(&a,1,.);'; put '%if %eval(&b >2) %then %do;'; put '&b'; put '%end;'; put '%else 0;'; put '%end;'; put '%else 0;'; put '%end;'; put '%else %if &switch=VIYARESTAPI %then %do;'; put '%mf_trimstr(%sysfunc(getoption(servicesbaseurl)),/)'; put '%end;'; put '%mend mf_getplatform;'; put '%macro mpeterm();'; put '%local oldloc;'; put 'data _null_;'; put 'if symexist(''SYSPRINTTOLOG'') then oldloc=symget(''SYSPRINTTOLOG'');'; put 'else oldloc=getoption(''LOG'');'; put 'if subpad(oldloc,1,1) not in (''"'',"''",'' '') then oldloc=quote(cats(oldloc));'; put 'call symputx(''oldloc'',oldloc,''l'');'; put 'run;'; put '%if %length(&oldloc)>0 %then %do;'; put 'proc printto log=log;'; put 'run;'; put 'data _null_;'; put 'infile &oldloc;'; put 'input; putlog _infile_;'; put 'run;'; put '%end;'; put '%if %sysfunc(exist(&dc_libref..mpe_requests)) and %mf_getplatform() ne SASVIYA'; put '%then %do;'; put 'data ;'; put 'if 0 then set &dc_libref..mpe_requests;'; put 'request_dttm=%sysfunc(datetime());'; put 'request_user="%mf_getuser()";'; put 'request_service="%scan(&_program,-2,/)/%scan(&_program,-1,/)";'; put 'request_params='''';'; put 'output;stop;'; put 'proc append base=&dc_libref..mpe_requests data=&syslast force nowarn;'; put 'run;'; put '%end;'; put '%mend mpeterm;'; put '* SAS Macros end;'; put '* SAS Includes start;'; put '* SAS Includes end;'; put '* Binary Files start;'; put '* Binary Files end;'; put '* ServiceInit start;'; put 'options noquotelenmax ps=max;'; put '/* create dummy macros to prevent stpbegin / stpend from corrupting sessions */'; put '%macro stpbegin();'; put '%put NOTE: the STPBEGIN macro should not be used for web apps!;'; put '%mend stpbegin;'; put '%macro stpend();'; put '%put NOTE: the STPEND macro should not be used for web apps!;'; put '%mend stpend;'; put '* ServiceInit end;'; put '* Service start;'; put '/**'; put '@file'; put '@brief Sample XLMAP Data hook program'; put '@details This hook script should NOT be modified in place, as the changes'; put 'would be lost in your next Data Controller deployment.'; put 'Instead, create a copy of this hook script and place it OUTSIDE the'; put 'Data Controller metadata folder.'; put 'Available macro variables:'; put '@li DC_LIBREF - The DC control library'; put '@li LIBREF - The library of the dataset being edited (is assigned)'; put '@li DS - The target dataset being loaded'; put '**/'; put '%let abort=0;'; put '%let errmsg=;'; put 'data work.staging_ds;'; put 'set work.staging_ds;'; put 'length errmsg $1000;'; put 'drop err:;'; put '/* KM1 validations */'; put 'if XLMAP_ID=''BASEL-KM1'' then do;'; put 'if XLMAP_RANGE_ID=''KM1:a'' & input(value_txt,8.)<100 then do;'; put 'errmsg=''Should be greater than 100'';'; put 'err=1;'; put 'end;'; put 'end;'; put '/* CR2 Validations */'; put 'if XLMAP_ID=''BASEL-CR2'' then do;'; put 'if XLMAP_RANGE_ID=''CR2-sec1'' & row_no=3 & input(value_txt,8.)>0 then do;'; put 'errmsg=''Should be negative'';'; put 'err=1;'; put 'end;'; put 'end;'; put '/* publish error message */'; put 'if err=1 then do;'; put 'errmsg=catx('' '',xlmap_range_id,'':'',value_txt,''->'',errmsg);'; put 'call symputx(''errmsg'',errmsg);'; put 'call symputx(''abort'',1);'; put 'end;'; put 'run;'; put '%mp_abort(iftrue=(&abort ne 0)'; put ',mac=xlmap_data_postedit'; put ',msg=%superq(errmsg)'; put ')'; put '* Service end;'; run; %mm_createwebservice(path=&appLoc/&path, name=&service, code=sascode, server=&serverName, replace=yes) filename sascode clear; %let path=services/lineage; %let service=fetchcollineage; filename sascode temp lrecl=32767; data _null_; file sascode; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '/**'; put '@file mp_jsonout.sas'; put '@brief Writes JSON in SASjs format to a fileref'; put '@details This macro can be used to OPEN a JSON stream and send one or more'; put 'tables as arrays of rows, where each row can be an object or a nested array.'; put 'There are two engines available - DATASTEP or PROCJSON.'; put 'PROC JSON is fast but will produce errs like the ones below if'; put 'special chars are encountered.'; put '> (ERR)OR: Some code points did not transcode.'; put '> An object or array close is not valid at this point in the JSON text.'; put '> Date value out of range'; put 'If this happens, try running with ENGINE=DATASTEP.'; put 'The DATASTEP engine is used to handle special SAS missing numerics, and'; put 'can also convert entire datasets to formatted values. Output JSON is always'; put 'in UTF-8.'; put 'Usage:'; put 'filename tmp temp;'; put 'data class; set sashelp.class;run;'; put '%mp_jsonout(OPEN,jref=tmp)'; put '%mp_jsonout(OBJ,class,jref=tmp)'; put '%mp_jsonout(OBJ,class,dslabel=class2,jref=tmp,showmeta=Y)'; put '%mp_jsonout(CLOSE,jref=tmp)'; put 'data _null_;'; put 'infile tmp;'; put 'input;putlog _infile_;'; put 'run;'; put 'If you are building web apps with SAS then you are strongly encouraged to use'; put 'the mX_createwebservice macros in combination with the'; put '[sasjs adapter](https://github.com/sasjs/adapter).'; put 'For more information see https://sasjs.io'; put '@param [in] action Valid values:'; put '@li OPEN - opens the JSON'; put '@li OBJ - sends a table with each row as an object'; put '@li ARR - sends a table with each row in an array'; put '@li CLOSE - closes the JSON'; put '@param [in] ds The dataset to send. Must be a work table.'; put '@param [out] jref= (_webout) The fileref to which to send the JSON'; put '@param [out] dslabel= The name to give the table in the exported JSON'; put '@param [in] fmt= (Y) Whether to keep (Y) or strip (N) formats from the table'; put '@param [in] engine= (DATASTEP) Which engine to use to send the JSON. Options:'; put '@li PROCJSON (default)'; put '@li DATASTEP (more reliable when data has non standard characters)'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to JSON'; put '

Related Files

'; put '@li mp_ds2fmtds.sas'; put '@version 9.2'; put '@author Allan Bowe'; put '@source https://github.com/sasjs/core'; put '**/'; put '%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y'; put ',engine=DATASTEP'; put ',missing=NULL'; put ',showmeta=N'; put ',maxobs=MAX'; put ')/*/STORE SOURCE*/;'; put '%local tempds colinfo fmtds i numcols numobs stmt_obs lastobs optval'; put 'tmpds1 tmpds2 tmpds3 tmpds4;'; put '%let numcols=0;'; put '%if &maxobs ne MAX %then %let stmt_obs=%str(if _n_>&maxobs then stop;);'; put '%if &action=OPEN %then %do;'; put 'options nobomfile;'; put 'data _null_;file &jref encoding=''utf-8'' lrecl=200;'; put 'put ''{"PROCESSED_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''"'';'; put 'run;'; put '%end;'; put '%else %if (&action=ARR or &action=OBJ) %then %do;'; put '/* force variable names to always be uppercase in the JSON */'; put 'options validvarname=upcase;'; put '/* To avoid issues with _webout on EBI - such as encoding diffs and truncation'; put '(https://support.sas.com/kb/49/325.html) we use temporary files */'; put 'filename _sjs1 temp lrecl=200 ;'; put 'data _null_; file _sjs1 encoding=''utf-8'';'; put 'put ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":";'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjs1 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs1 clear;'; put '/* grab col defs */'; put 'proc contents noprint data=&ds'; put 'out=_data_(keep=name type length format formatl formatd varnum label);'; put 'run;'; put '%let colinfo=%scan(&syslast,2,.);'; put 'proc sort data=&colinfo;'; put 'by varnum;'; put 'run;'; put '/* move meta to mac vars */'; put 'data &colinfo;'; put 'if _n_=1 then call symputx(''numcols'',nobs,''l'');'; put 'set &colinfo end=last nobs=nobs;'; put 'name=upcase(name);'; put '/* fix formats */'; put 'if type=2 or type=6 then do;'; put 'typelong=''char'';'; put 'length fmt $49.;'; put 'if format='''' then fmt=cats(''$'',length,''.'');'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else fmt=cats(format,formatl,''.'');'; put 'end;'; put 'else do;'; put 'typelong=''num'';'; put 'if format='''' then fmt=''best.'';'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else if formatd=0 then fmt=cats(format,formatl,''.'');'; put 'else fmt=cats(format,formatl,''.'',formatd);'; put 'end;'; put '/* 32 char unique name */'; put 'newname=''sasjs''!!substr(cats(put(md5(name),$hex32.)),1,27);'; put 'call symputx(cats(''name'',_n_),name,''l'');'; put 'call symputx(cats(''newname'',_n_),newname,''l'');'; put 'call symputx(cats(''length'',_n_),length,''l'');'; put 'call symputx(cats(''fmt'',_n_),fmt,''l'');'; put 'call symputx(cats(''type'',_n_),type,''l'');'; put 'call symputx(cats(''typelong'',_n_),typelong,''l'');'; put 'call symputx(cats(''label'',_n_),coalescec(label,name),''l'');'; put '/* overwritten when fmt=Y and a custom format exists in catalog */'; put 'if typelong=''num'' then call symputx(cats(''fmtlen'',_n_),200,''l'');'; put 'else call symputx(cats(''fmtlen'',_n_),min(32767,ceil((length+10)*1.5)),''l'');'; put 'run;'; put '%let tempds=%substr(_%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql;'; put 'select count(*) into: lastobs from &ds;'; put '%if &maxobs ne MAX %then %let lastobs=%sysfunc(min(&lastobs,&maxobs));'; put '%if &engine=PROCJSON %then %do;'; put '%if &missing=STRING %then %do;'; put '%put &sysmacroname: Special Missings not supported in proc json.;'; put '%put &sysmacroname: Switching to DATASTEP engine;'; put '%goto datastep;'; put '%end;'; put 'data &tempds;'; put 'set &ds;'; put '&stmt_obs;'; put '%if &fmt=N %then format _numeric_ best32.;;'; put '/* PRETTY is necessary to avoid line truncation in large files */'; put 'filename _sjs2 temp lrecl=131068 encoding=''utf-8'';'; put 'proc json out=_sjs2 pretty'; put '%if &action=ARR %then nokeys ;'; put ';export &tempds / nosastags fmtnumeric;'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs2 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs2 clear;'; put '%end;'; put '%else %if &engine=DATASTEP %then %do;'; put '%datastep:'; put '%if %sysfunc(exist(&ds)) ne 1 & %sysfunc(exist(&ds,VIEW)) ne 1'; put '%then %do;'; put '%put &sysmacroname: &ds NOT FOUND!!!;'; put '%return;'; put '%end;'; put '%if &fmt=Y %then %do;'; put '/**'; put '* Extract format definitions'; put '* First, by getting library locations from dictionary.formats'; put '* Then, by exporting the width using proc format'; put '* Cannot use maxw from sashelp.vformat as not always populated'; put '* Cannot use fmtinfo() as not supported in all flavours'; put '*/'; put '%let tmpds1=%substr(fmtsum%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds2=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds3=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds4=%substr(col%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql noprint;'; put 'create table &tmpds1 as'; put 'select cats(libname,''.'',memname) as FMTCAT,'; put 'FMTNAME'; put 'from dictionary.formats'; put 'where fmttype=''F'' and libname is not null'; put 'and fmtname in (select format from &colinfo where format is not null)'; put 'order by 1;'; put 'create table &tmpds2('; put 'FMTNAME char(32),'; put 'LENGTH num'; put ');'; put '%local catlist cat fmtlist i;'; put 'select distinct fmtcat into: catlist separated by '' '' from &tmpds1;'; put '%do i=1 %to %sysfunc(countw(&catlist,%str( )));'; put '%let cat=%scan(&catlist,&i,%str( ));'; put 'proc sql;'; put 'select distinct fmtname into: fmtlist separated by '' '''; put 'from &tmpds1 where fmtcat="&cat";'; put 'proc format lib=&cat cntlout=&tmpds3(keep=fmtname length);'; put 'select &fmtlist;'; put 'run;'; put 'proc sql;'; put 'insert into &tmpds2 select distinct fmtname,length from &tmpds3;'; put '%end;'; put 'proc sql;'; put 'create table &tmpds4 as'; put 'select a.*, b.length as MAXW'; put 'from &colinfo a'; put 'left join &tmpds2 b'; put 'on cats(a.format)=cats(upcase(b.fmtname))'; put 'order by a.varnum;'; put 'data _null_;'; put 'set &tmpds4;'; put 'if not missing(maxw);'; put 'call symputx('; put 'cats(''fmtlen'',_n_),'; put '/* vars need extra padding due to JSON escaping of special chars */'; put 'min(32767,ceil((max(length,maxw)+10)*1.5))'; put ',''l'''; put ');'; put 'run;'; put '/* configure varlenchk - as we are explicitly shortening the variables */'; put '%let optval=%sysfunc(getoption(varlenchk));'; put 'options varlenchk=NOWARN;'; put 'data _data_(compress=char);'; put '/* shorten the new vars */'; put 'length'; put '%do i=1 %to &numcols;'; put '&&name&i $&&fmtlen&i'; put '%end;'; put ';'; put '/* rename on entry */'; put 'set &ds(rename=('; put '%do i=1 %to &numcols;'; put '&&name&i=&&newname&i'; put '%end;'; put '));'; put '&stmt_obs;'; put 'drop'; put '%do i=1 %to &numcols;'; put '&&newname&i'; put '%end;'; put ';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=num %then %do;'; put '&&name&i=cats(put(&&newname&i,&&fmt&i));'; put '%end;'; put '%else %do;'; put '&&name&i=put(&&newname&i,&&fmt&i);'; put '%end;'; put '%end;'; put 'if _error_ then do;'; put 'call symputx(''syscc'',1012);'; put 'stop;'; put 'end;'; put 'run;'; put '%let fmtds=&syslast;'; put 'options varlenchk=&optval;'; put '%end;'; put 'proc format; /* credit yabwon for special null removal */'; put 'value bart (default=40)'; put '%if &missing=NULL %then %do;'; put '._ - .z = null'; put '%end;'; put '%else %do;'; put '._ = [quote()]'; put '. = null'; put '.a - .z = [quote()]'; put '%end;'; put 'other = [best.];'; put 'data &tempds;'; put 'attrib _all_ label='''';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'length &&name&i $&&fmtlen&i...;'; put 'format &&name&i $&&fmtlen&i...;'; put '%end;'; put '%end;'; put '%if &fmt=Y %then %do;'; put 'set &fmtds;'; put '%end;'; put '%else %do;'; put 'set &ds;'; put '%end;'; put '&stmt_obs;'; put 'format _numeric_ bart.;'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'if findc(&&name&i,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put '&&name&i=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,&&name&i)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else &&name&i=quote(cats(&&name&i));'; put '%end;'; put '%end;'; put 'run;'; put 'filename _sjs3 temp lrecl=131068 ;'; put 'data _null_;'; put 'file _sjs3 encoding=''utf-8'';'; put 'if _n_=1 then put "[";'; put 'set &tempds;'; put 'if _n_>1 then put "," @; put'; put '%if &action=ARR %then "[" ; %else "{" ;'; put '%do i=1 %to &numcols;'; put '%if &i>1 %then "," ;'; put '%if &action=OBJ %then """&&name&i"":" ;'; put '"&&name&i"n /* name literal for reserved variable names */'; put '%end;'; put '%if &action=ARR %then "]" ; %else "}" ; ;'; put '/* close out the table */'; put 'data _null_;'; put 'file _sjs3 mod encoding=''utf-8'';'; put 'put '']'';'; put 'run;'; put 'data _null_;'; put 'infile _sjs3 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs3 clear;'; put '%end;'; put 'proc sql;'; put 'drop table &colinfo, &tempds;'; put '%if %substr(&showmeta,1,1)=Y %then %do;'; put 'filename _sjs4 temp lrecl=131068 encoding=''utf-8'';'; put 'data _null_;'; put 'file _sjs4;'; put 'length label $350;'; put 'put ", ""$%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":{""vars"":{";'; put 'do i=1 to &numcols;'; put 'name=quote(trim(symget(cats(''name'',i))));'; put 'format=quote(trim(symget(cats(''fmt'',i))));'; put 'label=quote(prxchange(''s/\\/\\\\/'',-1,trim(symget(cats(''label'',i)))));'; put 'length=quote(trim(symget(cats(''length'',i))));'; put 'type=quote(trim(symget(cats(''typelong'',i))));'; put 'if i>1 then put "," @@;'; put 'put name '':{"format":'' format '',"label":'' label'; put ''',"length":'' length '',"type":'' type ''}'';'; put 'end;'; put 'put ''}}'';'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs4 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs4 clear;'; put '%end;'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put 'data _null_; file &jref encoding=''utf-8'' mod ;'; put 'put "}";'; put 'run;'; put '%end;'; put '%mend mp_jsonout;'; put '/**'; put '@file mm_webout.sas'; put '@brief Send data to/from SAS Stored Processes'; put '@details This macro should be added to the start of each Stored Process,'; put '**immediately** followed by a call to:'; put '%mm_webout(FETCH)'; put 'This will read all the input data and create same-named SAS datasets in the'; put 'WORK library. You can then insert your code, and send data back using the'; put 'following syntax:'; put 'data some datasets; * make some data ;'; put 'retain some columns;'; put 'run;'; put '%mm_webout(OPEN)'; put '%mm_webout(ARR,some) * Array format, fast, suitable for large tables ;'; put '%mm_webout(OBJ,datasets) * Object format, easier to work with ;'; put 'Finally, wrap everything up send some helpful system variables too'; put '%mm_webout(CLOSE)'; put '@param [in] action Either FETCH, OPEN, ARR, OBJ or CLOSE'; put '@param [in] ds The dataset to send back to the frontend'; put '@param [out] dslabel= Value to use instead of table name for sending to JSON'; put '@param [in] fmt= (N) Setting Y converts all vars to their formatted values'; put '@param [out] fref= (_webout) The fileref to which to write the JSON'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] workobs= (0) When set to a positive integer, will create a new'; put 'output object (WORK) which contains this number of observations from all'; put 'tables in the WORK library.'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to output JSON'; put '

SAS Macros

'; put '@li mp_jsonout.sas'; put '

Related Macros

'; put '@li ms_webout.sas'; put '@li mv_webout.sas'; put '@version 9.3'; put '@author Allan Bowe'; put '**/'; put '%macro mm_webout(action,ds,dslabel=,fref=_webout,fmt=N,missing=NULL'; put ',showmeta=N,maxobs=MAX,workobs=0'; put ');'; put '%global _webin_file_count _webin_fileref1 _webin_name1 _program _debug'; put 'sasjs_tables;'; put '%local i tempds jsonengine;'; put '/* see https://github.com/sasjs/core/issues/41 */'; put '%if "%upcase(&SYSENCODING)" ne "UTF-8" %then %let jsonengine=PROCJSON;'; put '%else %let jsonengine=DATASTEP;'; put '%if &action=FETCH %then %do;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'options mprint notes mprintnest;'; put '%end;'; put '%let _webin_file_count=%eval(&_webin_file_count+0);'; put '/* now read in the data */'; put '%do i=1 %to &_webin_file_count;'; put '%if &_webin_file_count=1 %then %do;'; put '%let _webin_fileref1=&_webin_fileref;'; put '%let _webin_name1=&_webin_name;'; put '%end;'; put 'data _null_;'; put 'infile &&_webin_fileref&i termstr=crlf;'; put 'input;'; put 'call symputx(''input_statement'',_infile_);'; put 'putlog "&&_webin_name&i input statement: " _infile_;'; put 'stop;'; put 'data &&_webin_name&i;'; put 'infile &&_webin_fileref&i firstobs=2 dsd termstr=crlf encoding=''utf-8'';'; put 'input &input_statement;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'if _n_<20 then putlog _infile_;'; put '%end;'; put 'run;'; put '%let sasjs_tables=&sasjs_tables &&_webin_name&i;'; put '%end;'; put '%end;'; put '%else %if &action=OPEN %then %do;'; put '/* fix encoding */'; put 'OPTIONS NOBOMFILE;'; put '/**'; put '* check xengine type to avoid the below err message:'; put '* > Function is only valid for filerefs using the CACHE access method.'; put '*/'; put 'data _null_;'; put 'set sashelp.vextfl(where=(fileref="_WEBOUT"));'; put 'if xengine=''STREAM'' then do;'; put 'rc=stpsrv_header(''Content-type'',"text/html; encoding=utf-8");'; put 'end;'; put 'run;'; put '/* setup json */'; put 'data _null_;file &fref encoding=''utf-8'';'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutBEGIN<<'';'; put '%end;'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'run;'; put '%end;'; put '%else %if &action=ARR or &action=OBJ %then %do;'; put '%mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt,jref=&fref'; put ',engine=&jsonengine,missing=&missing,showmeta=&showmeta,maxobs=&maxobs'; put ')'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put '/* To avoid issues with _webout on EBI we use a temporary file */'; put 'filename _sjsref temp lrecl=131068;'; put '%if %str(&workobs) > 0 %then %do;'; put '/* if debug mode, send back first XX records of each work table also */'; put 'data;run;%let tempds=%scan(&syslast,2,.);'; put 'ods output Members=&tempds;'; put 'proc datasets library=WORK memtype=data;'; put '%local wtcnt;%let wtcnt=0;'; put 'data _null_;'; put 'set &tempds;'; put 'if not (upcase(name) =:"DATA"); /* ignore temp datasets */'; put 'i+1;'; put 'call symputx(cats(''wt'',i),name,''l'');'; put 'call symputx(''wtcnt'',i,''l'');'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put ",""WORK"":{";'; put '%do i=1 %to &wtcnt;'; put '%let wt=&&wt&i;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'dsid=open("WORK.&wt",''is'');'; put 'nlobs=attrn(dsid,''NLOBS'');'; put 'nvars=attrn(dsid,''NVARS'');'; put 'rc=close(dsid);'; put 'if &i>1 then put '',''@;'; put 'put " ""&wt"" : {";'; put 'put ''"nlobs":'' nlobs;'; put 'put '',"nvars":'' nvars;'; put '%mp_jsonout(OBJ,&wt,jref=_sjsref,dslabel=first10rows,showmeta=Y'; put ',maxobs=&workobs'; put ')'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put '%end;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put 'run;'; put '%end;'; put '/* close off json */'; put 'data _null_;file _sjsref mod encoding=''utf-8'';'; put 'length SYSPROCESSNAME syserrortext syswarningtext autoexec $512;'; put 'put ",""_DEBUG"" : ""&_debug"" ";'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'autoexec=quote(urlencode(trim(getoption(''autoexec''))));'; put 'put '',"AUTOEXEC" : '' autoexec;'; put 'put ",""MF_GETUSER"" : ""%mf_getuser()"" ";'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'put ",""SYSENCODING"" : ""&sysencoding"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSPROCESSID"" : ""&SYSPROCESSID"" ";'; put 'put ",""SYSPROCESSMODE"" : ""&SYSPROCESSMODE"" ";'; put 'SYSPROCESSNAME=quote(urlencode(cats(SYSPROCESSNAME)));'; put 'put ",""SYSPROCESSNAME"" : " SYSPROCESSNAME;'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put '',"SYSWARNINGTEXT" : '' syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'length memsize $32;'; put 'memsize="%sysfunc(INPUTN(%sysfunc(getoption(memsize)), best.),sizekmg.)";'; put 'memsize=quote(cats(memsize));'; put 'put '',"MEMSIZE" : '' memsize;'; put 'put "}" @;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutEND<<'';'; put '%end;'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjsref lrecl=1 recfm=n;'; put 'file &fref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjsref clear;'; put '%end;'; put '%mend mm_webout;'; put '%macro webout(action,ds,dslabel=,fmt=,missing=NULL,showmeta=NO,maxobs=MAX);'; put '%mm_webout(&action,ds=&ds,dslabel=&dslabel,fmt=&fmt'; put ',missing=&missing'; put ',showmeta=&showmeta'; put ',maxobs=&maxobs'; put ') %mend;'; put '/* provide additional debug info */'; put '%global _program;'; put '%put &=syscc;'; put '%put user=%mf_getuser();'; put '%put pgm=&_program;'; put '%put timestamp=%sysfunc(datetime(),datetime19.);'; put '* Service Variables start;'; put '* Service Variables end;'; put '* SAS Macros start;'; put '%macro mf_getapploc(pgm);'; put '%if "&pgm"="" %then %do;'; put '%if %symexist(_program) %then %let pgm=&_program;'; put '%else %do;'; put '%put &sysmacroname: No value provided and no _program variable available;'; put '%return;'; put '%end;'; put '%end;'; put '%local root;'; put '/**'; put '* First check we are not in the tests/macros folder (which has no subfolders)'; put '* or specifically in the testsetup or testteardown services'; put '*/'; put '%if %index(&pgm,/tests/macros/)'; put 'or %index(&pgm,/tests/testsetup)'; put 'or %index(&pgm,/tests/testteardown)'; put '%then %do;'; put '%let root=%substr(&pgm,1,%index(&pgm,/tests)-1);'; put '&root'; put '%return;'; put '%end;'; put '/**'; put '* Next, move up two levels to avoid matches on subfolder or service name'; put '*/'; put '%let root=%substr(&pgm,1,%length(&pgm)-%length(%scan(&pgm,-1,/))-1);'; put '%let root=%substr(&root,1,%length(&root)-%length(%scan(&root,-1,/))-1);'; put '%if %index(&root,/tests/) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/tests/)-1);'; put '%end;'; put '%else %if %index(&root,/services) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/services)-1);'; put '%end;'; put '%else %if %index(&root,/jobs) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/jobs)-1);'; put '%end;'; put '%else %put &sysmacroname: Could not find an app location from &pgm;'; put '&root'; put '%mend mf_getapploc ;'; put '%macro mf_getuniquefileref(prefix=_,maxtries=1000,lrecl=32767);'; put '%local rc fname;'; put '%if &prefix=0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%end;'; put '%else %do;'; put '%local x len;'; put '%let len=%eval(8-%length(&prefix));'; put '%let x=0;'; put '%do x=0 %to &maxtries;'; put '%let fname=&prefix%substr(%sysfunc(ranuni(0)),3,&len);'; put '%if %sysfunc(fileref(&fname)) > 0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%return;'; put '%end;'; put '%end;'; put '%put unable to find available fileref after &maxtries attempts;'; put '%end;'; put '%mend mf_getuniquefileref;'; put '%macro mp_abort(mac=mp_abort.sas, type=, msg=, iftrue=%str(1=1)'; put ', errds=work.mp_abort_errds'; put ', mode=REGULAR'; put ')/*/STORE SOURCE*/;'; put '%global sysprocessmode sysprocessname sasjs_stpsrv_header_loc sasjsprocessmode;'; put '%local fref fid i;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%put NOTE: /// mp_abort macro executing //;'; put '%if %length(&mac)>0 %then %put NOTE- called by &mac;'; put '%put NOTE - &msg;'; put '%if %symexist(_SYSINCLUDEFILEDEVICE)'; put '/* abort cancel FILE does not restart outside the INCLUDE on Viya 3.5 */'; put 'and %superq(SYSPROCESSNAME) ne %str(Compute Server)'; put '%then %do;'; put '%if "*&_SYSINCLUDEFILEDEVICE*" ne "**" %then %do;'; put 'data &errds;'; put 'iftrue=''1=1'';'; put 'length mac $100 msg $5000;'; put 'mac=symget(''mac'');'; put 'msg=symget(''msg'');'; put 'run;'; put 'data _null_;'; put 'abort cancel FILE;'; put 'run;'; put '%return;'; put '%end;'; put '%end;'; put '/* Web App Context */'; put '%if %symexist(_PROGRAM)'; put 'or %superq(SYSPROCESSNAME) = %str(Compute Server)'; put 'or &mode=INCLUDE'; put '%then %do;'; put 'options obs=max replace mprint;'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5"'; put '%then %do;'; put 'options nosyntaxcheck;'; put '%end;'; put '%if &mode=INCLUDE %then %do;'; put '%if %sysfunc(exist(&errds))=1 %then %do;'; put 'data _null_;'; put 'set &errds;'; put 'call symputx(''iftrue'',iftrue,''l'');'; put 'call symputx(''mac'',mac,''l'');'; put 'call symputx(''msg'',msg,''l'');'; put 'putlog (_all_)(=);'; put 'run;'; put '%if (&iftrue)=0 %then %return;'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: No include errors found;'; put '%return;'; put '%end;'; put '%end;'; put '/* extract log errs / warns, if exist */'; put '%local logloc logline;'; put '%global logmsg; /* capture global messages */'; put '%if %symexist(SYSPRINTTOLOG) %then %let logloc=&SYSPRINTTOLOG;'; put '%else %let logloc=%qsysfunc(getoption(LOG));'; put 'proc printto log=log;run;'; put '%let logline=0;'; put '%if %length(&logloc)>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input; putlog _infile_;'; put 'i=1;'; put 'retain logonce 0;'; put 'if ('; put '_infile_=:"%str(WARN)ING" or _infile_=:"%str(ERR)OR"'; put ') and logonce=0 then'; put 'do;'; put 'call symputx(''logline'',_n_);'; put 'logonce+1;'; put 'end;'; put 'run;'; put '/* capture log including lines BEFORE the err */'; put '%if &logline>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input;'; put 'i=1;'; put 'stoploop=0;'; put 'if _n_ ge &logline-15 and stoploop=0 then do until (i>22);'; put 'call symputx(''logmsg'',catx(''\n'',symget(''logmsg''),_infile_));'; put 'input;'; put 'i+1;'; put 'stoploop=1;'; put 'end;'; put 'if stoploop=1 then stop;'; put 'run;'; put '%end;'; put '%end;'; put '%if %symexist(SYS_JES_JOB_URI) %then %do;'; put '/* setup webout for Viya */'; put 'options nobomfile;'; put '%if "X&SYS_JES_JOB_URI.X"="XX" %then %do;'; put 'filename _webout temp lrecl=999999 mod;'; put '%end;'; put '%else %do;'; put 'filename _webout filesrvc parenturi="&SYS_JES_JOB_URI"'; put 'name="_webout.json" lrecl=999999 mod;'; put '%end;'; put '%end;'; put '%else %if %sysfunc(filename(fref,&sasjs_stpsrv_header_loc))=0 %then %do;'; put 'options nobomfile;'; put '/* set up http header for SASjs Server */'; put '%let fid=%sysfunc(fopen(&fref,A));'; put '%if &fid=0 %then %do;'; put '%put %str(ERR)OR: %sysfunc(sysmsg());'; put '%return;'; put '%end;'; put '%let rc=%sysfunc(fput(&fid,%str(Content-Type: application/json)));'; put '%let rc=%sysfunc(fwrite(&fid));'; put '%let rc=%sysfunc(fclose(&fid));'; put '%let rc=%sysfunc(filename(&fref));'; put '%end;'; put '/* send response in SASjs JSON format */'; put 'data _null_;'; put 'file _webout mod lrecl=32000 encoding=''utf-8'';'; put 'length msg syswarningtext syserrortext $32767 mode $10 ;'; put 'sasdatetime=datetime();'; put 'msg=symget(''msg'');'; put '%if &logline>0 %then %do;'; put 'msg=cats(msg,''\n\nLog Extract:\n'',symget(''logmsg''));'; put '%end;'; put '/* escape the escapes */'; put 'msg=tranwrd(msg,''\'',''\\'');'; put '/* escape the quotes */'; put 'msg=tranwrd(msg,''"'',''\"'');'; put '/* ditch the CRLFs as chrome complains */'; put 'msg=compress(msg,,''kw'');'; put '/* quote without quoting the quotes (which are escaped instead) */'; put 'msg=cats(''"'',msg,''"'');'; put 'if symexist(''_debug'') then debug=quote(trim(symget(''_debug'')));'; put 'else debug=''""'';'; put 'if symget(''sasjsprocessmode'')=''Stored Program'' then mode=''SASJS'';'; put 'if mode ne ''SASJS'' then put ''>>weboutBEGIN<<'';'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'put '',"sasjsAbort" : [{'';'; put 'put '' "MSG":'' msg ;'; put 'put '' ,"MAC": "'' "&mac" ''"}]'';'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'put '',"_DEBUG":'' debug ;'; put 'if symexist(''_metauser'') then do;'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put 'end;'; put 'if symexist(''SYS_JES_JOB_URI'') then do;'; put 'SYS_JES_JOB_URI=quote(trim(symget(''SYS_JES_JOB_URI'')));'; put 'put '',"SYS_JES_JOB_URI": '' SYS_JES_JOB_URI;'; put 'end;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put ",""SYSWARNINGTEXT"" : " syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'put "}" ;'; put 'if mode ne ''SASJS'' then put ''>>weboutEND<<'';'; put 'run;'; put '%put _all_;'; put '%if "&sysprocessmode " = "SAS Stored Process Server " %then %do;'; put 'data _null_;'; put 'putlog ''stpsrvset program err and syscc'';'; put 'rc=stpsrvset(''program error'', 0);'; put 'call symputx("syscc",0,"g");'; put 'run;'; put '%if &sysscp=WIN'; put 'and 1=0 /* deprecating this logic until we figure out a consistent abort */'; put 'and "%substr(%str(&sysvlong ),1,8)"="9.04.01M"'; put 'and "%substr(%str(&sysvlong ),9,1)">"5" %then %do;'; put '/* skip approach (below) does not work in windows m6+ envs */'; put 'endsas;'; put '%end;'; put '%else %do;'; put '/**'; put '* endsas kills 9.4m3 deployments by orphaning multibridges.'; put '* Abort variants are ungraceful (non zero return code)'; put '* This approach lets SAS run silently until the end :-)'; put '* Caution - fails when called within a %include within a macro'; put '* Use mp_include() to handle this.'; put '*/'; put 'filename skip temp;'; put 'data _null_;'; put 'file skip;'; put 'put ''%macro skip();'';'; put 'comment ''%mend skip; -> fix lint '';'; put 'put ''%macro skippy();'';'; put 'comment ''%mend skippy; -> fix lint '';'; put 'run;'; put '%inc skip;'; put '%end;'; put '%end;'; put '%else %if "&sysprocessmode " = "SAS Compute Server " %then %do;'; put '/* endsas kills the session making it harder to fetch results */'; put 'data _null_;'; put 'syswarningtext=symget(''syswarningtext'');'; put 'syserrortext=symget(''syserrortext'');'; put 'abort_msg=symget(''msg'');'; put 'syscc=symget(''syscc'');'; put 'sysuserid=symget(''sysuserid'');'; put 'iftrue=symget(''iftrue'');'; put 'put (_all_)(/=);'; put 'call symputx(''syscc'',0);'; put 'abort cancel nolist;'; put 'run;'; put '%end;'; put '%else %do;'; put '%abort cancel;'; put '%end;'; put '%end;'; put '%else %do;'; put '%put _all_;'; put '%abort cancel;'; put '%end;'; put '%mend mp_abort;'; put '/** @endcond */'; put '%macro mm_getstpcode('; put 'tree=/User Folders/sasdemo/somestp'; put ',name='; put ',outloc=0'; put ',outref=0'; put ',mDebug=1'; put ',showlog=NO'; put ');'; put '%local mD;'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing &sysmacroname..sas;'; put '%&mD.put _local_;'; put '%if %length(&name)>0 %then %let name=/&name;'; put '/* first, check if STP exists */'; put '%local tsuri;'; put '%let tsuri=stopifempty ;'; put 'data _null_;'; put 'format type uri tsuri value $200.;'; put 'call missing (of _all_);'; put 'path="&tree&name(StoredProcess)";'; put '/* first, find the STP ID */'; put 'if metadata_pathobj("",path,"StoredProcess",type,uri)>0 then do;'; put '/* get sourcecode */'; put 'cnt=1;'; put 'do while (metadata_getnasn(uri,"Notes",cnt,tsuri)>0);'; put 'rc=metadata_getattr(tsuri,"Name",value);'; put '&mD.put tsuri= value=;'; put 'if value="SourceCode" then do;'; put '/* found it! */'; put 'rc=metadata_getattr(tsuri,"Id",value);'; put 'call symputx(''tsuri'',value,''l'');'; put 'stop;'; put 'end;'; put 'cnt+1;'; put 'end;'; put 'end;'; put 'else put (_all_)(=);'; put 'run;'; put '%mp_abort(iftrue= (&tsuri=stopifempty)'; put ',mac=mm_getstpcode'; put ',msg=%str(&tree&name.(StoredProcess) not found!)'; put ')'; put '/**'; put '* Now we can extract the textstore'; put '*/'; put 'filename __getdoc temp lrecl=10000000;'; put 'proc metadata'; put 'in="$METAREPOSITORY'; put ''; put 'SAS1"'; put 'out=__getdoc ;'; put 'run;'; put '/* find the beginning of the text */'; put '%local start;'; put 'data _null_;'; put 'infile __getdoc lrecl=10000;'; put 'input;'; put 'start=index(_infile_,''StoredText="'');'; put 'if start then do;'; put 'call symputx("start",start+11);'; put '*putlog ''"'' _infile_ ''"'';'; put 'end;'; put 'stop;'; put '%local outeng;'; put '%if "&outloc"="0" %then %let outeng=TEMP;'; put '%else %let outeng="&outloc";'; put '%local fref;'; put '%if &outref=0 %then %let fref=%mf_getuniquefileref();'; put '%else %let fref=&outref;'; put '/* read the content, byte by byte, resolving escaped chars */'; put 'filename &fref &outeng lrecl=100000;'; put 'data _null_;'; put 'length filein 8 fileid 8;'; put 'filein = fopen("__getdoc","I",1,"B");'; put 'fileid = fopen("&fref","O",1,"B");'; put 'rec = "20"x;'; put 'length entity $6;'; put 'do while(fread(filein)=0);'; put 'x+1;'; put 'if x>&start then do;'; put 'rc = fget(filein,rec,1);'; put 'if rec=''"'' then leave;'; put 'else if rec="&" then do;'; put 'entity=rec;'; put 'do until (rec=";");'; put 'if fread(filein) ne 0 then goto getout;'; put 'rc = fget(filein,rec,1);'; put 'entity=cats(entity,rec);'; put 'end;'; put 'select (entity);'; put 'when (''&'' ) rec=''&'' ;'; put 'when (''<'' ) rec=''<'' ;'; put 'when (''>'' ) rec=''>'' ;'; put 'when (''''') rec="''" ;'; put 'when (''"'') rec=''"'' ;'; put 'when ('' '') rec=''0A''x;'; put 'when ('' '') rec=''0D''x;'; put 'when (''$'' ) rec=''$'' ;'; put 'when ('' '') rec=''09''x;'; put 'otherwise putlog "%str(WARN)ING: missing value for " entity=;'; put 'end;'; put 'rc =fput(fileid, substr(rec,1,1));'; put 'rc =fwrite(fileid);'; put 'end;'; put 'else do;'; put 'rc =fput(fileid,rec);'; put 'rc =fwrite(fileid);'; put 'end;'; put 'end;'; put 'end;'; put 'getout:'; put 'rc=fclose(filein);'; put 'rc=fclose(fileid);'; put 'run;'; put '%if &showlog=YES %then %do;'; put 'data _null_;'; put 'infile &fref lrecl=32767 end=last;'; put 'input;'; put 'if _n_=1 then putlog ''>>stpcodeBEGIN<<'';'; put 'putlog _infile_;'; put 'if last then putlog ''>>stpcodeEND<<'';'; put 'run;'; put '%end;'; put 'filename __getdoc clear;'; put '%if &outref=0 %then %do;'; put 'filename &fref clear;'; put '%end;'; put '%mend mm_getstpcode;'; put '%macro dc_getsettings();'; put '%global _program;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(syscc=&syscc on entry (&syswarningtext &syserrortext))'; put ')'; put '%if %length(&_PROGRAM)>1 %then %let root=&_program;'; put '%else %do;'; put '%global _metauser;'; put '%let _metauser=&sysuserid;'; put '/* to mimic a "real" _program we need to give a dummy role and stp name */'; put '%let root=/dummyRole/dummyName;'; put '%end;'; put '/* the DC precode is stored in the Admin folder in the root of'; put 'the project. Lets find that root. */'; put '%put &=root;'; put '%let root=%mf_getapploc();'; put '%put &=root;'; put '/* Now we know the root location we can retrieve the params */'; put '%let temploc=%sysfunc(pathname(work))/settings.txt;'; put '%mm_getstpcode(tree=&root/services/public'; put ',name=Data_Controller_Settings'; put ',outloc=&temploc'; put ')'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Unable to run getstpcode)'; put ')'; put 'filename _getsets "&temploc" lrecl=2000;'; put '/*'; put 'Do not use mp_include here - this puts a copy in every service, which creates'; put 'compilation problems when calling services from mp_include'; put '*/'; put '%inc _getsets/source2;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Problem running &sysmacroname (&syswarningtext &syserrortext))'; put ')'; put '%mend dc_getsettings;'; put '%macro mf_fmtdttm('; put ')/*/STORE SOURCE*/;'; put '%if "&sysver"="9.2" or "&sysver"="9.3"'; put 'or ("&sysver"="9.4" and "%substr(&SYSVLONG,9,1)" le "3")'; put 'or "%substr(&sysver,1,1)"="4"'; put 'or "%substr(&sysver,1,1)"="5"'; put '%then %do;DATETIME19.3%end;'; put '%else %do;E8601DT26.6%end;'; put '%mend mf_fmtdttm;'; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '%macro mp_init(prefix=SASJS'; put ')/*/STORE SOURCE*/;'; put '%if %symexist(SASJS_PREFIX) %then %return; /* only run once */'; put '%global'; put 'SASJS_PREFIX /* the ONLY hard-coded global macro variable in SASjs */'; put '&prefix._FUNCTIONS /* used in mcf_init() to track core function compilation */'; put '&prefix._INIT_NUM /* initialisation time as numeric */'; put '&prefix._INIT_DTTM /* initialisation time in E8601DT26.6 format */'; put '&prefix.WORK /* avoid typing %sysfunc(pathname(work)) every time */'; put ';'; put '%let sasjs_prefix=&prefix;'; put 'data _null_;'; put 'dttm=datetime();'; put 'call symputx("&sasjs_prefix._init_num",dttm,''g'');'; put 'call symputx("&sasjs_prefix._init_dttm",put(dttm,E8601DT26.6),''g'');'; put 'call symputx("&sasjs_prefix.work",pathname(''WORK''),''g'');'; put 'run;'; put 'options'; put 'compress=CHAR /* default is none so ensure we have something! */'; put 'datastmtchk=ALLKEYWORDS /* protection from overwriting input datasets */'; put 'errorcheck=STRICT /* catch errs in libname/filename statements */'; put 'fmterr /* ensure err when a format cannot be found */'; put 'mergenoby=%str(ERR)OR /* throw err when a merge has no BY variables */'; put 'missing=. /* changing this can cause hard to detect errs */'; put 'noquotelenmax /* avoid warnings for long strings */'; put 'noreplace /* avoid overwriting permanent datasets */'; put 'ps=max /* reduce log size slightly */'; put 'ls=max /* reduce log even more and avoid word truncation */'; put 'validmemname=COMPATIBLE /* avoid special characters etc in table names */'; put 'validvarname=V7 /* avoid special characters etc in variable names */'; put 'varinitchk=%str(ERR)OR /* avoid data mistakes from variable name typos */'; put 'varlenchk=%str(ERR)OR /* fail hard if truncation (data loss) can result */'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5" %then %do;'; put 'noautocorrect /* disallow misspelled procedure names */'; put 'dsoptions=note2err /* undocumented - convert bad NOTEs to ERRs */'; put '%end;'; put ';'; put '%mend mp_init;'; put '%macro mpeinit(fetch=YES);'; put '%global mpeinit'; put 'mpeadmins /* group with unrestricted Meditor access */'; put 'mpelocapprovals /* location for landing and staging files */'; put 'mpelib /* location of configuration tables for DC */'; put 'dc_repo_users /* location of user / group metadata */'; put 'dc_licence_key /* extracted in dc_getsettings */'; put 'dc_activation_key /* extracted in dc_getsettings */'; put 'dc_locale /* extracted in dc_getsettings */'; put 'dc_dttmtfmt /* can be overridden in dc_getsettings */'; put '_debug'; put ';'; put '%if &mpeinit=1 %then %return;'; put '%else %let mpeinit=1;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(Problem on service startup (&syswarningtext &syserrortext))'; put ')'; put '%mp_init()'; put '%if &fetch=YES %then %do;'; put '%webout(FETCH)'; put '%end;'; put '%global _CLIENTNAME;'; put '%mp_abort(iftrue= (&_CLIENTNAME=SAS Enterprise Guide)'; put ',mac=&_program..sas'; put ',msg=%str(Data Controller is a web app and should not be executed from EG)'; put ')'; put 'options urlencoding=utf8 nobomfile lrecl=32767;'; put '%let perf=%sysfunc(datetime());'; put '%put perfdiff: 0;'; put '%let dc_locale=SYSTEM; /* default if not set */'; put '/**'; put '* E8601DT26.6 has widest database support - but not all SAS flavours can'; put '* handle it. Override in the settings STP if needed.'; put '*/'; put 'data _null_;'; put 'dc_dttmtfmt=''"%sysfunc(datetime(),''!!"%mf_fmtdttm()"!!'')"dt'';'; put 'call symputx(''dc_dttmtfmt'',dc_dttmtfmt);'; put 'put dc_dttmtfmt=;'; put 'run;'; put '%put &=dc_dttmtfmt;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc prior to dc_getsettings)'; put ')'; put '%dc_getsettings()'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc after dc_getsettings)'; put ')'; put 'data _null_;'; put 'set &DC_LIBREF..mpe_config(where=('; put 'var_scope="DC"'; put 'and &dc_dttmtfmt lt tx_to'; put 'and var_active=1'; put '));'; put 'call symputx(var_name,var_value,''G'');'; put 'putlog var_name "=" var_value;'; put 'run;'; put '%let mpelib=&dc_libref;'; put '%let mpeadmins=&dc_admin_group;'; put '%let mpelocapprovals=&dc_staging_area;'; put '%let dc_repo_users=&dc_repo_users;'; put '%if &dc_locale ne SYSTEM %then %do;'; put 'options locale=&dc_locale;'; put '%end;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(Problem during compilation or with STP precode (&syswarningtext))'; put ')'; put '%mend mpeinit;'; put '%macro mf_mval(var);'; put '%if %symexist(&var) %then %do;'; put '%superq(&var)'; put '%end;'; put '%mend mf_mval;'; put '%macro mf_trimstr(basestr,trimstr);'; put '%local baselen trimlen trimval;'; put '/* return if basestr is shorter than trimstr (or 0) */'; put '%let baselen=%length(%superq(basestr));'; put '%let trimlen=%length(%superq(trimstr));'; put '%if &baselen < &trimlen or &baselen=0 %then %return;'; put '/* obtain the characters from the end of basestr */'; put '%let trimval=%qsubstr(%superq(basestr)'; put ',%length(%superq(basestr))-&trimlen+1'; put ',&trimlen);'; put '/* compare and if matching, chop it off! */'; put '%if %superq(basestr)=%superq(trimstr) %then %do;'; put '%return;'; put '%end;'; put '%else %if %superq(trimval)=%superq(trimstr) %then %do;'; put '%qsubstr(%superq(basestr),1,%length(%superq(basestr))-&trimlen)'; put '%end;'; put '%else %do;'; put '&basestr'; put '%end;'; put '%mend mf_trimstr;'; put '%macro mf_getplatform(switch'; put ')/*/STORE SOURCE*/;'; put '%local a b c;'; put '%if &switch.NONE=NONE %then %do;'; put '%if %symexist(sasjsprocessmode) %then %do;'; put '%if &sasjsprocessmode=Stored Program %then %do;'; put 'SASJS'; put '%return;'; put '%end;'; put '%end;'; put '%if %symexist(sysprocessmode) %then %do;'; put '%if "&sysprocessmode"="SAS Object Server"'; put 'or "&sysprocessmode"= "SAS Compute Server" %then %do;'; put 'SASVIYA'; put '%end;'; put '%else %if "&sysprocessmode"="SAS Stored Process Server"'; put 'or "&sysprocessmode"="SAS Workspace Server"'; put '%then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if %symexist(_metaport) or %symexist(_metauser) %then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if &switch=SASSTUDIO %then %do;'; put '/* return the version of SAS Studio else 0 */'; put '%if %mf_mval(_CLIENTAPP)=%str(SAS Studio) %then %do;'; put '%let a=%mf_mval(_CLIENTVERSION);'; put '%let b=%scan(&a,1,.);'; put '%if %eval(&b >2) %then %do;'; put '&b'; put '%end;'; put '%else 0;'; put '%end;'; put '%else 0;'; put '%end;'; put '%else %if &switch=VIYARESTAPI %then %do;'; put '%mf_trimstr(%sysfunc(getoption(servicesbaseurl)),/)'; put '%end;'; put '%mend mf_getplatform;'; put '%macro mpeterm();'; put '%local oldloc;'; put 'data _null_;'; put 'if symexist(''SYSPRINTTOLOG'') then oldloc=symget(''SYSPRINTTOLOG'');'; put 'else oldloc=getoption(''LOG'');'; put 'if subpad(oldloc,1,1) not in (''"'',"''",'' '') then oldloc=quote(cats(oldloc));'; put 'call symputx(''oldloc'',oldloc,''l'');'; put 'run;'; put '%if %length(&oldloc)>0 %then %do;'; put 'proc printto log=log;'; put 'run;'; put 'data _null_;'; put 'infile &oldloc;'; put 'input; putlog _infile_;'; put 'run;'; put '%end;'; put '%if %sysfunc(exist(&dc_libref..mpe_requests)) and %mf_getplatform() ne SASVIYA'; put '%then %do;'; put 'data ;'; put 'if 0 then set &dc_libref..mpe_requests;'; put 'request_dttm=%sysfunc(datetime());'; put 'request_user="%mf_getuser()";'; put 'request_service="%scan(&_program,-2,/)/%scan(&_program,-1,/)";'; put 'request_params='''';'; put 'output;stop;'; put 'proc append base=&dc_libref..mpe_requests data=&syslast force nowarn;'; put 'run;'; put '%end;'; put '%mend mpeterm;'; put '%macro mm_assignlib('; put 'libref'; put ',mAbort=HARD'; put ')/*/STORE SOURCE*/;'; put '%local mp_abort msg;'; put '%let mp_abort=0;'; put '%if %sysfunc(libref(&libref)) %then %do;'; put 'data _null_;'; put 'length liburi LibName msg $200;'; put 'call missing(of _all_);'; put 'nobj=metadata_getnobj("omsobj:SASLibrary?@Libref=''&libref''",1,liburi);'; put 'if nobj=1 then do;'; put 'rc=metadata_getattr(liburi,"Name",LibName);'; put '/* now try and assign it */'; put 'if libname("&libref",,''meta'',cats(''liburi="'',liburi,''";'')) ne 0 then do;'; put 'putlog "&libref could not be assigned";'; put 'putlog liburi=;'; put '/**'; put '* Fetch the system message for display in the abort modal. This is'; put '* not always helpful though. One example, previously received:'; put '* NOTE: Libref XX refers to the same library metadata as libref XX.'; put '*/'; put 'msg=sysmsg();'; put 'if msg=:''ERROR: Libref SAVE is not assigned.'' then do;'; put 'msg=catx(" ",'; put '"Could not assign %upcase(&libref).",'; put '"Please check metadata permissions! Libname:",libname,'; put '"Liburi:",liburi'; put ');'; put 'end;'; put 'else if msg="ERROR: User does not have appropriate authorization "!!'; put '"level for library SAVE."'; put 'then do;'; put 'msg=catx(" ",'; put '"ERROR: User does not have appropriate authorization level",'; put '"for library %upcase(&libref), libname:",libname,'; put '"Liburi:",liburi'; put ');'; put 'end;'; put 'call symputx(''msg'',msg,''l'');'; put 'if "&mabort"=''HARD'' then call symputx(''mp_abort'',1,''l'');'; put 'end;'; put 'else do;'; put 'put (_all_)(=);'; put 'call symputx(''libname'',libname,''L'');'; put 'call symputx(''liburi'',liburi,''L'');'; put 'end;'; put 'end;'; put 'else if nobj>1 then do;'; put 'if "&mabort"=''HARD'' then call symputx(''mp_abort'',1);'; put 'call symputx(''msg'',"More than one library with libref=&libref");'; put 'end;'; put 'else do;'; put 'if "&mabort"=''HARD'' then call symputx(''mp_abort'',1);'; put 'call symputx(''msg'',"Library &libref not found in metadata");'; put 'end;'; put 'run;'; put '%put NOTE: &msg;'; put '%end;'; put '%else %do;'; put '%put NOTE: Library &libref is already assigned;'; put '%end;'; put '%mp_abort(iftrue= (&mp_abort=1)'; put ',mac=mm_assignlib.sas'; put ',msg=%superq(msg)'; put ')'; put '%mend mm_assignlib;'; put '/** @cond */'; put '%macro mf_getengine(libref'; put ')/*/STORE SOURCE*/;'; put '%local dsid engnum rc engine;'; put '/* in case the parameter is a libref.tablename, pull off just the libref */'; put '%let libref = %upcase(%scan(&libref, 1, %str(.)));'; put '%let dsid=%sysfunc('; put 'open(sashelp.vlibnam(where=(libname="%upcase(&libref)")),i)'; put ');'; put '%if (&dsid ^= 0) %then %do;'; put '%let engnum=%sysfunc(varnum(&dsid,ENGINE));'; put '%let rc=%sysfunc(fetch(&dsid));'; put '%let engine=%sysfunc(getvarc(&dsid,&engnum));'; put '%put &libref. ENGINE is &engine.;'; put '%let rc= %sysfunc(close(&dsid));'; put '%end;'; put '%upcase(&engine)'; put '%mend mf_getengine;'; put '/** @endcond */'; put '%macro mm_assigndirectlib('; put 'libref'; put ',open_passthrough='; put ',sql_options='; put ',mDebug=0'; put ',mAbort=0'; put ')/*/STORE SOURCE*/;'; put '%local mD;'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing mm_assigndirectlib.sas;'; put '%&mD.put _local_;'; put '%if &mAbort=1 %then %let mAbort=;'; put '%else %let mAbort=%str(*);'; put '%&mD.put NOTE: Creating direct (non META) connection to &libref library;'; put '%local cur_engine;'; put '%let cur_engine=%mf_getengine(&libref);'; put '%if &cur_engine ne META and &cur_engine ne %then %do;'; put '%put NOTE: &libref already has a direct (&cur_engine) libname connection;'; put '%return;'; put '%end;'; put '%else %if %upcase(&libref)=WORK %then %do;'; put '%put NOTE: We already have a direct connection to WORK :-) ;'; put '%return;'; put '%end;'; put '/* need to determine the library ENGINE first */'; put '%local engine;'; put 'data _null_;'; put 'length lib_uri engine $256;'; put 'call missing (of _all_);'; put '/* get URI for the particular library */'; put 'rc1=metadata_getnobj("omsobj:SASLibrary?@Libref =''&libref''",1,lib_uri);'; put '/* get the Engine attribute of the previous object */'; put 'rc2=metadata_getattr(lib_uri,''Engine'',engine);'; put 'putlog "mm_assigndirectlib for &libref:" rc1= lib_uri= rc2= engine=;'; put 'call symputx("liburi",lib_uri,''l'');'; put 'call symputx("engine",engine,''l'');'; put 'run;'; put '/* now obtain engine specific connection details */'; put '%if &engine=BASE %then %do;'; put '%&mD.put NOTE: Retrieving BASE library path;'; put 'data _null_;'; put 'length up_uri $256 path cat_path $1024;'; put 'retain cat_path;'; put 'call missing (of _all_);'; put '/* get all the filepaths of the UsingPackages association */'; put 'i=1;'; put 'rc3=metadata_getnasn("&liburi",''UsingPackages'',i,up_uri);'; put 'do while (rc3>0);'; put '/* get the DirectoryName attribute of the previous object */'; put 'rc4=metadata_getattr(up_uri,''DirectoryName'',path);'; put 'if i=1 then path = ''("''!!trim(path)!!''" '';'; put 'else path ='' "''!!trim(path)!!''" '';'; put 'cat_path = trim(cat_path) !! " " !! trim(path) ;'; put 'i+1;'; put 'rc3=metadata_getnasn("&liburi",''UsingPackages'',i,up_uri);'; put 'end;'; put 'cat_path = trim(cat_path) !! ")";'; put '&mD.putlog "NOTE: Getting physical path for &libref library";'; put '&mD.putlog rc3= up_uri= rc4= cat_path= path=;'; put '&mD.putlog "NOTE: Libname cmd will be:";'; put '&mD.putlog "libname &libref" cat_path;'; put 'call symputx("filepath",cat_path,''l'');'; put 'run;'; put '%if %sysevalf(&sysver<9.4) %then %do;'; put 'libname &libref &filepath;'; put '%end;'; put '%else %do;'; put '/* apply the new filelocks option to cater for temporary locks */'; put 'libname &libref &filepath filelockwait=5;'; put '%end;'; put '%end;'; put '%else %if &engine=REMOTE %then %do;'; put 'data x;'; put 'length rcCon rcProp rc k 3 uriCon uriProp PropertyValue PropertyName'; put 'Delimiter $256 properties $2048;'; put 'retain properties;'; put 'rcCon = metadata_getnasn("&liburi", "LibraryConnection", 1, uriCon);'; put 'rcProp = metadata_getnasn(uriCon, "Properties", 1, uriProp);'; put 'k = 1;'; put 'rcProp = metadata_getnasn(uriCon, "Properties", k, uriProp);'; put 'do while (rcProp > 0);'; put 'rc = metadata_getattr(uriProp , "DefaultValue",PropertyValue);'; put 'rc = metadata_getattr(uriProp , "PropertyName",PropertyName);'; put 'rc = metadata_getattr(uriProp , "Delimiter",Delimiter);'; put 'properties = trim(properties) !! " " !! trim(PropertyName)'; put '!! trim(Delimiter) !! trim(PropertyValue);'; put 'output;'; put 'k+1;'; put 'rcProp = metadata_getnasn(uriCon, "Properties", k, uriProp);'; put 'end;'; put '%&mD.put NOTE: Getting properties for REMOTE SHARE &libref library;'; put '&mD.put _all_;'; put '%&mD.put NOTE: Libname cmd will be:;'; put '%&mD.put libname &libref &engine &properties slibref=&libref;'; put 'call symputx ("properties",trim(properties),''l'');'; put 'run;'; put 'libname &libref &engine &properties slibref=&libref;'; put '%end;'; put '%else %if &engine=OLEDB %then %do;'; put '%&mD.put NOTE: Retrieving OLEDB connection details;'; put 'data _null_;'; put 'length domain datasource provider properties schema'; put 'connx_uri domain_uri conprop_uri lib_uri schema_uri value $256.;'; put 'call missing (of _all_);'; put '/* get source connection ID */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,connx_uri);'; put '/* get connection domain */'; put 'rc1=metadata_getnasn(connx_uri,''Domain'',1,domain_uri);'; put 'rc2=metadata_getattr(domain_uri,''Name'',domain);'; put '&mD.putlog / ''NOTE: '' // ''NOTE- connection id: '' connx_uri ;'; put '&mD.putlog ''NOTE- domain: '' domain;'; put '/* get DSN and PROVIDER from connection properties */'; put 'i=0;'; put 'do until (rc<0);'; put 'i+1;'; put 'rc=metadata_getnasn(connx_uri,''Properties'',i,conprop_uri);'; put 'rc2=metadata_getattr(conprop_uri,''Name'',value);'; put 'if value=''Connection.OLE.Property.DATASOURCE.Name.xmlKey.txt'' then do;'; put 'rc3=metadata_getattr(conprop_uri,''DefaultValue'',datasource);'; put 'end;'; put 'else if value=''Connection.OLE.Property.PROVIDER.Name.xmlKey.txt'' then do;'; put 'rc4=metadata_getattr(conprop_uri,''DefaultValue'',provider);'; put 'end;'; put 'else if value=''Connection.OLE.Property.PROPERTIES.Name.xmlKey.txt'' then'; put 'do;'; put 'rc5=metadata_getattr(conprop_uri,''DefaultValue'',properties);'; put 'end;'; put 'end;'; put '&mD.putlog ''NOTE- dsn/provider/properties: '' /'; put 'datasource provider properties;'; put '&mD.putlog ''NOTE- schema: '' schema // ''NOTE-'';'; put '/* get SCHEMA */'; put 'rc6=metadata_getnasn("&liburi",''UsingPackages'',1,lib_uri);'; put 'rc7=metadata_getattr(lib_uri,''SchemaName'',schema);'; put 'call symputx(''SQL_domain'',domain,''l'');'; put 'call symputx(''SQL_dsn'',datasource,''l'');'; put 'call symputx(''SQL_provider'',provider,''l'');'; put 'call symputx(''SQL_properties'',properties,''l'');'; put 'call symputx(''SQL_schema'',schema,''l'');'; put 'run;'; put '%if %length(&open_passthrough)>0 %then %do;'; put 'proc sql &sql_options;'; put 'connect to OLEDB as &open_passthrough(INSERT_SQL=YES'; put '/* need additional properties to make this work */'; put 'properties=(''Integrated Security''=SSPI'; put '''Persist Security Info''=True'; put '%sysfunc(compress(%str(&SQL_properties),%str(())))'; put ')'; put 'DATASOURCE=&sql_dsn PROMPT=NO'; put 'PROVIDER=&sql_provider SCHEMA=&sql_schema CONNECTION = GLOBAL);'; put '%end;'; put '%else %do;'; put 'LIBNAME &libref OLEDB PROPERTIES=&sql_properties'; put 'DATASOURCE=&sql_dsn PROVIDER=&sql_provider SCHEMA=&sql_schema'; put '%if %length(&sql_domain)>0 %then %do;'; put 'authdomain="&sql_domain"'; put '%end;'; put 'connection=shared;'; put '%end;'; put '%end;'; put '%else %if &engine=ODBC %then %do;'; put '%&mD.put NOTE: Retrieving ODBC connection details;'; put 'data _null_;'; put 'length connx_uri conprop_uri value datasource up_uri schema domprop_uri authdomain $256.;'; put 'call missing (of _all_);'; put '/* get source connection ID */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,connx_uri);'; put '/* get connection properties */'; put 'i=0;'; put 'do until (rc2<0);'; put 'i+1;'; put 'rc2=metadata_getnasn(connx_uri,''Properties'',i,conprop_uri);'; put 'rc3=metadata_getattr(conprop_uri,''Name'',value);'; put 'if value=''Connection.ODBC.Property.DATASRC.Name.xmlKey.txt'' then do;'; put 'rc4=metadata_getattr(conprop_uri,''DefaultValue'',datasource);'; put 'rc2=-1;'; put 'end;'; put 'end;'; put '/* get auth domain */'; put 'autrc=metadata_getnasn(connx_uri,"Domain",1,domprop_uri);'; put 'arc=metadata_getattr(domprop_uri,"Name",authdomain);'; put 'if not missing(authdomain) then authdomain=cats(''AUTHDOMAIN='',authdomain);'; put 'call symputx(''authdomain'',authdomain,''l'');'; put '/* get SCHEMA */'; put 'rc6=metadata_getnasn("&liburi",''UsingPackages'',1,up_uri);'; put 'rc7=metadata_getattr(up_uri,''SchemaName'',schema);'; put '&mD.put rc= connx_uri= rc2= conprop_uri= rc3= value= rc4= datasource='; put 'rc6= up_uri= rc7= schema=;'; put 'call symputx(''SQL_schema'',schema,''l'');'; put 'call symputx(''SQL_dsn'',datasource,''l'');'; put 'run;'; put '%if %length(&open_passthrough)>0 %then %do;'; put 'proc sql &sql_options;'; put 'connect to ODBC as &open_passthrough'; put '(INSERT_SQL=YES DATASRC=&sql_dsn. CONNECTION=global);'; put '%end;'; put '%else %do;'; put 'libname &libref ODBC DATASRC=&sql_dsn SCHEMA=&sql_schema &authdomain;'; put '%end;'; put '%end;'; put '%else %if &engine=POSTGRES %then %do;'; put '%put NOTE: Obtaining POSTGRES library details;'; put 'data _null_;'; put 'length database ignore_read_only_columns direct_exe preserve_col_names'; put 'preserve_tab_names server schema authdomain user password'; put 'prop name value uri urisrc $256.;'; put 'call missing (of _all_);'; put '/* get database value */'; put 'prop=''Connection.DBMS.Property.DB.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,database,"");'; put 'if database^='''' then database=''database=''!!quote(trim(database));'; put 'call symputx(''database'',database,''l'');'; put '/* get IGNORE_READ_ONLY_COLUMNS value */'; put 'prop=''Library.DBMS.Property.DBIROC.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,ignore_read_only_columns,"");'; put 'if ignore_read_only_columns^='''' then ignore_read_only_columns='; put '''ignore_read_only_columns=''!!ignore_read_only_columns;'; put 'call symputx(''ignore_read_only_columns'',ignore_read_only_columns,''l'');'; put '/* get DIRECT_EXE value */'; put 'prop=''Library.DBMS.Property.DirectExe.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,direct_exe,"");'; put 'if direct_exe^='''' then direct_exe=''direct_exe=''!!direct_exe;'; put 'call symputx(''direct_exe'',direct_exe,''l'');'; put '/* get PRESERVE_COL_NAMES value */'; put 'prop=''Library.DBMS.Property.PreserveColNames.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,preserve_col_names,"");'; put 'if preserve_col_names^='''' then preserve_col_names='; put '''preserve_col_names=''!!preserve_col_names;'; put 'call symputx(''preserve_col_names'',preserve_col_names,''l'');'; put '/* get PRESERVE_TAB_NAMES value */'; put '/* be careful with PRESERVE_TAB_NAMES=YES - it will mean your table will'; put 'become case sensitive!! */'; put 'prop=''Library.DBMS.Property.PreserveTabNames.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,preserve_tab_names,"");'; put 'if preserve_tab_names^='''' then preserve_tab_names='; put '''preserve_tab_names=''!!preserve_tab_names;'; put 'call symputx(''preserve_tab_names'',preserve_tab_names,''l'');'; put '/* get SERVER value */'; put 'if metadata_getnasn("&liburi","LibraryConnection",1,uri)>0 then do;'; put 'prop=''Connection.DBMS.Property.SERVER.Name.xmlKey.txt'';'; put 'rc=metadata_getprop(uri,prop,server,"");'; put 'end;'; put 'if server^='''' then server=''server=''!!quote(cats(server));'; put 'call symputx(''server'',server,''l'');'; put '/* get SCHEMA value */'; put 'if metadata_getnasn("&liburi","UsingPackages",1,uri)>0 then do;'; put 'rc=metadata_getattr(uri,"SchemaName",schema);'; put 'end;'; put 'if schema^='''' then schema=''schema=''!!schema;'; put 'call symputx(''schema'',schema,''l'');'; put '/* get AUTHDOMAIN value */'; put '/* this is only useful if the user account contains that auth domain'; put 'if metadata_getnasn("&liburi","DefaultLogin",1,uri)>0 then do;'; put 'rc=metadata_getnasn(uri,"Domain",1,urisrc);'; put 'rc=metadata_getattr(urisrc,"Name",authdomain);'; put 'end;'; put 'if authdomain^='''' then authdomain=''authdomain=''!!quote(trim(authdomain));'; put '*/'; put 'call symputx(''authdomain'',authdomain,''l'');'; put '/* get user & pass */'; put 'if authdomain='''' & metadata_getnasn("&liburi","DefaultLogin",1,uri)>0 then'; put 'do;'; put 'rc=metadata_getattr(uri,"UserID",user);'; put 'rc=metadata_getattr(uri,"Password",password);'; put 'end;'; put 'if user^='''' then do;'; put 'user=''user=''!!quote(trim(user));'; put 'password=''password=''!!quote(trim(password));'; put 'end;'; put 'call symputx(''user'',user,''l'');'; put 'call symputx(''password'',password,''l'');'; put '&md.put _all_;'; put 'run;'; put '%if %length(&open_passthrough)>0 %then %do;'; put '%put %str(WARN)ING: Passthrough option for postgres not yet supported;'; put '%return;'; put '%end;'; put '%else %do;'; put '%if &mdebug=1 %then %do;'; put '%put NOTE: Executing the following:/;'; put '%put NOTE- libname &libref POSTGRES &database &ignore_read_only_columns;'; put '%put NOTE- &direct_exe &preserve_col_names &preserve_tab_names;'; put '%put NOTE- &server &schema &authdomain &user &password //;'; put '%end;'; put 'libname &libref POSTGRES &database &ignore_read_only_columns &direct_exe'; put '&preserve_col_names &preserve_tab_names &server &schema &authdomain'; put '&user &password;'; put '%end;'; put '%end;'; put '%else %if &engine=ORACLE %then %do;'; put '%put NOTE: Obtaining &engine library details;'; put 'data _null_;'; put 'length assocuri1 assocuri2 assocuri3 authdomain path schema $256;'; put 'call missing (of _all_);'; put '/* get auth domain */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,assocuri1);'; put 'rc=metadata_getnasn(assocuri1,''Domain'',1,assocuri2);'; put 'rc=metadata_getattr(assocuri2,"Name",authdomain);'; put 'call symputx(''authdomain'',authdomain,''l'');'; put '/* path */'; put 'rc=metadata_getprop(assocuri1,'; put '''Connection.Oracle.Property.PATH.Name.xmlKey.txt'',path);'; put 'call symputx(''path'',path,''l'');'; put '/* schema */'; put 'rc=metadata_getnasn("&liburi",''UsingPackages'',1,assocuri3);'; put 'rc=metadata_getattr(assocuri3,''SchemaName'',schema);'; put 'call symputx(''schema'',schema,''l'');'; put 'run;'; put '%put NOTE: Executing the following:/; %put NOTE-;'; put '%put NOTE- libname &libref ORACLE path=&path schema=&schema;'; put '%put NOTE- authdomain=&authdomain;'; put '%put NOTE-;'; put 'libname &libref ORACLE path=&path schema=&schema authdomain=&authdomain;'; put '%end;'; put '%else %if &engine=SQLSVR %then %do;'; put '%put NOTE: Obtaining &engine library details;'; put 'data _null;'; put 'length assocuri1 assocuri2 assocuri3 authdomain path schema userid'; put 'passwd $256;'; put 'call missing (of _all_);'; put 'rc=metadata_getnasn("&liburi",''DefaultLogin'',1,assocuri1);'; put 'rc=metadata_getattr(assocuri1,"UserID",userid);'; put 'rc=metadata_getattr(assocuri1,"Password",passwd);'; put 'call symputx(''user'',userid,''l'');'; put 'call symputx(''pass'',passwd,''l'');'; put '/* path */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,assocuri2);'; put 'rc=metadata_getprop(assocuri2,'; put '''Connection.SQL.Property.Datasrc.Name.xmlKey.txt'',path);'; put 'call symputx(''path'',path,''l'');'; put '/* schema */'; put 'rc=metadata_getnasn("&liburi",''UsingPackages'',1,assocuri3);'; put 'rc=metadata_getattr(assocuri3,''SchemaName'',schema);'; put 'call symputx(''schema'',schema,''l'');'; put 'run;'; put '%put NOTE: Executing the following:/; %put NOTE-;'; put '%put NOTE- libname &libref SQLSVR datasrc=&path schema=&schema ;'; put '%put NOTE- user="&user" pass="XXX";'; put '%put NOTE-;'; put 'libname &libref SQLSVR datasrc=&path schema=&schema user="&user" pass="&pass";'; put '%end;'; put '%else %if &engine=TERADATA %then %do;'; put '%put NOTE: Obtaining &engine library details;'; put 'data _null;'; put 'length assocuri1 assocuri2 assocuri3 authdomain path schema userid'; put 'passwd $256;'; put 'call missing (of _all_);'; put '/* get auth domain */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,assocuri1);'; put 'rc=metadata_getnasn(assocuri1,''Domain'',1,assocuri2);'; put 'rc=metadata_getattr(assocuri2,"Name",authdomain);'; put 'call symputx(''authdomain'',authdomain,''l'');'; put '/*'; put 'rc=metadata_getnasn("&liburi",''DefaultLogin'',1,assocuri1);'; put 'rc=metadata_getattr(assocuri1,"UserID",userid);'; put 'rc=metadata_getattr(assocuri1,"Password",passwd);'; put 'call symputx(''user'',userid,''l'');'; put 'call symputx(''pass'',passwd,''l'');'; put '*/'; put '/* path */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,assocuri2);'; put 'rc=metadata_getprop(assocuri2,'; put '''Connection.Teradata.Property.SERVER.Name.xmlKey.txt'',path);'; put 'call symputx(''path'',path,''l'');'; put '/* schema */'; put 'rc=metadata_getnasn("&liburi",''UsingPackages'',1,assocuri3);'; put 'rc=metadata_getattr(assocuri3,''SchemaName'',schema);'; put 'call symputx(''schema'',schema,''l'');'; put 'run;'; put '%put NOTE: Executing the following:/; %put NOTE-;'; put '%put NOTE- libname &libref TERADATA server="&path" schema=&schema ;'; put '%put NOTe- authdomain=&authdomain;'; put '%put NOTE-;'; put 'libname &libref TERADATA server="&path" schema=&schema authdomain=&authdomain;'; put '%end;'; put '%else %if &engine= %then %do;'; put '%put NOTE: Libref &libref is not registered in metadata;'; put '%&mAbort.mp_abort('; put 'msg=%str(ERR)OR: Libref &libref is not registered in metadata'; put ',mac=mm_assigndirectlib.sas);'; put '%return;'; put '%end;'; put '%else %do;'; put '%put %str(WARN)ING: Engine &engine is currently unsupported;'; put '%put %str(WARN)ING- Please contact your support team.;'; put '%return;'; put '%end;'; put '%mend mm_assigndirectlib;'; put '%macro mm_getrepos('; put 'outds=work.mm_getrepos'; put ')/*/STORE SOURCE*/;'; put '* use a temporary fileref to hold the response;'; put 'filename response temp;'; put '/* get list of libraries */'; put 'proc metadata in='; put '"1"'; put 'out=response;'; put 'run;'; put '/* write the response to the log for debugging */'; put '/*'; put 'data _null_;'; put 'infile response lrecl=1048576;'; put 'input;'; put 'put _infile_;'; put 'run;'; put '*/'; put '/* create an XML map to read the response */'; put 'filename sxlemap temp;'; put 'data _null_;'; put 'file sxlemap;'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository";'; put 'put "";'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Id";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Name";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Desc";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@DefaultNS";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@RepositoryType";'; put 'put "characterstring20";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@RepositoryFormat";'; put 'put "characterstring10";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@Access";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@CurrentAccess";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@PauseState";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Path";'; put 'put "";'; put 'put "characterstring256";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Engine";'; put 'put "";'; put 'put "characterstring8";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Options";'; put 'put "";'; put 'put "characterstring32";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@MetadataCreated";'; put 'put "characterstring24";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@MetadataUpdated";'; put 'put "characterstring24";'; put 'put '''';'; put 'put ''
'';'; put 'run;'; put 'libname _XML_ xml xmlfileref=response xmlmap=sxlemap;'; put 'proc sort data= _XML_.SASRepos out=&outds;'; put 'by name;'; put 'run;'; put '/* clear references */'; put 'filename sxlemap clear;'; put 'filename response clear;'; put 'libname _XML_ clear;'; put '%mend mm_getrepos;'; put '%macro dc_assignlib(type,libref,passthru=);'; put '%put &sysmacroname entry vars:;'; put '%put _local_;'; put '/* take current repository */'; put '%local repo repocnt x;'; put '%let repo=%sysfunc(getoption(metarepository));'; put '/* get list of repositories and filter */'; put '%mm_getrepos(outds=repos)'; put '%let repocnt=0;'; put 'data repos;'; put 'set repos;'; put 'where repositorytype in(''CUSTOM'',''FOUNDATION'')'; put '& upcase(name) ne "%upcase(&repo)";'; put 'keep id name ;'; put 'call symputx(cats(''repo'',_n_),name,''l'');'; put 'call symputx(''repocnt'',_n_,''l'');'; put 'run;'; put '/* find out which of these repositories has the libref we are searching for */'; put '%local lib_uri;'; put '%let lib_uri=NOTFOUND;'; put 'data _null_; /* check default repo first */'; put 'length lib_uri $256;'; put 'call missing (of _all_);'; put 'rc=metadata_getnobj("omsobj:SASLibrary?@Libref =''&libref''",1,lib_uri);'; put 'call symputx(''lib_uri'',coalescec(lib_uri,''NOTFOUND''),''l'');'; put 'run;'; put '%if &lib_uri=NOTFOUND %then %do;'; put '%do x=1 %to &repocnt;'; put 'options metarepository=&&repo&x;'; put 'data _null_;'; put 'length lib_uri $256;'; put 'call missing (of _all_);'; put 'rc=metadata_getnobj("omsobj:SASLibrary?@Libref =''&libref''",1,lib_uri);'; put 'call symputx(''lib_uri'',coalescec(lib_uri,''NOTFOUND''),''l'');'; put 'run;'; put '%if &lib_uri ne NOTFOUND %then %let x=%eval(&repocnt+1);'; put '%end;'; put '%end;'; put '%if &type=READ %then %do;'; put '%mm_assignlib(&libref)'; put '%end;'; put '%else %if &type=WRITE %then %do;'; put '%mm_assigndirectlib(&libref,open_passthrough=&passthru)'; put '%end;'; put 'options metarepository=&repo;'; put '%mend dc_assignlib;'; put '/** @cond */'; put '%macro mf_existvar(libds /* 2 part dataset name */'; put ', var /* variable name */'; put ')/*/STORE SOURCE*/;'; put '%local dsid rc;'; put '%let dsid=%sysfunc(open(&libds,is));'; put '%if &dsid=0 %then %do;'; put '%put %sysfunc(sysmsg());'; put '0'; put '%end;'; put '%else %if %length(&var)=0 %then %do;'; put '0'; put '%let rc=%sysfunc(close(&dsid));'; put '%end;'; put '%else %do;'; put '%sysfunc(varnum(&dsid,&var))'; put '%let rc=%sysfunc(close(&dsid));'; put '%end;'; put '%mend mf_existvar;'; put '/** @endcond */'; put '%macro mf_getattrn('; put 'libds'; put ',attr'; put ')/*/STORE SOURCE*/;'; put '%local dsid rc;'; put '%let dsid=%sysfunc(open(&libds,is));'; put '%if &dsid = 0 %then %do;'; put '%put %str(WARN)ING: Cannot open %trim(&libds), system message below;'; put '%put %sysfunc(sysmsg());'; put '-1'; put '%end;'; put '%else %do;'; put '%sysfunc(attrn(&dsid,&attr))'; put '%let rc=%sysfunc(close(&dsid));'; put '%end;'; put '%mend mf_getattrn;'; put '%macro mf_getvartype(libds /* two level name */'; put ', var /* variable name from which to return the type */'; put ')/*/STORE SOURCE*/;'; put '%local dsid vnum vtype rc;'; put '/* Open dataset */'; put '%let dsid = %sysfunc(open(&libds));'; put '%if &dsid. > 0 %then %do;'; put '/* Get variable number */'; put '%let vnum = %sysfunc(varnum(&dsid, &var));'; put '/* Get variable type (C/N) */'; put '%if(&vnum. > 0) %then %let vtype = %sysfunc(vartype(&dsid, &vnum.));'; put '%else %do;'; put '%put NOTE: Variable &var does not exist in &libds;'; put '%let vtype = %str( );'; put '%end;'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: dataset &libds not opened! (rc=&dsid);'; put '%put &sysmacroname: %sysfunc(sysmsg());'; put '%return;'; put '%end;'; put '/* Close dataset */'; put '%let rc = %sysfunc(close(&dsid));'; put '/* Return variable type */'; put '&vtype'; put '%mend mf_getvartype;'; put '%macro mf_getattrc('; put 'libds'; put ',attr'; put ')/*/STORE SOURCE*/;'; put '%local dsid rc;'; put '%let dsid=%sysfunc(open(&libds,is));'; put '%if &dsid = 0 %then %do;'; put '%put %str(WARN)ING: Cannot open %trim(&libds), system message below;'; put '%put %sysfunc(sysmsg());'; put '-1'; put '%end;'; put '%else %do;'; put '%sysfunc(attrc(&dsid,&attr))'; put '%let rc=%sysfunc(close(&dsid));'; put '%end;'; put '%mend mf_getattrc;'; put '%macro mp_lockfilecheck('; put 'libds'; put ')/*/STORE SOURCE*/;'; put 'data _null_;'; put 'if _n_=1 then putlog "&sysmacroname entry vars:";'; put 'set sashelp.vmacro;'; put 'where scope="&sysmacroname";'; put 'put name ''='' value;'; put 'run;'; put '%mp_abort(iftrue= (&syscc>0)'; put ',mac=checklock.sas'; put ',msg=Aborting with syscc=&syscc on entry.'; put ')'; put '%mp_abort(iftrue= ("&libds"="0")'; put ',mac=&sysmacroname'; put ',msg=%str(libds not provided)'; put ')'; put '%local msg lib ds;'; put '%let lib=%upcase(%scan(&libds,1,.));'; put '%let ds=%upcase(%scan(&libds,2,.));'; put '/* in DC, format catalogs are passed with a -FC suffix. No saslock here! */'; put '%if %scan(&libds,2,-)=FC %then %do;'; put '%put &sysmacroname: Format Catalog detected, no lockfile applied to &libds;'; put '%return;'; put '%end;'; put '/* do not proceed if no observations can be processed */'; put '%let msg=options obs = 0. syserrortext=%superq(syserrortext);'; put '%mp_abort(iftrue= (%sysfunc(getoption(OBS))=0)'; put ',mac=checklock.sas'; put ',msg=%superq(msg)'; put ')'; put 'data _null_;'; put 'putlog "Checking engine & member type";'; put 'run;'; put '%local engine memtype;'; put '%let memtype=%mf_getattrc(&libds,MTYPE);'; put '%let engine=%mf_getattrc(&libds,ENGINE);'; put '%if &engine ne V9 and &engine ne BASE %then %do;'; put 'data _null_;'; put 'putlog "Lib &lib is not assigned using BASE engine - uses &engine instead";'; put 'putlog "SAS lock check will not be performed";'; put 'run;'; put '%return;'; put '%end;'; put '%else %if &memtype ne DATA %then %do;'; put '%put NOTE: Cannot lock a VIEW!! Memtype=&memtype;'; put '%return;'; put '%end;'; put 'data _null_;'; put 'putlog "Engine = &engine, memtype=&memtype";'; put 'putlog "Attempting lock statement";'; put 'run;'; put 'lock &libds;'; put '%local abortme;'; put '%let abortme=0;'; put '%if &syscc>0 or &SYSLCKRC ne 0 %then %do;'; put '%let msg=Unable to apply lock on &libds (SYSLCKRC=&SYSLCKRC syscc=&syscc);'; put '%put %str(ERR)OR: &sysmacroname: &msg;'; put '%let abortme=1;'; put '%end;'; put 'lock &libds clear;'; put '%mp_abort(iftrue= (&abortme=1)'; put ',mac=&sysmacroname'; put ',msg=%superq(msg)'; put ')'; put '%mend mp_lockfilecheck;'; put '%macro mp_lockanytable('; put 'action'; put ',lib= WORK'; put ',ds=0'; put ',ref='; put ',ctl_ds=0'; put ',loops=25'; put ',loop_secs=1'; put ');'; put 'data _null_;'; put 'if _n_=1 then putlog "&sysmacroname entry vars:";'; put 'set sashelp.vmacro;'; put 'where scope="&sysmacroname";'; put 'put name ''='' value;'; put 'run;'; put '%mp_abort(iftrue= ("&ds"="0" and &action ne MAKETABLE)'; put ',mac=&sysmacroname'; put ',msg=%str(dataset was not provided)'; put ')'; put '%mp_abort(iftrue= (&ctl_ds=0)'; put ',mac=&sysmacroname'; put ',msg=%str(Control dataset was not provided)'; put ')'; put '/* set up lib & mac vars */'; put '%let lib=%upcase(&lib);'; put '%let ds=%upcase(&ds);'; put '%let action=%upcase(&action);'; put '%local user x trans msg abortme;'; put '%let user=%mf_getuser();'; put '%let abortme=0;'; put '%mp_abort(iftrue= (&action ne LOCK & &action ne UNLOCK & &action ne MAKETABLE)'; put ',mac=&sysmacroname'; put ',msg=%str(Invalid action (&action) provided)'; put ')'; put '/* if an err condition exists, exit before we even begin */'; put '%mp_abort(iftrue= (&syscc>0 and &action=LOCK)'; put ',mac=&sysmacroname'; put ',msg=%str(aborting due to syscc=&syscc on LOCK entry)'; put ')'; put '/* do not bother locking work tables (else may affect all WORK libraries) */'; put '%if (%upcase(&lib)=WORK or %str(&lib)=%str()) & &action ne MAKETABLE %then %do;'; put '%put NOTE: WORK libraries will not be registered in the locking system.;'; put '%return;'; put '%end;'; put '/* do not proceed if no observations can be processed */'; put '%mp_abort(iftrue= (%sysfunc(getoption(OBS))=0)'; put ',mac=&sysmacroname'; put ',msg=%str(cannot continue when options obs = 0)'; put ')'; put '%if &ACTION=LOCK %then %do;'; put '/* abort if a SAS lock is already in place, or cannot be applied */'; put '%mp_lockfilecheck(&lib..&ds)'; put '/* next, check there is a record for this table */'; put '%local record_exists_check;'; put 'proc sql noprint;'; put 'select count(*) into: record_exists_check from &ctl_ds'; put 'where LOCK_LIB ="&lib" and LOCK_DS="&ds";'; put 'quit;'; put '%if &syscc>0 %then %put syscc=&syscc sqlrc=&sqlrc;'; put '%if &record_exists_check=0 %then %do;'; put 'data _null_;'; put 'putlog "&sysmacroname: adding record to lock table..";'; put 'run;'; put 'data ;'; put 'if 0 then set &ctl_ds;'; put 'LOCK_LIB ="&lib";'; put 'LOCK_DS="&ds";'; put 'LOCK_STATUS_CD=''LOCKED'';'; put 'LOCK_START_DTTM="%sysfunc(datetime(),%mf_fmtdttm())"dt;'; put 'LOCK_USER_NM="&user";'; put 'LOCK_PID="&sysjobid";'; put 'LOCK_REF="&ref";'; put 'output;stop;'; put 'run;'; put '%let trans=&syslast;'; put 'proc append base=&ctl_ds data=&trans;'; put 'run;'; put '%end;'; put '/* if record does exist, perform lock attempts */'; put '%else %do x=1 %to &loops;'; put 'data _null_;'; put 'putlog "&sysmacroname: attempting lock (iteration &x) "@;'; put 'putlog "at %sysfunc(datetime(),datetime19.) ..";'; put 'run;'; put 'proc sql;'; put 'update &ctl_ds'; put 'set LOCK_STATUS_CD=''LOCKED'''; put ', LOCK_START_DTTM="%sysfunc(datetime(),%mf_fmtdttm())"dt'; put ', LOCK_USER_NM="&user"'; put ', LOCK_PID="&sysjobid"'; put ', LOCK_REF="&ref"'; put 'where LOCK_LIB ="&lib" and LOCK_DS="&ds";'; put 'quit;'; put '/**'; put '* NOTE - occasionally SQL server will return an err code (deadlocked'; put '* transaction). If so, ignore it, keep calm, and carry on..'; put '*/'; put '%if &syscc>0 %then %do;'; put 'data _null_;'; put 'putlog ''NOTE-'' / ''NOTE-'';'; put 'putlog "NOTE- &sysmacroname: Update failed. "@;'; put 'putlog "Resetting err conditions and re-attempting.";'; put 'putlog "NOTE- syscc=&syscc syserr=&syserr sqlrc=&sqlrc";'; put 'putlog ''NOTE-'' / ''NOTE-'';'; put 'run;'; put '%let syscc=0;'; put '%let sqlrc=0;'; put '%end;'; put '/* now check if the record was successfully updated */'; put '%local success_check;'; put 'proc sql noprint;'; put 'select count(*) into: success_check from &ctl_ds'; put 'where LOCK_LIB ="&lib" and LOCK_DS="&ds"'; put 'and LOCK_PID="&sysjobid" and LOCK_STATUS_CD=''LOCKED'';'; put 'quit;'; put '%if &success_check=0 %then %do;'; put '%if &x < &loops %then %do;'; put '/* pause before next check */'; put 'data _null_;'; put 'putlog ''NOTE-'' / ''NOTE-'';'; put 'putlog "NOTE- &sysmacroname: table locked, waiting "@;'; put 'putlog "%sysfunc(sleep(&loop_secs)) seconds.. ";'; put 'putlog "NOTE- (iteration &x of &loops)";'; put 'putlog ''NOTE-'' / ''NOTE-'';'; put 'run;'; put '%end;'; put '%else %do;'; put '%let msg=Unable to lock &lib..&ds via &ctl_ds after &loops attempts.\n'; put 'Please ask your administrator to investigate!;'; put '%let abortme=1;'; put '%end;'; put '%end;'; put '%else %do;'; put 'data _null_;'; put 'putlog ''NOTE-'' / ''NOTE-'';'; put 'putlog "NOTE- &sysmacroname: Table &lib..&ds locked at "@;'; put 'putlog " %sysfunc(datetime(),datetime19.) (iteration &x)"@;'; put 'putlog ''NOTE-'' / ''NOTE-'';'; put 'run;'; put '%if &syscc>0 %then %do;'; put '%put setting syscc(&syscc) back to 0;'; put '%let syscc=0;'; put '%end;'; put '%let x=&loops; /* no more iterations needed */'; put '%end;'; put '%end;'; put '%end;'; put '%else %if &ACTION=UNLOCK %then %do;'; put '%local status cnt;'; put '%let cnt=0;'; put 'proc sql noprint;'; put 'select count(*) into: cnt from &ctl_ds where LOCK_LIB ="&lib" & LOCK_DS="&ds";'; put '%if &cnt=0 %then %do;'; put '%put %str(WAR)NING: &lib..&ds was not previously locked in &ctl_ds!;'; put '%end;'; put '%else %do;'; put 'select LOCK_STATUS_CD into: status from &ctl_ds'; put 'where LOCK_LIB ="&lib" and LOCK_DS="&ds";'; put 'quit;'; put '%if &syscc>0 %then %put syscc=&syscc sqlrc=&sqlrc;'; put '%if &status=LOCKED %then %do;'; put 'data _null_;'; put 'putlog "&sysmacroname: unlocking &lib..&ds:";'; put 'run;'; put 'proc sql;'; put 'update &ctl_ds'; put 'set LOCK_STATUS_CD=''UNLOCKED'''; put ', LOCK_END_DTTM="%sysfunc(datetime(),%mf_fmtdttm())"dt'; put ', LOCK_USER_NM="&user"'; put ', LOCK_PID="&sysjobid"'; put ', LOCK_REF="&ref"'; put 'where LOCK_LIB ="&lib" and LOCK_DS="&ds";'; put 'quit;'; put '%end;'; put '%else %if &status=UNLOCKED %then %do;'; put '%put %str(WAR)NING: &lib..&ds is already unlocked!;'; put '%end;'; put '%else %do;'; put '%put NOTE: Unrecognised STATUS_CD (&status) in &ctl_ds;'; put '%let abortme=1;'; put '%end;'; put '%end;'; put '%end;'; put '%else %do;'; put '%let msg=lock_anytable given unsupported action (&action);'; put '%let abortme=1;'; put '%end;'; put '/* catch errs - mp_abort must be called outside of a logic block */'; put '%mp_abort(iftrue=(&abortme=1),'; put 'msg=%superq(msg),'; put 'mac=&sysmacroname'; put ')'; put '%exit_macro:'; put 'data _null_;'; put 'put "&sysmacroname: Exit vars: action=&action lib=&lib ds=&ds";'; put 'put " syscc=&syscc sqlrc=&sqlrc syserr=&syserr";'; put 'run;'; put '%mend mp_lockanytable;'; put '%macro bitemporal_closeouts('; put 'tech_from=tx_from_dttm'; put ',tech_to = tx_to_dttm /* Technical TO datetime variable.'; put 'Req''d on BASE table only. */'; put ',base_lib=WORK /* Libref of the BASE table. */'; put ',base_dsn=BASETABLE /* Name of BASE table. */'; put ',append_lib=WORK /* Libref of the STAGING table. */'; put ',append_dsn=APPENDTABLE /* Name of STAGING table. */'; put ',PK= name sex /* Business key, space separated. */'; put '/* Should INCLUDE BUS_FROM field if relevant. */'; put ',NOW=DEFINE'; put ',FILTER= /* supply a filter to limit the update */'; put ',outdest= /* supply an unquoted filepath/filename.ext to get'; put 'a text file containing the update statements */'; put ',loadtype='; put ',loadtarget=YES /* if <> YES will return without changing anything */'; put ');'; put '%put ENTERING &sysmacroname;'; put '%local x var start;'; put '%let start=%sysfunc(datetime());'; put '%dc_assignlib(WRITE,&base_lib)'; put '%dc_assignlib(WRITE,&append_lib)'; put '%if &now=DEFINE %then %let now=&dc_dttmtfmt.;'; put '%put &=now;'; put '/**'; put '* perform basic checks'; put '*/'; put '/* do tables exist? */'; put '%if not %sysfunc(exist(&base_lib..&base_dsn)) %then %do;'; put '%mp_abort(msg=&base_lib..&base_dsn does not exist)'; put '%end;'; put '%else %if %sysfunc(exist(&append_lib..&append_dsn))=0'; put 'and %sysfunc(exist(&append_lib..&append_dsn,VIEW))=0 %then %do;'; put '%mp_abort(msg=&append_lib..&append_dsn does not exist)'; put '%end;'; put '/* do TX columns exist? */'; put '%if &loadtype ne UPDATE %then %do;'; put '%if not %mf_existvar(&base_lib..&base_dsn,&tech_from) %then %do;'; put '%mp_abort(msg=&tech_from does not exist on &base_lib..&base_dsn)'; put '%end;'; put '%else %if not %mf_existvar(&base_lib..&base_dsn,&tech_to) %then %do;'; put '%mp_abort(msg=&tech_to does not exist on &base_lib..&base_dsn)'; put '%end;'; put '%end;'; put '/* do PK columns exist? */'; put '%do x=1 %to %sysfunc(countw(&PK));'; put '%let var=%scan(&pk,&x,%str( ));'; put '%if not %mf_existvar(&base_lib..&base_dsn,&var) %then %do;'; put '%mp_abort(msg=&var does not exist on &base_lib..&base_dsn)'; put '%end;'; put '%else %if not %mf_existvar(&append_lib..&append_dsn,&var) %then %do;'; put '%mp_abort(msg=&var does not exist on &append_lib..&append_dsn)'; put '%end;'; put '%end;'; put '/* check uniqueness */'; put 'proc sort data=&append_lib..&append_dsn'; put 'out=___closeout1 noduprecs dupout=___closeout1a;'; put 'by &pk;'; put 'run;'; put '%if %mf_getattrn(___closeout1a,NLOBS)>0 %then'; put '%put NOTE: dups on (&PK) in (&append_lib..&append_dsn);'; put '/* is &NOW value within a tolerance? Should not allow renegade closeouts.. */'; put '%local gap;'; put '%let gap=0;'; put 'data _null_;'; put 'now=&now;'; put 'gap=intck(''HOURS'',now,datetime());'; put 'call symputx(''gap'',gap,''l'');'; put 'run;'; put '%mf_abort('; put 'iftrue=(&gap > 24),'; put 'msg=NOW variable (&now) is not within a 24hr tolerance'; put ')'; put '/* have any warnings / errs occurred thus far? If so, abort */'; put '%mf_abort('; put 'iftrue=(&syscc>0),'; put 'msg=Aborted due to SYSCC=&SYSCC status'; put ')'; put '/**'; put '* Create closeout statements. These are sent as individual SQL statements'; put '* to ensure pass-through utilisation. The update_cnt variable monitors'; put '* how many records were actually updated on the target table.'; put '*/'; put '%local update_cnt;'; put '%let update_cnt=0;'; put 'filename tmp temp;'; put 'data _null_;'; put 'set ___closeout1;'; put 'file tmp;'; put 'if _n_=1 then put ''proc sql noprint;'' ;'; put 'length string $32767.;'; put '%if &loadtype=UPDATE %then %do;'; put 'put "delete from &base_lib..&base_dsn where 1";'; put '%end;'; put '%else %do;'; put 'now=symget(''now'');'; put 'put "update &base_lib..&base_dsn set &tech_to= " now @;'; put '%if %mf_existvar(&base_lib..&base_dsn,PROCESSED_DTTM) %then %do;'; put 'put " ,PROCESSED_DTTM=" now @;'; put '%end;'; put 'put " where " now " lt &tech_to ";'; put '%end;'; put '%do x=1 %to %sysfunc(countw(&PK));'; put '%let var=%scan(&pk,&x,%str( ));'; put '%if %mf_getvartype(&base_lib..&base_dsn,&var)=C %then %do;'; put '/* use single quotes to avoid ampersand resolution in data */'; put 'string=" & &var=''"!!trim(prxchange("s/''/''''/",-1,&var))!!"''";'; put '%end;'; put '%else %do;'; put 'string=cats(" & &var=",&var);'; put '%end;'; put 'put string;'; put '%end;'; put 'put "&filter ;";'; put 'put ''%let update_cnt=%eval(&update_cnt+&sqlobs);%put update_cnt=&update_cnt;'';'; put 'run;'; put 'data _null_;'; put 'infile tmp;'; put 'input;'; put 'putlog _infile_;'; put 'run;'; put '%if &loadtarget ne YES %then %return;'; put '/* ensure we have a lock */'; put '%mp_lockanytable(LOCK,'; put 'lib=&base_lib,ds=&base_dsn'; put ',ref=bitemporal_closeouts'; put ',ctl_ds=&mpelib..mpe_lockanytable'; put ')'; put 'options source2;'; put '%inc tmp;'; put 'filename tmp clear;'; put '/**'; put '* Update audit tracker'; put '*/'; put '%local newobs; %let newobs=%mf_getattrn(work.___closeout1,NLOBS);'; put '%local user; %let user=%mf_getuser();'; put 'proc sql;'; put 'insert into &mpelib..mpe_dataloads'; put 'set libref=%upcase("&base_lib")'; put ',DSN=%upcase("&base_dsn")'; put ',ETLSOURCE="&append_lib..&append_dsn contained &newobs records"'; put ',LOADTYPE="CLOSEOUT"'; put ',DELETED_RECORDS=&update_cnt'; put ',NEW_RECORDS=0'; put ',DURATION=%sysfunc(datetime())-&start'; put ',USER_NM="&user"'; put ',PROCESSED_DTTM=&now;'; put 'quit;'; put '%mend bitemporal_closeouts;'; put '%macro mf_existds(libds'; put ')/*/STORE SOURCE*/;'; put '%if %sysfunc(exist(&libds)) ne 1 & %sysfunc(exist(&libds,VIEW)) ne 1 %then 0;'; put '%else 1;'; put '%mend mf_existds;'; put '%macro mf_getschema(libref'; put ')/*/STORE SOURCE*/;'; put '%local dsid vnum rc schema;'; put '/* in case the parameter is a libref.tablename, pull off just the libref */'; put '%let libref = %upcase(%scan(&libref, 1, %str(.)));'; put '%let dsid=%sysfunc(open(sashelp.vlibnam(where=('; put 'libname="%upcase(&libref)" and sysname=''Schema/Owner'''; put ')),i));'; put '%if (&dsid ^= 0) %then %do;'; put '%let vnum=%sysfunc(varnum(&dsid,SYSVALUE));'; put '%let rc=%sysfunc(fetch(&dsid));'; put '%let schema=%sysfunc(getvarc(&dsid,&vnum));'; put '%put &libref. schema is &schema.;'; put '%let rc= %sysfunc(close(&dsid));'; put '%end;'; put '&schema'; put '%mend mf_getschema;'; put '/** @endcond */'; put '%macro mf_getuniquename(prefix=MC);'; put '&prefix.%substr(%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32-%length(&prefix))'; put '%mend mf_getuniquename;'; put '%macro mf_getvarlist(libds'; put ',dlm=%str( )'; put ',quote=no'; put ',typefilter=A'; put ')/*/STORE SOURCE*/;'; put '/* declare local vars */'; put '%local outvar dsid nvars x rc dlm q var vtype;'; put '/* credit Rowland Hale - byte34 is double quote, 39 is single quote */'; put '%if %upcase("e)=DOUBLE %then %let q=%qsysfunc(byte(34));'; put '%else %if %upcase("e)=SINGLE %then %let q=%qsysfunc(byte(39));'; put '/* open dataset in macro */'; put '%let dsid=%sysfunc(open(&libds));'; put '%if &dsid %then %do;'; put '%let nvars=%sysfunc(attrn(&dsid,NVARS));'; put '%if &nvars>0 %then %do;'; put '/* add variables with supplied delimeter */'; put '%do x=1 %to &nvars;'; put '/* get variable type */'; put '%let vtype=%sysfunc(vartype(&dsid,&x));'; put '%if &vtype=&typefilter or &typefilter=A %then %do;'; put '%let var=&q.%sysfunc(varname(&dsid,&x))&q.;'; put '%if &var=&q&q %then %do;'; put '%put &sysmacroname: Empty column found in &libds!;'; put '%let var=&q. &q.;'; put '%end;'; put '%if %quote(&outvar)=%quote() %then %let outvar=&var;'; put '%else %let outvar=&outvar.&dlm.&var.;'; put '%end;'; put '%end;'; put '%end;'; put '%let rc=%sysfunc(close(&dsid));'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: Unable to open &libds (rc=&dsid);'; put '%put &sysmacroname: SYSMSG= %sysfunc(sysmsg());'; put '%let rc=%sysfunc(close(&dsid));'; put '%end;'; put '%do;%unquote(&outvar)%end;'; put '%mend mf_getvarlist;'; put '%macro mf_abort(mac=mf_abort.sas, msg=, iftrue=%str(1=1)'; put ')/des=''ungraceful abort'' /*STORE SOURCE*/;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%put NOTE: /// mf_abort macro executing //;'; put '%if %length(&mac)>0 %then %put NOTE- called by &mac;'; put '%put NOTE - &msg;'; put '%abort;'; put '%mend mf_abort;'; put '/** @endcond */'; put '%macro mf_verifymacvars('; put 'verifyVars /* list of macro variable NAMES */'; put ',makeUpcase=NO /* set to YES to make all the variable VALUES uppercase */'; put ',mAbort=SOFT'; put ')/*/STORE SOURCE*/;'; put '%local verifyIterator verifyVar abortmsg;'; put '%do verifyIterator=1 %to %sysfunc(countw(&verifyVars,%str( )));'; put '%let verifyVar=%qscan(&verifyVars,&verifyIterator,%str( ));'; put '%if not %symexist(&verifyvar) %then %do;'; put '%let abortmsg= Variable &verifyVar is MISSING;'; put '%goto exit_err;'; put '%end;'; put '%if %length(%trim(&&&verifyVar))=0 %then %do;'; put '%let abortmsg= Variable &verifyVar is EMPTY;'; put '%goto exit_err;'; put '%end;'; put '%if &makeupcase=YES %then %do;'; put '%let &verifyVar=%upcase(&&&verifyvar);'; put '%end;'; put '%end;'; put '%goto exit_success;'; put '%exit_err:'; put '%put &abortmsg;'; put '%mf_abort(iftrue=(&mabort ne SOFT),'; put 'mac=mf_verifymacvars,'; put 'msg=%str(&abortmsg)'; put ')'; put '0'; put '%return;'; put '%exit_success:'; put '1'; put '%mend mf_verifymacvars;'; put '%macro mf_wordsInStr1ButNotStr2('; put 'Str1= /* string containing words to extract */'; put ',Str2= /* used to compare with the extract string */'; put ')/*/STORE SOURCE*/;'; put '%local count_base count_extr i i2 extr_word base_word match outvar;'; put '%if %length(&str1)=0 or %length(&str2)=0 %then %do;'; put '%put base string (str1)= &str1;'; put '%put compare string (str2) = &str2;'; put '%return;'; put '%end;'; put '%let count_base=%sysfunc(countw(&Str2));'; put '%let count_extr=%sysfunc(countw(&Str1));'; put '%do i=1 %to &count_extr;'; put '%let extr_word=%scan(&Str1,&i,%str( ));'; put '%let match=0;'; put '%do i2=1 %to &count_base;'; put '%let base_word=%scan(&Str2,&i2,%str( ));'; put '%if &extr_word=&base_word %then %let match=1;'; put '%end;'; put '%if &match=0 %then %let outvar=&outvar &extr_word;'; put '%end;'; put '&outvar'; put '%mend mf_wordsInStr1ButNotStr2;'; put '%macro mf_isblank(param'; put ')/*/STORE SOURCE*/;'; put '%sysevalf(%superq(param)=,boolean)'; put '%mend mf_isblank;'; put '%macro mp_dropmembers('; put 'list /* space separated list of datasets / views */'; put ',libref=WORK /* can only drop from a single library at a time */'; put ',iftrue=%str(1=1)'; put ')/*/STORE SOURCE*/;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%if %mf_isblank(&list) %then %do;'; put '%put NOTE: nothing to drop!;'; put '%return;'; put '%end;'; put 'proc datasets lib=&libref nolist;'; put 'delete &list;'; put 'delete &list /mtype=view;'; put 'run;'; put '%mend mp_dropmembers;'; put '%macro mf_getquotedstr(IN_STR'; put ',DLM=%str(,)'; put ',QUOTE=S'; put ',indlm=%str( )'; put ')/*/STORE SOURCE*/;'; put '/* credit Rowland Hale - byte34 is double quote, 39 is single quote */'; put '%if "e=S %then %let quote=%qsysfunc(byte(39));'; put '%else %if "e=D %then %let quote=%qsysfunc(byte(34));'; put '%else %if "e=N %then %let quote=;'; put '%local i item buffer;'; put '%let i=1;'; put '%do %while (%qscan(&IN_STR,&i,%str(&indlm)) ne %str() ) ;'; put '%let item=%qscan(&IN_STR,&i,%str(&indlm));'; put '%if %bquote("E) ne %then %let item="E%qtrim(&item)"E;'; put '%else %let item=%qtrim(&item);'; put '%if (&i = 1) %then %let buffer =%qtrim(&item);'; put '%else %let buffer =&buffer&DLM%qtrim(&item);'; put '%let i = %eval(&i+1);'; put '%end;'; put '%let buffer=%sysfunc(coalescec(%qtrim(&buffer),"E"E));'; put '&buffer'; put '%mend mf_getquotedstr;'; put '%macro mf_nobs(libds'; put ')/*/STORE SOURCE*/;'; put '%mf_getattrn(&libds,NLOBS)'; put '%mend mf_nobs;'; put '%macro mp_retainedkey('; put 'base_lib=WORK'; put ',base_dsn=BASETABLE'; put ',append_lib=WORK'; put ',append_dsn=APPENDTABLE'; put ',retained_key=DEFAULT_RK'; put ',business_key= PK1 PK2'; put ',check_uniqueness=NO'; put ',maxkeytable=0'; put ',locktable=0'; put ',outds=WORK.APPEND'; put ',filter_str='; put ');'; put '%put &sysmacroname entry vars:;'; put '%put _local_;'; put '%local base_libds app_libds key_field check maxkey idx_pk newkey_cnt iserr'; put 'msg x tempds1 tempds2 comma_pk appnobs checknobs dropvar tempvar idx_val;'; put '%let base_libds=%upcase(&base_lib..&base_dsn);'; put '%let app_libds=%upcase(&append_lib..&append_dsn);'; put '%let tempds1=%mf_getuniquename();'; put '%let tempds2=%mf_getuniquename();'; put '%let comma_pk=%mf_getquotedstr(in_str=%str(&business_key),dlm=%str(,),quote=);'; put '%let outds=%sysfunc(ifc(%index(&outds,.)=0,work.&outds,&outds));'; put '/* validation checks */'; put '%let iserr=0;'; put '%if &syscc>0 %then %do;'; put '%let iserr=1;'; put '%let msg=%str(SYSCC=&syscc on macro entry);'; put '%end;'; put '%else %if %sysfunc(exist(&base_libds))=0 %then %do;'; put '%let iserr=1;'; put '%let msg=%str(Base LIBDS (&base_libds) expected but NOT FOUND);'; put '%end;'; put '%else %if %sysfunc(exist(&app_libds))=0 %then %do;'; put '%let iserr=1;'; put '%let msg=%str(Append LIBDS (&app_libds) expected but NOT FOUND);'; put '%end;'; put '%else %if &maxkeytable ne 0 and %sysfunc(exist(&maxkeytable))=0 %then %do;'; put '%let iserr=1;'; put '%let msg=%str(Maxkeytable (&maxkeytable) expected but NOT FOUND);'; put '%end;'; put '%else %if &maxkeytable ne 0 and %sysfunc(exist(&locktable))=0 %then %do;'; put '%let iserr=1;'; put '%let msg=%str(Locktable (&locktable) expected but NOT FOUND);'; put '%end;'; put '%else %if %length(&business_key)=0 %then %do;'; put '%let iserr=1;'; put '%let msg=%str(Business key (&business_key) expected but NOT FOUND);'; put '%end;'; put '%do x=1 %to %sysfunc(countw(&business_key));'; put '/* check business key values exist */'; put '%let key_field=%scan(&business_key,&x,%str( ));'; put '%if not %mf_existvar(&app_libds,&key_field) %then %do;'; put '%let iserr=1;'; put '%let msg=Business key (&key_field) not found on &app_libds!;'; put '%goto err;'; put '%end;'; put '%else %if not %mf_existvar(&base_libds,&key_field) %then %do;'; put '%let iserr=1;'; put '%let msg=Business key (&key_field) not found on &base_libds!;'; put '%goto err;'; put '%end;'; put '%end;'; put '%err:'; put '%if &iserr=1 %then %do;'; put '/* err case so first perform an unlock of the base table before exiting */'; put '%mp_lockanytable('; put 'UNLOCK,lib=&base_lib,ds=&base_dsn,ref=%superq(msg),ctl_ds=&locktable'; put ')'; put '%end;'; put '%mp_abort(iftrue=(&iserr=1),mac=mp_retainedkey,msg=%superq(msg))'; put 'proc sql noprint;'; put 'select sum(max(&retained_key),0) into: maxkey from &base_libds;'; put '/**'; put '* get base table RK and bus field values for lookup'; put '*/'; put 'proc sql noprint;'; put 'create table &tempds1 as'; put 'select distinct &comma_pk,&retained_key'; put 'from &base_libds &filter_str'; put 'order by &comma_pk,&retained_key;'; put '%if &check_uniqueness=YES %then %do;'; put 'select count(*) into:checknobs'; put 'from (select distinct &comma_pk from &app_libds);'; put 'select count(*) into: appnobs from &app_libds; /* might be view */'; put '%if &checknobs ne &appnobs %then %do;'; put '%let msg=Source table &app_libds is not unique on (&business_key);'; put '%let iserr=1;'; put '%end;'; put '%end;'; put '%if &iserr=1 %then %do;'; put '/* err case so first perform an unlock of the base table before exiting */'; put '%mp_lockanytable('; put 'UNLOCK,lib=&base_lib,ds=&base_dsn,ref=%superq(msg),ctl_ds=&locktable'; put ')'; put '%end;'; put '%mp_abort(iftrue= (&iserr=1),mac=mp_retainedkey,msg=%superq(msg))'; put '%if %mf_existvar(&app_libds,&retained_key)'; put '%then %let dropvar=(drop=&retained_key);'; put '/* prepare interim table with retained key populated for matching keys */'; put 'proc sql noprint;'; put 'create table &tempds2 as'; put 'select b.&retained_key, a.*'; put 'from &app_libds &dropvar a'; put 'left join &tempds1 b'; put 'on 1'; put '%do idx_pk=1 %to %sysfunc(countw(&business_key));'; put '%let idx_val=%scan(&business_key,&idx_pk);'; put 'and a.&idx_val=b.&idx_val'; put '%end;'; put 'order by &retained_key;'; put '/* identify the number of entries without retained keys (new records) */'; put 'select count(*) into: newkey_cnt'; put 'from &tempds2'; put 'where missing(&retained_key);'; put 'quit;'; put '/**'; put '* Update maxkey table if link provided'; put '*/'; put '%if &maxkeytable ne 0 %then %do;'; put 'proc sql noprint;'; put 'select count(*) into: check from &maxkeytable'; put 'where upcase(keytable)="&base_libds";'; put '%mp_lockanytable(LOCK'; put ',lib=%scan(&maxkeytable,1,.)'; put ',ds=%scan(&maxkeytable,2,.)'; put ',ref=Updating maxkeyvalues with mp_retainedkey'; put ',ctl_ds=&locktable'; put ')'; put 'proc sql;'; put '%if &check=0 %then %do;'; put 'insert into &maxkeytable'; put 'set keytable="&base_libds"'; put ',keycolumn="&retained_key"'; put ',max_key=%eval(&maxkey+&newkey_cnt)'; put ',processed_dttm="%sysfunc(datetime(),%mf_fmtdttm())"dt;'; put '%end;'; put '%else %do;'; put 'update &maxkeytable'; put 'set max_key=%eval(&maxkey+&newkey_cnt)'; put ',processed_dttm="%sysfunc(datetime(),%mf_fmtdttm())"dt'; put 'where keytable="&base_libds";'; put '%end;'; put '%mp_lockanytable(UNLOCK'; put ',lib=%scan(&maxkeytable,1,.)'; put ',ds=%scan(&maxkeytable,2,.)'; put ',ref=Updating maxkeyvalues with maxkey=%eval(&maxkey+&newkey_cnt)'; put ',ctl_ds=&locktable'; put ')'; put '%end;'; put '/* fill in the missing retained key values */'; put '%let tempvar=%mf_getuniquename();'; put 'data &outds(drop=&tempvar);'; put 'retain &tempvar %eval(&maxkey+1);'; put 'set &tempds2;'; put 'if &retained_key =. then &retained_key=&tempvar;'; put '&tempvar=&tempvar+1;'; put 'run;'; put '%mend mp_retainedkey;'; put '/** @cond */'; put '%macro mp_storediffs(libds'; put ',origds'; put ',key'; put ',delds=0'; put ',appds=0'; put ',modds=0'; put ',outds=work.mp_storediffs'; put ',loadref=0'; put ',processed_dttm=0'; put ',mdebug=0'; put ')/*/STORE SOURCE*/;'; put '%local dbg;'; put '%if &mdebug=1 %then %do;'; put '%put &sysmacroname entry vars:;'; put '%put _local_;'; put '%end;'; put '%else %let dbg=*;'; put '/* set up unique and temporary vars */'; put '%local ds1 ds2 ds3 ds4 hashkey inds_auto inds_keep dslist vlist;'; put '%let ds1=%upcase(work.%mf_getuniquename(prefix=mpsd_ds1));'; put '%let ds2=%upcase(work.%mf_getuniquename(prefix=mpsd_ds2));'; put '%let ds3=%upcase(work.%mf_getuniquename(prefix=mpsd_ds3));'; put '%let ds4=%upcase(work.%mf_getuniquename(prefix=mpsd_ds4));'; put '%let hashkey=%upcase(%mf_getuniquename(prefix=mpsd_hashkey));'; put '%let inds_auto=%upcase(%mf_getuniquename(prefix=mpsd_inds_auto));'; put '%let inds_keep=%upcase(%mf_getuniquename(prefix=mpsd_inds_keep));'; put '%let dslist=&origds;'; put '%if &delds ne 0 %then %do;'; put '%let delds=%upcase(&delds);'; put '%if %scan(&delds,-1,.)=&delds %then %let delds=WORK.&delds;'; put '%let dslist=&dslist &delds;'; put '%end;'; put '%if &appds ne 0 %then %do;'; put '%let appds=%upcase(&appds);'; put '%if %scan(&appds,-1,.)=&appds %then %let appds=WORK.&appds;'; put '%let dslist=&dslist &appds;'; put '%end;'; put '%if &modds ne 0 %then %do;'; put '%let modds=%upcase(&modds);'; put '%if %scan(&modds,-1,.)=&modds %then %let modds=WORK.&modds;'; put '%let dslist=&dslist &modds;'; put '%end;'; put '%let origds=%upcase(&origds);'; put '%if %scan(&origds,-1,.)=&origds %then %let origds=WORK.&origds;'; put '%let key=%upcase(&key);'; put '/* hash the key and append all the tables (marking the source) */'; put 'data &ds1;'; put 'set &dslist indsname=&inds_auto;'; put '&hashkey=put(md5(catx(''|'',%mf_getquotedstr(&key,quote=N))),$hex32.);'; put '&inds_keep=upcase(&inds_auto);'; put 'proc sort;'; put 'by &inds_keep &hashkey;'; put 'run;'; put '/* transpose numeric & char vars */'; put 'proc transpose data=&ds1'; put 'out=&ds2(rename=(&hashkey=key_hash _name_=tgtvar_nm col1=newval_num));'; put 'by &inds_keep &hashkey;'; put 'var _numeric_;'; put 'run;'; put 'proc transpose data=&ds1'; put 'out=&ds3('; put 'rename=(&hashkey=key_hash _name_=tgtvar_nm col1=newval_char)'; put 'where=(tgtvar_nm not in ("&hashkey","&inds_keep"))'; put ');'; put 'by &inds_keep &hashkey;'; put 'var _character_;'; put 'run;'; put '%if %index(&libds,-)>0 and %scan(&libds,2,-)=FC %then %do;'; put '/* this is a format catalog - cannot query cols directly */'; put '%let vlist="TYPE","FMTNAME","FMTROW","START","END","LABEL","MIN","MAX"'; put ',"DEFAULT","LENGTH","FUZZ","PREFIX","MULT","FILL","NOEDIT","SEXCL"'; put ',"EEXCL","HLO","DECSEP","DIG3SEP","DATATYPE","LANGUAGE";'; put '%end;'; put '%else %let vlist=%mf_getvarlist(&libds,dlm=%str(,),quote=DOUBLE);'; put 'data &ds4;'; put 'length &inds_keep $41 tgtvar_nm $32 _label_ $256;'; put 'if _n_=1 then call missing(_label_);'; put 'drop _label_;'; put 'set &ds2 &ds3 indsname=&inds_auto;'; put 'tgtvar_nm=upcase(tgtvar_nm);'; put 'if tgtvar_nm in (%upcase(&vlist));'; put 'if upcase(&inds_auto)="&ds2" then tgtvar_type=''N'';'; put 'else if upcase(&inds_auto)="&ds3" then tgtvar_type=''C'';'; put 'else do;'; put 'putlog ''ERR'' +(-1) "OR: unidentified vartype input!" &inds_auto;'; put 'call symputx(''syscc'',98);'; put 'end;'; put 'if &inds_keep="&appds" then move_type=''A'';'; put 'else if &inds_keep="&delds" then move_type=''D'';'; put 'else if &inds_keep="&modds" then move_type=''M'';'; put 'else if &inds_keep="&origds" then move_type=''O'';'; put 'else do;'; put 'putlog ''ERR'' +(-1) "OR: unidentified movetype input!" &inds_keep;'; put 'call symputx(''syscc'',99);'; put 'end;'; put 'tgtvar_nm=upcase(tgtvar_nm);'; put 'if tgtvar_nm in (%mf_getquotedstr(&key)) then is_pk=1;'; put 'else is_pk=0;'; put 'drop &inds_keep;'; put 'run;'; put '%if "&loadref"="0" %then %let loadref=%sysfunc(uuidgen());'; put '%if &processed_dttm=0 %then %let processed_dttm=%sysfunc(datetime());'; put '%let libds=%upcase(&libds);'; put '/* join orig vals for modified & deleted */'; put 'proc sql;'; put 'create table &outds as'; put 'select "&loadref" as load_ref length=36'; put ',&processed_dttm as processed_dttm format=E8601DT26.6'; put ',"%scan(&libds,1,.)" as libref length=8'; put ',"%scan(&libds,2,.)" as dsn length=32'; put ',b.key_hash length=32'; put ',b.move_type length=1'; put ',b.tgtvar_nm length=32'; put ',b.is_pk'; put ',case when b.move_type ne ''M'' then -1'; put 'when a.newval_num=b.newval_num and a.newval_char=b.newval_char then 0'; put 'else 1'; put 'end as is_diff'; put ',b.tgtvar_type length=1'; put ',case when b.move_type=''D'' then b.newval_num'; put 'else a.newval_num'; put 'end as oldval_num format=best32.'; put ',case when b.move_type=''D'' then .'; put 'else b.newval_num'; put 'end as newval_num format=best32.'; put ',case when b.move_type=''D'' then b.newval_char'; put 'else a.newval_char'; put 'end as oldval_char length=32765'; put ',case when b.move_type=''D'' then '''''; put 'else b.newval_char'; put 'end as newval_char length=32765'; put 'from &ds4(where=(move_type=''O'')) as a'; put 'right join &ds4(where=(move_type ne ''O'')) as b'; put 'on a.tgtvar_nm=b.tgtvar_nm'; put 'and a.key_hash=b.key_hash'; put 'order by move_type, key_hash,is_pk desc, tgtvar_nm;'; put '%if &mdebug=0 %then %do;'; put 'proc sql;'; put 'drop table &ds1, &ds2, &ds3, &ds4;'; put '%end;'; put '%mend mp_storediffs;'; put '/** @endcond */'; put '%macro bitemporal_dataloader('; put 'bus_from= /* Business FROM datetime variable. Req''d on'; put 'STAGING & BASE tables.*/'; put ',bus_to = /* Business TO datetime variable. Req''d on'; put 'STAGING & BASE tables. */'; put ',bus_from_override= /* Provide a hard coded BUS_FROM datetime value.*/'; put ',bus_to_override= /* provide a hard coded BUS_TO datetime value */'; put ',tech_from= /* Technical FROM datetime variable. Req''d on'; put 'BASE table only. */'; put ',tech_to = /* Technical TO datetime variable. Req''d on BASE'; put 'table only. */'; put ',processed= 0'; put ',base_lib=WORK /* Libref of the BASE table. */'; put ',base_dsn=BASETABLE /* Name of BASE table. */'; put ',append_lib=WORK /* Libref of the STAGING table. */'; put ',append_dsn=APPENDTABLE'; put ',high_date=''01JAN5999:00:00:00''dt /* High date to close out records */'; put ',PK= name sex'; put ',RK_UNDERLYING='; put ',KEEPVARS= /* Provides option for removing unwanted vars from append table */'; put ',RK_UPDATE_MAXKEYTABLE=NO /* If switching (or mix matching) with regular'; put 'SCD2 loader then set this switch to YES to'; put 'ensure the MAXKEYTABLE is updated with the'; put 'current maximum RK value for the target table'; put '*/'; put ',CHECK_UNIQUENESS=YES /* Perform a check of the APPEND table to ensure it is'; put 'unique on its business key */'; put ',ETLSOURCE=demo /* supply a value ($50.) to show as ETLSOURCE in'; put '&dclib..DATALOADS */'; put ',LOADTYPE=BITEMPORAL'; put ',RK_MAXKEYTABLE= mpe_maxkeyvalues'; put ',LOG=1 /* Switch to 0 to prevent records being added to'; put '&mpelib..mpe_DATALOADS (ie when testing)*/'; put ',DELETE_COL= _____DELETE__THIS__RECORD_____'; put '/* If this variable is found in the append dataset'; put 'then records are closed out (or deleted) in the'; put 'append table where that variable= "Yes" */'; put ',LOADTARGET=YES /* set to anything but uppercase YES to switch off'; put 'target table load and generate temp tables only */'; put ',CLOSE_VARS='; put '/*a problem with regular SCD2 or TXTEMPORAL loads is that there is'; put 'no facility to close out removed records (all records are'; put 'assumed new or changed). But how does one determine which'; put 'records are removed? Short of loading the entire table'; put 'each time? This parameter allows a set of variables'; put '(this should be a subset of the PK) to be declared, and'; put 'the macro will determine which records in the base table'; put 'need to be closed out ahead of the load.'; put 'For instance, given the following:'; put 'Base Table Staging Table'; put 'DATE ENTITY AMOUNT DATE ENTITY AMOUNT'; put 'JAN ACME4 66 JAN ACME4 66'; put 'FEB ACME4 99 FEB ACME4 99'; put 'FEB ACME1 22'; put 'By supplying DATE in CLOSE_VARS and DATE ENTITY as the PK,'; put 'the "FEB PAG 22" record would get closed out.'; put '*/'; put ',config_table=&dclib..MPE_CONFIG'; put ',dclib=&dc_libref'; put ',outds_del=work.outds_del'; put ',outds_add=work.outds_add'; put ',outds_mod=work.outds_mod'; put ',outds_audit=0'; put ');'; put '/* when changing this macro, update the version num here */'; put '%local ver;'; put '%let ver=32;'; put '%put &sysmacroname entry vars:;'; put '%put _local_;'; put '%dc_assignlib(WRITE,&base_lib) /* may not already be assigned */'; put '/* return straight away if nothing to load */'; put '%let nobs= %mf_getattrn(&append_lib..&append_dsn,NLOBS);'; put '%if &nobs=-1 %then %do;'; put 'proc sql noprint; select count(*) into: nobs from &append_lib..&append_dsn;'; put '%end;'; put '%if &nobs=0 %then %do;'; put '%put NOTE:; %put NOTE-;%put NOTE-;%put NOTE-;'; put '%put NOTE- Base dataset &append_lib..&append_dsn is empty. Nothing to upload!;'; put '%put NOTE-;%put NOTE-;%put NOTE-;'; put '%return;'; put '%end;'; put '/* hard exit if err condition exists */'; put '%mp_abort(iftrue= (&syscc > 0)'; put ',mac=bitemporal_dataloader'; put ',msg=%str(Bitemporal transform / job aborted due to SYSCC=&SYSCC status;)'; put ')'; put '%local engine_type;'; put '%let engine_type=%mf_getengine(&base_lib);'; put '%if (&engine_type=REDSHIFT or &engine_type=POSTGRES) and %length(&CLOSE_VARS)>0'; put '%then %do;'; put '%put NOTE:; %put NOTE-;%put NOTE-;%put NOTE-;'; put '%put NOTE- CLOSE_VARS functionality not yet supported in &engine_type;'; put '%put NOTE-;%put NOTE-;%put NOTE-;'; put '%return;'; put '%end;'; put '/**'; put '* The metadata functions (eg mf_existvar) will fail if the base table has a'; put '* SAS lock. So, make a snapshot of the base table for further use.'; put '* Also, make output tables (regardless).'; put '*/'; put '%local basecopy;'; put '%let basecopy=%mf_getuniquename(prefix=basecopy);'; put 'data &basecopy &outds_mod &outds_add &outds_del;'; put 'set &base_lib..&base_dsn;'; put 'stop;'; put 'run;'; put '%mp_abort(iftrue= (&syscc > 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc after base table copy - aborting due to table lock)'; put ')'; put '%local cols idx_pk md5_col ;'; put '%let md5_col=___TMP___md5;'; put '%let check_uniqueness=%upcase(&check_uniqueness);'; put '%let RK_UPDATE_MAXKEYTABLE=%upcase(&RK_UPDATE_MAXKEYTABLE);'; put '%let high_date=%unquote(&high_date);'; put '%let loadtype=%upcase(&loadtype);'; put '/* ensure irrelevant variables are cleared */'; put '%if &loadtype=BUSTEMPORAL %then %do;'; put '%let tech_from=;'; put '%let tech_to=;'; put '%end;'; put '%else %if &loadtype=TXTEMPORAL or &loadtype=UPDATE %then %do;'; put '%let bus_from=;'; put '%let bus_to=;'; put '%end;'; put '/* ensure relevant variables are supplied */'; put '%mp_abort(iftrue=(&loadtype=BITEMPORAL & %mf_verifymacvars(bus_from bus_to)=0)'; put ',mac=bitemporal_dataloader'; put ',msg=%str(Missing BUS_FROM / BUS_TO)'; put ')'; put '%mp_abort(iftrue=(&loadtype=TXTEMPORAL & %mf_verifymacvars(tech_from tech_to)=0)'; put ',mac=bitemporal_dataloader'; put ',msg=%str(Missing TECH_FROM / TECH_TO)'; put ')'; put '/**'; put '* drop any tables (may be defined as views or vice versa preventing overwrite)'; put '*/'; put '%mp_dropmembers(append bitemp0_append bitemp_cols)'; put '/* SQL Server requires its own time values */'; put '/* 9.2 will only give picture format down to seconds. 9.3 allows'; put 'milliseconds by using lower S and defining the decimal in the format name..*/'; put 'PROC FORMAT;'; put 'picture MyMSdt other=''%0Y-%0m-%0dT%0H:%0M:%0S'' (datatype=datetime);'; put 'RUN;'; put '%local dbnow;'; put '%let dbnow="%sysfunc(datetime(),%mf_fmtdttm())"dt;'; put 'data _null_;'; put '/* convert space separated macvar to comma separated for SQL processing */'; put 'call symputx(''PK_COMMA'',tranwrd(compbl("&pk"),'' '','',''),''L'');'; put 'call symputx(''PK_CNT'',countw("&pk",'' ''),''L'');'; put 'now=&dbnow;'; put 'call symputx(''NOW'',now,''L'');'; put 'call symputx(''SQLNOW'',cats("''",put(now,MyMSdt.),"''"),''L'');'; put 'length etlsource $100;'; put 'etlsource=subpad(symget(''etlsource''),1,100);'; put 'call symputx(''etlsource'',etlsource,''l'');'; put 'run;'; put '/**'; put '* Even if no PROCESSED var provided, assume that any variable named'; put '* PROCESSED_DTTM should be updated'; put '*/'; put '%if &processed=0 %then %do;'; put '%if %mf_existvar(&basecopy,PROCESSED_DTTM)'; put '%then %let processed=PROCESSED_DTTM;'; put '%else %let processed=;'; put '%end;'; put '/* extract colnames for md5 creation / change tracking */'; put 'proc contents noprint data=&base_lib..&base_dsn'; put 'out=work.bitemp_cols (keep=name type length varnum format:);'; put 'run;'; put 'proc sql noprint;'; put 'select name into: cols separated by '','''; put 'from work.bitemp_cols'; put 'where upcase(name) not in'; put '(%upcase("&bus_from","&bus_to"'; put ',"&tech_from","&tech_to"'; put ',"&processed","&delete_col")) ;'; put 'select case when type in (2,6) then cats(''put(md5(trim('',name,'')),$hex32.)'')'; put '/* multiply by 1 to strip precision errors (eg 0 != 0) */'; put '/* but ONLY if not missing, else will lose any special missing values */'; put 'else cats(''put(md5(trim(put(ifn(missing('''; put ',name,''),'',name,'','',name,''*1),binary64.))),$hex32.)'') end'; put 'into: stripcols separated by ''||'''; put 'from work.bitemp_cols'; put 'where upcase(name) not in'; put '(%upcase("&bus_from","&bus_to"'; put ',"&tech_from","&tech_to"'; put ',"&processed","&delete_col")) ;'; put '/* set default formats*/'; put '%let bus_from_fmt = datetime19.;'; put '%let bus_to_fmt = datetime19.;'; put '%let processed_fmt = datetime19.;'; put '%let tech_from_fmt = format=datetime19.;'; put '%let tech_to_fmt = format=datetime19.;'; put '%put &=stripcols;'; put '%put &=pk;'; put 'data _null_;'; put 'set work.bitemp_cols;'; put 'if type=2 or type=6 then do;'; put 'length fmt $49.;'; put 'if format='''' then fmt=cats(''$'',length,''.'');'; put 'else fmt=cats(format,formatl,''.'');'; put 'end;'; put 'else do;'; put 'if format='''' then fmt=cats(length,''.'');'; put 'else fmt=cats(format,formatl,''.'',formatd);'; put 'end;'; put 'if upcase(name)="%upcase(&bus_from)" then'; put 'call symputx(''bus_from_fmt'',fmt,''L'');'; put 'else if upcase(name)="%upcase(&bus_to)" then'; put 'call symputx(''bus_to_fmt'',fmt,''L'');'; put 'else if upcase(name)="%upcase(&tech_from)" then'; put 'call symputx(''tech_from_fmt'',"format="!!fmt,''L'');'; put 'else if upcase(name)="%upcase(&tech_to)" then'; put 'call symputx(''tech_to_fmt'',"format="!!fmt,''L'');'; put 'else if upcase(name)="%upcase(&processed)" then'; put 'call symputx(''processed_fmt'',fmt,''L'');'; put 'run;'; put '%if %index(%quote(&cols),___TMP___) %then %do;'; put '%let msg=%str(Table contains a variable name containing "___TMP___".%trim('; put ') This may conflict with temp variable generation!!);'; put '%mp_abort(msg=&msg,mac=bitemporal_dataloader);'; put '%let syscc=5;'; put '%return;'; put '%end;'; put '/* if transaction dates appear on the APPEND table, need to remove them */'; put '%local drop_tx_dates /* used in append table */'; put 'drop_tx_dates_noobs /* used to take the base table structure */;'; put '%if %mf_existvar(&append_lib..&append_dsn, &tech_from)'; put '%then %let drop_tx_dates=&tech_from;'; put '%if %mf_existvar(&append_lib..&append_dsn, &tech_to)'; put '%then %let drop_tx_dates=&drop_tx_dates &tech_to;'; put '%if %length(%trim(&drop_tx_dates))>0'; put '%then %let drop_tx_dates=(drop=&drop_tx_dates);'; put '%if %mf_existvar(&basecopy, &tech_from)'; put '%then %let drop_tx_dates_noobs=&tech_from;'; put '%if %mf_existvar(&basecopy, &tech_to)'; put '%then %let drop_tx_dates_noobs=&drop_tx_dates_noobs &tech_to;'; put '%if %length(%trim(&drop_tx_dates_noobs))>0'; put '%then %let drop_tx_dates_noobs=(drop=&drop_tx_dates_noobs obs=0);'; put '%else %let drop_tx_dates_noobs=(obs=0);'; put '/**'; put '* Lock the table. This is necessary as we are doing a two part update (first'; put '* closing records then appending new records). It is theoretically possible'; put '* that an upload may occur whilst preparing the staging tables. And the'; put '* staging tables are about to be prepared..'; put '*/'; put '%if &LOADTARGET = YES %then %do;'; put '%put locking &base_lib..&base_dsn;'; put '%mp_lockanytable(LOCK,'; put 'lib=&base_lib,ds=&base_dsn,ref=&ETLSOURCE,ctl_ds=&dclib..mpe_lockanytable'; put ')'; put '%if "&outds_audit" ne "0" %then %do;'; put '%put locking &outds_audit;'; put '%mp_lockanytable(LOCK'; put ',lib=%scan(&outds_audit,1,.)'; put ',ds=%scan(&outds_audit,2,.)'; put ',ref=&ETLSOURCE'; put ',ctl_ds=&dclib..mpe_lockanytable'; put ')'; put '%end;'; put '%end;'; put '%else %do;'; put '/* not an actual load, so avoid updating the max key table in next step. */'; put '%let rk_update_maxkeytable=NO;'; put '%end;'; put '%if %length(&RK_UNDERLYING)>0 %then %do;'; put '%mp_retainedkey('; put 'base_lib=&base_lib'; put ',base_dsn=&base_dsn'; put ',append_lib=&append_lib'; put ',append_dsn=&append_dsn'; put ',retained_key=&pk'; put ',business_key=&rk_underlying'; put ',check_uniqueness=&CHECK_UNIQUENESS'; put ',outds=work.append'; put '%if &rk_update_maxkeytable=NO %then %do;'; put ',maxkeytable=0'; put '%end;'; put '%else %do;'; put ',maxkeytable=&dclib..&RK_MAXKEYTABLE'; put '%end;'; put ',locktable=&dclib..mpe_lockanytable'; put '%if &loadtype=BITEMPORAL or &loadtype=TXTEMPORAL %then %do;'; put ',filter_str=%str( (where=( &now < &tech_to)) )'; put '%end;'; put ')'; put '%end;'; put '%else %do;'; put 'proc sql;'; put 'create view work.append as select * from &append_lib..&append_dsn;'; put '%end;'; put '/**'; put '* generate md5 for append table'; put '*/'; put '/* it is possible the source dataset has additional (unwanted) columns.'; put 'Drop if specified; */'; put '%if %length(&keepvars)>0 %then %do;'; put '/* remove tech dates from keepvars as they are generated later */'; put '%let keepvars=%sysfunc(tranwrd(%str( &keepvars ),%str( &tech_from ),%str( )));'; put '%let keepvars=%sysfunc(tranwrd(%str( &keepvars ),%str( &tech_to ),%str( )));'; put '%let keepvars=(keep=&keepvars &bus_from &bus_to &processed &md5_col);'; put '%end;'; put '/* CAS varchar types cause append issues here, so perform autoconvert'; put 'by creating empty local table first */'; put 'data;'; put 'set &base_lib..&base_dsn &drop_tx_dates_noobs;'; put 'run;'; put '%local emptybasetable; %let emptybasetable=&syslast;'; put 'data work.bitemp0_append &keepvars &outds_del(drop=&md5_col )'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5" %then %do;'; put '/nonote2err'; put '%end;'; put ';'; put '/* apply formats for bitemporal vars but not tx dates which are added later */'; put '%if %length(&keepvars)>0 and &loadtype=BITEMPORAL %then %do;'; put 'format &bus_from &bus_from_fmt;'; put 'format &bus_to &bus_to_fmt;'; put '%end;'; put 'set &emptybasetable /* base table reqd in case append has fewer cols */'; put 'work.append &drop_tx_dates;'; put '%if %length(%str(&bus_from_override))>0 %then %do;'; put '&bus_from= %unquote(&bus_from_override) ;'; put '%end;'; put '%if %length(%str(&bus_to_override))>0 %then %do;'; put '&bus_to= %unquote(&bus_to_override) ;'; put '%end;'; put 'length &md5_col $32;'; put '&md5_col=put(md5(&stripcols),hex32.);'; put '%if %length(&processed)>0 %then %do;'; put 'format &processed &processed_fmt;'; put '&processed=&now;'; put '%end;'; put '/**'; put '* If a delete column exists then create the delete dataset'; put '*/'; put '%if %mf_existvar(&append_lib..&append_dsn, &delete_col) %then %do;'; put 'drop &delete_col;'; put 'if upcase(&delete_col) = "YES" then output &outds_del ;'; put 'else output work.bitemp0_append ;'; put 'run;'; put '%if %mf_getattrn(&outds_del,NLOBS)>0 %then %do;'; put '%bitemporal_closeouts('; put 'tech_from=&tech_from'; put ',tech_to = &tech_to'; put ',base_lib=&base_lib'; put ',base_dsn=&base_dsn'; put ',append_lib=work'; put ',append_dsn=%scan(&outds_del,-1,.)'; put ',PK=&bus_from &pk'; put ',NOW=&dbnow'; put ',loadtarget=&loadtarget'; put ',loadtype=&loadtype'; put ')'; put '%end;'; put '%end;'; put '%else %do;'; put 'output work.bitemp0_append;'; put 'run;'; put '%end;'; put '%mp_abort(iftrue= (&syscc gt 0 at line 494)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc)'; put ')'; put '%if %length(&close_vars)>0 %then %do;'; put '/**'; put '* need to close out records that are not provided'; put '*/'; put 'proc sql;'; put 'create table bitemp1_closevars1 as'; put 'select distinct a.%mf_getquotedstr(in_str=&pk,dlm=%str(,a.),quote=)'; put 'from &base_lib..&base_dsn a'; put 'inner join work.bitemp0_append b'; put 'on 1=1'; put '/* join on closevars key */'; put '%do idx_pk=1 %to %sysfunc(countw(&close_vars));'; put '%let idx_val=%scan(&close_vars,&idx_pk);'; put 'and a.&idx_val=b.&idx_val'; put '%end;'; put '/* filter base on tech dates if necessary */'; put '%if &loadtype=TXTEMPORAL %then %do;'; put 'where a.&tech_from <=&now and &now < a.&tech_to'; put '%end;'; put ';'; put 'create table bitemp1_closevars2 as'; put 'select distinct a.*'; put 'from bitemp1_closevars1 a'; put 'left join work.bitemp0_append b'; put 'on 1=1'; put '/* join on primary key */'; put '%do idx_pk=1 %to %sysfunc(countw(&pk));'; put '%let idx_val=%scan(&pk,&idx_pk);'; put 'and a.&idx_val=b.&idx_val'; put '%end;'; put '/* identify removed records by null value in a field in PK but not close_vars'; put '*/'; put 'where b.%scan('; put '%mf_wordsInStr1ButNotStr2(Str1=&pk,Str2=&close_vars),1,%str( )'; put ') IS NULL'; put ';'; put '%if %mf_getattrn(bitemp1_closevars2,NLOBS)>0 %then %do;'; put '%bitemporal_closeouts('; put 'tech_from=&tech_from'; put ',tech_to = &tech_to'; put ',base_lib=&base_lib'; put ',base_dsn=&base_dsn'; put ',append_lib=work'; put ',append_dsn=bitemp1_closevars2'; put ',PK=&bus_from &pk'; put ',NOW=&dbnow'; put ',loadtarget=&loadtarget'; put ',loadtype=&loadtype'; put ')'; put '%end;'; put '%end;'; put '/* return if nothing to load (was just deletes) */'; put '%if %mf_getattrn(work.bitemp0_append,NLOBS)=0 %then %do;'; put '%put NOTE:; %put NOTE-;%put NOTE-;%put NOTE-;'; put '%put NOTE- No updates - just deletes!;'; put '%put NOTE-;%put NOTE-;%put NOTE-;'; put '%end;'; put '/**'; put '* If applying manual overrides to business dates, then the input table MUST'; put '* be unique on the PK. Check, and if not - abort.'; put '*/'; put '%local msg;'; put '%if %length(&bus_from_override.&bus_to_override)>0 or &CHECK_UNIQUENESS=YES'; put '%then %do;'; put 'proc sort data=work.bitemp0_append out=work.bitemp0_check nodupkey;'; put 'by &pk;'; put 'run;'; put '%if %mf_getattrn(work.bitemp0_check,NLOBS)'; put 'ne %mf_getattrn(work.bitemp0_append,NLOBS)'; put '%then %do;'; put '%let msg=INPUT table &append_lib..&append_dsn is not unique on PK (&pk);'; put '%mp_lockanytable(UNLOCK,lib=&base_lib,ds=&base_dsn,ref=&ETLSOURCE (&msg),'; put 'ctl_ds=&dclib..mpe_lockanytable'; put ')'; put '%mp_lockanytable(UNLOCK'; put ',lib=%scan(&outds_audit,1,.)'; put ',ds=%scan(&outds_audit,2,.)'; put ',ref=&ETLSOURCE'; put ',ctl_ds=&dclib..mpe_lockanytable'; put ')'; put '%mp_abort(msg=&msg,mac=bitemporal_dataloader.sas);'; put '%end;'; put '%end;'; put '/**'; put '* extract from BASE table. Only want matching records, as could be very BIG.'; put '* New records are subsequently identified via left join and test for nulls.'; put '*/'; put '%local temp_table temp_table2 base_table baselib_schema;'; put '%put DCNOTE: Extracting matching observations from &base_lib..&base_dsn;'; put '%if &engine_type=OLEDB %then %do;'; put '%let temp_table=##BITEMP_&base_dsn;'; put '%if &loadtype=BITEMPORAL or &loadtype=TXTEMPORAL %then'; put '%let base_table=(select * from [dbo].&base_dsn'; put 'where convert(datetime,&SQLNOW) < &tech_to );'; put '%else %let base_table=[dbo].&base_dsn;'; put 'proc sql;'; put 'create table &base_lib.."&temp_table"n as'; put 'select * from work.bitemp0_append;'; put '/* open up a connection for pass through SQL */'; put '%dc_assignlib(WRITE,&base_lib,passthru=myAlias)'; put 'create table work.bitemp0_base as select * from connection to myAlias('; put '%end;'; put '%else %if &engine_type=REDSHIFT or &engine_type=POSTGRES %then %do;'; put '/* grab schema */'; put '%let baselib_schema=%mf_getschema(&base_lib);'; put '%if &baselib_schema.X ne X %then %let baselib_schema=&baselib_schema..;'; put '/* grab redshift config */'; put '%local redcnt; %let redcnt=0;'; put '%if &engine_type=REDSHIFT %then %do;'; put 'data _null_;'; put 'set &config_table(where=(var_scope=''DCBL_REDSH'' and var_active=1));'; put 'x+1;'; put 'call symputx(cats(''rednm'',x),var_value,''l'');'; put 'call symputx(cats(''redval'',x),var_value,''l'');'; put 'call symputx(''redcnt'',x,''l'');'; put 'run;'; put '%end;'; put '/* cannot persist temp tables so must create a temporary permanent table */'; put '%let temp_table=%mf_getuniquename(prefix=XDCTEMP);'; put '%if &loadtype=BITEMPORAL or &loadtype=TXTEMPORAL %then'; put '%let base_table=(select * from &baselib_schema.&base_dsn'; put 'where timestamp &sqlnow < &tech_to );'; put '%else %let base_table=&baselib_schema.&base_dsn;'; put '/* make empty table first - must clone & drop extra cols as autoload is bad */'; put '%dc_assignlib(WRITE,&base_lib,passthru=myAlias)'; put 'exec (create table &temp_table (like &baselib_schema.&base_dsn)) by myAlias;'; put '%if &engine_type=REDSHIFT %then %do;'; put 'exec (alter table &temp_table alter sortkey none) by myAlias;'; put '%end;'; put '%local dropcols;'; put '%let dropcols=%mf_wordsinstr1butnotstr2('; put 'str1=%upcase(%mf_getvarlist(&basecopy))'; put ',str2=%upcase(&pk)'; put ');'; put '%if %length(&dropcols>0) %then %do idx_pk=1 %to %sysfunc(countw(&dropcols));'; put '%put &=dropcols;'; put '%let idx_val=%scan(&dropcols,&idx_pk);'; put 'exec(alter table &temp_table drop column &idx_val;) by myAlias;'; put '%end;'; put 'exec (alter table &temp_table add column &md5_col varchar(32);) by myAlias;'; put '/* create view to strip formats and avoid warns in log */'; put 'data work.vw_bitemp0/view=work.vw_bitemp0;'; put 'set work.bitemp0_append(keep=&pk &md5_col);'; put 'format _all_;'; put 'run;'; put 'proc append base=&base_lib..&temp_table'; put '%if &engine_type=REDSHIFT %then %do;'; put '('; put '%do idx_pk=1 %to &redcnt;'; put '&&rednm&idx_pk = &&redval&idxpk'; put '%end;'; put ')'; put '%end;'; put 'data=work.vw_bitemp0 force nowarn;'; put 'run;'; put '/* open up a connection for pass through SQL */'; put '%dc_assignlib(WRITE,&base_lib,passthru=myAlias)'; put 'create table work.bitemp0_base as select * from connection to myAlias('; put '%end;'; put '%else %if &engine_type=CAS %then %do;'; put '%if &loadtype=BITEMPORAL or &loadtype=TXTEMPORAL %then'; put '%let base_table=&base_lib..&base_dsn'; put '(where=(&tech_from <=&now and &now < &tech_to));'; put '%else %let base_table=&base_lib..&base_dsn;'; put '%let temp_table=CASUSER.%mf_getuniquename(prefix=DC);'; put 'data &temp_table;'; put 'set work.bitemp0_append;'; put 'run;'; put '%let bitemp0base=CASUSER.%mf_getuniquename(prefix=DC);'; put 'proc fedsql sessref=dcsession;'; put 'create table &bitemp0base{options replace=true} as'; put '%end;'; put '%else %do;'; put '%let temp_table=work.bitemp0_append;'; put '%if &loadtype=BITEMPORAL or &loadtype=TXTEMPORAL %then'; put '%let base_table=&base_lib..&base_dsn'; put '(where=(&tech_from <=&now and &now < &tech_to));'; put '%else %let base_table=&base_lib..&base_dsn;'; put 'proc sql;'; put 'create table work.bitemp0_base as'; put '%end;'; put 'select a.&md5_col /* this identifies NEW records */'; put ', b.*'; put '/* assume first PK field cannot be null (if defined in a PK constraint then'; put 'it definitely cannot be null) */'; put ', case when b.%scan(&pk,1) IS NULL then 1 else 0 end as ___TMP___NEW_FLG'; put 'from &baselib_schema.&temp_table a'; put 'left join &base_table b'; put 'on 1=1'; put '%do idx_pk=1 %to &pk_cnt;'; put '%let idx_val=%scan(&pk,&idx_pk);'; put 'and a.&idx_val=b.&idx_val'; put '%end;'; put '%if &engine_type=OLEDB or &engine_type=REDSHIFT or &engine_type=POSTGRES'; put '%then %do;'; put '); proc sql; drop table &base_lib.."&temp_table"n;'; put '%end;'; put '%else %if &engine_type=CAS %then %do;'; put ';'; put 'quit;'; put 'data work.bitemp0_base;'; put 'set &bitemp0base;'; put 'run;'; put 'proc sql;'; put 'drop table &temp_table;'; put 'drop table &bitemp0base;'; put '%end;'; put '%else %do;'; put ';'; put '%end;'; put '/**'; put '* matching & changed records are those without NULL key values'; put '* &idx_val resolves to rightmost PK value (loop above)'; put '*/'; put '%put syscc (line525)=&syscc, sqlrc=&sqlrc;'; put '%mp_abort(iftrue= (&syscc gt 0 or &sqlrc>0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc sqlrc=&sqlrc)'; put ')'; put '%put hashcols2=&stripcols;'; put 'proc sql;'; put 'create table work.bitemp1_current(drop=___TMP___NEW_FLG) as'; put 'select *'; put ', put(md5(&stripcols),$hex32.) as &md5_col'; put 'from work.bitemp0_base (drop=&md5_col)'; put 'where ___TMP___NEW_FLG=0;'; put '/**'; put '* NEW records were identified in ___TMP___NEW_FLG in bitemp0_base'; put '*/'; put 'proc sql;'; put 'create table &outds_add'; put '(drop=&md5_col'; put '%if %mf_existvar(work.bitemp0_base, &delete_col) %then %do;'; put '&delete_col'; put '%end;'; put ')'; put 'as select a.*'; put '%if &loadtype=BITEMPORAL or &loadtype=TXTEMPORAL %then %do;'; put ',&now as &tech_from &tech_from_fmt'; put ',&high_date as &tech_to &tech_to_fmt'; put '%end;'; put 'from work.bitemp0_append a /* STAGING records (mix of existing & new) */'; put ', work.bitemp0_base b /* BASE records (contains null values for new) */'; put 'where a.&md5_col=b.&md5_col /* took staging md5 across in left join */'; put 'and b.___TMP___NEW_FLG=1; /* NEW records also identified in bitemp0_base */'; put '/**'; put '* identify INSERTS. These are records with the same business key but'; put '* the bus_from and bus_to value are higher / lower (respectively)'; put '* such that the existing record needs to be SPLIT to surround the new'; put '* record.'; put '* eg: OLD RECORD from=1 to=10'; put '* NEW RECORD from=5 to=7'; put '*'; put '* APPENDED RECORDS:'; put '* - from=1 to=5'; put '* - from=5 to=7'; put '* - from=7 to=10'; put '*/'; put '/* inserts cannot happen with TXTEMPORAL */'; put '%if &loadtype=BITEMPORAL or &loadtype=BUSTEMPORAL %then %do;'; put '/* IDENTIFY */'; put 'create table work.bitemp3_inserts as'; put 'select b.*'; put ',a.&bus_from as ___TMP___from'; put ',a.&bus_to as ___TMP___to'; put 'from work.bitemp0_append a'; put ',work.bitemp1_current b'; put 'where a.&bus_from > b.&bus_from'; put 'and a.&bus_to < b.&bus_to'; put '%do idx_pk=1 %to &pk_cnt;'; put '%let idx_val=%scan(&pk,&idx_pk);'; put 'and a.&idx_val=b.&idx_val'; put '%end;'; put 'order by'; put '/* compress blanks and then insert commas (as the datetime fields may'; put 'not be in use) */'; put '%sysfunc(tranwrd(%sysfunc(compbl('; put '&pk &bus_from &bus_to &processed'; put ')),%str( ), %str(,)))'; put ';'; put '/* SPLIT */'; put 'data work.bitemp3a_inserts (drop=___TMP___from ___TMP___retain ___TMP___to) ;'; put 'set work.bitemp3_inserts;'; put 'by &pk &bus_from &bus_to &processed;'; put 'if first.&idx_val then do;'; put '___TMP___retain=&bus_to;'; put '&bus_to=___TMP___from;'; put 'output;'; put '&bus_to=___TMP___retain;'; put 'end;'; put 'if last.&idx_val then do;'; put '&bus_from=___TMP___to;'; put 'output;'; put 'end;'; put 'run;'; put '%end;'; put '%else %do;'; put '/* TX temporal load */'; put 'data work.bitemp3a_inserts;'; put 'set work.bitemp1_current;'; put 'stop;'; put 'run;'; put '%end;'; put '/* APPEND */'; put 'proc sql;'; put 'create view work.bitemp3a_view as'; put 'select * from work.bitemp1_current'; put 'where &md5_col not in (select &md5_col from work.bitemp3a_inserts);'; put 'data bitemp3b_newbase;'; put 'set work.bitemp3a_inserts work.bitemp3a_view;'; put 'run;'; put '/** do not use! this converts short numerics into 8 bytes'; put 'proc sql;'; put 'create table work.bitemp3b_newbase as'; put 'select * from work.bitemp3a_inserts'; put 'union corr'; put 'select * from work.bitemp1_current'; put 'where &md5_col not in (select &md5_col from work.bitemp3a_inserts);'; put '*/'; put '/**'; put '* identify CHANGED records from staging.'; put '* Same business key with different temporal dates or md5 value'; put '* This table must be overlayed onto / into existing business history'; put '*/'; put 'proc sql;'; put 'create table work.bitemp4_updated as select distinct a.*'; put 'from work.bitemp0_append a'; put ',work.bitemp3b_newbase b'; put 'where 1=1'; put '%do idx_pk=1 %to &pk_cnt;'; put '%let idx_val=%scan(&pk,&idx_pk);'; put 'and a.&idx_val=b.&idx_val'; put '%end;'; put 'and ( a.&md5_col ne b.&md5_col'; put '%if &loadtype=BITEMPORAL or &loadtype=BUSTEMPORAL %then %do;'; put 'OR (a.&bus_from ne b.&bus_from or a.&bus_to ne b.&bus_to)'; put '%end;'; put ')'; put ';'; put '/**'; put '* This section would have been one simple step with union all'; put '* but that converts short numerics into 8 bytes!'; put '* so, convoluted alternative to retain the same functionality.'; put '*/'; put '/* base records */'; put 'create view work.bitemp4_prep1 as'; put 'select ''BASE'' as ___TMP___'; put ',b.*'; put 'from work.bitemp4_updated a'; put ',work.bitemp3b_newbase b'; put 'where 1'; put '%do idx_pk=1 %to &pk_cnt;'; put '%let idx_val=%scan(&pk,&idx_pk);'; put 'and a.&idx_val=b.&idx_val'; put '%end;'; put ';'; put '/* updated records */'; put 'create view work.bitemp4_prep2 as'; put 'select ''STAG'' as ___TMP___ ,*'; put 'from work.bitemp4_updated;'; put '/* ensure we only keep columns that appear in both */'; put '%local bp1 bp2 bp3 bp4;'; put '%let bp1=%mf_getvarlist(bitemp4_prep1);'; put '%let bp2=%mf_getvarlist(bitemp4_prep2);'; put '%let bp3=%mf_wordsInStr1ButNotStr2(Str1=&bp1,Str2=&bp2);'; put '%let bp4=%mf_wordsInStr1ButNotStr2(Str1=&bp2,Str2=&bp1);'; put 'data work.bitemp4_prep3/view=bitemp4_prep3;'; put 'set bitemp4_prep1 bitemp4_prep2;'; put '%if %length(XX&bp3&bp4)>2 %then %do;'; put 'drop &bp3 &bp4 ;'; put '%end;'; put 'run;'; put '/* remove duplicates */'; put 'proc sql;'; put 'create table work.bitemp4a_allrecs as'; put 'select distinct *'; put 'from work.bitemp4_prep3'; put 'order by'; put '/* compress blanks and then insert commas (as the datetime fields'; put 'may not be in use) */'; put '%sysfunc(tranwrd(%sysfunc(compbl('; put '&pk &bus_from &bus_to &processed'; put ')),%str( ), %str(,)))'; put ';'; put '%if &loadtype=BITEMPORAL or &loadtype=BUSTEMPORAL %then %do;'; put '/* this section aligns the business dates'; put '(eg for inserts or overlaps in the range) */'; put 'data work.bitemp4b_firstpass (drop=___TMP___cond ___TMP___from ___TMP___to );'; put 'set work.bitemp4a_allrecs;'; put 'by &pk &bus_from &bus_to &processed;'; put 'retain ___TMP___cond ''Name of Condition'';'; put 'retain ___TMP___from ___TMP___to 0;'; put '___TMP___md5lag=lag(&md5_col);'; put '/* reset retained variables */'; put 'if first.&idx_val then do;'; put 'call missing (___TMP___cond, ___TMP___from, ___TMP___to,___TMP___md5lag);'; put 'end;'; put 'else do;'; put '/* if record is identical, carry forward bus_from (and bus_to if higher)*/'; put 'if &md5_col=___TMP___md5lag then do;'; put '&bus_from=___TMP___from;'; put 'if &bus_to<___TMP___to then &bus_to=___TMP___to;'; put 'end;'; put 'end;'; put 'if ___TMP___=''STAG'' then do;'; put '/* need to carry forward the closing record */'; put '___TMP___cond=''Condition 1'';'; put 'end;'; put 'else if ___TMP___cond=''Condition 1'' then do;'; put '/* else ensure bus_from starts from prior record bus_to */'; put 'if &md5_col ne ___TMP___md5lag and &bus_from <= ___TMP___to'; put 'then &bus_from= ___TMP___to;'; put '/* new record may replace old record entirely */'; put 'if &bus_to <= &bus_from then delete;'; put 'else call missing (___TMP___cond, ___TMP___from, ___TMP___to);'; put 'end;'; put '___TMP___from=&bus_from;'; put '___TMP___to=&bus_to;'; put 'run;'; put '%end;'; put '%else %do;'; put '/* keep staged records only */'; put 'data work.bitemp4b_firstpass;'; put 'set work.bitemp4a_allrecs;'; put 'if ___TMP___=''STAG'';'; put 'run;'; put '%end;'; put '/* next phase is to pass through in reverse - so set up the sort statement */'; put '%local byvar;'; put '%do idx_pk=1 %to &pk_cnt;'; put '%let byvar=&byvar descending %scan(&pk,&idx_pk);'; put '%end;'; put '%if &loadtype=BITEMPORAL or &loadtype=BUSTEMPORAL'; put '%then %let byvar=&byvar descending &bus_from descending &bus_to;'; put '/* if matching bus dates supplied, need to ensure we also have a sort'; put 'between BASE and STAGING tables */'; put '%let byvar=&byvar descending ___TMP___;'; put 'proc sort data=work.bitemp4b_firstpass out=work.bitemp4c_sort ;'; put 'by &byvar;'; put 'run;'; put '/**'; put '* Now (in reverse) pass back business start dates'; put '*/'; put 'data work.bitemp4d_secondpass;'; put '%if &loadtype=BITEMPORAL or &loadtype=TXTEMPORAL %then %do;'; put '&tech_from=&now;'; put '&tech_to=&high_date;'; put '%end;'; put 'set work.bitemp4c_sort ;'; put 'by &byvar;'; put 'retain ___TMP___cond ''Name of Condition'';'; put 'retain ___TMP___from ___TMP___to 0;'; put '%if &loadtype=BITEMPORAL or &loadtype=BUSTEMPORAL %then %do;'; put '/* put / _all_ /;*/'; put '___TMP___md5lag=lag(&md5_col);'; put 'if first.&idx_val then do;'; put '/* reset retained variables */'; put 'call missing (___TMP___cond,___TMP___from,___TMP___to,___TMP___md5lag);'; put 'end;'; put 'else do;'; put '/* if record is identical, carry back bus_to */'; put 'if &md5_col=___TMP___md5lag then &bus_to=___TMP___to;'; put 'end;'; put 'if ___TMP___=''STAG'' then do;'; put '/* need to carry forward the closing record */'; put '___TMP___cond=''Condition 2'';'; put 'end;'; put 'else if ___TMP___cond=''Condition 2'' then do;'; put '/* else ensure bus_to stops at subsequent record bus_from */'; put 'if &md5_col ne ___TMP___md5lag and &bus_to >= ___TMP___from'; put 'then &bus_to= ___TMP___from;'; put '/* new record may replace old record entirely */'; put 'if &bus_from >= &bus_to then delete;'; put 'if &bus_from=___TMP___from and &bus_to=___TMP___to then delete;'; put 'else call missing (___TMP___cond, ___TMP___from, ___TMP___to);'; put 'end;'; put '___TMP___from=&bus_from;'; put '___TMP___to=&bus_to;'; put '%end;'; put 'run;'; put '%put syscc (line600)=&syscc;'; put '/**'; put 'There may still be some records (eg old business history) which have not'; put 'changed.'; put 'Need to identify these and remove from the append so they are not updated'; put 'unnecessarily. This is done by generating a new md5 (which INCLUDES the'; put 'business key) and any matching / identical records are split out (from those'; put 'that need to be updated).'; put '*/'; put '%if &loadtype=BITEMPORAL %then %do;'; put '%let cat_string=catx(''|'' ,&bus_from,&bus_to);'; put 'data bitemp5a_lkp (keep=&md5_col);'; put 'set bitemp0_base;'; put '/* for BITEMPORAL we need to compare business dates also */'; put '&md5_col=put(md5(&cat_string!!''|''!!&stripcols),$hex32.);'; put 'run;'; put 'data bitemp5b_updates;'; put 'set bitemp4d_secondpass;'; put 'if _n_=1 then do;'; put 'dcl hash md5_lkp(dataset:''bitemp5a_lkp'');'; put 'md5_lkp.definekey("&md5_col");'; put 'md5_lkp.definedone();'; put 'end;'; put '/* drop old md5 col as will rebuild with new business dates */'; put '&md5_col=put(md5(&cat_string!!''|''!!&stripcols),$hex32.) ;'; put 'if md5_lkp.check()=0 then delete;'; put 'run;'; put 'proc sql;'; put '/* get min bus from as will update (close out) all records from this point'; put '(for that PK)*/'; put 'create table work.bitemp5d_subquery as'; put 'select &pk_comma, min(&bus_from)as &bus_from, max(&bus_to) as &bus_to'; put 'from work.bitemp5b_updates'; put 'group by &pk_comma;'; put '/* index has a huge efficiency impact on upcoming nested subquery */'; put 'create index index1 on work.bitemp5d_subquery(&pk_comma,&bus_from, &bus_to);'; put '%let lastds=work.bitemp5b_updates;'; put '%end;'; put '%else %if &loadtype=TXTEMPORAL or &loadtype=UPDATE %then %do;'; put 'proc sql;'; put 'create table work.bitemp5d_subquery as'; put 'select distinct &pk_comma'; put 'from bitemp4d_secondpass;'; put '%let lastds=work.bitemp4d_secondpass;'; put '%end;'; put '%else %let lastds=work.bitemp4d_secondpass;'; put '/* create single append table (an overlapped pre-sert may be classed as'; put 'both an update AND a new record). Also create temp views that may be'; put 'used for pre-load analysis. */'; put 'data &outds_mod;'; put 'set &lastds(drop=___TMP___: &md5_col);'; put 'run;'; put 'data bitemp6_allrecs / view=bitemp6_allrecs;'; put 'set &outds_mod /* UPDATED records */'; put '&outds_add /* NEW records */;'; put 'run;'; put 'proc sort data=work.bitemp6_allrecs'; put 'out=work.bitemp6_unique'; put 'noduprec'; put 'dupout=work.xx_BADBADBAD;'; put 'by _all_;'; put 'run;'; put '/* we have all our temp tables now so exit if this is all that is needed */'; put '%if &LOADTARGET ne YES %then %return;'; put '/* also exit if an err condition exists */'; put '%if &syscc>0 %then %do;'; put '%put syscc=&syscc;'; put '%mp_lockanytable(UNLOCK,lib=&base_lib,ds=&base_dsn,ref=&ETLSOURCE,'; put 'ctl_ds=&dclib..mpe_lockanytable'; put ')'; put '%if "&outds_audit" ne "0" %then %do;'; put '%mp_lockanytable(UNLOCK'; put ',lib=%scan(&outds_audit,1,.)'; put ',ds=%scan(&outds_audit,2,.)'; put ',ref=&ETLSOURCE'; put ',ctl_ds=&dclib..mpe_lockanytable'; put ')'; put '%end;'; put '%end;'; put '%mp_abort(iftrue= (&syscc>0)'; put ',mac=&sysmacroname in &_program'; put ',msg=%str(Bitemporal transform / job aborted due to SYSCC=&SYSCC status)'; put ')'; put '/* final check - abort if a lock has appeared on the target or audit table */'; put '%mp_lockfilecheck(libds=&base_lib..&base_dsn)'; put '%if %mf_existds(&outds_audit) %then %do;'; put '%mp_lockfilecheck(libds=&outds_audit)'; put '%end;'; put '/**'; put '* STAGING TABLES PREPARED, ERR CONDITION TESTED FOR.. NOW TO LOAD!!'; put '*/'; put '/**'; put '* First, CLOSE OUT changed records (if not a REPLACE)'; put '* Note that SAS does not support ANSI standard for UPDATE with a join condition.'; put '* However - this can be worked around using a nested subquery..'; put '*/'; put 'data _null_;'; put 'putlog "&sysmacroname: CLOSEOUTS commencing";'; put 'run;'; put '%if %mf_getattrn(&lastds,NLOBS)=0 %then %do;'; put 'data _null_;'; put 'putlog "&sysmacroname: No closeouts needed";'; put 'run;'; put '%end;'; put '%else %if &engine_type=CAS %then %do;'; put '%mp_abort(iftrue= (&loadtype=BITEMPORAL or &loadtype=TXTEMPORAL)'; put ',mac=&sysmacroname in &_program'; put ',msg=%str(&loadtype not yet supported in CAS engine)'; put ')'; put '/* create temp table for deletions */'; put '%local delds;%let delds=%mf_getuniquename(prefix=DC);'; put 'data casuser.&delds;'; put 'set work.bitemp5d_subquery;'; put 'run;'; put '/* delete the records */'; put 'proc cas ;'; put 'table.deleteRows / table={'; put 'caslib="&base_lib",'; put 'name="&base_dsn",'; put 'where="1=1",'; put 'whereTable={caslib=''CASUSER'',name="&delds"}'; put '};'; put 'quit;'; put '/* drop temp table */'; put 'proc sql;'; put 'drop table CASUSER.&delds;'; put '%end;'; put '%else %if (&loadtype=BITEMPORAL or &loadtype=TXTEMPORAL or &loadtype=UPDATE)'; put '%then %do;'; put 'data _null_;'; put 'putlog "&sysmacroname: &loadtype operation using &engine_type engine";'; put 'run;'; put '%local flexinow;'; put 'proc sql;'; put '/* if OLEDB then create a temp table for efficiency */'; put '%local innertable;'; put '%if &engine_type=OLEDB %then %do;'; put '%let innertable=[##BITEMP_&base_dsn];'; put '%let top_table=[dbo].&base_dsn;'; put '%let flexinow=&SQLNOW;'; put 'create table &base_lib.."##BITEMP_&base_dsn"n as'; put 'select * from work.bitemp5d_subquery;'; put '/* open up a connection for pass through SQL */'; put '%dc_assignlib(WRITE,&base_lib,passthru=myAlias)'; put 'execute('; put '%end;'; put '%else %if &engine_type=REDSHIFT or &engine_type=POSTGRES %then %do;'; put '%let innertable=%mf_getuniquename(prefix=XDCTEMP);'; put '%let top_table=&baselib_schema.&base_dsn;'; put '%let flexinow=timestamp &SQLNOW;'; put '/* make empty table first - must clone & drop extra cols'; put 'as autoload is bad */'; put '%dc_assignlib(WRITE,&base_lib,passthru=myAlias)'; put 'exec (create table &innertable (like &baselib_schema.&base_dsn)) by myAlias;'; put '%if &engine_type=REDSHIFT %then %do;'; put 'exec (alter table &innertable alter sortkey none) by myAlias;'; put '%end;'; put '%let dropcols=%mf_wordsinstr1butnotstr2('; put 'str1=%upcase(%mf_getvarlist(&basecopy))'; put ',str2=%upcase(%mf_getvarlist(work.bitemp5d_subquery))'; put ');'; put '%if %length(&dropcols>0) %then %do idx_pk=1 %to %sysfunc(countw(&dropcols));'; put '%put &=dropcols;'; put '%let idx_val=%scan(&dropcols,&idx_pk);'; put 'exec(alter table &innertable drop column &idx_val;) by myAlias;;'; put '%end;'; put '/* create view to strip formats and avoid warns in log */'; put 'data work.vw_bitemp5d/view=work.vw_bitemp5d;'; put 'set work.bitemp5d_subquery;'; put 'format _all_;'; put 'run;'; put 'proc append base=&base_lib..&innertable ('; put '%do idx_pk=1 %to &redcnt;'; put '&&rednm&idx_pk = &&redval&idxpk'; put '%end;'; put ')'; put 'data=work.vw_bitemp5d force nowarn;'; put 'run;'; put '/* open up a connection for pass through SQL */'; put '%dc_assignlib(WRITE,&base_lib,passthru=myAlias)'; put 'execute('; put '%end;'; put '%else %do;'; put '%let innertable=bitemp5d_subquery;'; put '%let top_table=&base_lib..&base_dsn;'; put '%let flexinow=&now;'; put '%end;'; put '%if &loadtype=BITEMPORAL or &loadtype=TXTEMPORAL %then %do;'; put 'update &top_table set &tech_to=&flexinow'; put '%if %length(&processed)>0 %then %do;'; put ',&processed=&flexinow'; put '%end;'; put 'where &tech_from <= &flexinow and &flexinow < &tech_to and'; put '%end;'; put '%else %if &loadtype=UPDATE %then %do;'; put '/* changed records are deleted then re-appended when doing UPDATEs */'; put 'delete from &top_table where'; put '%end;'; put '%else %do;'; put '%put %str(ERR)OR: BUSTEMPORAL NOT YET SUPPORTED;'; put '%let syscc=5;'; put '%mp_lockanytable(UNLOCK,lib=&base_lib,ds=&base_dsn,ref=&ETLSOURCE,'; put 'ctl_ds=&dclib..mpe_lockanytable'; put ')'; put '%mp_lockanytable(UNLOCK'; put ',lib=%scan(&outds_audit,1,.)'; put ',ds=%scan(&outds_audit,2,.)'; put ',ref=&ETLSOURCE'; put ',ctl_ds=&dclib..mpe_lockanytable'; put ')'; put '%goto end_of_macro;'; put '%end;'; put '/* perform join inside query as per'; put 'http://stackoverflow.com/questions/24629793/update-with-a-proc-sql */'; put 'exists( select 1 from &baselib_schema.&innertable where'; put '/* loop PK join */'; put '%do idx_pk=1 %to &pk_cnt;'; put '%let idx_val=%scan(&pk,&idx_pk);'; put '&base_dsn..&idx_val=&innertable..&idx_val and'; put '%end;'; put '%if &loadtype=BITEMPORAL %then %do;'; put '&base_dsn..&bus_from >= &innertable..&bus_from'; put 'and &base_dsn..&bus_to <= &innertable..&bus_to and'; put '%end;'; put '/* close the statement */'; put '1=1);'; put '%if &engine_type=OLEDB or &engine_type=REDSHIFT or &engine_type=POSTGRES'; put '%then %do;'; put ') by myAlias;'; put 'execute (drop table &baselib_schema.&innertable) by myAlias;'; put '%end;'; put '%end;'; put 'quit;'; put 'data _null_;'; put 'putlog "&sysmacroname: Closeout complete";'; put 'run;'; put '/**'; put '* Append the new / updated records'; put '*/'; put '%if &engine_type=CAS %then %do;'; put '/* get varchar variables ready for casting */'; put '%local vcfmt vcrename vcassign vcdrop;'; put 'data _null_;'; put 'set work.bitemp_cols(where=(type=6)) end=last;'; put 'length vcrename vcassign vcdrop vcfmt $32767 rancol $32;'; put 'retain vcrename vcassign vcdrop vcfmt;'; put 'if _n_=1 then vcrename=''(rename=('';'; put 'rancol=resolve(''%mf_getuniquename()'');'; put 'vcfmt=trim(vcfmt)!!''length ''!!cats(name)!!'' varchar(*);'';'; put 'vcrename=trim(vcrename)!!'' ''!!cats(name,''='',rancol);'; put 'vcassign=cats(vcassign,name,''='',rancol,'';'');'; put 'vcdrop=cats(vcdrop,''drop ''!!rancol,'';'');'; put 'if last then do;'; put 'vcrename=cats(vcrename,''))'');'; put 'call symputx(''vcfmt'',vcfmt);'; put 'call symputx(''vcrename'',vcrename);'; put 'call symputx(''vcassign'',vcassign);'; put 'call symputx(''vcdrop'',vcdrop);'; put 'end;'; put 'run;'; put '/* prepare a temp cas table with varchars casted */'; put '%let tmp=%mf_getuniquename();'; put 'data casuser.&tmp ;'; put '&vcfmt'; put 'set work.bitemp6_unique &vcrename;'; put '&vcassign'; put '&vcdrop'; put 'run;'; put '/* load the table with varchars applied*/'; put 'data &base_lib..&base_dsn (append=yes )/sessref=dcsession ;'; put 'set casuser.&tmp;'; put 'run;'; put '/* drop temp table */'; put 'proc sql;'; put 'drop table CASUSER.&tmp;'; put '/* this code will not work as regular tables do not have varchars */'; put '/*'; put 'proc casutil;'; put 'load data=work.bitemp6_unique'; put 'outcaslib="&base_lib" casout="&base_dsn" append ;'; put 'quit;'; put '*/'; put '%end;'; put '%else %if &engine_type=REDSHIFT or &engine_type=POSTGRES %then %do;'; put 'proc append base=&base_lib..&base_dsn'; put '%if &engine_type=REDSHIFT %then %do;'; put '('; put '%do idx_pk=1 %to &redcnt;'; put '&&rednm&idx_pk = &&redval&idxpk'; put '%end;'; put ')'; put '%end;'; put 'data=bitemp6_unique force nowarn;'; put 'run;'; put '%end;'; put '%else %do;'; put 'proc append base=&base_lib..&base_dsn data=bitemp6_unique force nowarn; run;'; put '%end;'; put '%mp_lockanytable(UNLOCK,lib=&base_lib,ds=&base_dsn,ref=&ETLSOURCE,'; put 'ctl_ds=&dclib..mpe_lockanytable'; put ')'; put '/* final check on syscc */'; put '%mp_abort(iftrue= (&syscc >4)'; put ',mac=&_program'; put ',msg=%str(!!Upload NOT successful!! Failed on actual update / append stage..)'; put ')'; put '%if &outds_audit ne 0 and &LOADTARGET=YES %then %do;'; put 'data work.vw_outds_orig /view=work.vw_outds_orig;'; put 'set work.bitemp0_base (drop=&md5_col);'; put 'where ___TMP___NEW_FLG=0;'; put 'drop ___TMP___NEW_FLG;'; put 'run;'; put '/* update the AUDIT table */'; put '%if %mf_existds(&outds_audit) %then %do;'; put 'options mprint;'; put '%mp_storediffs(&base_lib..&base_dsn'; put ',work.vw_outds_orig'; put ',&pk &bus_from'; put ',delds=&outds_del'; put ',modds=&outds_mod'; put ',appds=&outds_add'; put ',outds=work.mp_storediffs'; put ',processed_dttm=&now'; put ',loadref=%superq(etlsource)'; put ')'; put '/* exclude unchanged values in modified rows */'; put 'data work.mp_storediffs;'; put 'set work.mp_storediffs;'; put 'if MOVE_TYPE="M" and IS_PK=0 and IS_DIFF=0 then delete;'; put '* putlog load_ref= libref= dsn= key_hash= tgtvar_nm=;'; put 'run;'; put 'proc append base=&outds_audit data=work.mp_storediffs;'; put 'run;'; put '%mp_lockanytable(UNLOCK'; put ',lib=%scan(&outds_audit,1,.)'; put ',ds=%scan(&outds_audit,2,.)'; put ',ref=&ETLSOURCE'; put ',ctl_ds=&dclib..mpe_lockanytable'; put ')'; put '%end;'; put '%end;'; put '%mp_abort(iftrue= (&syscc >4)'; put ',mac=bitemporal_dataloader'; put ',msg=%str(Problem in audit stage (&outds_audit))'; put ')'; put '%let user=%mf_getUser();'; put '/**'; put 'Notify as appropriate EMAILS DISABLED'; put '%sumo_alerts(ALERT_EVENT=UPDATE'; put ', ALERT_TARGET=&base_lib..&base_dsn'; put ', from_user= &user);'; put '*/'; put '/* monitor BiTemporal usage */'; put '%if &log=1 %then %do;'; put '%put syscc=&syscc;'; put '/* do not perform duration calc in pass through */'; put '%local dur;'; put 'data _null_;'; put 'now=symget(''now'');'; put 'dur=%sysfunc(datetime())-&now;'; put 'call symputx(''dur'',dur,''l'');'; put 'run;'; put 'proc sql;'; put 'insert into &dclib..mpe_dataloads'; put 'set libref=%upcase("&base_lib")'; put ',DSN=%upcase("&base_dsn")'; put ',ETLSOURCE="&ETLSOURCE"'; put ',LOADTYPE="&loadtype"'; put ',CHANGED_RECORDS=%mf_getattrn(&lastds,NLOBS)'; put ',NEW_RECORDS=%mf_getattrn(&outds_add,NLOBS)'; put ',DELETED_RECORDS=%mf_getattrn(&outds_del,NLOBS)'; put ',DURATION=&dur'; put ',MAC_VER="v&ver"'; put ',user_nm="&user"'; put ',PROCESSED_DTTM=&now;'; put 'quit;'; put '%put syscc=&syscc;'; put '%end;'; put '%end_of_macro:'; put '%mend bitemporal_dataloader;'; put '%macro meta_mapper('; put 'baseds=work.allmap /* base table to contain metamapping (two level) */'; put ', stageds=col_meta /* temp table to append to base*/'; put ', metaid=OMSOBJ:Column\A5HOSDWY.BF00LWQT'; put ', direction=REVERSE /* either REVERSE or FORWARDS */'; put ', level=0 /* system var - show level of nesting */'; put ', job= /* system var - avoid looping same source */'; put ', levelcheck=50 /* system var - avoid going too deep down the rabbit hole */'; put ', append=NO /* system var - when YES means appending within nested loop */'; put ');'; put '%if &level>&levelcheck %then %return;'; put '%put &sysmacroname entry vars:;'; put '%put _local_;'; put '%if &direction=REVERSE %then %do;'; put '%let start=Target;'; put '%let finish=Source;'; put '%end;'; put '%else %do;'; put '%let start=Source;'; put '%let finish=Target;'; put '%end;'; put '%if &append=NO %then %do;'; put 'proc datasets lib=work;'; put 'delete %scan(&baseds,2,.);'; put 'quit;'; put '%let index_statement=(index=(HASH/unique));'; put '%end;'; put '%else %let index_statement=;'; put 'data &stageds &index_statement ;'; put 'length HASH $32'; put 'jobname sourcetablename sourcecolname sourcecoluri'; put 'map_type map_transform targettablename targetcolname targetcoluri'; put 'uri targettableuri tfmuri sourcetableuri scuri tpuri tmpuri mturi $256'; put 'Derived_Rule $500 Marker_ID Name_ID N_Name LibRef engine sourcePublicType'; put 'targetPublicType $64;'; put 'keep HASH jobname sourcetablename sourcecolname sourcecoluri'; put 'map_type map_transform'; put 'targettablename targetcolname targetcoluri Derived_Rule level;'; put '/* proc transpose logic only */'; put 'length sourceshorttablename sourcemembertype sourcelocation'; put 'assoc assocuri name sturi foundrefuri foundfinishuri'; put 'targetshorttablename targetlocation targetmembertype trafoName $256'; put 'sourceshorttableuri sourceshortcoluri checkrdm targetshorttableuri'; put 'targetshortcoluri selected_direction $17'; put 'lturi _location $200;'; put 'call missing (of _all_);'; put '&start.coluri="&METAID";'; put 'level=&level;'; put '/* first get table associated with the column */'; put 'if metadata_getnasn(&start.coluri,''Table'',1,&start.tableuri)=0 then do;'; put 'putlog "ERR" "OR: Table not found";'; put 'stop;'; put 'end;'; put 'rc=metadata_getattr(&start.tableuri,"Name",&start.tablename);'; put 'rc=metadata_getattr(&start.coluri,"Name",&start.colname);'; put 'rc=metadata_getattr(&start.tableuri,''PublicType'',&start.PublicType);'; put 'if (metadata_getnasn(&start.tableuri, "TablePackage",1, tpuri)>0) then'; put 'do;'; put 'rc=metadata_getattr(tpuri,"Libref",LibRef);'; put 'rc=metadata_getattr(tpuri,"Engine",engine);'; put 'if missing(libref) then do;'; put 'rc=metadata_getnasn(tpuri, "UsedByPackages",1, tmpuri);'; put 'rc=metadata_getattr(tmpuri,"Libref",LibRef);'; put 'rc=metadata_getattr(tmpuri,"Engine",engine);'; put 'end;'; put '&start.tablename=cats('; put 'upcase(LibRef),''-'',upcase(engine),''.'',&start.tablename'; put ');'; put 'end;'; put 'else if (&start.PublicType="ExternalFile") then do;'; put 'rc=metadata_getnasn(&start.tableuri, "OwningFile",1, tmpuri);'; put 'rc=metadata_getnasn(tmpuri, "FileRefs",1, tmpuri);'; put 'rc=metadata_getnasn(tmpuri, "FileRefLocations",1, tmpuri);'; put 'rc=metadata_getattr(tmpuri,''FileName'',&start.tablename);'; put 'end;'; put 'else &start.tablename = ''work. ''||trim(lowcase(&start.tablename));'; put '/* now loop the Source / TargetFeatureMaps */'; put 'tfm=1;'; put 'do while(metadata_getnasn(&start.coluri,"&start.FeatureMaps",tfm,tfmuri)>0);'; put 'call missing(derived_rule);'; put 'rc=metadata_getattr(tfmuri,''TransformRole'',map_type);'; put '/* get job and step name */'; put 'if (metadata_getnasn(tfmuri,''AssociatedClassifierMap'',1,tmpuri)<1) then'; put 'do;'; put 'rc=metadata_getnasn(tfmuri,"&finish.Transformations",1,tmpuri);'; put 'rc=metadata_getnasn(tmpuri,''AssociatedClassifierMap'',1,tmpuri);'; put 'end ;'; put 'rc=metadata_getnasn(tmpuri,''Steps'',1,tmpuri);'; put 'rc=metadata_getattr(tmpuri,''Name'',map_transform);'; put 'rc=metadata_getnasn(tmpuri,''Activities'',1,tmpuri);'; put 'rc=metadata_getnasn(tmpuri,''Jobs'',1,tmpuri);'; put 'rc=metadata_getattr(tmpuri,''Name'',jobname);'; put 'if Map_Type = ''DERIVED'' then do;'; put 'if(metadata_getnasn(tfmuri,"SourceCode",1, scuri)>0) then do;'; put '/* standard */'; put 'mturi=tfmuri;'; put 'end;'; put 'else do;'; put '/* some SQL joins store transform rules elsewhere */'; put 'rc=metadata_getnasn(tfmuri,"Feature&start.s",1, tmpuri);'; put 'if (metadata_getnasn(tmpuri,"Variables",1, tmpuri)>0)'; put 'then rc=metadata_getnasn(tmpuri,"OwningTransformation",1, mturi);'; put 'else rc=metadata_getnasn(tfmuri,"Transformation&start.s",1, mturi);'; put 'rc=metadata_getnasn(mturi,"SourceCode",1, scuri);'; put 'end;'; put 'rc=metadata_getattr(scuri,"StoredText",Derived_Rule);'; put 'Derived_Rule = compress(Derived_Rule,''0A''x);'; put '/* loop to generate derived rule (swap ref numbers for col descs) */'; put 'sv=1;'; put 'do while(metadata_getnasn(mturi,"SubstitutionVariables",sv,tmpuri)>0);'; put 'rc=metadata_getattr(tmpuri,"Marker",Marker_ID);'; put 'rc=metadata_getattr(tmpuri,"Name",Name_ID);'; put 'N_Name = compress(scan(Name_ID,2,''-''));'; put 'Derived_Rule=tranwrd('; put 'Derived_Rule,compress(Marker_ID),compress(N_Name)'; put ');'; put 'sv+1;'; put 'end;'; put 'end;'; put '/* get source col attributes */'; put 'fs=1;'; put 'do while(metadata_getnasn(tfmuri,"Feature&finish.s",fs,&finish.coluri)>0);'; put 'rc=metadata_getattr(&finish.coluri,''Name'',&finish.colname);'; put 'rc=metadata_getnasn(&finish.coluri,''Table'',1,&finish.tableuri);'; put 'rc=metadata_getattr(&finish.tableuri,''Name'',&finish.tablename);'; put 'rc=metadata_getattr(&finish.tableuri,''PublicType'',&finish.PublicType);'; put 'if (metadata_getnasn(&finish.tableuri,"TablePackage",1,tpuri)>0) then'; put 'do;'; put 'rc=metadata_getattr(tpuri,"Libref",LibRef);'; put 'rc=metadata_getattr(tpuri,"Engine",engine);'; put 'if missing(libref) then do;'; put 'rc=metadata_getnasn(tpuri, "UsedByPackages",1, tmpuri);'; put 'rc=metadata_getattr(tmpuri,"Libref",LibRef);'; put 'rc=metadata_getattr(tmpuri,"Engine",engine);'; put 'end;'; put '&finish.tablename=cats('; put 'upcase(LibRef),''-'',upcase(engine),''.'',&finish.tablename'; put ');'; put 'end;'; put 'else if (&finish.PublicType="ExternalFile") then do;'; put 'rc=metadata_getnasn(&finish.tableuri, "OwningFile",1, tmpuri);'; put 'rc=metadata_getnasn(tmpuri, "FileRefs",1, tmpuri);'; put 'rc=metadata_getnasn(tmpuri, "FileRefLocations",1, tmpuri);'; put 'rc=metadata_getattr(tmpuri,''FileName'',&finish.tablename);'; put 'end;'; put 'else &finish.tablename=compress(''work.''||lowcase(&finish.tablename));'; put '/* do a lookup to see if this record has been loaded before,'; put 'IF base table exists */'; put 'hash=put(md5('; put 'cats(jobname,sourcecoluri,map_type,map_transform,targetcoluri)'; put '),$hex32.);'; put '%if %sysfunc(exist(&baseds)) %then %do;'; put 'set &baseds(keep=hash) key=hash/unique;'; put 'if _iorc_ ne 0 then do;'; put '/* hash did not exist, hence this is a new record */'; put 'output;'; put '_error_=0;'; put 'end;'; put '%end;'; put '%else %do;'; put 'output;'; put '%end;'; put 'fs+1;'; put 'end;'; put 'tfm+1;'; put 'end;'; put '/* No finish URI found - so proceed to see if this is due to transpose */'; put 'if missing(&finish.coluri)'; put 'and (metadata_getnasn(&start.tableuri,"&start.ClassifierMaps",1,tmpuri)>0)'; put 'then do;'; put 'length trafoname $256;'; put 'call missing(trafoName);'; put 'rc=metadata_getattr(tmpuri,''Name'',trafoName);'; put '/* get &finsh.tablename and jobname */'; put 'rc=metadata_getnasn(tmpuri,"Classifier&finish.s",1,&finish.tableuri);'; put 'rc=metadata_getattr(&finish.tableuri,''Name'',&finish.tablename);'; put '&finish.shorttablename=&finish.tablename;'; put 'rc=metadata_getnasn(tmpuri,''Steps'',1,tmpuri);'; put 'rc=metadata_getnasn(tmpuri,''Activities'',1,tmpuri);'; put 'rc=metadata_getnasn(tmpuri,''Jobs'',1,tmpuri);'; put 'rc=metadata_getattr(tmpuri,''Name'',jobname);'; put 'rc1=1;n1=1;'; put 'do while(rc1>0);'; put 'rc1=metadata_getnasl(&finish.tableuri,n1,assoc);'; put 'if (assoc="Columns") then do;'; put 'rc2=1;n2=1;'; put 'do while(rc2>0 and missing(foundfinishuri));'; put '/* Walk through all column associations: SpecSourceTransformations*/'; put 'rc2=metadata_getnasn(&finish.tableuri,trim(assoc),n2,assocuri);'; put '/* REVERSE */'; put '%if ("&direction." = "REVERSE") %then %do;'; put 'if metadata_getnasn(assocuri,"SpecSourceTransformations",1,sturi)>0'; put 'then do;'; put 'rc=metadata_getattr(sturi,"Name",name);'; put '/* SAS Transpose: varColumns */'; put 'if (name ="varColumns") then do;'; put 'foundfinishuri = "true"; /* do a while exit */'; put 'put "scource colname name=" name;'; put 'put "scource colname uri=" assocuri;'; put '&finish.coluri=assocuri;'; put '&finish.shortcoluri=substr('; put '&finish.coluri,find(&finish.coluri, ''\'')+1'; put ');'; put 'rc=metadata_getattr(&finish.coluri,''Name'',&finish.colname);'; put 'rc=metadata_getnasn(&finish.coluri,''Table'',1,&finish.tableuri);'; put 'rc=metadata_getattr(&finish.tableuri,''Name'',&finish.tablename);'; put '&finish.shorttablename=&finish.tablename;'; put 'map_type = "ONETOONE";'; put '/* get MemberType */'; put 'rc=metadata_getattr('; put '&finish.tableuri,"MemberType",&finish.membertype'; put ');'; put '&finish.shorttableuri = substr('; put '&finish.tableuri,find(&finish.tableuri, ''\'')+1'; put ');'; put 'rc=metadata_getattr('; put '&finish.shorttableuri,"PublicType",&finish.publictype'; put ');'; put 'if metadata_getnasn('; put '&finish.shorttableuri,"TablePackage",1,tpuri'; put ')>0'; put 'then do;'; put '/* init LibRef to overwrite previous for &start!!! */'; put 'call missing (LibRef);'; put 'rc=metadata_getattr(tpuri,"Libref",LibRef);'; put 'rc=metadata_getattr(tpuri,"Engine",engine);'; put 'if missing(libref) then do;'; put 'rc=metadata_getnasn(tpuri, "UsedByPackages",1, tmpuri);'; put 'rc=metadata_getattr(tmpuri,"Libref",LibRef);'; put 'rc=metadata_getattr(tmpuri,"Engine",engine);'; put 'end;'; put '&finish.shorttablename=&finish.tablename;'; put '&finish.tablename=cats('; put 'upcase(LibRef),''-'',upcase(engine),''.'',&finish.tablename'; put ');'; put 'end;'; put '/* get SAS Folder location of the table */'; put 'if &finish.publictype eq "Table" then do;'; put 'lturi=&finish.shorttableuri;'; put 'rc=metadata_getnasn(lturi,"Trees",1,lturi);'; put 'rc=metadata_getattr(lturi,"Name",&finish.location);'; put 'tree=1;'; put 'do while (tree>0);'; put 'tree=metadata_getnasn(lturi,"ParentTree",1,lturi);'; put 'if tree > 0 then do;'; put 'rc=metadata_getattr(lturi,"Name",_location);'; put '&finish.location=catx(''/'',_location,&finish.location);'; put 'end;'; put 'end;'; put '&finish.location = ''/''||&finish.location;'; put 'end;'; put 'map_transform = trafoName;'; put 'derived_rule = "Transpose vertical";'; put '/* do a lookup to see if this record has been loaded before,'; put 'IF base table exists */'; put 'hash=put(md5('; put 'cats(jobname,sourcecoluri,map_type,map_transform,targetcoluri)'; put '),$hex32.);'; put '%if %sysfunc(exist(&baseds)) %then %do;'; put 'set &baseds(keep=hash) key=hash/unique;'; put 'if _iorc_ ne 0 then do;'; put '/* hash did not exist, hence this is a new record */'; put 'output;'; put '_error_=0;'; put 'end;'; put '%end;'; put '%else %do;'; put 'output;'; put '%end;'; put 'end; /* (name = "_VALUE_COLUMN") */'; put 'end; /* (metadata_getnasn(assocuri2,"Spec&finish.xxx",1,sturi)>0) */'; put '%end; /* &direction = "REVERSE" */'; put '/* FORWARDS: if TargetFeatureMaps not available: -> target! */'; put '%if ("&direction." = "FORWARDS") %then %do;'; put 'if (metadata_getnasn(assocuri,"TargetFeatureMaps",1,sfuri)<0)'; put 'then do;'; put 'rc=metadata_getattr(assocuri,"Name",name);'; put 'if not missing(assocuri) then do;'; put 'put "target colname name=" name;'; put 'put "target colname uri=" assocuri;'; put '&finish.coluri=assocuri;'; put '&finish.shortcoluri=substr('; put '&finish.coluri,find(&finish.coluri, ''\'')+1'; put ');'; put 'rc=metadata_getattr(&finish.coluri,''Name'',&finish.colname);'; put 'rc=metadata_getnasn(&finish.coluri,''Table'',1,&finish.tableuri);'; put 'rc=metadata_getattr(&finish.tableuri,''Name'',&finish.tablename);'; put '&finish.shorttablename=&finish.tablename;'; put 'map_type = "ONETOMANY";'; put '/* get MemberType */'; put 'rc=metadata_getattr('; put '&finish.tableuri,"MemberType",&finish.membertype'; put ');'; put '&finish.shorttableuri = substr('; put '&finish.tableuri,find(&finish.tableuri, ''\'')+1'; put ');'; put 'rc=metadata_getattr('; put '&finish.shorttableuri,"PublicType",&finish.publictype'; put ');'; put 'if metadata_getnasn('; put '&finish.shorttableuri,"TablePackage",1,tpuri'; put ')>0'; put 'then do;'; put '/* init LibRef to overwrite previous for &start!!! */'; put 'call missing (LibRef);'; put 'rc=metadata_getattr(tpuri,"Libref",LibRef);'; put 'rc=metadata_getattr(tpuri,"Engine",engine);'; put 'if missing(libref) then do;'; put 'rc=metadata_getnasn(tpuri, "UsedByPackages",1, tmpuri);'; put 'rc=metadata_getattr(tmpuri,"Libref",LibRef);'; put 'rc=metadata_getattr(tmpuri,"Engine",engine);'; put 'end;'; put '&finish.shorttablename=&finish.tablename;'; put '&finish.tablename=cats('; put 'upcase(LibRef),''-'',upcase(engine),''.'',&finish.tablename'; put ');'; put 'end;'; put '/* get table''s SAS Folder location */'; put 'if &finish.publictype eq "Table" then do;'; put 'lturi=&finish.shorttableuri;'; put 'rc=metadata_getnasn(lturi,"Trees",1,lturi);'; put 'rc=metadata_getattr(lturi,"Name",&finish.location);'; put 'tree=1;'; put 'do while (tree>0);'; put 'tree=metadata_getnasn(lturi,"ParentTree",1,lturi);'; put 'if tree > 0 then do;'; put 'rc=metadata_getattr(lturi,"Name",_location);'; put '&finish.location=catx(''/'',_location,&finish.location);'; put 'end;'; put 'end;'; put '&finish.location = ''/''||&finish.location;'; put 'end;'; put 'map_transform = trafoName;'; put 'derived_rule = "Transpose horizontal";'; put '/* do a lookup to see if this record has been loaded before,'; put 'IF base table exists */'; put 'hash=put(md5('; put 'cats(jobname,sourcecoluri,map_type,map_transform,targetcoluri)'; put '),$hex32.);'; put '%if %sysfunc(exist(&baseds)) %then %do;'; put 'set &baseds(keep=hash) key=hash/unique;'; put 'if _iorc_ ne 0 then do;'; put '/* hash did not exist, hence this is a new record */'; put 'output;'; put '_error_=0;'; put 'end;'; put '%end;'; put '%else %do;'; put 'output;'; put '%end;'; put 'end;'; put 'end;'; put '%end; /* (&direction = "FORWARDS" */'; put 'call missing(assocuri);'; put 'n2+1;'; put 'end; /* while(rc1>0) */'; put 'end; /* (assoc="Columns") */'; put 'call missing(assoc);'; put 'n1+1;'; put 'end; /* while(rc1>0) */'; put 'end; /* missing(&finish.coluri) */'; put '/* ################# end mapping for Transpose flat ################# */'; put 'stop;'; put 'run;'; put 'proc append base=&baseds data=&stageds;'; put 'run;'; put 'data _null_;'; put 'set &stageds;'; put 'call execute(''%meta_mapper(metaid=''!!&finish.coluri'; put '!!",baseds=&baseds"'; put '!!",direction=&direction"'; put '!!",level=%eval(&level+1)"'; put '!!",levelcheck=&levelcheck"'; put '!!",job="!!jobname'; put '!!",append=YES)");'; put 'run;'; put '%mend meta_mapper;'; put '* SAS Macros end;'; put '* SAS Includes start;'; put '* SAS Includes end;'; put '* Binary Files start;'; put '* Binary Files end;'; put '* ServiceInit start;'; put 'options noquotelenmax ps=max;'; put '/* create dummy macros to prevent stpbegin / stpend from corrupting sessions */'; put '%macro stpbegin();'; put '%put NOTE: the STPBEGIN macro should not be used for web apps!;'; put '%mend stpbegin;'; put '%macro stpend();'; put '%put NOTE: the STPEND macro should not be used for web apps!;'; put '%mend stpend;'; put '* ServiceInit end;'; put '* Service start;'; put '/**'; put '@file'; put '@brief fetch the metadata and server as dotlang'; put 'Some nice ideas for formatting are available here:'; put 'https://renenyffenegger.ch/notes/tools/Graphviz/examples/index'; put '

SAS Macros

'; put '@li mp_abort.sas'; put '@li mf_getuser.sas'; put '@li bitemporal_dataloader.sas'; put '@li meta_mapper.sas'; put '@version 9.4'; put '@author 4GL Apps Ltd'; put '@copyright 4GL Apps Ltd. This code may only be used within Data Controller'; put 'and may not be re-distributed or re-sold without the express permission of'; put '4GL Apps Ltd.'; put '**/'; put '%mpeinit()'; put '%global column_id direction refresh;'; put '/* enable col id and direction to be passed as url params */'; put '%let exist=%sysfunc(exist(work.SASControlTable));'; put '%let inds=%sysfunc(ifc(&exist=1,SASControlTable,_null_));'; put '%let max_depth=50;'; put '%put &=inds;'; put 'data _null_;'; put 'length max_depth $ 8;'; put 'set &inds;'; put 'call symputx(''column_id'',coluri);'; put 'call symputx(''direction'',direction);'; put 'call symputx(''refresh'',refresh);'; put 'if input(max_depth,8.)>0 then call symputx(''max_depth'',max_depth);'; put 'putlog (_all_)(=);'; put 'run;'; put '%put &=max_depth &=refresh;'; put 'data info;'; put 'length coluri colname taburi tabname liburi libref $256;'; put 'call missing(of _all_);'; put 'if metadata_getattr("&column_id","Name",colname)<0 then do;'; put 'putlog "Col &column_id not found";'; put 'call symputx(''syscc'',''1234'');'; put 'stop;'; put 'end;'; put 'rc=metadata_getnasn("&column_id","Table",1,taburi);'; put 'rc=metadata_getattr(taburi,"Name",tabname);'; put 'rc=metadata_getnasn(taburi,"TablePackage",1,liburi);'; put 'rc=metadata_getattr(liburi,"Libref",libref);'; put 'call symputx(''lib'',libref);'; put 'call symputx(''tab'',tabname);'; put 'call symputx(''col'',colname);'; put 'run;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(syscc=&syscc)'; put ')'; put '%macro launcher();'; put '/* check whether a lineage run already taken place */'; put 'proc sql noprint;'; put 'create table existing_data as'; put 'select * from &mpelib..mpe_lineage_cols'; put 'where col_id="&column_id"'; put 'and direction="%substr(&direction,1,1)";'; put '/* no data, so make some, and append it */'; put '%if &sqlobs=0 or &refresh=1 %then %do;'; put '%meta_mapper(metaid=&column_id'; put ', direction=&direction /* either REVERSE or FORWARDS */'; put ', baseds=work.allmap'; put ', levelcheck=%eval(&max_depth-1)'; put ')'; put 'data append;'; put 'length col_id $32 direction $1 modified_by $64;'; put 'retain col_id "&column_id";'; put 'retain direction "%substr(&direction,1,1)";'; put '%global modified_by modified_dttm;'; put '%let modified_dttm=%sysfunc(datetime());'; put 'retain modified_dttm &modified_dttm;'; put 'retain modified_by "%mf_getuser()";'; put '%let modified_by=%mf_getuser();'; put 'set allmap;'; put 'drop hash;'; put 'run;'; put 'proc sort data=append out=appendme nodupkey;'; put 'by col_id direction sourcecoluri targetcoluri map_type map_transform;'; put 'run;'; put '%bitemporal_dataloader(base_lib=&mpelib'; put ',base_dsn=mpe_lineage_cols'; put ',append_dsn=appendme'; put ',PK=col_id direction sourcecoluri targetcoluri map_type map_transform'; put ',etlsource=&_program'; put ',loadtype=UPDATE'; put ',close_vars=col_id direction'; put ',dclib=&mpelib'; put ')'; put '%end;'; put '%else %do;'; put '/* data exists, so use it */'; put 'data work.allmap(drop=modified_by modified_dttm);'; put 'set existing_data(drop=col_id direction );'; put 'if _n_=1 then do;'; put 'call symputx(''modified_by'',modified_by,''g'');'; put 'call symputx(''modified_dttm'',modified_dttm,''g'');'; put 'end;'; put 'where level < &max_depth;'; put 'run;'; put '%end;'; put '%mend launcher;'; put '%launcher()'; put '/* generate graphviz */'; put 'filename tmp "%sysfunc(pathname(work))\GraphViz%sysfunc(datetime()).txt"'; put 'lrecl=10000 encoding=''utf-8'';'; put 'options noquotelenmax;'; put '%macro fcmpconditional();'; put '%if &sysver=9.3 and &sysscp=WIN %then %do;'; put '/* nothing - as the FCMP function causes an exception err in this case */'; put '%end;'; put '%else %do;'; put '/* prepare quick func to enable word wrapping of transformations */'; put 'options cmplib=work.funcs;'; put 'proc fcmp outlib=work.funcs.macrocore;'; put 'function wordwrap(str $,cols,splitchar $) $;'; put 'length outstr $32767 curstr $5000;'; put 'base=0;'; put 'put str=;'; put 'do i=1 to countw(str,'' '','' '');'; put 'curstr=scan(str,i,'' '');'; put 'outstr=trim(outstr)!!'' ''!!curstr;'; put 'base=base+length(curstr)+1;'; put 'if base>cols then do;'; put 'outstr=cats(outstr,splitchar);'; put 'base=0;'; put 'end;'; put 'end;'; put 'return (outstr);'; put 'endsub;'; put 'run;'; put '%end;'; put '%mend fcmpconditional;'; put '%fcmpconditional()'; put '/* prepare label with metadata */'; put 'proc sql;'; put 'create table jobs as select distinct jobname as job from work.allmap ;'; put 'create table cols as select distinct upcase(scan(cat,1,''.-'')) as tmplib'; put ',cats(calculated tmplib,''.'',upcase(scan(cat,2,''.''))) as tmptab'; put ',cats(calculated tmptab,''.'',upcase(col)) as col'; put 'from (select sourcetablename as cat, sourcecolname as col from work.allmap'; put 'union select targettablename as cat, targetcolname as col from work.allmap )'; put 'having findc(tmplib,''/\'')=0 and tmplib ne ''WORK'';'; put 'create table files as select distinct file'; put 'from (select sourcetablename as file from work.allmap'; put 'where findc(sourcetablename,''/\'')>0'; put 'union select targettablename as file from work.allmap'; put 'where findc(targettablename,''/\'')>0'; put ') ;'; put 'create table libs as select distinct tmplib as lib from cols;'; put 'create table tabs as select distinct tmptab as tab from cols;'; put 'data _null_;'; put 'file tmp;'; put 'put ''digraph G {'; put 'concentrate=true;'; put 'node [style=filled,shape=plain];'; put 'labelloc = "t";'; put ''';'; put 'label= "label=<'; put ''; put ''; put ''; put ''; put ''; put ''; put '
&direction Lineage for &col
Library:
&libGenerated by:&modified_by
Table:&tabGenerated on:'; put '%sysfunc(round(&modified_dttm,2),datetime19.)
>";'; put 'put label;'; put 'if "FORWARD"="&direction" then call symputx(''dirdesc'',''Impacted'');'; put 'else call symputx(''dirdesc'',''Source'');'; put '/* close out if there is no lineage */'; put 'if nobs=0 then put ''x [label="No lineage found" shape=Mdiamond]}'';'; put 'set work.allmap nobs=nobs;'; put 'stop;'; put 'run;'; put 'data graphviz1;'; put 'file tmp mod;'; put 'length line arrow $1000 stab ttab slib tlib $100 sbox tbox tooltip $500;'; put 'if _n_=1 then call missing(line, sbox, tbox, tooltip);'; put 'set work.allmap ;'; put 'sourceid=sourcecoluri;'; put 'targetid=targetcoluri;'; put 'if index(sourcetablename,'':'') then do;'; put 'slib='''';'; put 'stab=sourcetablename;'; put 'end;'; put 'else if map_transform=''File Reader'' then do;'; put 'stab=scan(sourcetablename,-1,''/\'');'; put 'slib=subpad(sourcetablename,1,length(sourcetablename)-length(stab));'; put 'end;'; put 'else do;'; put 'slib=scan(sourcetablename,1,''.'');'; put 'stab=scan(sourcetablename,2,''.'');'; put 'end;'; put 'if index(targettablename,'':'') then do;'; put 'tlib='''';'; put 'ttab=targettablename;'; put 'end;'; put 'else if map_transform=''File Reader'' then do;'; put 'ttab=scan(targettablename,-1,''/\'');'; put 'tlib=subpad(targettablename,1,length(targettablename)-length(ttab));'; put 'end;'; put 'else do;'; put 'tlib=scan(targettablename,1,''.'');'; put 'ttab=scan(targettablename,2,''.'');'; put 'end;'; put 'if trim(derived_rule) ne '''' then do;'; put 'derived_rule=tranwrd(derived_rule,''"'',''\"'');'; put '%macro quick();'; put '%if "&sysver"="9.3" and "&sysscp"="WIN" %then %do;'; put 'arrow=cats(''[color=Red, fontcolor=Red, penwidth="3", arrowsize="2",'''; put ',''label=">>'',map_transform,''<<\n'',derived_rule,''"]'');'; put '%end;'; put '%else %do;'; put 'arrow=cats(''[color=Red, fontcolor=Red, penwidth="3", arrowsize="2",'''; put ',''label=">>'',map_transform,''<<\n'',wordwrap(derived_rule,24,''\n''),''"]'');'; put '%end;'; put '%mend quick; %quick()'; put 'end;'; put 'else arrow=cats(''[ label="'',map_transform,''"]'');'; put 'source=quote(strip(sourceid));'; put 'target=quote(strip(targetid));'; put 'put '' '' source '' -> '' target arrow;'; put 'run;'; put 'data graphviz2 (keep=id tab lib col tooltip map_transform);'; put 'set graphviz1 (rename=(source=id stab=tab slib=lib sourcecolname=col ))'; put 'graphviz1 (rename=(target=id ttab=tab tlib=lib targetcolname=col ));'; put 'if upcase(lib)=:''WORK'' then tooltip=cats('',tooltip="Job:'',jobname,''"'');'; put 'else tooltip='''';'; put 'run;'; put 'proc sort data=graphviz2 out=graphviz3 noduprec; by _all_; run;'; put 'data _null_;'; put 'length shape $100 ;'; put 'set graphviz3 end=last;'; put 'file tmp mod;'; put 'tab=tranwrd(tab,''\'',''\\'');'; put 'tab=tranwrd(tab,''&'',''&'');'; put 'lib=tranwrd(lib,''&'',''&'');'; put 'if upcase(lib)=:''WORK'' then do;'; put 'lib=''WORK'';'; put 'put id ''[label=<
Table'' tab'; put '''
Column'' col'; put '''
> ,fillcolor=lightgrey, shape=" " '' tooltip '']'';'; put 'end;'; put 'else if map_transform=''File Reader'' then do;'; put 'put id ''[label="Location: '' lib ''\nFile:'' tab ''\nColumn: '' col'; put '''",shape=parallelogram, fillcolor="#00b300"'' tooltip '']'';'; put 'end;'; put 'else do;'; put 'engine=scan(lib,2,''-'');'; put 'lib=scan(lib,1,''-'');'; put 'if engine=''BASE'' then fillcolour=''lightyellow '';'; put 'else fillcolour=''lightblue'';'; put 'shape='' shape=cylinder, fillcolor= ''!!fillcolour;'; put 'put id ''[label=<
Library'' lib'; put '''
Table'' tab'; put '''
Column'' col'; put '''
> ,'' shape tooltip '']'';'; put 'end;'; put 'run;'; put 'data _null_;'; put 'file tmp mod;'; put '/* close out if records exist */'; put 'set work.allmap;'; put 'put ''}'';'; put 'stop;'; put 'run;'; put 'data flatdata;'; put 'length type $8 item $256;'; put 'keep type item;'; put 'set cols(in=cols) tabs(in=tabs) files(in=files) libs(in=libs) jobs(in=jobs);'; put 'if cols then do;'; put 'type=''Column'';'; put 'item=col;'; put 'end;'; put 'else if tabs then do;'; put 'type=''Table'';'; put 'item=tab;'; put 'end;'; put 'else if files then do;'; put 'type=''File'';'; put 'item=file;'; put 'end;'; put 'else if libs then do;'; put 'type=''Library'';'; put 'item=lib;'; put 'end;'; put 'else if jobs then do;'; put 'type=''Job'';'; put 'item=job;'; put 'end;'; put 'run;'; put 'data fromSAS;'; put 'infile tmp end=last;'; put 'file tmp;'; put 'input ;'; put 'string=_infile_;'; put 'put string;'; put 'run;'; put 'filename tmp clear;'; put '/* get list of IDs so frontend can make a clickable list */'; put 'proc sql;'; put 'create table ids as select distinct id from graphviz3;'; put '%webout(OPEN)'; put '%webout(OBJ,fromSAS)'; put '%webout(OBJ,ids,dslabel=clickableIDS)'; put '%webout(OBJ,info)'; put '%webout(OBJ,flatdata)'; put '%webout(CLOSE)'; put '%mpeterm()'; put '* Service end;'; run; %mm_createwebservice(path=&appLoc/&path, name=&service, code=sascode, server=&serverName, replace=yes) filename sascode clear; %let service=fetchtablelineage; filename sascode temp lrecl=32767; data _null_; file sascode; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '/**'; put '@file mp_jsonout.sas'; put '@brief Writes JSON in SASjs format to a fileref'; put '@details This macro can be used to OPEN a JSON stream and send one or more'; put 'tables as arrays of rows, where each row can be an object or a nested array.'; put 'There are two engines available - DATASTEP or PROCJSON.'; put 'PROC JSON is fast but will produce errs like the ones below if'; put 'special chars are encountered.'; put '> (ERR)OR: Some code points did not transcode.'; put '> An object or array close is not valid at this point in the JSON text.'; put '> Date value out of range'; put 'If this happens, try running with ENGINE=DATASTEP.'; put 'The DATASTEP engine is used to handle special SAS missing numerics, and'; put 'can also convert entire datasets to formatted values. Output JSON is always'; put 'in UTF-8.'; put 'Usage:'; put 'filename tmp temp;'; put 'data class; set sashelp.class;run;'; put '%mp_jsonout(OPEN,jref=tmp)'; put '%mp_jsonout(OBJ,class,jref=tmp)'; put '%mp_jsonout(OBJ,class,dslabel=class2,jref=tmp,showmeta=Y)'; put '%mp_jsonout(CLOSE,jref=tmp)'; put 'data _null_;'; put 'infile tmp;'; put 'input;putlog _infile_;'; put 'run;'; put 'If you are building web apps with SAS then you are strongly encouraged to use'; put 'the mX_createwebservice macros in combination with the'; put '[sasjs adapter](https://github.com/sasjs/adapter).'; put 'For more information see https://sasjs.io'; put '@param [in] action Valid values:'; put '@li OPEN - opens the JSON'; put '@li OBJ - sends a table with each row as an object'; put '@li ARR - sends a table with each row in an array'; put '@li CLOSE - closes the JSON'; put '@param [in] ds The dataset to send. Must be a work table.'; put '@param [out] jref= (_webout) The fileref to which to send the JSON'; put '@param [out] dslabel= The name to give the table in the exported JSON'; put '@param [in] fmt= (Y) Whether to keep (Y) or strip (N) formats from the table'; put '@param [in] engine= (DATASTEP) Which engine to use to send the JSON. Options:'; put '@li PROCJSON (default)'; put '@li DATASTEP (more reliable when data has non standard characters)'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to JSON'; put '

Related Files

'; put '@li mp_ds2fmtds.sas'; put '@version 9.2'; put '@author Allan Bowe'; put '@source https://github.com/sasjs/core'; put '**/'; put '%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y'; put ',engine=DATASTEP'; put ',missing=NULL'; put ',showmeta=N'; put ',maxobs=MAX'; put ')/*/STORE SOURCE*/;'; put '%local tempds colinfo fmtds i numcols numobs stmt_obs lastobs optval'; put 'tmpds1 tmpds2 tmpds3 tmpds4;'; put '%let numcols=0;'; put '%if &maxobs ne MAX %then %let stmt_obs=%str(if _n_>&maxobs then stop;);'; put '%if &action=OPEN %then %do;'; put 'options nobomfile;'; put 'data _null_;file &jref encoding=''utf-8'' lrecl=200;'; put 'put ''{"PROCESSED_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''"'';'; put 'run;'; put '%end;'; put '%else %if (&action=ARR or &action=OBJ) %then %do;'; put '/* force variable names to always be uppercase in the JSON */'; put 'options validvarname=upcase;'; put '/* To avoid issues with _webout on EBI - such as encoding diffs and truncation'; put '(https://support.sas.com/kb/49/325.html) we use temporary files */'; put 'filename _sjs1 temp lrecl=200 ;'; put 'data _null_; file _sjs1 encoding=''utf-8'';'; put 'put ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":";'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjs1 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs1 clear;'; put '/* grab col defs */'; put 'proc contents noprint data=&ds'; put 'out=_data_(keep=name type length format formatl formatd varnum label);'; put 'run;'; put '%let colinfo=%scan(&syslast,2,.);'; put 'proc sort data=&colinfo;'; put 'by varnum;'; put 'run;'; put '/* move meta to mac vars */'; put 'data &colinfo;'; put 'if _n_=1 then call symputx(''numcols'',nobs,''l'');'; put 'set &colinfo end=last nobs=nobs;'; put 'name=upcase(name);'; put '/* fix formats */'; put 'if type=2 or type=6 then do;'; put 'typelong=''char'';'; put 'length fmt $49.;'; put 'if format='''' then fmt=cats(''$'',length,''.'');'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else fmt=cats(format,formatl,''.'');'; put 'end;'; put 'else do;'; put 'typelong=''num'';'; put 'if format='''' then fmt=''best.'';'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else if formatd=0 then fmt=cats(format,formatl,''.'');'; put 'else fmt=cats(format,formatl,''.'',formatd);'; put 'end;'; put '/* 32 char unique name */'; put 'newname=''sasjs''!!substr(cats(put(md5(name),$hex32.)),1,27);'; put 'call symputx(cats(''name'',_n_),name,''l'');'; put 'call symputx(cats(''newname'',_n_),newname,''l'');'; put 'call symputx(cats(''length'',_n_),length,''l'');'; put 'call symputx(cats(''fmt'',_n_),fmt,''l'');'; put 'call symputx(cats(''type'',_n_),type,''l'');'; put 'call symputx(cats(''typelong'',_n_),typelong,''l'');'; put 'call symputx(cats(''label'',_n_),coalescec(label,name),''l'');'; put '/* overwritten when fmt=Y and a custom format exists in catalog */'; put 'if typelong=''num'' then call symputx(cats(''fmtlen'',_n_),200,''l'');'; put 'else call symputx(cats(''fmtlen'',_n_),min(32767,ceil((length+10)*1.5)),''l'');'; put 'run;'; put '%let tempds=%substr(_%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql;'; put 'select count(*) into: lastobs from &ds;'; put '%if &maxobs ne MAX %then %let lastobs=%sysfunc(min(&lastobs,&maxobs));'; put '%if &engine=PROCJSON %then %do;'; put '%if &missing=STRING %then %do;'; put '%put &sysmacroname: Special Missings not supported in proc json.;'; put '%put &sysmacroname: Switching to DATASTEP engine;'; put '%goto datastep;'; put '%end;'; put 'data &tempds;'; put 'set &ds;'; put '&stmt_obs;'; put '%if &fmt=N %then format _numeric_ best32.;;'; put '/* PRETTY is necessary to avoid line truncation in large files */'; put 'filename _sjs2 temp lrecl=131068 encoding=''utf-8'';'; put 'proc json out=_sjs2 pretty'; put '%if &action=ARR %then nokeys ;'; put ';export &tempds / nosastags fmtnumeric;'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs2 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs2 clear;'; put '%end;'; put '%else %if &engine=DATASTEP %then %do;'; put '%datastep:'; put '%if %sysfunc(exist(&ds)) ne 1 & %sysfunc(exist(&ds,VIEW)) ne 1'; put '%then %do;'; put '%put &sysmacroname: &ds NOT FOUND!!!;'; put '%return;'; put '%end;'; put '%if &fmt=Y %then %do;'; put '/**'; put '* Extract format definitions'; put '* First, by getting library locations from dictionary.formats'; put '* Then, by exporting the width using proc format'; put '* Cannot use maxw from sashelp.vformat as not always populated'; put '* Cannot use fmtinfo() as not supported in all flavours'; put '*/'; put '%let tmpds1=%substr(fmtsum%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds2=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds3=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds4=%substr(col%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql noprint;'; put 'create table &tmpds1 as'; put 'select cats(libname,''.'',memname) as FMTCAT,'; put 'FMTNAME'; put 'from dictionary.formats'; put 'where fmttype=''F'' and libname is not null'; put 'and fmtname in (select format from &colinfo where format is not null)'; put 'order by 1;'; put 'create table &tmpds2('; put 'FMTNAME char(32),'; put 'LENGTH num'; put ');'; put '%local catlist cat fmtlist i;'; put 'select distinct fmtcat into: catlist separated by '' '' from &tmpds1;'; put '%do i=1 %to %sysfunc(countw(&catlist,%str( )));'; put '%let cat=%scan(&catlist,&i,%str( ));'; put 'proc sql;'; put 'select distinct fmtname into: fmtlist separated by '' '''; put 'from &tmpds1 where fmtcat="&cat";'; put 'proc format lib=&cat cntlout=&tmpds3(keep=fmtname length);'; put 'select &fmtlist;'; put 'run;'; put 'proc sql;'; put 'insert into &tmpds2 select distinct fmtname,length from &tmpds3;'; put '%end;'; put 'proc sql;'; put 'create table &tmpds4 as'; put 'select a.*, b.length as MAXW'; put 'from &colinfo a'; put 'left join &tmpds2 b'; put 'on cats(a.format)=cats(upcase(b.fmtname))'; put 'order by a.varnum;'; put 'data _null_;'; put 'set &tmpds4;'; put 'if not missing(maxw);'; put 'call symputx('; put 'cats(''fmtlen'',_n_),'; put '/* vars need extra padding due to JSON escaping of special chars */'; put 'min(32767,ceil((max(length,maxw)+10)*1.5))'; put ',''l'''; put ');'; put 'run;'; put '/* configure varlenchk - as we are explicitly shortening the variables */'; put '%let optval=%sysfunc(getoption(varlenchk));'; put 'options varlenchk=NOWARN;'; put 'data _data_(compress=char);'; put '/* shorten the new vars */'; put 'length'; put '%do i=1 %to &numcols;'; put '&&name&i $&&fmtlen&i'; put '%end;'; put ';'; put '/* rename on entry */'; put 'set &ds(rename=('; put '%do i=1 %to &numcols;'; put '&&name&i=&&newname&i'; put '%end;'; put '));'; put '&stmt_obs;'; put 'drop'; put '%do i=1 %to &numcols;'; put '&&newname&i'; put '%end;'; put ';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=num %then %do;'; put '&&name&i=cats(put(&&newname&i,&&fmt&i));'; put '%end;'; put '%else %do;'; put '&&name&i=put(&&newname&i,&&fmt&i);'; put '%end;'; put '%end;'; put 'if _error_ then do;'; put 'call symputx(''syscc'',1012);'; put 'stop;'; put 'end;'; put 'run;'; put '%let fmtds=&syslast;'; put 'options varlenchk=&optval;'; put '%end;'; put 'proc format; /* credit yabwon for special null removal */'; put 'value bart (default=40)'; put '%if &missing=NULL %then %do;'; put '._ - .z = null'; put '%end;'; put '%else %do;'; put '._ = [quote()]'; put '. = null'; put '.a - .z = [quote()]'; put '%end;'; put 'other = [best.];'; put 'data &tempds;'; put 'attrib _all_ label='''';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'length &&name&i $&&fmtlen&i...;'; put 'format &&name&i $&&fmtlen&i...;'; put '%end;'; put '%end;'; put '%if &fmt=Y %then %do;'; put 'set &fmtds;'; put '%end;'; put '%else %do;'; put 'set &ds;'; put '%end;'; put '&stmt_obs;'; put 'format _numeric_ bart.;'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'if findc(&&name&i,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put '&&name&i=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,&&name&i)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else &&name&i=quote(cats(&&name&i));'; put '%end;'; put '%end;'; put 'run;'; put 'filename _sjs3 temp lrecl=131068 ;'; put 'data _null_;'; put 'file _sjs3 encoding=''utf-8'';'; put 'if _n_=1 then put "[";'; put 'set &tempds;'; put 'if _n_>1 then put "," @; put'; put '%if &action=ARR %then "[" ; %else "{" ;'; put '%do i=1 %to &numcols;'; put '%if &i>1 %then "," ;'; put '%if &action=OBJ %then """&&name&i"":" ;'; put '"&&name&i"n /* name literal for reserved variable names */'; put '%end;'; put '%if &action=ARR %then "]" ; %else "}" ; ;'; put '/* close out the table */'; put 'data _null_;'; put 'file _sjs3 mod encoding=''utf-8'';'; put 'put '']'';'; put 'run;'; put 'data _null_;'; put 'infile _sjs3 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs3 clear;'; put '%end;'; put 'proc sql;'; put 'drop table &colinfo, &tempds;'; put '%if %substr(&showmeta,1,1)=Y %then %do;'; put 'filename _sjs4 temp lrecl=131068 encoding=''utf-8'';'; put 'data _null_;'; put 'file _sjs4;'; put 'length label $350;'; put 'put ", ""$%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":{""vars"":{";'; put 'do i=1 to &numcols;'; put 'name=quote(trim(symget(cats(''name'',i))));'; put 'format=quote(trim(symget(cats(''fmt'',i))));'; put 'label=quote(prxchange(''s/\\/\\\\/'',-1,trim(symget(cats(''label'',i)))));'; put 'length=quote(trim(symget(cats(''length'',i))));'; put 'type=quote(trim(symget(cats(''typelong'',i))));'; put 'if i>1 then put "," @@;'; put 'put name '':{"format":'' format '',"label":'' label'; put ''',"length":'' length '',"type":'' type ''}'';'; put 'end;'; put 'put ''}}'';'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs4 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs4 clear;'; put '%end;'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put 'data _null_; file &jref encoding=''utf-8'' mod ;'; put 'put "}";'; put 'run;'; put '%end;'; put '%mend mp_jsonout;'; put '/**'; put '@file mm_webout.sas'; put '@brief Send data to/from SAS Stored Processes'; put '@details This macro should be added to the start of each Stored Process,'; put '**immediately** followed by a call to:'; put '%mm_webout(FETCH)'; put 'This will read all the input data and create same-named SAS datasets in the'; put 'WORK library. You can then insert your code, and send data back using the'; put 'following syntax:'; put 'data some datasets; * make some data ;'; put 'retain some columns;'; put 'run;'; put '%mm_webout(OPEN)'; put '%mm_webout(ARR,some) * Array format, fast, suitable for large tables ;'; put '%mm_webout(OBJ,datasets) * Object format, easier to work with ;'; put 'Finally, wrap everything up send some helpful system variables too'; put '%mm_webout(CLOSE)'; put '@param [in] action Either FETCH, OPEN, ARR, OBJ or CLOSE'; put '@param [in] ds The dataset to send back to the frontend'; put '@param [out] dslabel= Value to use instead of table name for sending to JSON'; put '@param [in] fmt= (N) Setting Y converts all vars to their formatted values'; put '@param [out] fref= (_webout) The fileref to which to write the JSON'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] workobs= (0) When set to a positive integer, will create a new'; put 'output object (WORK) which contains this number of observations from all'; put 'tables in the WORK library.'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to output JSON'; put '

SAS Macros

'; put '@li mp_jsonout.sas'; put '

Related Macros

'; put '@li ms_webout.sas'; put '@li mv_webout.sas'; put '@version 9.3'; put '@author Allan Bowe'; put '**/'; put '%macro mm_webout(action,ds,dslabel=,fref=_webout,fmt=N,missing=NULL'; put ',showmeta=N,maxobs=MAX,workobs=0'; put ');'; put '%global _webin_file_count _webin_fileref1 _webin_name1 _program _debug'; put 'sasjs_tables;'; put '%local i tempds jsonengine;'; put '/* see https://github.com/sasjs/core/issues/41 */'; put '%if "%upcase(&SYSENCODING)" ne "UTF-8" %then %let jsonengine=PROCJSON;'; put '%else %let jsonengine=DATASTEP;'; put '%if &action=FETCH %then %do;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'options mprint notes mprintnest;'; put '%end;'; put '%let _webin_file_count=%eval(&_webin_file_count+0);'; put '/* now read in the data */'; put '%do i=1 %to &_webin_file_count;'; put '%if &_webin_file_count=1 %then %do;'; put '%let _webin_fileref1=&_webin_fileref;'; put '%let _webin_name1=&_webin_name;'; put '%end;'; put 'data _null_;'; put 'infile &&_webin_fileref&i termstr=crlf;'; put 'input;'; put 'call symputx(''input_statement'',_infile_);'; put 'putlog "&&_webin_name&i input statement: " _infile_;'; put 'stop;'; put 'data &&_webin_name&i;'; put 'infile &&_webin_fileref&i firstobs=2 dsd termstr=crlf encoding=''utf-8'';'; put 'input &input_statement;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'if _n_<20 then putlog _infile_;'; put '%end;'; put 'run;'; put '%let sasjs_tables=&sasjs_tables &&_webin_name&i;'; put '%end;'; put '%end;'; put '%else %if &action=OPEN %then %do;'; put '/* fix encoding */'; put 'OPTIONS NOBOMFILE;'; put '/**'; put '* check xengine type to avoid the below err message:'; put '* > Function is only valid for filerefs using the CACHE access method.'; put '*/'; put 'data _null_;'; put 'set sashelp.vextfl(where=(fileref="_WEBOUT"));'; put 'if xengine=''STREAM'' then do;'; put 'rc=stpsrv_header(''Content-type'',"text/html; encoding=utf-8");'; put 'end;'; put 'run;'; put '/* setup json */'; put 'data _null_;file &fref encoding=''utf-8'';'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutBEGIN<<'';'; put '%end;'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'run;'; put '%end;'; put '%else %if &action=ARR or &action=OBJ %then %do;'; put '%mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt,jref=&fref'; put ',engine=&jsonengine,missing=&missing,showmeta=&showmeta,maxobs=&maxobs'; put ')'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put '/* To avoid issues with _webout on EBI we use a temporary file */'; put 'filename _sjsref temp lrecl=131068;'; put '%if %str(&workobs) > 0 %then %do;'; put '/* if debug mode, send back first XX records of each work table also */'; put 'data;run;%let tempds=%scan(&syslast,2,.);'; put 'ods output Members=&tempds;'; put 'proc datasets library=WORK memtype=data;'; put '%local wtcnt;%let wtcnt=0;'; put 'data _null_;'; put 'set &tempds;'; put 'if not (upcase(name) =:"DATA"); /* ignore temp datasets */'; put 'i+1;'; put 'call symputx(cats(''wt'',i),name,''l'');'; put 'call symputx(''wtcnt'',i,''l'');'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put ",""WORK"":{";'; put '%do i=1 %to &wtcnt;'; put '%let wt=&&wt&i;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'dsid=open("WORK.&wt",''is'');'; put 'nlobs=attrn(dsid,''NLOBS'');'; put 'nvars=attrn(dsid,''NVARS'');'; put 'rc=close(dsid);'; put 'if &i>1 then put '',''@;'; put 'put " ""&wt"" : {";'; put 'put ''"nlobs":'' nlobs;'; put 'put '',"nvars":'' nvars;'; put '%mp_jsonout(OBJ,&wt,jref=_sjsref,dslabel=first10rows,showmeta=Y'; put ',maxobs=&workobs'; put ')'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put '%end;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put 'run;'; put '%end;'; put '/* close off json */'; put 'data _null_;file _sjsref mod encoding=''utf-8'';'; put 'length SYSPROCESSNAME syserrortext syswarningtext autoexec $512;'; put 'put ",""_DEBUG"" : ""&_debug"" ";'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'autoexec=quote(urlencode(trim(getoption(''autoexec''))));'; put 'put '',"AUTOEXEC" : '' autoexec;'; put 'put ",""MF_GETUSER"" : ""%mf_getuser()"" ";'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'put ",""SYSENCODING"" : ""&sysencoding"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSPROCESSID"" : ""&SYSPROCESSID"" ";'; put 'put ",""SYSPROCESSMODE"" : ""&SYSPROCESSMODE"" ";'; put 'SYSPROCESSNAME=quote(urlencode(cats(SYSPROCESSNAME)));'; put 'put ",""SYSPROCESSNAME"" : " SYSPROCESSNAME;'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put '',"SYSWARNINGTEXT" : '' syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'length memsize $32;'; put 'memsize="%sysfunc(INPUTN(%sysfunc(getoption(memsize)), best.),sizekmg.)";'; put 'memsize=quote(cats(memsize));'; put 'put '',"MEMSIZE" : '' memsize;'; put 'put "}" @;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutEND<<'';'; put '%end;'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjsref lrecl=1 recfm=n;'; put 'file &fref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjsref clear;'; put '%end;'; put '%mend mm_webout;'; put '%macro webout(action,ds,dslabel=,fmt=,missing=NULL,showmeta=NO,maxobs=MAX);'; put '%mm_webout(&action,ds=&ds,dslabel=&dslabel,fmt=&fmt'; put ',missing=&missing'; put ',showmeta=&showmeta'; put ',maxobs=&maxobs'; put ') %mend;'; put '/* provide additional debug info */'; put '%global _program;'; put '%put &=syscc;'; put '%put user=%mf_getuser();'; put '%put pgm=&_program;'; put '%put timestamp=%sysfunc(datetime(),datetime19.);'; put '* Service Variables start;'; put '* Service Variables end;'; put '* SAS Macros start;'; put '%macro mf_getapploc(pgm);'; put '%if "&pgm"="" %then %do;'; put '%if %symexist(_program) %then %let pgm=&_program;'; put '%else %do;'; put '%put &sysmacroname: No value provided and no _program variable available;'; put '%return;'; put '%end;'; put '%end;'; put '%local root;'; put '/**'; put '* First check we are not in the tests/macros folder (which has no subfolders)'; put '* or specifically in the testsetup or testteardown services'; put '*/'; put '%if %index(&pgm,/tests/macros/)'; put 'or %index(&pgm,/tests/testsetup)'; put 'or %index(&pgm,/tests/testteardown)'; put '%then %do;'; put '%let root=%substr(&pgm,1,%index(&pgm,/tests)-1);'; put '&root'; put '%return;'; put '%end;'; put '/**'; put '* Next, move up two levels to avoid matches on subfolder or service name'; put '*/'; put '%let root=%substr(&pgm,1,%length(&pgm)-%length(%scan(&pgm,-1,/))-1);'; put '%let root=%substr(&root,1,%length(&root)-%length(%scan(&root,-1,/))-1);'; put '%if %index(&root,/tests/) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/tests/)-1);'; put '%end;'; put '%else %if %index(&root,/services) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/services)-1);'; put '%end;'; put '%else %if %index(&root,/jobs) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/jobs)-1);'; put '%end;'; put '%else %put &sysmacroname: Could not find an app location from &pgm;'; put '&root'; put '%mend mf_getapploc ;'; put '%macro mf_getuniquefileref(prefix=_,maxtries=1000,lrecl=32767);'; put '%local rc fname;'; put '%if &prefix=0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%end;'; put '%else %do;'; put '%local x len;'; put '%let len=%eval(8-%length(&prefix));'; put '%let x=0;'; put '%do x=0 %to &maxtries;'; put '%let fname=&prefix%substr(%sysfunc(ranuni(0)),3,&len);'; put '%if %sysfunc(fileref(&fname)) > 0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%return;'; put '%end;'; put '%end;'; put '%put unable to find available fileref after &maxtries attempts;'; put '%end;'; put '%mend mf_getuniquefileref;'; put '%macro mp_abort(mac=mp_abort.sas, type=, msg=, iftrue=%str(1=1)'; put ', errds=work.mp_abort_errds'; put ', mode=REGULAR'; put ')/*/STORE SOURCE*/;'; put '%global sysprocessmode sysprocessname sasjs_stpsrv_header_loc sasjsprocessmode;'; put '%local fref fid i;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%put NOTE: /// mp_abort macro executing //;'; put '%if %length(&mac)>0 %then %put NOTE- called by &mac;'; put '%put NOTE - &msg;'; put '%if %symexist(_SYSINCLUDEFILEDEVICE)'; put '/* abort cancel FILE does not restart outside the INCLUDE on Viya 3.5 */'; put 'and %superq(SYSPROCESSNAME) ne %str(Compute Server)'; put '%then %do;'; put '%if "*&_SYSINCLUDEFILEDEVICE*" ne "**" %then %do;'; put 'data &errds;'; put 'iftrue=''1=1'';'; put 'length mac $100 msg $5000;'; put 'mac=symget(''mac'');'; put 'msg=symget(''msg'');'; put 'run;'; put 'data _null_;'; put 'abort cancel FILE;'; put 'run;'; put '%return;'; put '%end;'; put '%end;'; put '/* Web App Context */'; put '%if %symexist(_PROGRAM)'; put 'or %superq(SYSPROCESSNAME) = %str(Compute Server)'; put 'or &mode=INCLUDE'; put '%then %do;'; put 'options obs=max replace mprint;'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5"'; put '%then %do;'; put 'options nosyntaxcheck;'; put '%end;'; put '%if &mode=INCLUDE %then %do;'; put '%if %sysfunc(exist(&errds))=1 %then %do;'; put 'data _null_;'; put 'set &errds;'; put 'call symputx(''iftrue'',iftrue,''l'');'; put 'call symputx(''mac'',mac,''l'');'; put 'call symputx(''msg'',msg,''l'');'; put 'putlog (_all_)(=);'; put 'run;'; put '%if (&iftrue)=0 %then %return;'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: No include errors found;'; put '%return;'; put '%end;'; put '%end;'; put '/* extract log errs / warns, if exist */'; put '%local logloc logline;'; put '%global logmsg; /* capture global messages */'; put '%if %symexist(SYSPRINTTOLOG) %then %let logloc=&SYSPRINTTOLOG;'; put '%else %let logloc=%qsysfunc(getoption(LOG));'; put 'proc printto log=log;run;'; put '%let logline=0;'; put '%if %length(&logloc)>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input; putlog _infile_;'; put 'i=1;'; put 'retain logonce 0;'; put 'if ('; put '_infile_=:"%str(WARN)ING" or _infile_=:"%str(ERR)OR"'; put ') and logonce=0 then'; put 'do;'; put 'call symputx(''logline'',_n_);'; put 'logonce+1;'; put 'end;'; put 'run;'; put '/* capture log including lines BEFORE the err */'; put '%if &logline>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input;'; put 'i=1;'; put 'stoploop=0;'; put 'if _n_ ge &logline-15 and stoploop=0 then do until (i>22);'; put 'call symputx(''logmsg'',catx(''\n'',symget(''logmsg''),_infile_));'; put 'input;'; put 'i+1;'; put 'stoploop=1;'; put 'end;'; put 'if stoploop=1 then stop;'; put 'run;'; put '%end;'; put '%end;'; put '%if %symexist(SYS_JES_JOB_URI) %then %do;'; put '/* setup webout for Viya */'; put 'options nobomfile;'; put '%if "X&SYS_JES_JOB_URI.X"="XX" %then %do;'; put 'filename _webout temp lrecl=999999 mod;'; put '%end;'; put '%else %do;'; put 'filename _webout filesrvc parenturi="&SYS_JES_JOB_URI"'; put 'name="_webout.json" lrecl=999999 mod;'; put '%end;'; put '%end;'; put '%else %if %sysfunc(filename(fref,&sasjs_stpsrv_header_loc))=0 %then %do;'; put 'options nobomfile;'; put '/* set up http header for SASjs Server */'; put '%let fid=%sysfunc(fopen(&fref,A));'; put '%if &fid=0 %then %do;'; put '%put %str(ERR)OR: %sysfunc(sysmsg());'; put '%return;'; put '%end;'; put '%let rc=%sysfunc(fput(&fid,%str(Content-Type: application/json)));'; put '%let rc=%sysfunc(fwrite(&fid));'; put '%let rc=%sysfunc(fclose(&fid));'; put '%let rc=%sysfunc(filename(&fref));'; put '%end;'; put '/* send response in SASjs JSON format */'; put 'data _null_;'; put 'file _webout mod lrecl=32000 encoding=''utf-8'';'; put 'length msg syswarningtext syserrortext $32767 mode $10 ;'; put 'sasdatetime=datetime();'; put 'msg=symget(''msg'');'; put '%if &logline>0 %then %do;'; put 'msg=cats(msg,''\n\nLog Extract:\n'',symget(''logmsg''));'; put '%end;'; put '/* escape the escapes */'; put 'msg=tranwrd(msg,''\'',''\\'');'; put '/* escape the quotes */'; put 'msg=tranwrd(msg,''"'',''\"'');'; put '/* ditch the CRLFs as chrome complains */'; put 'msg=compress(msg,,''kw'');'; put '/* quote without quoting the quotes (which are escaped instead) */'; put 'msg=cats(''"'',msg,''"'');'; put 'if symexist(''_debug'') then debug=quote(trim(symget(''_debug'')));'; put 'else debug=''""'';'; put 'if symget(''sasjsprocessmode'')=''Stored Program'' then mode=''SASJS'';'; put 'if mode ne ''SASJS'' then put ''>>weboutBEGIN<<'';'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'put '',"sasjsAbort" : [{'';'; put 'put '' "MSG":'' msg ;'; put 'put '' ,"MAC": "'' "&mac" ''"}]'';'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'put '',"_DEBUG":'' debug ;'; put 'if symexist(''_metauser'') then do;'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put 'end;'; put 'if symexist(''SYS_JES_JOB_URI'') then do;'; put 'SYS_JES_JOB_URI=quote(trim(symget(''SYS_JES_JOB_URI'')));'; put 'put '',"SYS_JES_JOB_URI": '' SYS_JES_JOB_URI;'; put 'end;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put ",""SYSWARNINGTEXT"" : " syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'put "}" ;'; put 'if mode ne ''SASJS'' then put ''>>weboutEND<<'';'; put 'run;'; put '%put _all_;'; put '%if "&sysprocessmode " = "SAS Stored Process Server " %then %do;'; put 'data _null_;'; put 'putlog ''stpsrvset program err and syscc'';'; put 'rc=stpsrvset(''program error'', 0);'; put 'call symputx("syscc",0,"g");'; put 'run;'; put '%if &sysscp=WIN'; put 'and 1=0 /* deprecating this logic until we figure out a consistent abort */'; put 'and "%substr(%str(&sysvlong ),1,8)"="9.04.01M"'; put 'and "%substr(%str(&sysvlong ),9,1)">"5" %then %do;'; put '/* skip approach (below) does not work in windows m6+ envs */'; put 'endsas;'; put '%end;'; put '%else %do;'; put '/**'; put '* endsas kills 9.4m3 deployments by orphaning multibridges.'; put '* Abort variants are ungraceful (non zero return code)'; put '* This approach lets SAS run silently until the end :-)'; put '* Caution - fails when called within a %include within a macro'; put '* Use mp_include() to handle this.'; put '*/'; put 'filename skip temp;'; put 'data _null_;'; put 'file skip;'; put 'put ''%macro skip();'';'; put 'comment ''%mend skip; -> fix lint '';'; put 'put ''%macro skippy();'';'; put 'comment ''%mend skippy; -> fix lint '';'; put 'run;'; put '%inc skip;'; put '%end;'; put '%end;'; put '%else %if "&sysprocessmode " = "SAS Compute Server " %then %do;'; put '/* endsas kills the session making it harder to fetch results */'; put 'data _null_;'; put 'syswarningtext=symget(''syswarningtext'');'; put 'syserrortext=symget(''syserrortext'');'; put 'abort_msg=symget(''msg'');'; put 'syscc=symget(''syscc'');'; put 'sysuserid=symget(''sysuserid'');'; put 'iftrue=symget(''iftrue'');'; put 'put (_all_)(/=);'; put 'call symputx(''syscc'',0);'; put 'abort cancel nolist;'; put 'run;'; put '%end;'; put '%else %do;'; put '%abort cancel;'; put '%end;'; put '%end;'; put '%else %do;'; put '%put _all_;'; put '%abort cancel;'; put '%end;'; put '%mend mp_abort;'; put '/** @endcond */'; put '%macro mm_getstpcode('; put 'tree=/User Folders/sasdemo/somestp'; put ',name='; put ',outloc=0'; put ',outref=0'; put ',mDebug=1'; put ',showlog=NO'; put ');'; put '%local mD;'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing &sysmacroname..sas;'; put '%&mD.put _local_;'; put '%if %length(&name)>0 %then %let name=/&name;'; put '/* first, check if STP exists */'; put '%local tsuri;'; put '%let tsuri=stopifempty ;'; put 'data _null_;'; put 'format type uri tsuri value $200.;'; put 'call missing (of _all_);'; put 'path="&tree&name(StoredProcess)";'; put '/* first, find the STP ID */'; put 'if metadata_pathobj("",path,"StoredProcess",type,uri)>0 then do;'; put '/* get sourcecode */'; put 'cnt=1;'; put 'do while (metadata_getnasn(uri,"Notes",cnt,tsuri)>0);'; put 'rc=metadata_getattr(tsuri,"Name",value);'; put '&mD.put tsuri= value=;'; put 'if value="SourceCode" then do;'; put '/* found it! */'; put 'rc=metadata_getattr(tsuri,"Id",value);'; put 'call symputx(''tsuri'',value,''l'');'; put 'stop;'; put 'end;'; put 'cnt+1;'; put 'end;'; put 'end;'; put 'else put (_all_)(=);'; put 'run;'; put '%mp_abort(iftrue= (&tsuri=stopifempty)'; put ',mac=mm_getstpcode'; put ',msg=%str(&tree&name.(StoredProcess) not found!)'; put ')'; put '/**'; put '* Now we can extract the textstore'; put '*/'; put 'filename __getdoc temp lrecl=10000000;'; put 'proc metadata'; put 'in="$METAREPOSITORY'; put ''; put 'SAS1"'; put 'out=__getdoc ;'; put 'run;'; put '/* find the beginning of the text */'; put '%local start;'; put 'data _null_;'; put 'infile __getdoc lrecl=10000;'; put 'input;'; put 'start=index(_infile_,''StoredText="'');'; put 'if start then do;'; put 'call symputx("start",start+11);'; put '*putlog ''"'' _infile_ ''"'';'; put 'end;'; put 'stop;'; put '%local outeng;'; put '%if "&outloc"="0" %then %let outeng=TEMP;'; put '%else %let outeng="&outloc";'; put '%local fref;'; put '%if &outref=0 %then %let fref=%mf_getuniquefileref();'; put '%else %let fref=&outref;'; put '/* read the content, byte by byte, resolving escaped chars */'; put 'filename &fref &outeng lrecl=100000;'; put 'data _null_;'; put 'length filein 8 fileid 8;'; put 'filein = fopen("__getdoc","I",1,"B");'; put 'fileid = fopen("&fref","O",1,"B");'; put 'rec = "20"x;'; put 'length entity $6;'; put 'do while(fread(filein)=0);'; put 'x+1;'; put 'if x>&start then do;'; put 'rc = fget(filein,rec,1);'; put 'if rec=''"'' then leave;'; put 'else if rec="&" then do;'; put 'entity=rec;'; put 'do until (rec=";");'; put 'if fread(filein) ne 0 then goto getout;'; put 'rc = fget(filein,rec,1);'; put 'entity=cats(entity,rec);'; put 'end;'; put 'select (entity);'; put 'when (''&'' ) rec=''&'' ;'; put 'when (''<'' ) rec=''<'' ;'; put 'when (''>'' ) rec=''>'' ;'; put 'when (''''') rec="''" ;'; put 'when (''"'') rec=''"'' ;'; put 'when ('' '') rec=''0A''x;'; put 'when ('' '') rec=''0D''x;'; put 'when (''$'' ) rec=''$'' ;'; put 'when ('' '') rec=''09''x;'; put 'otherwise putlog "%str(WARN)ING: missing value for " entity=;'; put 'end;'; put 'rc =fput(fileid, substr(rec,1,1));'; put 'rc =fwrite(fileid);'; put 'end;'; put 'else do;'; put 'rc =fput(fileid,rec);'; put 'rc =fwrite(fileid);'; put 'end;'; put 'end;'; put 'end;'; put 'getout:'; put 'rc=fclose(filein);'; put 'rc=fclose(fileid);'; put 'run;'; put '%if &showlog=YES %then %do;'; put 'data _null_;'; put 'infile &fref lrecl=32767 end=last;'; put 'input;'; put 'if _n_=1 then putlog ''>>stpcodeBEGIN<<'';'; put 'putlog _infile_;'; put 'if last then putlog ''>>stpcodeEND<<'';'; put 'run;'; put '%end;'; put 'filename __getdoc clear;'; put '%if &outref=0 %then %do;'; put 'filename &fref clear;'; put '%end;'; put '%mend mm_getstpcode;'; put '%macro dc_getsettings();'; put '%global _program;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(syscc=&syscc on entry (&syswarningtext &syserrortext))'; put ')'; put '%if %length(&_PROGRAM)>1 %then %let root=&_program;'; put '%else %do;'; put '%global _metauser;'; put '%let _metauser=&sysuserid;'; put '/* to mimic a "real" _program we need to give a dummy role and stp name */'; put '%let root=/dummyRole/dummyName;'; put '%end;'; put '/* the DC precode is stored in the Admin folder in the root of'; put 'the project. Lets find that root. */'; put '%put &=root;'; put '%let root=%mf_getapploc();'; put '%put &=root;'; put '/* Now we know the root location we can retrieve the params */'; put '%let temploc=%sysfunc(pathname(work))/settings.txt;'; put '%mm_getstpcode(tree=&root/services/public'; put ',name=Data_Controller_Settings'; put ',outloc=&temploc'; put ')'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Unable to run getstpcode)'; put ')'; put 'filename _getsets "&temploc" lrecl=2000;'; put '/*'; put 'Do not use mp_include here - this puts a copy in every service, which creates'; put 'compilation problems when calling services from mp_include'; put '*/'; put '%inc _getsets/source2;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Problem running &sysmacroname (&syswarningtext &syserrortext))'; put ')'; put '%mend dc_getsettings;'; put '%macro mf_fmtdttm('; put ')/*/STORE SOURCE*/;'; put '%if "&sysver"="9.2" or "&sysver"="9.3"'; put 'or ("&sysver"="9.4" and "%substr(&SYSVLONG,9,1)" le "3")'; put 'or "%substr(&sysver,1,1)"="4"'; put 'or "%substr(&sysver,1,1)"="5"'; put '%then %do;DATETIME19.3%end;'; put '%else %do;E8601DT26.6%end;'; put '%mend mf_fmtdttm;'; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '%macro mp_init(prefix=SASJS'; put ')/*/STORE SOURCE*/;'; put '%if %symexist(SASJS_PREFIX) %then %return; /* only run once */'; put '%global'; put 'SASJS_PREFIX /* the ONLY hard-coded global macro variable in SASjs */'; put '&prefix._FUNCTIONS /* used in mcf_init() to track core function compilation */'; put '&prefix._INIT_NUM /* initialisation time as numeric */'; put '&prefix._INIT_DTTM /* initialisation time in E8601DT26.6 format */'; put '&prefix.WORK /* avoid typing %sysfunc(pathname(work)) every time */'; put ';'; put '%let sasjs_prefix=&prefix;'; put 'data _null_;'; put 'dttm=datetime();'; put 'call symputx("&sasjs_prefix._init_num",dttm,''g'');'; put 'call symputx("&sasjs_prefix._init_dttm",put(dttm,E8601DT26.6),''g'');'; put 'call symputx("&sasjs_prefix.work",pathname(''WORK''),''g'');'; put 'run;'; put 'options'; put 'compress=CHAR /* default is none so ensure we have something! */'; put 'datastmtchk=ALLKEYWORDS /* protection from overwriting input datasets */'; put 'errorcheck=STRICT /* catch errs in libname/filename statements */'; put 'fmterr /* ensure err when a format cannot be found */'; put 'mergenoby=%str(ERR)OR /* throw err when a merge has no BY variables */'; put 'missing=. /* changing this can cause hard to detect errs */'; put 'noquotelenmax /* avoid warnings for long strings */'; put 'noreplace /* avoid overwriting permanent datasets */'; put 'ps=max /* reduce log size slightly */'; put 'ls=max /* reduce log even more and avoid word truncation */'; put 'validmemname=COMPATIBLE /* avoid special characters etc in table names */'; put 'validvarname=V7 /* avoid special characters etc in variable names */'; put 'varinitchk=%str(ERR)OR /* avoid data mistakes from variable name typos */'; put 'varlenchk=%str(ERR)OR /* fail hard if truncation (data loss) can result */'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5" %then %do;'; put 'noautocorrect /* disallow misspelled procedure names */'; put 'dsoptions=note2err /* undocumented - convert bad NOTEs to ERRs */'; put '%end;'; put ';'; put '%mend mp_init;'; put '%macro mpeinit(fetch=YES);'; put '%global mpeinit'; put 'mpeadmins /* group with unrestricted Meditor access */'; put 'mpelocapprovals /* location for landing and staging files */'; put 'mpelib /* location of configuration tables for DC */'; put 'dc_repo_users /* location of user / group metadata */'; put 'dc_licence_key /* extracted in dc_getsettings */'; put 'dc_activation_key /* extracted in dc_getsettings */'; put 'dc_locale /* extracted in dc_getsettings */'; put 'dc_dttmtfmt /* can be overridden in dc_getsettings */'; put '_debug'; put ';'; put '%if &mpeinit=1 %then %return;'; put '%else %let mpeinit=1;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(Problem on service startup (&syswarningtext &syserrortext))'; put ')'; put '%mp_init()'; put '%if &fetch=YES %then %do;'; put '%webout(FETCH)'; put '%end;'; put '%global _CLIENTNAME;'; put '%mp_abort(iftrue= (&_CLIENTNAME=SAS Enterprise Guide)'; put ',mac=&_program..sas'; put ',msg=%str(Data Controller is a web app and should not be executed from EG)'; put ')'; put 'options urlencoding=utf8 nobomfile lrecl=32767;'; put '%let perf=%sysfunc(datetime());'; put '%put perfdiff: 0;'; put '%let dc_locale=SYSTEM; /* default if not set */'; put '/**'; put '* E8601DT26.6 has widest database support - but not all SAS flavours can'; put '* handle it. Override in the settings STP if needed.'; put '*/'; put 'data _null_;'; put 'dc_dttmtfmt=''"%sysfunc(datetime(),''!!"%mf_fmtdttm()"!!'')"dt'';'; put 'call symputx(''dc_dttmtfmt'',dc_dttmtfmt);'; put 'put dc_dttmtfmt=;'; put 'run;'; put '%put &=dc_dttmtfmt;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc prior to dc_getsettings)'; put ')'; put '%dc_getsettings()'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc after dc_getsettings)'; put ')'; put 'data _null_;'; put 'set &DC_LIBREF..mpe_config(where=('; put 'var_scope="DC"'; put 'and &dc_dttmtfmt lt tx_to'; put 'and var_active=1'; put '));'; put 'call symputx(var_name,var_value,''G'');'; put 'putlog var_name "=" var_value;'; put 'run;'; put '%let mpelib=&dc_libref;'; put '%let mpeadmins=&dc_admin_group;'; put '%let mpelocapprovals=&dc_staging_area;'; put '%let dc_repo_users=&dc_repo_users;'; put '%if &dc_locale ne SYSTEM %then %do;'; put 'options locale=&dc_locale;'; put '%end;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(Problem during compilation or with STP precode (&syswarningtext))'; put ')'; put '%mend mpeinit;'; put '%macro mf_mval(var);'; put '%if %symexist(&var) %then %do;'; put '%superq(&var)'; put '%end;'; put '%mend mf_mval;'; put '%macro mf_trimstr(basestr,trimstr);'; put '%local baselen trimlen trimval;'; put '/* return if basestr is shorter than trimstr (or 0) */'; put '%let baselen=%length(%superq(basestr));'; put '%let trimlen=%length(%superq(trimstr));'; put '%if &baselen < &trimlen or &baselen=0 %then %return;'; put '/* obtain the characters from the end of basestr */'; put '%let trimval=%qsubstr(%superq(basestr)'; put ',%length(%superq(basestr))-&trimlen+1'; put ',&trimlen);'; put '/* compare and if matching, chop it off! */'; put '%if %superq(basestr)=%superq(trimstr) %then %do;'; put '%return;'; put '%end;'; put '%else %if %superq(trimval)=%superq(trimstr) %then %do;'; put '%qsubstr(%superq(basestr),1,%length(%superq(basestr))-&trimlen)'; put '%end;'; put '%else %do;'; put '&basestr'; put '%end;'; put '%mend mf_trimstr;'; put '%macro mf_getplatform(switch'; put ')/*/STORE SOURCE*/;'; put '%local a b c;'; put '%if &switch.NONE=NONE %then %do;'; put '%if %symexist(sasjsprocessmode) %then %do;'; put '%if &sasjsprocessmode=Stored Program %then %do;'; put 'SASJS'; put '%return;'; put '%end;'; put '%end;'; put '%if %symexist(sysprocessmode) %then %do;'; put '%if "&sysprocessmode"="SAS Object Server"'; put 'or "&sysprocessmode"= "SAS Compute Server" %then %do;'; put 'SASVIYA'; put '%end;'; put '%else %if "&sysprocessmode"="SAS Stored Process Server"'; put 'or "&sysprocessmode"="SAS Workspace Server"'; put '%then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if %symexist(_metaport) or %symexist(_metauser) %then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if &switch=SASSTUDIO %then %do;'; put '/* return the version of SAS Studio else 0 */'; put '%if %mf_mval(_CLIENTAPP)=%str(SAS Studio) %then %do;'; put '%let a=%mf_mval(_CLIENTVERSION);'; put '%let b=%scan(&a,1,.);'; put '%if %eval(&b >2) %then %do;'; put '&b'; put '%end;'; put '%else 0;'; put '%end;'; put '%else 0;'; put '%end;'; put '%else %if &switch=VIYARESTAPI %then %do;'; put '%mf_trimstr(%sysfunc(getoption(servicesbaseurl)),/)'; put '%end;'; put '%mend mf_getplatform;'; put '%macro mpeterm();'; put '%local oldloc;'; put 'data _null_;'; put 'if symexist(''SYSPRINTTOLOG'') then oldloc=symget(''SYSPRINTTOLOG'');'; put 'else oldloc=getoption(''LOG'');'; put 'if subpad(oldloc,1,1) not in (''"'',"''",'' '') then oldloc=quote(cats(oldloc));'; put 'call symputx(''oldloc'',oldloc,''l'');'; put 'run;'; put '%if %length(&oldloc)>0 %then %do;'; put 'proc printto log=log;'; put 'run;'; put 'data _null_;'; put 'infile &oldloc;'; put 'input; putlog _infile_;'; put 'run;'; put '%end;'; put '%if %sysfunc(exist(&dc_libref..mpe_requests)) and %mf_getplatform() ne SASVIYA'; put '%then %do;'; put 'data ;'; put 'if 0 then set &dc_libref..mpe_requests;'; put 'request_dttm=%sysfunc(datetime());'; put 'request_user="%mf_getuser()";'; put 'request_service="%scan(&_program,-2,/)/%scan(&_program,-1,/)";'; put 'request_params='''';'; put 'output;stop;'; put 'proc append base=&dc_libref..mpe_requests data=&syslast force nowarn;'; put 'run;'; put '%end;'; put '%mend mpeterm;'; put '* SAS Macros end;'; put '* SAS Includes start;'; put '* SAS Includes end;'; put '* Binary Files start;'; put '* Binary Files end;'; put '* ServiceInit start;'; put 'options noquotelenmax ps=max;'; put '/* create dummy macros to prevent stpbegin / stpend from corrupting sessions */'; put '%macro stpbegin();'; put '%put NOTE: the STPBEGIN macro should not be used for web apps!;'; put '%mend stpbegin;'; put '%macro stpend();'; put '%put NOTE: the STPEND macro should not be used for web apps!;'; put '%mend stpend;'; put '* ServiceInit end;'; put '* Service start;'; put '/**'; put '@file'; put '@brief fetch Table Lineage for SAS 9'; put 'Some nice ideas for formatting are available here:'; put 'https://renenyffenegger.ch/notes/tools/Graphviz/examples/index'; put '

SAS Macros

'; put '@li mpeinit.sas'; put '@version 9.3'; put '@author 4GL Apps Ltd'; put '@copyright 4GL Apps Ltd. This code may only be used within Data Controller'; put 'and may not be re-distributed or re-sold without the express permission of'; put '4GL Apps Ltd.'; put '**/'; put '%mpeinit()'; put '%global table_id direction graphOrientation;'; put '/* enable table id and direction to be passed as url params */'; put '%let exist=%sysfunc(exist(work.SASControlTable));'; put '%let inds=%sysfunc(ifc(&exist=1,SASControlTable,_null_));'; put '%put &=inds;'; put '%let max_depth=50;'; put 'data _null_;'; put 'length max_depth $ 8;'; put 'set &inds;'; put 'call symputx(''table_id'',table_id);'; put 'call symputx(''direction'',direction);'; put 'call symputx(''graphOrientation'',''LR'');'; put 'if max_depth>''0'' then call symputx(''max_depth'',max_depth);'; put 'putlog (_all_)(=);'; put 'run;'; put '%put &=max_depth;'; put '%mp_abort(iftrue= (&table_id=undefined)'; put ',mac=&_program'; put ',msg=%str(Table_id UNDEFINED provided from frontend)'; put ')'; put 'data work.info;'; put 'length tableid tablename liburi $64 libref $8;'; put 'drop rc;'; put 'tableid="&table_id";'; put 'call missing(liburi);'; put 'rc=metadata_getattr(tableid,"Name",tablename);'; put 'if metadata_getnasn(tableid,"TablePackage",1,liburi)>0 then do;'; put 'rc=metadata_getattr(liburi,"Libref",libref);'; put 'libref=upcase(libref);'; put 'end;'; put 'tablename=upcase(tablename);'; put 'if missing(libref) then libref=''nolib'';'; put 'call symputx(''libds'',cats(libref,''.'',tablename));'; put 'run;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(syscc=&syscc)'; put ')'; put '%let src=%sysfunc(ifc(&direction=REVERSE,src,tgt));'; put '%let tgt=%sysfunc(ifc(&direction=REVERSE,tgt,src));'; put 'data work.sourcetable/view=work.sourcetable;'; put 'set &mpelib..MPE_LINEAGE_TABS;'; put 'where &dc_dttmtfmt. lt tx_to ;'; put 'drop tx_from tx_to;'; put 'run;'; put '%macro recursivejoin(iter=0'; put ',maxiter=&max_depth /* avoid infinite loop */'; put ');'; put '%if &iter=0 %then %do;'; put 'data work.baseds ;'; put 'retain level 0;'; put 'set work.sourcetable;'; put 'where &tgt.tableid="&table_id";'; put 'run;'; put '%let iter=1;'; put 'proc sql;'; put '%end;'; put '%else %if &iter>&maxiter %then %return;'; put 'create table work.appendds as'; put 'select distinct &iter as level'; put ',b.*'; put 'from work.baseds a'; put 'left join work.sourcetable b'; put 'on a.&src.tableid=b.&tgt.tableid'; put 'where a.level=%eval(&iter.-1)'; put 'and b.&src.tableid is not null'; put 'and a.&src.tableid is not null'; put 'and a.&src.tableid ne ''N/A'';'; put '%let obs=&sqlobs;'; put 'insert into work.baseds select * from work.appendds;'; put '%if &obs %then %do;'; put '%recursivejoin(iter=%eval(&iter.+1) )'; put '%end;'; put '%mend recursivejoin;'; put '%recursivejoin()'; put 'proc sql;'; put 'create table work.final as'; put 'select distinct *'; put 'from work.baseds(drop=level)'; put 'where jobid is not null;'; put '/* generate graphviz */'; put 'filename tmp "%sysfunc(pathname(work))\GraphViz%sysfunc(datetime()).txt"'; put 'lrecl=10000 encoding=''utf-8'';'; put '/* prepare label with metadata */'; put 'proc sql;'; put 'create table work.jobs as'; put 'select distinct jobid'; put ', jobname'; put ',quote(cats(jobid))||'' [label=''||quote(cats(jobname))||''];'' as line'; put 'from work.final;'; put 'create table work.tables as'; put 'select distinct &src.tableid as tableid'; put ',&src.tablename as tablename'; put ',&src.libref as libref'; put 'from work.final'; put 'where &src.tableid ne ''N/A'''; put 'union select'; put '&tgt.tableid as tableid'; put ',&tgt.tablename as tablename'; put ',&tgt.libref as libref'; put 'from work.final'; put 'where &tgt.tableid ne ''N/A'''; put 'order by libref, tablename;'; put 'create table idlookup as'; put 'select tableid as metaid'; put ',''TABLE'' as metatype'; put ',cats(libref,''.'',tablename) as metaname'; put 'from work.tables'; put 'union'; put 'select jobid as metaid'; put ',''JOB'' as metatype'; put ',jobname as metaname'; put 'from work.jobs'; put 'order by metaid;'; put 'data CRAYONS;'; put 'length attribute value $8;'; put 'infile datalines4 dsd;'; put 'input attribute value;'; put 'call symput(cats(''col'',_n_),quote(trim(value)));'; put 'datalines;'; put 'red,#e6194b'; put 'green,#3cb44b'; put 'blue,#4363d8'; put 'orange,#f58231'; put 'purple,#911eb4'; put 'cyan,#46f0f0'; put 'magenta,#f032e6'; put 'lime,#bcf60c'; put 'pink,#fabebe'; put 'teal,#008080'; put 'lavender,#e6beff'; put 'brown,#9a6324'; put 'beige,#fffac8'; put 'maroon,#800000'; put 'mint,#aaffc3'; put 'olive,#808000'; put 'apricot,#ffd8b1'; put 'navy,#000075'; put 'gray,#808080'; put 'black,#00000'; put 'yellow,#ffe119'; put 'white,#ffffff'; put ';;;;'; put 'run;'; put 'proc sort data=work.tables out=work.libs nodupkey;'; put 'by libref;'; put 'run;'; put 'data work.alllibs;'; put 'set work.libs end=last;'; put 'length line $1000. ;'; put 'crayon=symget(cats(''col'',_n_));'; put 'call symputx(libref,crayon);'; put 'if _n_=1 then do;'; put 'line=''subgraph cluster_libs { label="Libraries";'';output;'; put 'end;'; put 'line=cats(libref)!!'' [label=''||quote(cats(libref))||''; style="filled"; color='''; put '||cats(crayon)||'', shape = Mrecord, fontcolor=white]'';output;'; put 'if last then do;'; put 'line=''}'';output;'; put 'end;'; put 'run;'; put 'data alltables;'; put 'length line $1000. ;'; put 'set work.tables;'; put 'crayon=symget(libref);'; put 'line=quote(cats(tableid))||'' [label="''||cats(tablename)'; put '!!''", color=''!!cats(crayon)'; put '!!'', shape=cylinder,style=filled,fontcolor=white];'';'; put 'output;'; put 'run;'; put 'proc sort'; put 'data=final(keep=&src.tableid jobid &src.libref) out=&src.relations nodupkey;'; put 'by &src.tableid jobid;'; put 'proc sort'; put 'data=final(keep=&tgt.tableid jobid &tgt.libref) out=&tgt.relations nodupkey;'; put 'by jobid &tgt.tableid;'; put 'run;'; put 'data srcrelations;'; put 'set srcrelations;'; put 'length line $1000;'; put 'where srctableid ne ''N/A'';'; put 'line=cats('; put '''"'',cats(srctableid),''" -> "'',jobid,''" [color='',symget(srclibref),''];'''; put ');'; put 'data tgtrelations;'; put 'set tgtrelations;'; put 'where tgttableid ne ''N/A'';'; put 'length line $1000;'; put 'line=cats('; put '''"'',cats(jobid),''" -> "'',tgttableid,''" [color='',symget(tgtlibref),''];'''; put ');'; put 'run;'; put 'data finalfinal;'; put 'set work.alllibs(keep=line) work.alltables (keep=line) work.jobs(keep=line)'; put 'work.&src.relations(keep=line) work.&tgt.relations(keep=line) end=last;'; put 'if _N_ = 1 then do;'; put 'firstline=line;'; put 'line=''strict digraph "''!!"&libds"!!''" {''; output;'; put 'line="rankdir=&graphOrientation; nodesep=0.5; node [shape = octagon];";output;'; put 'line=firstline;'; put 'end;'; put 'output;'; put 'if last then do;'; put 'line=''}'';'; put 'output;'; put 'end;'; put 'drop firstline;'; put 'run;'; put '%webout(OPEN)'; put '%webout(OBJ,finalfinal)'; put '%webout(OBJ,info)'; put '%webout(OBJ,final,dslabel=flatdata)'; put '%webout(OBJ,idlookup)'; put '%webout(CLOSE)'; put '%mpeterm()'; put '* Service end;'; run; %mm_createwebservice(path=&appLoc/&path, name=&service, code=sascode, server=&serverName, replace=yes) filename sascode clear; %let service=getmetacols; filename sascode temp lrecl=32767; data _null_; file sascode; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '/**'; put '@file mp_jsonout.sas'; put '@brief Writes JSON in SASjs format to a fileref'; put '@details This macro can be used to OPEN a JSON stream and send one or more'; put 'tables as arrays of rows, where each row can be an object or a nested array.'; put 'There are two engines available - DATASTEP or PROCJSON.'; put 'PROC JSON is fast but will produce errs like the ones below if'; put 'special chars are encountered.'; put '> (ERR)OR: Some code points did not transcode.'; put '> An object or array close is not valid at this point in the JSON text.'; put '> Date value out of range'; put 'If this happens, try running with ENGINE=DATASTEP.'; put 'The DATASTEP engine is used to handle special SAS missing numerics, and'; put 'can also convert entire datasets to formatted values. Output JSON is always'; put 'in UTF-8.'; put 'Usage:'; put 'filename tmp temp;'; put 'data class; set sashelp.class;run;'; put '%mp_jsonout(OPEN,jref=tmp)'; put '%mp_jsonout(OBJ,class,jref=tmp)'; put '%mp_jsonout(OBJ,class,dslabel=class2,jref=tmp,showmeta=Y)'; put '%mp_jsonout(CLOSE,jref=tmp)'; put 'data _null_;'; put 'infile tmp;'; put 'input;putlog _infile_;'; put 'run;'; put 'If you are building web apps with SAS then you are strongly encouraged to use'; put 'the mX_createwebservice macros in combination with the'; put '[sasjs adapter](https://github.com/sasjs/adapter).'; put 'For more information see https://sasjs.io'; put '@param [in] action Valid values:'; put '@li OPEN - opens the JSON'; put '@li OBJ - sends a table with each row as an object'; put '@li ARR - sends a table with each row in an array'; put '@li CLOSE - closes the JSON'; put '@param [in] ds The dataset to send. Must be a work table.'; put '@param [out] jref= (_webout) The fileref to which to send the JSON'; put '@param [out] dslabel= The name to give the table in the exported JSON'; put '@param [in] fmt= (Y) Whether to keep (Y) or strip (N) formats from the table'; put '@param [in] engine= (DATASTEP) Which engine to use to send the JSON. Options:'; put '@li PROCJSON (default)'; put '@li DATASTEP (more reliable when data has non standard characters)'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to JSON'; put '

Related Files

'; put '@li mp_ds2fmtds.sas'; put '@version 9.2'; put '@author Allan Bowe'; put '@source https://github.com/sasjs/core'; put '**/'; put '%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y'; put ',engine=DATASTEP'; put ',missing=NULL'; put ',showmeta=N'; put ',maxobs=MAX'; put ')/*/STORE SOURCE*/;'; put '%local tempds colinfo fmtds i numcols numobs stmt_obs lastobs optval'; put 'tmpds1 tmpds2 tmpds3 tmpds4;'; put '%let numcols=0;'; put '%if &maxobs ne MAX %then %let stmt_obs=%str(if _n_>&maxobs then stop;);'; put '%if &action=OPEN %then %do;'; put 'options nobomfile;'; put 'data _null_;file &jref encoding=''utf-8'' lrecl=200;'; put 'put ''{"PROCESSED_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''"'';'; put 'run;'; put '%end;'; put '%else %if (&action=ARR or &action=OBJ) %then %do;'; put '/* force variable names to always be uppercase in the JSON */'; put 'options validvarname=upcase;'; put '/* To avoid issues with _webout on EBI - such as encoding diffs and truncation'; put '(https://support.sas.com/kb/49/325.html) we use temporary files */'; put 'filename _sjs1 temp lrecl=200 ;'; put 'data _null_; file _sjs1 encoding=''utf-8'';'; put 'put ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":";'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjs1 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs1 clear;'; put '/* grab col defs */'; put 'proc contents noprint data=&ds'; put 'out=_data_(keep=name type length format formatl formatd varnum label);'; put 'run;'; put '%let colinfo=%scan(&syslast,2,.);'; put 'proc sort data=&colinfo;'; put 'by varnum;'; put 'run;'; put '/* move meta to mac vars */'; put 'data &colinfo;'; put 'if _n_=1 then call symputx(''numcols'',nobs,''l'');'; put 'set &colinfo end=last nobs=nobs;'; put 'name=upcase(name);'; put '/* fix formats */'; put 'if type=2 or type=6 then do;'; put 'typelong=''char'';'; put 'length fmt $49.;'; put 'if format='''' then fmt=cats(''$'',length,''.'');'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else fmt=cats(format,formatl,''.'');'; put 'end;'; put 'else do;'; put 'typelong=''num'';'; put 'if format='''' then fmt=''best.'';'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else if formatd=0 then fmt=cats(format,formatl,''.'');'; put 'else fmt=cats(format,formatl,''.'',formatd);'; put 'end;'; put '/* 32 char unique name */'; put 'newname=''sasjs''!!substr(cats(put(md5(name),$hex32.)),1,27);'; put 'call symputx(cats(''name'',_n_),name,''l'');'; put 'call symputx(cats(''newname'',_n_),newname,''l'');'; put 'call symputx(cats(''length'',_n_),length,''l'');'; put 'call symputx(cats(''fmt'',_n_),fmt,''l'');'; put 'call symputx(cats(''type'',_n_),type,''l'');'; put 'call symputx(cats(''typelong'',_n_),typelong,''l'');'; put 'call symputx(cats(''label'',_n_),coalescec(label,name),''l'');'; put '/* overwritten when fmt=Y and a custom format exists in catalog */'; put 'if typelong=''num'' then call symputx(cats(''fmtlen'',_n_),200,''l'');'; put 'else call symputx(cats(''fmtlen'',_n_),min(32767,ceil((length+10)*1.5)),''l'');'; put 'run;'; put '%let tempds=%substr(_%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql;'; put 'select count(*) into: lastobs from &ds;'; put '%if &maxobs ne MAX %then %let lastobs=%sysfunc(min(&lastobs,&maxobs));'; put '%if &engine=PROCJSON %then %do;'; put '%if &missing=STRING %then %do;'; put '%put &sysmacroname: Special Missings not supported in proc json.;'; put '%put &sysmacroname: Switching to DATASTEP engine;'; put '%goto datastep;'; put '%end;'; put 'data &tempds;'; put 'set &ds;'; put '&stmt_obs;'; put '%if &fmt=N %then format _numeric_ best32.;;'; put '/* PRETTY is necessary to avoid line truncation in large files */'; put 'filename _sjs2 temp lrecl=131068 encoding=''utf-8'';'; put 'proc json out=_sjs2 pretty'; put '%if &action=ARR %then nokeys ;'; put ';export &tempds / nosastags fmtnumeric;'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs2 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs2 clear;'; put '%end;'; put '%else %if &engine=DATASTEP %then %do;'; put '%datastep:'; put '%if %sysfunc(exist(&ds)) ne 1 & %sysfunc(exist(&ds,VIEW)) ne 1'; put '%then %do;'; put '%put &sysmacroname: &ds NOT FOUND!!!;'; put '%return;'; put '%end;'; put '%if &fmt=Y %then %do;'; put '/**'; put '* Extract format definitions'; put '* First, by getting library locations from dictionary.formats'; put '* Then, by exporting the width using proc format'; put '* Cannot use maxw from sashelp.vformat as not always populated'; put '* Cannot use fmtinfo() as not supported in all flavours'; put '*/'; put '%let tmpds1=%substr(fmtsum%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds2=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds3=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds4=%substr(col%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql noprint;'; put 'create table &tmpds1 as'; put 'select cats(libname,''.'',memname) as FMTCAT,'; put 'FMTNAME'; put 'from dictionary.formats'; put 'where fmttype=''F'' and libname is not null'; put 'and fmtname in (select format from &colinfo where format is not null)'; put 'order by 1;'; put 'create table &tmpds2('; put 'FMTNAME char(32),'; put 'LENGTH num'; put ');'; put '%local catlist cat fmtlist i;'; put 'select distinct fmtcat into: catlist separated by '' '' from &tmpds1;'; put '%do i=1 %to %sysfunc(countw(&catlist,%str( )));'; put '%let cat=%scan(&catlist,&i,%str( ));'; put 'proc sql;'; put 'select distinct fmtname into: fmtlist separated by '' '''; put 'from &tmpds1 where fmtcat="&cat";'; put 'proc format lib=&cat cntlout=&tmpds3(keep=fmtname length);'; put 'select &fmtlist;'; put 'run;'; put 'proc sql;'; put 'insert into &tmpds2 select distinct fmtname,length from &tmpds3;'; put '%end;'; put 'proc sql;'; put 'create table &tmpds4 as'; put 'select a.*, b.length as MAXW'; put 'from &colinfo a'; put 'left join &tmpds2 b'; put 'on cats(a.format)=cats(upcase(b.fmtname))'; put 'order by a.varnum;'; put 'data _null_;'; put 'set &tmpds4;'; put 'if not missing(maxw);'; put 'call symputx('; put 'cats(''fmtlen'',_n_),'; put '/* vars need extra padding due to JSON escaping of special chars */'; put 'min(32767,ceil((max(length,maxw)+10)*1.5))'; put ',''l'''; put ');'; put 'run;'; put '/* configure varlenchk - as we are explicitly shortening the variables */'; put '%let optval=%sysfunc(getoption(varlenchk));'; put 'options varlenchk=NOWARN;'; put 'data _data_(compress=char);'; put '/* shorten the new vars */'; put 'length'; put '%do i=1 %to &numcols;'; put '&&name&i $&&fmtlen&i'; put '%end;'; put ';'; put '/* rename on entry */'; put 'set &ds(rename=('; put '%do i=1 %to &numcols;'; put '&&name&i=&&newname&i'; put '%end;'; put '));'; put '&stmt_obs;'; put 'drop'; put '%do i=1 %to &numcols;'; put '&&newname&i'; put '%end;'; put ';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=num %then %do;'; put '&&name&i=cats(put(&&newname&i,&&fmt&i));'; put '%end;'; put '%else %do;'; put '&&name&i=put(&&newname&i,&&fmt&i);'; put '%end;'; put '%end;'; put 'if _error_ then do;'; put 'call symputx(''syscc'',1012);'; put 'stop;'; put 'end;'; put 'run;'; put '%let fmtds=&syslast;'; put 'options varlenchk=&optval;'; put '%end;'; put 'proc format; /* credit yabwon for special null removal */'; put 'value bart (default=40)'; put '%if &missing=NULL %then %do;'; put '._ - .z = null'; put '%end;'; put '%else %do;'; put '._ = [quote()]'; put '. = null'; put '.a - .z = [quote()]'; put '%end;'; put 'other = [best.];'; put 'data &tempds;'; put 'attrib _all_ label='''';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'length &&name&i $&&fmtlen&i...;'; put 'format &&name&i $&&fmtlen&i...;'; put '%end;'; put '%end;'; put '%if &fmt=Y %then %do;'; put 'set &fmtds;'; put '%end;'; put '%else %do;'; put 'set &ds;'; put '%end;'; put '&stmt_obs;'; put 'format _numeric_ bart.;'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'if findc(&&name&i,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put '&&name&i=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,&&name&i)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else &&name&i=quote(cats(&&name&i));'; put '%end;'; put '%end;'; put 'run;'; put 'filename _sjs3 temp lrecl=131068 ;'; put 'data _null_;'; put 'file _sjs3 encoding=''utf-8'';'; put 'if _n_=1 then put "[";'; put 'set &tempds;'; put 'if _n_>1 then put "," @; put'; put '%if &action=ARR %then "[" ; %else "{" ;'; put '%do i=1 %to &numcols;'; put '%if &i>1 %then "," ;'; put '%if &action=OBJ %then """&&name&i"":" ;'; put '"&&name&i"n /* name literal for reserved variable names */'; put '%end;'; put '%if &action=ARR %then "]" ; %else "}" ; ;'; put '/* close out the table */'; put 'data _null_;'; put 'file _sjs3 mod encoding=''utf-8'';'; put 'put '']'';'; put 'run;'; put 'data _null_;'; put 'infile _sjs3 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs3 clear;'; put '%end;'; put 'proc sql;'; put 'drop table &colinfo, &tempds;'; put '%if %substr(&showmeta,1,1)=Y %then %do;'; put 'filename _sjs4 temp lrecl=131068 encoding=''utf-8'';'; put 'data _null_;'; put 'file _sjs4;'; put 'length label $350;'; put 'put ", ""$%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":{""vars"":{";'; put 'do i=1 to &numcols;'; put 'name=quote(trim(symget(cats(''name'',i))));'; put 'format=quote(trim(symget(cats(''fmt'',i))));'; put 'label=quote(prxchange(''s/\\/\\\\/'',-1,trim(symget(cats(''label'',i)))));'; put 'length=quote(trim(symget(cats(''length'',i))));'; put 'type=quote(trim(symget(cats(''typelong'',i))));'; put 'if i>1 then put "," @@;'; put 'put name '':{"format":'' format '',"label":'' label'; put ''',"length":'' length '',"type":'' type ''}'';'; put 'end;'; put 'put ''}}'';'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs4 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs4 clear;'; put '%end;'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put 'data _null_; file &jref encoding=''utf-8'' mod ;'; put 'put "}";'; put 'run;'; put '%end;'; put '%mend mp_jsonout;'; put '/**'; put '@file mm_webout.sas'; put '@brief Send data to/from SAS Stored Processes'; put '@details This macro should be added to the start of each Stored Process,'; put '**immediately** followed by a call to:'; put '%mm_webout(FETCH)'; put 'This will read all the input data and create same-named SAS datasets in the'; put 'WORK library. You can then insert your code, and send data back using the'; put 'following syntax:'; put 'data some datasets; * make some data ;'; put 'retain some columns;'; put 'run;'; put '%mm_webout(OPEN)'; put '%mm_webout(ARR,some) * Array format, fast, suitable for large tables ;'; put '%mm_webout(OBJ,datasets) * Object format, easier to work with ;'; put 'Finally, wrap everything up send some helpful system variables too'; put '%mm_webout(CLOSE)'; put '@param [in] action Either FETCH, OPEN, ARR, OBJ or CLOSE'; put '@param [in] ds The dataset to send back to the frontend'; put '@param [out] dslabel= Value to use instead of table name for sending to JSON'; put '@param [in] fmt= (N) Setting Y converts all vars to their formatted values'; put '@param [out] fref= (_webout) The fileref to which to write the JSON'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] workobs= (0) When set to a positive integer, will create a new'; put 'output object (WORK) which contains this number of observations from all'; put 'tables in the WORK library.'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to output JSON'; put '

SAS Macros

'; put '@li mp_jsonout.sas'; put '

Related Macros

'; put '@li ms_webout.sas'; put '@li mv_webout.sas'; put '@version 9.3'; put '@author Allan Bowe'; put '**/'; put '%macro mm_webout(action,ds,dslabel=,fref=_webout,fmt=N,missing=NULL'; put ',showmeta=N,maxobs=MAX,workobs=0'; put ');'; put '%global _webin_file_count _webin_fileref1 _webin_name1 _program _debug'; put 'sasjs_tables;'; put '%local i tempds jsonengine;'; put '/* see https://github.com/sasjs/core/issues/41 */'; put '%if "%upcase(&SYSENCODING)" ne "UTF-8" %then %let jsonengine=PROCJSON;'; put '%else %let jsonengine=DATASTEP;'; put '%if &action=FETCH %then %do;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'options mprint notes mprintnest;'; put '%end;'; put '%let _webin_file_count=%eval(&_webin_file_count+0);'; put '/* now read in the data */'; put '%do i=1 %to &_webin_file_count;'; put '%if &_webin_file_count=1 %then %do;'; put '%let _webin_fileref1=&_webin_fileref;'; put '%let _webin_name1=&_webin_name;'; put '%end;'; put 'data _null_;'; put 'infile &&_webin_fileref&i termstr=crlf;'; put 'input;'; put 'call symputx(''input_statement'',_infile_);'; put 'putlog "&&_webin_name&i input statement: " _infile_;'; put 'stop;'; put 'data &&_webin_name&i;'; put 'infile &&_webin_fileref&i firstobs=2 dsd termstr=crlf encoding=''utf-8'';'; put 'input &input_statement;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'if _n_<20 then putlog _infile_;'; put '%end;'; put 'run;'; put '%let sasjs_tables=&sasjs_tables &&_webin_name&i;'; put '%end;'; put '%end;'; put '%else %if &action=OPEN %then %do;'; put '/* fix encoding */'; put 'OPTIONS NOBOMFILE;'; put '/**'; put '* check xengine type to avoid the below err message:'; put '* > Function is only valid for filerefs using the CACHE access method.'; put '*/'; put 'data _null_;'; put 'set sashelp.vextfl(where=(fileref="_WEBOUT"));'; put 'if xengine=''STREAM'' then do;'; put 'rc=stpsrv_header(''Content-type'',"text/html; encoding=utf-8");'; put 'end;'; put 'run;'; put '/* setup json */'; put 'data _null_;file &fref encoding=''utf-8'';'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutBEGIN<<'';'; put '%end;'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'run;'; put '%end;'; put '%else %if &action=ARR or &action=OBJ %then %do;'; put '%mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt,jref=&fref'; put ',engine=&jsonengine,missing=&missing,showmeta=&showmeta,maxobs=&maxobs'; put ')'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put '/* To avoid issues with _webout on EBI we use a temporary file */'; put 'filename _sjsref temp lrecl=131068;'; put '%if %str(&workobs) > 0 %then %do;'; put '/* if debug mode, send back first XX records of each work table also */'; put 'data;run;%let tempds=%scan(&syslast,2,.);'; put 'ods output Members=&tempds;'; put 'proc datasets library=WORK memtype=data;'; put '%local wtcnt;%let wtcnt=0;'; put 'data _null_;'; put 'set &tempds;'; put 'if not (upcase(name) =:"DATA"); /* ignore temp datasets */'; put 'i+1;'; put 'call symputx(cats(''wt'',i),name,''l'');'; put 'call symputx(''wtcnt'',i,''l'');'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put ",""WORK"":{";'; put '%do i=1 %to &wtcnt;'; put '%let wt=&&wt&i;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'dsid=open("WORK.&wt",''is'');'; put 'nlobs=attrn(dsid,''NLOBS'');'; put 'nvars=attrn(dsid,''NVARS'');'; put 'rc=close(dsid);'; put 'if &i>1 then put '',''@;'; put 'put " ""&wt"" : {";'; put 'put ''"nlobs":'' nlobs;'; put 'put '',"nvars":'' nvars;'; put '%mp_jsonout(OBJ,&wt,jref=_sjsref,dslabel=first10rows,showmeta=Y'; put ',maxobs=&workobs'; put ')'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put '%end;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put 'run;'; put '%end;'; put '/* close off json */'; put 'data _null_;file _sjsref mod encoding=''utf-8'';'; put 'length SYSPROCESSNAME syserrortext syswarningtext autoexec $512;'; put 'put ",""_DEBUG"" : ""&_debug"" ";'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'autoexec=quote(urlencode(trim(getoption(''autoexec''))));'; put 'put '',"AUTOEXEC" : '' autoexec;'; put 'put ",""MF_GETUSER"" : ""%mf_getuser()"" ";'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'put ",""SYSENCODING"" : ""&sysencoding"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSPROCESSID"" : ""&SYSPROCESSID"" ";'; put 'put ",""SYSPROCESSMODE"" : ""&SYSPROCESSMODE"" ";'; put 'SYSPROCESSNAME=quote(urlencode(cats(SYSPROCESSNAME)));'; put 'put ",""SYSPROCESSNAME"" : " SYSPROCESSNAME;'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put '',"SYSWARNINGTEXT" : '' syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'length memsize $32;'; put 'memsize="%sysfunc(INPUTN(%sysfunc(getoption(memsize)), best.),sizekmg.)";'; put 'memsize=quote(cats(memsize));'; put 'put '',"MEMSIZE" : '' memsize;'; put 'put "}" @;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutEND<<'';'; put '%end;'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjsref lrecl=1 recfm=n;'; put 'file &fref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjsref clear;'; put '%end;'; put '%mend mm_webout;'; put '%macro webout(action,ds,dslabel=,fmt=,missing=NULL,showmeta=NO,maxobs=MAX);'; put '%mm_webout(&action,ds=&ds,dslabel=&dslabel,fmt=&fmt'; put ',missing=&missing'; put ',showmeta=&showmeta'; put ',maxobs=&maxobs'; put ') %mend;'; put '/* provide additional debug info */'; put '%global _program;'; put '%put &=syscc;'; put '%put user=%mf_getuser();'; put '%put pgm=&_program;'; put '%put timestamp=%sysfunc(datetime(),datetime19.);'; put '* Service Variables start;'; put '* Service Variables end;'; put '* SAS Macros start;'; put '%macro mf_getapploc(pgm);'; put '%if "&pgm"="" %then %do;'; put '%if %symexist(_program) %then %let pgm=&_program;'; put '%else %do;'; put '%put &sysmacroname: No value provided and no _program variable available;'; put '%return;'; put '%end;'; put '%end;'; put '%local root;'; put '/**'; put '* First check we are not in the tests/macros folder (which has no subfolders)'; put '* or specifically in the testsetup or testteardown services'; put '*/'; put '%if %index(&pgm,/tests/macros/)'; put 'or %index(&pgm,/tests/testsetup)'; put 'or %index(&pgm,/tests/testteardown)'; put '%then %do;'; put '%let root=%substr(&pgm,1,%index(&pgm,/tests)-1);'; put '&root'; put '%return;'; put '%end;'; put '/**'; put '* Next, move up two levels to avoid matches on subfolder or service name'; put '*/'; put '%let root=%substr(&pgm,1,%length(&pgm)-%length(%scan(&pgm,-1,/))-1);'; put '%let root=%substr(&root,1,%length(&root)-%length(%scan(&root,-1,/))-1);'; put '%if %index(&root,/tests/) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/tests/)-1);'; put '%end;'; put '%else %if %index(&root,/services) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/services)-1);'; put '%end;'; put '%else %if %index(&root,/jobs) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/jobs)-1);'; put '%end;'; put '%else %put &sysmacroname: Could not find an app location from &pgm;'; put '&root'; put '%mend mf_getapploc ;'; put '%macro mf_getuniquefileref(prefix=_,maxtries=1000,lrecl=32767);'; put '%local rc fname;'; put '%if &prefix=0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%end;'; put '%else %do;'; put '%local x len;'; put '%let len=%eval(8-%length(&prefix));'; put '%let x=0;'; put '%do x=0 %to &maxtries;'; put '%let fname=&prefix%substr(%sysfunc(ranuni(0)),3,&len);'; put '%if %sysfunc(fileref(&fname)) > 0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%return;'; put '%end;'; put '%end;'; put '%put unable to find available fileref after &maxtries attempts;'; put '%end;'; put '%mend mf_getuniquefileref;'; put '%macro mp_abort(mac=mp_abort.sas, type=, msg=, iftrue=%str(1=1)'; put ', errds=work.mp_abort_errds'; put ', mode=REGULAR'; put ')/*/STORE SOURCE*/;'; put '%global sysprocessmode sysprocessname sasjs_stpsrv_header_loc sasjsprocessmode;'; put '%local fref fid i;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%put NOTE: /// mp_abort macro executing //;'; put '%if %length(&mac)>0 %then %put NOTE- called by &mac;'; put '%put NOTE - &msg;'; put '%if %symexist(_SYSINCLUDEFILEDEVICE)'; put '/* abort cancel FILE does not restart outside the INCLUDE on Viya 3.5 */'; put 'and %superq(SYSPROCESSNAME) ne %str(Compute Server)'; put '%then %do;'; put '%if "*&_SYSINCLUDEFILEDEVICE*" ne "**" %then %do;'; put 'data &errds;'; put 'iftrue=''1=1'';'; put 'length mac $100 msg $5000;'; put 'mac=symget(''mac'');'; put 'msg=symget(''msg'');'; put 'run;'; put 'data _null_;'; put 'abort cancel FILE;'; put 'run;'; put '%return;'; put '%end;'; put '%end;'; put '/* Web App Context */'; put '%if %symexist(_PROGRAM)'; put 'or %superq(SYSPROCESSNAME) = %str(Compute Server)'; put 'or &mode=INCLUDE'; put '%then %do;'; put 'options obs=max replace mprint;'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5"'; put '%then %do;'; put 'options nosyntaxcheck;'; put '%end;'; put '%if &mode=INCLUDE %then %do;'; put '%if %sysfunc(exist(&errds))=1 %then %do;'; put 'data _null_;'; put 'set &errds;'; put 'call symputx(''iftrue'',iftrue,''l'');'; put 'call symputx(''mac'',mac,''l'');'; put 'call symputx(''msg'',msg,''l'');'; put 'putlog (_all_)(=);'; put 'run;'; put '%if (&iftrue)=0 %then %return;'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: No include errors found;'; put '%return;'; put '%end;'; put '%end;'; put '/* extract log errs / warns, if exist */'; put '%local logloc logline;'; put '%global logmsg; /* capture global messages */'; put '%if %symexist(SYSPRINTTOLOG) %then %let logloc=&SYSPRINTTOLOG;'; put '%else %let logloc=%qsysfunc(getoption(LOG));'; put 'proc printto log=log;run;'; put '%let logline=0;'; put '%if %length(&logloc)>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input; putlog _infile_;'; put 'i=1;'; put 'retain logonce 0;'; put 'if ('; put '_infile_=:"%str(WARN)ING" or _infile_=:"%str(ERR)OR"'; put ') and logonce=0 then'; put 'do;'; put 'call symputx(''logline'',_n_);'; put 'logonce+1;'; put 'end;'; put 'run;'; put '/* capture log including lines BEFORE the err */'; put '%if &logline>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input;'; put 'i=1;'; put 'stoploop=0;'; put 'if _n_ ge &logline-15 and stoploop=0 then do until (i>22);'; put 'call symputx(''logmsg'',catx(''\n'',symget(''logmsg''),_infile_));'; put 'input;'; put 'i+1;'; put 'stoploop=1;'; put 'end;'; put 'if stoploop=1 then stop;'; put 'run;'; put '%end;'; put '%end;'; put '%if %symexist(SYS_JES_JOB_URI) %then %do;'; put '/* setup webout for Viya */'; put 'options nobomfile;'; put '%if "X&SYS_JES_JOB_URI.X"="XX" %then %do;'; put 'filename _webout temp lrecl=999999 mod;'; put '%end;'; put '%else %do;'; put 'filename _webout filesrvc parenturi="&SYS_JES_JOB_URI"'; put 'name="_webout.json" lrecl=999999 mod;'; put '%end;'; put '%end;'; put '%else %if %sysfunc(filename(fref,&sasjs_stpsrv_header_loc))=0 %then %do;'; put 'options nobomfile;'; put '/* set up http header for SASjs Server */'; put '%let fid=%sysfunc(fopen(&fref,A));'; put '%if &fid=0 %then %do;'; put '%put %str(ERR)OR: %sysfunc(sysmsg());'; put '%return;'; put '%end;'; put '%let rc=%sysfunc(fput(&fid,%str(Content-Type: application/json)));'; put '%let rc=%sysfunc(fwrite(&fid));'; put '%let rc=%sysfunc(fclose(&fid));'; put '%let rc=%sysfunc(filename(&fref));'; put '%end;'; put '/* send response in SASjs JSON format */'; put 'data _null_;'; put 'file _webout mod lrecl=32000 encoding=''utf-8'';'; put 'length msg syswarningtext syserrortext $32767 mode $10 ;'; put 'sasdatetime=datetime();'; put 'msg=symget(''msg'');'; put '%if &logline>0 %then %do;'; put 'msg=cats(msg,''\n\nLog Extract:\n'',symget(''logmsg''));'; put '%end;'; put '/* escape the escapes */'; put 'msg=tranwrd(msg,''\'',''\\'');'; put '/* escape the quotes */'; put 'msg=tranwrd(msg,''"'',''\"'');'; put '/* ditch the CRLFs as chrome complains */'; put 'msg=compress(msg,,''kw'');'; put '/* quote without quoting the quotes (which are escaped instead) */'; put 'msg=cats(''"'',msg,''"'');'; put 'if symexist(''_debug'') then debug=quote(trim(symget(''_debug'')));'; put 'else debug=''""'';'; put 'if symget(''sasjsprocessmode'')=''Stored Program'' then mode=''SASJS'';'; put 'if mode ne ''SASJS'' then put ''>>weboutBEGIN<<'';'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'put '',"sasjsAbort" : [{'';'; put 'put '' "MSG":'' msg ;'; put 'put '' ,"MAC": "'' "&mac" ''"}]'';'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'put '',"_DEBUG":'' debug ;'; put 'if symexist(''_metauser'') then do;'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put 'end;'; put 'if symexist(''SYS_JES_JOB_URI'') then do;'; put 'SYS_JES_JOB_URI=quote(trim(symget(''SYS_JES_JOB_URI'')));'; put 'put '',"SYS_JES_JOB_URI": '' SYS_JES_JOB_URI;'; put 'end;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put ",""SYSWARNINGTEXT"" : " syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'put "}" ;'; put 'if mode ne ''SASJS'' then put ''>>weboutEND<<'';'; put 'run;'; put '%put _all_;'; put '%if "&sysprocessmode " = "SAS Stored Process Server " %then %do;'; put 'data _null_;'; put 'putlog ''stpsrvset program err and syscc'';'; put 'rc=stpsrvset(''program error'', 0);'; put 'call symputx("syscc",0,"g");'; put 'run;'; put '%if &sysscp=WIN'; put 'and 1=0 /* deprecating this logic until we figure out a consistent abort */'; put 'and "%substr(%str(&sysvlong ),1,8)"="9.04.01M"'; put 'and "%substr(%str(&sysvlong ),9,1)">"5" %then %do;'; put '/* skip approach (below) does not work in windows m6+ envs */'; put 'endsas;'; put '%end;'; put '%else %do;'; put '/**'; put '* endsas kills 9.4m3 deployments by orphaning multibridges.'; put '* Abort variants are ungraceful (non zero return code)'; put '* This approach lets SAS run silently until the end :-)'; put '* Caution - fails when called within a %include within a macro'; put '* Use mp_include() to handle this.'; put '*/'; put 'filename skip temp;'; put 'data _null_;'; put 'file skip;'; put 'put ''%macro skip();'';'; put 'comment ''%mend skip; -> fix lint '';'; put 'put ''%macro skippy();'';'; put 'comment ''%mend skippy; -> fix lint '';'; put 'run;'; put '%inc skip;'; put '%end;'; put '%end;'; put '%else %if "&sysprocessmode " = "SAS Compute Server " %then %do;'; put '/* endsas kills the session making it harder to fetch results */'; put 'data _null_;'; put 'syswarningtext=symget(''syswarningtext'');'; put 'syserrortext=symget(''syserrortext'');'; put 'abort_msg=symget(''msg'');'; put 'syscc=symget(''syscc'');'; put 'sysuserid=symget(''sysuserid'');'; put 'iftrue=symget(''iftrue'');'; put 'put (_all_)(/=);'; put 'call symputx(''syscc'',0);'; put 'abort cancel nolist;'; put 'run;'; put '%end;'; put '%else %do;'; put '%abort cancel;'; put '%end;'; put '%end;'; put '%else %do;'; put '%put _all_;'; put '%abort cancel;'; put '%end;'; put '%mend mp_abort;'; put '/** @endcond */'; put '%macro mm_getstpcode('; put 'tree=/User Folders/sasdemo/somestp'; put ',name='; put ',outloc=0'; put ',outref=0'; put ',mDebug=1'; put ',showlog=NO'; put ');'; put '%local mD;'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing &sysmacroname..sas;'; put '%&mD.put _local_;'; put '%if %length(&name)>0 %then %let name=/&name;'; put '/* first, check if STP exists */'; put '%local tsuri;'; put '%let tsuri=stopifempty ;'; put 'data _null_;'; put 'format type uri tsuri value $200.;'; put 'call missing (of _all_);'; put 'path="&tree&name(StoredProcess)";'; put '/* first, find the STP ID */'; put 'if metadata_pathobj("",path,"StoredProcess",type,uri)>0 then do;'; put '/* get sourcecode */'; put 'cnt=1;'; put 'do while (metadata_getnasn(uri,"Notes",cnt,tsuri)>0);'; put 'rc=metadata_getattr(tsuri,"Name",value);'; put '&mD.put tsuri= value=;'; put 'if value="SourceCode" then do;'; put '/* found it! */'; put 'rc=metadata_getattr(tsuri,"Id",value);'; put 'call symputx(''tsuri'',value,''l'');'; put 'stop;'; put 'end;'; put 'cnt+1;'; put 'end;'; put 'end;'; put 'else put (_all_)(=);'; put 'run;'; put '%mp_abort(iftrue= (&tsuri=stopifempty)'; put ',mac=mm_getstpcode'; put ',msg=%str(&tree&name.(StoredProcess) not found!)'; put ')'; put '/**'; put '* Now we can extract the textstore'; put '*/'; put 'filename __getdoc temp lrecl=10000000;'; put 'proc metadata'; put 'in="$METAREPOSITORY'; put ''; put 'SAS1"'; put 'out=__getdoc ;'; put 'run;'; put '/* find the beginning of the text */'; put '%local start;'; put 'data _null_;'; put 'infile __getdoc lrecl=10000;'; put 'input;'; put 'start=index(_infile_,''StoredText="'');'; put 'if start then do;'; put 'call symputx("start",start+11);'; put '*putlog ''"'' _infile_ ''"'';'; put 'end;'; put 'stop;'; put '%local outeng;'; put '%if "&outloc"="0" %then %let outeng=TEMP;'; put '%else %let outeng="&outloc";'; put '%local fref;'; put '%if &outref=0 %then %let fref=%mf_getuniquefileref();'; put '%else %let fref=&outref;'; put '/* read the content, byte by byte, resolving escaped chars */'; put 'filename &fref &outeng lrecl=100000;'; put 'data _null_;'; put 'length filein 8 fileid 8;'; put 'filein = fopen("__getdoc","I",1,"B");'; put 'fileid = fopen("&fref","O",1,"B");'; put 'rec = "20"x;'; put 'length entity $6;'; put 'do while(fread(filein)=0);'; put 'x+1;'; put 'if x>&start then do;'; put 'rc = fget(filein,rec,1);'; put 'if rec=''"'' then leave;'; put 'else if rec="&" then do;'; put 'entity=rec;'; put 'do until (rec=";");'; put 'if fread(filein) ne 0 then goto getout;'; put 'rc = fget(filein,rec,1);'; put 'entity=cats(entity,rec);'; put 'end;'; put 'select (entity);'; put 'when (''&'' ) rec=''&'' ;'; put 'when (''<'' ) rec=''<'' ;'; put 'when (''>'' ) rec=''>'' ;'; put 'when (''''') rec="''" ;'; put 'when (''"'') rec=''"'' ;'; put 'when ('' '') rec=''0A''x;'; put 'when ('' '') rec=''0D''x;'; put 'when (''$'' ) rec=''$'' ;'; put 'when ('' '') rec=''09''x;'; put 'otherwise putlog "%str(WARN)ING: missing value for " entity=;'; put 'end;'; put 'rc =fput(fileid, substr(rec,1,1));'; put 'rc =fwrite(fileid);'; put 'end;'; put 'else do;'; put 'rc =fput(fileid,rec);'; put 'rc =fwrite(fileid);'; put 'end;'; put 'end;'; put 'end;'; put 'getout:'; put 'rc=fclose(filein);'; put 'rc=fclose(fileid);'; put 'run;'; put '%if &showlog=YES %then %do;'; put 'data _null_;'; put 'infile &fref lrecl=32767 end=last;'; put 'input;'; put 'if _n_=1 then putlog ''>>stpcodeBEGIN<<'';'; put 'putlog _infile_;'; put 'if last then putlog ''>>stpcodeEND<<'';'; put 'run;'; put '%end;'; put 'filename __getdoc clear;'; put '%if &outref=0 %then %do;'; put 'filename &fref clear;'; put '%end;'; put '%mend mm_getstpcode;'; put '%macro dc_getsettings();'; put '%global _program;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(syscc=&syscc on entry (&syswarningtext &syserrortext))'; put ')'; put '%if %length(&_PROGRAM)>1 %then %let root=&_program;'; put '%else %do;'; put '%global _metauser;'; put '%let _metauser=&sysuserid;'; put '/* to mimic a "real" _program we need to give a dummy role and stp name */'; put '%let root=/dummyRole/dummyName;'; put '%end;'; put '/* the DC precode is stored in the Admin folder in the root of'; put 'the project. Lets find that root. */'; put '%put &=root;'; put '%let root=%mf_getapploc();'; put '%put &=root;'; put '/* Now we know the root location we can retrieve the params */'; put '%let temploc=%sysfunc(pathname(work))/settings.txt;'; put '%mm_getstpcode(tree=&root/services/public'; put ',name=Data_Controller_Settings'; put ',outloc=&temploc'; put ')'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Unable to run getstpcode)'; put ')'; put 'filename _getsets "&temploc" lrecl=2000;'; put '/*'; put 'Do not use mp_include here - this puts a copy in every service, which creates'; put 'compilation problems when calling services from mp_include'; put '*/'; put '%inc _getsets/source2;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Problem running &sysmacroname (&syswarningtext &syserrortext))'; put ')'; put '%mend dc_getsettings;'; put '%macro mf_fmtdttm('; put ')/*/STORE SOURCE*/;'; put '%if "&sysver"="9.2" or "&sysver"="9.3"'; put 'or ("&sysver"="9.4" and "%substr(&SYSVLONG,9,1)" le "3")'; put 'or "%substr(&sysver,1,1)"="4"'; put 'or "%substr(&sysver,1,1)"="5"'; put '%then %do;DATETIME19.3%end;'; put '%else %do;E8601DT26.6%end;'; put '%mend mf_fmtdttm;'; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '%macro mp_init(prefix=SASJS'; put ')/*/STORE SOURCE*/;'; put '%if %symexist(SASJS_PREFIX) %then %return; /* only run once */'; put '%global'; put 'SASJS_PREFIX /* the ONLY hard-coded global macro variable in SASjs */'; put '&prefix._FUNCTIONS /* used in mcf_init() to track core function compilation */'; put '&prefix._INIT_NUM /* initialisation time as numeric */'; put '&prefix._INIT_DTTM /* initialisation time in E8601DT26.6 format */'; put '&prefix.WORK /* avoid typing %sysfunc(pathname(work)) every time */'; put ';'; put '%let sasjs_prefix=&prefix;'; put 'data _null_;'; put 'dttm=datetime();'; put 'call symputx("&sasjs_prefix._init_num",dttm,''g'');'; put 'call symputx("&sasjs_prefix._init_dttm",put(dttm,E8601DT26.6),''g'');'; put 'call symputx("&sasjs_prefix.work",pathname(''WORK''),''g'');'; put 'run;'; put 'options'; put 'compress=CHAR /* default is none so ensure we have something! */'; put 'datastmtchk=ALLKEYWORDS /* protection from overwriting input datasets */'; put 'errorcheck=STRICT /* catch errs in libname/filename statements */'; put 'fmterr /* ensure err when a format cannot be found */'; put 'mergenoby=%str(ERR)OR /* throw err when a merge has no BY variables */'; put 'missing=. /* changing this can cause hard to detect errs */'; put 'noquotelenmax /* avoid warnings for long strings */'; put 'noreplace /* avoid overwriting permanent datasets */'; put 'ps=max /* reduce log size slightly */'; put 'ls=max /* reduce log even more and avoid word truncation */'; put 'validmemname=COMPATIBLE /* avoid special characters etc in table names */'; put 'validvarname=V7 /* avoid special characters etc in variable names */'; put 'varinitchk=%str(ERR)OR /* avoid data mistakes from variable name typos */'; put 'varlenchk=%str(ERR)OR /* fail hard if truncation (data loss) can result */'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5" %then %do;'; put 'noautocorrect /* disallow misspelled procedure names */'; put 'dsoptions=note2err /* undocumented - convert bad NOTEs to ERRs */'; put '%end;'; put ';'; put '%mend mp_init;'; put '%macro mpeinit(fetch=YES);'; put '%global mpeinit'; put 'mpeadmins /* group with unrestricted Meditor access */'; put 'mpelocapprovals /* location for landing and staging files */'; put 'mpelib /* location of configuration tables for DC */'; put 'dc_repo_users /* location of user / group metadata */'; put 'dc_licence_key /* extracted in dc_getsettings */'; put 'dc_activation_key /* extracted in dc_getsettings */'; put 'dc_locale /* extracted in dc_getsettings */'; put 'dc_dttmtfmt /* can be overridden in dc_getsettings */'; put '_debug'; put ';'; put '%if &mpeinit=1 %then %return;'; put '%else %let mpeinit=1;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(Problem on service startup (&syswarningtext &syserrortext))'; put ')'; put '%mp_init()'; put '%if &fetch=YES %then %do;'; put '%webout(FETCH)'; put '%end;'; put '%global _CLIENTNAME;'; put '%mp_abort(iftrue= (&_CLIENTNAME=SAS Enterprise Guide)'; put ',mac=&_program..sas'; put ',msg=%str(Data Controller is a web app and should not be executed from EG)'; put ')'; put 'options urlencoding=utf8 nobomfile lrecl=32767;'; put '%let perf=%sysfunc(datetime());'; put '%put perfdiff: 0;'; put '%let dc_locale=SYSTEM; /* default if not set */'; put '/**'; put '* E8601DT26.6 has widest database support - but not all SAS flavours can'; put '* handle it. Override in the settings STP if needed.'; put '*/'; put 'data _null_;'; put 'dc_dttmtfmt=''"%sysfunc(datetime(),''!!"%mf_fmtdttm()"!!'')"dt'';'; put 'call symputx(''dc_dttmtfmt'',dc_dttmtfmt);'; put 'put dc_dttmtfmt=;'; put 'run;'; put '%put &=dc_dttmtfmt;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc prior to dc_getsettings)'; put ')'; put '%dc_getsettings()'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc after dc_getsettings)'; put ')'; put 'data _null_;'; put 'set &DC_LIBREF..mpe_config(where=('; put 'var_scope="DC"'; put 'and &dc_dttmtfmt lt tx_to'; put 'and var_active=1'; put '));'; put 'call symputx(var_name,var_value,''G'');'; put 'putlog var_name "=" var_value;'; put 'run;'; put '%let mpelib=&dc_libref;'; put '%let mpeadmins=&dc_admin_group;'; put '%let mpelocapprovals=&dc_staging_area;'; put '%let dc_repo_users=&dc_repo_users;'; put '%if &dc_locale ne SYSTEM %then %do;'; put 'options locale=&dc_locale;'; put '%end;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(Problem during compilation or with STP precode (&syswarningtext))'; put ')'; put '%mend mpeinit;'; put '%macro mf_mval(var);'; put '%if %symexist(&var) %then %do;'; put '%superq(&var)'; put '%end;'; put '%mend mf_mval;'; put '%macro mf_trimstr(basestr,trimstr);'; put '%local baselen trimlen trimval;'; put '/* return if basestr is shorter than trimstr (or 0) */'; put '%let baselen=%length(%superq(basestr));'; put '%let trimlen=%length(%superq(trimstr));'; put '%if &baselen < &trimlen or &baselen=0 %then %return;'; put '/* obtain the characters from the end of basestr */'; put '%let trimval=%qsubstr(%superq(basestr)'; put ',%length(%superq(basestr))-&trimlen+1'; put ',&trimlen);'; put '/* compare and if matching, chop it off! */'; put '%if %superq(basestr)=%superq(trimstr) %then %do;'; put '%return;'; put '%end;'; put '%else %if %superq(trimval)=%superq(trimstr) %then %do;'; put '%qsubstr(%superq(basestr),1,%length(%superq(basestr))-&trimlen)'; put '%end;'; put '%else %do;'; put '&basestr'; put '%end;'; put '%mend mf_trimstr;'; put '%macro mf_getplatform(switch'; put ')/*/STORE SOURCE*/;'; put '%local a b c;'; put '%if &switch.NONE=NONE %then %do;'; put '%if %symexist(sasjsprocessmode) %then %do;'; put '%if &sasjsprocessmode=Stored Program %then %do;'; put 'SASJS'; put '%return;'; put '%end;'; put '%end;'; put '%if %symexist(sysprocessmode) %then %do;'; put '%if "&sysprocessmode"="SAS Object Server"'; put 'or "&sysprocessmode"= "SAS Compute Server" %then %do;'; put 'SASVIYA'; put '%end;'; put '%else %if "&sysprocessmode"="SAS Stored Process Server"'; put 'or "&sysprocessmode"="SAS Workspace Server"'; put '%then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if %symexist(_metaport) or %symexist(_metauser) %then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if &switch=SASSTUDIO %then %do;'; put '/* return the version of SAS Studio else 0 */'; put '%if %mf_mval(_CLIENTAPP)=%str(SAS Studio) %then %do;'; put '%let a=%mf_mval(_CLIENTVERSION);'; put '%let b=%scan(&a,1,.);'; put '%if %eval(&b >2) %then %do;'; put '&b'; put '%end;'; put '%else 0;'; put '%end;'; put '%else 0;'; put '%end;'; put '%else %if &switch=VIYARESTAPI %then %do;'; put '%mf_trimstr(%sysfunc(getoption(servicesbaseurl)),/)'; put '%end;'; put '%mend mf_getplatform;'; put '%macro mpeterm();'; put '%local oldloc;'; put 'data _null_;'; put 'if symexist(''SYSPRINTTOLOG'') then oldloc=symget(''SYSPRINTTOLOG'');'; put 'else oldloc=getoption(''LOG'');'; put 'if subpad(oldloc,1,1) not in (''"'',"''",'' '') then oldloc=quote(cats(oldloc));'; put 'call symputx(''oldloc'',oldloc,''l'');'; put 'run;'; put '%if %length(&oldloc)>0 %then %do;'; put 'proc printto log=log;'; put 'run;'; put 'data _null_;'; put 'infile &oldloc;'; put 'input; putlog _infile_;'; put 'run;'; put '%end;'; put '%if %sysfunc(exist(&dc_libref..mpe_requests)) and %mf_getplatform() ne SASVIYA'; put '%then %do;'; put 'data ;'; put 'if 0 then set &dc_libref..mpe_requests;'; put 'request_dttm=%sysfunc(datetime());'; put 'request_user="%mf_getuser()";'; put 'request_service="%scan(&_program,-2,/)/%scan(&_program,-1,/)";'; put 'request_params='''';'; put 'output;stop;'; put 'proc append base=&dc_libref..mpe_requests data=&syslast force nowarn;'; put 'run;'; put '%end;'; put '%mend mpeterm;'; put '%macro mm_getcols('; put 'tableuri='; put ',outds=work.mm_getcols'; put ')/*/STORE SOURCE*/;'; put 'data &outds;'; put 'keep col: SAS:;'; put 'length assoc uri coluri colname coldesc SASColumnType SASFormat SASInformat'; put 'SASPrecision SASColumnLength $256;'; put 'call missing (of _all_);'; put 'uri=symget(''tableuri'');'; put 'n=1;'; put 'do while (metadata_getnasn(uri,''Columns'',n,coluri)>0);'; put 'rc3=metadata_getattr(coluri,"Name",colname);'; put 'rc3=metadata_getattr(coluri,"Desc",coldesc);'; put 'rc4=metadata_getattr(coluri,"SASColumnType",SASColumnType);'; put 'rc5=metadata_getattr(coluri,"SASFormat",SASFormat);'; put 'rc6=metadata_getattr(coluri,"SASInformat",SASInformat);'; put 'rc7=metadata_getattr(coluri,"SASPrecision",SASPrecision);'; put 'rc8=metadata_getattr(coluri,"SASColumnLength",SASColumnLength);'; put 'output;'; put 'call missing(colname,coldesc,SASColumnType,SASFormat,SASInformat'; put ',SASPrecision,SASColumnLength);'; put 'n+1;'; put 'end;'; put 'run;'; put 'proc sort;'; put 'by colname;'; put 'run;'; put '%mend mm_getcols;'; put '* SAS Macros end;'; put '* SAS Includes start;'; put '* SAS Includes end;'; put '* Binary Files start;'; put '* Binary Files end;'; put '* ServiceInit start;'; put 'options noquotelenmax ps=max;'; put '/* create dummy macros to prevent stpbegin / stpend from corrupting sessions */'; put '%macro stpbegin();'; put '%put NOTE: the STPBEGIN macro should not be used for web apps!;'; put '%mend stpbegin;'; put '%macro stpend();'; put '%put NOTE: the STPEND macro should not be used for web apps!;'; put '%mend stpend;'; put '* ServiceInit end;'; put '* Service start;'; put '/**'; put '@file getmetacols.sas'; put '@brief List the cols as defined in metadata'; put '@details Provide a table uri and get list of columns'; put '

SAS Macros

'; put '@li mm_getcols.sas'; put '@li mp_abort.sas'; put '@version 9.2'; put '@author 4GL Apps Ltd'; put '@copyright 4GL Apps Ltd. This code may only be used within Data Controller'; put 'and may not be re-distributed or re-sold without the express permission of'; put '4GL Apps Ltd.'; put '**/'; put '%mpeinit()'; put '%global tableuri;'; put '%let exist=%sysfunc(exist(work.SASControlTable));'; put '%let inds=%sysfunc(ifc(&exist=1,SASControlTable,_null_));'; put '%put &=inds;'; put 'data _null_;'; put 'set &inds;'; put 'call symputx(''tableuri'',scan(tableuri,-1,''\''));'; put 'run;'; put '%put &=tableuri;'; put '/* load parameters */'; put '%mm_getcols(tableuri=&tableuri,outds=metacols)'; put 'data out;'; put 'set metacols;'; put 'keep col:;'; put 'run;'; put '%webout(OPEN)'; put '%webout(OBJ,out,dslabel=metacols)'; put '%webout(CLOSE)'; put '%mpeterm()'; put '* Service end;'; run; %mm_createwebservice(path=&appLoc/&path, name=&service, code=sascode, server=&serverName, replace=yes) filename sascode clear; %let service=getmetatables; filename sascode temp lrecl=32767; data _null_; file sascode; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '/**'; put '@file mp_jsonout.sas'; put '@brief Writes JSON in SASjs format to a fileref'; put '@details This macro can be used to OPEN a JSON stream and send one or more'; put 'tables as arrays of rows, where each row can be an object or a nested array.'; put 'There are two engines available - DATASTEP or PROCJSON.'; put 'PROC JSON is fast but will produce errs like the ones below if'; put 'special chars are encountered.'; put '> (ERR)OR: Some code points did not transcode.'; put '> An object or array close is not valid at this point in the JSON text.'; put '> Date value out of range'; put 'If this happens, try running with ENGINE=DATASTEP.'; put 'The DATASTEP engine is used to handle special SAS missing numerics, and'; put 'can also convert entire datasets to formatted values. Output JSON is always'; put 'in UTF-8.'; put 'Usage:'; put 'filename tmp temp;'; put 'data class; set sashelp.class;run;'; put '%mp_jsonout(OPEN,jref=tmp)'; put '%mp_jsonout(OBJ,class,jref=tmp)'; put '%mp_jsonout(OBJ,class,dslabel=class2,jref=tmp,showmeta=Y)'; put '%mp_jsonout(CLOSE,jref=tmp)'; put 'data _null_;'; put 'infile tmp;'; put 'input;putlog _infile_;'; put 'run;'; put 'If you are building web apps with SAS then you are strongly encouraged to use'; put 'the mX_createwebservice macros in combination with the'; put '[sasjs adapter](https://github.com/sasjs/adapter).'; put 'For more information see https://sasjs.io'; put '@param [in] action Valid values:'; put '@li OPEN - opens the JSON'; put '@li OBJ - sends a table with each row as an object'; put '@li ARR - sends a table with each row in an array'; put '@li CLOSE - closes the JSON'; put '@param [in] ds The dataset to send. Must be a work table.'; put '@param [out] jref= (_webout) The fileref to which to send the JSON'; put '@param [out] dslabel= The name to give the table in the exported JSON'; put '@param [in] fmt= (Y) Whether to keep (Y) or strip (N) formats from the table'; put '@param [in] engine= (DATASTEP) Which engine to use to send the JSON. Options:'; put '@li PROCJSON (default)'; put '@li DATASTEP (more reliable when data has non standard characters)'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to JSON'; put '

Related Files

'; put '@li mp_ds2fmtds.sas'; put '@version 9.2'; put '@author Allan Bowe'; put '@source https://github.com/sasjs/core'; put '**/'; put '%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y'; put ',engine=DATASTEP'; put ',missing=NULL'; put ',showmeta=N'; put ',maxobs=MAX'; put ')/*/STORE SOURCE*/;'; put '%local tempds colinfo fmtds i numcols numobs stmt_obs lastobs optval'; put 'tmpds1 tmpds2 tmpds3 tmpds4;'; put '%let numcols=0;'; put '%if &maxobs ne MAX %then %let stmt_obs=%str(if _n_>&maxobs then stop;);'; put '%if &action=OPEN %then %do;'; put 'options nobomfile;'; put 'data _null_;file &jref encoding=''utf-8'' lrecl=200;'; put 'put ''{"PROCESSED_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''"'';'; put 'run;'; put '%end;'; put '%else %if (&action=ARR or &action=OBJ) %then %do;'; put '/* force variable names to always be uppercase in the JSON */'; put 'options validvarname=upcase;'; put '/* To avoid issues with _webout on EBI - such as encoding diffs and truncation'; put '(https://support.sas.com/kb/49/325.html) we use temporary files */'; put 'filename _sjs1 temp lrecl=200 ;'; put 'data _null_; file _sjs1 encoding=''utf-8'';'; put 'put ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":";'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjs1 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs1 clear;'; put '/* grab col defs */'; put 'proc contents noprint data=&ds'; put 'out=_data_(keep=name type length format formatl formatd varnum label);'; put 'run;'; put '%let colinfo=%scan(&syslast,2,.);'; put 'proc sort data=&colinfo;'; put 'by varnum;'; put 'run;'; put '/* move meta to mac vars */'; put 'data &colinfo;'; put 'if _n_=1 then call symputx(''numcols'',nobs,''l'');'; put 'set &colinfo end=last nobs=nobs;'; put 'name=upcase(name);'; put '/* fix formats */'; put 'if type=2 or type=6 then do;'; put 'typelong=''char'';'; put 'length fmt $49.;'; put 'if format='''' then fmt=cats(''$'',length,''.'');'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else fmt=cats(format,formatl,''.'');'; put 'end;'; put 'else do;'; put 'typelong=''num'';'; put 'if format='''' then fmt=''best.'';'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else if formatd=0 then fmt=cats(format,formatl,''.'');'; put 'else fmt=cats(format,formatl,''.'',formatd);'; put 'end;'; put '/* 32 char unique name */'; put 'newname=''sasjs''!!substr(cats(put(md5(name),$hex32.)),1,27);'; put 'call symputx(cats(''name'',_n_),name,''l'');'; put 'call symputx(cats(''newname'',_n_),newname,''l'');'; put 'call symputx(cats(''length'',_n_),length,''l'');'; put 'call symputx(cats(''fmt'',_n_),fmt,''l'');'; put 'call symputx(cats(''type'',_n_),type,''l'');'; put 'call symputx(cats(''typelong'',_n_),typelong,''l'');'; put 'call symputx(cats(''label'',_n_),coalescec(label,name),''l'');'; put '/* overwritten when fmt=Y and a custom format exists in catalog */'; put 'if typelong=''num'' then call symputx(cats(''fmtlen'',_n_),200,''l'');'; put 'else call symputx(cats(''fmtlen'',_n_),min(32767,ceil((length+10)*1.5)),''l'');'; put 'run;'; put '%let tempds=%substr(_%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql;'; put 'select count(*) into: lastobs from &ds;'; put '%if &maxobs ne MAX %then %let lastobs=%sysfunc(min(&lastobs,&maxobs));'; put '%if &engine=PROCJSON %then %do;'; put '%if &missing=STRING %then %do;'; put '%put &sysmacroname: Special Missings not supported in proc json.;'; put '%put &sysmacroname: Switching to DATASTEP engine;'; put '%goto datastep;'; put '%end;'; put 'data &tempds;'; put 'set &ds;'; put '&stmt_obs;'; put '%if &fmt=N %then format _numeric_ best32.;;'; put '/* PRETTY is necessary to avoid line truncation in large files */'; put 'filename _sjs2 temp lrecl=131068 encoding=''utf-8'';'; put 'proc json out=_sjs2 pretty'; put '%if &action=ARR %then nokeys ;'; put ';export &tempds / nosastags fmtnumeric;'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs2 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs2 clear;'; put '%end;'; put '%else %if &engine=DATASTEP %then %do;'; put '%datastep:'; put '%if %sysfunc(exist(&ds)) ne 1 & %sysfunc(exist(&ds,VIEW)) ne 1'; put '%then %do;'; put '%put &sysmacroname: &ds NOT FOUND!!!;'; put '%return;'; put '%end;'; put '%if &fmt=Y %then %do;'; put '/**'; put '* Extract format definitions'; put '* First, by getting library locations from dictionary.formats'; put '* Then, by exporting the width using proc format'; put '* Cannot use maxw from sashelp.vformat as not always populated'; put '* Cannot use fmtinfo() as not supported in all flavours'; put '*/'; put '%let tmpds1=%substr(fmtsum%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds2=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds3=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds4=%substr(col%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql noprint;'; put 'create table &tmpds1 as'; put 'select cats(libname,''.'',memname) as FMTCAT,'; put 'FMTNAME'; put 'from dictionary.formats'; put 'where fmttype=''F'' and libname is not null'; put 'and fmtname in (select format from &colinfo where format is not null)'; put 'order by 1;'; put 'create table &tmpds2('; put 'FMTNAME char(32),'; put 'LENGTH num'; put ');'; put '%local catlist cat fmtlist i;'; put 'select distinct fmtcat into: catlist separated by '' '' from &tmpds1;'; put '%do i=1 %to %sysfunc(countw(&catlist,%str( )));'; put '%let cat=%scan(&catlist,&i,%str( ));'; put 'proc sql;'; put 'select distinct fmtname into: fmtlist separated by '' '''; put 'from &tmpds1 where fmtcat="&cat";'; put 'proc format lib=&cat cntlout=&tmpds3(keep=fmtname length);'; put 'select &fmtlist;'; put 'run;'; put 'proc sql;'; put 'insert into &tmpds2 select distinct fmtname,length from &tmpds3;'; put '%end;'; put 'proc sql;'; put 'create table &tmpds4 as'; put 'select a.*, b.length as MAXW'; put 'from &colinfo a'; put 'left join &tmpds2 b'; put 'on cats(a.format)=cats(upcase(b.fmtname))'; put 'order by a.varnum;'; put 'data _null_;'; put 'set &tmpds4;'; put 'if not missing(maxw);'; put 'call symputx('; put 'cats(''fmtlen'',_n_),'; put '/* vars need extra padding due to JSON escaping of special chars */'; put 'min(32767,ceil((max(length,maxw)+10)*1.5))'; put ',''l'''; put ');'; put 'run;'; put '/* configure varlenchk - as we are explicitly shortening the variables */'; put '%let optval=%sysfunc(getoption(varlenchk));'; put 'options varlenchk=NOWARN;'; put 'data _data_(compress=char);'; put '/* shorten the new vars */'; put 'length'; put '%do i=1 %to &numcols;'; put '&&name&i $&&fmtlen&i'; put '%end;'; put ';'; put '/* rename on entry */'; put 'set &ds(rename=('; put '%do i=1 %to &numcols;'; put '&&name&i=&&newname&i'; put '%end;'; put '));'; put '&stmt_obs;'; put 'drop'; put '%do i=1 %to &numcols;'; put '&&newname&i'; put '%end;'; put ';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=num %then %do;'; put '&&name&i=cats(put(&&newname&i,&&fmt&i));'; put '%end;'; put '%else %do;'; put '&&name&i=put(&&newname&i,&&fmt&i);'; put '%end;'; put '%end;'; put 'if _error_ then do;'; put 'call symputx(''syscc'',1012);'; put 'stop;'; put 'end;'; put 'run;'; put '%let fmtds=&syslast;'; put 'options varlenchk=&optval;'; put '%end;'; put 'proc format; /* credit yabwon for special null removal */'; put 'value bart (default=40)'; put '%if &missing=NULL %then %do;'; put '._ - .z = null'; put '%end;'; put '%else %do;'; put '._ = [quote()]'; put '. = null'; put '.a - .z = [quote()]'; put '%end;'; put 'other = [best.];'; put 'data &tempds;'; put 'attrib _all_ label='''';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'length &&name&i $&&fmtlen&i...;'; put 'format &&name&i $&&fmtlen&i...;'; put '%end;'; put '%end;'; put '%if &fmt=Y %then %do;'; put 'set &fmtds;'; put '%end;'; put '%else %do;'; put 'set &ds;'; put '%end;'; put '&stmt_obs;'; put 'format _numeric_ bart.;'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'if findc(&&name&i,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put '&&name&i=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,&&name&i)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else &&name&i=quote(cats(&&name&i));'; put '%end;'; put '%end;'; put 'run;'; put 'filename _sjs3 temp lrecl=131068 ;'; put 'data _null_;'; put 'file _sjs3 encoding=''utf-8'';'; put 'if _n_=1 then put "[";'; put 'set &tempds;'; put 'if _n_>1 then put "," @; put'; put '%if &action=ARR %then "[" ; %else "{" ;'; put '%do i=1 %to &numcols;'; put '%if &i>1 %then "," ;'; put '%if &action=OBJ %then """&&name&i"":" ;'; put '"&&name&i"n /* name literal for reserved variable names */'; put '%end;'; put '%if &action=ARR %then "]" ; %else "}" ; ;'; put '/* close out the table */'; put 'data _null_;'; put 'file _sjs3 mod encoding=''utf-8'';'; put 'put '']'';'; put 'run;'; put 'data _null_;'; put 'infile _sjs3 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs3 clear;'; put '%end;'; put 'proc sql;'; put 'drop table &colinfo, &tempds;'; put '%if %substr(&showmeta,1,1)=Y %then %do;'; put 'filename _sjs4 temp lrecl=131068 encoding=''utf-8'';'; put 'data _null_;'; put 'file _sjs4;'; put 'length label $350;'; put 'put ", ""$%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":{""vars"":{";'; put 'do i=1 to &numcols;'; put 'name=quote(trim(symget(cats(''name'',i))));'; put 'format=quote(trim(symget(cats(''fmt'',i))));'; put 'label=quote(prxchange(''s/\\/\\\\/'',-1,trim(symget(cats(''label'',i)))));'; put 'length=quote(trim(symget(cats(''length'',i))));'; put 'type=quote(trim(symget(cats(''typelong'',i))));'; put 'if i>1 then put "," @@;'; put 'put name '':{"format":'' format '',"label":'' label'; put ''',"length":'' length '',"type":'' type ''}'';'; put 'end;'; put 'put ''}}'';'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs4 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs4 clear;'; put '%end;'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put 'data _null_; file &jref encoding=''utf-8'' mod ;'; put 'put "}";'; put 'run;'; put '%end;'; put '%mend mp_jsonout;'; put '/**'; put '@file mm_webout.sas'; put '@brief Send data to/from SAS Stored Processes'; put '@details This macro should be added to the start of each Stored Process,'; put '**immediately** followed by a call to:'; put '%mm_webout(FETCH)'; put 'This will read all the input data and create same-named SAS datasets in the'; put 'WORK library. You can then insert your code, and send data back using the'; put 'following syntax:'; put 'data some datasets; * make some data ;'; put 'retain some columns;'; put 'run;'; put '%mm_webout(OPEN)'; put '%mm_webout(ARR,some) * Array format, fast, suitable for large tables ;'; put '%mm_webout(OBJ,datasets) * Object format, easier to work with ;'; put 'Finally, wrap everything up send some helpful system variables too'; put '%mm_webout(CLOSE)'; put '@param [in] action Either FETCH, OPEN, ARR, OBJ or CLOSE'; put '@param [in] ds The dataset to send back to the frontend'; put '@param [out] dslabel= Value to use instead of table name for sending to JSON'; put '@param [in] fmt= (N) Setting Y converts all vars to their formatted values'; put '@param [out] fref= (_webout) The fileref to which to write the JSON'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] workobs= (0) When set to a positive integer, will create a new'; put 'output object (WORK) which contains this number of observations from all'; put 'tables in the WORK library.'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to output JSON'; put '

SAS Macros

'; put '@li mp_jsonout.sas'; put '

Related Macros

'; put '@li ms_webout.sas'; put '@li mv_webout.sas'; put '@version 9.3'; put '@author Allan Bowe'; put '**/'; put '%macro mm_webout(action,ds,dslabel=,fref=_webout,fmt=N,missing=NULL'; put ',showmeta=N,maxobs=MAX,workobs=0'; put ');'; put '%global _webin_file_count _webin_fileref1 _webin_name1 _program _debug'; put 'sasjs_tables;'; put '%local i tempds jsonengine;'; put '/* see https://github.com/sasjs/core/issues/41 */'; put '%if "%upcase(&SYSENCODING)" ne "UTF-8" %then %let jsonengine=PROCJSON;'; put '%else %let jsonengine=DATASTEP;'; put '%if &action=FETCH %then %do;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'options mprint notes mprintnest;'; put '%end;'; put '%let _webin_file_count=%eval(&_webin_file_count+0);'; put '/* now read in the data */'; put '%do i=1 %to &_webin_file_count;'; put '%if &_webin_file_count=1 %then %do;'; put '%let _webin_fileref1=&_webin_fileref;'; put '%let _webin_name1=&_webin_name;'; put '%end;'; put 'data _null_;'; put 'infile &&_webin_fileref&i termstr=crlf;'; put 'input;'; put 'call symputx(''input_statement'',_infile_);'; put 'putlog "&&_webin_name&i input statement: " _infile_;'; put 'stop;'; put 'data &&_webin_name&i;'; put 'infile &&_webin_fileref&i firstobs=2 dsd termstr=crlf encoding=''utf-8'';'; put 'input &input_statement;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'if _n_<20 then putlog _infile_;'; put '%end;'; put 'run;'; put '%let sasjs_tables=&sasjs_tables &&_webin_name&i;'; put '%end;'; put '%end;'; put '%else %if &action=OPEN %then %do;'; put '/* fix encoding */'; put 'OPTIONS NOBOMFILE;'; put '/**'; put '* check xengine type to avoid the below err message:'; put '* > Function is only valid for filerefs using the CACHE access method.'; put '*/'; put 'data _null_;'; put 'set sashelp.vextfl(where=(fileref="_WEBOUT"));'; put 'if xengine=''STREAM'' then do;'; put 'rc=stpsrv_header(''Content-type'',"text/html; encoding=utf-8");'; put 'end;'; put 'run;'; put '/* setup json */'; put 'data _null_;file &fref encoding=''utf-8'';'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutBEGIN<<'';'; put '%end;'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'run;'; put '%end;'; put '%else %if &action=ARR or &action=OBJ %then %do;'; put '%mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt,jref=&fref'; put ',engine=&jsonengine,missing=&missing,showmeta=&showmeta,maxobs=&maxobs'; put ')'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put '/* To avoid issues with _webout on EBI we use a temporary file */'; put 'filename _sjsref temp lrecl=131068;'; put '%if %str(&workobs) > 0 %then %do;'; put '/* if debug mode, send back first XX records of each work table also */'; put 'data;run;%let tempds=%scan(&syslast,2,.);'; put 'ods output Members=&tempds;'; put 'proc datasets library=WORK memtype=data;'; put '%local wtcnt;%let wtcnt=0;'; put 'data _null_;'; put 'set &tempds;'; put 'if not (upcase(name) =:"DATA"); /* ignore temp datasets */'; put 'i+1;'; put 'call symputx(cats(''wt'',i),name,''l'');'; put 'call symputx(''wtcnt'',i,''l'');'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put ",""WORK"":{";'; put '%do i=1 %to &wtcnt;'; put '%let wt=&&wt&i;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'dsid=open("WORK.&wt",''is'');'; put 'nlobs=attrn(dsid,''NLOBS'');'; put 'nvars=attrn(dsid,''NVARS'');'; put 'rc=close(dsid);'; put 'if &i>1 then put '',''@;'; put 'put " ""&wt"" : {";'; put 'put ''"nlobs":'' nlobs;'; put 'put '',"nvars":'' nvars;'; put '%mp_jsonout(OBJ,&wt,jref=_sjsref,dslabel=first10rows,showmeta=Y'; put ',maxobs=&workobs'; put ')'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put '%end;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put 'run;'; put '%end;'; put '/* close off json */'; put 'data _null_;file _sjsref mod encoding=''utf-8'';'; put 'length SYSPROCESSNAME syserrortext syswarningtext autoexec $512;'; put 'put ",""_DEBUG"" : ""&_debug"" ";'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'autoexec=quote(urlencode(trim(getoption(''autoexec''))));'; put 'put '',"AUTOEXEC" : '' autoexec;'; put 'put ",""MF_GETUSER"" : ""%mf_getuser()"" ";'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'put ",""SYSENCODING"" : ""&sysencoding"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSPROCESSID"" : ""&SYSPROCESSID"" ";'; put 'put ",""SYSPROCESSMODE"" : ""&SYSPROCESSMODE"" ";'; put 'SYSPROCESSNAME=quote(urlencode(cats(SYSPROCESSNAME)));'; put 'put ",""SYSPROCESSNAME"" : " SYSPROCESSNAME;'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put '',"SYSWARNINGTEXT" : '' syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'length memsize $32;'; put 'memsize="%sysfunc(INPUTN(%sysfunc(getoption(memsize)), best.),sizekmg.)";'; put 'memsize=quote(cats(memsize));'; put 'put '',"MEMSIZE" : '' memsize;'; put 'put "}" @;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutEND<<'';'; put '%end;'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjsref lrecl=1 recfm=n;'; put 'file &fref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjsref clear;'; put '%end;'; put '%mend mm_webout;'; put '%macro webout(action,ds,dslabel=,fmt=,missing=NULL,showmeta=NO,maxobs=MAX);'; put '%mm_webout(&action,ds=&ds,dslabel=&dslabel,fmt=&fmt'; put ',missing=&missing'; put ',showmeta=&showmeta'; put ',maxobs=&maxobs'; put ') %mend;'; put '/* provide additional debug info */'; put '%global _program;'; put '%put &=syscc;'; put '%put user=%mf_getuser();'; put '%put pgm=&_program;'; put '%put timestamp=%sysfunc(datetime(),datetime19.);'; put '* Service Variables start;'; put '* Service Variables end;'; put '* SAS Macros start;'; put '%macro mf_getapploc(pgm);'; put '%if "&pgm"="" %then %do;'; put '%if %symexist(_program) %then %let pgm=&_program;'; put '%else %do;'; put '%put &sysmacroname: No value provided and no _program variable available;'; put '%return;'; put '%end;'; put '%end;'; put '%local root;'; put '/**'; put '* First check we are not in the tests/macros folder (which has no subfolders)'; put '* or specifically in the testsetup or testteardown services'; put '*/'; put '%if %index(&pgm,/tests/macros/)'; put 'or %index(&pgm,/tests/testsetup)'; put 'or %index(&pgm,/tests/testteardown)'; put '%then %do;'; put '%let root=%substr(&pgm,1,%index(&pgm,/tests)-1);'; put '&root'; put '%return;'; put '%end;'; put '/**'; put '* Next, move up two levels to avoid matches on subfolder or service name'; put '*/'; put '%let root=%substr(&pgm,1,%length(&pgm)-%length(%scan(&pgm,-1,/))-1);'; put '%let root=%substr(&root,1,%length(&root)-%length(%scan(&root,-1,/))-1);'; put '%if %index(&root,/tests/) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/tests/)-1);'; put '%end;'; put '%else %if %index(&root,/services) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/services)-1);'; put '%end;'; put '%else %if %index(&root,/jobs) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/jobs)-1);'; put '%end;'; put '%else %put &sysmacroname: Could not find an app location from &pgm;'; put '&root'; put '%mend mf_getapploc ;'; put '%macro mf_getuniquefileref(prefix=_,maxtries=1000,lrecl=32767);'; put '%local rc fname;'; put '%if &prefix=0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%end;'; put '%else %do;'; put '%local x len;'; put '%let len=%eval(8-%length(&prefix));'; put '%let x=0;'; put '%do x=0 %to &maxtries;'; put '%let fname=&prefix%substr(%sysfunc(ranuni(0)),3,&len);'; put '%if %sysfunc(fileref(&fname)) > 0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%return;'; put '%end;'; put '%end;'; put '%put unable to find available fileref after &maxtries attempts;'; put '%end;'; put '%mend mf_getuniquefileref;'; put '%macro mp_abort(mac=mp_abort.sas, type=, msg=, iftrue=%str(1=1)'; put ', errds=work.mp_abort_errds'; put ', mode=REGULAR'; put ')/*/STORE SOURCE*/;'; put '%global sysprocessmode sysprocessname sasjs_stpsrv_header_loc sasjsprocessmode;'; put '%local fref fid i;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%put NOTE: /// mp_abort macro executing //;'; put '%if %length(&mac)>0 %then %put NOTE- called by &mac;'; put '%put NOTE - &msg;'; put '%if %symexist(_SYSINCLUDEFILEDEVICE)'; put '/* abort cancel FILE does not restart outside the INCLUDE on Viya 3.5 */'; put 'and %superq(SYSPROCESSNAME) ne %str(Compute Server)'; put '%then %do;'; put '%if "*&_SYSINCLUDEFILEDEVICE*" ne "**" %then %do;'; put 'data &errds;'; put 'iftrue=''1=1'';'; put 'length mac $100 msg $5000;'; put 'mac=symget(''mac'');'; put 'msg=symget(''msg'');'; put 'run;'; put 'data _null_;'; put 'abort cancel FILE;'; put 'run;'; put '%return;'; put '%end;'; put '%end;'; put '/* Web App Context */'; put '%if %symexist(_PROGRAM)'; put 'or %superq(SYSPROCESSNAME) = %str(Compute Server)'; put 'or &mode=INCLUDE'; put '%then %do;'; put 'options obs=max replace mprint;'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5"'; put '%then %do;'; put 'options nosyntaxcheck;'; put '%end;'; put '%if &mode=INCLUDE %then %do;'; put '%if %sysfunc(exist(&errds))=1 %then %do;'; put 'data _null_;'; put 'set &errds;'; put 'call symputx(''iftrue'',iftrue,''l'');'; put 'call symputx(''mac'',mac,''l'');'; put 'call symputx(''msg'',msg,''l'');'; put 'putlog (_all_)(=);'; put 'run;'; put '%if (&iftrue)=0 %then %return;'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: No include errors found;'; put '%return;'; put '%end;'; put '%end;'; put '/* extract log errs / warns, if exist */'; put '%local logloc logline;'; put '%global logmsg; /* capture global messages */'; put '%if %symexist(SYSPRINTTOLOG) %then %let logloc=&SYSPRINTTOLOG;'; put '%else %let logloc=%qsysfunc(getoption(LOG));'; put 'proc printto log=log;run;'; put '%let logline=0;'; put '%if %length(&logloc)>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input; putlog _infile_;'; put 'i=1;'; put 'retain logonce 0;'; put 'if ('; put '_infile_=:"%str(WARN)ING" or _infile_=:"%str(ERR)OR"'; put ') and logonce=0 then'; put 'do;'; put 'call symputx(''logline'',_n_);'; put 'logonce+1;'; put 'end;'; put 'run;'; put '/* capture log including lines BEFORE the err */'; put '%if &logline>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input;'; put 'i=1;'; put 'stoploop=0;'; put 'if _n_ ge &logline-15 and stoploop=0 then do until (i>22);'; put 'call symputx(''logmsg'',catx(''\n'',symget(''logmsg''),_infile_));'; put 'input;'; put 'i+1;'; put 'stoploop=1;'; put 'end;'; put 'if stoploop=1 then stop;'; put 'run;'; put '%end;'; put '%end;'; put '%if %symexist(SYS_JES_JOB_URI) %then %do;'; put '/* setup webout for Viya */'; put 'options nobomfile;'; put '%if "X&SYS_JES_JOB_URI.X"="XX" %then %do;'; put 'filename _webout temp lrecl=999999 mod;'; put '%end;'; put '%else %do;'; put 'filename _webout filesrvc parenturi="&SYS_JES_JOB_URI"'; put 'name="_webout.json" lrecl=999999 mod;'; put '%end;'; put '%end;'; put '%else %if %sysfunc(filename(fref,&sasjs_stpsrv_header_loc))=0 %then %do;'; put 'options nobomfile;'; put '/* set up http header for SASjs Server */'; put '%let fid=%sysfunc(fopen(&fref,A));'; put '%if &fid=0 %then %do;'; put '%put %str(ERR)OR: %sysfunc(sysmsg());'; put '%return;'; put '%end;'; put '%let rc=%sysfunc(fput(&fid,%str(Content-Type: application/json)));'; put '%let rc=%sysfunc(fwrite(&fid));'; put '%let rc=%sysfunc(fclose(&fid));'; put '%let rc=%sysfunc(filename(&fref));'; put '%end;'; put '/* send response in SASjs JSON format */'; put 'data _null_;'; put 'file _webout mod lrecl=32000 encoding=''utf-8'';'; put 'length msg syswarningtext syserrortext $32767 mode $10 ;'; put 'sasdatetime=datetime();'; put 'msg=symget(''msg'');'; put '%if &logline>0 %then %do;'; put 'msg=cats(msg,''\n\nLog Extract:\n'',symget(''logmsg''));'; put '%end;'; put '/* escape the escapes */'; put 'msg=tranwrd(msg,''\'',''\\'');'; put '/* escape the quotes */'; put 'msg=tranwrd(msg,''"'',''\"'');'; put '/* ditch the CRLFs as chrome complains */'; put 'msg=compress(msg,,''kw'');'; put '/* quote without quoting the quotes (which are escaped instead) */'; put 'msg=cats(''"'',msg,''"'');'; put 'if symexist(''_debug'') then debug=quote(trim(symget(''_debug'')));'; put 'else debug=''""'';'; put 'if symget(''sasjsprocessmode'')=''Stored Program'' then mode=''SASJS'';'; put 'if mode ne ''SASJS'' then put ''>>weboutBEGIN<<'';'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'put '',"sasjsAbort" : [{'';'; put 'put '' "MSG":'' msg ;'; put 'put '' ,"MAC": "'' "&mac" ''"}]'';'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'put '',"_DEBUG":'' debug ;'; put 'if symexist(''_metauser'') then do;'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put 'end;'; put 'if symexist(''SYS_JES_JOB_URI'') then do;'; put 'SYS_JES_JOB_URI=quote(trim(symget(''SYS_JES_JOB_URI'')));'; put 'put '',"SYS_JES_JOB_URI": '' SYS_JES_JOB_URI;'; put 'end;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put ",""SYSWARNINGTEXT"" : " syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'put "}" ;'; put 'if mode ne ''SASJS'' then put ''>>weboutEND<<'';'; put 'run;'; put '%put _all_;'; put '%if "&sysprocessmode " = "SAS Stored Process Server " %then %do;'; put 'data _null_;'; put 'putlog ''stpsrvset program err and syscc'';'; put 'rc=stpsrvset(''program error'', 0);'; put 'call symputx("syscc",0,"g");'; put 'run;'; put '%if &sysscp=WIN'; put 'and 1=0 /* deprecating this logic until we figure out a consistent abort */'; put 'and "%substr(%str(&sysvlong ),1,8)"="9.04.01M"'; put 'and "%substr(%str(&sysvlong ),9,1)">"5" %then %do;'; put '/* skip approach (below) does not work in windows m6+ envs */'; put 'endsas;'; put '%end;'; put '%else %do;'; put '/**'; put '* endsas kills 9.4m3 deployments by orphaning multibridges.'; put '* Abort variants are ungraceful (non zero return code)'; put '* This approach lets SAS run silently until the end :-)'; put '* Caution - fails when called within a %include within a macro'; put '* Use mp_include() to handle this.'; put '*/'; put 'filename skip temp;'; put 'data _null_;'; put 'file skip;'; put 'put ''%macro skip();'';'; put 'comment ''%mend skip; -> fix lint '';'; put 'put ''%macro skippy();'';'; put 'comment ''%mend skippy; -> fix lint '';'; put 'run;'; put '%inc skip;'; put '%end;'; put '%end;'; put '%else %if "&sysprocessmode " = "SAS Compute Server " %then %do;'; put '/* endsas kills the session making it harder to fetch results */'; put 'data _null_;'; put 'syswarningtext=symget(''syswarningtext'');'; put 'syserrortext=symget(''syserrortext'');'; put 'abort_msg=symget(''msg'');'; put 'syscc=symget(''syscc'');'; put 'sysuserid=symget(''sysuserid'');'; put 'iftrue=symget(''iftrue'');'; put 'put (_all_)(/=);'; put 'call symputx(''syscc'',0);'; put 'abort cancel nolist;'; put 'run;'; put '%end;'; put '%else %do;'; put '%abort cancel;'; put '%end;'; put '%end;'; put '%else %do;'; put '%put _all_;'; put '%abort cancel;'; put '%end;'; put '%mend mp_abort;'; put '/** @endcond */'; put '%macro mm_getstpcode('; put 'tree=/User Folders/sasdemo/somestp'; put ',name='; put ',outloc=0'; put ',outref=0'; put ',mDebug=1'; put ',showlog=NO'; put ');'; put '%local mD;'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing &sysmacroname..sas;'; put '%&mD.put _local_;'; put '%if %length(&name)>0 %then %let name=/&name;'; put '/* first, check if STP exists */'; put '%local tsuri;'; put '%let tsuri=stopifempty ;'; put 'data _null_;'; put 'format type uri tsuri value $200.;'; put 'call missing (of _all_);'; put 'path="&tree&name(StoredProcess)";'; put '/* first, find the STP ID */'; put 'if metadata_pathobj("",path,"StoredProcess",type,uri)>0 then do;'; put '/* get sourcecode */'; put 'cnt=1;'; put 'do while (metadata_getnasn(uri,"Notes",cnt,tsuri)>0);'; put 'rc=metadata_getattr(tsuri,"Name",value);'; put '&mD.put tsuri= value=;'; put 'if value="SourceCode" then do;'; put '/* found it! */'; put 'rc=metadata_getattr(tsuri,"Id",value);'; put 'call symputx(''tsuri'',value,''l'');'; put 'stop;'; put 'end;'; put 'cnt+1;'; put 'end;'; put 'end;'; put 'else put (_all_)(=);'; put 'run;'; put '%mp_abort(iftrue= (&tsuri=stopifempty)'; put ',mac=mm_getstpcode'; put ',msg=%str(&tree&name.(StoredProcess) not found!)'; put ')'; put '/**'; put '* Now we can extract the textstore'; put '*/'; put 'filename __getdoc temp lrecl=10000000;'; put 'proc metadata'; put 'in="$METAREPOSITORY'; put ''; put 'SAS1"'; put 'out=__getdoc ;'; put 'run;'; put '/* find the beginning of the text */'; put '%local start;'; put 'data _null_;'; put 'infile __getdoc lrecl=10000;'; put 'input;'; put 'start=index(_infile_,''StoredText="'');'; put 'if start then do;'; put 'call symputx("start",start+11);'; put '*putlog ''"'' _infile_ ''"'';'; put 'end;'; put 'stop;'; put '%local outeng;'; put '%if "&outloc"="0" %then %let outeng=TEMP;'; put '%else %let outeng="&outloc";'; put '%local fref;'; put '%if &outref=0 %then %let fref=%mf_getuniquefileref();'; put '%else %let fref=&outref;'; put '/* read the content, byte by byte, resolving escaped chars */'; put 'filename &fref &outeng lrecl=100000;'; put 'data _null_;'; put 'length filein 8 fileid 8;'; put 'filein = fopen("__getdoc","I",1,"B");'; put 'fileid = fopen("&fref","O",1,"B");'; put 'rec = "20"x;'; put 'length entity $6;'; put 'do while(fread(filein)=0);'; put 'x+1;'; put 'if x>&start then do;'; put 'rc = fget(filein,rec,1);'; put 'if rec=''"'' then leave;'; put 'else if rec="&" then do;'; put 'entity=rec;'; put 'do until (rec=";");'; put 'if fread(filein) ne 0 then goto getout;'; put 'rc = fget(filein,rec,1);'; put 'entity=cats(entity,rec);'; put 'end;'; put 'select (entity);'; put 'when (''&'' ) rec=''&'' ;'; put 'when (''<'' ) rec=''<'' ;'; put 'when (''>'' ) rec=''>'' ;'; put 'when (''''') rec="''" ;'; put 'when (''"'') rec=''"'' ;'; put 'when ('' '') rec=''0A''x;'; put 'when ('' '') rec=''0D''x;'; put 'when (''$'' ) rec=''$'' ;'; put 'when ('' '') rec=''09''x;'; put 'otherwise putlog "%str(WARN)ING: missing value for " entity=;'; put 'end;'; put 'rc =fput(fileid, substr(rec,1,1));'; put 'rc =fwrite(fileid);'; put 'end;'; put 'else do;'; put 'rc =fput(fileid,rec);'; put 'rc =fwrite(fileid);'; put 'end;'; put 'end;'; put 'end;'; put 'getout:'; put 'rc=fclose(filein);'; put 'rc=fclose(fileid);'; put 'run;'; put '%if &showlog=YES %then %do;'; put 'data _null_;'; put 'infile &fref lrecl=32767 end=last;'; put 'input;'; put 'if _n_=1 then putlog ''>>stpcodeBEGIN<<'';'; put 'putlog _infile_;'; put 'if last then putlog ''>>stpcodeEND<<'';'; put 'run;'; put '%end;'; put 'filename __getdoc clear;'; put '%if &outref=0 %then %do;'; put 'filename &fref clear;'; put '%end;'; put '%mend mm_getstpcode;'; put '%macro dc_getsettings();'; put '%global _program;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(syscc=&syscc on entry (&syswarningtext &syserrortext))'; put ')'; put '%if %length(&_PROGRAM)>1 %then %let root=&_program;'; put '%else %do;'; put '%global _metauser;'; put '%let _metauser=&sysuserid;'; put '/* to mimic a "real" _program we need to give a dummy role and stp name */'; put '%let root=/dummyRole/dummyName;'; put '%end;'; put '/* the DC precode is stored in the Admin folder in the root of'; put 'the project. Lets find that root. */'; put '%put &=root;'; put '%let root=%mf_getapploc();'; put '%put &=root;'; put '/* Now we know the root location we can retrieve the params */'; put '%let temploc=%sysfunc(pathname(work))/settings.txt;'; put '%mm_getstpcode(tree=&root/services/public'; put ',name=Data_Controller_Settings'; put ',outloc=&temploc'; put ')'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Unable to run getstpcode)'; put ')'; put 'filename _getsets "&temploc" lrecl=2000;'; put '/*'; put 'Do not use mp_include here - this puts a copy in every service, which creates'; put 'compilation problems when calling services from mp_include'; put '*/'; put '%inc _getsets/source2;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Problem running &sysmacroname (&syswarningtext &syserrortext))'; put ')'; put '%mend dc_getsettings;'; put '%macro mf_fmtdttm('; put ')/*/STORE SOURCE*/;'; put '%if "&sysver"="9.2" or "&sysver"="9.3"'; put 'or ("&sysver"="9.4" and "%substr(&SYSVLONG,9,1)" le "3")'; put 'or "%substr(&sysver,1,1)"="4"'; put 'or "%substr(&sysver,1,1)"="5"'; put '%then %do;DATETIME19.3%end;'; put '%else %do;E8601DT26.6%end;'; put '%mend mf_fmtdttm;'; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '%macro mp_init(prefix=SASJS'; put ')/*/STORE SOURCE*/;'; put '%if %symexist(SASJS_PREFIX) %then %return; /* only run once */'; put '%global'; put 'SASJS_PREFIX /* the ONLY hard-coded global macro variable in SASjs */'; put '&prefix._FUNCTIONS /* used in mcf_init() to track core function compilation */'; put '&prefix._INIT_NUM /* initialisation time as numeric */'; put '&prefix._INIT_DTTM /* initialisation time in E8601DT26.6 format */'; put '&prefix.WORK /* avoid typing %sysfunc(pathname(work)) every time */'; put ';'; put '%let sasjs_prefix=&prefix;'; put 'data _null_;'; put 'dttm=datetime();'; put 'call symputx("&sasjs_prefix._init_num",dttm,''g'');'; put 'call symputx("&sasjs_prefix._init_dttm",put(dttm,E8601DT26.6),''g'');'; put 'call symputx("&sasjs_prefix.work",pathname(''WORK''),''g'');'; put 'run;'; put 'options'; put 'compress=CHAR /* default is none so ensure we have something! */'; put 'datastmtchk=ALLKEYWORDS /* protection from overwriting input datasets */'; put 'errorcheck=STRICT /* catch errs in libname/filename statements */'; put 'fmterr /* ensure err when a format cannot be found */'; put 'mergenoby=%str(ERR)OR /* throw err when a merge has no BY variables */'; put 'missing=. /* changing this can cause hard to detect errs */'; put 'noquotelenmax /* avoid warnings for long strings */'; put 'noreplace /* avoid overwriting permanent datasets */'; put 'ps=max /* reduce log size slightly */'; put 'ls=max /* reduce log even more and avoid word truncation */'; put 'validmemname=COMPATIBLE /* avoid special characters etc in table names */'; put 'validvarname=V7 /* avoid special characters etc in variable names */'; put 'varinitchk=%str(ERR)OR /* avoid data mistakes from variable name typos */'; put 'varlenchk=%str(ERR)OR /* fail hard if truncation (data loss) can result */'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5" %then %do;'; put 'noautocorrect /* disallow misspelled procedure names */'; put 'dsoptions=note2err /* undocumented - convert bad NOTEs to ERRs */'; put '%end;'; put ';'; put '%mend mp_init;'; put '%macro mpeinit(fetch=YES);'; put '%global mpeinit'; put 'mpeadmins /* group with unrestricted Meditor access */'; put 'mpelocapprovals /* location for landing and staging files */'; put 'mpelib /* location of configuration tables for DC */'; put 'dc_repo_users /* location of user / group metadata */'; put 'dc_licence_key /* extracted in dc_getsettings */'; put 'dc_activation_key /* extracted in dc_getsettings */'; put 'dc_locale /* extracted in dc_getsettings */'; put 'dc_dttmtfmt /* can be overridden in dc_getsettings */'; put '_debug'; put ';'; put '%if &mpeinit=1 %then %return;'; put '%else %let mpeinit=1;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(Problem on service startup (&syswarningtext &syserrortext))'; put ')'; put '%mp_init()'; put '%if &fetch=YES %then %do;'; put '%webout(FETCH)'; put '%end;'; put '%global _CLIENTNAME;'; put '%mp_abort(iftrue= (&_CLIENTNAME=SAS Enterprise Guide)'; put ',mac=&_program..sas'; put ',msg=%str(Data Controller is a web app and should not be executed from EG)'; put ')'; put 'options urlencoding=utf8 nobomfile lrecl=32767;'; put '%let perf=%sysfunc(datetime());'; put '%put perfdiff: 0;'; put '%let dc_locale=SYSTEM; /* default if not set */'; put '/**'; put '* E8601DT26.6 has widest database support - but not all SAS flavours can'; put '* handle it. Override in the settings STP if needed.'; put '*/'; put 'data _null_;'; put 'dc_dttmtfmt=''"%sysfunc(datetime(),''!!"%mf_fmtdttm()"!!'')"dt'';'; put 'call symputx(''dc_dttmtfmt'',dc_dttmtfmt);'; put 'put dc_dttmtfmt=;'; put 'run;'; put '%put &=dc_dttmtfmt;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc prior to dc_getsettings)'; put ')'; put '%dc_getsettings()'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc after dc_getsettings)'; put ')'; put 'data _null_;'; put 'set &DC_LIBREF..mpe_config(where=('; put 'var_scope="DC"'; put 'and &dc_dttmtfmt lt tx_to'; put 'and var_active=1'; put '));'; put 'call symputx(var_name,var_value,''G'');'; put 'putlog var_name "=" var_value;'; put 'run;'; put '%let mpelib=&dc_libref;'; put '%let mpeadmins=&dc_admin_group;'; put '%let mpelocapprovals=&dc_staging_area;'; put '%let dc_repo_users=&dc_repo_users;'; put '%if &dc_locale ne SYSTEM %then %do;'; put 'options locale=&dc_locale;'; put '%end;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(Problem during compilation or with STP precode (&syswarningtext))'; put ')'; put '%mend mpeinit;'; put '%macro mf_mval(var);'; put '%if %symexist(&var) %then %do;'; put '%superq(&var)'; put '%end;'; put '%mend mf_mval;'; put '%macro mf_trimstr(basestr,trimstr);'; put '%local baselen trimlen trimval;'; put '/* return if basestr is shorter than trimstr (or 0) */'; put '%let baselen=%length(%superq(basestr));'; put '%let trimlen=%length(%superq(trimstr));'; put '%if &baselen < &trimlen or &baselen=0 %then %return;'; put '/* obtain the characters from the end of basestr */'; put '%let trimval=%qsubstr(%superq(basestr)'; put ',%length(%superq(basestr))-&trimlen+1'; put ',&trimlen);'; put '/* compare and if matching, chop it off! */'; put '%if %superq(basestr)=%superq(trimstr) %then %do;'; put '%return;'; put '%end;'; put '%else %if %superq(trimval)=%superq(trimstr) %then %do;'; put '%qsubstr(%superq(basestr),1,%length(%superq(basestr))-&trimlen)'; put '%end;'; put '%else %do;'; put '&basestr'; put '%end;'; put '%mend mf_trimstr;'; put '%macro mf_getplatform(switch'; put ')/*/STORE SOURCE*/;'; put '%local a b c;'; put '%if &switch.NONE=NONE %then %do;'; put '%if %symexist(sasjsprocessmode) %then %do;'; put '%if &sasjsprocessmode=Stored Program %then %do;'; put 'SASJS'; put '%return;'; put '%end;'; put '%end;'; put '%if %symexist(sysprocessmode) %then %do;'; put '%if "&sysprocessmode"="SAS Object Server"'; put 'or "&sysprocessmode"= "SAS Compute Server" %then %do;'; put 'SASVIYA'; put '%end;'; put '%else %if "&sysprocessmode"="SAS Stored Process Server"'; put 'or "&sysprocessmode"="SAS Workspace Server"'; put '%then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if %symexist(_metaport) or %symexist(_metauser) %then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if &switch=SASSTUDIO %then %do;'; put '/* return the version of SAS Studio else 0 */'; put '%if %mf_mval(_CLIENTAPP)=%str(SAS Studio) %then %do;'; put '%let a=%mf_mval(_CLIENTVERSION);'; put '%let b=%scan(&a,1,.);'; put '%if %eval(&b >2) %then %do;'; put '&b'; put '%end;'; put '%else 0;'; put '%end;'; put '%else 0;'; put '%end;'; put '%else %if &switch=VIYARESTAPI %then %do;'; put '%mf_trimstr(%sysfunc(getoption(servicesbaseurl)),/)'; put '%end;'; put '%mend mf_getplatform;'; put '%macro mpeterm();'; put '%local oldloc;'; put 'data _null_;'; put 'if symexist(''SYSPRINTTOLOG'') then oldloc=symget(''SYSPRINTTOLOG'');'; put 'else oldloc=getoption(''LOG'');'; put 'if subpad(oldloc,1,1) not in (''"'',"''",'' '') then oldloc=quote(cats(oldloc));'; put 'call symputx(''oldloc'',oldloc,''l'');'; put 'run;'; put '%if %length(&oldloc)>0 %then %do;'; put 'proc printto log=log;'; put 'run;'; put 'data _null_;'; put 'infile &oldloc;'; put 'input; putlog _infile_;'; put 'run;'; put '%end;'; put '%if %sysfunc(exist(&dc_libref..mpe_requests)) and %mf_getplatform() ne SASVIYA'; put '%then %do;'; put 'data ;'; put 'if 0 then set &dc_libref..mpe_requests;'; put 'request_dttm=%sysfunc(datetime());'; put 'request_user="%mf_getuser()";'; put 'request_service="%scan(&_program,-2,/)/%scan(&_program,-1,/)";'; put 'request_params='''';'; put 'output;stop;'; put 'proc append base=&dc_libref..mpe_requests data=&syslast force nowarn;'; put 'run;'; put '%end;'; put '%mend mpeterm;'; put '%macro mm_gettables('; put 'uri='; put ',outds=work.mm_gettables'; put ',getauth=YES'; put ')/*/STORE SOURCE*/;'; put 'data &outds;'; put 'length uri serveruri conn_uri domainuri libname ServerContext AuthDomain'; put 'path_schema usingpkguri type tableuri $256 id $17'; put 'libdesc $200 libref engine $8 IsDBMSLibname IsPreassigned $1'; put 'tablename $50 /* metadata table names can be longer than $32 */'; put ';'; put 'keep libname libdesc libref engine ServerContext path_schema AuthDomain'; put 'tableuri tablename IsPreassigned IsDBMSLibname id;'; put 'call missing (of _all_);'; put 'uri=symget(''uri'');'; put 'rc= metadata_getattr(uri, "Name", libname);'; put 'if rc <0 then do;'; put 'put ''The library is not defined in this metadata repository.'';'; put 'stop;'; put 'end;'; put 'rc= metadata_getattr(uri, "Desc", libdesc);'; put 'rc= metadata_getattr(uri, "Libref", libref);'; put 'rc= metadata_getattr(uri, "Engine", engine);'; put 'rc= metadata_getattr(uri, "IsDBMSLibname", IsDBMSLibname);'; put 'rc= metadata_getattr(uri, "IsPreassigned", IsPreassigned);'; put 'rc= metadata_getattr(uri, "Id", Id);'; put '/*** Get associated ServerContext ***/'; put 'rc= metadata_getnasn(uri, "DeployedComponents", 1, serveruri);'; put 'if rc > 0 then rc2= metadata_getattr(serveruri, "Name", ServerContext);'; put 'else ServerContext='''';'; put '/*** If the library is a DBMS library, get the Authentication Domain'; put 'associated with the DBMS connection credentials ***/'; put 'if IsDBMSLibname="1" and "&getauth"=''YES'' then do;'; put 'rc= metadata_getnasn(uri, "LibraryConnection", 1, conn_uri);'; put 'if rc>0 then do;'; put 'rc2= metadata_getnasn(conn_uri, "Domain", 1, domainuri);'; put 'if rc2>0 then rc3= metadata_getattr(domainuri, "Name", AuthDomain);'; put 'end;'; put 'end;'; put '/*** Get the path/database schema for this library ***/'; put 'rc=metadata_getnasn(uri, "UsingPackages", 1, usingpkguri);'; put 'if rc>0 then do;'; put 'rc=metadata_resolve(usingpkguri,type,id);'; put 'if type=''Directory'' then'; put 'rc=metadata_getattr(usingpkguri, "DirectoryName", path_schema);'; put 'else if type=''DatabaseSchema'' then'; put 'rc=metadata_getattr(usingpkguri, "Name", path_schema);'; put 'else path_schema="unknown";'; put 'end;'; put '/*** Get the tables associated with this library ***/'; put '/*** If DBMS, tables are associated with DatabaseSchema ***/'; put 'if type=''DatabaseSchema'' then do;'; put 't=1;'; put 'ntab=metadata_getnasn(usingpkguri, "Tables", t, tableuri);'; put 'if ntab>0 then do t=1 to ntab;'; put 'tableuri='''';'; put 'tablename='''';'; put 'ntab=metadata_getnasn(usingpkguri, "Tables", t, tableuri);'; put 'tabrc= metadata_getattr(tableuri, "Name", tablename);'; put 'output;'; put 'end;'; put 'else put ''Library '' libname '' has no tables registered'';'; put 'end;'; put 'else if type in (''Directory'',''SASLibrary'') then do;'; put 't=1;'; put 'ntab=metadata_getnasn(uri, "Tables", t, tableuri);'; put 'if ntab>0 then do t=1 to ntab;'; put 'tableuri='''';'; put 'tablename='''';'; put 'ntab=metadata_getnasn(uri, "Tables", t, tableuri);'; put 'tabrc= metadata_getattr(tableuri, "Name", tablename);'; put 'output;'; put 'end;'; put 'else put ''Library '' libname '' has no tables registered'';'; put 'end;'; put 'run;'; put 'proc sort;'; put 'by tablename tableuri;'; put 'run;'; put '%mend mm_gettables;'; put '* SAS Macros end;'; put '* SAS Includes start;'; put '* SAS Includes end;'; put '* Binary Files start;'; put '* Binary Files end;'; put '* ServiceInit start;'; put 'options noquotelenmax ps=max;'; put '/* create dummy macros to prevent stpbegin / stpend from corrupting sessions */'; put '%macro stpbegin();'; put '%put NOTE: the STPBEGIN macro should not be used for web apps!;'; put '%mend stpbegin;'; put '%macro stpend();'; put '%put NOTE: the STPEND macro should not be used for web apps!;'; put '%mend stpend;'; put '* ServiceInit end;'; put '* Service start;'; put '/**'; put '@file getmetatables.sas'; put '@brief List the tables as defined in metadata'; put '@details Provide a library uri and get list of tables'; put '

SAS Macros

'; put '@li mm_gettables.sas'; put '@li mp_abort.sas'; put '@version 9.2'; put '@author 4GL Apps Ltd'; put '@copyright 4GL Apps Ltd. This code may only be used within Data Controller'; put 'and may not be re-distributed or re-sold without the express permission of'; put '4GL Apps Ltd.'; put '**/'; put '%mpeinit()'; put '%global liburi;'; put '%let exist=%sysfunc(exist(work.SASControlTable));'; put '%let inds=%sysfunc(ifc(&exist=1,SASControlTable,_null_));'; put '%put &=inds;'; put 'data _null_;'; put 'set &inds;'; put 'call symputx(''liburi'',liburi);'; put 'run;'; put '/* load parameters */'; put '%mm_gettables(uri=&liburi,outds=metatables,getauth=NO)'; put 'data out;'; put 'set metatables;'; put 'keep table:;'; put 'run;'; put '%webout(OPEN)'; put '%webout(OBJ,metatables)'; put '%webout(CLOSE)'; put '%mpeterm()'; put '* Service end;'; run; %mm_createwebservice(path=&appLoc/&path, name=&service, code=sascode, server=&serverName, replace=yes) filename sascode clear; %let path=services/metanav; %let service=metadetails; filename sascode temp lrecl=32767; data _null_; file sascode; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '/**'; put '@file mp_jsonout.sas'; put '@brief Writes JSON in SASjs format to a fileref'; put '@details This macro can be used to OPEN a JSON stream and send one or more'; put 'tables as arrays of rows, where each row can be an object or a nested array.'; put 'There are two engines available - DATASTEP or PROCJSON.'; put 'PROC JSON is fast but will produce errs like the ones below if'; put 'special chars are encountered.'; put '> (ERR)OR: Some code points did not transcode.'; put '> An object or array close is not valid at this point in the JSON text.'; put '> Date value out of range'; put 'If this happens, try running with ENGINE=DATASTEP.'; put 'The DATASTEP engine is used to handle special SAS missing numerics, and'; put 'can also convert entire datasets to formatted values. Output JSON is always'; put 'in UTF-8.'; put 'Usage:'; put 'filename tmp temp;'; put 'data class; set sashelp.class;run;'; put '%mp_jsonout(OPEN,jref=tmp)'; put '%mp_jsonout(OBJ,class,jref=tmp)'; put '%mp_jsonout(OBJ,class,dslabel=class2,jref=tmp,showmeta=Y)'; put '%mp_jsonout(CLOSE,jref=tmp)'; put 'data _null_;'; put 'infile tmp;'; put 'input;putlog _infile_;'; put 'run;'; put 'If you are building web apps with SAS then you are strongly encouraged to use'; put 'the mX_createwebservice macros in combination with the'; put '[sasjs adapter](https://github.com/sasjs/adapter).'; put 'For more information see https://sasjs.io'; put '@param [in] action Valid values:'; put '@li OPEN - opens the JSON'; put '@li OBJ - sends a table with each row as an object'; put '@li ARR - sends a table with each row in an array'; put '@li CLOSE - closes the JSON'; put '@param [in] ds The dataset to send. Must be a work table.'; put '@param [out] jref= (_webout) The fileref to which to send the JSON'; put '@param [out] dslabel= The name to give the table in the exported JSON'; put '@param [in] fmt= (Y) Whether to keep (Y) or strip (N) formats from the table'; put '@param [in] engine= (DATASTEP) Which engine to use to send the JSON. Options:'; put '@li PROCJSON (default)'; put '@li DATASTEP (more reliable when data has non standard characters)'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to JSON'; put '

Related Files

'; put '@li mp_ds2fmtds.sas'; put '@version 9.2'; put '@author Allan Bowe'; put '@source https://github.com/sasjs/core'; put '**/'; put '%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y'; put ',engine=DATASTEP'; put ',missing=NULL'; put ',showmeta=N'; put ',maxobs=MAX'; put ')/*/STORE SOURCE*/;'; put '%local tempds colinfo fmtds i numcols numobs stmt_obs lastobs optval'; put 'tmpds1 tmpds2 tmpds3 tmpds4;'; put '%let numcols=0;'; put '%if &maxobs ne MAX %then %let stmt_obs=%str(if _n_>&maxobs then stop;);'; put '%if &action=OPEN %then %do;'; put 'options nobomfile;'; put 'data _null_;file &jref encoding=''utf-8'' lrecl=200;'; put 'put ''{"PROCESSED_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''"'';'; put 'run;'; put '%end;'; put '%else %if (&action=ARR or &action=OBJ) %then %do;'; put '/* force variable names to always be uppercase in the JSON */'; put 'options validvarname=upcase;'; put '/* To avoid issues with _webout on EBI - such as encoding diffs and truncation'; put '(https://support.sas.com/kb/49/325.html) we use temporary files */'; put 'filename _sjs1 temp lrecl=200 ;'; put 'data _null_; file _sjs1 encoding=''utf-8'';'; put 'put ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":";'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjs1 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs1 clear;'; put '/* grab col defs */'; put 'proc contents noprint data=&ds'; put 'out=_data_(keep=name type length format formatl formatd varnum label);'; put 'run;'; put '%let colinfo=%scan(&syslast,2,.);'; put 'proc sort data=&colinfo;'; put 'by varnum;'; put 'run;'; put '/* move meta to mac vars */'; put 'data &colinfo;'; put 'if _n_=1 then call symputx(''numcols'',nobs,''l'');'; put 'set &colinfo end=last nobs=nobs;'; put 'name=upcase(name);'; put '/* fix formats */'; put 'if type=2 or type=6 then do;'; put 'typelong=''char'';'; put 'length fmt $49.;'; put 'if format='''' then fmt=cats(''$'',length,''.'');'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else fmt=cats(format,formatl,''.'');'; put 'end;'; put 'else do;'; put 'typelong=''num'';'; put 'if format='''' then fmt=''best.'';'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else if formatd=0 then fmt=cats(format,formatl,''.'');'; put 'else fmt=cats(format,formatl,''.'',formatd);'; put 'end;'; put '/* 32 char unique name */'; put 'newname=''sasjs''!!substr(cats(put(md5(name),$hex32.)),1,27);'; put 'call symputx(cats(''name'',_n_),name,''l'');'; put 'call symputx(cats(''newname'',_n_),newname,''l'');'; put 'call symputx(cats(''length'',_n_),length,''l'');'; put 'call symputx(cats(''fmt'',_n_),fmt,''l'');'; put 'call symputx(cats(''type'',_n_),type,''l'');'; put 'call symputx(cats(''typelong'',_n_),typelong,''l'');'; put 'call symputx(cats(''label'',_n_),coalescec(label,name),''l'');'; put '/* overwritten when fmt=Y and a custom format exists in catalog */'; put 'if typelong=''num'' then call symputx(cats(''fmtlen'',_n_),200,''l'');'; put 'else call symputx(cats(''fmtlen'',_n_),min(32767,ceil((length+10)*1.5)),''l'');'; put 'run;'; put '%let tempds=%substr(_%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql;'; put 'select count(*) into: lastobs from &ds;'; put '%if &maxobs ne MAX %then %let lastobs=%sysfunc(min(&lastobs,&maxobs));'; put '%if &engine=PROCJSON %then %do;'; put '%if &missing=STRING %then %do;'; put '%put &sysmacroname: Special Missings not supported in proc json.;'; put '%put &sysmacroname: Switching to DATASTEP engine;'; put '%goto datastep;'; put '%end;'; put 'data &tempds;'; put 'set &ds;'; put '&stmt_obs;'; put '%if &fmt=N %then format _numeric_ best32.;;'; put '/* PRETTY is necessary to avoid line truncation in large files */'; put 'filename _sjs2 temp lrecl=131068 encoding=''utf-8'';'; put 'proc json out=_sjs2 pretty'; put '%if &action=ARR %then nokeys ;'; put ';export &tempds / nosastags fmtnumeric;'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs2 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs2 clear;'; put '%end;'; put '%else %if &engine=DATASTEP %then %do;'; put '%datastep:'; put '%if %sysfunc(exist(&ds)) ne 1 & %sysfunc(exist(&ds,VIEW)) ne 1'; put '%then %do;'; put '%put &sysmacroname: &ds NOT FOUND!!!;'; put '%return;'; put '%end;'; put '%if &fmt=Y %then %do;'; put '/**'; put '* Extract format definitions'; put '* First, by getting library locations from dictionary.formats'; put '* Then, by exporting the width using proc format'; put '* Cannot use maxw from sashelp.vformat as not always populated'; put '* Cannot use fmtinfo() as not supported in all flavours'; put '*/'; put '%let tmpds1=%substr(fmtsum%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds2=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds3=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds4=%substr(col%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql noprint;'; put 'create table &tmpds1 as'; put 'select cats(libname,''.'',memname) as FMTCAT,'; put 'FMTNAME'; put 'from dictionary.formats'; put 'where fmttype=''F'' and libname is not null'; put 'and fmtname in (select format from &colinfo where format is not null)'; put 'order by 1;'; put 'create table &tmpds2('; put 'FMTNAME char(32),'; put 'LENGTH num'; put ');'; put '%local catlist cat fmtlist i;'; put 'select distinct fmtcat into: catlist separated by '' '' from &tmpds1;'; put '%do i=1 %to %sysfunc(countw(&catlist,%str( )));'; put '%let cat=%scan(&catlist,&i,%str( ));'; put 'proc sql;'; put 'select distinct fmtname into: fmtlist separated by '' '''; put 'from &tmpds1 where fmtcat="&cat";'; put 'proc format lib=&cat cntlout=&tmpds3(keep=fmtname length);'; put 'select &fmtlist;'; put 'run;'; put 'proc sql;'; put 'insert into &tmpds2 select distinct fmtname,length from &tmpds3;'; put '%end;'; put 'proc sql;'; put 'create table &tmpds4 as'; put 'select a.*, b.length as MAXW'; put 'from &colinfo a'; put 'left join &tmpds2 b'; put 'on cats(a.format)=cats(upcase(b.fmtname))'; put 'order by a.varnum;'; put 'data _null_;'; put 'set &tmpds4;'; put 'if not missing(maxw);'; put 'call symputx('; put 'cats(''fmtlen'',_n_),'; put '/* vars need extra padding due to JSON escaping of special chars */'; put 'min(32767,ceil((max(length,maxw)+10)*1.5))'; put ',''l'''; put ');'; put 'run;'; put '/* configure varlenchk - as we are explicitly shortening the variables */'; put '%let optval=%sysfunc(getoption(varlenchk));'; put 'options varlenchk=NOWARN;'; put 'data _data_(compress=char);'; put '/* shorten the new vars */'; put 'length'; put '%do i=1 %to &numcols;'; put '&&name&i $&&fmtlen&i'; put '%end;'; put ';'; put '/* rename on entry */'; put 'set &ds(rename=('; put '%do i=1 %to &numcols;'; put '&&name&i=&&newname&i'; put '%end;'; put '));'; put '&stmt_obs;'; put 'drop'; put '%do i=1 %to &numcols;'; put '&&newname&i'; put '%end;'; put ';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=num %then %do;'; put '&&name&i=cats(put(&&newname&i,&&fmt&i));'; put '%end;'; put '%else %do;'; put '&&name&i=put(&&newname&i,&&fmt&i);'; put '%end;'; put '%end;'; put 'if _error_ then do;'; put 'call symputx(''syscc'',1012);'; put 'stop;'; put 'end;'; put 'run;'; put '%let fmtds=&syslast;'; put 'options varlenchk=&optval;'; put '%end;'; put 'proc format; /* credit yabwon for special null removal */'; put 'value bart (default=40)'; put '%if &missing=NULL %then %do;'; put '._ - .z = null'; put '%end;'; put '%else %do;'; put '._ = [quote()]'; put '. = null'; put '.a - .z = [quote()]'; put '%end;'; put 'other = [best.];'; put 'data &tempds;'; put 'attrib _all_ label='''';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'length &&name&i $&&fmtlen&i...;'; put 'format &&name&i $&&fmtlen&i...;'; put '%end;'; put '%end;'; put '%if &fmt=Y %then %do;'; put 'set &fmtds;'; put '%end;'; put '%else %do;'; put 'set &ds;'; put '%end;'; put '&stmt_obs;'; put 'format _numeric_ bart.;'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'if findc(&&name&i,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put '&&name&i=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,&&name&i)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else &&name&i=quote(cats(&&name&i));'; put '%end;'; put '%end;'; put 'run;'; put 'filename _sjs3 temp lrecl=131068 ;'; put 'data _null_;'; put 'file _sjs3 encoding=''utf-8'';'; put 'if _n_=1 then put "[";'; put 'set &tempds;'; put 'if _n_>1 then put "," @; put'; put '%if &action=ARR %then "[" ; %else "{" ;'; put '%do i=1 %to &numcols;'; put '%if &i>1 %then "," ;'; put '%if &action=OBJ %then """&&name&i"":" ;'; put '"&&name&i"n /* name literal for reserved variable names */'; put '%end;'; put '%if &action=ARR %then "]" ; %else "}" ; ;'; put '/* close out the table */'; put 'data _null_;'; put 'file _sjs3 mod encoding=''utf-8'';'; put 'put '']'';'; put 'run;'; put 'data _null_;'; put 'infile _sjs3 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs3 clear;'; put '%end;'; put 'proc sql;'; put 'drop table &colinfo, &tempds;'; put '%if %substr(&showmeta,1,1)=Y %then %do;'; put 'filename _sjs4 temp lrecl=131068 encoding=''utf-8'';'; put 'data _null_;'; put 'file _sjs4;'; put 'length label $350;'; put 'put ", ""$%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":{""vars"":{";'; put 'do i=1 to &numcols;'; put 'name=quote(trim(symget(cats(''name'',i))));'; put 'format=quote(trim(symget(cats(''fmt'',i))));'; put 'label=quote(prxchange(''s/\\/\\\\/'',-1,trim(symget(cats(''label'',i)))));'; put 'length=quote(trim(symget(cats(''length'',i))));'; put 'type=quote(trim(symget(cats(''typelong'',i))));'; put 'if i>1 then put "," @@;'; put 'put name '':{"format":'' format '',"label":'' label'; put ''',"length":'' length '',"type":'' type ''}'';'; put 'end;'; put 'put ''}}'';'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs4 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs4 clear;'; put '%end;'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put 'data _null_; file &jref encoding=''utf-8'' mod ;'; put 'put "}";'; put 'run;'; put '%end;'; put '%mend mp_jsonout;'; put '/**'; put '@file mm_webout.sas'; put '@brief Send data to/from SAS Stored Processes'; put '@details This macro should be added to the start of each Stored Process,'; put '**immediately** followed by a call to:'; put '%mm_webout(FETCH)'; put 'This will read all the input data and create same-named SAS datasets in the'; put 'WORK library. You can then insert your code, and send data back using the'; put 'following syntax:'; put 'data some datasets; * make some data ;'; put 'retain some columns;'; put 'run;'; put '%mm_webout(OPEN)'; put '%mm_webout(ARR,some) * Array format, fast, suitable for large tables ;'; put '%mm_webout(OBJ,datasets) * Object format, easier to work with ;'; put 'Finally, wrap everything up send some helpful system variables too'; put '%mm_webout(CLOSE)'; put '@param [in] action Either FETCH, OPEN, ARR, OBJ or CLOSE'; put '@param [in] ds The dataset to send back to the frontend'; put '@param [out] dslabel= Value to use instead of table name for sending to JSON'; put '@param [in] fmt= (N) Setting Y converts all vars to their formatted values'; put '@param [out] fref= (_webout) The fileref to which to write the JSON'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] workobs= (0) When set to a positive integer, will create a new'; put 'output object (WORK) which contains this number of observations from all'; put 'tables in the WORK library.'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to output JSON'; put '

SAS Macros

'; put '@li mp_jsonout.sas'; put '

Related Macros

'; put '@li ms_webout.sas'; put '@li mv_webout.sas'; put '@version 9.3'; put '@author Allan Bowe'; put '**/'; put '%macro mm_webout(action,ds,dslabel=,fref=_webout,fmt=N,missing=NULL'; put ',showmeta=N,maxobs=MAX,workobs=0'; put ');'; put '%global _webin_file_count _webin_fileref1 _webin_name1 _program _debug'; put 'sasjs_tables;'; put '%local i tempds jsonengine;'; put '/* see https://github.com/sasjs/core/issues/41 */'; put '%if "%upcase(&SYSENCODING)" ne "UTF-8" %then %let jsonengine=PROCJSON;'; put '%else %let jsonengine=DATASTEP;'; put '%if &action=FETCH %then %do;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'options mprint notes mprintnest;'; put '%end;'; put '%let _webin_file_count=%eval(&_webin_file_count+0);'; put '/* now read in the data */'; put '%do i=1 %to &_webin_file_count;'; put '%if &_webin_file_count=1 %then %do;'; put '%let _webin_fileref1=&_webin_fileref;'; put '%let _webin_name1=&_webin_name;'; put '%end;'; put 'data _null_;'; put 'infile &&_webin_fileref&i termstr=crlf;'; put 'input;'; put 'call symputx(''input_statement'',_infile_);'; put 'putlog "&&_webin_name&i input statement: " _infile_;'; put 'stop;'; put 'data &&_webin_name&i;'; put 'infile &&_webin_fileref&i firstobs=2 dsd termstr=crlf encoding=''utf-8'';'; put 'input &input_statement;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'if _n_<20 then putlog _infile_;'; put '%end;'; put 'run;'; put '%let sasjs_tables=&sasjs_tables &&_webin_name&i;'; put '%end;'; put '%end;'; put '%else %if &action=OPEN %then %do;'; put '/* fix encoding */'; put 'OPTIONS NOBOMFILE;'; put '/**'; put '* check xengine type to avoid the below err message:'; put '* > Function is only valid for filerefs using the CACHE access method.'; put '*/'; put 'data _null_;'; put 'set sashelp.vextfl(where=(fileref="_WEBOUT"));'; put 'if xengine=''STREAM'' then do;'; put 'rc=stpsrv_header(''Content-type'',"text/html; encoding=utf-8");'; put 'end;'; put 'run;'; put '/* setup json */'; put 'data _null_;file &fref encoding=''utf-8'';'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutBEGIN<<'';'; put '%end;'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'run;'; put '%end;'; put '%else %if &action=ARR or &action=OBJ %then %do;'; put '%mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt,jref=&fref'; put ',engine=&jsonengine,missing=&missing,showmeta=&showmeta,maxobs=&maxobs'; put ')'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put '/* To avoid issues with _webout on EBI we use a temporary file */'; put 'filename _sjsref temp lrecl=131068;'; put '%if %str(&workobs) > 0 %then %do;'; put '/* if debug mode, send back first XX records of each work table also */'; put 'data;run;%let tempds=%scan(&syslast,2,.);'; put 'ods output Members=&tempds;'; put 'proc datasets library=WORK memtype=data;'; put '%local wtcnt;%let wtcnt=0;'; put 'data _null_;'; put 'set &tempds;'; put 'if not (upcase(name) =:"DATA"); /* ignore temp datasets */'; put 'i+1;'; put 'call symputx(cats(''wt'',i),name,''l'');'; put 'call symputx(''wtcnt'',i,''l'');'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put ",""WORK"":{";'; put '%do i=1 %to &wtcnt;'; put '%let wt=&&wt&i;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'dsid=open("WORK.&wt",''is'');'; put 'nlobs=attrn(dsid,''NLOBS'');'; put 'nvars=attrn(dsid,''NVARS'');'; put 'rc=close(dsid);'; put 'if &i>1 then put '',''@;'; put 'put " ""&wt"" : {";'; put 'put ''"nlobs":'' nlobs;'; put 'put '',"nvars":'' nvars;'; put '%mp_jsonout(OBJ,&wt,jref=_sjsref,dslabel=first10rows,showmeta=Y'; put ',maxobs=&workobs'; put ')'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put '%end;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put 'run;'; put '%end;'; put '/* close off json */'; put 'data _null_;file _sjsref mod encoding=''utf-8'';'; put 'length SYSPROCESSNAME syserrortext syswarningtext autoexec $512;'; put 'put ",""_DEBUG"" : ""&_debug"" ";'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'autoexec=quote(urlencode(trim(getoption(''autoexec''))));'; put 'put '',"AUTOEXEC" : '' autoexec;'; put 'put ",""MF_GETUSER"" : ""%mf_getuser()"" ";'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'put ",""SYSENCODING"" : ""&sysencoding"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSPROCESSID"" : ""&SYSPROCESSID"" ";'; put 'put ",""SYSPROCESSMODE"" : ""&SYSPROCESSMODE"" ";'; put 'SYSPROCESSNAME=quote(urlencode(cats(SYSPROCESSNAME)));'; put 'put ",""SYSPROCESSNAME"" : " SYSPROCESSNAME;'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put '',"SYSWARNINGTEXT" : '' syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'length memsize $32;'; put 'memsize="%sysfunc(INPUTN(%sysfunc(getoption(memsize)), best.),sizekmg.)";'; put 'memsize=quote(cats(memsize));'; put 'put '',"MEMSIZE" : '' memsize;'; put 'put "}" @;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutEND<<'';'; put '%end;'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjsref lrecl=1 recfm=n;'; put 'file &fref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjsref clear;'; put '%end;'; put '%mend mm_webout;'; put '%macro webout(action,ds,dslabel=,fmt=,missing=NULL,showmeta=NO,maxobs=MAX);'; put '%mm_webout(&action,ds=&ds,dslabel=&dslabel,fmt=&fmt'; put ',missing=&missing'; put ',showmeta=&showmeta'; put ',maxobs=&maxobs'; put ') %mend;'; put '/* provide additional debug info */'; put '%global _program;'; put '%put &=syscc;'; put '%put user=%mf_getuser();'; put '%put pgm=&_program;'; put '%put timestamp=%sysfunc(datetime(),datetime19.);'; put '* Service Variables start;'; put '* Service Variables end;'; put '* SAS Macros start;'; put '%macro mf_getapploc(pgm);'; put '%if "&pgm"="" %then %do;'; put '%if %symexist(_program) %then %let pgm=&_program;'; put '%else %do;'; put '%put &sysmacroname: No value provided and no _program variable available;'; put '%return;'; put '%end;'; put '%end;'; put '%local root;'; put '/**'; put '* First check we are not in the tests/macros folder (which has no subfolders)'; put '* or specifically in the testsetup or testteardown services'; put '*/'; put '%if %index(&pgm,/tests/macros/)'; put 'or %index(&pgm,/tests/testsetup)'; put 'or %index(&pgm,/tests/testteardown)'; put '%then %do;'; put '%let root=%substr(&pgm,1,%index(&pgm,/tests)-1);'; put '&root'; put '%return;'; put '%end;'; put '/**'; put '* Next, move up two levels to avoid matches on subfolder or service name'; put '*/'; put '%let root=%substr(&pgm,1,%length(&pgm)-%length(%scan(&pgm,-1,/))-1);'; put '%let root=%substr(&root,1,%length(&root)-%length(%scan(&root,-1,/))-1);'; put '%if %index(&root,/tests/) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/tests/)-1);'; put '%end;'; put '%else %if %index(&root,/services) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/services)-1);'; put '%end;'; put '%else %if %index(&root,/jobs) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/jobs)-1);'; put '%end;'; put '%else %put &sysmacroname: Could not find an app location from &pgm;'; put '&root'; put '%mend mf_getapploc ;'; put '%macro mf_getuniquefileref(prefix=_,maxtries=1000,lrecl=32767);'; put '%local rc fname;'; put '%if &prefix=0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%end;'; put '%else %do;'; put '%local x len;'; put '%let len=%eval(8-%length(&prefix));'; put '%let x=0;'; put '%do x=0 %to &maxtries;'; put '%let fname=&prefix%substr(%sysfunc(ranuni(0)),3,&len);'; put '%if %sysfunc(fileref(&fname)) > 0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%return;'; put '%end;'; put '%end;'; put '%put unable to find available fileref after &maxtries attempts;'; put '%end;'; put '%mend mf_getuniquefileref;'; put '%macro mp_abort(mac=mp_abort.sas, type=, msg=, iftrue=%str(1=1)'; put ', errds=work.mp_abort_errds'; put ', mode=REGULAR'; put ')/*/STORE SOURCE*/;'; put '%global sysprocessmode sysprocessname sasjs_stpsrv_header_loc sasjsprocessmode;'; put '%local fref fid i;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%put NOTE: /// mp_abort macro executing //;'; put '%if %length(&mac)>0 %then %put NOTE- called by &mac;'; put '%put NOTE - &msg;'; put '%if %symexist(_SYSINCLUDEFILEDEVICE)'; put '/* abort cancel FILE does not restart outside the INCLUDE on Viya 3.5 */'; put 'and %superq(SYSPROCESSNAME) ne %str(Compute Server)'; put '%then %do;'; put '%if "*&_SYSINCLUDEFILEDEVICE*" ne "**" %then %do;'; put 'data &errds;'; put 'iftrue=''1=1'';'; put 'length mac $100 msg $5000;'; put 'mac=symget(''mac'');'; put 'msg=symget(''msg'');'; put 'run;'; put 'data _null_;'; put 'abort cancel FILE;'; put 'run;'; put '%return;'; put '%end;'; put '%end;'; put '/* Web App Context */'; put '%if %symexist(_PROGRAM)'; put 'or %superq(SYSPROCESSNAME) = %str(Compute Server)'; put 'or &mode=INCLUDE'; put '%then %do;'; put 'options obs=max replace mprint;'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5"'; put '%then %do;'; put 'options nosyntaxcheck;'; put '%end;'; put '%if &mode=INCLUDE %then %do;'; put '%if %sysfunc(exist(&errds))=1 %then %do;'; put 'data _null_;'; put 'set &errds;'; put 'call symputx(''iftrue'',iftrue,''l'');'; put 'call symputx(''mac'',mac,''l'');'; put 'call symputx(''msg'',msg,''l'');'; put 'putlog (_all_)(=);'; put 'run;'; put '%if (&iftrue)=0 %then %return;'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: No include errors found;'; put '%return;'; put '%end;'; put '%end;'; put '/* extract log errs / warns, if exist */'; put '%local logloc logline;'; put '%global logmsg; /* capture global messages */'; put '%if %symexist(SYSPRINTTOLOG) %then %let logloc=&SYSPRINTTOLOG;'; put '%else %let logloc=%qsysfunc(getoption(LOG));'; put 'proc printto log=log;run;'; put '%let logline=0;'; put '%if %length(&logloc)>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input; putlog _infile_;'; put 'i=1;'; put 'retain logonce 0;'; put 'if ('; put '_infile_=:"%str(WARN)ING" or _infile_=:"%str(ERR)OR"'; put ') and logonce=0 then'; put 'do;'; put 'call symputx(''logline'',_n_);'; put 'logonce+1;'; put 'end;'; put 'run;'; put '/* capture log including lines BEFORE the err */'; put '%if &logline>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input;'; put 'i=1;'; put 'stoploop=0;'; put 'if _n_ ge &logline-15 and stoploop=0 then do until (i>22);'; put 'call symputx(''logmsg'',catx(''\n'',symget(''logmsg''),_infile_));'; put 'input;'; put 'i+1;'; put 'stoploop=1;'; put 'end;'; put 'if stoploop=1 then stop;'; put 'run;'; put '%end;'; put '%end;'; put '%if %symexist(SYS_JES_JOB_URI) %then %do;'; put '/* setup webout for Viya */'; put 'options nobomfile;'; put '%if "X&SYS_JES_JOB_URI.X"="XX" %then %do;'; put 'filename _webout temp lrecl=999999 mod;'; put '%end;'; put '%else %do;'; put 'filename _webout filesrvc parenturi="&SYS_JES_JOB_URI"'; put 'name="_webout.json" lrecl=999999 mod;'; put '%end;'; put '%end;'; put '%else %if %sysfunc(filename(fref,&sasjs_stpsrv_header_loc))=0 %then %do;'; put 'options nobomfile;'; put '/* set up http header for SASjs Server */'; put '%let fid=%sysfunc(fopen(&fref,A));'; put '%if &fid=0 %then %do;'; put '%put %str(ERR)OR: %sysfunc(sysmsg());'; put '%return;'; put '%end;'; put '%let rc=%sysfunc(fput(&fid,%str(Content-Type: application/json)));'; put '%let rc=%sysfunc(fwrite(&fid));'; put '%let rc=%sysfunc(fclose(&fid));'; put '%let rc=%sysfunc(filename(&fref));'; put '%end;'; put '/* send response in SASjs JSON format */'; put 'data _null_;'; put 'file _webout mod lrecl=32000 encoding=''utf-8'';'; put 'length msg syswarningtext syserrortext $32767 mode $10 ;'; put 'sasdatetime=datetime();'; put 'msg=symget(''msg'');'; put '%if &logline>0 %then %do;'; put 'msg=cats(msg,''\n\nLog Extract:\n'',symget(''logmsg''));'; put '%end;'; put '/* escape the escapes */'; put 'msg=tranwrd(msg,''\'',''\\'');'; put '/* escape the quotes */'; put 'msg=tranwrd(msg,''"'',''\"'');'; put '/* ditch the CRLFs as chrome complains */'; put 'msg=compress(msg,,''kw'');'; put '/* quote without quoting the quotes (which are escaped instead) */'; put 'msg=cats(''"'',msg,''"'');'; put 'if symexist(''_debug'') then debug=quote(trim(symget(''_debug'')));'; put 'else debug=''""'';'; put 'if symget(''sasjsprocessmode'')=''Stored Program'' then mode=''SASJS'';'; put 'if mode ne ''SASJS'' then put ''>>weboutBEGIN<<'';'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'put '',"sasjsAbort" : [{'';'; put 'put '' "MSG":'' msg ;'; put 'put '' ,"MAC": "'' "&mac" ''"}]'';'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'put '',"_DEBUG":'' debug ;'; put 'if symexist(''_metauser'') then do;'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put 'end;'; put 'if symexist(''SYS_JES_JOB_URI'') then do;'; put 'SYS_JES_JOB_URI=quote(trim(symget(''SYS_JES_JOB_URI'')));'; put 'put '',"SYS_JES_JOB_URI": '' SYS_JES_JOB_URI;'; put 'end;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put ",""SYSWARNINGTEXT"" : " syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'put "}" ;'; put 'if mode ne ''SASJS'' then put ''>>weboutEND<<'';'; put 'run;'; put '%put _all_;'; put '%if "&sysprocessmode " = "SAS Stored Process Server " %then %do;'; put 'data _null_;'; put 'putlog ''stpsrvset program err and syscc'';'; put 'rc=stpsrvset(''program error'', 0);'; put 'call symputx("syscc",0,"g");'; put 'run;'; put '%if &sysscp=WIN'; put 'and 1=0 /* deprecating this logic until we figure out a consistent abort */'; put 'and "%substr(%str(&sysvlong ),1,8)"="9.04.01M"'; put 'and "%substr(%str(&sysvlong ),9,1)">"5" %then %do;'; put '/* skip approach (below) does not work in windows m6+ envs */'; put 'endsas;'; put '%end;'; put '%else %do;'; put '/**'; put '* endsas kills 9.4m3 deployments by orphaning multibridges.'; put '* Abort variants are ungraceful (non zero return code)'; put '* This approach lets SAS run silently until the end :-)'; put '* Caution - fails when called within a %include within a macro'; put '* Use mp_include() to handle this.'; put '*/'; put 'filename skip temp;'; put 'data _null_;'; put 'file skip;'; put 'put ''%macro skip();'';'; put 'comment ''%mend skip; -> fix lint '';'; put 'put ''%macro skippy();'';'; put 'comment ''%mend skippy; -> fix lint '';'; put 'run;'; put '%inc skip;'; put '%end;'; put '%end;'; put '%else %if "&sysprocessmode " = "SAS Compute Server " %then %do;'; put '/* endsas kills the session making it harder to fetch results */'; put 'data _null_;'; put 'syswarningtext=symget(''syswarningtext'');'; put 'syserrortext=symget(''syserrortext'');'; put 'abort_msg=symget(''msg'');'; put 'syscc=symget(''syscc'');'; put 'sysuserid=symget(''sysuserid'');'; put 'iftrue=symget(''iftrue'');'; put 'put (_all_)(/=);'; put 'call symputx(''syscc'',0);'; put 'abort cancel nolist;'; put 'run;'; put '%end;'; put '%else %do;'; put '%abort cancel;'; put '%end;'; put '%end;'; put '%else %do;'; put '%put _all_;'; put '%abort cancel;'; put '%end;'; put '%mend mp_abort;'; put '/** @endcond */'; put '%macro mm_getstpcode('; put 'tree=/User Folders/sasdemo/somestp'; put ',name='; put ',outloc=0'; put ',outref=0'; put ',mDebug=1'; put ',showlog=NO'; put ');'; put '%local mD;'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing &sysmacroname..sas;'; put '%&mD.put _local_;'; put '%if %length(&name)>0 %then %let name=/&name;'; put '/* first, check if STP exists */'; put '%local tsuri;'; put '%let tsuri=stopifempty ;'; put 'data _null_;'; put 'format type uri tsuri value $200.;'; put 'call missing (of _all_);'; put 'path="&tree&name(StoredProcess)";'; put '/* first, find the STP ID */'; put 'if metadata_pathobj("",path,"StoredProcess",type,uri)>0 then do;'; put '/* get sourcecode */'; put 'cnt=1;'; put 'do while (metadata_getnasn(uri,"Notes",cnt,tsuri)>0);'; put 'rc=metadata_getattr(tsuri,"Name",value);'; put '&mD.put tsuri= value=;'; put 'if value="SourceCode" then do;'; put '/* found it! */'; put 'rc=metadata_getattr(tsuri,"Id",value);'; put 'call symputx(''tsuri'',value,''l'');'; put 'stop;'; put 'end;'; put 'cnt+1;'; put 'end;'; put 'end;'; put 'else put (_all_)(=);'; put 'run;'; put '%mp_abort(iftrue= (&tsuri=stopifempty)'; put ',mac=mm_getstpcode'; put ',msg=%str(&tree&name.(StoredProcess) not found!)'; put ')'; put '/**'; put '* Now we can extract the textstore'; put '*/'; put 'filename __getdoc temp lrecl=10000000;'; put 'proc metadata'; put 'in="$METAREPOSITORY'; put ''; put 'SAS1"'; put 'out=__getdoc ;'; put 'run;'; put '/* find the beginning of the text */'; put '%local start;'; put 'data _null_;'; put 'infile __getdoc lrecl=10000;'; put 'input;'; put 'start=index(_infile_,''StoredText="'');'; put 'if start then do;'; put 'call symputx("start",start+11);'; put '*putlog ''"'' _infile_ ''"'';'; put 'end;'; put 'stop;'; put '%local outeng;'; put '%if "&outloc"="0" %then %let outeng=TEMP;'; put '%else %let outeng="&outloc";'; put '%local fref;'; put '%if &outref=0 %then %let fref=%mf_getuniquefileref();'; put '%else %let fref=&outref;'; put '/* read the content, byte by byte, resolving escaped chars */'; put 'filename &fref &outeng lrecl=100000;'; put 'data _null_;'; put 'length filein 8 fileid 8;'; put 'filein = fopen("__getdoc","I",1,"B");'; put 'fileid = fopen("&fref","O",1,"B");'; put 'rec = "20"x;'; put 'length entity $6;'; put 'do while(fread(filein)=0);'; put 'x+1;'; put 'if x>&start then do;'; put 'rc = fget(filein,rec,1);'; put 'if rec=''"'' then leave;'; put 'else if rec="&" then do;'; put 'entity=rec;'; put 'do until (rec=";");'; put 'if fread(filein) ne 0 then goto getout;'; put 'rc = fget(filein,rec,1);'; put 'entity=cats(entity,rec);'; put 'end;'; put 'select (entity);'; put 'when (''&'' ) rec=''&'' ;'; put 'when (''<'' ) rec=''<'' ;'; put 'when (''>'' ) rec=''>'' ;'; put 'when (''''') rec="''" ;'; put 'when (''"'') rec=''"'' ;'; put 'when ('' '') rec=''0A''x;'; put 'when ('' '') rec=''0D''x;'; put 'when (''$'' ) rec=''$'' ;'; put 'when ('' '') rec=''09''x;'; put 'otherwise putlog "%str(WARN)ING: missing value for " entity=;'; put 'end;'; put 'rc =fput(fileid, substr(rec,1,1));'; put 'rc =fwrite(fileid);'; put 'end;'; put 'else do;'; put 'rc =fput(fileid,rec);'; put 'rc =fwrite(fileid);'; put 'end;'; put 'end;'; put 'end;'; put 'getout:'; put 'rc=fclose(filein);'; put 'rc=fclose(fileid);'; put 'run;'; put '%if &showlog=YES %then %do;'; put 'data _null_;'; put 'infile &fref lrecl=32767 end=last;'; put 'input;'; put 'if _n_=1 then putlog ''>>stpcodeBEGIN<<'';'; put 'putlog _infile_;'; put 'if last then putlog ''>>stpcodeEND<<'';'; put 'run;'; put '%end;'; put 'filename __getdoc clear;'; put '%if &outref=0 %then %do;'; put 'filename &fref clear;'; put '%end;'; put '%mend mm_getstpcode;'; put '%macro dc_getsettings();'; put '%global _program;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(syscc=&syscc on entry (&syswarningtext &syserrortext))'; put ')'; put '%if %length(&_PROGRAM)>1 %then %let root=&_program;'; put '%else %do;'; put '%global _metauser;'; put '%let _metauser=&sysuserid;'; put '/* to mimic a "real" _program we need to give a dummy role and stp name */'; put '%let root=/dummyRole/dummyName;'; put '%end;'; put '/* the DC precode is stored in the Admin folder in the root of'; put 'the project. Lets find that root. */'; put '%put &=root;'; put '%let root=%mf_getapploc();'; put '%put &=root;'; put '/* Now we know the root location we can retrieve the params */'; put '%let temploc=%sysfunc(pathname(work))/settings.txt;'; put '%mm_getstpcode(tree=&root/services/public'; put ',name=Data_Controller_Settings'; put ',outloc=&temploc'; put ')'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Unable to run getstpcode)'; put ')'; put 'filename _getsets "&temploc" lrecl=2000;'; put '/*'; put 'Do not use mp_include here - this puts a copy in every service, which creates'; put 'compilation problems when calling services from mp_include'; put '*/'; put '%inc _getsets/source2;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Problem running &sysmacroname (&syswarningtext &syserrortext))'; put ')'; put '%mend dc_getsettings;'; put '%macro mf_fmtdttm('; put ')/*/STORE SOURCE*/;'; put '%if "&sysver"="9.2" or "&sysver"="9.3"'; put 'or ("&sysver"="9.4" and "%substr(&SYSVLONG,9,1)" le "3")'; put 'or "%substr(&sysver,1,1)"="4"'; put 'or "%substr(&sysver,1,1)"="5"'; put '%then %do;DATETIME19.3%end;'; put '%else %do;E8601DT26.6%end;'; put '%mend mf_fmtdttm;'; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '%macro mp_init(prefix=SASJS'; put ')/*/STORE SOURCE*/;'; put '%if %symexist(SASJS_PREFIX) %then %return; /* only run once */'; put '%global'; put 'SASJS_PREFIX /* the ONLY hard-coded global macro variable in SASjs */'; put '&prefix._FUNCTIONS /* used in mcf_init() to track core function compilation */'; put '&prefix._INIT_NUM /* initialisation time as numeric */'; put '&prefix._INIT_DTTM /* initialisation time in E8601DT26.6 format */'; put '&prefix.WORK /* avoid typing %sysfunc(pathname(work)) every time */'; put ';'; put '%let sasjs_prefix=&prefix;'; put 'data _null_;'; put 'dttm=datetime();'; put 'call symputx("&sasjs_prefix._init_num",dttm,''g'');'; put 'call symputx("&sasjs_prefix._init_dttm",put(dttm,E8601DT26.6),''g'');'; put 'call symputx("&sasjs_prefix.work",pathname(''WORK''),''g'');'; put 'run;'; put 'options'; put 'compress=CHAR /* default is none so ensure we have something! */'; put 'datastmtchk=ALLKEYWORDS /* protection from overwriting input datasets */'; put 'errorcheck=STRICT /* catch errs in libname/filename statements */'; put 'fmterr /* ensure err when a format cannot be found */'; put 'mergenoby=%str(ERR)OR /* throw err when a merge has no BY variables */'; put 'missing=. /* changing this can cause hard to detect errs */'; put 'noquotelenmax /* avoid warnings for long strings */'; put 'noreplace /* avoid overwriting permanent datasets */'; put 'ps=max /* reduce log size slightly */'; put 'ls=max /* reduce log even more and avoid word truncation */'; put 'validmemname=COMPATIBLE /* avoid special characters etc in table names */'; put 'validvarname=V7 /* avoid special characters etc in variable names */'; put 'varinitchk=%str(ERR)OR /* avoid data mistakes from variable name typos */'; put 'varlenchk=%str(ERR)OR /* fail hard if truncation (data loss) can result */'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5" %then %do;'; put 'noautocorrect /* disallow misspelled procedure names */'; put 'dsoptions=note2err /* undocumented - convert bad NOTEs to ERRs */'; put '%end;'; put ';'; put '%mend mp_init;'; put '%macro mpeinit(fetch=YES);'; put '%global mpeinit'; put 'mpeadmins /* group with unrestricted Meditor access */'; put 'mpelocapprovals /* location for landing and staging files */'; put 'mpelib /* location of configuration tables for DC */'; put 'dc_repo_users /* location of user / group metadata */'; put 'dc_licence_key /* extracted in dc_getsettings */'; put 'dc_activation_key /* extracted in dc_getsettings */'; put 'dc_locale /* extracted in dc_getsettings */'; put 'dc_dttmtfmt /* can be overridden in dc_getsettings */'; put '_debug'; put ';'; put '%if &mpeinit=1 %then %return;'; put '%else %let mpeinit=1;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(Problem on service startup (&syswarningtext &syserrortext))'; put ')'; put '%mp_init()'; put '%if &fetch=YES %then %do;'; put '%webout(FETCH)'; put '%end;'; put '%global _CLIENTNAME;'; put '%mp_abort(iftrue= (&_CLIENTNAME=SAS Enterprise Guide)'; put ',mac=&_program..sas'; put ',msg=%str(Data Controller is a web app and should not be executed from EG)'; put ')'; put 'options urlencoding=utf8 nobomfile lrecl=32767;'; put '%let perf=%sysfunc(datetime());'; put '%put perfdiff: 0;'; put '%let dc_locale=SYSTEM; /* default if not set */'; put '/**'; put '* E8601DT26.6 has widest database support - but not all SAS flavours can'; put '* handle it. Override in the settings STP if needed.'; put '*/'; put 'data _null_;'; put 'dc_dttmtfmt=''"%sysfunc(datetime(),''!!"%mf_fmtdttm()"!!'')"dt'';'; put 'call symputx(''dc_dttmtfmt'',dc_dttmtfmt);'; put 'put dc_dttmtfmt=;'; put 'run;'; put '%put &=dc_dttmtfmt;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc prior to dc_getsettings)'; put ')'; put '%dc_getsettings()'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc after dc_getsettings)'; put ')'; put 'data _null_;'; put 'set &DC_LIBREF..mpe_config(where=('; put 'var_scope="DC"'; put 'and &dc_dttmtfmt lt tx_to'; put 'and var_active=1'; put '));'; put 'call symputx(var_name,var_value,''G'');'; put 'putlog var_name "=" var_value;'; put 'run;'; put '%let mpelib=&dc_libref;'; put '%let mpeadmins=&dc_admin_group;'; put '%let mpelocapprovals=&dc_staging_area;'; put '%let dc_repo_users=&dc_repo_users;'; put '%if &dc_locale ne SYSTEM %then %do;'; put 'options locale=&dc_locale;'; put '%end;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(Problem during compilation or with STP precode (&syswarningtext))'; put ')'; put '%mend mpeinit;'; put '%macro mf_mval(var);'; put '%if %symexist(&var) %then %do;'; put '%superq(&var)'; put '%end;'; put '%mend mf_mval;'; put '%macro mf_trimstr(basestr,trimstr);'; put '%local baselen trimlen trimval;'; put '/* return if basestr is shorter than trimstr (or 0) */'; put '%let baselen=%length(%superq(basestr));'; put '%let trimlen=%length(%superq(trimstr));'; put '%if &baselen < &trimlen or &baselen=0 %then %return;'; put '/* obtain the characters from the end of basestr */'; put '%let trimval=%qsubstr(%superq(basestr)'; put ',%length(%superq(basestr))-&trimlen+1'; put ',&trimlen);'; put '/* compare and if matching, chop it off! */'; put '%if %superq(basestr)=%superq(trimstr) %then %do;'; put '%return;'; put '%end;'; put '%else %if %superq(trimval)=%superq(trimstr) %then %do;'; put '%qsubstr(%superq(basestr),1,%length(%superq(basestr))-&trimlen)'; put '%end;'; put '%else %do;'; put '&basestr'; put '%end;'; put '%mend mf_trimstr;'; put '%macro mf_getplatform(switch'; put ')/*/STORE SOURCE*/;'; put '%local a b c;'; put '%if &switch.NONE=NONE %then %do;'; put '%if %symexist(sasjsprocessmode) %then %do;'; put '%if &sasjsprocessmode=Stored Program %then %do;'; put 'SASJS'; put '%return;'; put '%end;'; put '%end;'; put '%if %symexist(sysprocessmode) %then %do;'; put '%if "&sysprocessmode"="SAS Object Server"'; put 'or "&sysprocessmode"= "SAS Compute Server" %then %do;'; put 'SASVIYA'; put '%end;'; put '%else %if "&sysprocessmode"="SAS Stored Process Server"'; put 'or "&sysprocessmode"="SAS Workspace Server"'; put '%then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if %symexist(_metaport) or %symexist(_metauser) %then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if &switch=SASSTUDIO %then %do;'; put '/* return the version of SAS Studio else 0 */'; put '%if %mf_mval(_CLIENTAPP)=%str(SAS Studio) %then %do;'; put '%let a=%mf_mval(_CLIENTVERSION);'; put '%let b=%scan(&a,1,.);'; put '%if %eval(&b >2) %then %do;'; put '&b'; put '%end;'; put '%else 0;'; put '%end;'; put '%else 0;'; put '%end;'; put '%else %if &switch=VIYARESTAPI %then %do;'; put '%mf_trimstr(%sysfunc(getoption(servicesbaseurl)),/)'; put '%end;'; put '%mend mf_getplatform;'; put '%macro mpeterm();'; put '%local oldloc;'; put 'data _null_;'; put 'if symexist(''SYSPRINTTOLOG'') then oldloc=symget(''SYSPRINTTOLOG'');'; put 'else oldloc=getoption(''LOG'');'; put 'if subpad(oldloc,1,1) not in (''"'',"''",'' '') then oldloc=quote(cats(oldloc));'; put 'call symputx(''oldloc'',oldloc,''l'');'; put 'run;'; put '%if %length(&oldloc)>0 %then %do;'; put 'proc printto log=log;'; put 'run;'; put 'data _null_;'; put 'infile &oldloc;'; put 'input; putlog _infile_;'; put 'run;'; put '%end;'; put '%if %sysfunc(exist(&dc_libref..mpe_requests)) and %mf_getplatform() ne SASVIYA'; put '%then %do;'; put 'data ;'; put 'if 0 then set &dc_libref..mpe_requests;'; put 'request_dttm=%sysfunc(datetime());'; put 'request_user="%mf_getuser()";'; put 'request_service="%scan(&_program,-2,/)/%scan(&_program,-1,/)";'; put 'request_params='''';'; put 'output;stop;'; put 'proc append base=&dc_libref..mpe_requests data=&syslast force nowarn;'; put 'run;'; put '%end;'; put '%mend mpeterm;'; put '%macro mpe_getvars(injs,outds);'; put '/* load parameters */'; put 'data _null_;'; put '__dummychar='''';__dummynum=0;'; put 'set &outds;'; put 'array __charvals _character_;'; put 'do over __charvals;'; put 'call symputx(vname(__charvals),__charvals,''g'');'; put 'end;'; put 'array __numvals _numeric_;'; put 'do over __numvals;'; put 'call symputx(vname(__numvals),__numvals,''g'');'; put 'end;'; put 'run;'; put '%mend mpe_getvars;'; put '%macro mm_getdetails(uri'; put ',outattrs=work.attributes'; put ',outassocs=work.associations'; put ')/*/STORE SOURCE*/;'; put 'data &outassocs;'; put 'keep assoc assocuri name;'; put 'length assoc assocuri name $256;'; put 'call missing(of _all_);'; put 'rc1=1;n1=1;'; put 'do while(rc1>0);'; put '/* Walk through all possible associations of this object. */'; put 'rc1=metadata_getnasl("&uri",n1,assoc);'; put 'rc2=1;n2=1;'; put 'do while(rc2>0);'; put '/* Walk through all the associations on this machine object. */'; put 'rc2=metadata_getnasn("&uri",trim(assoc),n2,assocuri);'; put 'if (rc2>0) then do;'; put 'rc3=metadata_getattr(assocuri,"Name",name);'; put 'output;'; put 'end;'; put 'call missing(name,assocuri);'; put 'n2+1;'; put 'end;'; put 'n1+1;'; put 'end;'; put 'run;'; put 'proc sort;'; put 'by assoc name;'; put 'run;'; put 'data &outattrs;'; put 'keep type name value;'; put 'length type $4 name $256 value $32767;'; put 'rc1=1;n1=1;type=''Prop'';name='''';value='''';'; put 'do while(rc1>0);'; put 'rc1=metadata_getnprp("&uri",n1,name,value);'; put 'if rc1>0 then output;'; put 'n1+1;'; put 'end;'; put 'rc1=1;n1=1;type=''Attr'';'; put 'do while(rc1>0);'; put 'rc1=metadata_getnatr("&uri",n1,name,value);'; put 'if rc1>0 then output;'; put 'n1+1;'; put 'end;'; put 'run;'; put 'proc sort;'; put 'by type name;'; put 'run;'; put '%mend mm_getdetails;'; put '* SAS Macros end;'; put '* SAS Includes start;'; put '* SAS Includes end;'; put '* Binary Files start;'; put '* Binary Files end;'; put '* ServiceInit start;'; put 'options noquotelenmax ps=max;'; put '/* create dummy macros to prevent stpbegin / stpend from corrupting sessions */'; put '%macro stpbegin();'; put '%put NOTE: the STPBEGIN macro should not be used for web apps!;'; put '%mend stpbegin;'; put '%macro stpend();'; put '%put NOTE: the STPEND macro should not be used for web apps!;'; put '%mend stpend;'; put '* ServiceInit end;'; put '* Service start;'; put '/**'; put '@file metadetails.sas'; put '@brief Retrieves metadata attributes and associations for a particular object'; put '@details'; put '

SAS Macros

'; put '@li mm_getdetails.sas'; put '@li mpe_getvars.sas'; put '@li mpeinit.sas'; put '@version 9.3'; put '@author 4GL Apps Ltd'; put '@copyright 4GL Apps Ltd. This code may only be used within Data Controller'; put 'and may not be re-distributed or re-sold without the express permission of'; put '4GL Apps Ltd.'; put '**/'; put '%mpeinit()'; put '%mpe_getvars(SASControlTable, SASControlTable)'; put '%mm_getdetails(&objecturi'; put ',outattrs=work.attributes'; put ',outassocs=work.associations'; put ')'; put '%webout(OPEN)'; put '%webout(OBJ,attributes)'; put '%webout(OBJ,associations)'; put '%webout(CLOSE)'; put '%mpeterm()'; put '* Service end;'; run; %mm_createwebservice(path=&appLoc/&path, name=&service, code=sascode, server=&serverName, replace=yes) filename sascode clear; %let service=metaobjects; filename sascode temp lrecl=32767; data _null_; file sascode; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '/**'; put '@file mp_jsonout.sas'; put '@brief Writes JSON in SASjs format to a fileref'; put '@details This macro can be used to OPEN a JSON stream and send one or more'; put 'tables as arrays of rows, where each row can be an object or a nested array.'; put 'There are two engines available - DATASTEP or PROCJSON.'; put 'PROC JSON is fast but will produce errs like the ones below if'; put 'special chars are encountered.'; put '> (ERR)OR: Some code points did not transcode.'; put '> An object or array close is not valid at this point in the JSON text.'; put '> Date value out of range'; put 'If this happens, try running with ENGINE=DATASTEP.'; put 'The DATASTEP engine is used to handle special SAS missing numerics, and'; put 'can also convert entire datasets to formatted values. Output JSON is always'; put 'in UTF-8.'; put 'Usage:'; put 'filename tmp temp;'; put 'data class; set sashelp.class;run;'; put '%mp_jsonout(OPEN,jref=tmp)'; put '%mp_jsonout(OBJ,class,jref=tmp)'; put '%mp_jsonout(OBJ,class,dslabel=class2,jref=tmp,showmeta=Y)'; put '%mp_jsonout(CLOSE,jref=tmp)'; put 'data _null_;'; put 'infile tmp;'; put 'input;putlog _infile_;'; put 'run;'; put 'If you are building web apps with SAS then you are strongly encouraged to use'; put 'the mX_createwebservice macros in combination with the'; put '[sasjs adapter](https://github.com/sasjs/adapter).'; put 'For more information see https://sasjs.io'; put '@param [in] action Valid values:'; put '@li OPEN - opens the JSON'; put '@li OBJ - sends a table with each row as an object'; put '@li ARR - sends a table with each row in an array'; put '@li CLOSE - closes the JSON'; put '@param [in] ds The dataset to send. Must be a work table.'; put '@param [out] jref= (_webout) The fileref to which to send the JSON'; put '@param [out] dslabel= The name to give the table in the exported JSON'; put '@param [in] fmt= (Y) Whether to keep (Y) or strip (N) formats from the table'; put '@param [in] engine= (DATASTEP) Which engine to use to send the JSON. Options:'; put '@li PROCJSON (default)'; put '@li DATASTEP (more reliable when data has non standard characters)'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to JSON'; put '

Related Files

'; put '@li mp_ds2fmtds.sas'; put '@version 9.2'; put '@author Allan Bowe'; put '@source https://github.com/sasjs/core'; put '**/'; put '%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y'; put ',engine=DATASTEP'; put ',missing=NULL'; put ',showmeta=N'; put ',maxobs=MAX'; put ')/*/STORE SOURCE*/;'; put '%local tempds colinfo fmtds i numcols numobs stmt_obs lastobs optval'; put 'tmpds1 tmpds2 tmpds3 tmpds4;'; put '%let numcols=0;'; put '%if &maxobs ne MAX %then %let stmt_obs=%str(if _n_>&maxobs then stop;);'; put '%if &action=OPEN %then %do;'; put 'options nobomfile;'; put 'data _null_;file &jref encoding=''utf-8'' lrecl=200;'; put 'put ''{"PROCESSED_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''"'';'; put 'run;'; put '%end;'; put '%else %if (&action=ARR or &action=OBJ) %then %do;'; put '/* force variable names to always be uppercase in the JSON */'; put 'options validvarname=upcase;'; put '/* To avoid issues with _webout on EBI - such as encoding diffs and truncation'; put '(https://support.sas.com/kb/49/325.html) we use temporary files */'; put 'filename _sjs1 temp lrecl=200 ;'; put 'data _null_; file _sjs1 encoding=''utf-8'';'; put 'put ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":";'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjs1 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs1 clear;'; put '/* grab col defs */'; put 'proc contents noprint data=&ds'; put 'out=_data_(keep=name type length format formatl formatd varnum label);'; put 'run;'; put '%let colinfo=%scan(&syslast,2,.);'; put 'proc sort data=&colinfo;'; put 'by varnum;'; put 'run;'; put '/* move meta to mac vars */'; put 'data &colinfo;'; put 'if _n_=1 then call symputx(''numcols'',nobs,''l'');'; put 'set &colinfo end=last nobs=nobs;'; put 'name=upcase(name);'; put '/* fix formats */'; put 'if type=2 or type=6 then do;'; put 'typelong=''char'';'; put 'length fmt $49.;'; put 'if format='''' then fmt=cats(''$'',length,''.'');'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else fmt=cats(format,formatl,''.'');'; put 'end;'; put 'else do;'; put 'typelong=''num'';'; put 'if format='''' then fmt=''best.'';'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else if formatd=0 then fmt=cats(format,formatl,''.'');'; put 'else fmt=cats(format,formatl,''.'',formatd);'; put 'end;'; put '/* 32 char unique name */'; put 'newname=''sasjs''!!substr(cats(put(md5(name),$hex32.)),1,27);'; put 'call symputx(cats(''name'',_n_),name,''l'');'; put 'call symputx(cats(''newname'',_n_),newname,''l'');'; put 'call symputx(cats(''length'',_n_),length,''l'');'; put 'call symputx(cats(''fmt'',_n_),fmt,''l'');'; put 'call symputx(cats(''type'',_n_),type,''l'');'; put 'call symputx(cats(''typelong'',_n_),typelong,''l'');'; put 'call symputx(cats(''label'',_n_),coalescec(label,name),''l'');'; put '/* overwritten when fmt=Y and a custom format exists in catalog */'; put 'if typelong=''num'' then call symputx(cats(''fmtlen'',_n_),200,''l'');'; put 'else call symputx(cats(''fmtlen'',_n_),min(32767,ceil((length+10)*1.5)),''l'');'; put 'run;'; put '%let tempds=%substr(_%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql;'; put 'select count(*) into: lastobs from &ds;'; put '%if &maxobs ne MAX %then %let lastobs=%sysfunc(min(&lastobs,&maxobs));'; put '%if &engine=PROCJSON %then %do;'; put '%if &missing=STRING %then %do;'; put '%put &sysmacroname: Special Missings not supported in proc json.;'; put '%put &sysmacroname: Switching to DATASTEP engine;'; put '%goto datastep;'; put '%end;'; put 'data &tempds;'; put 'set &ds;'; put '&stmt_obs;'; put '%if &fmt=N %then format _numeric_ best32.;;'; put '/* PRETTY is necessary to avoid line truncation in large files */'; put 'filename _sjs2 temp lrecl=131068 encoding=''utf-8'';'; put 'proc json out=_sjs2 pretty'; put '%if &action=ARR %then nokeys ;'; put ';export &tempds / nosastags fmtnumeric;'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs2 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs2 clear;'; put '%end;'; put '%else %if &engine=DATASTEP %then %do;'; put '%datastep:'; put '%if %sysfunc(exist(&ds)) ne 1 & %sysfunc(exist(&ds,VIEW)) ne 1'; put '%then %do;'; put '%put &sysmacroname: &ds NOT FOUND!!!;'; put '%return;'; put '%end;'; put '%if &fmt=Y %then %do;'; put '/**'; put '* Extract format definitions'; put '* First, by getting library locations from dictionary.formats'; put '* Then, by exporting the width using proc format'; put '* Cannot use maxw from sashelp.vformat as not always populated'; put '* Cannot use fmtinfo() as not supported in all flavours'; put '*/'; put '%let tmpds1=%substr(fmtsum%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds2=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds3=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds4=%substr(col%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql noprint;'; put 'create table &tmpds1 as'; put 'select cats(libname,''.'',memname) as FMTCAT,'; put 'FMTNAME'; put 'from dictionary.formats'; put 'where fmttype=''F'' and libname is not null'; put 'and fmtname in (select format from &colinfo where format is not null)'; put 'order by 1;'; put 'create table &tmpds2('; put 'FMTNAME char(32),'; put 'LENGTH num'; put ');'; put '%local catlist cat fmtlist i;'; put 'select distinct fmtcat into: catlist separated by '' '' from &tmpds1;'; put '%do i=1 %to %sysfunc(countw(&catlist,%str( )));'; put '%let cat=%scan(&catlist,&i,%str( ));'; put 'proc sql;'; put 'select distinct fmtname into: fmtlist separated by '' '''; put 'from &tmpds1 where fmtcat="&cat";'; put 'proc format lib=&cat cntlout=&tmpds3(keep=fmtname length);'; put 'select &fmtlist;'; put 'run;'; put 'proc sql;'; put 'insert into &tmpds2 select distinct fmtname,length from &tmpds3;'; put '%end;'; put 'proc sql;'; put 'create table &tmpds4 as'; put 'select a.*, b.length as MAXW'; put 'from &colinfo a'; put 'left join &tmpds2 b'; put 'on cats(a.format)=cats(upcase(b.fmtname))'; put 'order by a.varnum;'; put 'data _null_;'; put 'set &tmpds4;'; put 'if not missing(maxw);'; put 'call symputx('; put 'cats(''fmtlen'',_n_),'; put '/* vars need extra padding due to JSON escaping of special chars */'; put 'min(32767,ceil((max(length,maxw)+10)*1.5))'; put ',''l'''; put ');'; put 'run;'; put '/* configure varlenchk - as we are explicitly shortening the variables */'; put '%let optval=%sysfunc(getoption(varlenchk));'; put 'options varlenchk=NOWARN;'; put 'data _data_(compress=char);'; put '/* shorten the new vars */'; put 'length'; put '%do i=1 %to &numcols;'; put '&&name&i $&&fmtlen&i'; put '%end;'; put ';'; put '/* rename on entry */'; put 'set &ds(rename=('; put '%do i=1 %to &numcols;'; put '&&name&i=&&newname&i'; put '%end;'; put '));'; put '&stmt_obs;'; put 'drop'; put '%do i=1 %to &numcols;'; put '&&newname&i'; put '%end;'; put ';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=num %then %do;'; put '&&name&i=cats(put(&&newname&i,&&fmt&i));'; put '%end;'; put '%else %do;'; put '&&name&i=put(&&newname&i,&&fmt&i);'; put '%end;'; put '%end;'; put 'if _error_ then do;'; put 'call symputx(''syscc'',1012);'; put 'stop;'; put 'end;'; put 'run;'; put '%let fmtds=&syslast;'; put 'options varlenchk=&optval;'; put '%end;'; put 'proc format; /* credit yabwon for special null removal */'; put 'value bart (default=40)'; put '%if &missing=NULL %then %do;'; put '._ - .z = null'; put '%end;'; put '%else %do;'; put '._ = [quote()]'; put '. = null'; put '.a - .z = [quote()]'; put '%end;'; put 'other = [best.];'; put 'data &tempds;'; put 'attrib _all_ label='''';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'length &&name&i $&&fmtlen&i...;'; put 'format &&name&i $&&fmtlen&i...;'; put '%end;'; put '%end;'; put '%if &fmt=Y %then %do;'; put 'set &fmtds;'; put '%end;'; put '%else %do;'; put 'set &ds;'; put '%end;'; put '&stmt_obs;'; put 'format _numeric_ bart.;'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'if findc(&&name&i,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put '&&name&i=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,&&name&i)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else &&name&i=quote(cats(&&name&i));'; put '%end;'; put '%end;'; put 'run;'; put 'filename _sjs3 temp lrecl=131068 ;'; put 'data _null_;'; put 'file _sjs3 encoding=''utf-8'';'; put 'if _n_=1 then put "[";'; put 'set &tempds;'; put 'if _n_>1 then put "," @; put'; put '%if &action=ARR %then "[" ; %else "{" ;'; put '%do i=1 %to &numcols;'; put '%if &i>1 %then "," ;'; put '%if &action=OBJ %then """&&name&i"":" ;'; put '"&&name&i"n /* name literal for reserved variable names */'; put '%end;'; put '%if &action=ARR %then "]" ; %else "}" ; ;'; put '/* close out the table */'; put 'data _null_;'; put 'file _sjs3 mod encoding=''utf-8'';'; put 'put '']'';'; put 'run;'; put 'data _null_;'; put 'infile _sjs3 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs3 clear;'; put '%end;'; put 'proc sql;'; put 'drop table &colinfo, &tempds;'; put '%if %substr(&showmeta,1,1)=Y %then %do;'; put 'filename _sjs4 temp lrecl=131068 encoding=''utf-8'';'; put 'data _null_;'; put 'file _sjs4;'; put 'length label $350;'; put 'put ", ""$%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":{""vars"":{";'; put 'do i=1 to &numcols;'; put 'name=quote(trim(symget(cats(''name'',i))));'; put 'format=quote(trim(symget(cats(''fmt'',i))));'; put 'label=quote(prxchange(''s/\\/\\\\/'',-1,trim(symget(cats(''label'',i)))));'; put 'length=quote(trim(symget(cats(''length'',i))));'; put 'type=quote(trim(symget(cats(''typelong'',i))));'; put 'if i>1 then put "," @@;'; put 'put name '':{"format":'' format '',"label":'' label'; put ''',"length":'' length '',"type":'' type ''}'';'; put 'end;'; put 'put ''}}'';'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs4 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs4 clear;'; put '%end;'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put 'data _null_; file &jref encoding=''utf-8'' mod ;'; put 'put "}";'; put 'run;'; put '%end;'; put '%mend mp_jsonout;'; put '/**'; put '@file mm_webout.sas'; put '@brief Send data to/from SAS Stored Processes'; put '@details This macro should be added to the start of each Stored Process,'; put '**immediately** followed by a call to:'; put '%mm_webout(FETCH)'; put 'This will read all the input data and create same-named SAS datasets in the'; put 'WORK library. You can then insert your code, and send data back using the'; put 'following syntax:'; put 'data some datasets; * make some data ;'; put 'retain some columns;'; put 'run;'; put '%mm_webout(OPEN)'; put '%mm_webout(ARR,some) * Array format, fast, suitable for large tables ;'; put '%mm_webout(OBJ,datasets) * Object format, easier to work with ;'; put 'Finally, wrap everything up send some helpful system variables too'; put '%mm_webout(CLOSE)'; put '@param [in] action Either FETCH, OPEN, ARR, OBJ or CLOSE'; put '@param [in] ds The dataset to send back to the frontend'; put '@param [out] dslabel= Value to use instead of table name for sending to JSON'; put '@param [in] fmt= (N) Setting Y converts all vars to their formatted values'; put '@param [out] fref= (_webout) The fileref to which to write the JSON'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] workobs= (0) When set to a positive integer, will create a new'; put 'output object (WORK) which contains this number of observations from all'; put 'tables in the WORK library.'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to output JSON'; put '

SAS Macros

'; put '@li mp_jsonout.sas'; put '

Related Macros

'; put '@li ms_webout.sas'; put '@li mv_webout.sas'; put '@version 9.3'; put '@author Allan Bowe'; put '**/'; put '%macro mm_webout(action,ds,dslabel=,fref=_webout,fmt=N,missing=NULL'; put ',showmeta=N,maxobs=MAX,workobs=0'; put ');'; put '%global _webin_file_count _webin_fileref1 _webin_name1 _program _debug'; put 'sasjs_tables;'; put '%local i tempds jsonengine;'; put '/* see https://github.com/sasjs/core/issues/41 */'; put '%if "%upcase(&SYSENCODING)" ne "UTF-8" %then %let jsonengine=PROCJSON;'; put '%else %let jsonengine=DATASTEP;'; put '%if &action=FETCH %then %do;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'options mprint notes mprintnest;'; put '%end;'; put '%let _webin_file_count=%eval(&_webin_file_count+0);'; put '/* now read in the data */'; put '%do i=1 %to &_webin_file_count;'; put '%if &_webin_file_count=1 %then %do;'; put '%let _webin_fileref1=&_webin_fileref;'; put '%let _webin_name1=&_webin_name;'; put '%end;'; put 'data _null_;'; put 'infile &&_webin_fileref&i termstr=crlf;'; put 'input;'; put 'call symputx(''input_statement'',_infile_);'; put 'putlog "&&_webin_name&i input statement: " _infile_;'; put 'stop;'; put 'data &&_webin_name&i;'; put 'infile &&_webin_fileref&i firstobs=2 dsd termstr=crlf encoding=''utf-8'';'; put 'input &input_statement;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'if _n_<20 then putlog _infile_;'; put '%end;'; put 'run;'; put '%let sasjs_tables=&sasjs_tables &&_webin_name&i;'; put '%end;'; put '%end;'; put '%else %if &action=OPEN %then %do;'; put '/* fix encoding */'; put 'OPTIONS NOBOMFILE;'; put '/**'; put '* check xengine type to avoid the below err message:'; put '* > Function is only valid for filerefs using the CACHE access method.'; put '*/'; put 'data _null_;'; put 'set sashelp.vextfl(where=(fileref="_WEBOUT"));'; put 'if xengine=''STREAM'' then do;'; put 'rc=stpsrv_header(''Content-type'',"text/html; encoding=utf-8");'; put 'end;'; put 'run;'; put '/* setup json */'; put 'data _null_;file &fref encoding=''utf-8'';'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutBEGIN<<'';'; put '%end;'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'run;'; put '%end;'; put '%else %if &action=ARR or &action=OBJ %then %do;'; put '%mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt,jref=&fref'; put ',engine=&jsonengine,missing=&missing,showmeta=&showmeta,maxobs=&maxobs'; put ')'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put '/* To avoid issues with _webout on EBI we use a temporary file */'; put 'filename _sjsref temp lrecl=131068;'; put '%if %str(&workobs) > 0 %then %do;'; put '/* if debug mode, send back first XX records of each work table also */'; put 'data;run;%let tempds=%scan(&syslast,2,.);'; put 'ods output Members=&tempds;'; put 'proc datasets library=WORK memtype=data;'; put '%local wtcnt;%let wtcnt=0;'; put 'data _null_;'; put 'set &tempds;'; put 'if not (upcase(name) =:"DATA"); /* ignore temp datasets */'; put 'i+1;'; put 'call symputx(cats(''wt'',i),name,''l'');'; put 'call symputx(''wtcnt'',i,''l'');'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put ",""WORK"":{";'; put '%do i=1 %to &wtcnt;'; put '%let wt=&&wt&i;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'dsid=open("WORK.&wt",''is'');'; put 'nlobs=attrn(dsid,''NLOBS'');'; put 'nvars=attrn(dsid,''NVARS'');'; put 'rc=close(dsid);'; put 'if &i>1 then put '',''@;'; put 'put " ""&wt"" : {";'; put 'put ''"nlobs":'' nlobs;'; put 'put '',"nvars":'' nvars;'; put '%mp_jsonout(OBJ,&wt,jref=_sjsref,dslabel=first10rows,showmeta=Y'; put ',maxobs=&workobs'; put ')'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put '%end;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put 'run;'; put '%end;'; put '/* close off json */'; put 'data _null_;file _sjsref mod encoding=''utf-8'';'; put 'length SYSPROCESSNAME syserrortext syswarningtext autoexec $512;'; put 'put ",""_DEBUG"" : ""&_debug"" ";'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'autoexec=quote(urlencode(trim(getoption(''autoexec''))));'; put 'put '',"AUTOEXEC" : '' autoexec;'; put 'put ",""MF_GETUSER"" : ""%mf_getuser()"" ";'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'put ",""SYSENCODING"" : ""&sysencoding"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSPROCESSID"" : ""&SYSPROCESSID"" ";'; put 'put ",""SYSPROCESSMODE"" : ""&SYSPROCESSMODE"" ";'; put 'SYSPROCESSNAME=quote(urlencode(cats(SYSPROCESSNAME)));'; put 'put ",""SYSPROCESSNAME"" : " SYSPROCESSNAME;'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put '',"SYSWARNINGTEXT" : '' syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'length memsize $32;'; put 'memsize="%sysfunc(INPUTN(%sysfunc(getoption(memsize)), best.),sizekmg.)";'; put 'memsize=quote(cats(memsize));'; put 'put '',"MEMSIZE" : '' memsize;'; put 'put "}" @;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutEND<<'';'; put '%end;'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjsref lrecl=1 recfm=n;'; put 'file &fref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjsref clear;'; put '%end;'; put '%mend mm_webout;'; put '%macro webout(action,ds,dslabel=,fmt=,missing=NULL,showmeta=NO,maxobs=MAX);'; put '%mm_webout(&action,ds=&ds,dslabel=&dslabel,fmt=&fmt'; put ',missing=&missing'; put ',showmeta=&showmeta'; put ',maxobs=&maxobs'; put ') %mend;'; put '/* provide additional debug info */'; put '%global _program;'; put '%put &=syscc;'; put '%put user=%mf_getuser();'; put '%put pgm=&_program;'; put '%put timestamp=%sysfunc(datetime(),datetime19.);'; put '* Service Variables start;'; put '* Service Variables end;'; put '* SAS Macros start;'; put '%macro mf_getapploc(pgm);'; put '%if "&pgm"="" %then %do;'; put '%if %symexist(_program) %then %let pgm=&_program;'; put '%else %do;'; put '%put &sysmacroname: No value provided and no _program variable available;'; put '%return;'; put '%end;'; put '%end;'; put '%local root;'; put '/**'; put '* First check we are not in the tests/macros folder (which has no subfolders)'; put '* or specifically in the testsetup or testteardown services'; put '*/'; put '%if %index(&pgm,/tests/macros/)'; put 'or %index(&pgm,/tests/testsetup)'; put 'or %index(&pgm,/tests/testteardown)'; put '%then %do;'; put '%let root=%substr(&pgm,1,%index(&pgm,/tests)-1);'; put '&root'; put '%return;'; put '%end;'; put '/**'; put '* Next, move up two levels to avoid matches on subfolder or service name'; put '*/'; put '%let root=%substr(&pgm,1,%length(&pgm)-%length(%scan(&pgm,-1,/))-1);'; put '%let root=%substr(&root,1,%length(&root)-%length(%scan(&root,-1,/))-1);'; put '%if %index(&root,/tests/) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/tests/)-1);'; put '%end;'; put '%else %if %index(&root,/services) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/services)-1);'; put '%end;'; put '%else %if %index(&root,/jobs) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/jobs)-1);'; put '%end;'; put '%else %put &sysmacroname: Could not find an app location from &pgm;'; put '&root'; put '%mend mf_getapploc ;'; put '%macro mf_getuniquefileref(prefix=_,maxtries=1000,lrecl=32767);'; put '%local rc fname;'; put '%if &prefix=0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%end;'; put '%else %do;'; put '%local x len;'; put '%let len=%eval(8-%length(&prefix));'; put '%let x=0;'; put '%do x=0 %to &maxtries;'; put '%let fname=&prefix%substr(%sysfunc(ranuni(0)),3,&len);'; put '%if %sysfunc(fileref(&fname)) > 0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%return;'; put '%end;'; put '%end;'; put '%put unable to find available fileref after &maxtries attempts;'; put '%end;'; put '%mend mf_getuniquefileref;'; put '%macro mp_abort(mac=mp_abort.sas, type=, msg=, iftrue=%str(1=1)'; put ', errds=work.mp_abort_errds'; put ', mode=REGULAR'; put ')/*/STORE SOURCE*/;'; put '%global sysprocessmode sysprocessname sasjs_stpsrv_header_loc sasjsprocessmode;'; put '%local fref fid i;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%put NOTE: /// mp_abort macro executing //;'; put '%if %length(&mac)>0 %then %put NOTE- called by &mac;'; put '%put NOTE - &msg;'; put '%if %symexist(_SYSINCLUDEFILEDEVICE)'; put '/* abort cancel FILE does not restart outside the INCLUDE on Viya 3.5 */'; put 'and %superq(SYSPROCESSNAME) ne %str(Compute Server)'; put '%then %do;'; put '%if "*&_SYSINCLUDEFILEDEVICE*" ne "**" %then %do;'; put 'data &errds;'; put 'iftrue=''1=1'';'; put 'length mac $100 msg $5000;'; put 'mac=symget(''mac'');'; put 'msg=symget(''msg'');'; put 'run;'; put 'data _null_;'; put 'abort cancel FILE;'; put 'run;'; put '%return;'; put '%end;'; put '%end;'; put '/* Web App Context */'; put '%if %symexist(_PROGRAM)'; put 'or %superq(SYSPROCESSNAME) = %str(Compute Server)'; put 'or &mode=INCLUDE'; put '%then %do;'; put 'options obs=max replace mprint;'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5"'; put '%then %do;'; put 'options nosyntaxcheck;'; put '%end;'; put '%if &mode=INCLUDE %then %do;'; put '%if %sysfunc(exist(&errds))=1 %then %do;'; put 'data _null_;'; put 'set &errds;'; put 'call symputx(''iftrue'',iftrue,''l'');'; put 'call symputx(''mac'',mac,''l'');'; put 'call symputx(''msg'',msg,''l'');'; put 'putlog (_all_)(=);'; put 'run;'; put '%if (&iftrue)=0 %then %return;'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: No include errors found;'; put '%return;'; put '%end;'; put '%end;'; put '/* extract log errs / warns, if exist */'; put '%local logloc logline;'; put '%global logmsg; /* capture global messages */'; put '%if %symexist(SYSPRINTTOLOG) %then %let logloc=&SYSPRINTTOLOG;'; put '%else %let logloc=%qsysfunc(getoption(LOG));'; put 'proc printto log=log;run;'; put '%let logline=0;'; put '%if %length(&logloc)>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input; putlog _infile_;'; put 'i=1;'; put 'retain logonce 0;'; put 'if ('; put '_infile_=:"%str(WARN)ING" or _infile_=:"%str(ERR)OR"'; put ') and logonce=0 then'; put 'do;'; put 'call symputx(''logline'',_n_);'; put 'logonce+1;'; put 'end;'; put 'run;'; put '/* capture log including lines BEFORE the err */'; put '%if &logline>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input;'; put 'i=1;'; put 'stoploop=0;'; put 'if _n_ ge &logline-15 and stoploop=0 then do until (i>22);'; put 'call symputx(''logmsg'',catx(''\n'',symget(''logmsg''),_infile_));'; put 'input;'; put 'i+1;'; put 'stoploop=1;'; put 'end;'; put 'if stoploop=1 then stop;'; put 'run;'; put '%end;'; put '%end;'; put '%if %symexist(SYS_JES_JOB_URI) %then %do;'; put '/* setup webout for Viya */'; put 'options nobomfile;'; put '%if "X&SYS_JES_JOB_URI.X"="XX" %then %do;'; put 'filename _webout temp lrecl=999999 mod;'; put '%end;'; put '%else %do;'; put 'filename _webout filesrvc parenturi="&SYS_JES_JOB_URI"'; put 'name="_webout.json" lrecl=999999 mod;'; put '%end;'; put '%end;'; put '%else %if %sysfunc(filename(fref,&sasjs_stpsrv_header_loc))=0 %then %do;'; put 'options nobomfile;'; put '/* set up http header for SASjs Server */'; put '%let fid=%sysfunc(fopen(&fref,A));'; put '%if &fid=0 %then %do;'; put '%put %str(ERR)OR: %sysfunc(sysmsg());'; put '%return;'; put '%end;'; put '%let rc=%sysfunc(fput(&fid,%str(Content-Type: application/json)));'; put '%let rc=%sysfunc(fwrite(&fid));'; put '%let rc=%sysfunc(fclose(&fid));'; put '%let rc=%sysfunc(filename(&fref));'; put '%end;'; put '/* send response in SASjs JSON format */'; put 'data _null_;'; put 'file _webout mod lrecl=32000 encoding=''utf-8'';'; put 'length msg syswarningtext syserrortext $32767 mode $10 ;'; put 'sasdatetime=datetime();'; put 'msg=symget(''msg'');'; put '%if &logline>0 %then %do;'; put 'msg=cats(msg,''\n\nLog Extract:\n'',symget(''logmsg''));'; put '%end;'; put '/* escape the escapes */'; put 'msg=tranwrd(msg,''\'',''\\'');'; put '/* escape the quotes */'; put 'msg=tranwrd(msg,''"'',''\"'');'; put '/* ditch the CRLFs as chrome complains */'; put 'msg=compress(msg,,''kw'');'; put '/* quote without quoting the quotes (which are escaped instead) */'; put 'msg=cats(''"'',msg,''"'');'; put 'if symexist(''_debug'') then debug=quote(trim(symget(''_debug'')));'; put 'else debug=''""'';'; put 'if symget(''sasjsprocessmode'')=''Stored Program'' then mode=''SASJS'';'; put 'if mode ne ''SASJS'' then put ''>>weboutBEGIN<<'';'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'put '',"sasjsAbort" : [{'';'; put 'put '' "MSG":'' msg ;'; put 'put '' ,"MAC": "'' "&mac" ''"}]'';'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'put '',"_DEBUG":'' debug ;'; put 'if symexist(''_metauser'') then do;'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put 'end;'; put 'if symexist(''SYS_JES_JOB_URI'') then do;'; put 'SYS_JES_JOB_URI=quote(trim(symget(''SYS_JES_JOB_URI'')));'; put 'put '',"SYS_JES_JOB_URI": '' SYS_JES_JOB_URI;'; put 'end;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put ",""SYSWARNINGTEXT"" : " syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'put "}" ;'; put 'if mode ne ''SASJS'' then put ''>>weboutEND<<'';'; put 'run;'; put '%put _all_;'; put '%if "&sysprocessmode " = "SAS Stored Process Server " %then %do;'; put 'data _null_;'; put 'putlog ''stpsrvset program err and syscc'';'; put 'rc=stpsrvset(''program error'', 0);'; put 'call symputx("syscc",0,"g");'; put 'run;'; put '%if &sysscp=WIN'; put 'and 1=0 /* deprecating this logic until we figure out a consistent abort */'; put 'and "%substr(%str(&sysvlong ),1,8)"="9.04.01M"'; put 'and "%substr(%str(&sysvlong ),9,1)">"5" %then %do;'; put '/* skip approach (below) does not work in windows m6+ envs */'; put 'endsas;'; put '%end;'; put '%else %do;'; put '/**'; put '* endsas kills 9.4m3 deployments by orphaning multibridges.'; put '* Abort variants are ungraceful (non zero return code)'; put '* This approach lets SAS run silently until the end :-)'; put '* Caution - fails when called within a %include within a macro'; put '* Use mp_include() to handle this.'; put '*/'; put 'filename skip temp;'; put 'data _null_;'; put 'file skip;'; put 'put ''%macro skip();'';'; put 'comment ''%mend skip; -> fix lint '';'; put 'put ''%macro skippy();'';'; put 'comment ''%mend skippy; -> fix lint '';'; put 'run;'; put '%inc skip;'; put '%end;'; put '%end;'; put '%else %if "&sysprocessmode " = "SAS Compute Server " %then %do;'; put '/* endsas kills the session making it harder to fetch results */'; put 'data _null_;'; put 'syswarningtext=symget(''syswarningtext'');'; put 'syserrortext=symget(''syserrortext'');'; put 'abort_msg=symget(''msg'');'; put 'syscc=symget(''syscc'');'; put 'sysuserid=symget(''sysuserid'');'; put 'iftrue=symget(''iftrue'');'; put 'put (_all_)(/=);'; put 'call symputx(''syscc'',0);'; put 'abort cancel nolist;'; put 'run;'; put '%end;'; put '%else %do;'; put '%abort cancel;'; put '%end;'; put '%end;'; put '%else %do;'; put '%put _all_;'; put '%abort cancel;'; put '%end;'; put '%mend mp_abort;'; put '/** @endcond */'; put '%macro mm_getstpcode('; put 'tree=/User Folders/sasdemo/somestp'; put ',name='; put ',outloc=0'; put ',outref=0'; put ',mDebug=1'; put ',showlog=NO'; put ');'; put '%local mD;'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing &sysmacroname..sas;'; put '%&mD.put _local_;'; put '%if %length(&name)>0 %then %let name=/&name;'; put '/* first, check if STP exists */'; put '%local tsuri;'; put '%let tsuri=stopifempty ;'; put 'data _null_;'; put 'format type uri tsuri value $200.;'; put 'call missing (of _all_);'; put 'path="&tree&name(StoredProcess)";'; put '/* first, find the STP ID */'; put 'if metadata_pathobj("",path,"StoredProcess",type,uri)>0 then do;'; put '/* get sourcecode */'; put 'cnt=1;'; put 'do while (metadata_getnasn(uri,"Notes",cnt,tsuri)>0);'; put 'rc=metadata_getattr(tsuri,"Name",value);'; put '&mD.put tsuri= value=;'; put 'if value="SourceCode" then do;'; put '/* found it! */'; put 'rc=metadata_getattr(tsuri,"Id",value);'; put 'call symputx(''tsuri'',value,''l'');'; put 'stop;'; put 'end;'; put 'cnt+1;'; put 'end;'; put 'end;'; put 'else put (_all_)(=);'; put 'run;'; put '%mp_abort(iftrue= (&tsuri=stopifempty)'; put ',mac=mm_getstpcode'; put ',msg=%str(&tree&name.(StoredProcess) not found!)'; put ')'; put '/**'; put '* Now we can extract the textstore'; put '*/'; put 'filename __getdoc temp lrecl=10000000;'; put 'proc metadata'; put 'in="$METAREPOSITORY'; put ''; put 'SAS1"'; put 'out=__getdoc ;'; put 'run;'; put '/* find the beginning of the text */'; put '%local start;'; put 'data _null_;'; put 'infile __getdoc lrecl=10000;'; put 'input;'; put 'start=index(_infile_,''StoredText="'');'; put 'if start then do;'; put 'call symputx("start",start+11);'; put '*putlog ''"'' _infile_ ''"'';'; put 'end;'; put 'stop;'; put '%local outeng;'; put '%if "&outloc"="0" %then %let outeng=TEMP;'; put '%else %let outeng="&outloc";'; put '%local fref;'; put '%if &outref=0 %then %let fref=%mf_getuniquefileref();'; put '%else %let fref=&outref;'; put '/* read the content, byte by byte, resolving escaped chars */'; put 'filename &fref &outeng lrecl=100000;'; put 'data _null_;'; put 'length filein 8 fileid 8;'; put 'filein = fopen("__getdoc","I",1,"B");'; put 'fileid = fopen("&fref","O",1,"B");'; put 'rec = "20"x;'; put 'length entity $6;'; put 'do while(fread(filein)=0);'; put 'x+1;'; put 'if x>&start then do;'; put 'rc = fget(filein,rec,1);'; put 'if rec=''"'' then leave;'; put 'else if rec="&" then do;'; put 'entity=rec;'; put 'do until (rec=";");'; put 'if fread(filein) ne 0 then goto getout;'; put 'rc = fget(filein,rec,1);'; put 'entity=cats(entity,rec);'; put 'end;'; put 'select (entity);'; put 'when (''&'' ) rec=''&'' ;'; put 'when (''<'' ) rec=''<'' ;'; put 'when (''>'' ) rec=''>'' ;'; put 'when (''''') rec="''" ;'; put 'when (''"'') rec=''"'' ;'; put 'when ('' '') rec=''0A''x;'; put 'when ('' '') rec=''0D''x;'; put 'when (''$'' ) rec=''$'' ;'; put 'when ('' '') rec=''09''x;'; put 'otherwise putlog "%str(WARN)ING: missing value for " entity=;'; put 'end;'; put 'rc =fput(fileid, substr(rec,1,1));'; put 'rc =fwrite(fileid);'; put 'end;'; put 'else do;'; put 'rc =fput(fileid,rec);'; put 'rc =fwrite(fileid);'; put 'end;'; put 'end;'; put 'end;'; put 'getout:'; put 'rc=fclose(filein);'; put 'rc=fclose(fileid);'; put 'run;'; put '%if &showlog=YES %then %do;'; put 'data _null_;'; put 'infile &fref lrecl=32767 end=last;'; put 'input;'; put 'if _n_=1 then putlog ''>>stpcodeBEGIN<<'';'; put 'putlog _infile_;'; put 'if last then putlog ''>>stpcodeEND<<'';'; put 'run;'; put '%end;'; put 'filename __getdoc clear;'; put '%if &outref=0 %then %do;'; put 'filename &fref clear;'; put '%end;'; put '%mend mm_getstpcode;'; put '%macro dc_getsettings();'; put '%global _program;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(syscc=&syscc on entry (&syswarningtext &syserrortext))'; put ')'; put '%if %length(&_PROGRAM)>1 %then %let root=&_program;'; put '%else %do;'; put '%global _metauser;'; put '%let _metauser=&sysuserid;'; put '/* to mimic a "real" _program we need to give a dummy role and stp name */'; put '%let root=/dummyRole/dummyName;'; put '%end;'; put '/* the DC precode is stored in the Admin folder in the root of'; put 'the project. Lets find that root. */'; put '%put &=root;'; put '%let root=%mf_getapploc();'; put '%put &=root;'; put '/* Now we know the root location we can retrieve the params */'; put '%let temploc=%sysfunc(pathname(work))/settings.txt;'; put '%mm_getstpcode(tree=&root/services/public'; put ',name=Data_Controller_Settings'; put ',outloc=&temploc'; put ')'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Unable to run getstpcode)'; put ')'; put 'filename _getsets "&temploc" lrecl=2000;'; put '/*'; put 'Do not use mp_include here - this puts a copy in every service, which creates'; put 'compilation problems when calling services from mp_include'; put '*/'; put '%inc _getsets/source2;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Problem running &sysmacroname (&syswarningtext &syserrortext))'; put ')'; put '%mend dc_getsettings;'; put '%macro mf_fmtdttm('; put ')/*/STORE SOURCE*/;'; put '%if "&sysver"="9.2" or "&sysver"="9.3"'; put 'or ("&sysver"="9.4" and "%substr(&SYSVLONG,9,1)" le "3")'; put 'or "%substr(&sysver,1,1)"="4"'; put 'or "%substr(&sysver,1,1)"="5"'; put '%then %do;DATETIME19.3%end;'; put '%else %do;E8601DT26.6%end;'; put '%mend mf_fmtdttm;'; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '%macro mp_init(prefix=SASJS'; put ')/*/STORE SOURCE*/;'; put '%if %symexist(SASJS_PREFIX) %then %return; /* only run once */'; put '%global'; put 'SASJS_PREFIX /* the ONLY hard-coded global macro variable in SASjs */'; put '&prefix._FUNCTIONS /* used in mcf_init() to track core function compilation */'; put '&prefix._INIT_NUM /* initialisation time as numeric */'; put '&prefix._INIT_DTTM /* initialisation time in E8601DT26.6 format */'; put '&prefix.WORK /* avoid typing %sysfunc(pathname(work)) every time */'; put ';'; put '%let sasjs_prefix=&prefix;'; put 'data _null_;'; put 'dttm=datetime();'; put 'call symputx("&sasjs_prefix._init_num",dttm,''g'');'; put 'call symputx("&sasjs_prefix._init_dttm",put(dttm,E8601DT26.6),''g'');'; put 'call symputx("&sasjs_prefix.work",pathname(''WORK''),''g'');'; put 'run;'; put 'options'; put 'compress=CHAR /* default is none so ensure we have something! */'; put 'datastmtchk=ALLKEYWORDS /* protection from overwriting input datasets */'; put 'errorcheck=STRICT /* catch errs in libname/filename statements */'; put 'fmterr /* ensure err when a format cannot be found */'; put 'mergenoby=%str(ERR)OR /* throw err when a merge has no BY variables */'; put 'missing=. /* changing this can cause hard to detect errs */'; put 'noquotelenmax /* avoid warnings for long strings */'; put 'noreplace /* avoid overwriting permanent datasets */'; put 'ps=max /* reduce log size slightly */'; put 'ls=max /* reduce log even more and avoid word truncation */'; put 'validmemname=COMPATIBLE /* avoid special characters etc in table names */'; put 'validvarname=V7 /* avoid special characters etc in variable names */'; put 'varinitchk=%str(ERR)OR /* avoid data mistakes from variable name typos */'; put 'varlenchk=%str(ERR)OR /* fail hard if truncation (data loss) can result */'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5" %then %do;'; put 'noautocorrect /* disallow misspelled procedure names */'; put 'dsoptions=note2err /* undocumented - convert bad NOTEs to ERRs */'; put '%end;'; put ';'; put '%mend mp_init;'; put '%macro mpeinit(fetch=YES);'; put '%global mpeinit'; put 'mpeadmins /* group with unrestricted Meditor access */'; put 'mpelocapprovals /* location for landing and staging files */'; put 'mpelib /* location of configuration tables for DC */'; put 'dc_repo_users /* location of user / group metadata */'; put 'dc_licence_key /* extracted in dc_getsettings */'; put 'dc_activation_key /* extracted in dc_getsettings */'; put 'dc_locale /* extracted in dc_getsettings */'; put 'dc_dttmtfmt /* can be overridden in dc_getsettings */'; put '_debug'; put ';'; put '%if &mpeinit=1 %then %return;'; put '%else %let mpeinit=1;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(Problem on service startup (&syswarningtext &syserrortext))'; put ')'; put '%mp_init()'; put '%if &fetch=YES %then %do;'; put '%webout(FETCH)'; put '%end;'; put '%global _CLIENTNAME;'; put '%mp_abort(iftrue= (&_CLIENTNAME=SAS Enterprise Guide)'; put ',mac=&_program..sas'; put ',msg=%str(Data Controller is a web app and should not be executed from EG)'; put ')'; put 'options urlencoding=utf8 nobomfile lrecl=32767;'; put '%let perf=%sysfunc(datetime());'; put '%put perfdiff: 0;'; put '%let dc_locale=SYSTEM; /* default if not set */'; put '/**'; put '* E8601DT26.6 has widest database support - but not all SAS flavours can'; put '* handle it. Override in the settings STP if needed.'; put '*/'; put 'data _null_;'; put 'dc_dttmtfmt=''"%sysfunc(datetime(),''!!"%mf_fmtdttm()"!!'')"dt'';'; put 'call symputx(''dc_dttmtfmt'',dc_dttmtfmt);'; put 'put dc_dttmtfmt=;'; put 'run;'; put '%put &=dc_dttmtfmt;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc prior to dc_getsettings)'; put ')'; put '%dc_getsettings()'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc after dc_getsettings)'; put ')'; put 'data _null_;'; put 'set &DC_LIBREF..mpe_config(where=('; put 'var_scope="DC"'; put 'and &dc_dttmtfmt lt tx_to'; put 'and var_active=1'; put '));'; put 'call symputx(var_name,var_value,''G'');'; put 'putlog var_name "=" var_value;'; put 'run;'; put '%let mpelib=&dc_libref;'; put '%let mpeadmins=&dc_admin_group;'; put '%let mpelocapprovals=&dc_staging_area;'; put '%let dc_repo_users=&dc_repo_users;'; put '%if &dc_locale ne SYSTEM %then %do;'; put 'options locale=&dc_locale;'; put '%end;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(Problem during compilation or with STP precode (&syswarningtext))'; put ')'; put '%mend mpeinit;'; put '%macro mf_mval(var);'; put '%if %symexist(&var) %then %do;'; put '%superq(&var)'; put '%end;'; put '%mend mf_mval;'; put '%macro mf_trimstr(basestr,trimstr);'; put '%local baselen trimlen trimval;'; put '/* return if basestr is shorter than trimstr (or 0) */'; put '%let baselen=%length(%superq(basestr));'; put '%let trimlen=%length(%superq(trimstr));'; put '%if &baselen < &trimlen or &baselen=0 %then %return;'; put '/* obtain the characters from the end of basestr */'; put '%let trimval=%qsubstr(%superq(basestr)'; put ',%length(%superq(basestr))-&trimlen+1'; put ',&trimlen);'; put '/* compare and if matching, chop it off! */'; put '%if %superq(basestr)=%superq(trimstr) %then %do;'; put '%return;'; put '%end;'; put '%else %if %superq(trimval)=%superq(trimstr) %then %do;'; put '%qsubstr(%superq(basestr),1,%length(%superq(basestr))-&trimlen)'; put '%end;'; put '%else %do;'; put '&basestr'; put '%end;'; put '%mend mf_trimstr;'; put '%macro mf_getplatform(switch'; put ')/*/STORE SOURCE*/;'; put '%local a b c;'; put '%if &switch.NONE=NONE %then %do;'; put '%if %symexist(sasjsprocessmode) %then %do;'; put '%if &sasjsprocessmode=Stored Program %then %do;'; put 'SASJS'; put '%return;'; put '%end;'; put '%end;'; put '%if %symexist(sysprocessmode) %then %do;'; put '%if "&sysprocessmode"="SAS Object Server"'; put 'or "&sysprocessmode"= "SAS Compute Server" %then %do;'; put 'SASVIYA'; put '%end;'; put '%else %if "&sysprocessmode"="SAS Stored Process Server"'; put 'or "&sysprocessmode"="SAS Workspace Server"'; put '%then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if %symexist(_metaport) or %symexist(_metauser) %then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if &switch=SASSTUDIO %then %do;'; put '/* return the version of SAS Studio else 0 */'; put '%if %mf_mval(_CLIENTAPP)=%str(SAS Studio) %then %do;'; put '%let a=%mf_mval(_CLIENTVERSION);'; put '%let b=%scan(&a,1,.);'; put '%if %eval(&b >2) %then %do;'; put '&b'; put '%end;'; put '%else 0;'; put '%end;'; put '%else 0;'; put '%end;'; put '%else %if &switch=VIYARESTAPI %then %do;'; put '%mf_trimstr(%sysfunc(getoption(servicesbaseurl)),/)'; put '%end;'; put '%mend mf_getplatform;'; put '%macro mpeterm();'; put '%local oldloc;'; put 'data _null_;'; put 'if symexist(''SYSPRINTTOLOG'') then oldloc=symget(''SYSPRINTTOLOG'');'; put 'else oldloc=getoption(''LOG'');'; put 'if subpad(oldloc,1,1) not in (''"'',"''",'' '') then oldloc=quote(cats(oldloc));'; put 'call symputx(''oldloc'',oldloc,''l'');'; put 'run;'; put '%if %length(&oldloc)>0 %then %do;'; put 'proc printto log=log;'; put 'run;'; put 'data _null_;'; put 'infile &oldloc;'; put 'input; putlog _infile_;'; put 'run;'; put '%end;'; put '%if %sysfunc(exist(&dc_libref..mpe_requests)) and %mf_getplatform() ne SASVIYA'; put '%then %do;'; put 'data ;'; put 'if 0 then set &dc_libref..mpe_requests;'; put 'request_dttm=%sysfunc(datetime());'; put 'request_user="%mf_getuser()";'; put 'request_service="%scan(&_program,-2,/)/%scan(&_program,-1,/)";'; put 'request_params='''';'; put 'output;stop;'; put 'proc append base=&dc_libref..mpe_requests data=&syslast force nowarn;'; put 'run;'; put '%end;'; put '%mend mpeterm;'; put '%macro mpe_getvars(injs,outds);'; put '/* load parameters */'; put 'data _null_;'; put '__dummychar='''';__dummynum=0;'; put 'set &outds;'; put 'array __charvals _character_;'; put 'do over __charvals;'; put 'call symputx(vname(__charvals),__charvals,''g'');'; put 'end;'; put 'array __numvals _numeric_;'; put 'do over __numvals;'; put 'call symputx(vname(__numvals),__numvals,''g'');'; put 'end;'; put 'run;'; put '%mend mpe_getvars;'; put '%macro mm_getobjects('; put 'type=SASLibrary'; put ',outds=work.mm_getobjects'; put ')/*/STORE SOURCE*/;'; put '* use a temporary fileref to hold the response;'; put 'filename response temp;'; put '/* get list of libraries */'; put 'proc metadata in='; put '"$METAREPOSITORY'; put '&typeSAS'; put '0"'; put 'out=response;'; put 'run;'; put '/* write the response to the log for debugging */'; put 'data _null_;'; put 'infile response lrecl=1048576;'; put 'input;'; put 'put _infile_;'; put 'run;'; put '/* create an XML map to read the response */'; put 'filename sxlemap temp;'; put 'data _null_;'; put 'file sxlemap;'; put 'put '''';'; put 'put "/GetMetadataObjects/Objects/&type";'; put 'put "";'; put 'put '''';'; put 'put "/GetMetadataObjects/Objects/&type/@Id";'; put 'put "characterstring200";'; put 'put '''';'; put 'put "/GetMetadataObjects/Objects/&type/@Name";'; put 'put "characterstring200";'; put 'put ''
'';'; put 'run;'; put 'libname _XML_ xml xmlfileref=response xmlmap=sxlemap;'; put 'proc sort data= _XML_.SASObjects out=&outds;'; put 'by name;'; put 'run;'; put '/* clear references */'; put 'filename sxlemap clear;'; put 'filename response clear;'; put 'libname _XML_ clear;'; put '%mend mm_getobjects;'; put '* SAS Macros end;'; put '* SAS Includes start;'; put '* SAS Includes end;'; put '* Binary Files start;'; put '* Binary Files end;'; put '* ServiceInit start;'; put 'options noquotelenmax ps=max;'; put '/* create dummy macros to prevent stpbegin / stpend from corrupting sessions */'; put '%macro stpbegin();'; put '%put NOTE: the STPBEGIN macro should not be used for web apps!;'; put '%mend stpbegin;'; put '%macro stpend();'; put '%put NOTE: the STPEND macro should not be used for web apps!;'; put '%mend stpend;'; put '* ServiceInit end;'; put '* Service start;'; put '/**'; put '@file metaobjects.sas'; put '@brief Retrieves list of objects for a particular metadata type'; put '@details'; put '

SAS Macros

'; put '@li mm_getobjects.sas'; put '@li mpe_getvars.sas'; put '@li mpeinit.sas'; put '@version 9.3'; put '@author 4GL Apps Ltd'; put '@copyright 4GL Apps Ltd. This code may only be used within Data Controller'; put 'and may not be re-distributed or re-sold without the express permission of'; put '4GL Apps Ltd.'; put '**/'; put '%mpeinit()'; put '%mpe_getvars(SASControlTable, SASControlTable)'; put 'options metarepository=&repo;'; put '%mm_getobjects('; put 'type=&metatype'; put ',outds=work.objects'; put ')'; put '%webout(OPEN)'; put '%webout(OBJ,objects)'; put '%webout(CLOSE)'; put '%mpeterm()'; put '* Service end;'; run; %mm_createwebservice(path=&appLoc/&path, name=&service, code=sascode, server=&serverName, replace=yes) filename sascode clear; %let service=metarepos; filename sascode temp lrecl=32767; data _null_; file sascode; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '/**'; put '@file mp_jsonout.sas'; put '@brief Writes JSON in SASjs format to a fileref'; put '@details This macro can be used to OPEN a JSON stream and send one or more'; put 'tables as arrays of rows, where each row can be an object or a nested array.'; put 'There are two engines available - DATASTEP or PROCJSON.'; put 'PROC JSON is fast but will produce errs like the ones below if'; put 'special chars are encountered.'; put '> (ERR)OR: Some code points did not transcode.'; put '> An object or array close is not valid at this point in the JSON text.'; put '> Date value out of range'; put 'If this happens, try running with ENGINE=DATASTEP.'; put 'The DATASTEP engine is used to handle special SAS missing numerics, and'; put 'can also convert entire datasets to formatted values. Output JSON is always'; put 'in UTF-8.'; put 'Usage:'; put 'filename tmp temp;'; put 'data class; set sashelp.class;run;'; put '%mp_jsonout(OPEN,jref=tmp)'; put '%mp_jsonout(OBJ,class,jref=tmp)'; put '%mp_jsonout(OBJ,class,dslabel=class2,jref=tmp,showmeta=Y)'; put '%mp_jsonout(CLOSE,jref=tmp)'; put 'data _null_;'; put 'infile tmp;'; put 'input;putlog _infile_;'; put 'run;'; put 'If you are building web apps with SAS then you are strongly encouraged to use'; put 'the mX_createwebservice macros in combination with the'; put '[sasjs adapter](https://github.com/sasjs/adapter).'; put 'For more information see https://sasjs.io'; put '@param [in] action Valid values:'; put '@li OPEN - opens the JSON'; put '@li OBJ - sends a table with each row as an object'; put '@li ARR - sends a table with each row in an array'; put '@li CLOSE - closes the JSON'; put '@param [in] ds The dataset to send. Must be a work table.'; put '@param [out] jref= (_webout) The fileref to which to send the JSON'; put '@param [out] dslabel= The name to give the table in the exported JSON'; put '@param [in] fmt= (Y) Whether to keep (Y) or strip (N) formats from the table'; put '@param [in] engine= (DATASTEP) Which engine to use to send the JSON. Options:'; put '@li PROCJSON (default)'; put '@li DATASTEP (more reliable when data has non standard characters)'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to JSON'; put '

Related Files

'; put '@li mp_ds2fmtds.sas'; put '@version 9.2'; put '@author Allan Bowe'; put '@source https://github.com/sasjs/core'; put '**/'; put '%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y'; put ',engine=DATASTEP'; put ',missing=NULL'; put ',showmeta=N'; put ',maxobs=MAX'; put ')/*/STORE SOURCE*/;'; put '%local tempds colinfo fmtds i numcols numobs stmt_obs lastobs optval'; put 'tmpds1 tmpds2 tmpds3 tmpds4;'; put '%let numcols=0;'; put '%if &maxobs ne MAX %then %let stmt_obs=%str(if _n_>&maxobs then stop;);'; put '%if &action=OPEN %then %do;'; put 'options nobomfile;'; put 'data _null_;file &jref encoding=''utf-8'' lrecl=200;'; put 'put ''{"PROCESSED_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''"'';'; put 'run;'; put '%end;'; put '%else %if (&action=ARR or &action=OBJ) %then %do;'; put '/* force variable names to always be uppercase in the JSON */'; put 'options validvarname=upcase;'; put '/* To avoid issues with _webout on EBI - such as encoding diffs and truncation'; put '(https://support.sas.com/kb/49/325.html) we use temporary files */'; put 'filename _sjs1 temp lrecl=200 ;'; put 'data _null_; file _sjs1 encoding=''utf-8'';'; put 'put ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":";'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjs1 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs1 clear;'; put '/* grab col defs */'; put 'proc contents noprint data=&ds'; put 'out=_data_(keep=name type length format formatl formatd varnum label);'; put 'run;'; put '%let colinfo=%scan(&syslast,2,.);'; put 'proc sort data=&colinfo;'; put 'by varnum;'; put 'run;'; put '/* move meta to mac vars */'; put 'data &colinfo;'; put 'if _n_=1 then call symputx(''numcols'',nobs,''l'');'; put 'set &colinfo end=last nobs=nobs;'; put 'name=upcase(name);'; put '/* fix formats */'; put 'if type=2 or type=6 then do;'; put 'typelong=''char'';'; put 'length fmt $49.;'; put 'if format='''' then fmt=cats(''$'',length,''.'');'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else fmt=cats(format,formatl,''.'');'; put 'end;'; put 'else do;'; put 'typelong=''num'';'; put 'if format='''' then fmt=''best.'';'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else if formatd=0 then fmt=cats(format,formatl,''.'');'; put 'else fmt=cats(format,formatl,''.'',formatd);'; put 'end;'; put '/* 32 char unique name */'; put 'newname=''sasjs''!!substr(cats(put(md5(name),$hex32.)),1,27);'; put 'call symputx(cats(''name'',_n_),name,''l'');'; put 'call symputx(cats(''newname'',_n_),newname,''l'');'; put 'call symputx(cats(''length'',_n_),length,''l'');'; put 'call symputx(cats(''fmt'',_n_),fmt,''l'');'; put 'call symputx(cats(''type'',_n_),type,''l'');'; put 'call symputx(cats(''typelong'',_n_),typelong,''l'');'; put 'call symputx(cats(''label'',_n_),coalescec(label,name),''l'');'; put '/* overwritten when fmt=Y and a custom format exists in catalog */'; put 'if typelong=''num'' then call symputx(cats(''fmtlen'',_n_),200,''l'');'; put 'else call symputx(cats(''fmtlen'',_n_),min(32767,ceil((length+10)*1.5)),''l'');'; put 'run;'; put '%let tempds=%substr(_%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql;'; put 'select count(*) into: lastobs from &ds;'; put '%if &maxobs ne MAX %then %let lastobs=%sysfunc(min(&lastobs,&maxobs));'; put '%if &engine=PROCJSON %then %do;'; put '%if &missing=STRING %then %do;'; put '%put &sysmacroname: Special Missings not supported in proc json.;'; put '%put &sysmacroname: Switching to DATASTEP engine;'; put '%goto datastep;'; put '%end;'; put 'data &tempds;'; put 'set &ds;'; put '&stmt_obs;'; put '%if &fmt=N %then format _numeric_ best32.;;'; put '/* PRETTY is necessary to avoid line truncation in large files */'; put 'filename _sjs2 temp lrecl=131068 encoding=''utf-8'';'; put 'proc json out=_sjs2 pretty'; put '%if &action=ARR %then nokeys ;'; put ';export &tempds / nosastags fmtnumeric;'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs2 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs2 clear;'; put '%end;'; put '%else %if &engine=DATASTEP %then %do;'; put '%datastep:'; put '%if %sysfunc(exist(&ds)) ne 1 & %sysfunc(exist(&ds,VIEW)) ne 1'; put '%then %do;'; put '%put &sysmacroname: &ds NOT FOUND!!!;'; put '%return;'; put '%end;'; put '%if &fmt=Y %then %do;'; put '/**'; put '* Extract format definitions'; put '* First, by getting library locations from dictionary.formats'; put '* Then, by exporting the width using proc format'; put '* Cannot use maxw from sashelp.vformat as not always populated'; put '* Cannot use fmtinfo() as not supported in all flavours'; put '*/'; put '%let tmpds1=%substr(fmtsum%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds2=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds3=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds4=%substr(col%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql noprint;'; put 'create table &tmpds1 as'; put 'select cats(libname,''.'',memname) as FMTCAT,'; put 'FMTNAME'; put 'from dictionary.formats'; put 'where fmttype=''F'' and libname is not null'; put 'and fmtname in (select format from &colinfo where format is not null)'; put 'order by 1;'; put 'create table &tmpds2('; put 'FMTNAME char(32),'; put 'LENGTH num'; put ');'; put '%local catlist cat fmtlist i;'; put 'select distinct fmtcat into: catlist separated by '' '' from &tmpds1;'; put '%do i=1 %to %sysfunc(countw(&catlist,%str( )));'; put '%let cat=%scan(&catlist,&i,%str( ));'; put 'proc sql;'; put 'select distinct fmtname into: fmtlist separated by '' '''; put 'from &tmpds1 where fmtcat="&cat";'; put 'proc format lib=&cat cntlout=&tmpds3(keep=fmtname length);'; put 'select &fmtlist;'; put 'run;'; put 'proc sql;'; put 'insert into &tmpds2 select distinct fmtname,length from &tmpds3;'; put '%end;'; put 'proc sql;'; put 'create table &tmpds4 as'; put 'select a.*, b.length as MAXW'; put 'from &colinfo a'; put 'left join &tmpds2 b'; put 'on cats(a.format)=cats(upcase(b.fmtname))'; put 'order by a.varnum;'; put 'data _null_;'; put 'set &tmpds4;'; put 'if not missing(maxw);'; put 'call symputx('; put 'cats(''fmtlen'',_n_),'; put '/* vars need extra padding due to JSON escaping of special chars */'; put 'min(32767,ceil((max(length,maxw)+10)*1.5))'; put ',''l'''; put ');'; put 'run;'; put '/* configure varlenchk - as we are explicitly shortening the variables */'; put '%let optval=%sysfunc(getoption(varlenchk));'; put 'options varlenchk=NOWARN;'; put 'data _data_(compress=char);'; put '/* shorten the new vars */'; put 'length'; put '%do i=1 %to &numcols;'; put '&&name&i $&&fmtlen&i'; put '%end;'; put ';'; put '/* rename on entry */'; put 'set &ds(rename=('; put '%do i=1 %to &numcols;'; put '&&name&i=&&newname&i'; put '%end;'; put '));'; put '&stmt_obs;'; put 'drop'; put '%do i=1 %to &numcols;'; put '&&newname&i'; put '%end;'; put ';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=num %then %do;'; put '&&name&i=cats(put(&&newname&i,&&fmt&i));'; put '%end;'; put '%else %do;'; put '&&name&i=put(&&newname&i,&&fmt&i);'; put '%end;'; put '%end;'; put 'if _error_ then do;'; put 'call symputx(''syscc'',1012);'; put 'stop;'; put 'end;'; put 'run;'; put '%let fmtds=&syslast;'; put 'options varlenchk=&optval;'; put '%end;'; put 'proc format; /* credit yabwon for special null removal */'; put 'value bart (default=40)'; put '%if &missing=NULL %then %do;'; put '._ - .z = null'; put '%end;'; put '%else %do;'; put '._ = [quote()]'; put '. = null'; put '.a - .z = [quote()]'; put '%end;'; put 'other = [best.];'; put 'data &tempds;'; put 'attrib _all_ label='''';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'length &&name&i $&&fmtlen&i...;'; put 'format &&name&i $&&fmtlen&i...;'; put '%end;'; put '%end;'; put '%if &fmt=Y %then %do;'; put 'set &fmtds;'; put '%end;'; put '%else %do;'; put 'set &ds;'; put '%end;'; put '&stmt_obs;'; put 'format _numeric_ bart.;'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'if findc(&&name&i,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put '&&name&i=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,&&name&i)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else &&name&i=quote(cats(&&name&i));'; put '%end;'; put '%end;'; put 'run;'; put 'filename _sjs3 temp lrecl=131068 ;'; put 'data _null_;'; put 'file _sjs3 encoding=''utf-8'';'; put 'if _n_=1 then put "[";'; put 'set &tempds;'; put 'if _n_>1 then put "," @; put'; put '%if &action=ARR %then "[" ; %else "{" ;'; put '%do i=1 %to &numcols;'; put '%if &i>1 %then "," ;'; put '%if &action=OBJ %then """&&name&i"":" ;'; put '"&&name&i"n /* name literal for reserved variable names */'; put '%end;'; put '%if &action=ARR %then "]" ; %else "}" ; ;'; put '/* close out the table */'; put 'data _null_;'; put 'file _sjs3 mod encoding=''utf-8'';'; put 'put '']'';'; put 'run;'; put 'data _null_;'; put 'infile _sjs3 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs3 clear;'; put '%end;'; put 'proc sql;'; put 'drop table &colinfo, &tempds;'; put '%if %substr(&showmeta,1,1)=Y %then %do;'; put 'filename _sjs4 temp lrecl=131068 encoding=''utf-8'';'; put 'data _null_;'; put 'file _sjs4;'; put 'length label $350;'; put 'put ", ""$%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":{""vars"":{";'; put 'do i=1 to &numcols;'; put 'name=quote(trim(symget(cats(''name'',i))));'; put 'format=quote(trim(symget(cats(''fmt'',i))));'; put 'label=quote(prxchange(''s/\\/\\\\/'',-1,trim(symget(cats(''label'',i)))));'; put 'length=quote(trim(symget(cats(''length'',i))));'; put 'type=quote(trim(symget(cats(''typelong'',i))));'; put 'if i>1 then put "," @@;'; put 'put name '':{"format":'' format '',"label":'' label'; put ''',"length":'' length '',"type":'' type ''}'';'; put 'end;'; put 'put ''}}'';'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs4 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs4 clear;'; put '%end;'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put 'data _null_; file &jref encoding=''utf-8'' mod ;'; put 'put "}";'; put 'run;'; put '%end;'; put '%mend mp_jsonout;'; put '/**'; put '@file mm_webout.sas'; put '@brief Send data to/from SAS Stored Processes'; put '@details This macro should be added to the start of each Stored Process,'; put '**immediately** followed by a call to:'; put '%mm_webout(FETCH)'; put 'This will read all the input data and create same-named SAS datasets in the'; put 'WORK library. You can then insert your code, and send data back using the'; put 'following syntax:'; put 'data some datasets; * make some data ;'; put 'retain some columns;'; put 'run;'; put '%mm_webout(OPEN)'; put '%mm_webout(ARR,some) * Array format, fast, suitable for large tables ;'; put '%mm_webout(OBJ,datasets) * Object format, easier to work with ;'; put 'Finally, wrap everything up send some helpful system variables too'; put '%mm_webout(CLOSE)'; put '@param [in] action Either FETCH, OPEN, ARR, OBJ or CLOSE'; put '@param [in] ds The dataset to send back to the frontend'; put '@param [out] dslabel= Value to use instead of table name for sending to JSON'; put '@param [in] fmt= (N) Setting Y converts all vars to their formatted values'; put '@param [out] fref= (_webout) The fileref to which to write the JSON'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] workobs= (0) When set to a positive integer, will create a new'; put 'output object (WORK) which contains this number of observations from all'; put 'tables in the WORK library.'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to output JSON'; put '

SAS Macros

'; put '@li mp_jsonout.sas'; put '

Related Macros

'; put '@li ms_webout.sas'; put '@li mv_webout.sas'; put '@version 9.3'; put '@author Allan Bowe'; put '**/'; put '%macro mm_webout(action,ds,dslabel=,fref=_webout,fmt=N,missing=NULL'; put ',showmeta=N,maxobs=MAX,workobs=0'; put ');'; put '%global _webin_file_count _webin_fileref1 _webin_name1 _program _debug'; put 'sasjs_tables;'; put '%local i tempds jsonengine;'; put '/* see https://github.com/sasjs/core/issues/41 */'; put '%if "%upcase(&SYSENCODING)" ne "UTF-8" %then %let jsonengine=PROCJSON;'; put '%else %let jsonengine=DATASTEP;'; put '%if &action=FETCH %then %do;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'options mprint notes mprintnest;'; put '%end;'; put '%let _webin_file_count=%eval(&_webin_file_count+0);'; put '/* now read in the data */'; put '%do i=1 %to &_webin_file_count;'; put '%if &_webin_file_count=1 %then %do;'; put '%let _webin_fileref1=&_webin_fileref;'; put '%let _webin_name1=&_webin_name;'; put '%end;'; put 'data _null_;'; put 'infile &&_webin_fileref&i termstr=crlf;'; put 'input;'; put 'call symputx(''input_statement'',_infile_);'; put 'putlog "&&_webin_name&i input statement: " _infile_;'; put 'stop;'; put 'data &&_webin_name&i;'; put 'infile &&_webin_fileref&i firstobs=2 dsd termstr=crlf encoding=''utf-8'';'; put 'input &input_statement;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'if _n_<20 then putlog _infile_;'; put '%end;'; put 'run;'; put '%let sasjs_tables=&sasjs_tables &&_webin_name&i;'; put '%end;'; put '%end;'; put '%else %if &action=OPEN %then %do;'; put '/* fix encoding */'; put 'OPTIONS NOBOMFILE;'; put '/**'; put '* check xengine type to avoid the below err message:'; put '* > Function is only valid for filerefs using the CACHE access method.'; put '*/'; put 'data _null_;'; put 'set sashelp.vextfl(where=(fileref="_WEBOUT"));'; put 'if xengine=''STREAM'' then do;'; put 'rc=stpsrv_header(''Content-type'',"text/html; encoding=utf-8");'; put 'end;'; put 'run;'; put '/* setup json */'; put 'data _null_;file &fref encoding=''utf-8'';'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutBEGIN<<'';'; put '%end;'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'run;'; put '%end;'; put '%else %if &action=ARR or &action=OBJ %then %do;'; put '%mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt,jref=&fref'; put ',engine=&jsonengine,missing=&missing,showmeta=&showmeta,maxobs=&maxobs'; put ')'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put '/* To avoid issues with _webout on EBI we use a temporary file */'; put 'filename _sjsref temp lrecl=131068;'; put '%if %str(&workobs) > 0 %then %do;'; put '/* if debug mode, send back first XX records of each work table also */'; put 'data;run;%let tempds=%scan(&syslast,2,.);'; put 'ods output Members=&tempds;'; put 'proc datasets library=WORK memtype=data;'; put '%local wtcnt;%let wtcnt=0;'; put 'data _null_;'; put 'set &tempds;'; put 'if not (upcase(name) =:"DATA"); /* ignore temp datasets */'; put 'i+1;'; put 'call symputx(cats(''wt'',i),name,''l'');'; put 'call symputx(''wtcnt'',i,''l'');'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put ",""WORK"":{";'; put '%do i=1 %to &wtcnt;'; put '%let wt=&&wt&i;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'dsid=open("WORK.&wt",''is'');'; put 'nlobs=attrn(dsid,''NLOBS'');'; put 'nvars=attrn(dsid,''NVARS'');'; put 'rc=close(dsid);'; put 'if &i>1 then put '',''@;'; put 'put " ""&wt"" : {";'; put 'put ''"nlobs":'' nlobs;'; put 'put '',"nvars":'' nvars;'; put '%mp_jsonout(OBJ,&wt,jref=_sjsref,dslabel=first10rows,showmeta=Y'; put ',maxobs=&workobs'; put ')'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put '%end;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put 'run;'; put '%end;'; put '/* close off json */'; put 'data _null_;file _sjsref mod encoding=''utf-8'';'; put 'length SYSPROCESSNAME syserrortext syswarningtext autoexec $512;'; put 'put ",""_DEBUG"" : ""&_debug"" ";'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'autoexec=quote(urlencode(trim(getoption(''autoexec''))));'; put 'put '',"AUTOEXEC" : '' autoexec;'; put 'put ",""MF_GETUSER"" : ""%mf_getuser()"" ";'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'put ",""SYSENCODING"" : ""&sysencoding"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSPROCESSID"" : ""&SYSPROCESSID"" ";'; put 'put ",""SYSPROCESSMODE"" : ""&SYSPROCESSMODE"" ";'; put 'SYSPROCESSNAME=quote(urlencode(cats(SYSPROCESSNAME)));'; put 'put ",""SYSPROCESSNAME"" : " SYSPROCESSNAME;'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put '',"SYSWARNINGTEXT" : '' syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'length memsize $32;'; put 'memsize="%sysfunc(INPUTN(%sysfunc(getoption(memsize)), best.),sizekmg.)";'; put 'memsize=quote(cats(memsize));'; put 'put '',"MEMSIZE" : '' memsize;'; put 'put "}" @;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutEND<<'';'; put '%end;'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjsref lrecl=1 recfm=n;'; put 'file &fref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjsref clear;'; put '%end;'; put '%mend mm_webout;'; put '%macro webout(action,ds,dslabel=,fmt=,missing=NULL,showmeta=NO,maxobs=MAX);'; put '%mm_webout(&action,ds=&ds,dslabel=&dslabel,fmt=&fmt'; put ',missing=&missing'; put ',showmeta=&showmeta'; put ',maxobs=&maxobs'; put ') %mend;'; put '/* provide additional debug info */'; put '%global _program;'; put '%put &=syscc;'; put '%put user=%mf_getuser();'; put '%put pgm=&_program;'; put '%put timestamp=%sysfunc(datetime(),datetime19.);'; put '* Service Variables start;'; put '* Service Variables end;'; put '* SAS Macros start;'; put '%macro mf_getapploc(pgm);'; put '%if "&pgm"="" %then %do;'; put '%if %symexist(_program) %then %let pgm=&_program;'; put '%else %do;'; put '%put &sysmacroname: No value provided and no _program variable available;'; put '%return;'; put '%end;'; put '%end;'; put '%local root;'; put '/**'; put '* First check we are not in the tests/macros folder (which has no subfolders)'; put '* or specifically in the testsetup or testteardown services'; put '*/'; put '%if %index(&pgm,/tests/macros/)'; put 'or %index(&pgm,/tests/testsetup)'; put 'or %index(&pgm,/tests/testteardown)'; put '%then %do;'; put '%let root=%substr(&pgm,1,%index(&pgm,/tests)-1);'; put '&root'; put '%return;'; put '%end;'; put '/**'; put '* Next, move up two levels to avoid matches on subfolder or service name'; put '*/'; put '%let root=%substr(&pgm,1,%length(&pgm)-%length(%scan(&pgm,-1,/))-1);'; put '%let root=%substr(&root,1,%length(&root)-%length(%scan(&root,-1,/))-1);'; put '%if %index(&root,/tests/) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/tests/)-1);'; put '%end;'; put '%else %if %index(&root,/services) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/services)-1);'; put '%end;'; put '%else %if %index(&root,/jobs) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/jobs)-1);'; put '%end;'; put '%else %put &sysmacroname: Could not find an app location from &pgm;'; put '&root'; put '%mend mf_getapploc ;'; put '%macro mf_getuniquefileref(prefix=_,maxtries=1000,lrecl=32767);'; put '%local rc fname;'; put '%if &prefix=0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%end;'; put '%else %do;'; put '%local x len;'; put '%let len=%eval(8-%length(&prefix));'; put '%let x=0;'; put '%do x=0 %to &maxtries;'; put '%let fname=&prefix%substr(%sysfunc(ranuni(0)),3,&len);'; put '%if %sysfunc(fileref(&fname)) > 0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%return;'; put '%end;'; put '%end;'; put '%put unable to find available fileref after &maxtries attempts;'; put '%end;'; put '%mend mf_getuniquefileref;'; put '%macro mp_abort(mac=mp_abort.sas, type=, msg=, iftrue=%str(1=1)'; put ', errds=work.mp_abort_errds'; put ', mode=REGULAR'; put ')/*/STORE SOURCE*/;'; put '%global sysprocessmode sysprocessname sasjs_stpsrv_header_loc sasjsprocessmode;'; put '%local fref fid i;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%put NOTE: /// mp_abort macro executing //;'; put '%if %length(&mac)>0 %then %put NOTE- called by &mac;'; put '%put NOTE - &msg;'; put '%if %symexist(_SYSINCLUDEFILEDEVICE)'; put '/* abort cancel FILE does not restart outside the INCLUDE on Viya 3.5 */'; put 'and %superq(SYSPROCESSNAME) ne %str(Compute Server)'; put '%then %do;'; put '%if "*&_SYSINCLUDEFILEDEVICE*" ne "**" %then %do;'; put 'data &errds;'; put 'iftrue=''1=1'';'; put 'length mac $100 msg $5000;'; put 'mac=symget(''mac'');'; put 'msg=symget(''msg'');'; put 'run;'; put 'data _null_;'; put 'abort cancel FILE;'; put 'run;'; put '%return;'; put '%end;'; put '%end;'; put '/* Web App Context */'; put '%if %symexist(_PROGRAM)'; put 'or %superq(SYSPROCESSNAME) = %str(Compute Server)'; put 'or &mode=INCLUDE'; put '%then %do;'; put 'options obs=max replace mprint;'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5"'; put '%then %do;'; put 'options nosyntaxcheck;'; put '%end;'; put '%if &mode=INCLUDE %then %do;'; put '%if %sysfunc(exist(&errds))=1 %then %do;'; put 'data _null_;'; put 'set &errds;'; put 'call symputx(''iftrue'',iftrue,''l'');'; put 'call symputx(''mac'',mac,''l'');'; put 'call symputx(''msg'',msg,''l'');'; put 'putlog (_all_)(=);'; put 'run;'; put '%if (&iftrue)=0 %then %return;'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: No include errors found;'; put '%return;'; put '%end;'; put '%end;'; put '/* extract log errs / warns, if exist */'; put '%local logloc logline;'; put '%global logmsg; /* capture global messages */'; put '%if %symexist(SYSPRINTTOLOG) %then %let logloc=&SYSPRINTTOLOG;'; put '%else %let logloc=%qsysfunc(getoption(LOG));'; put 'proc printto log=log;run;'; put '%let logline=0;'; put '%if %length(&logloc)>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input; putlog _infile_;'; put 'i=1;'; put 'retain logonce 0;'; put 'if ('; put '_infile_=:"%str(WARN)ING" or _infile_=:"%str(ERR)OR"'; put ') and logonce=0 then'; put 'do;'; put 'call symputx(''logline'',_n_);'; put 'logonce+1;'; put 'end;'; put 'run;'; put '/* capture log including lines BEFORE the err */'; put '%if &logline>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input;'; put 'i=1;'; put 'stoploop=0;'; put 'if _n_ ge &logline-15 and stoploop=0 then do until (i>22);'; put 'call symputx(''logmsg'',catx(''\n'',symget(''logmsg''),_infile_));'; put 'input;'; put 'i+1;'; put 'stoploop=1;'; put 'end;'; put 'if stoploop=1 then stop;'; put 'run;'; put '%end;'; put '%end;'; put '%if %symexist(SYS_JES_JOB_URI) %then %do;'; put '/* setup webout for Viya */'; put 'options nobomfile;'; put '%if "X&SYS_JES_JOB_URI.X"="XX" %then %do;'; put 'filename _webout temp lrecl=999999 mod;'; put '%end;'; put '%else %do;'; put 'filename _webout filesrvc parenturi="&SYS_JES_JOB_URI"'; put 'name="_webout.json" lrecl=999999 mod;'; put '%end;'; put '%end;'; put '%else %if %sysfunc(filename(fref,&sasjs_stpsrv_header_loc))=0 %then %do;'; put 'options nobomfile;'; put '/* set up http header for SASjs Server */'; put '%let fid=%sysfunc(fopen(&fref,A));'; put '%if &fid=0 %then %do;'; put '%put %str(ERR)OR: %sysfunc(sysmsg());'; put '%return;'; put '%end;'; put '%let rc=%sysfunc(fput(&fid,%str(Content-Type: application/json)));'; put '%let rc=%sysfunc(fwrite(&fid));'; put '%let rc=%sysfunc(fclose(&fid));'; put '%let rc=%sysfunc(filename(&fref));'; put '%end;'; put '/* send response in SASjs JSON format */'; put 'data _null_;'; put 'file _webout mod lrecl=32000 encoding=''utf-8'';'; put 'length msg syswarningtext syserrortext $32767 mode $10 ;'; put 'sasdatetime=datetime();'; put 'msg=symget(''msg'');'; put '%if &logline>0 %then %do;'; put 'msg=cats(msg,''\n\nLog Extract:\n'',symget(''logmsg''));'; put '%end;'; put '/* escape the escapes */'; put 'msg=tranwrd(msg,''\'',''\\'');'; put '/* escape the quotes */'; put 'msg=tranwrd(msg,''"'',''\"'');'; put '/* ditch the CRLFs as chrome complains */'; put 'msg=compress(msg,,''kw'');'; put '/* quote without quoting the quotes (which are escaped instead) */'; put 'msg=cats(''"'',msg,''"'');'; put 'if symexist(''_debug'') then debug=quote(trim(symget(''_debug'')));'; put 'else debug=''""'';'; put 'if symget(''sasjsprocessmode'')=''Stored Program'' then mode=''SASJS'';'; put 'if mode ne ''SASJS'' then put ''>>weboutBEGIN<<'';'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'put '',"sasjsAbort" : [{'';'; put 'put '' "MSG":'' msg ;'; put 'put '' ,"MAC": "'' "&mac" ''"}]'';'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'put '',"_DEBUG":'' debug ;'; put 'if symexist(''_metauser'') then do;'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put 'end;'; put 'if symexist(''SYS_JES_JOB_URI'') then do;'; put 'SYS_JES_JOB_URI=quote(trim(symget(''SYS_JES_JOB_URI'')));'; put 'put '',"SYS_JES_JOB_URI": '' SYS_JES_JOB_URI;'; put 'end;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put ",""SYSWARNINGTEXT"" : " syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'put "}" ;'; put 'if mode ne ''SASJS'' then put ''>>weboutEND<<'';'; put 'run;'; put '%put _all_;'; put '%if "&sysprocessmode " = "SAS Stored Process Server " %then %do;'; put 'data _null_;'; put 'putlog ''stpsrvset program err and syscc'';'; put 'rc=stpsrvset(''program error'', 0);'; put 'call symputx("syscc",0,"g");'; put 'run;'; put '%if &sysscp=WIN'; put 'and 1=0 /* deprecating this logic until we figure out a consistent abort */'; put 'and "%substr(%str(&sysvlong ),1,8)"="9.04.01M"'; put 'and "%substr(%str(&sysvlong ),9,1)">"5" %then %do;'; put '/* skip approach (below) does not work in windows m6+ envs */'; put 'endsas;'; put '%end;'; put '%else %do;'; put '/**'; put '* endsas kills 9.4m3 deployments by orphaning multibridges.'; put '* Abort variants are ungraceful (non zero return code)'; put '* This approach lets SAS run silently until the end :-)'; put '* Caution - fails when called within a %include within a macro'; put '* Use mp_include() to handle this.'; put '*/'; put 'filename skip temp;'; put 'data _null_;'; put 'file skip;'; put 'put ''%macro skip();'';'; put 'comment ''%mend skip; -> fix lint '';'; put 'put ''%macro skippy();'';'; put 'comment ''%mend skippy; -> fix lint '';'; put 'run;'; put '%inc skip;'; put '%end;'; put '%end;'; put '%else %if "&sysprocessmode " = "SAS Compute Server " %then %do;'; put '/* endsas kills the session making it harder to fetch results */'; put 'data _null_;'; put 'syswarningtext=symget(''syswarningtext'');'; put 'syserrortext=symget(''syserrortext'');'; put 'abort_msg=symget(''msg'');'; put 'syscc=symget(''syscc'');'; put 'sysuserid=symget(''sysuserid'');'; put 'iftrue=symget(''iftrue'');'; put 'put (_all_)(/=);'; put 'call symputx(''syscc'',0);'; put 'abort cancel nolist;'; put 'run;'; put '%end;'; put '%else %do;'; put '%abort cancel;'; put '%end;'; put '%end;'; put '%else %do;'; put '%put _all_;'; put '%abort cancel;'; put '%end;'; put '%mend mp_abort;'; put '/** @endcond */'; put '%macro mm_getstpcode('; put 'tree=/User Folders/sasdemo/somestp'; put ',name='; put ',outloc=0'; put ',outref=0'; put ',mDebug=1'; put ',showlog=NO'; put ');'; put '%local mD;'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing &sysmacroname..sas;'; put '%&mD.put _local_;'; put '%if %length(&name)>0 %then %let name=/&name;'; put '/* first, check if STP exists */'; put '%local tsuri;'; put '%let tsuri=stopifempty ;'; put 'data _null_;'; put 'format type uri tsuri value $200.;'; put 'call missing (of _all_);'; put 'path="&tree&name(StoredProcess)";'; put '/* first, find the STP ID */'; put 'if metadata_pathobj("",path,"StoredProcess",type,uri)>0 then do;'; put '/* get sourcecode */'; put 'cnt=1;'; put 'do while (metadata_getnasn(uri,"Notes",cnt,tsuri)>0);'; put 'rc=metadata_getattr(tsuri,"Name",value);'; put '&mD.put tsuri= value=;'; put 'if value="SourceCode" then do;'; put '/* found it! */'; put 'rc=metadata_getattr(tsuri,"Id",value);'; put 'call symputx(''tsuri'',value,''l'');'; put 'stop;'; put 'end;'; put 'cnt+1;'; put 'end;'; put 'end;'; put 'else put (_all_)(=);'; put 'run;'; put '%mp_abort(iftrue= (&tsuri=stopifempty)'; put ',mac=mm_getstpcode'; put ',msg=%str(&tree&name.(StoredProcess) not found!)'; put ')'; put '/**'; put '* Now we can extract the textstore'; put '*/'; put 'filename __getdoc temp lrecl=10000000;'; put 'proc metadata'; put 'in="$METAREPOSITORY'; put ''; put 'SAS1"'; put 'out=__getdoc ;'; put 'run;'; put '/* find the beginning of the text */'; put '%local start;'; put 'data _null_;'; put 'infile __getdoc lrecl=10000;'; put 'input;'; put 'start=index(_infile_,''StoredText="'');'; put 'if start then do;'; put 'call symputx("start",start+11);'; put '*putlog ''"'' _infile_ ''"'';'; put 'end;'; put 'stop;'; put '%local outeng;'; put '%if "&outloc"="0" %then %let outeng=TEMP;'; put '%else %let outeng="&outloc";'; put '%local fref;'; put '%if &outref=0 %then %let fref=%mf_getuniquefileref();'; put '%else %let fref=&outref;'; put '/* read the content, byte by byte, resolving escaped chars */'; put 'filename &fref &outeng lrecl=100000;'; put 'data _null_;'; put 'length filein 8 fileid 8;'; put 'filein = fopen("__getdoc","I",1,"B");'; put 'fileid = fopen("&fref","O",1,"B");'; put 'rec = "20"x;'; put 'length entity $6;'; put 'do while(fread(filein)=0);'; put 'x+1;'; put 'if x>&start then do;'; put 'rc = fget(filein,rec,1);'; put 'if rec=''"'' then leave;'; put 'else if rec="&" then do;'; put 'entity=rec;'; put 'do until (rec=";");'; put 'if fread(filein) ne 0 then goto getout;'; put 'rc = fget(filein,rec,1);'; put 'entity=cats(entity,rec);'; put 'end;'; put 'select (entity);'; put 'when (''&'' ) rec=''&'' ;'; put 'when (''<'' ) rec=''<'' ;'; put 'when (''>'' ) rec=''>'' ;'; put 'when (''''') rec="''" ;'; put 'when (''"'') rec=''"'' ;'; put 'when ('' '') rec=''0A''x;'; put 'when ('' '') rec=''0D''x;'; put 'when (''$'' ) rec=''$'' ;'; put 'when ('' '') rec=''09''x;'; put 'otherwise putlog "%str(WARN)ING: missing value for " entity=;'; put 'end;'; put 'rc =fput(fileid, substr(rec,1,1));'; put 'rc =fwrite(fileid);'; put 'end;'; put 'else do;'; put 'rc =fput(fileid,rec);'; put 'rc =fwrite(fileid);'; put 'end;'; put 'end;'; put 'end;'; put 'getout:'; put 'rc=fclose(filein);'; put 'rc=fclose(fileid);'; put 'run;'; put '%if &showlog=YES %then %do;'; put 'data _null_;'; put 'infile &fref lrecl=32767 end=last;'; put 'input;'; put 'if _n_=1 then putlog ''>>stpcodeBEGIN<<'';'; put 'putlog _infile_;'; put 'if last then putlog ''>>stpcodeEND<<'';'; put 'run;'; put '%end;'; put 'filename __getdoc clear;'; put '%if &outref=0 %then %do;'; put 'filename &fref clear;'; put '%end;'; put '%mend mm_getstpcode;'; put '%macro dc_getsettings();'; put '%global _program;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(syscc=&syscc on entry (&syswarningtext &syserrortext))'; put ')'; put '%if %length(&_PROGRAM)>1 %then %let root=&_program;'; put '%else %do;'; put '%global _metauser;'; put '%let _metauser=&sysuserid;'; put '/* to mimic a "real" _program we need to give a dummy role and stp name */'; put '%let root=/dummyRole/dummyName;'; put '%end;'; put '/* the DC precode is stored in the Admin folder in the root of'; put 'the project. Lets find that root. */'; put '%put &=root;'; put '%let root=%mf_getapploc();'; put '%put &=root;'; put '/* Now we know the root location we can retrieve the params */'; put '%let temploc=%sysfunc(pathname(work))/settings.txt;'; put '%mm_getstpcode(tree=&root/services/public'; put ',name=Data_Controller_Settings'; put ',outloc=&temploc'; put ')'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Unable to run getstpcode)'; put ')'; put 'filename _getsets "&temploc" lrecl=2000;'; put '/*'; put 'Do not use mp_include here - this puts a copy in every service, which creates'; put 'compilation problems when calling services from mp_include'; put '*/'; put '%inc _getsets/source2;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Problem running &sysmacroname (&syswarningtext &syserrortext))'; put ')'; put '%mend dc_getsettings;'; put '%macro mf_fmtdttm('; put ')/*/STORE SOURCE*/;'; put '%if "&sysver"="9.2" or "&sysver"="9.3"'; put 'or ("&sysver"="9.4" and "%substr(&SYSVLONG,9,1)" le "3")'; put 'or "%substr(&sysver,1,1)"="4"'; put 'or "%substr(&sysver,1,1)"="5"'; put '%then %do;DATETIME19.3%end;'; put '%else %do;E8601DT26.6%end;'; put '%mend mf_fmtdttm;'; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '%macro mp_init(prefix=SASJS'; put ')/*/STORE SOURCE*/;'; put '%if %symexist(SASJS_PREFIX) %then %return; /* only run once */'; put '%global'; put 'SASJS_PREFIX /* the ONLY hard-coded global macro variable in SASjs */'; put '&prefix._FUNCTIONS /* used in mcf_init() to track core function compilation */'; put '&prefix._INIT_NUM /* initialisation time as numeric */'; put '&prefix._INIT_DTTM /* initialisation time in E8601DT26.6 format */'; put '&prefix.WORK /* avoid typing %sysfunc(pathname(work)) every time */'; put ';'; put '%let sasjs_prefix=&prefix;'; put 'data _null_;'; put 'dttm=datetime();'; put 'call symputx("&sasjs_prefix._init_num",dttm,''g'');'; put 'call symputx("&sasjs_prefix._init_dttm",put(dttm,E8601DT26.6),''g'');'; put 'call symputx("&sasjs_prefix.work",pathname(''WORK''),''g'');'; put 'run;'; put 'options'; put 'compress=CHAR /* default is none so ensure we have something! */'; put 'datastmtchk=ALLKEYWORDS /* protection from overwriting input datasets */'; put 'errorcheck=STRICT /* catch errs in libname/filename statements */'; put 'fmterr /* ensure err when a format cannot be found */'; put 'mergenoby=%str(ERR)OR /* throw err when a merge has no BY variables */'; put 'missing=. /* changing this can cause hard to detect errs */'; put 'noquotelenmax /* avoid warnings for long strings */'; put 'noreplace /* avoid overwriting permanent datasets */'; put 'ps=max /* reduce log size slightly */'; put 'ls=max /* reduce log even more and avoid word truncation */'; put 'validmemname=COMPATIBLE /* avoid special characters etc in table names */'; put 'validvarname=V7 /* avoid special characters etc in variable names */'; put 'varinitchk=%str(ERR)OR /* avoid data mistakes from variable name typos */'; put 'varlenchk=%str(ERR)OR /* fail hard if truncation (data loss) can result */'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5" %then %do;'; put 'noautocorrect /* disallow misspelled procedure names */'; put 'dsoptions=note2err /* undocumented - convert bad NOTEs to ERRs */'; put '%end;'; put ';'; put '%mend mp_init;'; put '%macro mpeinit(fetch=YES);'; put '%global mpeinit'; put 'mpeadmins /* group with unrestricted Meditor access */'; put 'mpelocapprovals /* location for landing and staging files */'; put 'mpelib /* location of configuration tables for DC */'; put 'dc_repo_users /* location of user / group metadata */'; put 'dc_licence_key /* extracted in dc_getsettings */'; put 'dc_activation_key /* extracted in dc_getsettings */'; put 'dc_locale /* extracted in dc_getsettings */'; put 'dc_dttmtfmt /* can be overridden in dc_getsettings */'; put '_debug'; put ';'; put '%if &mpeinit=1 %then %return;'; put '%else %let mpeinit=1;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(Problem on service startup (&syswarningtext &syserrortext))'; put ')'; put '%mp_init()'; put '%if &fetch=YES %then %do;'; put '%webout(FETCH)'; put '%end;'; put '%global _CLIENTNAME;'; put '%mp_abort(iftrue= (&_CLIENTNAME=SAS Enterprise Guide)'; put ',mac=&_program..sas'; put ',msg=%str(Data Controller is a web app and should not be executed from EG)'; put ')'; put 'options urlencoding=utf8 nobomfile lrecl=32767;'; put '%let perf=%sysfunc(datetime());'; put '%put perfdiff: 0;'; put '%let dc_locale=SYSTEM; /* default if not set */'; put '/**'; put '* E8601DT26.6 has widest database support - but not all SAS flavours can'; put '* handle it. Override in the settings STP if needed.'; put '*/'; put 'data _null_;'; put 'dc_dttmtfmt=''"%sysfunc(datetime(),''!!"%mf_fmtdttm()"!!'')"dt'';'; put 'call symputx(''dc_dttmtfmt'',dc_dttmtfmt);'; put 'put dc_dttmtfmt=;'; put 'run;'; put '%put &=dc_dttmtfmt;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc prior to dc_getsettings)'; put ')'; put '%dc_getsettings()'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc after dc_getsettings)'; put ')'; put 'data _null_;'; put 'set &DC_LIBREF..mpe_config(where=('; put 'var_scope="DC"'; put 'and &dc_dttmtfmt lt tx_to'; put 'and var_active=1'; put '));'; put 'call symputx(var_name,var_value,''G'');'; put 'putlog var_name "=" var_value;'; put 'run;'; put '%let mpelib=&dc_libref;'; put '%let mpeadmins=&dc_admin_group;'; put '%let mpelocapprovals=&dc_staging_area;'; put '%let dc_repo_users=&dc_repo_users;'; put '%if &dc_locale ne SYSTEM %then %do;'; put 'options locale=&dc_locale;'; put '%end;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(Problem during compilation or with STP precode (&syswarningtext))'; put ')'; put '%mend mpeinit;'; put '%macro mf_mval(var);'; put '%if %symexist(&var) %then %do;'; put '%superq(&var)'; put '%end;'; put '%mend mf_mval;'; put '%macro mf_trimstr(basestr,trimstr);'; put '%local baselen trimlen trimval;'; put '/* return if basestr is shorter than trimstr (or 0) */'; put '%let baselen=%length(%superq(basestr));'; put '%let trimlen=%length(%superq(trimstr));'; put '%if &baselen < &trimlen or &baselen=0 %then %return;'; put '/* obtain the characters from the end of basestr */'; put '%let trimval=%qsubstr(%superq(basestr)'; put ',%length(%superq(basestr))-&trimlen+1'; put ',&trimlen);'; put '/* compare and if matching, chop it off! */'; put '%if %superq(basestr)=%superq(trimstr) %then %do;'; put '%return;'; put '%end;'; put '%else %if %superq(trimval)=%superq(trimstr) %then %do;'; put '%qsubstr(%superq(basestr),1,%length(%superq(basestr))-&trimlen)'; put '%end;'; put '%else %do;'; put '&basestr'; put '%end;'; put '%mend mf_trimstr;'; put '%macro mf_getplatform(switch'; put ')/*/STORE SOURCE*/;'; put '%local a b c;'; put '%if &switch.NONE=NONE %then %do;'; put '%if %symexist(sasjsprocessmode) %then %do;'; put '%if &sasjsprocessmode=Stored Program %then %do;'; put 'SASJS'; put '%return;'; put '%end;'; put '%end;'; put '%if %symexist(sysprocessmode) %then %do;'; put '%if "&sysprocessmode"="SAS Object Server"'; put 'or "&sysprocessmode"= "SAS Compute Server" %then %do;'; put 'SASVIYA'; put '%end;'; put '%else %if "&sysprocessmode"="SAS Stored Process Server"'; put 'or "&sysprocessmode"="SAS Workspace Server"'; put '%then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if %symexist(_metaport) or %symexist(_metauser) %then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if &switch=SASSTUDIO %then %do;'; put '/* return the version of SAS Studio else 0 */'; put '%if %mf_mval(_CLIENTAPP)=%str(SAS Studio) %then %do;'; put '%let a=%mf_mval(_CLIENTVERSION);'; put '%let b=%scan(&a,1,.);'; put '%if %eval(&b >2) %then %do;'; put '&b'; put '%end;'; put '%else 0;'; put '%end;'; put '%else 0;'; put '%end;'; put '%else %if &switch=VIYARESTAPI %then %do;'; put '%mf_trimstr(%sysfunc(getoption(servicesbaseurl)),/)'; put '%end;'; put '%mend mf_getplatform;'; put '%macro mpeterm();'; put '%local oldloc;'; put 'data _null_;'; put 'if symexist(''SYSPRINTTOLOG'') then oldloc=symget(''SYSPRINTTOLOG'');'; put 'else oldloc=getoption(''LOG'');'; put 'if subpad(oldloc,1,1) not in (''"'',"''",'' '') then oldloc=quote(cats(oldloc));'; put 'call symputx(''oldloc'',oldloc,''l'');'; put 'run;'; put '%if %length(&oldloc)>0 %then %do;'; put 'proc printto log=log;'; put 'run;'; put 'data _null_;'; put 'infile &oldloc;'; put 'input; putlog _infile_;'; put 'run;'; put '%end;'; put '%if %sysfunc(exist(&dc_libref..mpe_requests)) and %mf_getplatform() ne SASVIYA'; put '%then %do;'; put 'data ;'; put 'if 0 then set &dc_libref..mpe_requests;'; put 'request_dttm=%sysfunc(datetime());'; put 'request_user="%mf_getuser()";'; put 'request_service="%scan(&_program,-2,/)/%scan(&_program,-1,/)";'; put 'request_params='''';'; put 'output;stop;'; put 'proc append base=&dc_libref..mpe_requests data=&syslast force nowarn;'; put 'run;'; put '%end;'; put '%mend mpeterm;'; put '%macro mm_getrepos('; put 'outds=work.mm_getrepos'; put ')/*/STORE SOURCE*/;'; put '* use a temporary fileref to hold the response;'; put 'filename response temp;'; put '/* get list of libraries */'; put 'proc metadata in='; put '"1"'; put 'out=response;'; put 'run;'; put '/* write the response to the log for debugging */'; put '/*'; put 'data _null_;'; put 'infile response lrecl=1048576;'; put 'input;'; put 'put _infile_;'; put 'run;'; put '*/'; put '/* create an XML map to read the response */'; put 'filename sxlemap temp;'; put 'data _null_;'; put 'file sxlemap;'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository";'; put 'put "";'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Id";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Name";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Desc";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@DefaultNS";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@RepositoryType";'; put 'put "characterstring20";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@RepositoryFormat";'; put 'put "characterstring10";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@Access";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@CurrentAccess";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@PauseState";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Path";'; put 'put "";'; put 'put "characterstring256";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Engine";'; put 'put "";'; put 'put "characterstring8";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Options";'; put 'put "";'; put 'put "characterstring32";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@MetadataCreated";'; put 'put "characterstring24";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@MetadataUpdated";'; put 'put "characterstring24";'; put 'put '''';'; put 'put ''
'';'; put 'run;'; put 'libname _XML_ xml xmlfileref=response xmlmap=sxlemap;'; put 'proc sort data= _XML_.SASRepos out=&outds;'; put 'by name;'; put 'run;'; put '/* clear references */'; put 'filename sxlemap clear;'; put 'filename response clear;'; put 'libname _XML_ clear;'; put '%mend mm_getrepos;'; put '* SAS Macros end;'; put '* SAS Includes start;'; put '* SAS Includes end;'; put '* Binary Files start;'; put '* Binary Files end;'; put '* ServiceInit start;'; put 'options noquotelenmax ps=max;'; put '/* create dummy macros to prevent stpbegin / stpend from corrupting sessions */'; put '%macro stpbegin();'; put '%put NOTE: the STPBEGIN macro should not be used for web apps!;'; put '%mend stpbegin;'; put '%macro stpend();'; put '%put NOTE: the STPEND macro should not be used for web apps!;'; put '%mend stpend;'; put '* ServiceInit end;'; put '* Service start;'; put '/**'; put '@file metarepos.sas'; put '@brief Retrieves list of metadata types'; put '@details'; put '

SAS Macros

'; put '@li mm_getrepos.sas'; put '@version 9.3'; put '@author 4GL Apps Ltd'; put '@copyright 4GL Apps Ltd. This code may only be used within Data Controller'; put 'and may not be re-distributed or re-sold without the express permission of'; put '4GL Apps Ltd.'; put '**/'; put '%mpeinit()'; put '%mm_getrepos(outds=work.repos)'; put 'data outrepos;'; put 'set repos;'; put 'if repositorytype in (''CUSTOM'',''FOUNDATION'');'; put 'keep id name description;'; put 'run;'; put '%webout(OPEN)'; put '%webout(OBJ,outrepos)'; put '%webout(CLOSE)'; put '%mpeterm()'; put '* Service end;'; run; %mm_createwebservice(path=&appLoc/&path, name=&service, code=sascode, server=&serverName, replace=yes) filename sascode clear; %let service=metatypes; filename sascode temp lrecl=32767; data _null_; file sascode; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '/**'; put '@file mp_jsonout.sas'; put '@brief Writes JSON in SASjs format to a fileref'; put '@details This macro can be used to OPEN a JSON stream and send one or more'; put 'tables as arrays of rows, where each row can be an object or a nested array.'; put 'There are two engines available - DATASTEP or PROCJSON.'; put 'PROC JSON is fast but will produce errs like the ones below if'; put 'special chars are encountered.'; put '> (ERR)OR: Some code points did not transcode.'; put '> An object or array close is not valid at this point in the JSON text.'; put '> Date value out of range'; put 'If this happens, try running with ENGINE=DATASTEP.'; put 'The DATASTEP engine is used to handle special SAS missing numerics, and'; put 'can also convert entire datasets to formatted values. Output JSON is always'; put 'in UTF-8.'; put 'Usage:'; put 'filename tmp temp;'; put 'data class; set sashelp.class;run;'; put '%mp_jsonout(OPEN,jref=tmp)'; put '%mp_jsonout(OBJ,class,jref=tmp)'; put '%mp_jsonout(OBJ,class,dslabel=class2,jref=tmp,showmeta=Y)'; put '%mp_jsonout(CLOSE,jref=tmp)'; put 'data _null_;'; put 'infile tmp;'; put 'input;putlog _infile_;'; put 'run;'; put 'If you are building web apps with SAS then you are strongly encouraged to use'; put 'the mX_createwebservice macros in combination with the'; put '[sasjs adapter](https://github.com/sasjs/adapter).'; put 'For more information see https://sasjs.io'; put '@param [in] action Valid values:'; put '@li OPEN - opens the JSON'; put '@li OBJ - sends a table with each row as an object'; put '@li ARR - sends a table with each row in an array'; put '@li CLOSE - closes the JSON'; put '@param [in] ds The dataset to send. Must be a work table.'; put '@param [out] jref= (_webout) The fileref to which to send the JSON'; put '@param [out] dslabel= The name to give the table in the exported JSON'; put '@param [in] fmt= (Y) Whether to keep (Y) or strip (N) formats from the table'; put '@param [in] engine= (DATASTEP) Which engine to use to send the JSON. Options:'; put '@li PROCJSON (default)'; put '@li DATASTEP (more reliable when data has non standard characters)'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to JSON'; put '

Related Files

'; put '@li mp_ds2fmtds.sas'; put '@version 9.2'; put '@author Allan Bowe'; put '@source https://github.com/sasjs/core'; put '**/'; put '%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y'; put ',engine=DATASTEP'; put ',missing=NULL'; put ',showmeta=N'; put ',maxobs=MAX'; put ')/*/STORE SOURCE*/;'; put '%local tempds colinfo fmtds i numcols numobs stmt_obs lastobs optval'; put 'tmpds1 tmpds2 tmpds3 tmpds4;'; put '%let numcols=0;'; put '%if &maxobs ne MAX %then %let stmt_obs=%str(if _n_>&maxobs then stop;);'; put '%if &action=OPEN %then %do;'; put 'options nobomfile;'; put 'data _null_;file &jref encoding=''utf-8'' lrecl=200;'; put 'put ''{"PROCESSED_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''"'';'; put 'run;'; put '%end;'; put '%else %if (&action=ARR or &action=OBJ) %then %do;'; put '/* force variable names to always be uppercase in the JSON */'; put 'options validvarname=upcase;'; put '/* To avoid issues with _webout on EBI - such as encoding diffs and truncation'; put '(https://support.sas.com/kb/49/325.html) we use temporary files */'; put 'filename _sjs1 temp lrecl=200 ;'; put 'data _null_; file _sjs1 encoding=''utf-8'';'; put 'put ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":";'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjs1 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs1 clear;'; put '/* grab col defs */'; put 'proc contents noprint data=&ds'; put 'out=_data_(keep=name type length format formatl formatd varnum label);'; put 'run;'; put '%let colinfo=%scan(&syslast,2,.);'; put 'proc sort data=&colinfo;'; put 'by varnum;'; put 'run;'; put '/* move meta to mac vars */'; put 'data &colinfo;'; put 'if _n_=1 then call symputx(''numcols'',nobs,''l'');'; put 'set &colinfo end=last nobs=nobs;'; put 'name=upcase(name);'; put '/* fix formats */'; put 'if type=2 or type=6 then do;'; put 'typelong=''char'';'; put 'length fmt $49.;'; put 'if format='''' then fmt=cats(''$'',length,''.'');'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else fmt=cats(format,formatl,''.'');'; put 'end;'; put 'else do;'; put 'typelong=''num'';'; put 'if format='''' then fmt=''best.'';'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else if formatd=0 then fmt=cats(format,formatl,''.'');'; put 'else fmt=cats(format,formatl,''.'',formatd);'; put 'end;'; put '/* 32 char unique name */'; put 'newname=''sasjs''!!substr(cats(put(md5(name),$hex32.)),1,27);'; put 'call symputx(cats(''name'',_n_),name,''l'');'; put 'call symputx(cats(''newname'',_n_),newname,''l'');'; put 'call symputx(cats(''length'',_n_),length,''l'');'; put 'call symputx(cats(''fmt'',_n_),fmt,''l'');'; put 'call symputx(cats(''type'',_n_),type,''l'');'; put 'call symputx(cats(''typelong'',_n_),typelong,''l'');'; put 'call symputx(cats(''label'',_n_),coalescec(label,name),''l'');'; put '/* overwritten when fmt=Y and a custom format exists in catalog */'; put 'if typelong=''num'' then call symputx(cats(''fmtlen'',_n_),200,''l'');'; put 'else call symputx(cats(''fmtlen'',_n_),min(32767,ceil((length+10)*1.5)),''l'');'; put 'run;'; put '%let tempds=%substr(_%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql;'; put 'select count(*) into: lastobs from &ds;'; put '%if &maxobs ne MAX %then %let lastobs=%sysfunc(min(&lastobs,&maxobs));'; put '%if &engine=PROCJSON %then %do;'; put '%if &missing=STRING %then %do;'; put '%put &sysmacroname: Special Missings not supported in proc json.;'; put '%put &sysmacroname: Switching to DATASTEP engine;'; put '%goto datastep;'; put '%end;'; put 'data &tempds;'; put 'set &ds;'; put '&stmt_obs;'; put '%if &fmt=N %then format _numeric_ best32.;;'; put '/* PRETTY is necessary to avoid line truncation in large files */'; put 'filename _sjs2 temp lrecl=131068 encoding=''utf-8'';'; put 'proc json out=_sjs2 pretty'; put '%if &action=ARR %then nokeys ;'; put ';export &tempds / nosastags fmtnumeric;'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs2 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs2 clear;'; put '%end;'; put '%else %if &engine=DATASTEP %then %do;'; put '%datastep:'; put '%if %sysfunc(exist(&ds)) ne 1 & %sysfunc(exist(&ds,VIEW)) ne 1'; put '%then %do;'; put '%put &sysmacroname: &ds NOT FOUND!!!;'; put '%return;'; put '%end;'; put '%if &fmt=Y %then %do;'; put '/**'; put '* Extract format definitions'; put '* First, by getting library locations from dictionary.formats'; put '* Then, by exporting the width using proc format'; put '* Cannot use maxw from sashelp.vformat as not always populated'; put '* Cannot use fmtinfo() as not supported in all flavours'; put '*/'; put '%let tmpds1=%substr(fmtsum%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds2=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds3=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds4=%substr(col%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql noprint;'; put 'create table &tmpds1 as'; put 'select cats(libname,''.'',memname) as FMTCAT,'; put 'FMTNAME'; put 'from dictionary.formats'; put 'where fmttype=''F'' and libname is not null'; put 'and fmtname in (select format from &colinfo where format is not null)'; put 'order by 1;'; put 'create table &tmpds2('; put 'FMTNAME char(32),'; put 'LENGTH num'; put ');'; put '%local catlist cat fmtlist i;'; put 'select distinct fmtcat into: catlist separated by '' '' from &tmpds1;'; put '%do i=1 %to %sysfunc(countw(&catlist,%str( )));'; put '%let cat=%scan(&catlist,&i,%str( ));'; put 'proc sql;'; put 'select distinct fmtname into: fmtlist separated by '' '''; put 'from &tmpds1 where fmtcat="&cat";'; put 'proc format lib=&cat cntlout=&tmpds3(keep=fmtname length);'; put 'select &fmtlist;'; put 'run;'; put 'proc sql;'; put 'insert into &tmpds2 select distinct fmtname,length from &tmpds3;'; put '%end;'; put 'proc sql;'; put 'create table &tmpds4 as'; put 'select a.*, b.length as MAXW'; put 'from &colinfo a'; put 'left join &tmpds2 b'; put 'on cats(a.format)=cats(upcase(b.fmtname))'; put 'order by a.varnum;'; put 'data _null_;'; put 'set &tmpds4;'; put 'if not missing(maxw);'; put 'call symputx('; put 'cats(''fmtlen'',_n_),'; put '/* vars need extra padding due to JSON escaping of special chars */'; put 'min(32767,ceil((max(length,maxw)+10)*1.5))'; put ',''l'''; put ');'; put 'run;'; put '/* configure varlenchk - as we are explicitly shortening the variables */'; put '%let optval=%sysfunc(getoption(varlenchk));'; put 'options varlenchk=NOWARN;'; put 'data _data_(compress=char);'; put '/* shorten the new vars */'; put 'length'; put '%do i=1 %to &numcols;'; put '&&name&i $&&fmtlen&i'; put '%end;'; put ';'; put '/* rename on entry */'; put 'set &ds(rename=('; put '%do i=1 %to &numcols;'; put '&&name&i=&&newname&i'; put '%end;'; put '));'; put '&stmt_obs;'; put 'drop'; put '%do i=1 %to &numcols;'; put '&&newname&i'; put '%end;'; put ';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=num %then %do;'; put '&&name&i=cats(put(&&newname&i,&&fmt&i));'; put '%end;'; put '%else %do;'; put '&&name&i=put(&&newname&i,&&fmt&i);'; put '%end;'; put '%end;'; put 'if _error_ then do;'; put 'call symputx(''syscc'',1012);'; put 'stop;'; put 'end;'; put 'run;'; put '%let fmtds=&syslast;'; put 'options varlenchk=&optval;'; put '%end;'; put 'proc format; /* credit yabwon for special null removal */'; put 'value bart (default=40)'; put '%if &missing=NULL %then %do;'; put '._ - .z = null'; put '%end;'; put '%else %do;'; put '._ = [quote()]'; put '. = null'; put '.a - .z = [quote()]'; put '%end;'; put 'other = [best.];'; put 'data &tempds;'; put 'attrib _all_ label='''';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'length &&name&i $&&fmtlen&i...;'; put 'format &&name&i $&&fmtlen&i...;'; put '%end;'; put '%end;'; put '%if &fmt=Y %then %do;'; put 'set &fmtds;'; put '%end;'; put '%else %do;'; put 'set &ds;'; put '%end;'; put '&stmt_obs;'; put 'format _numeric_ bart.;'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'if findc(&&name&i,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put '&&name&i=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,&&name&i)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else &&name&i=quote(cats(&&name&i));'; put '%end;'; put '%end;'; put 'run;'; put 'filename _sjs3 temp lrecl=131068 ;'; put 'data _null_;'; put 'file _sjs3 encoding=''utf-8'';'; put 'if _n_=1 then put "[";'; put 'set &tempds;'; put 'if _n_>1 then put "," @; put'; put '%if &action=ARR %then "[" ; %else "{" ;'; put '%do i=1 %to &numcols;'; put '%if &i>1 %then "," ;'; put '%if &action=OBJ %then """&&name&i"":" ;'; put '"&&name&i"n /* name literal for reserved variable names */'; put '%end;'; put '%if &action=ARR %then "]" ; %else "}" ; ;'; put '/* close out the table */'; put 'data _null_;'; put 'file _sjs3 mod encoding=''utf-8'';'; put 'put '']'';'; put 'run;'; put 'data _null_;'; put 'infile _sjs3 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs3 clear;'; put '%end;'; put 'proc sql;'; put 'drop table &colinfo, &tempds;'; put '%if %substr(&showmeta,1,1)=Y %then %do;'; put 'filename _sjs4 temp lrecl=131068 encoding=''utf-8'';'; put 'data _null_;'; put 'file _sjs4;'; put 'length label $350;'; put 'put ", ""$%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":{""vars"":{";'; put 'do i=1 to &numcols;'; put 'name=quote(trim(symget(cats(''name'',i))));'; put 'format=quote(trim(symget(cats(''fmt'',i))));'; put 'label=quote(prxchange(''s/\\/\\\\/'',-1,trim(symget(cats(''label'',i)))));'; put 'length=quote(trim(symget(cats(''length'',i))));'; put 'type=quote(trim(symget(cats(''typelong'',i))));'; put 'if i>1 then put "," @@;'; put 'put name '':{"format":'' format '',"label":'' label'; put ''',"length":'' length '',"type":'' type ''}'';'; put 'end;'; put 'put ''}}'';'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs4 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs4 clear;'; put '%end;'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put 'data _null_; file &jref encoding=''utf-8'' mod ;'; put 'put "}";'; put 'run;'; put '%end;'; put '%mend mp_jsonout;'; put '/**'; put '@file mm_webout.sas'; put '@brief Send data to/from SAS Stored Processes'; put '@details This macro should be added to the start of each Stored Process,'; put '**immediately** followed by a call to:'; put '%mm_webout(FETCH)'; put 'This will read all the input data and create same-named SAS datasets in the'; put 'WORK library. You can then insert your code, and send data back using the'; put 'following syntax:'; put 'data some datasets; * make some data ;'; put 'retain some columns;'; put 'run;'; put '%mm_webout(OPEN)'; put '%mm_webout(ARR,some) * Array format, fast, suitable for large tables ;'; put '%mm_webout(OBJ,datasets) * Object format, easier to work with ;'; put 'Finally, wrap everything up send some helpful system variables too'; put '%mm_webout(CLOSE)'; put '@param [in] action Either FETCH, OPEN, ARR, OBJ or CLOSE'; put '@param [in] ds The dataset to send back to the frontend'; put '@param [out] dslabel= Value to use instead of table name for sending to JSON'; put '@param [in] fmt= (N) Setting Y converts all vars to their formatted values'; put '@param [out] fref= (_webout) The fileref to which to write the JSON'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] workobs= (0) When set to a positive integer, will create a new'; put 'output object (WORK) which contains this number of observations from all'; put 'tables in the WORK library.'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to output JSON'; put '

SAS Macros

'; put '@li mp_jsonout.sas'; put '

Related Macros

'; put '@li ms_webout.sas'; put '@li mv_webout.sas'; put '@version 9.3'; put '@author Allan Bowe'; put '**/'; put '%macro mm_webout(action,ds,dslabel=,fref=_webout,fmt=N,missing=NULL'; put ',showmeta=N,maxobs=MAX,workobs=0'; put ');'; put '%global _webin_file_count _webin_fileref1 _webin_name1 _program _debug'; put 'sasjs_tables;'; put '%local i tempds jsonengine;'; put '/* see https://github.com/sasjs/core/issues/41 */'; put '%if "%upcase(&SYSENCODING)" ne "UTF-8" %then %let jsonengine=PROCJSON;'; put '%else %let jsonengine=DATASTEP;'; put '%if &action=FETCH %then %do;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'options mprint notes mprintnest;'; put '%end;'; put '%let _webin_file_count=%eval(&_webin_file_count+0);'; put '/* now read in the data */'; put '%do i=1 %to &_webin_file_count;'; put '%if &_webin_file_count=1 %then %do;'; put '%let _webin_fileref1=&_webin_fileref;'; put '%let _webin_name1=&_webin_name;'; put '%end;'; put 'data _null_;'; put 'infile &&_webin_fileref&i termstr=crlf;'; put 'input;'; put 'call symputx(''input_statement'',_infile_);'; put 'putlog "&&_webin_name&i input statement: " _infile_;'; put 'stop;'; put 'data &&_webin_name&i;'; put 'infile &&_webin_fileref&i firstobs=2 dsd termstr=crlf encoding=''utf-8'';'; put 'input &input_statement;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'if _n_<20 then putlog _infile_;'; put '%end;'; put 'run;'; put '%let sasjs_tables=&sasjs_tables &&_webin_name&i;'; put '%end;'; put '%end;'; put '%else %if &action=OPEN %then %do;'; put '/* fix encoding */'; put 'OPTIONS NOBOMFILE;'; put '/**'; put '* check xengine type to avoid the below err message:'; put '* > Function is only valid for filerefs using the CACHE access method.'; put '*/'; put 'data _null_;'; put 'set sashelp.vextfl(where=(fileref="_WEBOUT"));'; put 'if xengine=''STREAM'' then do;'; put 'rc=stpsrv_header(''Content-type'',"text/html; encoding=utf-8");'; put 'end;'; put 'run;'; put '/* setup json */'; put 'data _null_;file &fref encoding=''utf-8'';'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutBEGIN<<'';'; put '%end;'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'run;'; put '%end;'; put '%else %if &action=ARR or &action=OBJ %then %do;'; put '%mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt,jref=&fref'; put ',engine=&jsonengine,missing=&missing,showmeta=&showmeta,maxobs=&maxobs'; put ')'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put '/* To avoid issues with _webout on EBI we use a temporary file */'; put 'filename _sjsref temp lrecl=131068;'; put '%if %str(&workobs) > 0 %then %do;'; put '/* if debug mode, send back first XX records of each work table also */'; put 'data;run;%let tempds=%scan(&syslast,2,.);'; put 'ods output Members=&tempds;'; put 'proc datasets library=WORK memtype=data;'; put '%local wtcnt;%let wtcnt=0;'; put 'data _null_;'; put 'set &tempds;'; put 'if not (upcase(name) =:"DATA"); /* ignore temp datasets */'; put 'i+1;'; put 'call symputx(cats(''wt'',i),name,''l'');'; put 'call symputx(''wtcnt'',i,''l'');'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put ",""WORK"":{";'; put '%do i=1 %to &wtcnt;'; put '%let wt=&&wt&i;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'dsid=open("WORK.&wt",''is'');'; put 'nlobs=attrn(dsid,''NLOBS'');'; put 'nvars=attrn(dsid,''NVARS'');'; put 'rc=close(dsid);'; put 'if &i>1 then put '',''@;'; put 'put " ""&wt"" : {";'; put 'put ''"nlobs":'' nlobs;'; put 'put '',"nvars":'' nvars;'; put '%mp_jsonout(OBJ,&wt,jref=_sjsref,dslabel=first10rows,showmeta=Y'; put ',maxobs=&workobs'; put ')'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put '%end;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put 'run;'; put '%end;'; put '/* close off json */'; put 'data _null_;file _sjsref mod encoding=''utf-8'';'; put 'length SYSPROCESSNAME syserrortext syswarningtext autoexec $512;'; put 'put ",""_DEBUG"" : ""&_debug"" ";'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'autoexec=quote(urlencode(trim(getoption(''autoexec''))));'; put 'put '',"AUTOEXEC" : '' autoexec;'; put 'put ",""MF_GETUSER"" : ""%mf_getuser()"" ";'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'put ",""SYSENCODING"" : ""&sysencoding"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSPROCESSID"" : ""&SYSPROCESSID"" ";'; put 'put ",""SYSPROCESSMODE"" : ""&SYSPROCESSMODE"" ";'; put 'SYSPROCESSNAME=quote(urlencode(cats(SYSPROCESSNAME)));'; put 'put ",""SYSPROCESSNAME"" : " SYSPROCESSNAME;'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put '',"SYSWARNINGTEXT" : '' syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'length memsize $32;'; put 'memsize="%sysfunc(INPUTN(%sysfunc(getoption(memsize)), best.),sizekmg.)";'; put 'memsize=quote(cats(memsize));'; put 'put '',"MEMSIZE" : '' memsize;'; put 'put "}" @;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutEND<<'';'; put '%end;'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjsref lrecl=1 recfm=n;'; put 'file &fref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjsref clear;'; put '%end;'; put '%mend mm_webout;'; put '%macro webout(action,ds,dslabel=,fmt=,missing=NULL,showmeta=NO,maxobs=MAX);'; put '%mm_webout(&action,ds=&ds,dslabel=&dslabel,fmt=&fmt'; put ',missing=&missing'; put ',showmeta=&showmeta'; put ',maxobs=&maxobs'; put ') %mend;'; put '/* provide additional debug info */'; put '%global _program;'; put '%put &=syscc;'; put '%put user=%mf_getuser();'; put '%put pgm=&_program;'; put '%put timestamp=%sysfunc(datetime(),datetime19.);'; put '* Service Variables start;'; put '* Service Variables end;'; put '* SAS Macros start;'; put '%macro mf_getapploc(pgm);'; put '%if "&pgm"="" %then %do;'; put '%if %symexist(_program) %then %let pgm=&_program;'; put '%else %do;'; put '%put &sysmacroname: No value provided and no _program variable available;'; put '%return;'; put '%end;'; put '%end;'; put '%local root;'; put '/**'; put '* First check we are not in the tests/macros folder (which has no subfolders)'; put '* or specifically in the testsetup or testteardown services'; put '*/'; put '%if %index(&pgm,/tests/macros/)'; put 'or %index(&pgm,/tests/testsetup)'; put 'or %index(&pgm,/tests/testteardown)'; put '%then %do;'; put '%let root=%substr(&pgm,1,%index(&pgm,/tests)-1);'; put '&root'; put '%return;'; put '%end;'; put '/**'; put '* Next, move up two levels to avoid matches on subfolder or service name'; put '*/'; put '%let root=%substr(&pgm,1,%length(&pgm)-%length(%scan(&pgm,-1,/))-1);'; put '%let root=%substr(&root,1,%length(&root)-%length(%scan(&root,-1,/))-1);'; put '%if %index(&root,/tests/) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/tests/)-1);'; put '%end;'; put '%else %if %index(&root,/services) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/services)-1);'; put '%end;'; put '%else %if %index(&root,/jobs) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/jobs)-1);'; put '%end;'; put '%else %put &sysmacroname: Could not find an app location from &pgm;'; put '&root'; put '%mend mf_getapploc ;'; put '%macro mf_getuniquefileref(prefix=_,maxtries=1000,lrecl=32767);'; put '%local rc fname;'; put '%if &prefix=0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%end;'; put '%else %do;'; put '%local x len;'; put '%let len=%eval(8-%length(&prefix));'; put '%let x=0;'; put '%do x=0 %to &maxtries;'; put '%let fname=&prefix%substr(%sysfunc(ranuni(0)),3,&len);'; put '%if %sysfunc(fileref(&fname)) > 0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%return;'; put '%end;'; put '%end;'; put '%put unable to find available fileref after &maxtries attempts;'; put '%end;'; put '%mend mf_getuniquefileref;'; put '%macro mp_abort(mac=mp_abort.sas, type=, msg=, iftrue=%str(1=1)'; put ', errds=work.mp_abort_errds'; put ', mode=REGULAR'; put ')/*/STORE SOURCE*/;'; put '%global sysprocessmode sysprocessname sasjs_stpsrv_header_loc sasjsprocessmode;'; put '%local fref fid i;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%put NOTE: /// mp_abort macro executing //;'; put '%if %length(&mac)>0 %then %put NOTE- called by &mac;'; put '%put NOTE - &msg;'; put '%if %symexist(_SYSINCLUDEFILEDEVICE)'; put '/* abort cancel FILE does not restart outside the INCLUDE on Viya 3.5 */'; put 'and %superq(SYSPROCESSNAME) ne %str(Compute Server)'; put '%then %do;'; put '%if "*&_SYSINCLUDEFILEDEVICE*" ne "**" %then %do;'; put 'data &errds;'; put 'iftrue=''1=1'';'; put 'length mac $100 msg $5000;'; put 'mac=symget(''mac'');'; put 'msg=symget(''msg'');'; put 'run;'; put 'data _null_;'; put 'abort cancel FILE;'; put 'run;'; put '%return;'; put '%end;'; put '%end;'; put '/* Web App Context */'; put '%if %symexist(_PROGRAM)'; put 'or %superq(SYSPROCESSNAME) = %str(Compute Server)'; put 'or &mode=INCLUDE'; put '%then %do;'; put 'options obs=max replace mprint;'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5"'; put '%then %do;'; put 'options nosyntaxcheck;'; put '%end;'; put '%if &mode=INCLUDE %then %do;'; put '%if %sysfunc(exist(&errds))=1 %then %do;'; put 'data _null_;'; put 'set &errds;'; put 'call symputx(''iftrue'',iftrue,''l'');'; put 'call symputx(''mac'',mac,''l'');'; put 'call symputx(''msg'',msg,''l'');'; put 'putlog (_all_)(=);'; put 'run;'; put '%if (&iftrue)=0 %then %return;'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: No include errors found;'; put '%return;'; put '%end;'; put '%end;'; put '/* extract log errs / warns, if exist */'; put '%local logloc logline;'; put '%global logmsg; /* capture global messages */'; put '%if %symexist(SYSPRINTTOLOG) %then %let logloc=&SYSPRINTTOLOG;'; put '%else %let logloc=%qsysfunc(getoption(LOG));'; put 'proc printto log=log;run;'; put '%let logline=0;'; put '%if %length(&logloc)>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input; putlog _infile_;'; put 'i=1;'; put 'retain logonce 0;'; put 'if ('; put '_infile_=:"%str(WARN)ING" or _infile_=:"%str(ERR)OR"'; put ') and logonce=0 then'; put 'do;'; put 'call symputx(''logline'',_n_);'; put 'logonce+1;'; put 'end;'; put 'run;'; put '/* capture log including lines BEFORE the err */'; put '%if &logline>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input;'; put 'i=1;'; put 'stoploop=0;'; put 'if _n_ ge &logline-15 and stoploop=0 then do until (i>22);'; put 'call symputx(''logmsg'',catx(''\n'',symget(''logmsg''),_infile_));'; put 'input;'; put 'i+1;'; put 'stoploop=1;'; put 'end;'; put 'if stoploop=1 then stop;'; put 'run;'; put '%end;'; put '%end;'; put '%if %symexist(SYS_JES_JOB_URI) %then %do;'; put '/* setup webout for Viya */'; put 'options nobomfile;'; put '%if "X&SYS_JES_JOB_URI.X"="XX" %then %do;'; put 'filename _webout temp lrecl=999999 mod;'; put '%end;'; put '%else %do;'; put 'filename _webout filesrvc parenturi="&SYS_JES_JOB_URI"'; put 'name="_webout.json" lrecl=999999 mod;'; put '%end;'; put '%end;'; put '%else %if %sysfunc(filename(fref,&sasjs_stpsrv_header_loc))=0 %then %do;'; put 'options nobomfile;'; put '/* set up http header for SASjs Server */'; put '%let fid=%sysfunc(fopen(&fref,A));'; put '%if &fid=0 %then %do;'; put '%put %str(ERR)OR: %sysfunc(sysmsg());'; put '%return;'; put '%end;'; put '%let rc=%sysfunc(fput(&fid,%str(Content-Type: application/json)));'; put '%let rc=%sysfunc(fwrite(&fid));'; put '%let rc=%sysfunc(fclose(&fid));'; put '%let rc=%sysfunc(filename(&fref));'; put '%end;'; put '/* send response in SASjs JSON format */'; put 'data _null_;'; put 'file _webout mod lrecl=32000 encoding=''utf-8'';'; put 'length msg syswarningtext syserrortext $32767 mode $10 ;'; put 'sasdatetime=datetime();'; put 'msg=symget(''msg'');'; put '%if &logline>0 %then %do;'; put 'msg=cats(msg,''\n\nLog Extract:\n'',symget(''logmsg''));'; put '%end;'; put '/* escape the escapes */'; put 'msg=tranwrd(msg,''\'',''\\'');'; put '/* escape the quotes */'; put 'msg=tranwrd(msg,''"'',''\"'');'; put '/* ditch the CRLFs as chrome complains */'; put 'msg=compress(msg,,''kw'');'; put '/* quote without quoting the quotes (which are escaped instead) */'; put 'msg=cats(''"'',msg,''"'');'; put 'if symexist(''_debug'') then debug=quote(trim(symget(''_debug'')));'; put 'else debug=''""'';'; put 'if symget(''sasjsprocessmode'')=''Stored Program'' then mode=''SASJS'';'; put 'if mode ne ''SASJS'' then put ''>>weboutBEGIN<<'';'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'put '',"sasjsAbort" : [{'';'; put 'put '' "MSG":'' msg ;'; put 'put '' ,"MAC": "'' "&mac" ''"}]'';'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'put '',"_DEBUG":'' debug ;'; put 'if symexist(''_metauser'') then do;'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put 'end;'; put 'if symexist(''SYS_JES_JOB_URI'') then do;'; put 'SYS_JES_JOB_URI=quote(trim(symget(''SYS_JES_JOB_URI'')));'; put 'put '',"SYS_JES_JOB_URI": '' SYS_JES_JOB_URI;'; put 'end;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put ",""SYSWARNINGTEXT"" : " syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'put "}" ;'; put 'if mode ne ''SASJS'' then put ''>>weboutEND<<'';'; put 'run;'; put '%put _all_;'; put '%if "&sysprocessmode " = "SAS Stored Process Server " %then %do;'; put 'data _null_;'; put 'putlog ''stpsrvset program err and syscc'';'; put 'rc=stpsrvset(''program error'', 0);'; put 'call symputx("syscc",0,"g");'; put 'run;'; put '%if &sysscp=WIN'; put 'and 1=0 /* deprecating this logic until we figure out a consistent abort */'; put 'and "%substr(%str(&sysvlong ),1,8)"="9.04.01M"'; put 'and "%substr(%str(&sysvlong ),9,1)">"5" %then %do;'; put '/* skip approach (below) does not work in windows m6+ envs */'; put 'endsas;'; put '%end;'; put '%else %do;'; put '/**'; put '* endsas kills 9.4m3 deployments by orphaning multibridges.'; put '* Abort variants are ungraceful (non zero return code)'; put '* This approach lets SAS run silently until the end :-)'; put '* Caution - fails when called within a %include within a macro'; put '* Use mp_include() to handle this.'; put '*/'; put 'filename skip temp;'; put 'data _null_;'; put 'file skip;'; put 'put ''%macro skip();'';'; put 'comment ''%mend skip; -> fix lint '';'; put 'put ''%macro skippy();'';'; put 'comment ''%mend skippy; -> fix lint '';'; put 'run;'; put '%inc skip;'; put '%end;'; put '%end;'; put '%else %if "&sysprocessmode " = "SAS Compute Server " %then %do;'; put '/* endsas kills the session making it harder to fetch results */'; put 'data _null_;'; put 'syswarningtext=symget(''syswarningtext'');'; put 'syserrortext=symget(''syserrortext'');'; put 'abort_msg=symget(''msg'');'; put 'syscc=symget(''syscc'');'; put 'sysuserid=symget(''sysuserid'');'; put 'iftrue=symget(''iftrue'');'; put 'put (_all_)(/=);'; put 'call symputx(''syscc'',0);'; put 'abort cancel nolist;'; put 'run;'; put '%end;'; put '%else %do;'; put '%abort cancel;'; put '%end;'; put '%end;'; put '%else %do;'; put '%put _all_;'; put '%abort cancel;'; put '%end;'; put '%mend mp_abort;'; put '/** @endcond */'; put '%macro mm_getstpcode('; put 'tree=/User Folders/sasdemo/somestp'; put ',name='; put ',outloc=0'; put ',outref=0'; put ',mDebug=1'; put ',showlog=NO'; put ');'; put '%local mD;'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing &sysmacroname..sas;'; put '%&mD.put _local_;'; put '%if %length(&name)>0 %then %let name=/&name;'; put '/* first, check if STP exists */'; put '%local tsuri;'; put '%let tsuri=stopifempty ;'; put 'data _null_;'; put 'format type uri tsuri value $200.;'; put 'call missing (of _all_);'; put 'path="&tree&name(StoredProcess)";'; put '/* first, find the STP ID */'; put 'if metadata_pathobj("",path,"StoredProcess",type,uri)>0 then do;'; put '/* get sourcecode */'; put 'cnt=1;'; put 'do while (metadata_getnasn(uri,"Notes",cnt,tsuri)>0);'; put 'rc=metadata_getattr(tsuri,"Name",value);'; put '&mD.put tsuri= value=;'; put 'if value="SourceCode" then do;'; put '/* found it! */'; put 'rc=metadata_getattr(tsuri,"Id",value);'; put 'call symputx(''tsuri'',value,''l'');'; put 'stop;'; put 'end;'; put 'cnt+1;'; put 'end;'; put 'end;'; put 'else put (_all_)(=);'; put 'run;'; put '%mp_abort(iftrue= (&tsuri=stopifempty)'; put ',mac=mm_getstpcode'; put ',msg=%str(&tree&name.(StoredProcess) not found!)'; put ')'; put '/**'; put '* Now we can extract the textstore'; put '*/'; put 'filename __getdoc temp lrecl=10000000;'; put 'proc metadata'; put 'in="$METAREPOSITORY'; put ''; put 'SAS1"'; put 'out=__getdoc ;'; put 'run;'; put '/* find the beginning of the text */'; put '%local start;'; put 'data _null_;'; put 'infile __getdoc lrecl=10000;'; put 'input;'; put 'start=index(_infile_,''StoredText="'');'; put 'if start then do;'; put 'call symputx("start",start+11);'; put '*putlog ''"'' _infile_ ''"'';'; put 'end;'; put 'stop;'; put '%local outeng;'; put '%if "&outloc"="0" %then %let outeng=TEMP;'; put '%else %let outeng="&outloc";'; put '%local fref;'; put '%if &outref=0 %then %let fref=%mf_getuniquefileref();'; put '%else %let fref=&outref;'; put '/* read the content, byte by byte, resolving escaped chars */'; put 'filename &fref &outeng lrecl=100000;'; put 'data _null_;'; put 'length filein 8 fileid 8;'; put 'filein = fopen("__getdoc","I",1,"B");'; put 'fileid = fopen("&fref","O",1,"B");'; put 'rec = "20"x;'; put 'length entity $6;'; put 'do while(fread(filein)=0);'; put 'x+1;'; put 'if x>&start then do;'; put 'rc = fget(filein,rec,1);'; put 'if rec=''"'' then leave;'; put 'else if rec="&" then do;'; put 'entity=rec;'; put 'do until (rec=";");'; put 'if fread(filein) ne 0 then goto getout;'; put 'rc = fget(filein,rec,1);'; put 'entity=cats(entity,rec);'; put 'end;'; put 'select (entity);'; put 'when (''&'' ) rec=''&'' ;'; put 'when (''<'' ) rec=''<'' ;'; put 'when (''>'' ) rec=''>'' ;'; put 'when (''''') rec="''" ;'; put 'when (''"'') rec=''"'' ;'; put 'when ('' '') rec=''0A''x;'; put 'when ('' '') rec=''0D''x;'; put 'when (''$'' ) rec=''$'' ;'; put 'when ('' '') rec=''09''x;'; put 'otherwise putlog "%str(WARN)ING: missing value for " entity=;'; put 'end;'; put 'rc =fput(fileid, substr(rec,1,1));'; put 'rc =fwrite(fileid);'; put 'end;'; put 'else do;'; put 'rc =fput(fileid,rec);'; put 'rc =fwrite(fileid);'; put 'end;'; put 'end;'; put 'end;'; put 'getout:'; put 'rc=fclose(filein);'; put 'rc=fclose(fileid);'; put 'run;'; put '%if &showlog=YES %then %do;'; put 'data _null_;'; put 'infile &fref lrecl=32767 end=last;'; put 'input;'; put 'if _n_=1 then putlog ''>>stpcodeBEGIN<<'';'; put 'putlog _infile_;'; put 'if last then putlog ''>>stpcodeEND<<'';'; put 'run;'; put '%end;'; put 'filename __getdoc clear;'; put '%if &outref=0 %then %do;'; put 'filename &fref clear;'; put '%end;'; put '%mend mm_getstpcode;'; put '%macro dc_getsettings();'; put '%global _program;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(syscc=&syscc on entry (&syswarningtext &syserrortext))'; put ')'; put '%if %length(&_PROGRAM)>1 %then %let root=&_program;'; put '%else %do;'; put '%global _metauser;'; put '%let _metauser=&sysuserid;'; put '/* to mimic a "real" _program we need to give a dummy role and stp name */'; put '%let root=/dummyRole/dummyName;'; put '%end;'; put '/* the DC precode is stored in the Admin folder in the root of'; put 'the project. Lets find that root. */'; put '%put &=root;'; put '%let root=%mf_getapploc();'; put '%put &=root;'; put '/* Now we know the root location we can retrieve the params */'; put '%let temploc=%sysfunc(pathname(work))/settings.txt;'; put '%mm_getstpcode(tree=&root/services/public'; put ',name=Data_Controller_Settings'; put ',outloc=&temploc'; put ')'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Unable to run getstpcode)'; put ')'; put 'filename _getsets "&temploc" lrecl=2000;'; put '/*'; put 'Do not use mp_include here - this puts a copy in every service, which creates'; put 'compilation problems when calling services from mp_include'; put '*/'; put '%inc _getsets/source2;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Problem running &sysmacroname (&syswarningtext &syserrortext))'; put ')'; put '%mend dc_getsettings;'; put '%macro mf_fmtdttm('; put ')/*/STORE SOURCE*/;'; put '%if "&sysver"="9.2" or "&sysver"="9.3"'; put 'or ("&sysver"="9.4" and "%substr(&SYSVLONG,9,1)" le "3")'; put 'or "%substr(&sysver,1,1)"="4"'; put 'or "%substr(&sysver,1,1)"="5"'; put '%then %do;DATETIME19.3%end;'; put '%else %do;E8601DT26.6%end;'; put '%mend mf_fmtdttm;'; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '%macro mp_init(prefix=SASJS'; put ')/*/STORE SOURCE*/;'; put '%if %symexist(SASJS_PREFIX) %then %return; /* only run once */'; put '%global'; put 'SASJS_PREFIX /* the ONLY hard-coded global macro variable in SASjs */'; put '&prefix._FUNCTIONS /* used in mcf_init() to track core function compilation */'; put '&prefix._INIT_NUM /* initialisation time as numeric */'; put '&prefix._INIT_DTTM /* initialisation time in E8601DT26.6 format */'; put '&prefix.WORK /* avoid typing %sysfunc(pathname(work)) every time */'; put ';'; put '%let sasjs_prefix=&prefix;'; put 'data _null_;'; put 'dttm=datetime();'; put 'call symputx("&sasjs_prefix._init_num",dttm,''g'');'; put 'call symputx("&sasjs_prefix._init_dttm",put(dttm,E8601DT26.6),''g'');'; put 'call symputx("&sasjs_prefix.work",pathname(''WORK''),''g'');'; put 'run;'; put 'options'; put 'compress=CHAR /* default is none so ensure we have something! */'; put 'datastmtchk=ALLKEYWORDS /* protection from overwriting input datasets */'; put 'errorcheck=STRICT /* catch errs in libname/filename statements */'; put 'fmterr /* ensure err when a format cannot be found */'; put 'mergenoby=%str(ERR)OR /* throw err when a merge has no BY variables */'; put 'missing=. /* changing this can cause hard to detect errs */'; put 'noquotelenmax /* avoid warnings for long strings */'; put 'noreplace /* avoid overwriting permanent datasets */'; put 'ps=max /* reduce log size slightly */'; put 'ls=max /* reduce log even more and avoid word truncation */'; put 'validmemname=COMPATIBLE /* avoid special characters etc in table names */'; put 'validvarname=V7 /* avoid special characters etc in variable names */'; put 'varinitchk=%str(ERR)OR /* avoid data mistakes from variable name typos */'; put 'varlenchk=%str(ERR)OR /* fail hard if truncation (data loss) can result */'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5" %then %do;'; put 'noautocorrect /* disallow misspelled procedure names */'; put 'dsoptions=note2err /* undocumented - convert bad NOTEs to ERRs */'; put '%end;'; put ';'; put '%mend mp_init;'; put '%macro mpeinit(fetch=YES);'; put '%global mpeinit'; put 'mpeadmins /* group with unrestricted Meditor access */'; put 'mpelocapprovals /* location for landing and staging files */'; put 'mpelib /* location of configuration tables for DC */'; put 'dc_repo_users /* location of user / group metadata */'; put 'dc_licence_key /* extracted in dc_getsettings */'; put 'dc_activation_key /* extracted in dc_getsettings */'; put 'dc_locale /* extracted in dc_getsettings */'; put 'dc_dttmtfmt /* can be overridden in dc_getsettings */'; put '_debug'; put ';'; put '%if &mpeinit=1 %then %return;'; put '%else %let mpeinit=1;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(Problem on service startup (&syswarningtext &syserrortext))'; put ')'; put '%mp_init()'; put '%if &fetch=YES %then %do;'; put '%webout(FETCH)'; put '%end;'; put '%global _CLIENTNAME;'; put '%mp_abort(iftrue= (&_CLIENTNAME=SAS Enterprise Guide)'; put ',mac=&_program..sas'; put ',msg=%str(Data Controller is a web app and should not be executed from EG)'; put ')'; put 'options urlencoding=utf8 nobomfile lrecl=32767;'; put '%let perf=%sysfunc(datetime());'; put '%put perfdiff: 0;'; put '%let dc_locale=SYSTEM; /* default if not set */'; put '/**'; put '* E8601DT26.6 has widest database support - but not all SAS flavours can'; put '* handle it. Override in the settings STP if needed.'; put '*/'; put 'data _null_;'; put 'dc_dttmtfmt=''"%sysfunc(datetime(),''!!"%mf_fmtdttm()"!!'')"dt'';'; put 'call symputx(''dc_dttmtfmt'',dc_dttmtfmt);'; put 'put dc_dttmtfmt=;'; put 'run;'; put '%put &=dc_dttmtfmt;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc prior to dc_getsettings)'; put ')'; put '%dc_getsettings()'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc after dc_getsettings)'; put ')'; put 'data _null_;'; put 'set &DC_LIBREF..mpe_config(where=('; put 'var_scope="DC"'; put 'and &dc_dttmtfmt lt tx_to'; put 'and var_active=1'; put '));'; put 'call symputx(var_name,var_value,''G'');'; put 'putlog var_name "=" var_value;'; put 'run;'; put '%let mpelib=&dc_libref;'; put '%let mpeadmins=&dc_admin_group;'; put '%let mpelocapprovals=&dc_staging_area;'; put '%let dc_repo_users=&dc_repo_users;'; put '%if &dc_locale ne SYSTEM %then %do;'; put 'options locale=&dc_locale;'; put '%end;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(Problem during compilation or with STP precode (&syswarningtext))'; put ')'; put '%mend mpeinit;'; put '%macro mf_mval(var);'; put '%if %symexist(&var) %then %do;'; put '%superq(&var)'; put '%end;'; put '%mend mf_mval;'; put '%macro mf_trimstr(basestr,trimstr);'; put '%local baselen trimlen trimval;'; put '/* return if basestr is shorter than trimstr (or 0) */'; put '%let baselen=%length(%superq(basestr));'; put '%let trimlen=%length(%superq(trimstr));'; put '%if &baselen < &trimlen or &baselen=0 %then %return;'; put '/* obtain the characters from the end of basestr */'; put '%let trimval=%qsubstr(%superq(basestr)'; put ',%length(%superq(basestr))-&trimlen+1'; put ',&trimlen);'; put '/* compare and if matching, chop it off! */'; put '%if %superq(basestr)=%superq(trimstr) %then %do;'; put '%return;'; put '%end;'; put '%else %if %superq(trimval)=%superq(trimstr) %then %do;'; put '%qsubstr(%superq(basestr),1,%length(%superq(basestr))-&trimlen)'; put '%end;'; put '%else %do;'; put '&basestr'; put '%end;'; put '%mend mf_trimstr;'; put '%macro mf_getplatform(switch'; put ')/*/STORE SOURCE*/;'; put '%local a b c;'; put '%if &switch.NONE=NONE %then %do;'; put '%if %symexist(sasjsprocessmode) %then %do;'; put '%if &sasjsprocessmode=Stored Program %then %do;'; put 'SASJS'; put '%return;'; put '%end;'; put '%end;'; put '%if %symexist(sysprocessmode) %then %do;'; put '%if "&sysprocessmode"="SAS Object Server"'; put 'or "&sysprocessmode"= "SAS Compute Server" %then %do;'; put 'SASVIYA'; put '%end;'; put '%else %if "&sysprocessmode"="SAS Stored Process Server"'; put 'or "&sysprocessmode"="SAS Workspace Server"'; put '%then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if %symexist(_metaport) or %symexist(_metauser) %then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if &switch=SASSTUDIO %then %do;'; put '/* return the version of SAS Studio else 0 */'; put '%if %mf_mval(_CLIENTAPP)=%str(SAS Studio) %then %do;'; put '%let a=%mf_mval(_CLIENTVERSION);'; put '%let b=%scan(&a,1,.);'; put '%if %eval(&b >2) %then %do;'; put '&b'; put '%end;'; put '%else 0;'; put '%end;'; put '%else 0;'; put '%end;'; put '%else %if &switch=VIYARESTAPI %then %do;'; put '%mf_trimstr(%sysfunc(getoption(servicesbaseurl)),/)'; put '%end;'; put '%mend mf_getplatform;'; put '%macro mpeterm();'; put '%local oldloc;'; put 'data _null_;'; put 'if symexist(''SYSPRINTTOLOG'') then oldloc=symget(''SYSPRINTTOLOG'');'; put 'else oldloc=getoption(''LOG'');'; put 'if subpad(oldloc,1,1) not in (''"'',"''",'' '') then oldloc=quote(cats(oldloc));'; put 'call symputx(''oldloc'',oldloc,''l'');'; put 'run;'; put '%if %length(&oldloc)>0 %then %do;'; put 'proc printto log=log;'; put 'run;'; put 'data _null_;'; put 'infile &oldloc;'; put 'input; putlog _infile_;'; put 'run;'; put '%end;'; put '%if %sysfunc(exist(&dc_libref..mpe_requests)) and %mf_getplatform() ne SASVIYA'; put '%then %do;'; put 'data ;'; put 'if 0 then set &dc_libref..mpe_requests;'; put 'request_dttm=%sysfunc(datetime());'; put 'request_user="%mf_getuser()";'; put 'request_service="%scan(&_program,-2,/)/%scan(&_program,-1,/)";'; put 'request_params='''';'; put 'output;stop;'; put 'proc append base=&dc_libref..mpe_requests data=&syslast force nowarn;'; put 'run;'; put '%end;'; put '%mend mpeterm;'; put '%macro mm_gettypes('; put 'outds=work.mm_gettypes'; put ')/*/STORE SOURCE*/;'; put '* use a temporary fileref to hold the response;'; put 'filename response temp;'; put '/* get list of libraries */'; put 'proc metadata in='; put ''''; put ''; put 'SAS'; put ''; put '2048'; put ''; put ''; put '$METAREPOSITORY'; put ''; put ''''; put 'out=response;'; put 'run;'; put '/* write the response to the log for debugging */'; put 'data _null_;'; put 'infile response lrecl=1048576;'; put 'input;'; put 'put _infile_;'; put 'run;'; put '/* create an XML map to read the response */'; put 'filename sxlemap temp;'; put 'data _null_;'; put 'file sxlemap;'; put 'put '''';'; put 'put ''//GetTypes/Types/Type'';'; put 'put ''64'';'; put 'put ''//GetTypes/Types/Type/@Id'';'; put 'put ''256'';'; put 'put ''//GetTypes/Types/Type/@Desc'';'; put 'put '''';'; put 'put ''//GetTypes/Types/Type/@HasSubtypes'';'; put 'put ''
'';'; put 'run;'; put 'libname _XML_ xml xmlfileref=response xmlmap=sxlemap;'; put '/* sort the response by library name */'; put 'proc sort data=_XML_.sastypes out=&outds;'; put 'by id;'; put 'run;'; put '/* clear references */'; put 'filename sxlemap clear;'; put 'filename response clear;'; put 'libname _XML_ clear;'; put '%mend mm_gettypes;'; put '* SAS Macros end;'; put '* SAS Includes start;'; put '* SAS Includes end;'; put '* Binary Files start;'; put '* Binary Files end;'; put '* ServiceInit start;'; put 'options noquotelenmax ps=max;'; put '/* create dummy macros to prevent stpbegin / stpend from corrupting sessions */'; put '%macro stpbegin();'; put '%put NOTE: the STPBEGIN macro should not be used for web apps!;'; put '%mend stpbegin;'; put '%macro stpend();'; put '%put NOTE: the STPEND macro should not be used for web apps!;'; put '%mend stpend;'; put '* ServiceInit end;'; put '* Service start;'; put '/**'; put '@file metatypes.sas'; put '@brief Retrieves list of metadata types'; put '@details'; put '

SAS Macros

'; put '@li mm_gettypes.sas'; put '@copyright 4GL Apps Ltd. This code may only be used within Data Controller'; put 'and may not be re-distributed or re-sold without the express permission of'; put '4GL Apps Ltd.'; put '**/'; put '%mpeinit()'; put '%mm_gettypes(outds=work.types)'; put '%webout(OPEN)'; put '%webout(OBJ,types)'; put '%webout(CLOSE)'; put '%mpeterm()'; put '* Service end;'; run; %mm_createwebservice(path=&appLoc/&path, name=&service, code=sascode, server=&serverName, replace=yes) filename sascode clear; %let path=services/public; %let service=getchangeinfo; filename sascode temp lrecl=32767; data _null_; file sascode; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '/**'; put '@file mp_jsonout.sas'; put '@brief Writes JSON in SASjs format to a fileref'; put '@details This macro can be used to OPEN a JSON stream and send one or more'; put 'tables as arrays of rows, where each row can be an object or a nested array.'; put 'There are two engines available - DATASTEP or PROCJSON.'; put 'PROC JSON is fast but will produce errs like the ones below if'; put 'special chars are encountered.'; put '> (ERR)OR: Some code points did not transcode.'; put '> An object or array close is not valid at this point in the JSON text.'; put '> Date value out of range'; put 'If this happens, try running with ENGINE=DATASTEP.'; put 'The DATASTEP engine is used to handle special SAS missing numerics, and'; put 'can also convert entire datasets to formatted values. Output JSON is always'; put 'in UTF-8.'; put 'Usage:'; put 'filename tmp temp;'; put 'data class; set sashelp.class;run;'; put '%mp_jsonout(OPEN,jref=tmp)'; put '%mp_jsonout(OBJ,class,jref=tmp)'; put '%mp_jsonout(OBJ,class,dslabel=class2,jref=tmp,showmeta=Y)'; put '%mp_jsonout(CLOSE,jref=tmp)'; put 'data _null_;'; put 'infile tmp;'; put 'input;putlog _infile_;'; put 'run;'; put 'If you are building web apps with SAS then you are strongly encouraged to use'; put 'the mX_createwebservice macros in combination with the'; put '[sasjs adapter](https://github.com/sasjs/adapter).'; put 'For more information see https://sasjs.io'; put '@param [in] action Valid values:'; put '@li OPEN - opens the JSON'; put '@li OBJ - sends a table with each row as an object'; put '@li ARR - sends a table with each row in an array'; put '@li CLOSE - closes the JSON'; put '@param [in] ds The dataset to send. Must be a work table.'; put '@param [out] jref= (_webout) The fileref to which to send the JSON'; put '@param [out] dslabel= The name to give the table in the exported JSON'; put '@param [in] fmt= (Y) Whether to keep (Y) or strip (N) formats from the table'; put '@param [in] engine= (DATASTEP) Which engine to use to send the JSON. Options:'; put '@li PROCJSON (default)'; put '@li DATASTEP (more reliable when data has non standard characters)'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to JSON'; put '

Related Files

'; put '@li mp_ds2fmtds.sas'; put '@version 9.2'; put '@author Allan Bowe'; put '@source https://github.com/sasjs/core'; put '**/'; put '%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y'; put ',engine=DATASTEP'; put ',missing=NULL'; put ',showmeta=N'; put ',maxobs=MAX'; put ')/*/STORE SOURCE*/;'; put '%local tempds colinfo fmtds i numcols numobs stmt_obs lastobs optval'; put 'tmpds1 tmpds2 tmpds3 tmpds4;'; put '%let numcols=0;'; put '%if &maxobs ne MAX %then %let stmt_obs=%str(if _n_>&maxobs then stop;);'; put '%if &action=OPEN %then %do;'; put 'options nobomfile;'; put 'data _null_;file &jref encoding=''utf-8'' lrecl=200;'; put 'put ''{"PROCESSED_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''"'';'; put 'run;'; put '%end;'; put '%else %if (&action=ARR or &action=OBJ) %then %do;'; put '/* force variable names to always be uppercase in the JSON */'; put 'options validvarname=upcase;'; put '/* To avoid issues with _webout on EBI - such as encoding diffs and truncation'; put '(https://support.sas.com/kb/49/325.html) we use temporary files */'; put 'filename _sjs1 temp lrecl=200 ;'; put 'data _null_; file _sjs1 encoding=''utf-8'';'; put 'put ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":";'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjs1 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs1 clear;'; put '/* grab col defs */'; put 'proc contents noprint data=&ds'; put 'out=_data_(keep=name type length format formatl formatd varnum label);'; put 'run;'; put '%let colinfo=%scan(&syslast,2,.);'; put 'proc sort data=&colinfo;'; put 'by varnum;'; put 'run;'; put '/* move meta to mac vars */'; put 'data &colinfo;'; put 'if _n_=1 then call symputx(''numcols'',nobs,''l'');'; put 'set &colinfo end=last nobs=nobs;'; put 'name=upcase(name);'; put '/* fix formats */'; put 'if type=2 or type=6 then do;'; put 'typelong=''char'';'; put 'length fmt $49.;'; put 'if format='''' then fmt=cats(''$'',length,''.'');'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else fmt=cats(format,formatl,''.'');'; put 'end;'; put 'else do;'; put 'typelong=''num'';'; put 'if format='''' then fmt=''best.'';'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else if formatd=0 then fmt=cats(format,formatl,''.'');'; put 'else fmt=cats(format,formatl,''.'',formatd);'; put 'end;'; put '/* 32 char unique name */'; put 'newname=''sasjs''!!substr(cats(put(md5(name),$hex32.)),1,27);'; put 'call symputx(cats(''name'',_n_),name,''l'');'; put 'call symputx(cats(''newname'',_n_),newname,''l'');'; put 'call symputx(cats(''length'',_n_),length,''l'');'; put 'call symputx(cats(''fmt'',_n_),fmt,''l'');'; put 'call symputx(cats(''type'',_n_),type,''l'');'; put 'call symputx(cats(''typelong'',_n_),typelong,''l'');'; put 'call symputx(cats(''label'',_n_),coalescec(label,name),''l'');'; put '/* overwritten when fmt=Y and a custom format exists in catalog */'; put 'if typelong=''num'' then call symputx(cats(''fmtlen'',_n_),200,''l'');'; put 'else call symputx(cats(''fmtlen'',_n_),min(32767,ceil((length+10)*1.5)),''l'');'; put 'run;'; put '%let tempds=%substr(_%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql;'; put 'select count(*) into: lastobs from &ds;'; put '%if &maxobs ne MAX %then %let lastobs=%sysfunc(min(&lastobs,&maxobs));'; put '%if &engine=PROCJSON %then %do;'; put '%if &missing=STRING %then %do;'; put '%put &sysmacroname: Special Missings not supported in proc json.;'; put '%put &sysmacroname: Switching to DATASTEP engine;'; put '%goto datastep;'; put '%end;'; put 'data &tempds;'; put 'set &ds;'; put '&stmt_obs;'; put '%if &fmt=N %then format _numeric_ best32.;;'; put '/* PRETTY is necessary to avoid line truncation in large files */'; put 'filename _sjs2 temp lrecl=131068 encoding=''utf-8'';'; put 'proc json out=_sjs2 pretty'; put '%if &action=ARR %then nokeys ;'; put ';export &tempds / nosastags fmtnumeric;'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs2 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs2 clear;'; put '%end;'; put '%else %if &engine=DATASTEP %then %do;'; put '%datastep:'; put '%if %sysfunc(exist(&ds)) ne 1 & %sysfunc(exist(&ds,VIEW)) ne 1'; put '%then %do;'; put '%put &sysmacroname: &ds NOT FOUND!!!;'; put '%return;'; put '%end;'; put '%if &fmt=Y %then %do;'; put '/**'; put '* Extract format definitions'; put '* First, by getting library locations from dictionary.formats'; put '* Then, by exporting the width using proc format'; put '* Cannot use maxw from sashelp.vformat as not always populated'; put '* Cannot use fmtinfo() as not supported in all flavours'; put '*/'; put '%let tmpds1=%substr(fmtsum%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds2=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds3=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds4=%substr(col%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql noprint;'; put 'create table &tmpds1 as'; put 'select cats(libname,''.'',memname) as FMTCAT,'; put 'FMTNAME'; put 'from dictionary.formats'; put 'where fmttype=''F'' and libname is not null'; put 'and fmtname in (select format from &colinfo where format is not null)'; put 'order by 1;'; put 'create table &tmpds2('; put 'FMTNAME char(32),'; put 'LENGTH num'; put ');'; put '%local catlist cat fmtlist i;'; put 'select distinct fmtcat into: catlist separated by '' '' from &tmpds1;'; put '%do i=1 %to %sysfunc(countw(&catlist,%str( )));'; put '%let cat=%scan(&catlist,&i,%str( ));'; put 'proc sql;'; put 'select distinct fmtname into: fmtlist separated by '' '''; put 'from &tmpds1 where fmtcat="&cat";'; put 'proc format lib=&cat cntlout=&tmpds3(keep=fmtname length);'; put 'select &fmtlist;'; put 'run;'; put 'proc sql;'; put 'insert into &tmpds2 select distinct fmtname,length from &tmpds3;'; put '%end;'; put 'proc sql;'; put 'create table &tmpds4 as'; put 'select a.*, b.length as MAXW'; put 'from &colinfo a'; put 'left join &tmpds2 b'; put 'on cats(a.format)=cats(upcase(b.fmtname))'; put 'order by a.varnum;'; put 'data _null_;'; put 'set &tmpds4;'; put 'if not missing(maxw);'; put 'call symputx('; put 'cats(''fmtlen'',_n_),'; put '/* vars need extra padding due to JSON escaping of special chars */'; put 'min(32767,ceil((max(length,maxw)+10)*1.5))'; put ',''l'''; put ');'; put 'run;'; put '/* configure varlenchk - as we are explicitly shortening the variables */'; put '%let optval=%sysfunc(getoption(varlenchk));'; put 'options varlenchk=NOWARN;'; put 'data _data_(compress=char);'; put '/* shorten the new vars */'; put 'length'; put '%do i=1 %to &numcols;'; put '&&name&i $&&fmtlen&i'; put '%end;'; put ';'; put '/* rename on entry */'; put 'set &ds(rename=('; put '%do i=1 %to &numcols;'; put '&&name&i=&&newname&i'; put '%end;'; put '));'; put '&stmt_obs;'; put 'drop'; put '%do i=1 %to &numcols;'; put '&&newname&i'; put '%end;'; put ';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=num %then %do;'; put '&&name&i=cats(put(&&newname&i,&&fmt&i));'; put '%end;'; put '%else %do;'; put '&&name&i=put(&&newname&i,&&fmt&i);'; put '%end;'; put '%end;'; put 'if _error_ then do;'; put 'call symputx(''syscc'',1012);'; put 'stop;'; put 'end;'; put 'run;'; put '%let fmtds=&syslast;'; put 'options varlenchk=&optval;'; put '%end;'; put 'proc format; /* credit yabwon for special null removal */'; put 'value bart (default=40)'; put '%if &missing=NULL %then %do;'; put '._ - .z = null'; put '%end;'; put '%else %do;'; put '._ = [quote()]'; put '. = null'; put '.a - .z = [quote()]'; put '%end;'; put 'other = [best.];'; put 'data &tempds;'; put 'attrib _all_ label='''';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'length &&name&i $&&fmtlen&i...;'; put 'format &&name&i $&&fmtlen&i...;'; put '%end;'; put '%end;'; put '%if &fmt=Y %then %do;'; put 'set &fmtds;'; put '%end;'; put '%else %do;'; put 'set &ds;'; put '%end;'; put '&stmt_obs;'; put 'format _numeric_ bart.;'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'if findc(&&name&i,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put '&&name&i=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,&&name&i)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else &&name&i=quote(cats(&&name&i));'; put '%end;'; put '%end;'; put 'run;'; put 'filename _sjs3 temp lrecl=131068 ;'; put 'data _null_;'; put 'file _sjs3 encoding=''utf-8'';'; put 'if _n_=1 then put "[";'; put 'set &tempds;'; put 'if _n_>1 then put "," @; put'; put '%if &action=ARR %then "[" ; %else "{" ;'; put '%do i=1 %to &numcols;'; put '%if &i>1 %then "," ;'; put '%if &action=OBJ %then """&&name&i"":" ;'; put '"&&name&i"n /* name literal for reserved variable names */'; put '%end;'; put '%if &action=ARR %then "]" ; %else "}" ; ;'; put '/* close out the table */'; put 'data _null_;'; put 'file _sjs3 mod encoding=''utf-8'';'; put 'put '']'';'; put 'run;'; put 'data _null_;'; put 'infile _sjs3 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs3 clear;'; put '%end;'; put 'proc sql;'; put 'drop table &colinfo, &tempds;'; put '%if %substr(&showmeta,1,1)=Y %then %do;'; put 'filename _sjs4 temp lrecl=131068 encoding=''utf-8'';'; put 'data _null_;'; put 'file _sjs4;'; put 'length label $350;'; put 'put ", ""$%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":{""vars"":{";'; put 'do i=1 to &numcols;'; put 'name=quote(trim(symget(cats(''name'',i))));'; put 'format=quote(trim(symget(cats(''fmt'',i))));'; put 'label=quote(prxchange(''s/\\/\\\\/'',-1,trim(symget(cats(''label'',i)))));'; put 'length=quote(trim(symget(cats(''length'',i))));'; put 'type=quote(trim(symget(cats(''typelong'',i))));'; put 'if i>1 then put "," @@;'; put 'put name '':{"format":'' format '',"label":'' label'; put ''',"length":'' length '',"type":'' type ''}'';'; put 'end;'; put 'put ''}}'';'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs4 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs4 clear;'; put '%end;'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put 'data _null_; file &jref encoding=''utf-8'' mod ;'; put 'put "}";'; put 'run;'; put '%end;'; put '%mend mp_jsonout;'; put '/**'; put '@file mm_webout.sas'; put '@brief Send data to/from SAS Stored Processes'; put '@details This macro should be added to the start of each Stored Process,'; put '**immediately** followed by a call to:'; put '%mm_webout(FETCH)'; put 'This will read all the input data and create same-named SAS datasets in the'; put 'WORK library. You can then insert your code, and send data back using the'; put 'following syntax:'; put 'data some datasets; * make some data ;'; put 'retain some columns;'; put 'run;'; put '%mm_webout(OPEN)'; put '%mm_webout(ARR,some) * Array format, fast, suitable for large tables ;'; put '%mm_webout(OBJ,datasets) * Object format, easier to work with ;'; put 'Finally, wrap everything up send some helpful system variables too'; put '%mm_webout(CLOSE)'; put '@param [in] action Either FETCH, OPEN, ARR, OBJ or CLOSE'; put '@param [in] ds The dataset to send back to the frontend'; put '@param [out] dslabel= Value to use instead of table name for sending to JSON'; put '@param [in] fmt= (N) Setting Y converts all vars to their formatted values'; put '@param [out] fref= (_webout) The fileref to which to write the JSON'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] workobs= (0) When set to a positive integer, will create a new'; put 'output object (WORK) which contains this number of observations from all'; put 'tables in the WORK library.'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to output JSON'; put '

SAS Macros

'; put '@li mp_jsonout.sas'; put '

Related Macros

'; put '@li ms_webout.sas'; put '@li mv_webout.sas'; put '@version 9.3'; put '@author Allan Bowe'; put '**/'; put '%macro mm_webout(action,ds,dslabel=,fref=_webout,fmt=N,missing=NULL'; put ',showmeta=N,maxobs=MAX,workobs=0'; put ');'; put '%global _webin_file_count _webin_fileref1 _webin_name1 _program _debug'; put 'sasjs_tables;'; put '%local i tempds jsonengine;'; put '/* see https://github.com/sasjs/core/issues/41 */'; put '%if "%upcase(&SYSENCODING)" ne "UTF-8" %then %let jsonengine=PROCJSON;'; put '%else %let jsonengine=DATASTEP;'; put '%if &action=FETCH %then %do;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'options mprint notes mprintnest;'; put '%end;'; put '%let _webin_file_count=%eval(&_webin_file_count+0);'; put '/* now read in the data */'; put '%do i=1 %to &_webin_file_count;'; put '%if &_webin_file_count=1 %then %do;'; put '%let _webin_fileref1=&_webin_fileref;'; put '%let _webin_name1=&_webin_name;'; put '%end;'; put 'data _null_;'; put 'infile &&_webin_fileref&i termstr=crlf;'; put 'input;'; put 'call symputx(''input_statement'',_infile_);'; put 'putlog "&&_webin_name&i input statement: " _infile_;'; put 'stop;'; put 'data &&_webin_name&i;'; put 'infile &&_webin_fileref&i firstobs=2 dsd termstr=crlf encoding=''utf-8'';'; put 'input &input_statement;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'if _n_<20 then putlog _infile_;'; put '%end;'; put 'run;'; put '%let sasjs_tables=&sasjs_tables &&_webin_name&i;'; put '%end;'; put '%end;'; put '%else %if &action=OPEN %then %do;'; put '/* fix encoding */'; put 'OPTIONS NOBOMFILE;'; put '/**'; put '* check xengine type to avoid the below err message:'; put '* > Function is only valid for filerefs using the CACHE access method.'; put '*/'; put 'data _null_;'; put 'set sashelp.vextfl(where=(fileref="_WEBOUT"));'; put 'if xengine=''STREAM'' then do;'; put 'rc=stpsrv_header(''Content-type'',"text/html; encoding=utf-8");'; put 'end;'; put 'run;'; put '/* setup json */'; put 'data _null_;file &fref encoding=''utf-8'';'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutBEGIN<<'';'; put '%end;'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'run;'; put '%end;'; put '%else %if &action=ARR or &action=OBJ %then %do;'; put '%mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt,jref=&fref'; put ',engine=&jsonengine,missing=&missing,showmeta=&showmeta,maxobs=&maxobs'; put ')'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put '/* To avoid issues with _webout on EBI we use a temporary file */'; put 'filename _sjsref temp lrecl=131068;'; put '%if %str(&workobs) > 0 %then %do;'; put '/* if debug mode, send back first XX records of each work table also */'; put 'data;run;%let tempds=%scan(&syslast,2,.);'; put 'ods output Members=&tempds;'; put 'proc datasets library=WORK memtype=data;'; put '%local wtcnt;%let wtcnt=0;'; put 'data _null_;'; put 'set &tempds;'; put 'if not (upcase(name) =:"DATA"); /* ignore temp datasets */'; put 'i+1;'; put 'call symputx(cats(''wt'',i),name,''l'');'; put 'call symputx(''wtcnt'',i,''l'');'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put ",""WORK"":{";'; put '%do i=1 %to &wtcnt;'; put '%let wt=&&wt&i;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'dsid=open("WORK.&wt",''is'');'; put 'nlobs=attrn(dsid,''NLOBS'');'; put 'nvars=attrn(dsid,''NVARS'');'; put 'rc=close(dsid);'; put 'if &i>1 then put '',''@;'; put 'put " ""&wt"" : {";'; put 'put ''"nlobs":'' nlobs;'; put 'put '',"nvars":'' nvars;'; put '%mp_jsonout(OBJ,&wt,jref=_sjsref,dslabel=first10rows,showmeta=Y'; put ',maxobs=&workobs'; put ')'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put '%end;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put 'run;'; put '%end;'; put '/* close off json */'; put 'data _null_;file _sjsref mod encoding=''utf-8'';'; put 'length SYSPROCESSNAME syserrortext syswarningtext autoexec $512;'; put 'put ",""_DEBUG"" : ""&_debug"" ";'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'autoexec=quote(urlencode(trim(getoption(''autoexec''))));'; put 'put '',"AUTOEXEC" : '' autoexec;'; put 'put ",""MF_GETUSER"" : ""%mf_getuser()"" ";'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'put ",""SYSENCODING"" : ""&sysencoding"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSPROCESSID"" : ""&SYSPROCESSID"" ";'; put 'put ",""SYSPROCESSMODE"" : ""&SYSPROCESSMODE"" ";'; put 'SYSPROCESSNAME=quote(urlencode(cats(SYSPROCESSNAME)));'; put 'put ",""SYSPROCESSNAME"" : " SYSPROCESSNAME;'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put '',"SYSWARNINGTEXT" : '' syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'length memsize $32;'; put 'memsize="%sysfunc(INPUTN(%sysfunc(getoption(memsize)), best.),sizekmg.)";'; put 'memsize=quote(cats(memsize));'; put 'put '',"MEMSIZE" : '' memsize;'; put 'put "}" @;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutEND<<'';'; put '%end;'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjsref lrecl=1 recfm=n;'; put 'file &fref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjsref clear;'; put '%end;'; put '%mend mm_webout;'; put '%macro webout(action,ds,dslabel=,fmt=,missing=NULL,showmeta=NO,maxobs=MAX);'; put '%mm_webout(&action,ds=&ds,dslabel=&dslabel,fmt=&fmt'; put ',missing=&missing'; put ',showmeta=&showmeta'; put ',maxobs=&maxobs'; put ') %mend;'; put '/* provide additional debug info */'; put '%global _program;'; put '%put &=syscc;'; put '%put user=%mf_getuser();'; put '%put pgm=&_program;'; put '%put timestamp=%sysfunc(datetime(),datetime19.);'; put '* Service Variables start;'; put '* Service Variables end;'; put '* SAS Macros start;'; put '%macro mf_getapploc(pgm);'; put '%if "&pgm"="" %then %do;'; put '%if %symexist(_program) %then %let pgm=&_program;'; put '%else %do;'; put '%put &sysmacroname: No value provided and no _program variable available;'; put '%return;'; put '%end;'; put '%end;'; put '%local root;'; put '/**'; put '* First check we are not in the tests/macros folder (which has no subfolders)'; put '* or specifically in the testsetup or testteardown services'; put '*/'; put '%if %index(&pgm,/tests/macros/)'; put 'or %index(&pgm,/tests/testsetup)'; put 'or %index(&pgm,/tests/testteardown)'; put '%then %do;'; put '%let root=%substr(&pgm,1,%index(&pgm,/tests)-1);'; put '&root'; put '%return;'; put '%end;'; put '/**'; put '* Next, move up two levels to avoid matches on subfolder or service name'; put '*/'; put '%let root=%substr(&pgm,1,%length(&pgm)-%length(%scan(&pgm,-1,/))-1);'; put '%let root=%substr(&root,1,%length(&root)-%length(%scan(&root,-1,/))-1);'; put '%if %index(&root,/tests/) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/tests/)-1);'; put '%end;'; put '%else %if %index(&root,/services) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/services)-1);'; put '%end;'; put '%else %if %index(&root,/jobs) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/jobs)-1);'; put '%end;'; put '%else %put &sysmacroname: Could not find an app location from &pgm;'; put '&root'; put '%mend mf_getapploc ;'; put '%macro mf_getuniquefileref(prefix=_,maxtries=1000,lrecl=32767);'; put '%local rc fname;'; put '%if &prefix=0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%end;'; put '%else %do;'; put '%local x len;'; put '%let len=%eval(8-%length(&prefix));'; put '%let x=0;'; put '%do x=0 %to &maxtries;'; put '%let fname=&prefix%substr(%sysfunc(ranuni(0)),3,&len);'; put '%if %sysfunc(fileref(&fname)) > 0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%return;'; put '%end;'; put '%end;'; put '%put unable to find available fileref after &maxtries attempts;'; put '%end;'; put '%mend mf_getuniquefileref;'; put '%macro mp_abort(mac=mp_abort.sas, type=, msg=, iftrue=%str(1=1)'; put ', errds=work.mp_abort_errds'; put ', mode=REGULAR'; put ')/*/STORE SOURCE*/;'; put '%global sysprocessmode sysprocessname sasjs_stpsrv_header_loc sasjsprocessmode;'; put '%local fref fid i;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%put NOTE: /// mp_abort macro executing //;'; put '%if %length(&mac)>0 %then %put NOTE- called by &mac;'; put '%put NOTE - &msg;'; put '%if %symexist(_SYSINCLUDEFILEDEVICE)'; put '/* abort cancel FILE does not restart outside the INCLUDE on Viya 3.5 */'; put 'and %superq(SYSPROCESSNAME) ne %str(Compute Server)'; put '%then %do;'; put '%if "*&_SYSINCLUDEFILEDEVICE*" ne "**" %then %do;'; put 'data &errds;'; put 'iftrue=''1=1'';'; put 'length mac $100 msg $5000;'; put 'mac=symget(''mac'');'; put 'msg=symget(''msg'');'; put 'run;'; put 'data _null_;'; put 'abort cancel FILE;'; put 'run;'; put '%return;'; put '%end;'; put '%end;'; put '/* Web App Context */'; put '%if %symexist(_PROGRAM)'; put 'or %superq(SYSPROCESSNAME) = %str(Compute Server)'; put 'or &mode=INCLUDE'; put '%then %do;'; put 'options obs=max replace mprint;'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5"'; put '%then %do;'; put 'options nosyntaxcheck;'; put '%end;'; put '%if &mode=INCLUDE %then %do;'; put '%if %sysfunc(exist(&errds))=1 %then %do;'; put 'data _null_;'; put 'set &errds;'; put 'call symputx(''iftrue'',iftrue,''l'');'; put 'call symputx(''mac'',mac,''l'');'; put 'call symputx(''msg'',msg,''l'');'; put 'putlog (_all_)(=);'; put 'run;'; put '%if (&iftrue)=0 %then %return;'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: No include errors found;'; put '%return;'; put '%end;'; put '%end;'; put '/* extract log errs / warns, if exist */'; put '%local logloc logline;'; put '%global logmsg; /* capture global messages */'; put '%if %symexist(SYSPRINTTOLOG) %then %let logloc=&SYSPRINTTOLOG;'; put '%else %let logloc=%qsysfunc(getoption(LOG));'; put 'proc printto log=log;run;'; put '%let logline=0;'; put '%if %length(&logloc)>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input; putlog _infile_;'; put 'i=1;'; put 'retain logonce 0;'; put 'if ('; put '_infile_=:"%str(WARN)ING" or _infile_=:"%str(ERR)OR"'; put ') and logonce=0 then'; put 'do;'; put 'call symputx(''logline'',_n_);'; put 'logonce+1;'; put 'end;'; put 'run;'; put '/* capture log including lines BEFORE the err */'; put '%if &logline>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input;'; put 'i=1;'; put 'stoploop=0;'; put 'if _n_ ge &logline-15 and stoploop=0 then do until (i>22);'; put 'call symputx(''logmsg'',catx(''\n'',symget(''logmsg''),_infile_));'; put 'input;'; put 'i+1;'; put 'stoploop=1;'; put 'end;'; put 'if stoploop=1 then stop;'; put 'run;'; put '%end;'; put '%end;'; put '%if %symexist(SYS_JES_JOB_URI) %then %do;'; put '/* setup webout for Viya */'; put 'options nobomfile;'; put '%if "X&SYS_JES_JOB_URI.X"="XX" %then %do;'; put 'filename _webout temp lrecl=999999 mod;'; put '%end;'; put '%else %do;'; put 'filename _webout filesrvc parenturi="&SYS_JES_JOB_URI"'; put 'name="_webout.json" lrecl=999999 mod;'; put '%end;'; put '%end;'; put '%else %if %sysfunc(filename(fref,&sasjs_stpsrv_header_loc))=0 %then %do;'; put 'options nobomfile;'; put '/* set up http header for SASjs Server */'; put '%let fid=%sysfunc(fopen(&fref,A));'; put '%if &fid=0 %then %do;'; put '%put %str(ERR)OR: %sysfunc(sysmsg());'; put '%return;'; put '%end;'; put '%let rc=%sysfunc(fput(&fid,%str(Content-Type: application/json)));'; put '%let rc=%sysfunc(fwrite(&fid));'; put '%let rc=%sysfunc(fclose(&fid));'; put '%let rc=%sysfunc(filename(&fref));'; put '%end;'; put '/* send response in SASjs JSON format */'; put 'data _null_;'; put 'file _webout mod lrecl=32000 encoding=''utf-8'';'; put 'length msg syswarningtext syserrortext $32767 mode $10 ;'; put 'sasdatetime=datetime();'; put 'msg=symget(''msg'');'; put '%if &logline>0 %then %do;'; put 'msg=cats(msg,''\n\nLog Extract:\n'',symget(''logmsg''));'; put '%end;'; put '/* escape the escapes */'; put 'msg=tranwrd(msg,''\'',''\\'');'; put '/* escape the quotes */'; put 'msg=tranwrd(msg,''"'',''\"'');'; put '/* ditch the CRLFs as chrome complains */'; put 'msg=compress(msg,,''kw'');'; put '/* quote without quoting the quotes (which are escaped instead) */'; put 'msg=cats(''"'',msg,''"'');'; put 'if symexist(''_debug'') then debug=quote(trim(symget(''_debug'')));'; put 'else debug=''""'';'; put 'if symget(''sasjsprocessmode'')=''Stored Program'' then mode=''SASJS'';'; put 'if mode ne ''SASJS'' then put ''>>weboutBEGIN<<'';'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'put '',"sasjsAbort" : [{'';'; put 'put '' "MSG":'' msg ;'; put 'put '' ,"MAC": "'' "&mac" ''"}]'';'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'put '',"_DEBUG":'' debug ;'; put 'if symexist(''_metauser'') then do;'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put 'end;'; put 'if symexist(''SYS_JES_JOB_URI'') then do;'; put 'SYS_JES_JOB_URI=quote(trim(symget(''SYS_JES_JOB_URI'')));'; put 'put '',"SYS_JES_JOB_URI": '' SYS_JES_JOB_URI;'; put 'end;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put ",""SYSWARNINGTEXT"" : " syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'put "}" ;'; put 'if mode ne ''SASJS'' then put ''>>weboutEND<<'';'; put 'run;'; put '%put _all_;'; put '%if "&sysprocessmode " = "SAS Stored Process Server " %then %do;'; put 'data _null_;'; put 'putlog ''stpsrvset program err and syscc'';'; put 'rc=stpsrvset(''program error'', 0);'; put 'call symputx("syscc",0,"g");'; put 'run;'; put '%if &sysscp=WIN'; put 'and 1=0 /* deprecating this logic until we figure out a consistent abort */'; put 'and "%substr(%str(&sysvlong ),1,8)"="9.04.01M"'; put 'and "%substr(%str(&sysvlong ),9,1)">"5" %then %do;'; put '/* skip approach (below) does not work in windows m6+ envs */'; put 'endsas;'; put '%end;'; put '%else %do;'; put '/**'; put '* endsas kills 9.4m3 deployments by orphaning multibridges.'; put '* Abort variants are ungraceful (non zero return code)'; put '* This approach lets SAS run silently until the end :-)'; put '* Caution - fails when called within a %include within a macro'; put '* Use mp_include() to handle this.'; put '*/'; put 'filename skip temp;'; put 'data _null_;'; put 'file skip;'; put 'put ''%macro skip();'';'; put 'comment ''%mend skip; -> fix lint '';'; put 'put ''%macro skippy();'';'; put 'comment ''%mend skippy; -> fix lint '';'; put 'run;'; put '%inc skip;'; put '%end;'; put '%end;'; put '%else %if "&sysprocessmode " = "SAS Compute Server " %then %do;'; put '/* endsas kills the session making it harder to fetch results */'; put 'data _null_;'; put 'syswarningtext=symget(''syswarningtext'');'; put 'syserrortext=symget(''syserrortext'');'; put 'abort_msg=symget(''msg'');'; put 'syscc=symget(''syscc'');'; put 'sysuserid=symget(''sysuserid'');'; put 'iftrue=symget(''iftrue'');'; put 'put (_all_)(/=);'; put 'call symputx(''syscc'',0);'; put 'abort cancel nolist;'; put 'run;'; put '%end;'; put '%else %do;'; put '%abort cancel;'; put '%end;'; put '%end;'; put '%else %do;'; put '%put _all_;'; put '%abort cancel;'; put '%end;'; put '%mend mp_abort;'; put '/** @endcond */'; put '%macro mm_getstpcode('; put 'tree=/User Folders/sasdemo/somestp'; put ',name='; put ',outloc=0'; put ',outref=0'; put ',mDebug=1'; put ',showlog=NO'; put ');'; put '%local mD;'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing &sysmacroname..sas;'; put '%&mD.put _local_;'; put '%if %length(&name)>0 %then %let name=/&name;'; put '/* first, check if STP exists */'; put '%local tsuri;'; put '%let tsuri=stopifempty ;'; put 'data _null_;'; put 'format type uri tsuri value $200.;'; put 'call missing (of _all_);'; put 'path="&tree&name(StoredProcess)";'; put '/* first, find the STP ID */'; put 'if metadata_pathobj("",path,"StoredProcess",type,uri)>0 then do;'; put '/* get sourcecode */'; put 'cnt=1;'; put 'do while (metadata_getnasn(uri,"Notes",cnt,tsuri)>0);'; put 'rc=metadata_getattr(tsuri,"Name",value);'; put '&mD.put tsuri= value=;'; put 'if value="SourceCode" then do;'; put '/* found it! */'; put 'rc=metadata_getattr(tsuri,"Id",value);'; put 'call symputx(''tsuri'',value,''l'');'; put 'stop;'; put 'end;'; put 'cnt+1;'; put 'end;'; put 'end;'; put 'else put (_all_)(=);'; put 'run;'; put '%mp_abort(iftrue= (&tsuri=stopifempty)'; put ',mac=mm_getstpcode'; put ',msg=%str(&tree&name.(StoredProcess) not found!)'; put ')'; put '/**'; put '* Now we can extract the textstore'; put '*/'; put 'filename __getdoc temp lrecl=10000000;'; put 'proc metadata'; put 'in="$METAREPOSITORY'; put ''; put 'SAS1"'; put 'out=__getdoc ;'; put 'run;'; put '/* find the beginning of the text */'; put '%local start;'; put 'data _null_;'; put 'infile __getdoc lrecl=10000;'; put 'input;'; put 'start=index(_infile_,''StoredText="'');'; put 'if start then do;'; put 'call symputx("start",start+11);'; put '*putlog ''"'' _infile_ ''"'';'; put 'end;'; put 'stop;'; put '%local outeng;'; put '%if "&outloc"="0" %then %let outeng=TEMP;'; put '%else %let outeng="&outloc";'; put '%local fref;'; put '%if &outref=0 %then %let fref=%mf_getuniquefileref();'; put '%else %let fref=&outref;'; put '/* read the content, byte by byte, resolving escaped chars */'; put 'filename &fref &outeng lrecl=100000;'; put 'data _null_;'; put 'length filein 8 fileid 8;'; put 'filein = fopen("__getdoc","I",1,"B");'; put 'fileid = fopen("&fref","O",1,"B");'; put 'rec = "20"x;'; put 'length entity $6;'; put 'do while(fread(filein)=0);'; put 'x+1;'; put 'if x>&start then do;'; put 'rc = fget(filein,rec,1);'; put 'if rec=''"'' then leave;'; put 'else if rec="&" then do;'; put 'entity=rec;'; put 'do until (rec=";");'; put 'if fread(filein) ne 0 then goto getout;'; put 'rc = fget(filein,rec,1);'; put 'entity=cats(entity,rec);'; put 'end;'; put 'select (entity);'; put 'when (''&'' ) rec=''&'' ;'; put 'when (''<'' ) rec=''<'' ;'; put 'when (''>'' ) rec=''>'' ;'; put 'when (''''') rec="''" ;'; put 'when (''"'') rec=''"'' ;'; put 'when ('' '') rec=''0A''x;'; put 'when ('' '') rec=''0D''x;'; put 'when (''$'' ) rec=''$'' ;'; put 'when ('' '') rec=''09''x;'; put 'otherwise putlog "%str(WARN)ING: missing value for " entity=;'; put 'end;'; put 'rc =fput(fileid, substr(rec,1,1));'; put 'rc =fwrite(fileid);'; put 'end;'; put 'else do;'; put 'rc =fput(fileid,rec);'; put 'rc =fwrite(fileid);'; put 'end;'; put 'end;'; put 'end;'; put 'getout:'; put 'rc=fclose(filein);'; put 'rc=fclose(fileid);'; put 'run;'; put '%if &showlog=YES %then %do;'; put 'data _null_;'; put 'infile &fref lrecl=32767 end=last;'; put 'input;'; put 'if _n_=1 then putlog ''>>stpcodeBEGIN<<'';'; put 'putlog _infile_;'; put 'if last then putlog ''>>stpcodeEND<<'';'; put 'run;'; put '%end;'; put 'filename __getdoc clear;'; put '%if &outref=0 %then %do;'; put 'filename &fref clear;'; put '%end;'; put '%mend mm_getstpcode;'; put '%macro dc_getsettings();'; put '%global _program;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(syscc=&syscc on entry (&syswarningtext &syserrortext))'; put ')'; put '%if %length(&_PROGRAM)>1 %then %let root=&_program;'; put '%else %do;'; put '%global _metauser;'; put '%let _metauser=&sysuserid;'; put '/* to mimic a "real" _program we need to give a dummy role and stp name */'; put '%let root=/dummyRole/dummyName;'; put '%end;'; put '/* the DC precode is stored in the Admin folder in the root of'; put 'the project. Lets find that root. */'; put '%put &=root;'; put '%let root=%mf_getapploc();'; put '%put &=root;'; put '/* Now we know the root location we can retrieve the params */'; put '%let temploc=%sysfunc(pathname(work))/settings.txt;'; put '%mm_getstpcode(tree=&root/services/public'; put ',name=Data_Controller_Settings'; put ',outloc=&temploc'; put ')'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Unable to run getstpcode)'; put ')'; put 'filename _getsets "&temploc" lrecl=2000;'; put '/*'; put 'Do not use mp_include here - this puts a copy in every service, which creates'; put 'compilation problems when calling services from mp_include'; put '*/'; put '%inc _getsets/source2;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Problem running &sysmacroname (&syswarningtext &syserrortext))'; put ')'; put '%mend dc_getsettings;'; put '%macro mf_fmtdttm('; put ')/*/STORE SOURCE*/;'; put '%if "&sysver"="9.2" or "&sysver"="9.3"'; put 'or ("&sysver"="9.4" and "%substr(&SYSVLONG,9,1)" le "3")'; put 'or "%substr(&sysver,1,1)"="4"'; put 'or "%substr(&sysver,1,1)"="5"'; put '%then %do;DATETIME19.3%end;'; put '%else %do;E8601DT26.6%end;'; put '%mend mf_fmtdttm;'; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '%macro mp_init(prefix=SASJS'; put ')/*/STORE SOURCE*/;'; put '%if %symexist(SASJS_PREFIX) %then %return; /* only run once */'; put '%global'; put 'SASJS_PREFIX /* the ONLY hard-coded global macro variable in SASjs */'; put '&prefix._FUNCTIONS /* used in mcf_init() to track core function compilation */'; put '&prefix._INIT_NUM /* initialisation time as numeric */'; put '&prefix._INIT_DTTM /* initialisation time in E8601DT26.6 format */'; put '&prefix.WORK /* avoid typing %sysfunc(pathname(work)) every time */'; put ';'; put '%let sasjs_prefix=&prefix;'; put 'data _null_;'; put 'dttm=datetime();'; put 'call symputx("&sasjs_prefix._init_num",dttm,''g'');'; put 'call symputx("&sasjs_prefix._init_dttm",put(dttm,E8601DT26.6),''g'');'; put 'call symputx("&sasjs_prefix.work",pathname(''WORK''),''g'');'; put 'run;'; put 'options'; put 'compress=CHAR /* default is none so ensure we have something! */'; put 'datastmtchk=ALLKEYWORDS /* protection from overwriting input datasets */'; put 'errorcheck=STRICT /* catch errs in libname/filename statements */'; put 'fmterr /* ensure err when a format cannot be found */'; put 'mergenoby=%str(ERR)OR /* throw err when a merge has no BY variables */'; put 'missing=. /* changing this can cause hard to detect errs */'; put 'noquotelenmax /* avoid warnings for long strings */'; put 'noreplace /* avoid overwriting permanent datasets */'; put 'ps=max /* reduce log size slightly */'; put 'ls=max /* reduce log even more and avoid word truncation */'; put 'validmemname=COMPATIBLE /* avoid special characters etc in table names */'; put 'validvarname=V7 /* avoid special characters etc in variable names */'; put 'varinitchk=%str(ERR)OR /* avoid data mistakes from variable name typos */'; put 'varlenchk=%str(ERR)OR /* fail hard if truncation (data loss) can result */'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5" %then %do;'; put 'noautocorrect /* disallow misspelled procedure names */'; put 'dsoptions=note2err /* undocumented - convert bad NOTEs to ERRs */'; put '%end;'; put ';'; put '%mend mp_init;'; put '%macro mpeinit(fetch=YES);'; put '%global mpeinit'; put 'mpeadmins /* group with unrestricted Meditor access */'; put 'mpelocapprovals /* location for landing and staging files */'; put 'mpelib /* location of configuration tables for DC */'; put 'dc_repo_users /* location of user / group metadata */'; put 'dc_licence_key /* extracted in dc_getsettings */'; put 'dc_activation_key /* extracted in dc_getsettings */'; put 'dc_locale /* extracted in dc_getsettings */'; put 'dc_dttmtfmt /* can be overridden in dc_getsettings */'; put '_debug'; put ';'; put '%if &mpeinit=1 %then %return;'; put '%else %let mpeinit=1;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(Problem on service startup (&syswarningtext &syserrortext))'; put ')'; put '%mp_init()'; put '%if &fetch=YES %then %do;'; put '%webout(FETCH)'; put '%end;'; put '%global _CLIENTNAME;'; put '%mp_abort(iftrue= (&_CLIENTNAME=SAS Enterprise Guide)'; put ',mac=&_program..sas'; put ',msg=%str(Data Controller is a web app and should not be executed from EG)'; put ')'; put 'options urlencoding=utf8 nobomfile lrecl=32767;'; put '%let perf=%sysfunc(datetime());'; put '%put perfdiff: 0;'; put '%let dc_locale=SYSTEM; /* default if not set */'; put '/**'; put '* E8601DT26.6 has widest database support - but not all SAS flavours can'; put '* handle it. Override in the settings STP if needed.'; put '*/'; put 'data _null_;'; put 'dc_dttmtfmt=''"%sysfunc(datetime(),''!!"%mf_fmtdttm()"!!'')"dt'';'; put 'call symputx(''dc_dttmtfmt'',dc_dttmtfmt);'; put 'put dc_dttmtfmt=;'; put 'run;'; put '%put &=dc_dttmtfmt;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc prior to dc_getsettings)'; put ')'; put '%dc_getsettings()'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc after dc_getsettings)'; put ')'; put 'data _null_;'; put 'set &DC_LIBREF..mpe_config(where=('; put 'var_scope="DC"'; put 'and &dc_dttmtfmt lt tx_to'; put 'and var_active=1'; put '));'; put 'call symputx(var_name,var_value,''G'');'; put 'putlog var_name "=" var_value;'; put 'run;'; put '%let mpelib=&dc_libref;'; put '%let mpeadmins=&dc_admin_group;'; put '%let mpelocapprovals=&dc_staging_area;'; put '%let dc_repo_users=&dc_repo_users;'; put '%if &dc_locale ne SYSTEM %then %do;'; put 'options locale=&dc_locale;'; put '%end;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(Problem during compilation or with STP precode (&syswarningtext))'; put ')'; put '%mend mpeinit;'; put '%macro mf_mval(var);'; put '%if %symexist(&var) %then %do;'; put '%superq(&var)'; put '%end;'; put '%mend mf_mval;'; put '%macro mf_trimstr(basestr,trimstr);'; put '%local baselen trimlen trimval;'; put '/* return if basestr is shorter than trimstr (or 0) */'; put '%let baselen=%length(%superq(basestr));'; put '%let trimlen=%length(%superq(trimstr));'; put '%if &baselen < &trimlen or &baselen=0 %then %return;'; put '/* obtain the characters from the end of basestr */'; put '%let trimval=%qsubstr(%superq(basestr)'; put ',%length(%superq(basestr))-&trimlen+1'; put ',&trimlen);'; put '/* compare and if matching, chop it off! */'; put '%if %superq(basestr)=%superq(trimstr) %then %do;'; put '%return;'; put '%end;'; put '%else %if %superq(trimval)=%superq(trimstr) %then %do;'; put '%qsubstr(%superq(basestr),1,%length(%superq(basestr))-&trimlen)'; put '%end;'; put '%else %do;'; put '&basestr'; put '%end;'; put '%mend mf_trimstr;'; put '%macro mf_getplatform(switch'; put ')/*/STORE SOURCE*/;'; put '%local a b c;'; put '%if &switch.NONE=NONE %then %do;'; put '%if %symexist(sasjsprocessmode) %then %do;'; put '%if &sasjsprocessmode=Stored Program %then %do;'; put 'SASJS'; put '%return;'; put '%end;'; put '%end;'; put '%if %symexist(sysprocessmode) %then %do;'; put '%if "&sysprocessmode"="SAS Object Server"'; put 'or "&sysprocessmode"= "SAS Compute Server" %then %do;'; put 'SASVIYA'; put '%end;'; put '%else %if "&sysprocessmode"="SAS Stored Process Server"'; put 'or "&sysprocessmode"="SAS Workspace Server"'; put '%then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if %symexist(_metaport) or %symexist(_metauser) %then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if &switch=SASSTUDIO %then %do;'; put '/* return the version of SAS Studio else 0 */'; put '%if %mf_mval(_CLIENTAPP)=%str(SAS Studio) %then %do;'; put '%let a=%mf_mval(_CLIENTVERSION);'; put '%let b=%scan(&a,1,.);'; put '%if %eval(&b >2) %then %do;'; put '&b'; put '%end;'; put '%else 0;'; put '%end;'; put '%else 0;'; put '%end;'; put '%else %if &switch=VIYARESTAPI %then %do;'; put '%mf_trimstr(%sysfunc(getoption(servicesbaseurl)),/)'; put '%end;'; put '%mend mf_getplatform;'; put '%macro mpeterm();'; put '%local oldloc;'; put 'data _null_;'; put 'if symexist(''SYSPRINTTOLOG'') then oldloc=symget(''SYSPRINTTOLOG'');'; put 'else oldloc=getoption(''LOG'');'; put 'if subpad(oldloc,1,1) not in (''"'',"''",'' '') then oldloc=quote(cats(oldloc));'; put 'call symputx(''oldloc'',oldloc,''l'');'; put 'run;'; put '%if %length(&oldloc)>0 %then %do;'; put 'proc printto log=log;'; put 'run;'; put 'data _null_;'; put 'infile &oldloc;'; put 'input; putlog _infile_;'; put 'run;'; put '%end;'; put '%if %sysfunc(exist(&dc_libref..mpe_requests)) and %mf_getplatform() ne SASVIYA'; put '%then %do;'; put 'data ;'; put 'if 0 then set &dc_libref..mpe_requests;'; put 'request_dttm=%sysfunc(datetime());'; put 'request_user="%mf_getuser()";'; put 'request_service="%scan(&_program,-2,/)/%scan(&_program,-1,/)";'; put 'request_params='''';'; put 'output;stop;'; put 'proc append base=&dc_libref..mpe_requests data=&syslast force nowarn;'; put 'run;'; put '%end;'; put '%mend mpeterm;'; put '%macro mm_assignlib('; put 'libref'; put ',mAbort=HARD'; put ')/*/STORE SOURCE*/;'; put '%local mp_abort msg;'; put '%let mp_abort=0;'; put '%if %sysfunc(libref(&libref)) %then %do;'; put 'data _null_;'; put 'length liburi LibName msg $200;'; put 'call missing(of _all_);'; put 'nobj=metadata_getnobj("omsobj:SASLibrary?@Libref=''&libref''",1,liburi);'; put 'if nobj=1 then do;'; put 'rc=metadata_getattr(liburi,"Name",LibName);'; put '/* now try and assign it */'; put 'if libname("&libref",,''meta'',cats(''liburi="'',liburi,''";'')) ne 0 then do;'; put 'putlog "&libref could not be assigned";'; put 'putlog liburi=;'; put '/**'; put '* Fetch the system message for display in the abort modal. This is'; put '* not always helpful though. One example, previously received:'; put '* NOTE: Libref XX refers to the same library metadata as libref XX.'; put '*/'; put 'msg=sysmsg();'; put 'if msg=:''ERROR: Libref SAVE is not assigned.'' then do;'; put 'msg=catx(" ",'; put '"Could not assign %upcase(&libref).",'; put '"Please check metadata permissions! Libname:",libname,'; put '"Liburi:",liburi'; put ');'; put 'end;'; put 'else if msg="ERROR: User does not have appropriate authorization "!!'; put '"level for library SAVE."'; put 'then do;'; put 'msg=catx(" ",'; put '"ERROR: User does not have appropriate authorization level",'; put '"for library %upcase(&libref), libname:",libname,'; put '"Liburi:",liburi'; put ');'; put 'end;'; put 'call symputx(''msg'',msg,''l'');'; put 'if "&mabort"=''HARD'' then call symputx(''mp_abort'',1,''l'');'; put 'end;'; put 'else do;'; put 'put (_all_)(=);'; put 'call symputx(''libname'',libname,''L'');'; put 'call symputx(''liburi'',liburi,''L'');'; put 'end;'; put 'end;'; put 'else if nobj>1 then do;'; put 'if "&mabort"=''HARD'' then call symputx(''mp_abort'',1);'; put 'call symputx(''msg'',"More than one library with libref=&libref");'; put 'end;'; put 'else do;'; put 'if "&mabort"=''HARD'' then call symputx(''mp_abort'',1);'; put 'call symputx(''msg'',"Library &libref not found in metadata");'; put 'end;'; put 'run;'; put '%put NOTE: &msg;'; put '%end;'; put '%else %do;'; put '%put NOTE: Library &libref is already assigned;'; put '%end;'; put '%mp_abort(iftrue= (&mp_abort=1)'; put ',mac=mm_assignlib.sas'; put ',msg=%superq(msg)'; put ')'; put '%mend mm_assignlib;'; put '/** @cond */'; put '%macro mf_getengine(libref'; put ')/*/STORE SOURCE*/;'; put '%local dsid engnum rc engine;'; put '/* in case the parameter is a libref.tablename, pull off just the libref */'; put '%let libref = %upcase(%scan(&libref, 1, %str(.)));'; put '%let dsid=%sysfunc('; put 'open(sashelp.vlibnam(where=(libname="%upcase(&libref)")),i)'; put ');'; put '%if (&dsid ^= 0) %then %do;'; put '%let engnum=%sysfunc(varnum(&dsid,ENGINE));'; put '%let rc=%sysfunc(fetch(&dsid));'; put '%let engine=%sysfunc(getvarc(&dsid,&engnum));'; put '%put &libref. ENGINE is &engine.;'; put '%let rc= %sysfunc(close(&dsid));'; put '%end;'; put '%upcase(&engine)'; put '%mend mf_getengine;'; put '/** @endcond */'; put '%macro mm_assigndirectlib('; put 'libref'; put ',open_passthrough='; put ',sql_options='; put ',mDebug=0'; put ',mAbort=0'; put ')/*/STORE SOURCE*/;'; put '%local mD;'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing mm_assigndirectlib.sas;'; put '%&mD.put _local_;'; put '%if &mAbort=1 %then %let mAbort=;'; put '%else %let mAbort=%str(*);'; put '%&mD.put NOTE: Creating direct (non META) connection to &libref library;'; put '%local cur_engine;'; put '%let cur_engine=%mf_getengine(&libref);'; put '%if &cur_engine ne META and &cur_engine ne %then %do;'; put '%put NOTE: &libref already has a direct (&cur_engine) libname connection;'; put '%return;'; put '%end;'; put '%else %if %upcase(&libref)=WORK %then %do;'; put '%put NOTE: We already have a direct connection to WORK :-) ;'; put '%return;'; put '%end;'; put '/* need to determine the library ENGINE first */'; put '%local engine;'; put 'data _null_;'; put 'length lib_uri engine $256;'; put 'call missing (of _all_);'; put '/* get URI for the particular library */'; put 'rc1=metadata_getnobj("omsobj:SASLibrary?@Libref =''&libref''",1,lib_uri);'; put '/* get the Engine attribute of the previous object */'; put 'rc2=metadata_getattr(lib_uri,''Engine'',engine);'; put 'putlog "mm_assigndirectlib for &libref:" rc1= lib_uri= rc2= engine=;'; put 'call symputx("liburi",lib_uri,''l'');'; put 'call symputx("engine",engine,''l'');'; put 'run;'; put '/* now obtain engine specific connection details */'; put '%if &engine=BASE %then %do;'; put '%&mD.put NOTE: Retrieving BASE library path;'; put 'data _null_;'; put 'length up_uri $256 path cat_path $1024;'; put 'retain cat_path;'; put 'call missing (of _all_);'; put '/* get all the filepaths of the UsingPackages association */'; put 'i=1;'; put 'rc3=metadata_getnasn("&liburi",''UsingPackages'',i,up_uri);'; put 'do while (rc3>0);'; put '/* get the DirectoryName attribute of the previous object */'; put 'rc4=metadata_getattr(up_uri,''DirectoryName'',path);'; put 'if i=1 then path = ''("''!!trim(path)!!''" '';'; put 'else path ='' "''!!trim(path)!!''" '';'; put 'cat_path = trim(cat_path) !! " " !! trim(path) ;'; put 'i+1;'; put 'rc3=metadata_getnasn("&liburi",''UsingPackages'',i,up_uri);'; put 'end;'; put 'cat_path = trim(cat_path) !! ")";'; put '&mD.putlog "NOTE: Getting physical path for &libref library";'; put '&mD.putlog rc3= up_uri= rc4= cat_path= path=;'; put '&mD.putlog "NOTE: Libname cmd will be:";'; put '&mD.putlog "libname &libref" cat_path;'; put 'call symputx("filepath",cat_path,''l'');'; put 'run;'; put '%if %sysevalf(&sysver<9.4) %then %do;'; put 'libname &libref &filepath;'; put '%end;'; put '%else %do;'; put '/* apply the new filelocks option to cater for temporary locks */'; put 'libname &libref &filepath filelockwait=5;'; put '%end;'; put '%end;'; put '%else %if &engine=REMOTE %then %do;'; put 'data x;'; put 'length rcCon rcProp rc k 3 uriCon uriProp PropertyValue PropertyName'; put 'Delimiter $256 properties $2048;'; put 'retain properties;'; put 'rcCon = metadata_getnasn("&liburi", "LibraryConnection", 1, uriCon);'; put 'rcProp = metadata_getnasn(uriCon, "Properties", 1, uriProp);'; put 'k = 1;'; put 'rcProp = metadata_getnasn(uriCon, "Properties", k, uriProp);'; put 'do while (rcProp > 0);'; put 'rc = metadata_getattr(uriProp , "DefaultValue",PropertyValue);'; put 'rc = metadata_getattr(uriProp , "PropertyName",PropertyName);'; put 'rc = metadata_getattr(uriProp , "Delimiter",Delimiter);'; put 'properties = trim(properties) !! " " !! trim(PropertyName)'; put '!! trim(Delimiter) !! trim(PropertyValue);'; put 'output;'; put 'k+1;'; put 'rcProp = metadata_getnasn(uriCon, "Properties", k, uriProp);'; put 'end;'; put '%&mD.put NOTE: Getting properties for REMOTE SHARE &libref library;'; put '&mD.put _all_;'; put '%&mD.put NOTE: Libname cmd will be:;'; put '%&mD.put libname &libref &engine &properties slibref=&libref;'; put 'call symputx ("properties",trim(properties),''l'');'; put 'run;'; put 'libname &libref &engine &properties slibref=&libref;'; put '%end;'; put '%else %if &engine=OLEDB %then %do;'; put '%&mD.put NOTE: Retrieving OLEDB connection details;'; put 'data _null_;'; put 'length domain datasource provider properties schema'; put 'connx_uri domain_uri conprop_uri lib_uri schema_uri value $256.;'; put 'call missing (of _all_);'; put '/* get source connection ID */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,connx_uri);'; put '/* get connection domain */'; put 'rc1=metadata_getnasn(connx_uri,''Domain'',1,domain_uri);'; put 'rc2=metadata_getattr(domain_uri,''Name'',domain);'; put '&mD.putlog / ''NOTE: '' // ''NOTE- connection id: '' connx_uri ;'; put '&mD.putlog ''NOTE- domain: '' domain;'; put '/* get DSN and PROVIDER from connection properties */'; put 'i=0;'; put 'do until (rc<0);'; put 'i+1;'; put 'rc=metadata_getnasn(connx_uri,''Properties'',i,conprop_uri);'; put 'rc2=metadata_getattr(conprop_uri,''Name'',value);'; put 'if value=''Connection.OLE.Property.DATASOURCE.Name.xmlKey.txt'' then do;'; put 'rc3=metadata_getattr(conprop_uri,''DefaultValue'',datasource);'; put 'end;'; put 'else if value=''Connection.OLE.Property.PROVIDER.Name.xmlKey.txt'' then do;'; put 'rc4=metadata_getattr(conprop_uri,''DefaultValue'',provider);'; put 'end;'; put 'else if value=''Connection.OLE.Property.PROPERTIES.Name.xmlKey.txt'' then'; put 'do;'; put 'rc5=metadata_getattr(conprop_uri,''DefaultValue'',properties);'; put 'end;'; put 'end;'; put '&mD.putlog ''NOTE- dsn/provider/properties: '' /'; put 'datasource provider properties;'; put '&mD.putlog ''NOTE- schema: '' schema // ''NOTE-'';'; put '/* get SCHEMA */'; put 'rc6=metadata_getnasn("&liburi",''UsingPackages'',1,lib_uri);'; put 'rc7=metadata_getattr(lib_uri,''SchemaName'',schema);'; put 'call symputx(''SQL_domain'',domain,''l'');'; put 'call symputx(''SQL_dsn'',datasource,''l'');'; put 'call symputx(''SQL_provider'',provider,''l'');'; put 'call symputx(''SQL_properties'',properties,''l'');'; put 'call symputx(''SQL_schema'',schema,''l'');'; put 'run;'; put '%if %length(&open_passthrough)>0 %then %do;'; put 'proc sql &sql_options;'; put 'connect to OLEDB as &open_passthrough(INSERT_SQL=YES'; put '/* need additional properties to make this work */'; put 'properties=(''Integrated Security''=SSPI'; put '''Persist Security Info''=True'; put '%sysfunc(compress(%str(&SQL_properties),%str(())))'; put ')'; put 'DATASOURCE=&sql_dsn PROMPT=NO'; put 'PROVIDER=&sql_provider SCHEMA=&sql_schema CONNECTION = GLOBAL);'; put '%end;'; put '%else %do;'; put 'LIBNAME &libref OLEDB PROPERTIES=&sql_properties'; put 'DATASOURCE=&sql_dsn PROVIDER=&sql_provider SCHEMA=&sql_schema'; put '%if %length(&sql_domain)>0 %then %do;'; put 'authdomain="&sql_domain"'; put '%end;'; put 'connection=shared;'; put '%end;'; put '%end;'; put '%else %if &engine=ODBC %then %do;'; put '%&mD.put NOTE: Retrieving ODBC connection details;'; put 'data _null_;'; put 'length connx_uri conprop_uri value datasource up_uri schema domprop_uri authdomain $256.;'; put 'call missing (of _all_);'; put '/* get source connection ID */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,connx_uri);'; put '/* get connection properties */'; put 'i=0;'; put 'do until (rc2<0);'; put 'i+1;'; put 'rc2=metadata_getnasn(connx_uri,''Properties'',i,conprop_uri);'; put 'rc3=metadata_getattr(conprop_uri,''Name'',value);'; put 'if value=''Connection.ODBC.Property.DATASRC.Name.xmlKey.txt'' then do;'; put 'rc4=metadata_getattr(conprop_uri,''DefaultValue'',datasource);'; put 'rc2=-1;'; put 'end;'; put 'end;'; put '/* get auth domain */'; put 'autrc=metadata_getnasn(connx_uri,"Domain",1,domprop_uri);'; put 'arc=metadata_getattr(domprop_uri,"Name",authdomain);'; put 'if not missing(authdomain) then authdomain=cats(''AUTHDOMAIN='',authdomain);'; put 'call symputx(''authdomain'',authdomain,''l'');'; put '/* get SCHEMA */'; put 'rc6=metadata_getnasn("&liburi",''UsingPackages'',1,up_uri);'; put 'rc7=metadata_getattr(up_uri,''SchemaName'',schema);'; put '&mD.put rc= connx_uri= rc2= conprop_uri= rc3= value= rc4= datasource='; put 'rc6= up_uri= rc7= schema=;'; put 'call symputx(''SQL_schema'',schema,''l'');'; put 'call symputx(''SQL_dsn'',datasource,''l'');'; put 'run;'; put '%if %length(&open_passthrough)>0 %then %do;'; put 'proc sql &sql_options;'; put 'connect to ODBC as &open_passthrough'; put '(INSERT_SQL=YES DATASRC=&sql_dsn. CONNECTION=global);'; put '%end;'; put '%else %do;'; put 'libname &libref ODBC DATASRC=&sql_dsn SCHEMA=&sql_schema &authdomain;'; put '%end;'; put '%end;'; put '%else %if &engine=POSTGRES %then %do;'; put '%put NOTE: Obtaining POSTGRES library details;'; put 'data _null_;'; put 'length database ignore_read_only_columns direct_exe preserve_col_names'; put 'preserve_tab_names server schema authdomain user password'; put 'prop name value uri urisrc $256.;'; put 'call missing (of _all_);'; put '/* get database value */'; put 'prop=''Connection.DBMS.Property.DB.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,database,"");'; put 'if database^='''' then database=''database=''!!quote(trim(database));'; put 'call symputx(''database'',database,''l'');'; put '/* get IGNORE_READ_ONLY_COLUMNS value */'; put 'prop=''Library.DBMS.Property.DBIROC.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,ignore_read_only_columns,"");'; put 'if ignore_read_only_columns^='''' then ignore_read_only_columns='; put '''ignore_read_only_columns=''!!ignore_read_only_columns;'; put 'call symputx(''ignore_read_only_columns'',ignore_read_only_columns,''l'');'; put '/* get DIRECT_EXE value */'; put 'prop=''Library.DBMS.Property.DirectExe.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,direct_exe,"");'; put 'if direct_exe^='''' then direct_exe=''direct_exe=''!!direct_exe;'; put 'call symputx(''direct_exe'',direct_exe,''l'');'; put '/* get PRESERVE_COL_NAMES value */'; put 'prop=''Library.DBMS.Property.PreserveColNames.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,preserve_col_names,"");'; put 'if preserve_col_names^='''' then preserve_col_names='; put '''preserve_col_names=''!!preserve_col_names;'; put 'call symputx(''preserve_col_names'',preserve_col_names,''l'');'; put '/* get PRESERVE_TAB_NAMES value */'; put '/* be careful with PRESERVE_TAB_NAMES=YES - it will mean your table will'; put 'become case sensitive!! */'; put 'prop=''Library.DBMS.Property.PreserveTabNames.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,preserve_tab_names,"");'; put 'if preserve_tab_names^='''' then preserve_tab_names='; put '''preserve_tab_names=''!!preserve_tab_names;'; put 'call symputx(''preserve_tab_names'',preserve_tab_names,''l'');'; put '/* get SERVER value */'; put 'if metadata_getnasn("&liburi","LibraryConnection",1,uri)>0 then do;'; put 'prop=''Connection.DBMS.Property.SERVER.Name.xmlKey.txt'';'; put 'rc=metadata_getprop(uri,prop,server,"");'; put 'end;'; put 'if server^='''' then server=''server=''!!quote(cats(server));'; put 'call symputx(''server'',server,''l'');'; put '/* get SCHEMA value */'; put 'if metadata_getnasn("&liburi","UsingPackages",1,uri)>0 then do;'; put 'rc=metadata_getattr(uri,"SchemaName",schema);'; put 'end;'; put 'if schema^='''' then schema=''schema=''!!schema;'; put 'call symputx(''schema'',schema,''l'');'; put '/* get AUTHDOMAIN value */'; put '/* this is only useful if the user account contains that auth domain'; put 'if metadata_getnasn("&liburi","DefaultLogin",1,uri)>0 then do;'; put 'rc=metadata_getnasn(uri,"Domain",1,urisrc);'; put 'rc=metadata_getattr(urisrc,"Name",authdomain);'; put 'end;'; put 'if authdomain^='''' then authdomain=''authdomain=''!!quote(trim(authdomain));'; put '*/'; put 'call symputx(''authdomain'',authdomain,''l'');'; put '/* get user & pass */'; put 'if authdomain='''' & metadata_getnasn("&liburi","DefaultLogin",1,uri)>0 then'; put 'do;'; put 'rc=metadata_getattr(uri,"UserID",user);'; put 'rc=metadata_getattr(uri,"Password",password);'; put 'end;'; put 'if user^='''' then do;'; put 'user=''user=''!!quote(trim(user));'; put 'password=''password=''!!quote(trim(password));'; put 'end;'; put 'call symputx(''user'',user,''l'');'; put 'call symputx(''password'',password,''l'');'; put '&md.put _all_;'; put 'run;'; put '%if %length(&open_passthrough)>0 %then %do;'; put '%put %str(WARN)ING: Passthrough option for postgres not yet supported;'; put '%return;'; put '%end;'; put '%else %do;'; put '%if &mdebug=1 %then %do;'; put '%put NOTE: Executing the following:/;'; put '%put NOTE- libname &libref POSTGRES &database &ignore_read_only_columns;'; put '%put NOTE- &direct_exe &preserve_col_names &preserve_tab_names;'; put '%put NOTE- &server &schema &authdomain &user &password //;'; put '%end;'; put 'libname &libref POSTGRES &database &ignore_read_only_columns &direct_exe'; put '&preserve_col_names &preserve_tab_names &server &schema &authdomain'; put '&user &password;'; put '%end;'; put '%end;'; put '%else %if &engine=ORACLE %then %do;'; put '%put NOTE: Obtaining &engine library details;'; put 'data _null_;'; put 'length assocuri1 assocuri2 assocuri3 authdomain path schema $256;'; put 'call missing (of _all_);'; put '/* get auth domain */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,assocuri1);'; put 'rc=metadata_getnasn(assocuri1,''Domain'',1,assocuri2);'; put 'rc=metadata_getattr(assocuri2,"Name",authdomain);'; put 'call symputx(''authdomain'',authdomain,''l'');'; put '/* path */'; put 'rc=metadata_getprop(assocuri1,'; put '''Connection.Oracle.Property.PATH.Name.xmlKey.txt'',path);'; put 'call symputx(''path'',path,''l'');'; put '/* schema */'; put 'rc=metadata_getnasn("&liburi",''UsingPackages'',1,assocuri3);'; put 'rc=metadata_getattr(assocuri3,''SchemaName'',schema);'; put 'call symputx(''schema'',schema,''l'');'; put 'run;'; put '%put NOTE: Executing the following:/; %put NOTE-;'; put '%put NOTE- libname &libref ORACLE path=&path schema=&schema;'; put '%put NOTE- authdomain=&authdomain;'; put '%put NOTE-;'; put 'libname &libref ORACLE path=&path schema=&schema authdomain=&authdomain;'; put '%end;'; put '%else %if &engine=SQLSVR %then %do;'; put '%put NOTE: Obtaining &engine library details;'; put 'data _null;'; put 'length assocuri1 assocuri2 assocuri3 authdomain path schema userid'; put 'passwd $256;'; put 'call missing (of _all_);'; put 'rc=metadata_getnasn("&liburi",''DefaultLogin'',1,assocuri1);'; put 'rc=metadata_getattr(assocuri1,"UserID",userid);'; put 'rc=metadata_getattr(assocuri1,"Password",passwd);'; put 'call symputx(''user'',userid,''l'');'; put 'call symputx(''pass'',passwd,''l'');'; put '/* path */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,assocuri2);'; put 'rc=metadata_getprop(assocuri2,'; put '''Connection.SQL.Property.Datasrc.Name.xmlKey.txt'',path);'; put 'call symputx(''path'',path,''l'');'; put '/* schema */'; put 'rc=metadata_getnasn("&liburi",''UsingPackages'',1,assocuri3);'; put 'rc=metadata_getattr(assocuri3,''SchemaName'',schema);'; put 'call symputx(''schema'',schema,''l'');'; put 'run;'; put '%put NOTE: Executing the following:/; %put NOTE-;'; put '%put NOTE- libname &libref SQLSVR datasrc=&path schema=&schema ;'; put '%put NOTE- user="&user" pass="XXX";'; put '%put NOTE-;'; put 'libname &libref SQLSVR datasrc=&path schema=&schema user="&user" pass="&pass";'; put '%end;'; put '%else %if &engine=TERADATA %then %do;'; put '%put NOTE: Obtaining &engine library details;'; put 'data _null;'; put 'length assocuri1 assocuri2 assocuri3 authdomain path schema userid'; put 'passwd $256;'; put 'call missing (of _all_);'; put '/* get auth domain */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,assocuri1);'; put 'rc=metadata_getnasn(assocuri1,''Domain'',1,assocuri2);'; put 'rc=metadata_getattr(assocuri2,"Name",authdomain);'; put 'call symputx(''authdomain'',authdomain,''l'');'; put '/*'; put 'rc=metadata_getnasn("&liburi",''DefaultLogin'',1,assocuri1);'; put 'rc=metadata_getattr(assocuri1,"UserID",userid);'; put 'rc=metadata_getattr(assocuri1,"Password",passwd);'; put 'call symputx(''user'',userid,''l'');'; put 'call symputx(''pass'',passwd,''l'');'; put '*/'; put '/* path */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,assocuri2);'; put 'rc=metadata_getprop(assocuri2,'; put '''Connection.Teradata.Property.SERVER.Name.xmlKey.txt'',path);'; put 'call symputx(''path'',path,''l'');'; put '/* schema */'; put 'rc=metadata_getnasn("&liburi",''UsingPackages'',1,assocuri3);'; put 'rc=metadata_getattr(assocuri3,''SchemaName'',schema);'; put 'call symputx(''schema'',schema,''l'');'; put 'run;'; put '%put NOTE: Executing the following:/; %put NOTE-;'; put '%put NOTE- libname &libref TERADATA server="&path" schema=&schema ;'; put '%put NOTe- authdomain=&authdomain;'; put '%put NOTE-;'; put 'libname &libref TERADATA server="&path" schema=&schema authdomain=&authdomain;'; put '%end;'; put '%else %if &engine= %then %do;'; put '%put NOTE: Libref &libref is not registered in metadata;'; put '%&mAbort.mp_abort('; put 'msg=%str(ERR)OR: Libref &libref is not registered in metadata'; put ',mac=mm_assigndirectlib.sas);'; put '%return;'; put '%end;'; put '%else %do;'; put '%put %str(WARN)ING: Engine &engine is currently unsupported;'; put '%put %str(WARN)ING- Please contact your support team.;'; put '%return;'; put '%end;'; put '%mend mm_assigndirectlib;'; put '%macro mm_getrepos('; put 'outds=work.mm_getrepos'; put ')/*/STORE SOURCE*/;'; put '* use a temporary fileref to hold the response;'; put 'filename response temp;'; put '/* get list of libraries */'; put 'proc metadata in='; put '"1"'; put 'out=response;'; put 'run;'; put '/* write the response to the log for debugging */'; put '/*'; put 'data _null_;'; put 'infile response lrecl=1048576;'; put 'input;'; put 'put _infile_;'; put 'run;'; put '*/'; put '/* create an XML map to read the response */'; put 'filename sxlemap temp;'; put 'data _null_;'; put 'file sxlemap;'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository";'; put 'put "";'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Id";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Name";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Desc";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@DefaultNS";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@RepositoryType";'; put 'put "characterstring20";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@RepositoryFormat";'; put 'put "characterstring10";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@Access";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@CurrentAccess";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@PauseState";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Path";'; put 'put "";'; put 'put "characterstring256";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Engine";'; put 'put "";'; put 'put "characterstring8";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Options";'; put 'put "";'; put 'put "characterstring32";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@MetadataCreated";'; put 'put "characterstring24";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@MetadataUpdated";'; put 'put "characterstring24";'; put 'put '''';'; put 'put ''
'';'; put 'run;'; put 'libname _XML_ xml xmlfileref=response xmlmap=sxlemap;'; put 'proc sort data= _XML_.SASRepos out=&outds;'; put 'by name;'; put 'run;'; put '/* clear references */'; put 'filename sxlemap clear;'; put 'filename response clear;'; put 'libname _XML_ clear;'; put '%mend mm_getrepos;'; put '%macro dc_assignlib(type,libref,passthru=);'; put '%put &sysmacroname entry vars:;'; put '%put _local_;'; put '/* take current repository */'; put '%local repo repocnt x;'; put '%let repo=%sysfunc(getoption(metarepository));'; put '/* get list of repositories and filter */'; put '%mm_getrepos(outds=repos)'; put '%let repocnt=0;'; put 'data repos;'; put 'set repos;'; put 'where repositorytype in(''CUSTOM'',''FOUNDATION'')'; put '& upcase(name) ne "%upcase(&repo)";'; put 'keep id name ;'; put 'call symputx(cats(''repo'',_n_),name,''l'');'; put 'call symputx(''repocnt'',_n_,''l'');'; put 'run;'; put '/* find out which of these repositories has the libref we are searching for */'; put '%local lib_uri;'; put '%let lib_uri=NOTFOUND;'; put 'data _null_; /* check default repo first */'; put 'length lib_uri $256;'; put 'call missing (of _all_);'; put 'rc=metadata_getnobj("omsobj:SASLibrary?@Libref =''&libref''",1,lib_uri);'; put 'call symputx(''lib_uri'',coalescec(lib_uri,''NOTFOUND''),''l'');'; put 'run;'; put '%if &lib_uri=NOTFOUND %then %do;'; put '%do x=1 %to &repocnt;'; put 'options metarepository=&&repo&x;'; put 'data _null_;'; put 'length lib_uri $256;'; put 'call missing (of _all_);'; put 'rc=metadata_getnobj("omsobj:SASLibrary?@Libref =''&libref''",1,lib_uri);'; put 'call symputx(''lib_uri'',coalescec(lib_uri,''NOTFOUND''),''l'');'; put 'run;'; put '%if &lib_uri ne NOTFOUND %then %let x=%eval(&repocnt+1);'; put '%end;'; put '%end;'; put '%if &type=READ %then %do;'; put '%mm_assignlib(&libref)'; put '%end;'; put '%else %if &type=WRITE %then %do;'; put '%mm_assigndirectlib(&libref,open_passthrough=&passthru)'; put '%end;'; put 'options metarepository=&repo;'; put '%mend dc_assignlib;'; put '* SAS Macros end;'; put '* SAS Includes start;'; put '* SAS Includes end;'; put '* Binary Files start;'; put '* Binary Files end;'; put '* ServiceInit start;'; put 'options noquotelenmax ps=max;'; put '/* create dummy macros to prevent stpbegin / stpend from corrupting sessions */'; put '%macro stpbegin();'; put '%put NOTE: the STPBEGIN macro should not be used for web apps!;'; put '%mend stpbegin;'; put '%macro stpend();'; put '%put NOTE: the STPEND macro should not be used for web apps!;'; put '%mend stpend;'; put '* ServiceInit end;'; put '* Service start;'; put '/**'; put '@file getchangeinfo.sas'; put '@brief Returns the details for an approval diff'; put '@details'; put '

SAS Macros

'; put '@li mf_getengine.sas'; put '@li dc_assignlib.sas'; put '@li mp_abort.sas'; put '@version 9.2'; put '@author 4GL Apps Ltd'; put '@copyright 4GL Apps Ltd. This code may only be used within Data Controller'; put 'and may not be re-distributed or re-sold without the express permission of'; put '4GL Apps Ltd.'; put '**/'; put '%mpeinit()'; put '%let table=;'; put 'data _null_;'; put 'set SASControlTable;'; put 'call symputx(''table'',table);'; put 'run;'; put '%dc_assignlib(WRITE,%scan(&table,1,.))'; put '%let max_ver_dttm=0;'; put 'data APPROVE1;'; put 'set &mpelib..mpe_submit'; put '(rename=(SUBMITTED_ON_DTTM=submitted_on REVIEWED_ON_DTTM=REVIEWED_ON));'; put 'where TABLE_ID="&TABLE";'; put 'TABLE_NM=cats(base_lib,''.'',base_ds);'; put 'BASE_TABLE=table_nm;'; put 'call symputx(''base_lib'',base_lib);'; put 'REVIEWED_ON_DTTM=put(reviewed_on,datetime19.);'; put 'SUBMITTED_ON_DTTM=put(submitted_on,datetime19.);'; put 'run;'; put 'data jsParams;'; put 'set approve1;'; put 'LIB_ENGINE="%mf_getEngine(&base_lib)";'; put 'run;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(syscc=&syscc)'; put ')'; put '%webout(OPEN)'; put '%webout(OBJ,jsParams)'; put '%webout(CLOSE)'; put '%mpeterm()'; put '* Service end;'; run; %mm_createwebservice(path=&appLoc/&path, name=&service, code=sascode, server=&serverName, replace=yes) filename sascode clear; %let service=getcols; filename sascode temp lrecl=32767; data _null_; file sascode; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '/**'; put '@file mp_jsonout.sas'; put '@brief Writes JSON in SASjs format to a fileref'; put '@details This macro can be used to OPEN a JSON stream and send one or more'; put 'tables as arrays of rows, where each row can be an object or a nested array.'; put 'There are two engines available - DATASTEP or PROCJSON.'; put 'PROC JSON is fast but will produce errs like the ones below if'; put 'special chars are encountered.'; put '> (ERR)OR: Some code points did not transcode.'; put '> An object or array close is not valid at this point in the JSON text.'; put '> Date value out of range'; put 'If this happens, try running with ENGINE=DATASTEP.'; put 'The DATASTEP engine is used to handle special SAS missing numerics, and'; put 'can also convert entire datasets to formatted values. Output JSON is always'; put 'in UTF-8.'; put 'Usage:'; put 'filename tmp temp;'; put 'data class; set sashelp.class;run;'; put '%mp_jsonout(OPEN,jref=tmp)'; put '%mp_jsonout(OBJ,class,jref=tmp)'; put '%mp_jsonout(OBJ,class,dslabel=class2,jref=tmp,showmeta=Y)'; put '%mp_jsonout(CLOSE,jref=tmp)'; put 'data _null_;'; put 'infile tmp;'; put 'input;putlog _infile_;'; put 'run;'; put 'If you are building web apps with SAS then you are strongly encouraged to use'; put 'the mX_createwebservice macros in combination with the'; put '[sasjs adapter](https://github.com/sasjs/adapter).'; put 'For more information see https://sasjs.io'; put '@param [in] action Valid values:'; put '@li OPEN - opens the JSON'; put '@li OBJ - sends a table with each row as an object'; put '@li ARR - sends a table with each row in an array'; put '@li CLOSE - closes the JSON'; put '@param [in] ds The dataset to send. Must be a work table.'; put '@param [out] jref= (_webout) The fileref to which to send the JSON'; put '@param [out] dslabel= The name to give the table in the exported JSON'; put '@param [in] fmt= (Y) Whether to keep (Y) or strip (N) formats from the table'; put '@param [in] engine= (DATASTEP) Which engine to use to send the JSON. Options:'; put '@li PROCJSON (default)'; put '@li DATASTEP (more reliable when data has non standard characters)'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to JSON'; put '

Related Files

'; put '@li mp_ds2fmtds.sas'; put '@version 9.2'; put '@author Allan Bowe'; put '@source https://github.com/sasjs/core'; put '**/'; put '%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y'; put ',engine=DATASTEP'; put ',missing=NULL'; put ',showmeta=N'; put ',maxobs=MAX'; put ')/*/STORE SOURCE*/;'; put '%local tempds colinfo fmtds i numcols numobs stmt_obs lastobs optval'; put 'tmpds1 tmpds2 tmpds3 tmpds4;'; put '%let numcols=0;'; put '%if &maxobs ne MAX %then %let stmt_obs=%str(if _n_>&maxobs then stop;);'; put '%if &action=OPEN %then %do;'; put 'options nobomfile;'; put 'data _null_;file &jref encoding=''utf-8'' lrecl=200;'; put 'put ''{"PROCESSED_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''"'';'; put 'run;'; put '%end;'; put '%else %if (&action=ARR or &action=OBJ) %then %do;'; put '/* force variable names to always be uppercase in the JSON */'; put 'options validvarname=upcase;'; put '/* To avoid issues with _webout on EBI - such as encoding diffs and truncation'; put '(https://support.sas.com/kb/49/325.html) we use temporary files */'; put 'filename _sjs1 temp lrecl=200 ;'; put 'data _null_; file _sjs1 encoding=''utf-8'';'; put 'put ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":";'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjs1 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs1 clear;'; put '/* grab col defs */'; put 'proc contents noprint data=&ds'; put 'out=_data_(keep=name type length format formatl formatd varnum label);'; put 'run;'; put '%let colinfo=%scan(&syslast,2,.);'; put 'proc sort data=&colinfo;'; put 'by varnum;'; put 'run;'; put '/* move meta to mac vars */'; put 'data &colinfo;'; put 'if _n_=1 then call symputx(''numcols'',nobs,''l'');'; put 'set &colinfo end=last nobs=nobs;'; put 'name=upcase(name);'; put '/* fix formats */'; put 'if type=2 or type=6 then do;'; put 'typelong=''char'';'; put 'length fmt $49.;'; put 'if format='''' then fmt=cats(''$'',length,''.'');'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else fmt=cats(format,formatl,''.'');'; put 'end;'; put 'else do;'; put 'typelong=''num'';'; put 'if format='''' then fmt=''best.'';'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else if formatd=0 then fmt=cats(format,formatl,''.'');'; put 'else fmt=cats(format,formatl,''.'',formatd);'; put 'end;'; put '/* 32 char unique name */'; put 'newname=''sasjs''!!substr(cats(put(md5(name),$hex32.)),1,27);'; put 'call symputx(cats(''name'',_n_),name,''l'');'; put 'call symputx(cats(''newname'',_n_),newname,''l'');'; put 'call symputx(cats(''length'',_n_),length,''l'');'; put 'call symputx(cats(''fmt'',_n_),fmt,''l'');'; put 'call symputx(cats(''type'',_n_),type,''l'');'; put 'call symputx(cats(''typelong'',_n_),typelong,''l'');'; put 'call symputx(cats(''label'',_n_),coalescec(label,name),''l'');'; put '/* overwritten when fmt=Y and a custom format exists in catalog */'; put 'if typelong=''num'' then call symputx(cats(''fmtlen'',_n_),200,''l'');'; put 'else call symputx(cats(''fmtlen'',_n_),min(32767,ceil((length+10)*1.5)),''l'');'; put 'run;'; put '%let tempds=%substr(_%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql;'; put 'select count(*) into: lastobs from &ds;'; put '%if &maxobs ne MAX %then %let lastobs=%sysfunc(min(&lastobs,&maxobs));'; put '%if &engine=PROCJSON %then %do;'; put '%if &missing=STRING %then %do;'; put '%put &sysmacroname: Special Missings not supported in proc json.;'; put '%put &sysmacroname: Switching to DATASTEP engine;'; put '%goto datastep;'; put '%end;'; put 'data &tempds;'; put 'set &ds;'; put '&stmt_obs;'; put '%if &fmt=N %then format _numeric_ best32.;;'; put '/* PRETTY is necessary to avoid line truncation in large files */'; put 'filename _sjs2 temp lrecl=131068 encoding=''utf-8'';'; put 'proc json out=_sjs2 pretty'; put '%if &action=ARR %then nokeys ;'; put ';export &tempds / nosastags fmtnumeric;'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs2 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs2 clear;'; put '%end;'; put '%else %if &engine=DATASTEP %then %do;'; put '%datastep:'; put '%if %sysfunc(exist(&ds)) ne 1 & %sysfunc(exist(&ds,VIEW)) ne 1'; put '%then %do;'; put '%put &sysmacroname: &ds NOT FOUND!!!;'; put '%return;'; put '%end;'; put '%if &fmt=Y %then %do;'; put '/**'; put '* Extract format definitions'; put '* First, by getting library locations from dictionary.formats'; put '* Then, by exporting the width using proc format'; put '* Cannot use maxw from sashelp.vformat as not always populated'; put '* Cannot use fmtinfo() as not supported in all flavours'; put '*/'; put '%let tmpds1=%substr(fmtsum%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds2=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds3=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds4=%substr(col%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql noprint;'; put 'create table &tmpds1 as'; put 'select cats(libname,''.'',memname) as FMTCAT,'; put 'FMTNAME'; put 'from dictionary.formats'; put 'where fmttype=''F'' and libname is not null'; put 'and fmtname in (select format from &colinfo where format is not null)'; put 'order by 1;'; put 'create table &tmpds2('; put 'FMTNAME char(32),'; put 'LENGTH num'; put ');'; put '%local catlist cat fmtlist i;'; put 'select distinct fmtcat into: catlist separated by '' '' from &tmpds1;'; put '%do i=1 %to %sysfunc(countw(&catlist,%str( )));'; put '%let cat=%scan(&catlist,&i,%str( ));'; put 'proc sql;'; put 'select distinct fmtname into: fmtlist separated by '' '''; put 'from &tmpds1 where fmtcat="&cat";'; put 'proc format lib=&cat cntlout=&tmpds3(keep=fmtname length);'; put 'select &fmtlist;'; put 'run;'; put 'proc sql;'; put 'insert into &tmpds2 select distinct fmtname,length from &tmpds3;'; put '%end;'; put 'proc sql;'; put 'create table &tmpds4 as'; put 'select a.*, b.length as MAXW'; put 'from &colinfo a'; put 'left join &tmpds2 b'; put 'on cats(a.format)=cats(upcase(b.fmtname))'; put 'order by a.varnum;'; put 'data _null_;'; put 'set &tmpds4;'; put 'if not missing(maxw);'; put 'call symputx('; put 'cats(''fmtlen'',_n_),'; put '/* vars need extra padding due to JSON escaping of special chars */'; put 'min(32767,ceil((max(length,maxw)+10)*1.5))'; put ',''l'''; put ');'; put 'run;'; put '/* configure varlenchk - as we are explicitly shortening the variables */'; put '%let optval=%sysfunc(getoption(varlenchk));'; put 'options varlenchk=NOWARN;'; put 'data _data_(compress=char);'; put '/* shorten the new vars */'; put 'length'; put '%do i=1 %to &numcols;'; put '&&name&i $&&fmtlen&i'; put '%end;'; put ';'; put '/* rename on entry */'; put 'set &ds(rename=('; put '%do i=1 %to &numcols;'; put '&&name&i=&&newname&i'; put '%end;'; put '));'; put '&stmt_obs;'; put 'drop'; put '%do i=1 %to &numcols;'; put '&&newname&i'; put '%end;'; put ';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=num %then %do;'; put '&&name&i=cats(put(&&newname&i,&&fmt&i));'; put '%end;'; put '%else %do;'; put '&&name&i=put(&&newname&i,&&fmt&i);'; put '%end;'; put '%end;'; put 'if _error_ then do;'; put 'call symputx(''syscc'',1012);'; put 'stop;'; put 'end;'; put 'run;'; put '%let fmtds=&syslast;'; put 'options varlenchk=&optval;'; put '%end;'; put 'proc format; /* credit yabwon for special null removal */'; put 'value bart (default=40)'; put '%if &missing=NULL %then %do;'; put '._ - .z = null'; put '%end;'; put '%else %do;'; put '._ = [quote()]'; put '. = null'; put '.a - .z = [quote()]'; put '%end;'; put 'other = [best.];'; put 'data &tempds;'; put 'attrib _all_ label='''';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'length &&name&i $&&fmtlen&i...;'; put 'format &&name&i $&&fmtlen&i...;'; put '%end;'; put '%end;'; put '%if &fmt=Y %then %do;'; put 'set &fmtds;'; put '%end;'; put '%else %do;'; put 'set &ds;'; put '%end;'; put '&stmt_obs;'; put 'format _numeric_ bart.;'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'if findc(&&name&i,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put '&&name&i=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,&&name&i)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else &&name&i=quote(cats(&&name&i));'; put '%end;'; put '%end;'; put 'run;'; put 'filename _sjs3 temp lrecl=131068 ;'; put 'data _null_;'; put 'file _sjs3 encoding=''utf-8'';'; put 'if _n_=1 then put "[";'; put 'set &tempds;'; put 'if _n_>1 then put "," @; put'; put '%if &action=ARR %then "[" ; %else "{" ;'; put '%do i=1 %to &numcols;'; put '%if &i>1 %then "," ;'; put '%if &action=OBJ %then """&&name&i"":" ;'; put '"&&name&i"n /* name literal for reserved variable names */'; put '%end;'; put '%if &action=ARR %then "]" ; %else "}" ; ;'; put '/* close out the table */'; put 'data _null_;'; put 'file _sjs3 mod encoding=''utf-8'';'; put 'put '']'';'; put 'run;'; put 'data _null_;'; put 'infile _sjs3 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs3 clear;'; put '%end;'; put 'proc sql;'; put 'drop table &colinfo, &tempds;'; put '%if %substr(&showmeta,1,1)=Y %then %do;'; put 'filename _sjs4 temp lrecl=131068 encoding=''utf-8'';'; put 'data _null_;'; put 'file _sjs4;'; put 'length label $350;'; put 'put ", ""$%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":{""vars"":{";'; put 'do i=1 to &numcols;'; put 'name=quote(trim(symget(cats(''name'',i))));'; put 'format=quote(trim(symget(cats(''fmt'',i))));'; put 'label=quote(prxchange(''s/\\/\\\\/'',-1,trim(symget(cats(''label'',i)))));'; put 'length=quote(trim(symget(cats(''length'',i))));'; put 'type=quote(trim(symget(cats(''typelong'',i))));'; put 'if i>1 then put "," @@;'; put 'put name '':{"format":'' format '',"label":'' label'; put ''',"length":'' length '',"type":'' type ''}'';'; put 'end;'; put 'put ''}}'';'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs4 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs4 clear;'; put '%end;'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put 'data _null_; file &jref encoding=''utf-8'' mod ;'; put 'put "}";'; put 'run;'; put '%end;'; put '%mend mp_jsonout;'; put '/**'; put '@file mm_webout.sas'; put '@brief Send data to/from SAS Stored Processes'; put '@details This macro should be added to the start of each Stored Process,'; put '**immediately** followed by a call to:'; put '%mm_webout(FETCH)'; put 'This will read all the input data and create same-named SAS datasets in the'; put 'WORK library. You can then insert your code, and send data back using the'; put 'following syntax:'; put 'data some datasets; * make some data ;'; put 'retain some columns;'; put 'run;'; put '%mm_webout(OPEN)'; put '%mm_webout(ARR,some) * Array format, fast, suitable for large tables ;'; put '%mm_webout(OBJ,datasets) * Object format, easier to work with ;'; put 'Finally, wrap everything up send some helpful system variables too'; put '%mm_webout(CLOSE)'; put '@param [in] action Either FETCH, OPEN, ARR, OBJ or CLOSE'; put '@param [in] ds The dataset to send back to the frontend'; put '@param [out] dslabel= Value to use instead of table name for sending to JSON'; put '@param [in] fmt= (N) Setting Y converts all vars to their formatted values'; put '@param [out] fref= (_webout) The fileref to which to write the JSON'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] workobs= (0) When set to a positive integer, will create a new'; put 'output object (WORK) which contains this number of observations from all'; put 'tables in the WORK library.'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to output JSON'; put '

SAS Macros

'; put '@li mp_jsonout.sas'; put '

Related Macros

'; put '@li ms_webout.sas'; put '@li mv_webout.sas'; put '@version 9.3'; put '@author Allan Bowe'; put '**/'; put '%macro mm_webout(action,ds,dslabel=,fref=_webout,fmt=N,missing=NULL'; put ',showmeta=N,maxobs=MAX,workobs=0'; put ');'; put '%global _webin_file_count _webin_fileref1 _webin_name1 _program _debug'; put 'sasjs_tables;'; put '%local i tempds jsonengine;'; put '/* see https://github.com/sasjs/core/issues/41 */'; put '%if "%upcase(&SYSENCODING)" ne "UTF-8" %then %let jsonengine=PROCJSON;'; put '%else %let jsonengine=DATASTEP;'; put '%if &action=FETCH %then %do;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'options mprint notes mprintnest;'; put '%end;'; put '%let _webin_file_count=%eval(&_webin_file_count+0);'; put '/* now read in the data */'; put '%do i=1 %to &_webin_file_count;'; put '%if &_webin_file_count=1 %then %do;'; put '%let _webin_fileref1=&_webin_fileref;'; put '%let _webin_name1=&_webin_name;'; put '%end;'; put 'data _null_;'; put 'infile &&_webin_fileref&i termstr=crlf;'; put 'input;'; put 'call symputx(''input_statement'',_infile_);'; put 'putlog "&&_webin_name&i input statement: " _infile_;'; put 'stop;'; put 'data &&_webin_name&i;'; put 'infile &&_webin_fileref&i firstobs=2 dsd termstr=crlf encoding=''utf-8'';'; put 'input &input_statement;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'if _n_<20 then putlog _infile_;'; put '%end;'; put 'run;'; put '%let sasjs_tables=&sasjs_tables &&_webin_name&i;'; put '%end;'; put '%end;'; put '%else %if &action=OPEN %then %do;'; put '/* fix encoding */'; put 'OPTIONS NOBOMFILE;'; put '/**'; put '* check xengine type to avoid the below err message:'; put '* > Function is only valid for filerefs using the CACHE access method.'; put '*/'; put 'data _null_;'; put 'set sashelp.vextfl(where=(fileref="_WEBOUT"));'; put 'if xengine=''STREAM'' then do;'; put 'rc=stpsrv_header(''Content-type'',"text/html; encoding=utf-8");'; put 'end;'; put 'run;'; put '/* setup json */'; put 'data _null_;file &fref encoding=''utf-8'';'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutBEGIN<<'';'; put '%end;'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'run;'; put '%end;'; put '%else %if &action=ARR or &action=OBJ %then %do;'; put '%mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt,jref=&fref'; put ',engine=&jsonengine,missing=&missing,showmeta=&showmeta,maxobs=&maxobs'; put ')'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put '/* To avoid issues with _webout on EBI we use a temporary file */'; put 'filename _sjsref temp lrecl=131068;'; put '%if %str(&workobs) > 0 %then %do;'; put '/* if debug mode, send back first XX records of each work table also */'; put 'data;run;%let tempds=%scan(&syslast,2,.);'; put 'ods output Members=&tempds;'; put 'proc datasets library=WORK memtype=data;'; put '%local wtcnt;%let wtcnt=0;'; put 'data _null_;'; put 'set &tempds;'; put 'if not (upcase(name) =:"DATA"); /* ignore temp datasets */'; put 'i+1;'; put 'call symputx(cats(''wt'',i),name,''l'');'; put 'call symputx(''wtcnt'',i,''l'');'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put ",""WORK"":{";'; put '%do i=1 %to &wtcnt;'; put '%let wt=&&wt&i;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'dsid=open("WORK.&wt",''is'');'; put 'nlobs=attrn(dsid,''NLOBS'');'; put 'nvars=attrn(dsid,''NVARS'');'; put 'rc=close(dsid);'; put 'if &i>1 then put '',''@;'; put 'put " ""&wt"" : {";'; put 'put ''"nlobs":'' nlobs;'; put 'put '',"nvars":'' nvars;'; put '%mp_jsonout(OBJ,&wt,jref=_sjsref,dslabel=first10rows,showmeta=Y'; put ',maxobs=&workobs'; put ')'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put '%end;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put 'run;'; put '%end;'; put '/* close off json */'; put 'data _null_;file _sjsref mod encoding=''utf-8'';'; put 'length SYSPROCESSNAME syserrortext syswarningtext autoexec $512;'; put 'put ",""_DEBUG"" : ""&_debug"" ";'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'autoexec=quote(urlencode(trim(getoption(''autoexec''))));'; put 'put '',"AUTOEXEC" : '' autoexec;'; put 'put ",""MF_GETUSER"" : ""%mf_getuser()"" ";'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'put ",""SYSENCODING"" : ""&sysencoding"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSPROCESSID"" : ""&SYSPROCESSID"" ";'; put 'put ",""SYSPROCESSMODE"" : ""&SYSPROCESSMODE"" ";'; put 'SYSPROCESSNAME=quote(urlencode(cats(SYSPROCESSNAME)));'; put 'put ",""SYSPROCESSNAME"" : " SYSPROCESSNAME;'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put '',"SYSWARNINGTEXT" : '' syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'length memsize $32;'; put 'memsize="%sysfunc(INPUTN(%sysfunc(getoption(memsize)), best.),sizekmg.)";'; put 'memsize=quote(cats(memsize));'; put 'put '',"MEMSIZE" : '' memsize;'; put 'put "}" @;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutEND<<'';'; put '%end;'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjsref lrecl=1 recfm=n;'; put 'file &fref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjsref clear;'; put '%end;'; put '%mend mm_webout;'; put '%macro webout(action,ds,dslabel=,fmt=,missing=NULL,showmeta=NO,maxobs=MAX);'; put '%mm_webout(&action,ds=&ds,dslabel=&dslabel,fmt=&fmt'; put ',missing=&missing'; put ',showmeta=&showmeta'; put ',maxobs=&maxobs'; put ') %mend;'; put '/* provide additional debug info */'; put '%global _program;'; put '%put &=syscc;'; put '%put user=%mf_getuser();'; put '%put pgm=&_program;'; put '%put timestamp=%sysfunc(datetime(),datetime19.);'; put '* Service Variables start;'; put '* Service Variables end;'; put '* SAS Macros start;'; put '%macro mf_getapploc(pgm);'; put '%if "&pgm"="" %then %do;'; put '%if %symexist(_program) %then %let pgm=&_program;'; put '%else %do;'; put '%put &sysmacroname: No value provided and no _program variable available;'; put '%return;'; put '%end;'; put '%end;'; put '%local root;'; put '/**'; put '* First check we are not in the tests/macros folder (which has no subfolders)'; put '* or specifically in the testsetup or testteardown services'; put '*/'; put '%if %index(&pgm,/tests/macros/)'; put 'or %index(&pgm,/tests/testsetup)'; put 'or %index(&pgm,/tests/testteardown)'; put '%then %do;'; put '%let root=%substr(&pgm,1,%index(&pgm,/tests)-1);'; put '&root'; put '%return;'; put '%end;'; put '/**'; put '* Next, move up two levels to avoid matches on subfolder or service name'; put '*/'; put '%let root=%substr(&pgm,1,%length(&pgm)-%length(%scan(&pgm,-1,/))-1);'; put '%let root=%substr(&root,1,%length(&root)-%length(%scan(&root,-1,/))-1);'; put '%if %index(&root,/tests/) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/tests/)-1);'; put '%end;'; put '%else %if %index(&root,/services) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/services)-1);'; put '%end;'; put '%else %if %index(&root,/jobs) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/jobs)-1);'; put '%end;'; put '%else %put &sysmacroname: Could not find an app location from &pgm;'; put '&root'; put '%mend mf_getapploc ;'; put '%macro mf_getuniquefileref(prefix=_,maxtries=1000,lrecl=32767);'; put '%local rc fname;'; put '%if &prefix=0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%end;'; put '%else %do;'; put '%local x len;'; put '%let len=%eval(8-%length(&prefix));'; put '%let x=0;'; put '%do x=0 %to &maxtries;'; put '%let fname=&prefix%substr(%sysfunc(ranuni(0)),3,&len);'; put '%if %sysfunc(fileref(&fname)) > 0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%return;'; put '%end;'; put '%end;'; put '%put unable to find available fileref after &maxtries attempts;'; put '%end;'; put '%mend mf_getuniquefileref;'; put '%macro mp_abort(mac=mp_abort.sas, type=, msg=, iftrue=%str(1=1)'; put ', errds=work.mp_abort_errds'; put ', mode=REGULAR'; put ')/*/STORE SOURCE*/;'; put '%global sysprocessmode sysprocessname sasjs_stpsrv_header_loc sasjsprocessmode;'; put '%local fref fid i;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%put NOTE: /// mp_abort macro executing //;'; put '%if %length(&mac)>0 %then %put NOTE- called by &mac;'; put '%put NOTE - &msg;'; put '%if %symexist(_SYSINCLUDEFILEDEVICE)'; put '/* abort cancel FILE does not restart outside the INCLUDE on Viya 3.5 */'; put 'and %superq(SYSPROCESSNAME) ne %str(Compute Server)'; put '%then %do;'; put '%if "*&_SYSINCLUDEFILEDEVICE*" ne "**" %then %do;'; put 'data &errds;'; put 'iftrue=''1=1'';'; put 'length mac $100 msg $5000;'; put 'mac=symget(''mac'');'; put 'msg=symget(''msg'');'; put 'run;'; put 'data _null_;'; put 'abort cancel FILE;'; put 'run;'; put '%return;'; put '%end;'; put '%end;'; put '/* Web App Context */'; put '%if %symexist(_PROGRAM)'; put 'or %superq(SYSPROCESSNAME) = %str(Compute Server)'; put 'or &mode=INCLUDE'; put '%then %do;'; put 'options obs=max replace mprint;'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5"'; put '%then %do;'; put 'options nosyntaxcheck;'; put '%end;'; put '%if &mode=INCLUDE %then %do;'; put '%if %sysfunc(exist(&errds))=1 %then %do;'; put 'data _null_;'; put 'set &errds;'; put 'call symputx(''iftrue'',iftrue,''l'');'; put 'call symputx(''mac'',mac,''l'');'; put 'call symputx(''msg'',msg,''l'');'; put 'putlog (_all_)(=);'; put 'run;'; put '%if (&iftrue)=0 %then %return;'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: No include errors found;'; put '%return;'; put '%end;'; put '%end;'; put '/* extract log errs / warns, if exist */'; put '%local logloc logline;'; put '%global logmsg; /* capture global messages */'; put '%if %symexist(SYSPRINTTOLOG) %then %let logloc=&SYSPRINTTOLOG;'; put '%else %let logloc=%qsysfunc(getoption(LOG));'; put 'proc printto log=log;run;'; put '%let logline=0;'; put '%if %length(&logloc)>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input; putlog _infile_;'; put 'i=1;'; put 'retain logonce 0;'; put 'if ('; put '_infile_=:"%str(WARN)ING" or _infile_=:"%str(ERR)OR"'; put ') and logonce=0 then'; put 'do;'; put 'call symputx(''logline'',_n_);'; put 'logonce+1;'; put 'end;'; put 'run;'; put '/* capture log including lines BEFORE the err */'; put '%if &logline>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input;'; put 'i=1;'; put 'stoploop=0;'; put 'if _n_ ge &logline-15 and stoploop=0 then do until (i>22);'; put 'call symputx(''logmsg'',catx(''\n'',symget(''logmsg''),_infile_));'; put 'input;'; put 'i+1;'; put 'stoploop=1;'; put 'end;'; put 'if stoploop=1 then stop;'; put 'run;'; put '%end;'; put '%end;'; put '%if %symexist(SYS_JES_JOB_URI) %then %do;'; put '/* setup webout for Viya */'; put 'options nobomfile;'; put '%if "X&SYS_JES_JOB_URI.X"="XX" %then %do;'; put 'filename _webout temp lrecl=999999 mod;'; put '%end;'; put '%else %do;'; put 'filename _webout filesrvc parenturi="&SYS_JES_JOB_URI"'; put 'name="_webout.json" lrecl=999999 mod;'; put '%end;'; put '%end;'; put '%else %if %sysfunc(filename(fref,&sasjs_stpsrv_header_loc))=0 %then %do;'; put 'options nobomfile;'; put '/* set up http header for SASjs Server */'; put '%let fid=%sysfunc(fopen(&fref,A));'; put '%if &fid=0 %then %do;'; put '%put %str(ERR)OR: %sysfunc(sysmsg());'; put '%return;'; put '%end;'; put '%let rc=%sysfunc(fput(&fid,%str(Content-Type: application/json)));'; put '%let rc=%sysfunc(fwrite(&fid));'; put '%let rc=%sysfunc(fclose(&fid));'; put '%let rc=%sysfunc(filename(&fref));'; put '%end;'; put '/* send response in SASjs JSON format */'; put 'data _null_;'; put 'file _webout mod lrecl=32000 encoding=''utf-8'';'; put 'length msg syswarningtext syserrortext $32767 mode $10 ;'; put 'sasdatetime=datetime();'; put 'msg=symget(''msg'');'; put '%if &logline>0 %then %do;'; put 'msg=cats(msg,''\n\nLog Extract:\n'',symget(''logmsg''));'; put '%end;'; put '/* escape the escapes */'; put 'msg=tranwrd(msg,''\'',''\\'');'; put '/* escape the quotes */'; put 'msg=tranwrd(msg,''"'',''\"'');'; put '/* ditch the CRLFs as chrome complains */'; put 'msg=compress(msg,,''kw'');'; put '/* quote without quoting the quotes (which are escaped instead) */'; put 'msg=cats(''"'',msg,''"'');'; put 'if symexist(''_debug'') then debug=quote(trim(symget(''_debug'')));'; put 'else debug=''""'';'; put 'if symget(''sasjsprocessmode'')=''Stored Program'' then mode=''SASJS'';'; put 'if mode ne ''SASJS'' then put ''>>weboutBEGIN<<'';'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'put '',"sasjsAbort" : [{'';'; put 'put '' "MSG":'' msg ;'; put 'put '' ,"MAC": "'' "&mac" ''"}]'';'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'put '',"_DEBUG":'' debug ;'; put 'if symexist(''_metauser'') then do;'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put 'end;'; put 'if symexist(''SYS_JES_JOB_URI'') then do;'; put 'SYS_JES_JOB_URI=quote(trim(symget(''SYS_JES_JOB_URI'')));'; put 'put '',"SYS_JES_JOB_URI": '' SYS_JES_JOB_URI;'; put 'end;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put ",""SYSWARNINGTEXT"" : " syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'put "}" ;'; put 'if mode ne ''SASJS'' then put ''>>weboutEND<<'';'; put 'run;'; put '%put _all_;'; put '%if "&sysprocessmode " = "SAS Stored Process Server " %then %do;'; put 'data _null_;'; put 'putlog ''stpsrvset program err and syscc'';'; put 'rc=stpsrvset(''program error'', 0);'; put 'call symputx("syscc",0,"g");'; put 'run;'; put '%if &sysscp=WIN'; put 'and 1=0 /* deprecating this logic until we figure out a consistent abort */'; put 'and "%substr(%str(&sysvlong ),1,8)"="9.04.01M"'; put 'and "%substr(%str(&sysvlong ),9,1)">"5" %then %do;'; put '/* skip approach (below) does not work in windows m6+ envs */'; put 'endsas;'; put '%end;'; put '%else %do;'; put '/**'; put '* endsas kills 9.4m3 deployments by orphaning multibridges.'; put '* Abort variants are ungraceful (non zero return code)'; put '* This approach lets SAS run silently until the end :-)'; put '* Caution - fails when called within a %include within a macro'; put '* Use mp_include() to handle this.'; put '*/'; put 'filename skip temp;'; put 'data _null_;'; put 'file skip;'; put 'put ''%macro skip();'';'; put 'comment ''%mend skip; -> fix lint '';'; put 'put ''%macro skippy();'';'; put 'comment ''%mend skippy; -> fix lint '';'; put 'run;'; put '%inc skip;'; put '%end;'; put '%end;'; put '%else %if "&sysprocessmode " = "SAS Compute Server " %then %do;'; put '/* endsas kills the session making it harder to fetch results */'; put 'data _null_;'; put 'syswarningtext=symget(''syswarningtext'');'; put 'syserrortext=symget(''syserrortext'');'; put 'abort_msg=symget(''msg'');'; put 'syscc=symget(''syscc'');'; put 'sysuserid=symget(''sysuserid'');'; put 'iftrue=symget(''iftrue'');'; put 'put (_all_)(/=);'; put 'call symputx(''syscc'',0);'; put 'abort cancel nolist;'; put 'run;'; put '%end;'; put '%else %do;'; put '%abort cancel;'; put '%end;'; put '%end;'; put '%else %do;'; put '%put _all_;'; put '%abort cancel;'; put '%end;'; put '%mend mp_abort;'; put '/** @endcond */'; put '%macro mm_getstpcode('; put 'tree=/User Folders/sasdemo/somestp'; put ',name='; put ',outloc=0'; put ',outref=0'; put ',mDebug=1'; put ',showlog=NO'; put ');'; put '%local mD;'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing &sysmacroname..sas;'; put '%&mD.put _local_;'; put '%if %length(&name)>0 %then %let name=/&name;'; put '/* first, check if STP exists */'; put '%local tsuri;'; put '%let tsuri=stopifempty ;'; put 'data _null_;'; put 'format type uri tsuri value $200.;'; put 'call missing (of _all_);'; put 'path="&tree&name(StoredProcess)";'; put '/* first, find the STP ID */'; put 'if metadata_pathobj("",path,"StoredProcess",type,uri)>0 then do;'; put '/* get sourcecode */'; put 'cnt=1;'; put 'do while (metadata_getnasn(uri,"Notes",cnt,tsuri)>0);'; put 'rc=metadata_getattr(tsuri,"Name",value);'; put '&mD.put tsuri= value=;'; put 'if value="SourceCode" then do;'; put '/* found it! */'; put 'rc=metadata_getattr(tsuri,"Id",value);'; put 'call symputx(''tsuri'',value,''l'');'; put 'stop;'; put 'end;'; put 'cnt+1;'; put 'end;'; put 'end;'; put 'else put (_all_)(=);'; put 'run;'; put '%mp_abort(iftrue= (&tsuri=stopifempty)'; put ',mac=mm_getstpcode'; put ',msg=%str(&tree&name.(StoredProcess) not found!)'; put ')'; put '/**'; put '* Now we can extract the textstore'; put '*/'; put 'filename __getdoc temp lrecl=10000000;'; put 'proc metadata'; put 'in="$METAREPOSITORY'; put ''; put 'SAS1"'; put 'out=__getdoc ;'; put 'run;'; put '/* find the beginning of the text */'; put '%local start;'; put 'data _null_;'; put 'infile __getdoc lrecl=10000;'; put 'input;'; put 'start=index(_infile_,''StoredText="'');'; put 'if start then do;'; put 'call symputx("start",start+11);'; put '*putlog ''"'' _infile_ ''"'';'; put 'end;'; put 'stop;'; put '%local outeng;'; put '%if "&outloc"="0" %then %let outeng=TEMP;'; put '%else %let outeng="&outloc";'; put '%local fref;'; put '%if &outref=0 %then %let fref=%mf_getuniquefileref();'; put '%else %let fref=&outref;'; put '/* read the content, byte by byte, resolving escaped chars */'; put 'filename &fref &outeng lrecl=100000;'; put 'data _null_;'; put 'length filein 8 fileid 8;'; put 'filein = fopen("__getdoc","I",1,"B");'; put 'fileid = fopen("&fref","O",1,"B");'; put 'rec = "20"x;'; put 'length entity $6;'; put 'do while(fread(filein)=0);'; put 'x+1;'; put 'if x>&start then do;'; put 'rc = fget(filein,rec,1);'; put 'if rec=''"'' then leave;'; put 'else if rec="&" then do;'; put 'entity=rec;'; put 'do until (rec=";");'; put 'if fread(filein) ne 0 then goto getout;'; put 'rc = fget(filein,rec,1);'; put 'entity=cats(entity,rec);'; put 'end;'; put 'select (entity);'; put 'when (''&'' ) rec=''&'' ;'; put 'when (''<'' ) rec=''<'' ;'; put 'when (''>'' ) rec=''>'' ;'; put 'when (''''') rec="''" ;'; put 'when (''"'') rec=''"'' ;'; put 'when ('' '') rec=''0A''x;'; put 'when ('' '') rec=''0D''x;'; put 'when (''$'' ) rec=''$'' ;'; put 'when ('' '') rec=''09''x;'; put 'otherwise putlog "%str(WARN)ING: missing value for " entity=;'; put 'end;'; put 'rc =fput(fileid, substr(rec,1,1));'; put 'rc =fwrite(fileid);'; put 'end;'; put 'else do;'; put 'rc =fput(fileid,rec);'; put 'rc =fwrite(fileid);'; put 'end;'; put 'end;'; put 'end;'; put 'getout:'; put 'rc=fclose(filein);'; put 'rc=fclose(fileid);'; put 'run;'; put '%if &showlog=YES %then %do;'; put 'data _null_;'; put 'infile &fref lrecl=32767 end=last;'; put 'input;'; put 'if _n_=1 then putlog ''>>stpcodeBEGIN<<'';'; put 'putlog _infile_;'; put 'if last then putlog ''>>stpcodeEND<<'';'; put 'run;'; put '%end;'; put 'filename __getdoc clear;'; put '%if &outref=0 %then %do;'; put 'filename &fref clear;'; put '%end;'; put '%mend mm_getstpcode;'; put '%macro dc_getsettings();'; put '%global _program;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(syscc=&syscc on entry (&syswarningtext &syserrortext))'; put ')'; put '%if %length(&_PROGRAM)>1 %then %let root=&_program;'; put '%else %do;'; put '%global _metauser;'; put '%let _metauser=&sysuserid;'; put '/* to mimic a "real" _program we need to give a dummy role and stp name */'; put '%let root=/dummyRole/dummyName;'; put '%end;'; put '/* the DC precode is stored in the Admin folder in the root of'; put 'the project. Lets find that root. */'; put '%put &=root;'; put '%let root=%mf_getapploc();'; put '%put &=root;'; put '/* Now we know the root location we can retrieve the params */'; put '%let temploc=%sysfunc(pathname(work))/settings.txt;'; put '%mm_getstpcode(tree=&root/services/public'; put ',name=Data_Controller_Settings'; put ',outloc=&temploc'; put ')'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Unable to run getstpcode)'; put ')'; put 'filename _getsets "&temploc" lrecl=2000;'; put '/*'; put 'Do not use mp_include here - this puts a copy in every service, which creates'; put 'compilation problems when calling services from mp_include'; put '*/'; put '%inc _getsets/source2;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Problem running &sysmacroname (&syswarningtext &syserrortext))'; put ')'; put '%mend dc_getsettings;'; put '%macro mf_fmtdttm('; put ')/*/STORE SOURCE*/;'; put '%if "&sysver"="9.2" or "&sysver"="9.3"'; put 'or ("&sysver"="9.4" and "%substr(&SYSVLONG,9,1)" le "3")'; put 'or "%substr(&sysver,1,1)"="4"'; put 'or "%substr(&sysver,1,1)"="5"'; put '%then %do;DATETIME19.3%end;'; put '%else %do;E8601DT26.6%end;'; put '%mend mf_fmtdttm;'; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '%macro mp_init(prefix=SASJS'; put ')/*/STORE SOURCE*/;'; put '%if %symexist(SASJS_PREFIX) %then %return; /* only run once */'; put '%global'; put 'SASJS_PREFIX /* the ONLY hard-coded global macro variable in SASjs */'; put '&prefix._FUNCTIONS /* used in mcf_init() to track core function compilation */'; put '&prefix._INIT_NUM /* initialisation time as numeric */'; put '&prefix._INIT_DTTM /* initialisation time in E8601DT26.6 format */'; put '&prefix.WORK /* avoid typing %sysfunc(pathname(work)) every time */'; put ';'; put '%let sasjs_prefix=&prefix;'; put 'data _null_;'; put 'dttm=datetime();'; put 'call symputx("&sasjs_prefix._init_num",dttm,''g'');'; put 'call symputx("&sasjs_prefix._init_dttm",put(dttm,E8601DT26.6),''g'');'; put 'call symputx("&sasjs_prefix.work",pathname(''WORK''),''g'');'; put 'run;'; put 'options'; put 'compress=CHAR /* default is none so ensure we have something! */'; put 'datastmtchk=ALLKEYWORDS /* protection from overwriting input datasets */'; put 'errorcheck=STRICT /* catch errs in libname/filename statements */'; put 'fmterr /* ensure err when a format cannot be found */'; put 'mergenoby=%str(ERR)OR /* throw err when a merge has no BY variables */'; put 'missing=. /* changing this can cause hard to detect errs */'; put 'noquotelenmax /* avoid warnings for long strings */'; put 'noreplace /* avoid overwriting permanent datasets */'; put 'ps=max /* reduce log size slightly */'; put 'ls=max /* reduce log even more and avoid word truncation */'; put 'validmemname=COMPATIBLE /* avoid special characters etc in table names */'; put 'validvarname=V7 /* avoid special characters etc in variable names */'; put 'varinitchk=%str(ERR)OR /* avoid data mistakes from variable name typos */'; put 'varlenchk=%str(ERR)OR /* fail hard if truncation (data loss) can result */'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5" %then %do;'; put 'noautocorrect /* disallow misspelled procedure names */'; put 'dsoptions=note2err /* undocumented - convert bad NOTEs to ERRs */'; put '%end;'; put ';'; put '%mend mp_init;'; put '%macro mpeinit(fetch=YES);'; put '%global mpeinit'; put 'mpeadmins /* group with unrestricted Meditor access */'; put 'mpelocapprovals /* location for landing and staging files */'; put 'mpelib /* location of configuration tables for DC */'; put 'dc_repo_users /* location of user / group metadata */'; put 'dc_licence_key /* extracted in dc_getsettings */'; put 'dc_activation_key /* extracted in dc_getsettings */'; put 'dc_locale /* extracted in dc_getsettings */'; put 'dc_dttmtfmt /* can be overridden in dc_getsettings */'; put '_debug'; put ';'; put '%if &mpeinit=1 %then %return;'; put '%else %let mpeinit=1;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(Problem on service startup (&syswarningtext &syserrortext))'; put ')'; put '%mp_init()'; put '%if &fetch=YES %then %do;'; put '%webout(FETCH)'; put '%end;'; put '%global _CLIENTNAME;'; put '%mp_abort(iftrue= (&_CLIENTNAME=SAS Enterprise Guide)'; put ',mac=&_program..sas'; put ',msg=%str(Data Controller is a web app and should not be executed from EG)'; put ')'; put 'options urlencoding=utf8 nobomfile lrecl=32767;'; put '%let perf=%sysfunc(datetime());'; put '%put perfdiff: 0;'; put '%let dc_locale=SYSTEM; /* default if not set */'; put '/**'; put '* E8601DT26.6 has widest database support - but not all SAS flavours can'; put '* handle it. Override in the settings STP if needed.'; put '*/'; put 'data _null_;'; put 'dc_dttmtfmt=''"%sysfunc(datetime(),''!!"%mf_fmtdttm()"!!'')"dt'';'; put 'call symputx(''dc_dttmtfmt'',dc_dttmtfmt);'; put 'put dc_dttmtfmt=;'; put 'run;'; put '%put &=dc_dttmtfmt;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc prior to dc_getsettings)'; put ')'; put '%dc_getsettings()'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc after dc_getsettings)'; put ')'; put 'data _null_;'; put 'set &DC_LIBREF..mpe_config(where=('; put 'var_scope="DC"'; put 'and &dc_dttmtfmt lt tx_to'; put 'and var_active=1'; put '));'; put 'call symputx(var_name,var_value,''G'');'; put 'putlog var_name "=" var_value;'; put 'run;'; put '%let mpelib=&dc_libref;'; put '%let mpeadmins=&dc_admin_group;'; put '%let mpelocapprovals=&dc_staging_area;'; put '%let dc_repo_users=&dc_repo_users;'; put '%if &dc_locale ne SYSTEM %then %do;'; put 'options locale=&dc_locale;'; put '%end;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(Problem during compilation or with STP precode (&syswarningtext))'; put ')'; put '%mend mpeinit;'; put '%macro mf_mval(var);'; put '%if %symexist(&var) %then %do;'; put '%superq(&var)'; put '%end;'; put '%mend mf_mval;'; put '%macro mf_trimstr(basestr,trimstr);'; put '%local baselen trimlen trimval;'; put '/* return if basestr is shorter than trimstr (or 0) */'; put '%let baselen=%length(%superq(basestr));'; put '%let trimlen=%length(%superq(trimstr));'; put '%if &baselen < &trimlen or &baselen=0 %then %return;'; put '/* obtain the characters from the end of basestr */'; put '%let trimval=%qsubstr(%superq(basestr)'; put ',%length(%superq(basestr))-&trimlen+1'; put ',&trimlen);'; put '/* compare and if matching, chop it off! */'; put '%if %superq(basestr)=%superq(trimstr) %then %do;'; put '%return;'; put '%end;'; put '%else %if %superq(trimval)=%superq(trimstr) %then %do;'; put '%qsubstr(%superq(basestr),1,%length(%superq(basestr))-&trimlen)'; put '%end;'; put '%else %do;'; put '&basestr'; put '%end;'; put '%mend mf_trimstr;'; put '%macro mf_getplatform(switch'; put ')/*/STORE SOURCE*/;'; put '%local a b c;'; put '%if &switch.NONE=NONE %then %do;'; put '%if %symexist(sasjsprocessmode) %then %do;'; put '%if &sasjsprocessmode=Stored Program %then %do;'; put 'SASJS'; put '%return;'; put '%end;'; put '%end;'; put '%if %symexist(sysprocessmode) %then %do;'; put '%if "&sysprocessmode"="SAS Object Server"'; put 'or "&sysprocessmode"= "SAS Compute Server" %then %do;'; put 'SASVIYA'; put '%end;'; put '%else %if "&sysprocessmode"="SAS Stored Process Server"'; put 'or "&sysprocessmode"="SAS Workspace Server"'; put '%then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if %symexist(_metaport) or %symexist(_metauser) %then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if &switch=SASSTUDIO %then %do;'; put '/* return the version of SAS Studio else 0 */'; put '%if %mf_mval(_CLIENTAPP)=%str(SAS Studio) %then %do;'; put '%let a=%mf_mval(_CLIENTVERSION);'; put '%let b=%scan(&a,1,.);'; put '%if %eval(&b >2) %then %do;'; put '&b'; put '%end;'; put '%else 0;'; put '%end;'; put '%else 0;'; put '%end;'; put '%else %if &switch=VIYARESTAPI %then %do;'; put '%mf_trimstr(%sysfunc(getoption(servicesbaseurl)),/)'; put '%end;'; put '%mend mf_getplatform;'; put '%macro mpeterm();'; put '%local oldloc;'; put 'data _null_;'; put 'if symexist(''SYSPRINTTOLOG'') then oldloc=symget(''SYSPRINTTOLOG'');'; put 'else oldloc=getoption(''LOG'');'; put 'if subpad(oldloc,1,1) not in (''"'',"''",'' '') then oldloc=quote(cats(oldloc));'; put 'call symputx(''oldloc'',oldloc,''l'');'; put 'run;'; put '%if %length(&oldloc)>0 %then %do;'; put 'proc printto log=log;'; put 'run;'; put 'data _null_;'; put 'infile &oldloc;'; put 'input; putlog _infile_;'; put 'run;'; put '%end;'; put '%if %sysfunc(exist(&dc_libref..mpe_requests)) and %mf_getplatform() ne SASVIYA'; put '%then %do;'; put 'data ;'; put 'if 0 then set &dc_libref..mpe_requests;'; put 'request_dttm=%sysfunc(datetime());'; put 'request_user="%mf_getuser()";'; put 'request_service="%scan(&_program,-2,/)/%scan(&_program,-1,/)";'; put 'request_params='''';'; put 'output;stop;'; put 'proc append base=&dc_libref..mpe_requests data=&syslast force nowarn;'; put 'run;'; put '%end;'; put '%mend mpeterm;'; put '%macro mm_assignlib('; put 'libref'; put ',mAbort=HARD'; put ')/*/STORE SOURCE*/;'; put '%local mp_abort msg;'; put '%let mp_abort=0;'; put '%if %sysfunc(libref(&libref)) %then %do;'; put 'data _null_;'; put 'length liburi LibName msg $200;'; put 'call missing(of _all_);'; put 'nobj=metadata_getnobj("omsobj:SASLibrary?@Libref=''&libref''",1,liburi);'; put 'if nobj=1 then do;'; put 'rc=metadata_getattr(liburi,"Name",LibName);'; put '/* now try and assign it */'; put 'if libname("&libref",,''meta'',cats(''liburi="'',liburi,''";'')) ne 0 then do;'; put 'putlog "&libref could not be assigned";'; put 'putlog liburi=;'; put '/**'; put '* Fetch the system message for display in the abort modal. This is'; put '* not always helpful though. One example, previously received:'; put '* NOTE: Libref XX refers to the same library metadata as libref XX.'; put '*/'; put 'msg=sysmsg();'; put 'if msg=:''ERROR: Libref SAVE is not assigned.'' then do;'; put 'msg=catx(" ",'; put '"Could not assign %upcase(&libref).",'; put '"Please check metadata permissions! Libname:",libname,'; put '"Liburi:",liburi'; put ');'; put 'end;'; put 'else if msg="ERROR: User does not have appropriate authorization "!!'; put '"level for library SAVE."'; put 'then do;'; put 'msg=catx(" ",'; put '"ERROR: User does not have appropriate authorization level",'; put '"for library %upcase(&libref), libname:",libname,'; put '"Liburi:",liburi'; put ');'; put 'end;'; put 'call symputx(''msg'',msg,''l'');'; put 'if "&mabort"=''HARD'' then call symputx(''mp_abort'',1,''l'');'; put 'end;'; put 'else do;'; put 'put (_all_)(=);'; put 'call symputx(''libname'',libname,''L'');'; put 'call symputx(''liburi'',liburi,''L'');'; put 'end;'; put 'end;'; put 'else if nobj>1 then do;'; put 'if "&mabort"=''HARD'' then call symputx(''mp_abort'',1);'; put 'call symputx(''msg'',"More than one library with libref=&libref");'; put 'end;'; put 'else do;'; put 'if "&mabort"=''HARD'' then call symputx(''mp_abort'',1);'; put 'call symputx(''msg'',"Library &libref not found in metadata");'; put 'end;'; put 'run;'; put '%put NOTE: &msg;'; put '%end;'; put '%else %do;'; put '%put NOTE: Library &libref is already assigned;'; put '%end;'; put '%mp_abort(iftrue= (&mp_abort=1)'; put ',mac=mm_assignlib.sas'; put ',msg=%superq(msg)'; put ')'; put '%mend mm_assignlib;'; put '/** @cond */'; put '%macro mf_getengine(libref'; put ')/*/STORE SOURCE*/;'; put '%local dsid engnum rc engine;'; put '/* in case the parameter is a libref.tablename, pull off just the libref */'; put '%let libref = %upcase(%scan(&libref, 1, %str(.)));'; put '%let dsid=%sysfunc('; put 'open(sashelp.vlibnam(where=(libname="%upcase(&libref)")),i)'; put ');'; put '%if (&dsid ^= 0) %then %do;'; put '%let engnum=%sysfunc(varnum(&dsid,ENGINE));'; put '%let rc=%sysfunc(fetch(&dsid));'; put '%let engine=%sysfunc(getvarc(&dsid,&engnum));'; put '%put &libref. ENGINE is &engine.;'; put '%let rc= %sysfunc(close(&dsid));'; put '%end;'; put '%upcase(&engine)'; put '%mend mf_getengine;'; put '/** @endcond */'; put '%macro mm_assigndirectlib('; put 'libref'; put ',open_passthrough='; put ',sql_options='; put ',mDebug=0'; put ',mAbort=0'; put ')/*/STORE SOURCE*/;'; put '%local mD;'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing mm_assigndirectlib.sas;'; put '%&mD.put _local_;'; put '%if &mAbort=1 %then %let mAbort=;'; put '%else %let mAbort=%str(*);'; put '%&mD.put NOTE: Creating direct (non META) connection to &libref library;'; put '%local cur_engine;'; put '%let cur_engine=%mf_getengine(&libref);'; put '%if &cur_engine ne META and &cur_engine ne %then %do;'; put '%put NOTE: &libref already has a direct (&cur_engine) libname connection;'; put '%return;'; put '%end;'; put '%else %if %upcase(&libref)=WORK %then %do;'; put '%put NOTE: We already have a direct connection to WORK :-) ;'; put '%return;'; put '%end;'; put '/* need to determine the library ENGINE first */'; put '%local engine;'; put 'data _null_;'; put 'length lib_uri engine $256;'; put 'call missing (of _all_);'; put '/* get URI for the particular library */'; put 'rc1=metadata_getnobj("omsobj:SASLibrary?@Libref =''&libref''",1,lib_uri);'; put '/* get the Engine attribute of the previous object */'; put 'rc2=metadata_getattr(lib_uri,''Engine'',engine);'; put 'putlog "mm_assigndirectlib for &libref:" rc1= lib_uri= rc2= engine=;'; put 'call symputx("liburi",lib_uri,''l'');'; put 'call symputx("engine",engine,''l'');'; put 'run;'; put '/* now obtain engine specific connection details */'; put '%if &engine=BASE %then %do;'; put '%&mD.put NOTE: Retrieving BASE library path;'; put 'data _null_;'; put 'length up_uri $256 path cat_path $1024;'; put 'retain cat_path;'; put 'call missing (of _all_);'; put '/* get all the filepaths of the UsingPackages association */'; put 'i=1;'; put 'rc3=metadata_getnasn("&liburi",''UsingPackages'',i,up_uri);'; put 'do while (rc3>0);'; put '/* get the DirectoryName attribute of the previous object */'; put 'rc4=metadata_getattr(up_uri,''DirectoryName'',path);'; put 'if i=1 then path = ''("''!!trim(path)!!''" '';'; put 'else path ='' "''!!trim(path)!!''" '';'; put 'cat_path = trim(cat_path) !! " " !! trim(path) ;'; put 'i+1;'; put 'rc3=metadata_getnasn("&liburi",''UsingPackages'',i,up_uri);'; put 'end;'; put 'cat_path = trim(cat_path) !! ")";'; put '&mD.putlog "NOTE: Getting physical path for &libref library";'; put '&mD.putlog rc3= up_uri= rc4= cat_path= path=;'; put '&mD.putlog "NOTE: Libname cmd will be:";'; put '&mD.putlog "libname &libref" cat_path;'; put 'call symputx("filepath",cat_path,''l'');'; put 'run;'; put '%if %sysevalf(&sysver<9.4) %then %do;'; put 'libname &libref &filepath;'; put '%end;'; put '%else %do;'; put '/* apply the new filelocks option to cater for temporary locks */'; put 'libname &libref &filepath filelockwait=5;'; put '%end;'; put '%end;'; put '%else %if &engine=REMOTE %then %do;'; put 'data x;'; put 'length rcCon rcProp rc k 3 uriCon uriProp PropertyValue PropertyName'; put 'Delimiter $256 properties $2048;'; put 'retain properties;'; put 'rcCon = metadata_getnasn("&liburi", "LibraryConnection", 1, uriCon);'; put 'rcProp = metadata_getnasn(uriCon, "Properties", 1, uriProp);'; put 'k = 1;'; put 'rcProp = metadata_getnasn(uriCon, "Properties", k, uriProp);'; put 'do while (rcProp > 0);'; put 'rc = metadata_getattr(uriProp , "DefaultValue",PropertyValue);'; put 'rc = metadata_getattr(uriProp , "PropertyName",PropertyName);'; put 'rc = metadata_getattr(uriProp , "Delimiter",Delimiter);'; put 'properties = trim(properties) !! " " !! trim(PropertyName)'; put '!! trim(Delimiter) !! trim(PropertyValue);'; put 'output;'; put 'k+1;'; put 'rcProp = metadata_getnasn(uriCon, "Properties", k, uriProp);'; put 'end;'; put '%&mD.put NOTE: Getting properties for REMOTE SHARE &libref library;'; put '&mD.put _all_;'; put '%&mD.put NOTE: Libname cmd will be:;'; put '%&mD.put libname &libref &engine &properties slibref=&libref;'; put 'call symputx ("properties",trim(properties),''l'');'; put 'run;'; put 'libname &libref &engine &properties slibref=&libref;'; put '%end;'; put '%else %if &engine=OLEDB %then %do;'; put '%&mD.put NOTE: Retrieving OLEDB connection details;'; put 'data _null_;'; put 'length domain datasource provider properties schema'; put 'connx_uri domain_uri conprop_uri lib_uri schema_uri value $256.;'; put 'call missing (of _all_);'; put '/* get source connection ID */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,connx_uri);'; put '/* get connection domain */'; put 'rc1=metadata_getnasn(connx_uri,''Domain'',1,domain_uri);'; put 'rc2=metadata_getattr(domain_uri,''Name'',domain);'; put '&mD.putlog / ''NOTE: '' // ''NOTE- connection id: '' connx_uri ;'; put '&mD.putlog ''NOTE- domain: '' domain;'; put '/* get DSN and PROVIDER from connection properties */'; put 'i=0;'; put 'do until (rc<0);'; put 'i+1;'; put 'rc=metadata_getnasn(connx_uri,''Properties'',i,conprop_uri);'; put 'rc2=metadata_getattr(conprop_uri,''Name'',value);'; put 'if value=''Connection.OLE.Property.DATASOURCE.Name.xmlKey.txt'' then do;'; put 'rc3=metadata_getattr(conprop_uri,''DefaultValue'',datasource);'; put 'end;'; put 'else if value=''Connection.OLE.Property.PROVIDER.Name.xmlKey.txt'' then do;'; put 'rc4=metadata_getattr(conprop_uri,''DefaultValue'',provider);'; put 'end;'; put 'else if value=''Connection.OLE.Property.PROPERTIES.Name.xmlKey.txt'' then'; put 'do;'; put 'rc5=metadata_getattr(conprop_uri,''DefaultValue'',properties);'; put 'end;'; put 'end;'; put '&mD.putlog ''NOTE- dsn/provider/properties: '' /'; put 'datasource provider properties;'; put '&mD.putlog ''NOTE- schema: '' schema // ''NOTE-'';'; put '/* get SCHEMA */'; put 'rc6=metadata_getnasn("&liburi",''UsingPackages'',1,lib_uri);'; put 'rc7=metadata_getattr(lib_uri,''SchemaName'',schema);'; put 'call symputx(''SQL_domain'',domain,''l'');'; put 'call symputx(''SQL_dsn'',datasource,''l'');'; put 'call symputx(''SQL_provider'',provider,''l'');'; put 'call symputx(''SQL_properties'',properties,''l'');'; put 'call symputx(''SQL_schema'',schema,''l'');'; put 'run;'; put '%if %length(&open_passthrough)>0 %then %do;'; put 'proc sql &sql_options;'; put 'connect to OLEDB as &open_passthrough(INSERT_SQL=YES'; put '/* need additional properties to make this work */'; put 'properties=(''Integrated Security''=SSPI'; put '''Persist Security Info''=True'; put '%sysfunc(compress(%str(&SQL_properties),%str(())))'; put ')'; put 'DATASOURCE=&sql_dsn PROMPT=NO'; put 'PROVIDER=&sql_provider SCHEMA=&sql_schema CONNECTION = GLOBAL);'; put '%end;'; put '%else %do;'; put 'LIBNAME &libref OLEDB PROPERTIES=&sql_properties'; put 'DATASOURCE=&sql_dsn PROVIDER=&sql_provider SCHEMA=&sql_schema'; put '%if %length(&sql_domain)>0 %then %do;'; put 'authdomain="&sql_domain"'; put '%end;'; put 'connection=shared;'; put '%end;'; put '%end;'; put '%else %if &engine=ODBC %then %do;'; put '%&mD.put NOTE: Retrieving ODBC connection details;'; put 'data _null_;'; put 'length connx_uri conprop_uri value datasource up_uri schema domprop_uri authdomain $256.;'; put 'call missing (of _all_);'; put '/* get source connection ID */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,connx_uri);'; put '/* get connection properties */'; put 'i=0;'; put 'do until (rc2<0);'; put 'i+1;'; put 'rc2=metadata_getnasn(connx_uri,''Properties'',i,conprop_uri);'; put 'rc3=metadata_getattr(conprop_uri,''Name'',value);'; put 'if value=''Connection.ODBC.Property.DATASRC.Name.xmlKey.txt'' then do;'; put 'rc4=metadata_getattr(conprop_uri,''DefaultValue'',datasource);'; put 'rc2=-1;'; put 'end;'; put 'end;'; put '/* get auth domain */'; put 'autrc=metadata_getnasn(connx_uri,"Domain",1,domprop_uri);'; put 'arc=metadata_getattr(domprop_uri,"Name",authdomain);'; put 'if not missing(authdomain) then authdomain=cats(''AUTHDOMAIN='',authdomain);'; put 'call symputx(''authdomain'',authdomain,''l'');'; put '/* get SCHEMA */'; put 'rc6=metadata_getnasn("&liburi",''UsingPackages'',1,up_uri);'; put 'rc7=metadata_getattr(up_uri,''SchemaName'',schema);'; put '&mD.put rc= connx_uri= rc2= conprop_uri= rc3= value= rc4= datasource='; put 'rc6= up_uri= rc7= schema=;'; put 'call symputx(''SQL_schema'',schema,''l'');'; put 'call symputx(''SQL_dsn'',datasource,''l'');'; put 'run;'; put '%if %length(&open_passthrough)>0 %then %do;'; put 'proc sql &sql_options;'; put 'connect to ODBC as &open_passthrough'; put '(INSERT_SQL=YES DATASRC=&sql_dsn. CONNECTION=global);'; put '%end;'; put '%else %do;'; put 'libname &libref ODBC DATASRC=&sql_dsn SCHEMA=&sql_schema &authdomain;'; put '%end;'; put '%end;'; put '%else %if &engine=POSTGRES %then %do;'; put '%put NOTE: Obtaining POSTGRES library details;'; put 'data _null_;'; put 'length database ignore_read_only_columns direct_exe preserve_col_names'; put 'preserve_tab_names server schema authdomain user password'; put 'prop name value uri urisrc $256.;'; put 'call missing (of _all_);'; put '/* get database value */'; put 'prop=''Connection.DBMS.Property.DB.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,database,"");'; put 'if database^='''' then database=''database=''!!quote(trim(database));'; put 'call symputx(''database'',database,''l'');'; put '/* get IGNORE_READ_ONLY_COLUMNS value */'; put 'prop=''Library.DBMS.Property.DBIROC.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,ignore_read_only_columns,"");'; put 'if ignore_read_only_columns^='''' then ignore_read_only_columns='; put '''ignore_read_only_columns=''!!ignore_read_only_columns;'; put 'call symputx(''ignore_read_only_columns'',ignore_read_only_columns,''l'');'; put '/* get DIRECT_EXE value */'; put 'prop=''Library.DBMS.Property.DirectExe.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,direct_exe,"");'; put 'if direct_exe^='''' then direct_exe=''direct_exe=''!!direct_exe;'; put 'call symputx(''direct_exe'',direct_exe,''l'');'; put '/* get PRESERVE_COL_NAMES value */'; put 'prop=''Library.DBMS.Property.PreserveColNames.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,preserve_col_names,"");'; put 'if preserve_col_names^='''' then preserve_col_names='; put '''preserve_col_names=''!!preserve_col_names;'; put 'call symputx(''preserve_col_names'',preserve_col_names,''l'');'; put '/* get PRESERVE_TAB_NAMES value */'; put '/* be careful with PRESERVE_TAB_NAMES=YES - it will mean your table will'; put 'become case sensitive!! */'; put 'prop=''Library.DBMS.Property.PreserveTabNames.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,preserve_tab_names,"");'; put 'if preserve_tab_names^='''' then preserve_tab_names='; put '''preserve_tab_names=''!!preserve_tab_names;'; put 'call symputx(''preserve_tab_names'',preserve_tab_names,''l'');'; put '/* get SERVER value */'; put 'if metadata_getnasn("&liburi","LibraryConnection",1,uri)>0 then do;'; put 'prop=''Connection.DBMS.Property.SERVER.Name.xmlKey.txt'';'; put 'rc=metadata_getprop(uri,prop,server,"");'; put 'end;'; put 'if server^='''' then server=''server=''!!quote(cats(server));'; put 'call symputx(''server'',server,''l'');'; put '/* get SCHEMA value */'; put 'if metadata_getnasn("&liburi","UsingPackages",1,uri)>0 then do;'; put 'rc=metadata_getattr(uri,"SchemaName",schema);'; put 'end;'; put 'if schema^='''' then schema=''schema=''!!schema;'; put 'call symputx(''schema'',schema,''l'');'; put '/* get AUTHDOMAIN value */'; put '/* this is only useful if the user account contains that auth domain'; put 'if metadata_getnasn("&liburi","DefaultLogin",1,uri)>0 then do;'; put 'rc=metadata_getnasn(uri,"Domain",1,urisrc);'; put 'rc=metadata_getattr(urisrc,"Name",authdomain);'; put 'end;'; put 'if authdomain^='''' then authdomain=''authdomain=''!!quote(trim(authdomain));'; put '*/'; put 'call symputx(''authdomain'',authdomain,''l'');'; put '/* get user & pass */'; put 'if authdomain='''' & metadata_getnasn("&liburi","DefaultLogin",1,uri)>0 then'; put 'do;'; put 'rc=metadata_getattr(uri,"UserID",user);'; put 'rc=metadata_getattr(uri,"Password",password);'; put 'end;'; put 'if user^='''' then do;'; put 'user=''user=''!!quote(trim(user));'; put 'password=''password=''!!quote(trim(password));'; put 'end;'; put 'call symputx(''user'',user,''l'');'; put 'call symputx(''password'',password,''l'');'; put '&md.put _all_;'; put 'run;'; put '%if %length(&open_passthrough)>0 %then %do;'; put '%put %str(WARN)ING: Passthrough option for postgres not yet supported;'; put '%return;'; put '%end;'; put '%else %do;'; put '%if &mdebug=1 %then %do;'; put '%put NOTE: Executing the following:/;'; put '%put NOTE- libname &libref POSTGRES &database &ignore_read_only_columns;'; put '%put NOTE- &direct_exe &preserve_col_names &preserve_tab_names;'; put '%put NOTE- &server &schema &authdomain &user &password //;'; put '%end;'; put 'libname &libref POSTGRES &database &ignore_read_only_columns &direct_exe'; put '&preserve_col_names &preserve_tab_names &server &schema &authdomain'; put '&user &password;'; put '%end;'; put '%end;'; put '%else %if &engine=ORACLE %then %do;'; put '%put NOTE: Obtaining &engine library details;'; put 'data _null_;'; put 'length assocuri1 assocuri2 assocuri3 authdomain path schema $256;'; put 'call missing (of _all_);'; put '/* get auth domain */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,assocuri1);'; put 'rc=metadata_getnasn(assocuri1,''Domain'',1,assocuri2);'; put 'rc=metadata_getattr(assocuri2,"Name",authdomain);'; put 'call symputx(''authdomain'',authdomain,''l'');'; put '/* path */'; put 'rc=metadata_getprop(assocuri1,'; put '''Connection.Oracle.Property.PATH.Name.xmlKey.txt'',path);'; put 'call symputx(''path'',path,''l'');'; put '/* schema */'; put 'rc=metadata_getnasn("&liburi",''UsingPackages'',1,assocuri3);'; put 'rc=metadata_getattr(assocuri3,''SchemaName'',schema);'; put 'call symputx(''schema'',schema,''l'');'; put 'run;'; put '%put NOTE: Executing the following:/; %put NOTE-;'; put '%put NOTE- libname &libref ORACLE path=&path schema=&schema;'; put '%put NOTE- authdomain=&authdomain;'; put '%put NOTE-;'; put 'libname &libref ORACLE path=&path schema=&schema authdomain=&authdomain;'; put '%end;'; put '%else %if &engine=SQLSVR %then %do;'; put '%put NOTE: Obtaining &engine library details;'; put 'data _null;'; put 'length assocuri1 assocuri2 assocuri3 authdomain path schema userid'; put 'passwd $256;'; put 'call missing (of _all_);'; put 'rc=metadata_getnasn("&liburi",''DefaultLogin'',1,assocuri1);'; put 'rc=metadata_getattr(assocuri1,"UserID",userid);'; put 'rc=metadata_getattr(assocuri1,"Password",passwd);'; put 'call symputx(''user'',userid,''l'');'; put 'call symputx(''pass'',passwd,''l'');'; put '/* path */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,assocuri2);'; put 'rc=metadata_getprop(assocuri2,'; put '''Connection.SQL.Property.Datasrc.Name.xmlKey.txt'',path);'; put 'call symputx(''path'',path,''l'');'; put '/* schema */'; put 'rc=metadata_getnasn("&liburi",''UsingPackages'',1,assocuri3);'; put 'rc=metadata_getattr(assocuri3,''SchemaName'',schema);'; put 'call symputx(''schema'',schema,''l'');'; put 'run;'; put '%put NOTE: Executing the following:/; %put NOTE-;'; put '%put NOTE- libname &libref SQLSVR datasrc=&path schema=&schema ;'; put '%put NOTE- user="&user" pass="XXX";'; put '%put NOTE-;'; put 'libname &libref SQLSVR datasrc=&path schema=&schema user="&user" pass="&pass";'; put '%end;'; put '%else %if &engine=TERADATA %then %do;'; put '%put NOTE: Obtaining &engine library details;'; put 'data _null;'; put 'length assocuri1 assocuri2 assocuri3 authdomain path schema userid'; put 'passwd $256;'; put 'call missing (of _all_);'; put '/* get auth domain */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,assocuri1);'; put 'rc=metadata_getnasn(assocuri1,''Domain'',1,assocuri2);'; put 'rc=metadata_getattr(assocuri2,"Name",authdomain);'; put 'call symputx(''authdomain'',authdomain,''l'');'; put '/*'; put 'rc=metadata_getnasn("&liburi",''DefaultLogin'',1,assocuri1);'; put 'rc=metadata_getattr(assocuri1,"UserID",userid);'; put 'rc=metadata_getattr(assocuri1,"Password",passwd);'; put 'call symputx(''user'',userid,''l'');'; put 'call symputx(''pass'',passwd,''l'');'; put '*/'; put '/* path */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,assocuri2);'; put 'rc=metadata_getprop(assocuri2,'; put '''Connection.Teradata.Property.SERVER.Name.xmlKey.txt'',path);'; put 'call symputx(''path'',path,''l'');'; put '/* schema */'; put 'rc=metadata_getnasn("&liburi",''UsingPackages'',1,assocuri3);'; put 'rc=metadata_getattr(assocuri3,''SchemaName'',schema);'; put 'call symputx(''schema'',schema,''l'');'; put 'run;'; put '%put NOTE: Executing the following:/; %put NOTE-;'; put '%put NOTE- libname &libref TERADATA server="&path" schema=&schema ;'; put '%put NOTe- authdomain=&authdomain;'; put '%put NOTE-;'; put 'libname &libref TERADATA server="&path" schema=&schema authdomain=&authdomain;'; put '%end;'; put '%else %if &engine= %then %do;'; put '%put NOTE: Libref &libref is not registered in metadata;'; put '%&mAbort.mp_abort('; put 'msg=%str(ERR)OR: Libref &libref is not registered in metadata'; put ',mac=mm_assigndirectlib.sas);'; put '%return;'; put '%end;'; put '%else %do;'; put '%put %str(WARN)ING: Engine &engine is currently unsupported;'; put '%put %str(WARN)ING- Please contact your support team.;'; put '%return;'; put '%end;'; put '%mend mm_assigndirectlib;'; put '%macro mm_getrepos('; put 'outds=work.mm_getrepos'; put ')/*/STORE SOURCE*/;'; put '* use a temporary fileref to hold the response;'; put 'filename response temp;'; put '/* get list of libraries */'; put 'proc metadata in='; put '"1"'; put 'out=response;'; put 'run;'; put '/* write the response to the log for debugging */'; put '/*'; put 'data _null_;'; put 'infile response lrecl=1048576;'; put 'input;'; put 'put _infile_;'; put 'run;'; put '*/'; put '/* create an XML map to read the response */'; put 'filename sxlemap temp;'; put 'data _null_;'; put 'file sxlemap;'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository";'; put 'put "";'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Id";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Name";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Desc";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@DefaultNS";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@RepositoryType";'; put 'put "characterstring20";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@RepositoryFormat";'; put 'put "characterstring10";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@Access";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@CurrentAccess";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@PauseState";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Path";'; put 'put "";'; put 'put "characterstring256";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Engine";'; put 'put "";'; put 'put "characterstring8";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Options";'; put 'put "";'; put 'put "characterstring32";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@MetadataCreated";'; put 'put "characterstring24";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@MetadataUpdated";'; put 'put "characterstring24";'; put 'put '''';'; put 'put ''
'';'; put 'run;'; put 'libname _XML_ xml xmlfileref=response xmlmap=sxlemap;'; put 'proc sort data= _XML_.SASRepos out=&outds;'; put 'by name;'; put 'run;'; put '/* clear references */'; put 'filename sxlemap clear;'; put 'filename response clear;'; put 'libname _XML_ clear;'; put '%mend mm_getrepos;'; put '%macro dc_assignlib(type,libref,passthru=);'; put '%put &sysmacroname entry vars:;'; put '%put _local_;'; put '/* take current repository */'; put '%local repo repocnt x;'; put '%let repo=%sysfunc(getoption(metarepository));'; put '/* get list of repositories and filter */'; put '%mm_getrepos(outds=repos)'; put '%let repocnt=0;'; put 'data repos;'; put 'set repos;'; put 'where repositorytype in(''CUSTOM'',''FOUNDATION'')'; put '& upcase(name) ne "%upcase(&repo)";'; put 'keep id name ;'; put 'call symputx(cats(''repo'',_n_),name,''l'');'; put 'call symputx(''repocnt'',_n_,''l'');'; put 'run;'; put '/* find out which of these repositories has the libref we are searching for */'; put '%local lib_uri;'; put '%let lib_uri=NOTFOUND;'; put 'data _null_; /* check default repo first */'; put 'length lib_uri $256;'; put 'call missing (of _all_);'; put 'rc=metadata_getnobj("omsobj:SASLibrary?@Libref =''&libref''",1,lib_uri);'; put 'call symputx(''lib_uri'',coalescec(lib_uri,''NOTFOUND''),''l'');'; put 'run;'; put '%if &lib_uri=NOTFOUND %then %do;'; put '%do x=1 %to &repocnt;'; put 'options metarepository=&&repo&x;'; put 'data _null_;'; put 'length lib_uri $256;'; put 'call missing (of _all_);'; put 'rc=metadata_getnobj("omsobj:SASLibrary?@Libref =''&libref''",1,lib_uri);'; put 'call symputx(''lib_uri'',coalescec(lib_uri,''NOTFOUND''),''l'');'; put 'run;'; put '%if &lib_uri ne NOTFOUND %then %let x=%eval(&repocnt+1);'; put '%end;'; put '%end;'; put '%if &type=READ %then %do;'; put '%mm_assignlib(&libref)'; put '%end;'; put '%else %if &type=WRITE %then %do;'; put '%mm_assigndirectlib(&libref,open_passthrough=&passthru)'; put '%end;'; put 'options metarepository=&repo;'; put '%mend dc_assignlib;'; put '%macro mf_getattrn('; put 'libds'; put ',attr'; put ')/*/STORE SOURCE*/;'; put '%local dsid rc;'; put '%let dsid=%sysfunc(open(&libds,is));'; put '%if &dsid = 0 %then %do;'; put '%put %str(WARN)ING: Cannot open %trim(&libds), system message below;'; put '%put %sysfunc(sysmsg());'; put '-1'; put '%end;'; put '%else %do;'; put '%sysfunc(attrn(&dsid,&attr))'; put '%let rc=%sysfunc(close(&dsid));'; put '%end;'; put '%mend mf_getattrn;'; put '%macro mf_getvalue(libds,variable,filter=1'; put ')/*/STORE SOURCE*/;'; put '%if %mf_getattrn(&libds,NLOBS)>0 %then %do;'; put '%local dsid rc &variable;'; put '%let dsid=%sysfunc(open(&libds(where=(&filter))));'; put '%syscall set(dsid);'; put '%let rc = %sysfunc(fetch(&dsid));'; put '%let rc = %sysfunc(close(&dsid));'; put '%trim(&&&variable)'; put '%end;'; put '%mend mf_getvalue;'; put '* SAS Macros end;'; put '* SAS Includes start;'; put '* SAS Includes end;'; put '* Binary Files start;'; put '* Binary Files end;'; put '* ServiceInit start;'; put 'options noquotelenmax ps=max;'; put '/* create dummy macros to prevent stpbegin / stpend from corrupting sessions */'; put '%macro stpbegin();'; put '%put NOTE: the STPBEGIN macro should not be used for web apps!;'; put '%mend stpbegin;'; put '%macro stpend();'; put '%put NOTE: the STPEND macro should not be used for web apps!;'; put '%mend stpend;'; put '* ServiceInit end;'; put '* Service start;'; put '/**'; put '@file getcols.sas'; put '@brief Retrieves column info to enable population of dropdowns'; put '@details'; put '

SAS Macros

'; put '@li dc_assignlib.sas'; put '@li mf_getvalue.sas'; put '@li mp_abort.sas'; put '@version 9.2'; put '@author 4GL Apps Ltd'; put '@copyright 4GL Apps Ltd. This code may only be used within Data Controller'; put 'and may not be re-distributed or re-sold without the express permission of'; put '4GL Apps Ltd.'; put '**/'; put '%mpeinit()'; put '%let ds=%mf_getvalue(work.iwant,libds);'; put '%dc_assignlib(READ,%scan(&ds,1,.))'; put 'proc contents noprint data=&ds'; put 'out=droplist1 (keep=name type length label varnum format:);'; put 'run;'; put 'data cols(keep=name type length varnum format label);'; put 'set droplist1(rename=(format=format2 type=type2));'; put 'name=upcase(name);'; put 'if type2=2 then do;'; put 'length format $49.;'; put 'if format2='''' then format=cats(''$'',length,''.'');'; put 'else if formatl=0 then format=cats(format2,''.'');'; put 'else format=cats(format2,formatl,''.'');'; put 'type=''C'';'; put 'ddtype=''CHARACTER'';'; put 'end;'; put 'else do;'; put 'if format2='''' then format=cats(length,''.'');'; put 'else if formatl=0 then format=cats(format2,''.'');'; put 'else if formatd=0 then format=cats(format2,formatl,''.'');'; put 'else format=cats(format2,formatl,''.'',formatd);'; put 'type=''N'';'; put 'if format=:''DATETIME'' then ddtype=''DATETIME'';'; put 'else if format=:''DATE'' then ddtype=''DATE'';'; put 'else if format=:''TIME'' then ddtype=''TIME'';'; put 'else ddtype=''NUMERIC'';'; put 'end;'; put 'if label='''' then label=name;'; put 'run;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(syscc=&syscc)'; put ')'; put '%webout(OPEN)'; put '%webout(OBJ,cols)'; put '%webout(CLOSE)'; put '%mpeterm()'; put '* Service end;'; run; %mm_createwebservice(path=&appLoc/&path, name=&service, code=sascode, server=&serverName, replace=yes) filename sascode clear; %let service=getcolvals; filename sascode temp lrecl=32767; data _null_; file sascode; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '/**'; put '@file mp_jsonout.sas'; put '@brief Writes JSON in SASjs format to a fileref'; put '@details This macro can be used to OPEN a JSON stream and send one or more'; put 'tables as arrays of rows, where each row can be an object or a nested array.'; put 'There are two engines available - DATASTEP or PROCJSON.'; put 'PROC JSON is fast but will produce errs like the ones below if'; put 'special chars are encountered.'; put '> (ERR)OR: Some code points did not transcode.'; put '> An object or array close is not valid at this point in the JSON text.'; put '> Date value out of range'; put 'If this happens, try running with ENGINE=DATASTEP.'; put 'The DATASTEP engine is used to handle special SAS missing numerics, and'; put 'can also convert entire datasets to formatted values. Output JSON is always'; put 'in UTF-8.'; put 'Usage:'; put 'filename tmp temp;'; put 'data class; set sashelp.class;run;'; put '%mp_jsonout(OPEN,jref=tmp)'; put '%mp_jsonout(OBJ,class,jref=tmp)'; put '%mp_jsonout(OBJ,class,dslabel=class2,jref=tmp,showmeta=Y)'; put '%mp_jsonout(CLOSE,jref=tmp)'; put 'data _null_;'; put 'infile tmp;'; put 'input;putlog _infile_;'; put 'run;'; put 'If you are building web apps with SAS then you are strongly encouraged to use'; put 'the mX_createwebservice macros in combination with the'; put '[sasjs adapter](https://github.com/sasjs/adapter).'; put 'For more information see https://sasjs.io'; put '@param [in] action Valid values:'; put '@li OPEN - opens the JSON'; put '@li OBJ - sends a table with each row as an object'; put '@li ARR - sends a table with each row in an array'; put '@li CLOSE - closes the JSON'; put '@param [in] ds The dataset to send. Must be a work table.'; put '@param [out] jref= (_webout) The fileref to which to send the JSON'; put '@param [out] dslabel= The name to give the table in the exported JSON'; put '@param [in] fmt= (Y) Whether to keep (Y) or strip (N) formats from the table'; put '@param [in] engine= (DATASTEP) Which engine to use to send the JSON. Options:'; put '@li PROCJSON (default)'; put '@li DATASTEP (more reliable when data has non standard characters)'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to JSON'; put '

Related Files

'; put '@li mp_ds2fmtds.sas'; put '@version 9.2'; put '@author Allan Bowe'; put '@source https://github.com/sasjs/core'; put '**/'; put '%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y'; put ',engine=DATASTEP'; put ',missing=NULL'; put ',showmeta=N'; put ',maxobs=MAX'; put ')/*/STORE SOURCE*/;'; put '%local tempds colinfo fmtds i numcols numobs stmt_obs lastobs optval'; put 'tmpds1 tmpds2 tmpds3 tmpds4;'; put '%let numcols=0;'; put '%if &maxobs ne MAX %then %let stmt_obs=%str(if _n_>&maxobs then stop;);'; put '%if &action=OPEN %then %do;'; put 'options nobomfile;'; put 'data _null_;file &jref encoding=''utf-8'' lrecl=200;'; put 'put ''{"PROCESSED_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''"'';'; put 'run;'; put '%end;'; put '%else %if (&action=ARR or &action=OBJ) %then %do;'; put '/* force variable names to always be uppercase in the JSON */'; put 'options validvarname=upcase;'; put '/* To avoid issues with _webout on EBI - such as encoding diffs and truncation'; put '(https://support.sas.com/kb/49/325.html) we use temporary files */'; put 'filename _sjs1 temp lrecl=200 ;'; put 'data _null_; file _sjs1 encoding=''utf-8'';'; put 'put ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":";'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjs1 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs1 clear;'; put '/* grab col defs */'; put 'proc contents noprint data=&ds'; put 'out=_data_(keep=name type length format formatl formatd varnum label);'; put 'run;'; put '%let colinfo=%scan(&syslast,2,.);'; put 'proc sort data=&colinfo;'; put 'by varnum;'; put 'run;'; put '/* move meta to mac vars */'; put 'data &colinfo;'; put 'if _n_=1 then call symputx(''numcols'',nobs,''l'');'; put 'set &colinfo end=last nobs=nobs;'; put 'name=upcase(name);'; put '/* fix formats */'; put 'if type=2 or type=6 then do;'; put 'typelong=''char'';'; put 'length fmt $49.;'; put 'if format='''' then fmt=cats(''$'',length,''.'');'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else fmt=cats(format,formatl,''.'');'; put 'end;'; put 'else do;'; put 'typelong=''num'';'; put 'if format='''' then fmt=''best.'';'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else if formatd=0 then fmt=cats(format,formatl,''.'');'; put 'else fmt=cats(format,formatl,''.'',formatd);'; put 'end;'; put '/* 32 char unique name */'; put 'newname=''sasjs''!!substr(cats(put(md5(name),$hex32.)),1,27);'; put 'call symputx(cats(''name'',_n_),name,''l'');'; put 'call symputx(cats(''newname'',_n_),newname,''l'');'; put 'call symputx(cats(''length'',_n_),length,''l'');'; put 'call symputx(cats(''fmt'',_n_),fmt,''l'');'; put 'call symputx(cats(''type'',_n_),type,''l'');'; put 'call symputx(cats(''typelong'',_n_),typelong,''l'');'; put 'call symputx(cats(''label'',_n_),coalescec(label,name),''l'');'; put '/* overwritten when fmt=Y and a custom format exists in catalog */'; put 'if typelong=''num'' then call symputx(cats(''fmtlen'',_n_),200,''l'');'; put 'else call symputx(cats(''fmtlen'',_n_),min(32767,ceil((length+10)*1.5)),''l'');'; put 'run;'; put '%let tempds=%substr(_%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql;'; put 'select count(*) into: lastobs from &ds;'; put '%if &maxobs ne MAX %then %let lastobs=%sysfunc(min(&lastobs,&maxobs));'; put '%if &engine=PROCJSON %then %do;'; put '%if &missing=STRING %then %do;'; put '%put &sysmacroname: Special Missings not supported in proc json.;'; put '%put &sysmacroname: Switching to DATASTEP engine;'; put '%goto datastep;'; put '%end;'; put 'data &tempds;'; put 'set &ds;'; put '&stmt_obs;'; put '%if &fmt=N %then format _numeric_ best32.;;'; put '/* PRETTY is necessary to avoid line truncation in large files */'; put 'filename _sjs2 temp lrecl=131068 encoding=''utf-8'';'; put 'proc json out=_sjs2 pretty'; put '%if &action=ARR %then nokeys ;'; put ';export &tempds / nosastags fmtnumeric;'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs2 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs2 clear;'; put '%end;'; put '%else %if &engine=DATASTEP %then %do;'; put '%datastep:'; put '%if %sysfunc(exist(&ds)) ne 1 & %sysfunc(exist(&ds,VIEW)) ne 1'; put '%then %do;'; put '%put &sysmacroname: &ds NOT FOUND!!!;'; put '%return;'; put '%end;'; put '%if &fmt=Y %then %do;'; put '/**'; put '* Extract format definitions'; put '* First, by getting library locations from dictionary.formats'; put '* Then, by exporting the width using proc format'; put '* Cannot use maxw from sashelp.vformat as not always populated'; put '* Cannot use fmtinfo() as not supported in all flavours'; put '*/'; put '%let tmpds1=%substr(fmtsum%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds2=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds3=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds4=%substr(col%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql noprint;'; put 'create table &tmpds1 as'; put 'select cats(libname,''.'',memname) as FMTCAT,'; put 'FMTNAME'; put 'from dictionary.formats'; put 'where fmttype=''F'' and libname is not null'; put 'and fmtname in (select format from &colinfo where format is not null)'; put 'order by 1;'; put 'create table &tmpds2('; put 'FMTNAME char(32),'; put 'LENGTH num'; put ');'; put '%local catlist cat fmtlist i;'; put 'select distinct fmtcat into: catlist separated by '' '' from &tmpds1;'; put '%do i=1 %to %sysfunc(countw(&catlist,%str( )));'; put '%let cat=%scan(&catlist,&i,%str( ));'; put 'proc sql;'; put 'select distinct fmtname into: fmtlist separated by '' '''; put 'from &tmpds1 where fmtcat="&cat";'; put 'proc format lib=&cat cntlout=&tmpds3(keep=fmtname length);'; put 'select &fmtlist;'; put 'run;'; put 'proc sql;'; put 'insert into &tmpds2 select distinct fmtname,length from &tmpds3;'; put '%end;'; put 'proc sql;'; put 'create table &tmpds4 as'; put 'select a.*, b.length as MAXW'; put 'from &colinfo a'; put 'left join &tmpds2 b'; put 'on cats(a.format)=cats(upcase(b.fmtname))'; put 'order by a.varnum;'; put 'data _null_;'; put 'set &tmpds4;'; put 'if not missing(maxw);'; put 'call symputx('; put 'cats(''fmtlen'',_n_),'; put '/* vars need extra padding due to JSON escaping of special chars */'; put 'min(32767,ceil((max(length,maxw)+10)*1.5))'; put ',''l'''; put ');'; put 'run;'; put '/* configure varlenchk - as we are explicitly shortening the variables */'; put '%let optval=%sysfunc(getoption(varlenchk));'; put 'options varlenchk=NOWARN;'; put 'data _data_(compress=char);'; put '/* shorten the new vars */'; put 'length'; put '%do i=1 %to &numcols;'; put '&&name&i $&&fmtlen&i'; put '%end;'; put ';'; put '/* rename on entry */'; put 'set &ds(rename=('; put '%do i=1 %to &numcols;'; put '&&name&i=&&newname&i'; put '%end;'; put '));'; put '&stmt_obs;'; put 'drop'; put '%do i=1 %to &numcols;'; put '&&newname&i'; put '%end;'; put ';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=num %then %do;'; put '&&name&i=cats(put(&&newname&i,&&fmt&i));'; put '%end;'; put '%else %do;'; put '&&name&i=put(&&newname&i,&&fmt&i);'; put '%end;'; put '%end;'; put 'if _error_ then do;'; put 'call symputx(''syscc'',1012);'; put 'stop;'; put 'end;'; put 'run;'; put '%let fmtds=&syslast;'; put 'options varlenchk=&optval;'; put '%end;'; put 'proc format; /* credit yabwon for special null removal */'; put 'value bart (default=40)'; put '%if &missing=NULL %then %do;'; put '._ - .z = null'; put '%end;'; put '%else %do;'; put '._ = [quote()]'; put '. = null'; put '.a - .z = [quote()]'; put '%end;'; put 'other = [best.];'; put 'data &tempds;'; put 'attrib _all_ label='''';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'length &&name&i $&&fmtlen&i...;'; put 'format &&name&i $&&fmtlen&i...;'; put '%end;'; put '%end;'; put '%if &fmt=Y %then %do;'; put 'set &fmtds;'; put '%end;'; put '%else %do;'; put 'set &ds;'; put '%end;'; put '&stmt_obs;'; put 'format _numeric_ bart.;'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'if findc(&&name&i,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put '&&name&i=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,&&name&i)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else &&name&i=quote(cats(&&name&i));'; put '%end;'; put '%end;'; put 'run;'; put 'filename _sjs3 temp lrecl=131068 ;'; put 'data _null_;'; put 'file _sjs3 encoding=''utf-8'';'; put 'if _n_=1 then put "[";'; put 'set &tempds;'; put 'if _n_>1 then put "," @; put'; put '%if &action=ARR %then "[" ; %else "{" ;'; put '%do i=1 %to &numcols;'; put '%if &i>1 %then "," ;'; put '%if &action=OBJ %then """&&name&i"":" ;'; put '"&&name&i"n /* name literal for reserved variable names */'; put '%end;'; put '%if &action=ARR %then "]" ; %else "}" ; ;'; put '/* close out the table */'; put 'data _null_;'; put 'file _sjs3 mod encoding=''utf-8'';'; put 'put '']'';'; put 'run;'; put 'data _null_;'; put 'infile _sjs3 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs3 clear;'; put '%end;'; put 'proc sql;'; put 'drop table &colinfo, &tempds;'; put '%if %substr(&showmeta,1,1)=Y %then %do;'; put 'filename _sjs4 temp lrecl=131068 encoding=''utf-8'';'; put 'data _null_;'; put 'file _sjs4;'; put 'length label $350;'; put 'put ", ""$%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":{""vars"":{";'; put 'do i=1 to &numcols;'; put 'name=quote(trim(symget(cats(''name'',i))));'; put 'format=quote(trim(symget(cats(''fmt'',i))));'; put 'label=quote(prxchange(''s/\\/\\\\/'',-1,trim(symget(cats(''label'',i)))));'; put 'length=quote(trim(symget(cats(''length'',i))));'; put 'type=quote(trim(symget(cats(''typelong'',i))));'; put 'if i>1 then put "," @@;'; put 'put name '':{"format":'' format '',"label":'' label'; put ''',"length":'' length '',"type":'' type ''}'';'; put 'end;'; put 'put ''}}'';'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs4 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs4 clear;'; put '%end;'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put 'data _null_; file &jref encoding=''utf-8'' mod ;'; put 'put "}";'; put 'run;'; put '%end;'; put '%mend mp_jsonout;'; put '/**'; put '@file mm_webout.sas'; put '@brief Send data to/from SAS Stored Processes'; put '@details This macro should be added to the start of each Stored Process,'; put '**immediately** followed by a call to:'; put '%mm_webout(FETCH)'; put 'This will read all the input data and create same-named SAS datasets in the'; put 'WORK library. You can then insert your code, and send data back using the'; put 'following syntax:'; put 'data some datasets; * make some data ;'; put 'retain some columns;'; put 'run;'; put '%mm_webout(OPEN)'; put '%mm_webout(ARR,some) * Array format, fast, suitable for large tables ;'; put '%mm_webout(OBJ,datasets) * Object format, easier to work with ;'; put 'Finally, wrap everything up send some helpful system variables too'; put '%mm_webout(CLOSE)'; put '@param [in] action Either FETCH, OPEN, ARR, OBJ or CLOSE'; put '@param [in] ds The dataset to send back to the frontend'; put '@param [out] dslabel= Value to use instead of table name for sending to JSON'; put '@param [in] fmt= (N) Setting Y converts all vars to their formatted values'; put '@param [out] fref= (_webout) The fileref to which to write the JSON'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] workobs= (0) When set to a positive integer, will create a new'; put 'output object (WORK) which contains this number of observations from all'; put 'tables in the WORK library.'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to output JSON'; put '

SAS Macros

'; put '@li mp_jsonout.sas'; put '

Related Macros

'; put '@li ms_webout.sas'; put '@li mv_webout.sas'; put '@version 9.3'; put '@author Allan Bowe'; put '**/'; put '%macro mm_webout(action,ds,dslabel=,fref=_webout,fmt=N,missing=NULL'; put ',showmeta=N,maxobs=MAX,workobs=0'; put ');'; put '%global _webin_file_count _webin_fileref1 _webin_name1 _program _debug'; put 'sasjs_tables;'; put '%local i tempds jsonengine;'; put '/* see https://github.com/sasjs/core/issues/41 */'; put '%if "%upcase(&SYSENCODING)" ne "UTF-8" %then %let jsonengine=PROCJSON;'; put '%else %let jsonengine=DATASTEP;'; put '%if &action=FETCH %then %do;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'options mprint notes mprintnest;'; put '%end;'; put '%let _webin_file_count=%eval(&_webin_file_count+0);'; put '/* now read in the data */'; put '%do i=1 %to &_webin_file_count;'; put '%if &_webin_file_count=1 %then %do;'; put '%let _webin_fileref1=&_webin_fileref;'; put '%let _webin_name1=&_webin_name;'; put '%end;'; put 'data _null_;'; put 'infile &&_webin_fileref&i termstr=crlf;'; put 'input;'; put 'call symputx(''input_statement'',_infile_);'; put 'putlog "&&_webin_name&i input statement: " _infile_;'; put 'stop;'; put 'data &&_webin_name&i;'; put 'infile &&_webin_fileref&i firstobs=2 dsd termstr=crlf encoding=''utf-8'';'; put 'input &input_statement;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'if _n_<20 then putlog _infile_;'; put '%end;'; put 'run;'; put '%let sasjs_tables=&sasjs_tables &&_webin_name&i;'; put '%end;'; put '%end;'; put '%else %if &action=OPEN %then %do;'; put '/* fix encoding */'; put 'OPTIONS NOBOMFILE;'; put '/**'; put '* check xengine type to avoid the below err message:'; put '* > Function is only valid for filerefs using the CACHE access method.'; put '*/'; put 'data _null_;'; put 'set sashelp.vextfl(where=(fileref="_WEBOUT"));'; put 'if xengine=''STREAM'' then do;'; put 'rc=stpsrv_header(''Content-type'',"text/html; encoding=utf-8");'; put 'end;'; put 'run;'; put '/* setup json */'; put 'data _null_;file &fref encoding=''utf-8'';'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutBEGIN<<'';'; put '%end;'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'run;'; put '%end;'; put '%else %if &action=ARR or &action=OBJ %then %do;'; put '%mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt,jref=&fref'; put ',engine=&jsonengine,missing=&missing,showmeta=&showmeta,maxobs=&maxobs'; put ')'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put '/* To avoid issues with _webout on EBI we use a temporary file */'; put 'filename _sjsref temp lrecl=131068;'; put '%if %str(&workobs) > 0 %then %do;'; put '/* if debug mode, send back first XX records of each work table also */'; put 'data;run;%let tempds=%scan(&syslast,2,.);'; put 'ods output Members=&tempds;'; put 'proc datasets library=WORK memtype=data;'; put '%local wtcnt;%let wtcnt=0;'; put 'data _null_;'; put 'set &tempds;'; put 'if not (upcase(name) =:"DATA"); /* ignore temp datasets */'; put 'i+1;'; put 'call symputx(cats(''wt'',i),name,''l'');'; put 'call symputx(''wtcnt'',i,''l'');'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put ",""WORK"":{";'; put '%do i=1 %to &wtcnt;'; put '%let wt=&&wt&i;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'dsid=open("WORK.&wt",''is'');'; put 'nlobs=attrn(dsid,''NLOBS'');'; put 'nvars=attrn(dsid,''NVARS'');'; put 'rc=close(dsid);'; put 'if &i>1 then put '',''@;'; put 'put " ""&wt"" : {";'; put 'put ''"nlobs":'' nlobs;'; put 'put '',"nvars":'' nvars;'; put '%mp_jsonout(OBJ,&wt,jref=_sjsref,dslabel=first10rows,showmeta=Y'; put ',maxobs=&workobs'; put ')'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put '%end;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put 'run;'; put '%end;'; put '/* close off json */'; put 'data _null_;file _sjsref mod encoding=''utf-8'';'; put 'length SYSPROCESSNAME syserrortext syswarningtext autoexec $512;'; put 'put ",""_DEBUG"" : ""&_debug"" ";'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'autoexec=quote(urlencode(trim(getoption(''autoexec''))));'; put 'put '',"AUTOEXEC" : '' autoexec;'; put 'put ",""MF_GETUSER"" : ""%mf_getuser()"" ";'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'put ",""SYSENCODING"" : ""&sysencoding"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSPROCESSID"" : ""&SYSPROCESSID"" ";'; put 'put ",""SYSPROCESSMODE"" : ""&SYSPROCESSMODE"" ";'; put 'SYSPROCESSNAME=quote(urlencode(cats(SYSPROCESSNAME)));'; put 'put ",""SYSPROCESSNAME"" : " SYSPROCESSNAME;'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put '',"SYSWARNINGTEXT" : '' syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'length memsize $32;'; put 'memsize="%sysfunc(INPUTN(%sysfunc(getoption(memsize)), best.),sizekmg.)";'; put 'memsize=quote(cats(memsize));'; put 'put '',"MEMSIZE" : '' memsize;'; put 'put "}" @;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutEND<<'';'; put '%end;'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjsref lrecl=1 recfm=n;'; put 'file &fref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjsref clear;'; put '%end;'; put '%mend mm_webout;'; put '%macro webout(action,ds,dslabel=,fmt=,missing=NULL,showmeta=NO,maxobs=MAX);'; put '%mm_webout(&action,ds=&ds,dslabel=&dslabel,fmt=&fmt'; put ',missing=&missing'; put ',showmeta=&showmeta'; put ',maxobs=&maxobs'; put ') %mend;'; put '/* provide additional debug info */'; put '%global _program;'; put '%put &=syscc;'; put '%put user=%mf_getuser();'; put '%put pgm=&_program;'; put '%put timestamp=%sysfunc(datetime(),datetime19.);'; put '* Service Variables start;'; put '* Service Variables end;'; put '* SAS Macros start;'; put '%macro mf_getapploc(pgm);'; put '%if "&pgm"="" %then %do;'; put '%if %symexist(_program) %then %let pgm=&_program;'; put '%else %do;'; put '%put &sysmacroname: No value provided and no _program variable available;'; put '%return;'; put '%end;'; put '%end;'; put '%local root;'; put '/**'; put '* First check we are not in the tests/macros folder (which has no subfolders)'; put '* or specifically in the testsetup or testteardown services'; put '*/'; put '%if %index(&pgm,/tests/macros/)'; put 'or %index(&pgm,/tests/testsetup)'; put 'or %index(&pgm,/tests/testteardown)'; put '%then %do;'; put '%let root=%substr(&pgm,1,%index(&pgm,/tests)-1);'; put '&root'; put '%return;'; put '%end;'; put '/**'; put '* Next, move up two levels to avoid matches on subfolder or service name'; put '*/'; put '%let root=%substr(&pgm,1,%length(&pgm)-%length(%scan(&pgm,-1,/))-1);'; put '%let root=%substr(&root,1,%length(&root)-%length(%scan(&root,-1,/))-1);'; put '%if %index(&root,/tests/) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/tests/)-1);'; put '%end;'; put '%else %if %index(&root,/services) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/services)-1);'; put '%end;'; put '%else %if %index(&root,/jobs) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/jobs)-1);'; put '%end;'; put '%else %put &sysmacroname: Could not find an app location from &pgm;'; put '&root'; put '%mend mf_getapploc ;'; put '%macro mf_getuniquefileref(prefix=_,maxtries=1000,lrecl=32767);'; put '%local rc fname;'; put '%if &prefix=0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%end;'; put '%else %do;'; put '%local x len;'; put '%let len=%eval(8-%length(&prefix));'; put '%let x=0;'; put '%do x=0 %to &maxtries;'; put '%let fname=&prefix%substr(%sysfunc(ranuni(0)),3,&len);'; put '%if %sysfunc(fileref(&fname)) > 0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%return;'; put '%end;'; put '%end;'; put '%put unable to find available fileref after &maxtries attempts;'; put '%end;'; put '%mend mf_getuniquefileref;'; put '%macro mp_abort(mac=mp_abort.sas, type=, msg=, iftrue=%str(1=1)'; put ', errds=work.mp_abort_errds'; put ', mode=REGULAR'; put ')/*/STORE SOURCE*/;'; put '%global sysprocessmode sysprocessname sasjs_stpsrv_header_loc sasjsprocessmode;'; put '%local fref fid i;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%put NOTE: /// mp_abort macro executing //;'; put '%if %length(&mac)>0 %then %put NOTE- called by &mac;'; put '%put NOTE - &msg;'; put '%if %symexist(_SYSINCLUDEFILEDEVICE)'; put '/* abort cancel FILE does not restart outside the INCLUDE on Viya 3.5 */'; put 'and %superq(SYSPROCESSNAME) ne %str(Compute Server)'; put '%then %do;'; put '%if "*&_SYSINCLUDEFILEDEVICE*" ne "**" %then %do;'; put 'data &errds;'; put 'iftrue=''1=1'';'; put 'length mac $100 msg $5000;'; put 'mac=symget(''mac'');'; put 'msg=symget(''msg'');'; put 'run;'; put 'data _null_;'; put 'abort cancel FILE;'; put 'run;'; put '%return;'; put '%end;'; put '%end;'; put '/* Web App Context */'; put '%if %symexist(_PROGRAM)'; put 'or %superq(SYSPROCESSNAME) = %str(Compute Server)'; put 'or &mode=INCLUDE'; put '%then %do;'; put 'options obs=max replace mprint;'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5"'; put '%then %do;'; put 'options nosyntaxcheck;'; put '%end;'; put '%if &mode=INCLUDE %then %do;'; put '%if %sysfunc(exist(&errds))=1 %then %do;'; put 'data _null_;'; put 'set &errds;'; put 'call symputx(''iftrue'',iftrue,''l'');'; put 'call symputx(''mac'',mac,''l'');'; put 'call symputx(''msg'',msg,''l'');'; put 'putlog (_all_)(=);'; put 'run;'; put '%if (&iftrue)=0 %then %return;'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: No include errors found;'; put '%return;'; put '%end;'; put '%end;'; put '/* extract log errs / warns, if exist */'; put '%local logloc logline;'; put '%global logmsg; /* capture global messages */'; put '%if %symexist(SYSPRINTTOLOG) %then %let logloc=&SYSPRINTTOLOG;'; put '%else %let logloc=%qsysfunc(getoption(LOG));'; put 'proc printto log=log;run;'; put '%let logline=0;'; put '%if %length(&logloc)>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input; putlog _infile_;'; put 'i=1;'; put 'retain logonce 0;'; put 'if ('; put '_infile_=:"%str(WARN)ING" or _infile_=:"%str(ERR)OR"'; put ') and logonce=0 then'; put 'do;'; put 'call symputx(''logline'',_n_);'; put 'logonce+1;'; put 'end;'; put 'run;'; put '/* capture log including lines BEFORE the err */'; put '%if &logline>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input;'; put 'i=1;'; put 'stoploop=0;'; put 'if _n_ ge &logline-15 and stoploop=0 then do until (i>22);'; put 'call symputx(''logmsg'',catx(''\n'',symget(''logmsg''),_infile_));'; put 'input;'; put 'i+1;'; put 'stoploop=1;'; put 'end;'; put 'if stoploop=1 then stop;'; put 'run;'; put '%end;'; put '%end;'; put '%if %symexist(SYS_JES_JOB_URI) %then %do;'; put '/* setup webout for Viya */'; put 'options nobomfile;'; put '%if "X&SYS_JES_JOB_URI.X"="XX" %then %do;'; put 'filename _webout temp lrecl=999999 mod;'; put '%end;'; put '%else %do;'; put 'filename _webout filesrvc parenturi="&SYS_JES_JOB_URI"'; put 'name="_webout.json" lrecl=999999 mod;'; put '%end;'; put '%end;'; put '%else %if %sysfunc(filename(fref,&sasjs_stpsrv_header_loc))=0 %then %do;'; put 'options nobomfile;'; put '/* set up http header for SASjs Server */'; put '%let fid=%sysfunc(fopen(&fref,A));'; put '%if &fid=0 %then %do;'; put '%put %str(ERR)OR: %sysfunc(sysmsg());'; put '%return;'; put '%end;'; put '%let rc=%sysfunc(fput(&fid,%str(Content-Type: application/json)));'; put '%let rc=%sysfunc(fwrite(&fid));'; put '%let rc=%sysfunc(fclose(&fid));'; put '%let rc=%sysfunc(filename(&fref));'; put '%end;'; put '/* send response in SASjs JSON format */'; put 'data _null_;'; put 'file _webout mod lrecl=32000 encoding=''utf-8'';'; put 'length msg syswarningtext syserrortext $32767 mode $10 ;'; put 'sasdatetime=datetime();'; put 'msg=symget(''msg'');'; put '%if &logline>0 %then %do;'; put 'msg=cats(msg,''\n\nLog Extract:\n'',symget(''logmsg''));'; put '%end;'; put '/* escape the escapes */'; put 'msg=tranwrd(msg,''\'',''\\'');'; put '/* escape the quotes */'; put 'msg=tranwrd(msg,''"'',''\"'');'; put '/* ditch the CRLFs as chrome complains */'; put 'msg=compress(msg,,''kw'');'; put '/* quote without quoting the quotes (which are escaped instead) */'; put 'msg=cats(''"'',msg,''"'');'; put 'if symexist(''_debug'') then debug=quote(trim(symget(''_debug'')));'; put 'else debug=''""'';'; put 'if symget(''sasjsprocessmode'')=''Stored Program'' then mode=''SASJS'';'; put 'if mode ne ''SASJS'' then put ''>>weboutBEGIN<<'';'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'put '',"sasjsAbort" : [{'';'; put 'put '' "MSG":'' msg ;'; put 'put '' ,"MAC": "'' "&mac" ''"}]'';'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'put '',"_DEBUG":'' debug ;'; put 'if symexist(''_metauser'') then do;'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put 'end;'; put 'if symexist(''SYS_JES_JOB_URI'') then do;'; put 'SYS_JES_JOB_URI=quote(trim(symget(''SYS_JES_JOB_URI'')));'; put 'put '',"SYS_JES_JOB_URI": '' SYS_JES_JOB_URI;'; put 'end;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put ",""SYSWARNINGTEXT"" : " syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'put "}" ;'; put 'if mode ne ''SASJS'' then put ''>>weboutEND<<'';'; put 'run;'; put '%put _all_;'; put '%if "&sysprocessmode " = "SAS Stored Process Server " %then %do;'; put 'data _null_;'; put 'putlog ''stpsrvset program err and syscc'';'; put 'rc=stpsrvset(''program error'', 0);'; put 'call symputx("syscc",0,"g");'; put 'run;'; put '%if &sysscp=WIN'; put 'and 1=0 /* deprecating this logic until we figure out a consistent abort */'; put 'and "%substr(%str(&sysvlong ),1,8)"="9.04.01M"'; put 'and "%substr(%str(&sysvlong ),9,1)">"5" %then %do;'; put '/* skip approach (below) does not work in windows m6+ envs */'; put 'endsas;'; put '%end;'; put '%else %do;'; put '/**'; put '* endsas kills 9.4m3 deployments by orphaning multibridges.'; put '* Abort variants are ungraceful (non zero return code)'; put '* This approach lets SAS run silently until the end :-)'; put '* Caution - fails when called within a %include within a macro'; put '* Use mp_include() to handle this.'; put '*/'; put 'filename skip temp;'; put 'data _null_;'; put 'file skip;'; put 'put ''%macro skip();'';'; put 'comment ''%mend skip; -> fix lint '';'; put 'put ''%macro skippy();'';'; put 'comment ''%mend skippy; -> fix lint '';'; put 'run;'; put '%inc skip;'; put '%end;'; put '%end;'; put '%else %if "&sysprocessmode " = "SAS Compute Server " %then %do;'; put '/* endsas kills the session making it harder to fetch results */'; put 'data _null_;'; put 'syswarningtext=symget(''syswarningtext'');'; put 'syserrortext=symget(''syserrortext'');'; put 'abort_msg=symget(''msg'');'; put 'syscc=symget(''syscc'');'; put 'sysuserid=symget(''sysuserid'');'; put 'iftrue=symget(''iftrue'');'; put 'put (_all_)(/=);'; put 'call symputx(''syscc'',0);'; put 'abort cancel nolist;'; put 'run;'; put '%end;'; put '%else %do;'; put '%abort cancel;'; put '%end;'; put '%end;'; put '%else %do;'; put '%put _all_;'; put '%abort cancel;'; put '%end;'; put '%mend mp_abort;'; put '/** @endcond */'; put '%macro mm_getstpcode('; put 'tree=/User Folders/sasdemo/somestp'; put ',name='; put ',outloc=0'; put ',outref=0'; put ',mDebug=1'; put ',showlog=NO'; put ');'; put '%local mD;'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing &sysmacroname..sas;'; put '%&mD.put _local_;'; put '%if %length(&name)>0 %then %let name=/&name;'; put '/* first, check if STP exists */'; put '%local tsuri;'; put '%let tsuri=stopifempty ;'; put 'data _null_;'; put 'format type uri tsuri value $200.;'; put 'call missing (of _all_);'; put 'path="&tree&name(StoredProcess)";'; put '/* first, find the STP ID */'; put 'if metadata_pathobj("",path,"StoredProcess",type,uri)>0 then do;'; put '/* get sourcecode */'; put 'cnt=1;'; put 'do while (metadata_getnasn(uri,"Notes",cnt,tsuri)>0);'; put 'rc=metadata_getattr(tsuri,"Name",value);'; put '&mD.put tsuri= value=;'; put 'if value="SourceCode" then do;'; put '/* found it! */'; put 'rc=metadata_getattr(tsuri,"Id",value);'; put 'call symputx(''tsuri'',value,''l'');'; put 'stop;'; put 'end;'; put 'cnt+1;'; put 'end;'; put 'end;'; put 'else put (_all_)(=);'; put 'run;'; put '%mp_abort(iftrue= (&tsuri=stopifempty)'; put ',mac=mm_getstpcode'; put ',msg=%str(&tree&name.(StoredProcess) not found!)'; put ')'; put '/**'; put '* Now we can extract the textstore'; put '*/'; put 'filename __getdoc temp lrecl=10000000;'; put 'proc metadata'; put 'in="$METAREPOSITORY'; put ''; put 'SAS1"'; put 'out=__getdoc ;'; put 'run;'; put '/* find the beginning of the text */'; put '%local start;'; put 'data _null_;'; put 'infile __getdoc lrecl=10000;'; put 'input;'; put 'start=index(_infile_,''StoredText="'');'; put 'if start then do;'; put 'call symputx("start",start+11);'; put '*putlog ''"'' _infile_ ''"'';'; put 'end;'; put 'stop;'; put '%local outeng;'; put '%if "&outloc"="0" %then %let outeng=TEMP;'; put '%else %let outeng="&outloc";'; put '%local fref;'; put '%if &outref=0 %then %let fref=%mf_getuniquefileref();'; put '%else %let fref=&outref;'; put '/* read the content, byte by byte, resolving escaped chars */'; put 'filename &fref &outeng lrecl=100000;'; put 'data _null_;'; put 'length filein 8 fileid 8;'; put 'filein = fopen("__getdoc","I",1,"B");'; put 'fileid = fopen("&fref","O",1,"B");'; put 'rec = "20"x;'; put 'length entity $6;'; put 'do while(fread(filein)=0);'; put 'x+1;'; put 'if x>&start then do;'; put 'rc = fget(filein,rec,1);'; put 'if rec=''"'' then leave;'; put 'else if rec="&" then do;'; put 'entity=rec;'; put 'do until (rec=";");'; put 'if fread(filein) ne 0 then goto getout;'; put 'rc = fget(filein,rec,1);'; put 'entity=cats(entity,rec);'; put 'end;'; put 'select (entity);'; put 'when (''&'' ) rec=''&'' ;'; put 'when (''<'' ) rec=''<'' ;'; put 'when (''>'' ) rec=''>'' ;'; put 'when (''''') rec="''" ;'; put 'when (''"'') rec=''"'' ;'; put 'when ('' '') rec=''0A''x;'; put 'when ('' '') rec=''0D''x;'; put 'when (''$'' ) rec=''$'' ;'; put 'when ('' '') rec=''09''x;'; put 'otherwise putlog "%str(WARN)ING: missing value for " entity=;'; put 'end;'; put 'rc =fput(fileid, substr(rec,1,1));'; put 'rc =fwrite(fileid);'; put 'end;'; put 'else do;'; put 'rc =fput(fileid,rec);'; put 'rc =fwrite(fileid);'; put 'end;'; put 'end;'; put 'end;'; put 'getout:'; put 'rc=fclose(filein);'; put 'rc=fclose(fileid);'; put 'run;'; put '%if &showlog=YES %then %do;'; put 'data _null_;'; put 'infile &fref lrecl=32767 end=last;'; put 'input;'; put 'if _n_=1 then putlog ''>>stpcodeBEGIN<<'';'; put 'putlog _infile_;'; put 'if last then putlog ''>>stpcodeEND<<'';'; put 'run;'; put '%end;'; put 'filename __getdoc clear;'; put '%if &outref=0 %then %do;'; put 'filename &fref clear;'; put '%end;'; put '%mend mm_getstpcode;'; put '%macro dc_getsettings();'; put '%global _program;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(syscc=&syscc on entry (&syswarningtext &syserrortext))'; put ')'; put '%if %length(&_PROGRAM)>1 %then %let root=&_program;'; put '%else %do;'; put '%global _metauser;'; put '%let _metauser=&sysuserid;'; put '/* to mimic a "real" _program we need to give a dummy role and stp name */'; put '%let root=/dummyRole/dummyName;'; put '%end;'; put '/* the DC precode is stored in the Admin folder in the root of'; put 'the project. Lets find that root. */'; put '%put &=root;'; put '%let root=%mf_getapploc();'; put '%put &=root;'; put '/* Now we know the root location we can retrieve the params */'; put '%let temploc=%sysfunc(pathname(work))/settings.txt;'; put '%mm_getstpcode(tree=&root/services/public'; put ',name=Data_Controller_Settings'; put ',outloc=&temploc'; put ')'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Unable to run getstpcode)'; put ')'; put 'filename _getsets "&temploc" lrecl=2000;'; put '/*'; put 'Do not use mp_include here - this puts a copy in every service, which creates'; put 'compilation problems when calling services from mp_include'; put '*/'; put '%inc _getsets/source2;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Problem running &sysmacroname (&syswarningtext &syserrortext))'; put ')'; put '%mend dc_getsettings;'; put '%macro mf_fmtdttm('; put ')/*/STORE SOURCE*/;'; put '%if "&sysver"="9.2" or "&sysver"="9.3"'; put 'or ("&sysver"="9.4" and "%substr(&SYSVLONG,9,1)" le "3")'; put 'or "%substr(&sysver,1,1)"="4"'; put 'or "%substr(&sysver,1,1)"="5"'; put '%then %do;DATETIME19.3%end;'; put '%else %do;E8601DT26.6%end;'; put '%mend mf_fmtdttm;'; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '%macro mp_init(prefix=SASJS'; put ')/*/STORE SOURCE*/;'; put '%if %symexist(SASJS_PREFIX) %then %return; /* only run once */'; put '%global'; put 'SASJS_PREFIX /* the ONLY hard-coded global macro variable in SASjs */'; put '&prefix._FUNCTIONS /* used in mcf_init() to track core function compilation */'; put '&prefix._INIT_NUM /* initialisation time as numeric */'; put '&prefix._INIT_DTTM /* initialisation time in E8601DT26.6 format */'; put '&prefix.WORK /* avoid typing %sysfunc(pathname(work)) every time */'; put ';'; put '%let sasjs_prefix=&prefix;'; put 'data _null_;'; put 'dttm=datetime();'; put 'call symputx("&sasjs_prefix._init_num",dttm,''g'');'; put 'call symputx("&sasjs_prefix._init_dttm",put(dttm,E8601DT26.6),''g'');'; put 'call symputx("&sasjs_prefix.work",pathname(''WORK''),''g'');'; put 'run;'; put 'options'; put 'compress=CHAR /* default is none so ensure we have something! */'; put 'datastmtchk=ALLKEYWORDS /* protection from overwriting input datasets */'; put 'errorcheck=STRICT /* catch errs in libname/filename statements */'; put 'fmterr /* ensure err when a format cannot be found */'; put 'mergenoby=%str(ERR)OR /* throw err when a merge has no BY variables */'; put 'missing=. /* changing this can cause hard to detect errs */'; put 'noquotelenmax /* avoid warnings for long strings */'; put 'noreplace /* avoid overwriting permanent datasets */'; put 'ps=max /* reduce log size slightly */'; put 'ls=max /* reduce log even more and avoid word truncation */'; put 'validmemname=COMPATIBLE /* avoid special characters etc in table names */'; put 'validvarname=V7 /* avoid special characters etc in variable names */'; put 'varinitchk=%str(ERR)OR /* avoid data mistakes from variable name typos */'; put 'varlenchk=%str(ERR)OR /* fail hard if truncation (data loss) can result */'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5" %then %do;'; put 'noautocorrect /* disallow misspelled procedure names */'; put 'dsoptions=note2err /* undocumented - convert bad NOTEs to ERRs */'; put '%end;'; put ';'; put '%mend mp_init;'; put '%macro mpeinit(fetch=YES);'; put '%global mpeinit'; put 'mpeadmins /* group with unrestricted Meditor access */'; put 'mpelocapprovals /* location for landing and staging files */'; put 'mpelib /* location of configuration tables for DC */'; put 'dc_repo_users /* location of user / group metadata */'; put 'dc_licence_key /* extracted in dc_getsettings */'; put 'dc_activation_key /* extracted in dc_getsettings */'; put 'dc_locale /* extracted in dc_getsettings */'; put 'dc_dttmtfmt /* can be overridden in dc_getsettings */'; put '_debug'; put ';'; put '%if &mpeinit=1 %then %return;'; put '%else %let mpeinit=1;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(Problem on service startup (&syswarningtext &syserrortext))'; put ')'; put '%mp_init()'; put '%if &fetch=YES %then %do;'; put '%webout(FETCH)'; put '%end;'; put '%global _CLIENTNAME;'; put '%mp_abort(iftrue= (&_CLIENTNAME=SAS Enterprise Guide)'; put ',mac=&_program..sas'; put ',msg=%str(Data Controller is a web app and should not be executed from EG)'; put ')'; put 'options urlencoding=utf8 nobomfile lrecl=32767;'; put '%let perf=%sysfunc(datetime());'; put '%put perfdiff: 0;'; put '%let dc_locale=SYSTEM; /* default if not set */'; put '/**'; put '* E8601DT26.6 has widest database support - but not all SAS flavours can'; put '* handle it. Override in the settings STP if needed.'; put '*/'; put 'data _null_;'; put 'dc_dttmtfmt=''"%sysfunc(datetime(),''!!"%mf_fmtdttm()"!!'')"dt'';'; put 'call symputx(''dc_dttmtfmt'',dc_dttmtfmt);'; put 'put dc_dttmtfmt=;'; put 'run;'; put '%put &=dc_dttmtfmt;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc prior to dc_getsettings)'; put ')'; put '%dc_getsettings()'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc after dc_getsettings)'; put ')'; put 'data _null_;'; put 'set &DC_LIBREF..mpe_config(where=('; put 'var_scope="DC"'; put 'and &dc_dttmtfmt lt tx_to'; put 'and var_active=1'; put '));'; put 'call symputx(var_name,var_value,''G'');'; put 'putlog var_name "=" var_value;'; put 'run;'; put '%let mpelib=&dc_libref;'; put '%let mpeadmins=&dc_admin_group;'; put '%let mpelocapprovals=&dc_staging_area;'; put '%let dc_repo_users=&dc_repo_users;'; put '%if &dc_locale ne SYSTEM %then %do;'; put 'options locale=&dc_locale;'; put '%end;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(Problem during compilation or with STP precode (&syswarningtext))'; put ')'; put '%mend mpeinit;'; put '%macro mf_mval(var);'; put '%if %symexist(&var) %then %do;'; put '%superq(&var)'; put '%end;'; put '%mend mf_mval;'; put '%macro mf_trimstr(basestr,trimstr);'; put '%local baselen trimlen trimval;'; put '/* return if basestr is shorter than trimstr (or 0) */'; put '%let baselen=%length(%superq(basestr));'; put '%let trimlen=%length(%superq(trimstr));'; put '%if &baselen < &trimlen or &baselen=0 %then %return;'; put '/* obtain the characters from the end of basestr */'; put '%let trimval=%qsubstr(%superq(basestr)'; put ',%length(%superq(basestr))-&trimlen+1'; put ',&trimlen);'; put '/* compare and if matching, chop it off! */'; put '%if %superq(basestr)=%superq(trimstr) %then %do;'; put '%return;'; put '%end;'; put '%else %if %superq(trimval)=%superq(trimstr) %then %do;'; put '%qsubstr(%superq(basestr),1,%length(%superq(basestr))-&trimlen)'; put '%end;'; put '%else %do;'; put '&basestr'; put '%end;'; put '%mend mf_trimstr;'; put '%macro mf_getplatform(switch'; put ')/*/STORE SOURCE*/;'; put '%local a b c;'; put '%if &switch.NONE=NONE %then %do;'; put '%if %symexist(sasjsprocessmode) %then %do;'; put '%if &sasjsprocessmode=Stored Program %then %do;'; put 'SASJS'; put '%return;'; put '%end;'; put '%end;'; put '%if %symexist(sysprocessmode) %then %do;'; put '%if "&sysprocessmode"="SAS Object Server"'; put 'or "&sysprocessmode"= "SAS Compute Server" %then %do;'; put 'SASVIYA'; put '%end;'; put '%else %if "&sysprocessmode"="SAS Stored Process Server"'; put 'or "&sysprocessmode"="SAS Workspace Server"'; put '%then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if %symexist(_metaport) or %symexist(_metauser) %then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if &switch=SASSTUDIO %then %do;'; put '/* return the version of SAS Studio else 0 */'; put '%if %mf_mval(_CLIENTAPP)=%str(SAS Studio) %then %do;'; put '%let a=%mf_mval(_CLIENTVERSION);'; put '%let b=%scan(&a,1,.);'; put '%if %eval(&b >2) %then %do;'; put '&b'; put '%end;'; put '%else 0;'; put '%end;'; put '%else 0;'; put '%end;'; put '%else %if &switch=VIYARESTAPI %then %do;'; put '%mf_trimstr(%sysfunc(getoption(servicesbaseurl)),/)'; put '%end;'; put '%mend mf_getplatform;'; put '%macro mpeterm();'; put '%local oldloc;'; put 'data _null_;'; put 'if symexist(''SYSPRINTTOLOG'') then oldloc=symget(''SYSPRINTTOLOG'');'; put 'else oldloc=getoption(''LOG'');'; put 'if subpad(oldloc,1,1) not in (''"'',"''",'' '') then oldloc=quote(cats(oldloc));'; put 'call symputx(''oldloc'',oldloc,''l'');'; put 'run;'; put '%if %length(&oldloc)>0 %then %do;'; put 'proc printto log=log;'; put 'run;'; put 'data _null_;'; put 'infile &oldloc;'; put 'input; putlog _infile_;'; put 'run;'; put '%end;'; put '%if %sysfunc(exist(&dc_libref..mpe_requests)) and %mf_getplatform() ne SASVIYA'; put '%then %do;'; put 'data ;'; put 'if 0 then set &dc_libref..mpe_requests;'; put 'request_dttm=%sysfunc(datetime());'; put 'request_user="%mf_getuser()";'; put 'request_service="%scan(&_program,-2,/)/%scan(&_program,-1,/)";'; put 'request_params='''';'; put 'output;stop;'; put 'proc append base=&dc_libref..mpe_requests data=&syslast force nowarn;'; put 'run;'; put '%end;'; put '%mend mpeterm;'; put '%macro mm_assignlib('; put 'libref'; put ',mAbort=HARD'; put ')/*/STORE SOURCE*/;'; put '%local mp_abort msg;'; put '%let mp_abort=0;'; put '%if %sysfunc(libref(&libref)) %then %do;'; put 'data _null_;'; put 'length liburi LibName msg $200;'; put 'call missing(of _all_);'; put 'nobj=metadata_getnobj("omsobj:SASLibrary?@Libref=''&libref''",1,liburi);'; put 'if nobj=1 then do;'; put 'rc=metadata_getattr(liburi,"Name",LibName);'; put '/* now try and assign it */'; put 'if libname("&libref",,''meta'',cats(''liburi="'',liburi,''";'')) ne 0 then do;'; put 'putlog "&libref could not be assigned";'; put 'putlog liburi=;'; put '/**'; put '* Fetch the system message for display in the abort modal. This is'; put '* not always helpful though. One example, previously received:'; put '* NOTE: Libref XX refers to the same library metadata as libref XX.'; put '*/'; put 'msg=sysmsg();'; put 'if msg=:''ERROR: Libref SAVE is not assigned.'' then do;'; put 'msg=catx(" ",'; put '"Could not assign %upcase(&libref).",'; put '"Please check metadata permissions! Libname:",libname,'; put '"Liburi:",liburi'; put ');'; put 'end;'; put 'else if msg="ERROR: User does not have appropriate authorization "!!'; put '"level for library SAVE."'; put 'then do;'; put 'msg=catx(" ",'; put '"ERROR: User does not have appropriate authorization level",'; put '"for library %upcase(&libref), libname:",libname,'; put '"Liburi:",liburi'; put ');'; put 'end;'; put 'call symputx(''msg'',msg,''l'');'; put 'if "&mabort"=''HARD'' then call symputx(''mp_abort'',1,''l'');'; put 'end;'; put 'else do;'; put 'put (_all_)(=);'; put 'call symputx(''libname'',libname,''L'');'; put 'call symputx(''liburi'',liburi,''L'');'; put 'end;'; put 'end;'; put 'else if nobj>1 then do;'; put 'if "&mabort"=''HARD'' then call symputx(''mp_abort'',1);'; put 'call symputx(''msg'',"More than one library with libref=&libref");'; put 'end;'; put 'else do;'; put 'if "&mabort"=''HARD'' then call symputx(''mp_abort'',1);'; put 'call symputx(''msg'',"Library &libref not found in metadata");'; put 'end;'; put 'run;'; put '%put NOTE: &msg;'; put '%end;'; put '%else %do;'; put '%put NOTE: Library &libref is already assigned;'; put '%end;'; put '%mp_abort(iftrue= (&mp_abort=1)'; put ',mac=mm_assignlib.sas'; put ',msg=%superq(msg)'; put ')'; put '%mend mm_assignlib;'; put '/** @cond */'; put '%macro mf_getengine(libref'; put ')/*/STORE SOURCE*/;'; put '%local dsid engnum rc engine;'; put '/* in case the parameter is a libref.tablename, pull off just the libref */'; put '%let libref = %upcase(%scan(&libref, 1, %str(.)));'; put '%let dsid=%sysfunc('; put 'open(sashelp.vlibnam(where=(libname="%upcase(&libref)")),i)'; put ');'; put '%if (&dsid ^= 0) %then %do;'; put '%let engnum=%sysfunc(varnum(&dsid,ENGINE));'; put '%let rc=%sysfunc(fetch(&dsid));'; put '%let engine=%sysfunc(getvarc(&dsid,&engnum));'; put '%put &libref. ENGINE is &engine.;'; put '%let rc= %sysfunc(close(&dsid));'; put '%end;'; put '%upcase(&engine)'; put '%mend mf_getengine;'; put '/** @endcond */'; put '%macro mm_assigndirectlib('; put 'libref'; put ',open_passthrough='; put ',sql_options='; put ',mDebug=0'; put ',mAbort=0'; put ')/*/STORE SOURCE*/;'; put '%local mD;'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing mm_assigndirectlib.sas;'; put '%&mD.put _local_;'; put '%if &mAbort=1 %then %let mAbort=;'; put '%else %let mAbort=%str(*);'; put '%&mD.put NOTE: Creating direct (non META) connection to &libref library;'; put '%local cur_engine;'; put '%let cur_engine=%mf_getengine(&libref);'; put '%if &cur_engine ne META and &cur_engine ne %then %do;'; put '%put NOTE: &libref already has a direct (&cur_engine) libname connection;'; put '%return;'; put '%end;'; put '%else %if %upcase(&libref)=WORK %then %do;'; put '%put NOTE: We already have a direct connection to WORK :-) ;'; put '%return;'; put '%end;'; put '/* need to determine the library ENGINE first */'; put '%local engine;'; put 'data _null_;'; put 'length lib_uri engine $256;'; put 'call missing (of _all_);'; put '/* get URI for the particular library */'; put 'rc1=metadata_getnobj("omsobj:SASLibrary?@Libref =''&libref''",1,lib_uri);'; put '/* get the Engine attribute of the previous object */'; put 'rc2=metadata_getattr(lib_uri,''Engine'',engine);'; put 'putlog "mm_assigndirectlib for &libref:" rc1= lib_uri= rc2= engine=;'; put 'call symputx("liburi",lib_uri,''l'');'; put 'call symputx("engine",engine,''l'');'; put 'run;'; put '/* now obtain engine specific connection details */'; put '%if &engine=BASE %then %do;'; put '%&mD.put NOTE: Retrieving BASE library path;'; put 'data _null_;'; put 'length up_uri $256 path cat_path $1024;'; put 'retain cat_path;'; put 'call missing (of _all_);'; put '/* get all the filepaths of the UsingPackages association */'; put 'i=1;'; put 'rc3=metadata_getnasn("&liburi",''UsingPackages'',i,up_uri);'; put 'do while (rc3>0);'; put '/* get the DirectoryName attribute of the previous object */'; put 'rc4=metadata_getattr(up_uri,''DirectoryName'',path);'; put 'if i=1 then path = ''("''!!trim(path)!!''" '';'; put 'else path ='' "''!!trim(path)!!''" '';'; put 'cat_path = trim(cat_path) !! " " !! trim(path) ;'; put 'i+1;'; put 'rc3=metadata_getnasn("&liburi",''UsingPackages'',i,up_uri);'; put 'end;'; put 'cat_path = trim(cat_path) !! ")";'; put '&mD.putlog "NOTE: Getting physical path for &libref library";'; put '&mD.putlog rc3= up_uri= rc4= cat_path= path=;'; put '&mD.putlog "NOTE: Libname cmd will be:";'; put '&mD.putlog "libname &libref" cat_path;'; put 'call symputx("filepath",cat_path,''l'');'; put 'run;'; put '%if %sysevalf(&sysver<9.4) %then %do;'; put 'libname &libref &filepath;'; put '%end;'; put '%else %do;'; put '/* apply the new filelocks option to cater for temporary locks */'; put 'libname &libref &filepath filelockwait=5;'; put '%end;'; put '%end;'; put '%else %if &engine=REMOTE %then %do;'; put 'data x;'; put 'length rcCon rcProp rc k 3 uriCon uriProp PropertyValue PropertyName'; put 'Delimiter $256 properties $2048;'; put 'retain properties;'; put 'rcCon = metadata_getnasn("&liburi", "LibraryConnection", 1, uriCon);'; put 'rcProp = metadata_getnasn(uriCon, "Properties", 1, uriProp);'; put 'k = 1;'; put 'rcProp = metadata_getnasn(uriCon, "Properties", k, uriProp);'; put 'do while (rcProp > 0);'; put 'rc = metadata_getattr(uriProp , "DefaultValue",PropertyValue);'; put 'rc = metadata_getattr(uriProp , "PropertyName",PropertyName);'; put 'rc = metadata_getattr(uriProp , "Delimiter",Delimiter);'; put 'properties = trim(properties) !! " " !! trim(PropertyName)'; put '!! trim(Delimiter) !! trim(PropertyValue);'; put 'output;'; put 'k+1;'; put 'rcProp = metadata_getnasn(uriCon, "Properties", k, uriProp);'; put 'end;'; put '%&mD.put NOTE: Getting properties for REMOTE SHARE &libref library;'; put '&mD.put _all_;'; put '%&mD.put NOTE: Libname cmd will be:;'; put '%&mD.put libname &libref &engine &properties slibref=&libref;'; put 'call symputx ("properties",trim(properties),''l'');'; put 'run;'; put 'libname &libref &engine &properties slibref=&libref;'; put '%end;'; put '%else %if &engine=OLEDB %then %do;'; put '%&mD.put NOTE: Retrieving OLEDB connection details;'; put 'data _null_;'; put 'length domain datasource provider properties schema'; put 'connx_uri domain_uri conprop_uri lib_uri schema_uri value $256.;'; put 'call missing (of _all_);'; put '/* get source connection ID */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,connx_uri);'; put '/* get connection domain */'; put 'rc1=metadata_getnasn(connx_uri,''Domain'',1,domain_uri);'; put 'rc2=metadata_getattr(domain_uri,''Name'',domain);'; put '&mD.putlog / ''NOTE: '' // ''NOTE- connection id: '' connx_uri ;'; put '&mD.putlog ''NOTE- domain: '' domain;'; put '/* get DSN and PROVIDER from connection properties */'; put 'i=0;'; put 'do until (rc<0);'; put 'i+1;'; put 'rc=metadata_getnasn(connx_uri,''Properties'',i,conprop_uri);'; put 'rc2=metadata_getattr(conprop_uri,''Name'',value);'; put 'if value=''Connection.OLE.Property.DATASOURCE.Name.xmlKey.txt'' then do;'; put 'rc3=metadata_getattr(conprop_uri,''DefaultValue'',datasource);'; put 'end;'; put 'else if value=''Connection.OLE.Property.PROVIDER.Name.xmlKey.txt'' then do;'; put 'rc4=metadata_getattr(conprop_uri,''DefaultValue'',provider);'; put 'end;'; put 'else if value=''Connection.OLE.Property.PROPERTIES.Name.xmlKey.txt'' then'; put 'do;'; put 'rc5=metadata_getattr(conprop_uri,''DefaultValue'',properties);'; put 'end;'; put 'end;'; put '&mD.putlog ''NOTE- dsn/provider/properties: '' /'; put 'datasource provider properties;'; put '&mD.putlog ''NOTE- schema: '' schema // ''NOTE-'';'; put '/* get SCHEMA */'; put 'rc6=metadata_getnasn("&liburi",''UsingPackages'',1,lib_uri);'; put 'rc7=metadata_getattr(lib_uri,''SchemaName'',schema);'; put 'call symputx(''SQL_domain'',domain,''l'');'; put 'call symputx(''SQL_dsn'',datasource,''l'');'; put 'call symputx(''SQL_provider'',provider,''l'');'; put 'call symputx(''SQL_properties'',properties,''l'');'; put 'call symputx(''SQL_schema'',schema,''l'');'; put 'run;'; put '%if %length(&open_passthrough)>0 %then %do;'; put 'proc sql &sql_options;'; put 'connect to OLEDB as &open_passthrough(INSERT_SQL=YES'; put '/* need additional properties to make this work */'; put 'properties=(''Integrated Security''=SSPI'; put '''Persist Security Info''=True'; put '%sysfunc(compress(%str(&SQL_properties),%str(())))'; put ')'; put 'DATASOURCE=&sql_dsn PROMPT=NO'; put 'PROVIDER=&sql_provider SCHEMA=&sql_schema CONNECTION = GLOBAL);'; put '%end;'; put '%else %do;'; put 'LIBNAME &libref OLEDB PROPERTIES=&sql_properties'; put 'DATASOURCE=&sql_dsn PROVIDER=&sql_provider SCHEMA=&sql_schema'; put '%if %length(&sql_domain)>0 %then %do;'; put 'authdomain="&sql_domain"'; put '%end;'; put 'connection=shared;'; put '%end;'; put '%end;'; put '%else %if &engine=ODBC %then %do;'; put '%&mD.put NOTE: Retrieving ODBC connection details;'; put 'data _null_;'; put 'length connx_uri conprop_uri value datasource up_uri schema domprop_uri authdomain $256.;'; put 'call missing (of _all_);'; put '/* get source connection ID */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,connx_uri);'; put '/* get connection properties */'; put 'i=0;'; put 'do until (rc2<0);'; put 'i+1;'; put 'rc2=metadata_getnasn(connx_uri,''Properties'',i,conprop_uri);'; put 'rc3=metadata_getattr(conprop_uri,''Name'',value);'; put 'if value=''Connection.ODBC.Property.DATASRC.Name.xmlKey.txt'' then do;'; put 'rc4=metadata_getattr(conprop_uri,''DefaultValue'',datasource);'; put 'rc2=-1;'; put 'end;'; put 'end;'; put '/* get auth domain */'; put 'autrc=metadata_getnasn(connx_uri,"Domain",1,domprop_uri);'; put 'arc=metadata_getattr(domprop_uri,"Name",authdomain);'; put 'if not missing(authdomain) then authdomain=cats(''AUTHDOMAIN='',authdomain);'; put 'call symputx(''authdomain'',authdomain,''l'');'; put '/* get SCHEMA */'; put 'rc6=metadata_getnasn("&liburi",''UsingPackages'',1,up_uri);'; put 'rc7=metadata_getattr(up_uri,''SchemaName'',schema);'; put '&mD.put rc= connx_uri= rc2= conprop_uri= rc3= value= rc4= datasource='; put 'rc6= up_uri= rc7= schema=;'; put 'call symputx(''SQL_schema'',schema,''l'');'; put 'call symputx(''SQL_dsn'',datasource,''l'');'; put 'run;'; put '%if %length(&open_passthrough)>0 %then %do;'; put 'proc sql &sql_options;'; put 'connect to ODBC as &open_passthrough'; put '(INSERT_SQL=YES DATASRC=&sql_dsn. CONNECTION=global);'; put '%end;'; put '%else %do;'; put 'libname &libref ODBC DATASRC=&sql_dsn SCHEMA=&sql_schema &authdomain;'; put '%end;'; put '%end;'; put '%else %if &engine=POSTGRES %then %do;'; put '%put NOTE: Obtaining POSTGRES library details;'; put 'data _null_;'; put 'length database ignore_read_only_columns direct_exe preserve_col_names'; put 'preserve_tab_names server schema authdomain user password'; put 'prop name value uri urisrc $256.;'; put 'call missing (of _all_);'; put '/* get database value */'; put 'prop=''Connection.DBMS.Property.DB.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,database,"");'; put 'if database^='''' then database=''database=''!!quote(trim(database));'; put 'call symputx(''database'',database,''l'');'; put '/* get IGNORE_READ_ONLY_COLUMNS value */'; put 'prop=''Library.DBMS.Property.DBIROC.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,ignore_read_only_columns,"");'; put 'if ignore_read_only_columns^='''' then ignore_read_only_columns='; put '''ignore_read_only_columns=''!!ignore_read_only_columns;'; put 'call symputx(''ignore_read_only_columns'',ignore_read_only_columns,''l'');'; put '/* get DIRECT_EXE value */'; put 'prop=''Library.DBMS.Property.DirectExe.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,direct_exe,"");'; put 'if direct_exe^='''' then direct_exe=''direct_exe=''!!direct_exe;'; put 'call symputx(''direct_exe'',direct_exe,''l'');'; put '/* get PRESERVE_COL_NAMES value */'; put 'prop=''Library.DBMS.Property.PreserveColNames.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,preserve_col_names,"");'; put 'if preserve_col_names^='''' then preserve_col_names='; put '''preserve_col_names=''!!preserve_col_names;'; put 'call symputx(''preserve_col_names'',preserve_col_names,''l'');'; put '/* get PRESERVE_TAB_NAMES value */'; put '/* be careful with PRESERVE_TAB_NAMES=YES - it will mean your table will'; put 'become case sensitive!! */'; put 'prop=''Library.DBMS.Property.PreserveTabNames.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,preserve_tab_names,"");'; put 'if preserve_tab_names^='''' then preserve_tab_names='; put '''preserve_tab_names=''!!preserve_tab_names;'; put 'call symputx(''preserve_tab_names'',preserve_tab_names,''l'');'; put '/* get SERVER value */'; put 'if metadata_getnasn("&liburi","LibraryConnection",1,uri)>0 then do;'; put 'prop=''Connection.DBMS.Property.SERVER.Name.xmlKey.txt'';'; put 'rc=metadata_getprop(uri,prop,server,"");'; put 'end;'; put 'if server^='''' then server=''server=''!!quote(cats(server));'; put 'call symputx(''server'',server,''l'');'; put '/* get SCHEMA value */'; put 'if metadata_getnasn("&liburi","UsingPackages",1,uri)>0 then do;'; put 'rc=metadata_getattr(uri,"SchemaName",schema);'; put 'end;'; put 'if schema^='''' then schema=''schema=''!!schema;'; put 'call symputx(''schema'',schema,''l'');'; put '/* get AUTHDOMAIN value */'; put '/* this is only useful if the user account contains that auth domain'; put 'if metadata_getnasn("&liburi","DefaultLogin",1,uri)>0 then do;'; put 'rc=metadata_getnasn(uri,"Domain",1,urisrc);'; put 'rc=metadata_getattr(urisrc,"Name",authdomain);'; put 'end;'; put 'if authdomain^='''' then authdomain=''authdomain=''!!quote(trim(authdomain));'; put '*/'; put 'call symputx(''authdomain'',authdomain,''l'');'; put '/* get user & pass */'; put 'if authdomain='''' & metadata_getnasn("&liburi","DefaultLogin",1,uri)>0 then'; put 'do;'; put 'rc=metadata_getattr(uri,"UserID",user);'; put 'rc=metadata_getattr(uri,"Password",password);'; put 'end;'; put 'if user^='''' then do;'; put 'user=''user=''!!quote(trim(user));'; put 'password=''password=''!!quote(trim(password));'; put 'end;'; put 'call symputx(''user'',user,''l'');'; put 'call symputx(''password'',password,''l'');'; put '&md.put _all_;'; put 'run;'; put '%if %length(&open_passthrough)>0 %then %do;'; put '%put %str(WARN)ING: Passthrough option for postgres not yet supported;'; put '%return;'; put '%end;'; put '%else %do;'; put '%if &mdebug=1 %then %do;'; put '%put NOTE: Executing the following:/;'; put '%put NOTE- libname &libref POSTGRES &database &ignore_read_only_columns;'; put '%put NOTE- &direct_exe &preserve_col_names &preserve_tab_names;'; put '%put NOTE- &server &schema &authdomain &user &password //;'; put '%end;'; put 'libname &libref POSTGRES &database &ignore_read_only_columns &direct_exe'; put '&preserve_col_names &preserve_tab_names &server &schema &authdomain'; put '&user &password;'; put '%end;'; put '%end;'; put '%else %if &engine=ORACLE %then %do;'; put '%put NOTE: Obtaining &engine library details;'; put 'data _null_;'; put 'length assocuri1 assocuri2 assocuri3 authdomain path schema $256;'; put 'call missing (of _all_);'; put '/* get auth domain */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,assocuri1);'; put 'rc=metadata_getnasn(assocuri1,''Domain'',1,assocuri2);'; put 'rc=metadata_getattr(assocuri2,"Name",authdomain);'; put 'call symputx(''authdomain'',authdomain,''l'');'; put '/* path */'; put 'rc=metadata_getprop(assocuri1,'; put '''Connection.Oracle.Property.PATH.Name.xmlKey.txt'',path);'; put 'call symputx(''path'',path,''l'');'; put '/* schema */'; put 'rc=metadata_getnasn("&liburi",''UsingPackages'',1,assocuri3);'; put 'rc=metadata_getattr(assocuri3,''SchemaName'',schema);'; put 'call symputx(''schema'',schema,''l'');'; put 'run;'; put '%put NOTE: Executing the following:/; %put NOTE-;'; put '%put NOTE- libname &libref ORACLE path=&path schema=&schema;'; put '%put NOTE- authdomain=&authdomain;'; put '%put NOTE-;'; put 'libname &libref ORACLE path=&path schema=&schema authdomain=&authdomain;'; put '%end;'; put '%else %if &engine=SQLSVR %then %do;'; put '%put NOTE: Obtaining &engine library details;'; put 'data _null;'; put 'length assocuri1 assocuri2 assocuri3 authdomain path schema userid'; put 'passwd $256;'; put 'call missing (of _all_);'; put 'rc=metadata_getnasn("&liburi",''DefaultLogin'',1,assocuri1);'; put 'rc=metadata_getattr(assocuri1,"UserID",userid);'; put 'rc=metadata_getattr(assocuri1,"Password",passwd);'; put 'call symputx(''user'',userid,''l'');'; put 'call symputx(''pass'',passwd,''l'');'; put '/* path */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,assocuri2);'; put 'rc=metadata_getprop(assocuri2,'; put '''Connection.SQL.Property.Datasrc.Name.xmlKey.txt'',path);'; put 'call symputx(''path'',path,''l'');'; put '/* schema */'; put 'rc=metadata_getnasn("&liburi",''UsingPackages'',1,assocuri3);'; put 'rc=metadata_getattr(assocuri3,''SchemaName'',schema);'; put 'call symputx(''schema'',schema,''l'');'; put 'run;'; put '%put NOTE: Executing the following:/; %put NOTE-;'; put '%put NOTE- libname &libref SQLSVR datasrc=&path schema=&schema ;'; put '%put NOTE- user="&user" pass="XXX";'; put '%put NOTE-;'; put 'libname &libref SQLSVR datasrc=&path schema=&schema user="&user" pass="&pass";'; put '%end;'; put '%else %if &engine=TERADATA %then %do;'; put '%put NOTE: Obtaining &engine library details;'; put 'data _null;'; put 'length assocuri1 assocuri2 assocuri3 authdomain path schema userid'; put 'passwd $256;'; put 'call missing (of _all_);'; put '/* get auth domain */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,assocuri1);'; put 'rc=metadata_getnasn(assocuri1,''Domain'',1,assocuri2);'; put 'rc=metadata_getattr(assocuri2,"Name",authdomain);'; put 'call symputx(''authdomain'',authdomain,''l'');'; put '/*'; put 'rc=metadata_getnasn("&liburi",''DefaultLogin'',1,assocuri1);'; put 'rc=metadata_getattr(assocuri1,"UserID",userid);'; put 'rc=metadata_getattr(assocuri1,"Password",passwd);'; put 'call symputx(''user'',userid,''l'');'; put 'call symputx(''pass'',passwd,''l'');'; put '*/'; put '/* path */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,assocuri2);'; put 'rc=metadata_getprop(assocuri2,'; put '''Connection.Teradata.Property.SERVER.Name.xmlKey.txt'',path);'; put 'call symputx(''path'',path,''l'');'; put '/* schema */'; put 'rc=metadata_getnasn("&liburi",''UsingPackages'',1,assocuri3);'; put 'rc=metadata_getattr(assocuri3,''SchemaName'',schema);'; put 'call symputx(''schema'',schema,''l'');'; put 'run;'; put '%put NOTE: Executing the following:/; %put NOTE-;'; put '%put NOTE- libname &libref TERADATA server="&path" schema=&schema ;'; put '%put NOTe- authdomain=&authdomain;'; put '%put NOTE-;'; put 'libname &libref TERADATA server="&path" schema=&schema authdomain=&authdomain;'; put '%end;'; put '%else %if &engine= %then %do;'; put '%put NOTE: Libref &libref is not registered in metadata;'; put '%&mAbort.mp_abort('; put 'msg=%str(ERR)OR: Libref &libref is not registered in metadata'; put ',mac=mm_assigndirectlib.sas);'; put '%return;'; put '%end;'; put '%else %do;'; put '%put %str(WARN)ING: Engine &engine is currently unsupported;'; put '%put %str(WARN)ING- Please contact your support team.;'; put '%return;'; put '%end;'; put '%mend mm_assigndirectlib;'; put '%macro mm_getrepos('; put 'outds=work.mm_getrepos'; put ')/*/STORE SOURCE*/;'; put '* use a temporary fileref to hold the response;'; put 'filename response temp;'; put '/* get list of libraries */'; put 'proc metadata in='; put '"1"'; put 'out=response;'; put 'run;'; put '/* write the response to the log for debugging */'; put '/*'; put 'data _null_;'; put 'infile response lrecl=1048576;'; put 'input;'; put 'put _infile_;'; put 'run;'; put '*/'; put '/* create an XML map to read the response */'; put 'filename sxlemap temp;'; put 'data _null_;'; put 'file sxlemap;'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository";'; put 'put "";'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Id";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Name";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Desc";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@DefaultNS";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@RepositoryType";'; put 'put "characterstring20";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@RepositoryFormat";'; put 'put "characterstring10";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@Access";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@CurrentAccess";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@PauseState";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Path";'; put 'put "";'; put 'put "characterstring256";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Engine";'; put 'put "";'; put 'put "characterstring8";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Options";'; put 'put "";'; put 'put "characterstring32";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@MetadataCreated";'; put 'put "characterstring24";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@MetadataUpdated";'; put 'put "characterstring24";'; put 'put '''';'; put 'put ''
'';'; put 'run;'; put 'libname _XML_ xml xmlfileref=response xmlmap=sxlemap;'; put 'proc sort data= _XML_.SASRepos out=&outds;'; put 'by name;'; put 'run;'; put '/* clear references */'; put 'filename sxlemap clear;'; put 'filename response clear;'; put 'libname _XML_ clear;'; put '%mend mm_getrepos;'; put '%macro dc_assignlib(type,libref,passthru=);'; put '%put &sysmacroname entry vars:;'; put '%put _local_;'; put '/* take current repository */'; put '%local repo repocnt x;'; put '%let repo=%sysfunc(getoption(metarepository));'; put '/* get list of repositories and filter */'; put '%mm_getrepos(outds=repos)'; put '%let repocnt=0;'; put 'data repos;'; put 'set repos;'; put 'where repositorytype in(''CUSTOM'',''FOUNDATION'')'; put '& upcase(name) ne "%upcase(&repo)";'; put 'keep id name ;'; put 'call symputx(cats(''repo'',_n_),name,''l'');'; put 'call symputx(''repocnt'',_n_,''l'');'; put 'run;'; put '/* find out which of these repositories has the libref we are searching for */'; put '%local lib_uri;'; put '%let lib_uri=NOTFOUND;'; put 'data _null_; /* check default repo first */'; put 'length lib_uri $256;'; put 'call missing (of _all_);'; put 'rc=metadata_getnobj("omsobj:SASLibrary?@Libref =''&libref''",1,lib_uri);'; put 'call symputx(''lib_uri'',coalescec(lib_uri,''NOTFOUND''),''l'');'; put 'run;'; put '%if &lib_uri=NOTFOUND %then %do;'; put '%do x=1 %to &repocnt;'; put 'options metarepository=&&repo&x;'; put 'data _null_;'; put 'length lib_uri $256;'; put 'call missing (of _all_);'; put 'rc=metadata_getnobj("omsobj:SASLibrary?@Libref =''&libref''",1,lib_uri);'; put 'call symputx(''lib_uri'',coalescec(lib_uri,''NOTFOUND''),''l'');'; put 'run;'; put '%if &lib_uri ne NOTFOUND %then %let x=%eval(&repocnt+1);'; put '%end;'; put '%end;'; put '%if &type=READ %then %do;'; put '%mm_assignlib(&libref)'; put '%end;'; put '%else %if &type=WRITE %then %do;'; put '%mm_assigndirectlib(&libref,open_passthrough=&passthru)'; put '%end;'; put 'options metarepository=&repo;'; put '%mend dc_assignlib;'; put '%macro mf_existds(libds'; put ')/*/STORE SOURCE*/;'; put '%if %sysfunc(exist(&libds)) ne 1 & %sysfunc(exist(&libds,VIEW)) ne 1 %then 0;'; put '%else 1;'; put '%mend mf_existds;'; put '%macro mf_getattrn('; put 'libds'; put ',attr'; put ')/*/STORE SOURCE*/;'; put '%local dsid rc;'; put '%let dsid=%sysfunc(open(&libds,is));'; put '%if &dsid = 0 %then %do;'; put '%put %str(WARN)ING: Cannot open %trim(&libds), system message below;'; put '%put %sysfunc(sysmsg());'; put '-1'; put '%end;'; put '%else %do;'; put '%sysfunc(attrn(&dsid,&attr))'; put '%let rc=%sysfunc(close(&dsid));'; put '%end;'; put '%mend mf_getattrn;'; put '%macro mf_getvalue(libds,variable,filter=1'; put ')/*/STORE SOURCE*/;'; put '%if %mf_getattrn(&libds,NLOBS)>0 %then %do;'; put '%local dsid rc &variable;'; put '%let dsid=%sysfunc(open(&libds(where=(&filter))));'; put '%syscall set(dsid);'; put '%let rc = %sysfunc(fetch(&dsid));'; put '%let rc = %sysfunc(close(&dsid));'; put '%trim(&&&variable)'; put '%end;'; put '%mend mf_getvalue;'; put '%macro mf_abort(mac=mf_abort.sas, msg=, iftrue=%str(1=1)'; put ')/des=''ungraceful abort'' /*STORE SOURCE*/;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%put NOTE: /// mf_abort macro executing //;'; put '%if %length(&mac)>0 %then %put NOTE- called by &mac;'; put '%put NOTE - &msg;'; put '%abort;'; put '%mend mf_abort;'; put '/** @endcond */'; put '%macro mf_verifymacvars('; put 'verifyVars /* list of macro variable NAMES */'; put ',makeUpcase=NO /* set to YES to make all the variable VALUES uppercase */'; put ',mAbort=SOFT'; put ')/*/STORE SOURCE*/;'; put '%local verifyIterator verifyVar abortmsg;'; put '%do verifyIterator=1 %to %sysfunc(countw(&verifyVars,%str( )));'; put '%let verifyVar=%qscan(&verifyVars,&verifyIterator,%str( ));'; put '%if not %symexist(&verifyvar) %then %do;'; put '%let abortmsg= Variable &verifyVar is MISSING;'; put '%goto exit_err;'; put '%end;'; put '%if %length(%trim(&&&verifyVar))=0 %then %do;'; put '%let abortmsg= Variable &verifyVar is EMPTY;'; put '%goto exit_err;'; put '%end;'; put '%if &makeupcase=YES %then %do;'; put '%let &verifyVar=%upcase(&&&verifyvar);'; put '%end;'; put '%end;'; put '%goto exit_success;'; put '%exit_err:'; put '%put &abortmsg;'; put '%mf_abort(iftrue=(&mabort ne SOFT),'; put 'mac=mf_verifymacvars,'; put 'msg=%str(&abortmsg)'; put ')'; put '0'; put '%return;'; put '%exit_success:'; put '1'; put '%mend mf_verifymacvars;'; put '%macro mf_getVarFormat(libds /* two level ds name */'; put ', var /* variable name from which to return the format */'; put ', force=0'; put ')/*/STORE SOURCE*/;'; put '%local dsid vnum vformat rc vlen vtype;'; put '/* Open dataset */'; put '%let dsid = %sysfunc(open(&libds));'; put '%if &dsid > 0 %then %do;'; put '/* Get variable number */'; put '%let vnum = %sysfunc(varnum(&dsid, &var));'; put '/* Get variable format */'; put '%if(&vnum > 0) %then %let vformat=%sysfunc(varfmt(&dsid, &vnum));'; put '%else %do;'; put '%put NOTE: Variable &var does not exist in &libds;'; put '%let rc = %sysfunc(close(&dsid));'; put '%return;'; put '%end;'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: dataset &libds not opened! (rc=&dsid);'; put '%put &sysmacroname: %sysfunc(sysmsg());'; put '%return;'; put '%end;'; put '/* supply a default if no format available */'; put '%if %length(&vformat)<2 & &force=1 %then %do;'; put '%let vlen = %sysfunc(varlen(&dsid, &vnum));'; put '%let vtype = %sysfunc(vartype(&dsid, &vnum.));'; put '%if &vtype=C %then %let vformat=$&vlen..;'; put '%else %let vformat=best.;'; put '%end;'; put '/* Close dataset */'; put '%let rc = %sysfunc(close(&dsid));'; put '/* Return variable format */'; put '&vformat'; put '%mend mf_getVarFormat;'; put '%macro mddl_sas_cntlout(libds=WORK.CNTLOUT);'; put 'proc sql;'; put 'create table &libds('; put 'TYPE char(1) label='; put '''Format Type: either N (num fmt), C (char fmt), I (num infmt) or J (char infmt)'''; put ',FMTNAME char(32) label=''Format name'''; put ',FMTROW num label='; put '''CALCULATED Position of record by FMTNAME (reqd for multilabel formats)'''; put ',START char(32767) label=''Starting value for format'''; put '/*'; put 'Keep lengths of START and END the same to avoid this err:'; put '"Start is greater than end: -<."'; put 'Similar usage note: https://support.sas.com/kb/69/330.html'; put '*/'; put ',END char(32767) label=''Ending value for format'''; put ',LABEL char(32767) label=''Format value label'''; put ',MIN num length=3 label=''Minimum length'''; put ',MAX num length=3 label=''Maximum length'''; put ',DEFAULT num length=3 label=''Default length'''; put ',LENGTH num length=3 label=''Format length'''; put ',FUZZ num label=''Fuzz value'''; put ',PREFIX char(2) label=''Prefix characters'''; put ',MULT num label=''Multiplier'''; put ',FILL char(1) label=''Fill character'''; put ',NOEDIT num length=3 label=''Is picture string noedit?'''; put ',SEXCL char(1) label=''Start exclusion'''; put ',EEXCL char(1) label=''End exclusion'''; put ',HLO char(13) label='; put '''More info: https://core.sasjs.io/mddl__sas__cntlout_8sas_source.html'''; put ',DECSEP char(1) label=''Decimal separator'''; put ',DIG3SEP char(1) label=''Three-digit separator'''; put ',DATATYPE char(8) label=''Date/time/datetime?'''; put ',LANGUAGE char(8) label=''Language for date strings'''; put ');'; put '%local lib;'; put '%let libds=%upcase(&libds);'; put '%if %index(&libds,.)=0 %then %let lib=WORK;'; put '%else %let lib=%scan(&libds,1,.);'; put 'proc datasets lib=&lib noprint;'; put 'modify %scan(&libds,-1,.);'; put 'index create'; put 'pk_cntlout=(type fmtname fmtrow)'; put '/nomiss unique;'; put 'quit;'; put '%mend mddl_sas_cntlout;'; put '%macro mf_getuniquename(prefix=MC);'; put '&prefix.%substr(%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32-%length(&prefix))'; put '%mend mf_getuniquename;'; put '%macro mp_aligndecimal(var,width=8);'; put '%local tmpvar;'; put '%let tmpvar=%mf_getuniquename(prefix=aligndp);'; put 'length &tmpvar $&width;'; put 'if index(&var,''.'') then do;'; put '&tmpvar=cats(scan(&var,1,''.''));'; put '&tmpvar=right(&tmpvar);'; put '&var=&tmpvar!!''.''!!cats(scan(&var,2,''.''));'; put 'end;'; put 'else do;'; put '&tmpvar=cats(&var);'; put '&tmpvar=right(&tmpvar);'; put '&var=&tmpvar;'; put 'end;'; put 'drop &tmpvar;'; put '%mend mp_aligndecimal;'; put '%macro mp_cntlout('; put 'iftrue=(1=1)'; put ',libcat='; put ',cntlout=work.fmtextract'; put ',fmtlist=0'; put ')/*/STORE SOURCE*/;'; put '%local ddlds cntlds i;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%let ddlds=%mf_getuniquename();'; put '%let cntlds=%mf_getuniquename();'; put '%mddl_sas_cntlout(libds=&ddlds)'; put '%if %index(&libcat,-)>0 and %scan(&libcat,2,-)=FC %then %do;'; put '%let libcat=%scan(&libcat,1,-);'; put '%end;'; put 'proc format lib=&libcat cntlout=&cntlds;'; put '%if "&fmtlist" ne "0" and "&fmtlist" ne "" %then %do;'; put 'select'; put '%do i=1 %to %sysfunc(countw(&fmtlist,%str( )));'; put '%scan(&fmtlist,&i,%str( ))'; put '%end;'; put ';'; put '%end;'; put 'run;'; put 'data &cntlout/nonote2err;'; put 'if 0 then set &ddlds;'; put 'set &cntlds;'; put 'by type fmtname notsorted;'; put '/* align the numeric values to avoid overlapping ranges */'; put 'if type in ("I","N") then do;'; put '%mp_aligndecimal(start,width=16)'; put '%mp_aligndecimal(end,width=16)'; put 'end;'; put '/* create row marker. Data cannot be sorted without it! */'; put 'if first.fmtname then fmtrow=1;'; put 'else fmtrow+1;'; put 'run;'; put 'proc sort;'; put 'by type fmtname fmtrow;'; put 'run;'; put 'proc sql;'; put 'drop table &ddlds,&cntlds;'; put '%mend mp_cntlout;'; put '/** @endcond */'; put '%macro mf_getvarlist(libds'; put ',dlm=%str( )'; put ',quote=no'; put ',typefilter=A'; put ')/*/STORE SOURCE*/;'; put '/* declare local vars */'; put '%local outvar dsid nvars x rc dlm q var vtype;'; put '/* credit Rowland Hale - byte34 is double quote, 39 is single quote */'; put '%if %upcase("e)=DOUBLE %then %let q=%qsysfunc(byte(34));'; put '%else %if %upcase("e)=SINGLE %then %let q=%qsysfunc(byte(39));'; put '/* open dataset in macro */'; put '%let dsid=%sysfunc(open(&libds));'; put '%if &dsid %then %do;'; put '%let nvars=%sysfunc(attrn(&dsid,NVARS));'; put '%if &nvars>0 %then %do;'; put '/* add variables with supplied delimeter */'; put '%do x=1 %to &nvars;'; put '/* get variable type */'; put '%let vtype=%sysfunc(vartype(&dsid,&x));'; put '%if &vtype=&typefilter or &typefilter=A %then %do;'; put '%let var=&q.%sysfunc(varname(&dsid,&x))&q.;'; put '%if &var=&q&q %then %do;'; put '%put &sysmacroname: Empty column found in &libds!;'; put '%let var=&q. &q.;'; put '%end;'; put '%if %quote(&outvar)=%quote() %then %let outvar=&var;'; put '%else %let outvar=&outvar.&dlm.&var.;'; put '%end;'; put '%end;'; put '%end;'; put '%let rc=%sysfunc(close(&dsid));'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: Unable to open &libds (rc=&dsid);'; put '%put &sysmacroname: SYSMSG= %sysfunc(sysmsg());'; put '%let rc=%sysfunc(close(&dsid));'; put '%end;'; put '%do;%unquote(&outvar)%end;'; put '%mend mf_getvarlist;'; put '%macro mf_getvartype(libds /* two level name */'; put ', var /* variable name from which to return the type */'; put ')/*/STORE SOURCE*/;'; put '%local dsid vnum vtype rc;'; put '/* Open dataset */'; put '%let dsid = %sysfunc(open(&libds));'; put '%if &dsid. > 0 %then %do;'; put '/* Get variable number */'; put '%let vnum = %sysfunc(varnum(&dsid, &var));'; put '/* Get variable type (C/N) */'; put '%if(&vnum. > 0) %then %let vtype = %sysfunc(vartype(&dsid, &vnum.));'; put '%else %do;'; put '%put NOTE: Variable &var does not exist in &libds;'; put '%let vtype = %str( );'; put '%end;'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: dataset &libds not opened! (rc=&dsid);'; put '%put &sysmacroname: %sysfunc(sysmsg());'; put '%return;'; put '%end;'; put '/* Close dataset */'; put '%let rc = %sysfunc(close(&dsid));'; put '/* Return variable type */'; put '&vtype'; put '%mend mf_getvartype;'; put '%macro mf_nobs(libds'; put ')/*/STORE SOURCE*/;'; put '%mf_getattrn(&libds,NLOBS)'; put '%mend mf_nobs;'; put '%macro mp_filtergenerate(inds,outref=filter);'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(syscc=&syscc - on macro entry)'; put ')'; put 'filename &outref temp;'; put '%if %mf_nobs(&inds)=0 %then %do;'; put '/* ensure we have a default filter */'; put 'data _null_;'; put 'file &outref;'; put 'put ''1=1'';'; put 'run;'; put '%end;'; put '%else %do;'; put 'proc sort data=&inds;'; put 'by SUBGROUP_ID;'; put 'run;'; put 'data _null_;'; put 'file &outref lrecl=32800;'; put 'set &inds end=last;'; put 'by SUBGROUP_ID;'; put 'if _n_=1 then put ''(('';'; put 'else if first.SUBGROUP_ID then put +1 GROUP_LOGIC ''('';'; put 'else put +2 SUBGROUP_LOGIC;'; put 'put +4 VARIABLE_NM OPERATOR_NM RAW_VALUE;'; put 'if last.SUBGROUP_ID then put '')''@;'; put 'if last then put '')'';'; put 'run;'; put '%end;'; put '%mend mp_filtergenerate;'; put '%macro mp_filtervalidate(inref,targetds,abort=YES,outds=work.mp_filtervalidate);'; put '%mp_abort(iftrue= (&syscc ne 0 or &syserr ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(syscc=&syscc / syserr=&syserr - on macro entry)'; put ')'; put '%local fref1;'; put '%let fref1=%mf_getuniquefileref();'; put 'data _null_;'; put 'file &fref1;'; put 'infile &inref end=eof;'; put 'if _n_=1 then do;'; put 'put "proc sql;";'; put 'put "validate select * from &targetds";'; put 'put "where " ;'; put 'end;'; put 'input;'; put 'put _infile_;'; put 'putlog _infile_;'; put 'if eof then put ";quit;";'; put 'run;'; put '%inc &fref1;'; put 'data &outds;'; put 'if &sqlrc or &syscc or &syserr then do;'; put 'REASON_CD=''VALIDATION_ERR''!!''OR: ''!!'; put 'coalescec(symget(''SYSERRORTEXT''),symget(''SYSWARNINGTEXT''));'; put 'output;'; put 'end;'; put 'else stop;'; put 'run;'; put 'filename &fref1 clear;'; put '%if %mf_nobs(&outds)>0 %then %do;'; put '%if &abort=YES %then %do;'; put 'data _null_;'; put 'set &outds;'; put 'call symputx(''REASON_CD'',reason_cd,''l'');'; put 'stop;'; put 'run;'; put '%mp_abort('; put 'mac=&sysmacroname,'; put 'msg=%str(Filter validation issues.)'; put ')'; put '%end;'; put '%let syscc=1008;'; put '%end;'; put '%mend mp_filtervalidate;'; put '%macro mp_filtercheck(inds,targetds=,outds=work.badrecords,abort=YES);'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(syscc=&syscc - on macro entry)'; put ')'; put '/* Validate input column */'; put '%local vtype;'; put '%let vtype=%mf_getvartype(&inds,RAW_VALUE);'; put '%mp_abort(iftrue=(&abort=YES and &vtype ne C),'; put 'mac=&sysmacroname,'; put 'msg=%str(%str(ERR)OR: RAW_VALUE must be character)'; put ')'; put '%if &vtype ne C %then %do;'; put '%put &sysmacroname: RAW_VALUE must be character;'; put '%let syscc=42;'; put '%return;'; put '%end;'; put '/**'; put '* Sanitise the values based on valid value lists, then strip out'; put '* quotes, commas, periods and spaces.'; put '*/'; put '%local reason_cd nobs;'; put '%let nobs=0;'; put 'data &outds;'; put '/*length GROUP_LOGIC SUBGROUP_LOGIC $3 SUBGROUP_ID 8 VARIABLE_NM $32'; put 'OPERATOR_NM $10 RAW_VALUE $4000;*/'; put 'set &inds end=last;'; put 'length reason_cd $4032 vtype vtype2 $1 vnum dsid 8 tmp $4000;'; put 'drop tmp;'; put '/* quick check to ensure column exists */'; put 'if upcase(VARIABLE_NM) not in'; put '(%upcase(%mf_getvarlist(&targetds,dlm=%str(,),quote=SINGLE)))'; put 'then do;'; put 'REASON_CD="Variable "!!cats(variable_nm)!!" not in &targetds";'; put 'putlog REASON_CD= VARIABLE_NM=;'; put 'call symputx(''reason_cd'',reason_cd,''l'');'; put 'call symputx(''nobs'',_n_,''l'');'; put 'output;'; put 'return;'; put 'end;'; put '/* need to open the dataset to get the column type */'; put 'retain dsid;'; put 'if _n_=1 then dsid=open("&targetds","i");'; put 'if dsid>0 then do;'; put 'vnum=varnum(dsid,VARIABLE_NM);'; put 'if vnum<1 then do;'; put '/* should not happen as was also tested for above */'; put 'REASON_CD=cats("Variable (",VARIABLE_NM,") not found in &targetds");'; put 'putlog REASON_CD= dsid=;'; put 'call symputx(''reason_cd'',reason_cd,''l'');'; put 'call symputx(''nobs'',_n_,''l'');'; put 'output;'; put 'goto endstep;'; put 'end;'; put '/* now we can get the type */'; put 'else vtype=vartype(dsid,vnum);'; put 'end;'; put 'else do;'; put 'REASON_CD=cats("Could not open &targetds");'; put 'putlog REASON_CD= dsid=;'; put 'call symputx(''reason_cd'',reason_cd,''l'');'; put 'call symputx(''nobs'',_n_,''l'');'; put 'output;'; put 'stop;'; put 'end;'; put '/* closed list checks */'; put 'if GROUP_LOGIC not in (''AND'',''OR'') then do;'; put 'REASON_CD=''GROUP_LOGIC should be AND/OR, not:''!!cats(GROUP_LOGIC);'; put 'putlog REASON_CD= GROUP_LOGIC=;'; put 'call symputx(''reason_cd'',reason_cd,''l'');'; put 'call symputx(''nobs'',_n_,''l'');'; put 'output;'; put 'end;'; put 'if SUBGROUP_LOGIC not in (''AND'',''OR'') then do;'; put 'REASON_CD=''SUBGROUP_LOGIC should be AND/OR, not:''!!cats(SUBGROUP_LOGIC);'; put 'putlog REASON_CD= SUBGROUP_LOGIC=;'; put 'call symputx(''reason_cd'',reason_cd,''l'');'; put 'call symputx(''nobs'',_n_,''l'');'; put 'output;'; put 'end;'; put 'if mod(SUBGROUP_ID,1) ne 0 then do;'; put 'REASON_CD=''SUBGROUP_ID should be integer, not ''!!cats(subgroup_id);'; put 'putlog REASON_CD= SUBGROUP_ID=;'; put 'call symputx(''reason_cd'',reason_cd,''l'');'; put 'call symputx(''nobs'',_n_,''l'');'; put 'output;'; put 'end;'; put 'if OPERATOR_NM not in'; put '(''='',''>'',''<'',''<='',''>='',''NE'',''GE'',''LE'',''BETWEEN'',''IN'',''NOT IN'',''CONTAINS'')'; put 'then do;'; put 'REASON_CD=''Invalid OPERATOR_NM: ''!!cats(OPERATOR_NM);'; put 'putlog REASON_CD= OPERATOR_NM=;'; put 'call symputx(''reason_cd'',reason_cd,''l'');'; put 'call symputx(''nobs'',_n_,''l'');'; put 'output;'; put 'end;'; put '/* special missing logic */'; put 'if vtype=''N'' & OPERATOR_NM in (''='',''>'',''<'',''<='',''>='',''NE'',''GE'',''LE'') then do;'; put 'if cats(upcase(raw_value)) in ('; put '''.'',''.A'',''.B'',''.C'',''.D'',''.E'',''.F'',''.G'',''.H'',''.I'',''.J'',''.K'',''.L'',''.M'',''.N'''; put '''.N'',''.O'',''.P'',''.Q'',''.R'',''.S'',''.T'',''.U'',''.V'',''.W'',''.X'',''.Y'',''.Z'',''._'''; put ')'; put 'then do;'; put '/* valid numeric - exit data step loop */'; put 'return;'; put 'end;'; put 'else if subpad(upcase(raw_value),1,1) in ('; put '''A'',''B'',''C'',''D'',''E'',''F'',''G'',''H'',''I'',''J'',''K'',''L'',''M'',''N'''; put '''N'',''O'',''P'',''Q'',''R'',''S'',''T'',''U'',''V'',''W'',''X'',''Y'',''Z'',''_'''; put ')'; put 'then do;'; put '/* check if the raw_value contains a valid variable NAME */'; put 'vnum=varnum(dsid,subpad(raw_value,1,32));'; put 'if vnum>0 then do;'; put '/* now we can get the type */'; put 'vtype2=vartype(dsid,vnum);'; put '/* check type matches */'; put 'if vtype2=vtype then do;'; put '/* valid target var - exit loop */'; put 'return;'; put 'end;'; put 'else do;'; put 'REASON_CD=cats("Compared Type (",vtype2,") is not (",vtype,")");'; put 'putlog REASON_CD= dsid=;'; put 'call symputx(''reason_cd'',reason_cd,''l'');'; put 'call symputx(''nobs'',_n_,''l'');'; put 'output;'; put 'goto endstep;'; put 'end;'; put 'end;'; put 'end;'; put 'end;'; put '/* special logic */'; put 'if OPERATOR_NM in (''IN'',''NOT IN'',''BETWEEN'') then do;'; put 'if OPERATOR_NM=''BETWEEN'' then raw_value1=tranwrd(raw_value,'' AND '','','');'; put 'else do;'; put 'if substr(raw_value,1,1) ne ''('''; put 'or substr(cats(reverse(raw_value)),1,1) ne '')'''; put 'then do;'; put 'REASON_CD=''Missing start/end bracket in RAW_VALUE'';'; put 'putlog REASON_CD= OPERATOR_NM= raw_value= raw_value1= ;'; put 'call symputx(''reason_cd'',reason_cd,''l'');'; put 'call symputx(''nobs'',_n_,''l'');'; put 'output;'; put 'end;'; put 'else raw_value1=substr(raw_value,2,max(length(raw_value)-2,0));'; put 'end;'; put '/* we now have a comma seperated list of values */'; put 'if vtype=''N'' then do i=1 to countc(raw_value1, '','')+1;'; put 'tmp=scan(raw_value1,i,'','');'; put 'if cats(tmp) ne ''.'' and input(tmp, ?? 8.) eq . then do;'; put 'if OPERATOR_NM =''BETWEEN'' and subpad(upcase(tmp),1,1) in ('; put '''A'',''B'',''C'',''D'',''E'',''F'',''G'',''H'',''I'',''J'',''K'',''L'',''M'',''N'''; put '''N'',''O'',''P'',''Q'',''R'',''S'',''T'',''U'',''V'',''W'',''X'',''Y'',''Z'',''_'''; put ')'; put 'then do;'; put '/* check if the raw_value contains a valid variable NAME */'; put '/* is not valid syntax for IN or NOT IN */'; put 'vnum=varnum(dsid,subpad(tmp,1,32));'; put 'if vnum>0 then do;'; put '/* now we can get the type */'; put 'vtype2=vartype(dsid,vnum);'; put '/* check type matches */'; put 'if vtype2=vtype then do;'; put '/* valid target var - exit loop */'; put 'return;'; put 'end;'; put 'else do;'; put 'REASON_CD=cats("Compared Type (",vtype2,") is not (",vtype,")");'; put 'putlog REASON_CD= dsid=;'; put 'call symputx(''reason_cd'',reason_cd,''l'');'; put 'call symputx(''nobs'',_n_,''l'');'; put 'output;'; put 'goto endstep;'; put 'end;'; put 'end;'; put 'end;'; put 'REASON_CD=''Non Numeric value provided'';'; put 'putlog REASON_CD= OPERATOR_NM= raw_value= raw_value1= ;'; put 'call symputx(''reason_cd'',reason_cd,''l'');'; put 'call symputx(''nobs'',_n_,''l'');'; put 'output;'; put 'end;'; put 'return;'; put 'end;'; put 'end;'; put 'else raw_value1=raw_value;'; put '/* remove nested literals eg '''' */'; put 'raw_value1=tranwrd(raw_value1,"''''",'''');'; put '/* now match string literals (always single quotes) */'; put 'raw_value2=raw_value1;'; put 'regex = prxparse("s/(\'').*?(\'')//");'; put 'call prxchange(regex,-1,raw_value2);'; put '/* remove commas and periods*/'; put 'raw_value3=compress(raw_value2,'',.'');'; put '/* output records that contain values other than digits and spaces */'; put 'if notdigit(compress(raw_value3,'' ''))>0 then do;'; put 'if vtype=''C'' and subpad(upcase(raw_value),1,1) in ('; put '''A'',''B'',''C'',''D'',''E'',''F'',''G'',''H'',''I'',''J'',''K'',''L'',''M'',''N'''; put '''N'',''O'',''P'',''Q'',''R'',''S'',''T'',''U'',''V'',''W'',''X'',''Y'',''Z'',''_'''; put ')'; put 'then do;'; put '/* check if the raw_value contains a valid variable NAME */'; put 'vnum=varnum(dsid,subpad(raw_value,1,32));'; put 'if vnum>0 then do;'; put '/* now we can get the type */'; put 'vtype2=vartype(dsid,vnum);'; put '/* check type matches */'; put 'if vtype2=vtype then do;'; put '/* valid target var - exit loop */'; put 'return;'; put 'end;'; put 'else do;'; put 'REASON_CD=cats("Compared Char Type (",vtype2,") is not (",vtype,")");'; put 'putlog REASON_CD= dsid=;'; put 'call symputx(''reason_cd'',reason_cd,''l'');'; put 'call symputx(''nobs'',_n_,''l'');'; put 'output;'; put 'goto endstep;'; put 'end;'; put 'end;'; put 'end;'; put 'putlog raw_value3= $hex32.;'; put 'REASON_CD=cats(''Invalid RAW_VALUE:'',raw_value);'; put 'putlog (_all_)(=);'; put 'call symputx(''reason_cd'',reason_cd,''l'');'; put 'call symputx(''nobs'',_n_,''l'');'; put 'output;'; put 'end;'; put 'endstep:'; put 'if last then rc=close(dsid);'; put 'run;'; put 'data _null_;'; put 'set &outds end=last;'; put 'putlog (_all_)(=);'; put 'run;'; put '%mp_abort(iftrue=(&abort=YES and &nobs>0),'; put 'mac=&sysmacroname,'; put 'msg=%str(Data issue: %superq(reason_cd))'; put ')'; put '%if &nobs>0 %then %do;'; put '%let syscc=1008;'; put '%return;'; put '%end;'; put '/**'; put '* syntax checking passed but it does not mean the filter is valid'; put '* for that we can run a proc sql validate query'; put '*/'; put '%local fref1;'; put '%let fref1=%mf_getuniquefileref();'; put '%mp_filtergenerate(&inds,outref=&fref1)'; put '/* this macro will also set syscc to 1008 if any issues found */'; put '%mp_filtervalidate(&fref1,&targetds,outds=&outds,abort=&abort)'; put '%mend mp_filtercheck;'; put '* SAS Macros end;'; put '* SAS Includes start;'; put '* SAS Includes end;'; put '* Binary Files start;'; put '* Binary Files end;'; put '* ServiceInit start;'; put 'options noquotelenmax ps=max;'; put '/* create dummy macros to prevent stpbegin / stpend from corrupting sessions */'; put '%macro stpbegin();'; put '%put NOTE: the STPBEGIN macro should not be used for web apps!;'; put '%mend stpbegin;'; put '%macro stpend();'; put '%put NOTE: the STPEND macro should not be used for web apps!;'; put '%mend stpend;'; put '* ServiceInit end;'; put '* Service start;'; put '/**'; put '@file'; put '@brief Retrieves column info to enable population of dropdowns'; put '@details An optional filterquery may be provided, if so then it is validated'; put 'and then used to filter the subsequent results.'; put '

Service Inputs

'; put '
IWANT
'; put 'The STARTROW and ROWS variables are used to fetch additional values beyond'; put 'the initial default (4000).'; put '|libds:$19.|col:$9.|STARTROW:8.|ROWS:8.|'; put '|---|---|---|---|'; put '|DC258467.MPE_X_TEST|SOME_TIME|4001|1000'; put '
FILTERQUERY
'; put '|GROUP_LOGIC:$3|SUBGROUP_LOGIC:$3|SUBGROUP_ID:8.|VARIABLE_NM:$32|OPERATOR_NM:$10|RAW_VALUE:$32767|'; put '|---|---|---|---|---|---|'; put '|AND|AND|1|SOME_BESTNUM|>|1|'; put '|AND|AND|1|SOME_TIME|=|77333|'; put '

Service Outputs

'; put '
VALS
'; put 'The type of this column actually depends on the underlying column type, so it can change'; put '|FORMATTED|UNFORMATTED|'; put '|---|---|'; put '|$44.00|44|'; put '
META
'; put '|COLUMN:$32.|SASFORMAT:$32.|STARTROW:8.|ROWS:8.|'; put '|---|---|---|---|'; put '|COL_NAME|DOLLAR8.2|4001|1000'; put '

SAS Macros

'; put '@li mf_existds.sas'; put '@li mf_getvalue.sas'; put '@li mf_verifymacvars.sas'; put '@li dc_assignlib.sas'; put '@li mf_getvarformat.sas'; put '@li mp_abort.sas'; put '@li mp_cntlout.sas'; put '@li mp_filtercheck.sas'; put '@li mp_filtergenerate.sas'; put '@version 9.2'; put '@author 4GL Apps Ltd.'; put '@copyright 4GL Apps Ltd. This code may only be used within Data Controller'; put 'and may not be re-distributed or re-sold without the express permission of'; put '4GL Apps Ltd.'; put '**/'; put '%mpeinit()'; put '/* input table may or may not exist */'; put 'data work.initvars;'; put 'length GROUP_LOGIC $3 SUBGROUP_LOGIC $3 SUBGROUP_ID 8 VARIABLE_NM $32'; put 'OPERATOR_NM $10 RAW_VALUE $32767;'; put 'call missing(of _all_);'; put 'stop;'; put 'data work.filterquery;'; put 'set %sysfunc(ifc('; put '%mf_existds(work.filterquery)=1'; put ',work.filterquery'; put ',work.initvars'; put '));'; put 'run;'; put '/* print data for debugging */'; put 'data _null_;'; put 'set work.iwant;'; put 'put (_all_)(=);'; put 'run;'; put 'data _null_;'; put 'set work.filterquery;'; put 'put (_all_)(=);'; put 'run;'; put '%let libds=%mf_getvalue(work.iwant,libds);'; put '%let col2=%mf_getvalue(work.iwant,col);'; put '%let is_fmt=0;'; put '%let startrow=1;'; put '%let rows=4000;'; put '%put &=libds;'; put '%put &=col2;'; put '%mp_abort(iftrue= (%mf_verifymacvars(libds col2)=0)'; put ',mac=&_program..sas'; put ',msg=%str(Missing inputs from iwant. Libds=&libds col=&col2 )'; put ')'; put '%dc_assignlib(WRITE,%scan(&libds,1,.))'; put 'data _null_;'; put 'call missing(startrow,rows);'; put 'set work.iwant;'; put '/* check if the request is for a format catalog */'; put 'call symputx(''orig_libds'',libds);'; put 'is_fmt=0;'; put 'if substr(cats(reverse(libds)),1,3)=:''CF-'' then do;'; put 'libds=scan(libds,1,''-'');'; put 'putlog "Format Catalog Captured";'; put 'call symputx(''libds'',''work.fmtextract'');'; put 'is_fmt=1;'; put 'end;'; put 'call symputx(''is_fmt'',is_fmt);'; put 'call symputx(''startrow'',coalesce(startrow,&startrow));'; put 'call symputx(''rows'',coalesce(rows,&rows));'; put 'putlog (_all_)(=);'; put 'run;'; put '%mp_cntlout('; put 'iftrue=(&is_fmt=1)'; put ',libcat=&orig_libds'; put ',fmtlist=0'; put ',cntlout=work.fmtextract'; put ')'; put '/**'; put '* Validate the filter query'; put '*/'; put '%mp_filtercheck(work.filterquery,targetds=&libds,abort=YES)'; put '/**'; put '* Prepare the query'; put '*/'; put '%mp_filtergenerate(work.filterquery,outref=myfilter)'; put '/* cannot %inc in a sql where clause, only data step, so - use a view */'; put 'data work.vw_vals/view=work.vw_vals;'; put 'set &libds;'; put 'where %inc myfilter;;'; put 'run;'; put 'proc sql;'; put 'create view work.vw_vals_sorted as'; put 'select distinct'; put 'put(&col2,%mf_getVarFormat(&libds,&col2,force=1)) as formatted,'; put '&col2 as unformatted'; put 'from work.vw_vals;'; put '/* restrict num of output values */'; put 'data work.vals;'; put 'set work.vw_vals_sorted;'; put 'if _n_ ge &startrow;'; put 'x+1;'; put 'if x>&rows then stop;'; put 'drop x;'; put 'run;'; put 'data vals;'; put '/* ensure empty value if table is empty, for dropdowns */'; put 'if nobs=0 then output;'; put 'set vals nobs=nobs;'; put 'format unformatted ;'; put 'output;'; put 'run;'; put 'proc sql noprint;'; put 'select count(*) into: nobs from work.vw_vals_sorted;'; put 'data meta;'; put 'column="&col2";'; put 'sasformat="%mf_getVarFormat(&libds,&col2)";'; put 'startrow=&startrow;'; put 'rows=&rows;'; put 'nobs=&nobs;'; put 'run;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(syscc=&syscc)'; put ')'; put '%webout(OPEN)'; put '%webout(OBJ,vals,missing=STRING,showmeta=YES)'; put '%webout(OBJ,meta)'; put '%webout(CLOSE)'; put '%mpeterm()'; put '* Service end;'; run; %mm_createwebservice(path=&appLoc/&path, name=&service, code=sascode, server=&serverName, replace=yes) filename sascode clear; %let service=getddl; filename sascode temp lrecl=32767; data _null_; file sascode; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '/**'; put '@file mp_jsonout.sas'; put '@brief Writes JSON in SASjs format to a fileref'; put '@details This macro can be used to OPEN a JSON stream and send one or more'; put 'tables as arrays of rows, where each row can be an object or a nested array.'; put 'There are two engines available - DATASTEP or PROCJSON.'; put 'PROC JSON is fast but will produce errs like the ones below if'; put 'special chars are encountered.'; put '> (ERR)OR: Some code points did not transcode.'; put '> An object or array close is not valid at this point in the JSON text.'; put '> Date value out of range'; put 'If this happens, try running with ENGINE=DATASTEP.'; put 'The DATASTEP engine is used to handle special SAS missing numerics, and'; put 'can also convert entire datasets to formatted values. Output JSON is always'; put 'in UTF-8.'; put 'Usage:'; put 'filename tmp temp;'; put 'data class; set sashelp.class;run;'; put '%mp_jsonout(OPEN,jref=tmp)'; put '%mp_jsonout(OBJ,class,jref=tmp)'; put '%mp_jsonout(OBJ,class,dslabel=class2,jref=tmp,showmeta=Y)'; put '%mp_jsonout(CLOSE,jref=tmp)'; put 'data _null_;'; put 'infile tmp;'; put 'input;putlog _infile_;'; put 'run;'; put 'If you are building web apps with SAS then you are strongly encouraged to use'; put 'the mX_createwebservice macros in combination with the'; put '[sasjs adapter](https://github.com/sasjs/adapter).'; put 'For more information see https://sasjs.io'; put '@param [in] action Valid values:'; put '@li OPEN - opens the JSON'; put '@li OBJ - sends a table with each row as an object'; put '@li ARR - sends a table with each row in an array'; put '@li CLOSE - closes the JSON'; put '@param [in] ds The dataset to send. Must be a work table.'; put '@param [out] jref= (_webout) The fileref to which to send the JSON'; put '@param [out] dslabel= The name to give the table in the exported JSON'; put '@param [in] fmt= (Y) Whether to keep (Y) or strip (N) formats from the table'; put '@param [in] engine= (DATASTEP) Which engine to use to send the JSON. Options:'; put '@li PROCJSON (default)'; put '@li DATASTEP (more reliable when data has non standard characters)'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to JSON'; put '

Related Files

'; put '@li mp_ds2fmtds.sas'; put '@version 9.2'; put '@author Allan Bowe'; put '@source https://github.com/sasjs/core'; put '**/'; put '%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y'; put ',engine=DATASTEP'; put ',missing=NULL'; put ',showmeta=N'; put ',maxobs=MAX'; put ')/*/STORE SOURCE*/;'; put '%local tempds colinfo fmtds i numcols numobs stmt_obs lastobs optval'; put 'tmpds1 tmpds2 tmpds3 tmpds4;'; put '%let numcols=0;'; put '%if &maxobs ne MAX %then %let stmt_obs=%str(if _n_>&maxobs then stop;);'; put '%if &action=OPEN %then %do;'; put 'options nobomfile;'; put 'data _null_;file &jref encoding=''utf-8'' lrecl=200;'; put 'put ''{"PROCESSED_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''"'';'; put 'run;'; put '%end;'; put '%else %if (&action=ARR or &action=OBJ) %then %do;'; put '/* force variable names to always be uppercase in the JSON */'; put 'options validvarname=upcase;'; put '/* To avoid issues with _webout on EBI - such as encoding diffs and truncation'; put '(https://support.sas.com/kb/49/325.html) we use temporary files */'; put 'filename _sjs1 temp lrecl=200 ;'; put 'data _null_; file _sjs1 encoding=''utf-8'';'; put 'put ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":";'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjs1 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs1 clear;'; put '/* grab col defs */'; put 'proc contents noprint data=&ds'; put 'out=_data_(keep=name type length format formatl formatd varnum label);'; put 'run;'; put '%let colinfo=%scan(&syslast,2,.);'; put 'proc sort data=&colinfo;'; put 'by varnum;'; put 'run;'; put '/* move meta to mac vars */'; put 'data &colinfo;'; put 'if _n_=1 then call symputx(''numcols'',nobs,''l'');'; put 'set &colinfo end=last nobs=nobs;'; put 'name=upcase(name);'; put '/* fix formats */'; put 'if type=2 or type=6 then do;'; put 'typelong=''char'';'; put 'length fmt $49.;'; put 'if format='''' then fmt=cats(''$'',length,''.'');'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else fmt=cats(format,formatl,''.'');'; put 'end;'; put 'else do;'; put 'typelong=''num'';'; put 'if format='''' then fmt=''best.'';'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else if formatd=0 then fmt=cats(format,formatl,''.'');'; put 'else fmt=cats(format,formatl,''.'',formatd);'; put 'end;'; put '/* 32 char unique name */'; put 'newname=''sasjs''!!substr(cats(put(md5(name),$hex32.)),1,27);'; put 'call symputx(cats(''name'',_n_),name,''l'');'; put 'call symputx(cats(''newname'',_n_),newname,''l'');'; put 'call symputx(cats(''length'',_n_),length,''l'');'; put 'call symputx(cats(''fmt'',_n_),fmt,''l'');'; put 'call symputx(cats(''type'',_n_),type,''l'');'; put 'call symputx(cats(''typelong'',_n_),typelong,''l'');'; put 'call symputx(cats(''label'',_n_),coalescec(label,name),''l'');'; put '/* overwritten when fmt=Y and a custom format exists in catalog */'; put 'if typelong=''num'' then call symputx(cats(''fmtlen'',_n_),200,''l'');'; put 'else call symputx(cats(''fmtlen'',_n_),min(32767,ceil((length+10)*1.5)),''l'');'; put 'run;'; put '%let tempds=%substr(_%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql;'; put 'select count(*) into: lastobs from &ds;'; put '%if &maxobs ne MAX %then %let lastobs=%sysfunc(min(&lastobs,&maxobs));'; put '%if &engine=PROCJSON %then %do;'; put '%if &missing=STRING %then %do;'; put '%put &sysmacroname: Special Missings not supported in proc json.;'; put '%put &sysmacroname: Switching to DATASTEP engine;'; put '%goto datastep;'; put '%end;'; put 'data &tempds;'; put 'set &ds;'; put '&stmt_obs;'; put '%if &fmt=N %then format _numeric_ best32.;;'; put '/* PRETTY is necessary to avoid line truncation in large files */'; put 'filename _sjs2 temp lrecl=131068 encoding=''utf-8'';'; put 'proc json out=_sjs2 pretty'; put '%if &action=ARR %then nokeys ;'; put ';export &tempds / nosastags fmtnumeric;'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs2 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs2 clear;'; put '%end;'; put '%else %if &engine=DATASTEP %then %do;'; put '%datastep:'; put '%if %sysfunc(exist(&ds)) ne 1 & %sysfunc(exist(&ds,VIEW)) ne 1'; put '%then %do;'; put '%put &sysmacroname: &ds NOT FOUND!!!;'; put '%return;'; put '%end;'; put '%if &fmt=Y %then %do;'; put '/**'; put '* Extract format definitions'; put '* First, by getting library locations from dictionary.formats'; put '* Then, by exporting the width using proc format'; put '* Cannot use maxw from sashelp.vformat as not always populated'; put '* Cannot use fmtinfo() as not supported in all flavours'; put '*/'; put '%let tmpds1=%substr(fmtsum%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds2=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds3=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds4=%substr(col%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql noprint;'; put 'create table &tmpds1 as'; put 'select cats(libname,''.'',memname) as FMTCAT,'; put 'FMTNAME'; put 'from dictionary.formats'; put 'where fmttype=''F'' and libname is not null'; put 'and fmtname in (select format from &colinfo where format is not null)'; put 'order by 1;'; put 'create table &tmpds2('; put 'FMTNAME char(32),'; put 'LENGTH num'; put ');'; put '%local catlist cat fmtlist i;'; put 'select distinct fmtcat into: catlist separated by '' '' from &tmpds1;'; put '%do i=1 %to %sysfunc(countw(&catlist,%str( )));'; put '%let cat=%scan(&catlist,&i,%str( ));'; put 'proc sql;'; put 'select distinct fmtname into: fmtlist separated by '' '''; put 'from &tmpds1 where fmtcat="&cat";'; put 'proc format lib=&cat cntlout=&tmpds3(keep=fmtname length);'; put 'select &fmtlist;'; put 'run;'; put 'proc sql;'; put 'insert into &tmpds2 select distinct fmtname,length from &tmpds3;'; put '%end;'; put 'proc sql;'; put 'create table &tmpds4 as'; put 'select a.*, b.length as MAXW'; put 'from &colinfo a'; put 'left join &tmpds2 b'; put 'on cats(a.format)=cats(upcase(b.fmtname))'; put 'order by a.varnum;'; put 'data _null_;'; put 'set &tmpds4;'; put 'if not missing(maxw);'; put 'call symputx('; put 'cats(''fmtlen'',_n_),'; put '/* vars need extra padding due to JSON escaping of special chars */'; put 'min(32767,ceil((max(length,maxw)+10)*1.5))'; put ',''l'''; put ');'; put 'run;'; put '/* configure varlenchk - as we are explicitly shortening the variables */'; put '%let optval=%sysfunc(getoption(varlenchk));'; put 'options varlenchk=NOWARN;'; put 'data _data_(compress=char);'; put '/* shorten the new vars */'; put 'length'; put '%do i=1 %to &numcols;'; put '&&name&i $&&fmtlen&i'; put '%end;'; put ';'; put '/* rename on entry */'; put 'set &ds(rename=('; put '%do i=1 %to &numcols;'; put '&&name&i=&&newname&i'; put '%end;'; put '));'; put '&stmt_obs;'; put 'drop'; put '%do i=1 %to &numcols;'; put '&&newname&i'; put '%end;'; put ';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=num %then %do;'; put '&&name&i=cats(put(&&newname&i,&&fmt&i));'; put '%end;'; put '%else %do;'; put '&&name&i=put(&&newname&i,&&fmt&i);'; put '%end;'; put '%end;'; put 'if _error_ then do;'; put 'call symputx(''syscc'',1012);'; put 'stop;'; put 'end;'; put 'run;'; put '%let fmtds=&syslast;'; put 'options varlenchk=&optval;'; put '%end;'; put 'proc format; /* credit yabwon for special null removal */'; put 'value bart (default=40)'; put '%if &missing=NULL %then %do;'; put '._ - .z = null'; put '%end;'; put '%else %do;'; put '._ = [quote()]'; put '. = null'; put '.a - .z = [quote()]'; put '%end;'; put 'other = [best.];'; put 'data &tempds;'; put 'attrib _all_ label='''';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'length &&name&i $&&fmtlen&i...;'; put 'format &&name&i $&&fmtlen&i...;'; put '%end;'; put '%end;'; put '%if &fmt=Y %then %do;'; put 'set &fmtds;'; put '%end;'; put '%else %do;'; put 'set &ds;'; put '%end;'; put '&stmt_obs;'; put 'format _numeric_ bart.;'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'if findc(&&name&i,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put '&&name&i=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,&&name&i)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else &&name&i=quote(cats(&&name&i));'; put '%end;'; put '%end;'; put 'run;'; put 'filename _sjs3 temp lrecl=131068 ;'; put 'data _null_;'; put 'file _sjs3 encoding=''utf-8'';'; put 'if _n_=1 then put "[";'; put 'set &tempds;'; put 'if _n_>1 then put "," @; put'; put '%if &action=ARR %then "[" ; %else "{" ;'; put '%do i=1 %to &numcols;'; put '%if &i>1 %then "," ;'; put '%if &action=OBJ %then """&&name&i"":" ;'; put '"&&name&i"n /* name literal for reserved variable names */'; put '%end;'; put '%if &action=ARR %then "]" ; %else "}" ; ;'; put '/* close out the table */'; put 'data _null_;'; put 'file _sjs3 mod encoding=''utf-8'';'; put 'put '']'';'; put 'run;'; put 'data _null_;'; put 'infile _sjs3 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs3 clear;'; put '%end;'; put 'proc sql;'; put 'drop table &colinfo, &tempds;'; put '%if %substr(&showmeta,1,1)=Y %then %do;'; put 'filename _sjs4 temp lrecl=131068 encoding=''utf-8'';'; put 'data _null_;'; put 'file _sjs4;'; put 'length label $350;'; put 'put ", ""$%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":{""vars"":{";'; put 'do i=1 to &numcols;'; put 'name=quote(trim(symget(cats(''name'',i))));'; put 'format=quote(trim(symget(cats(''fmt'',i))));'; put 'label=quote(prxchange(''s/\\/\\\\/'',-1,trim(symget(cats(''label'',i)))));'; put 'length=quote(trim(symget(cats(''length'',i))));'; put 'type=quote(trim(symget(cats(''typelong'',i))));'; put 'if i>1 then put "," @@;'; put 'put name '':{"format":'' format '',"label":'' label'; put ''',"length":'' length '',"type":'' type ''}'';'; put 'end;'; put 'put ''}}'';'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs4 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs4 clear;'; put '%end;'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put 'data _null_; file &jref encoding=''utf-8'' mod ;'; put 'put "}";'; put 'run;'; put '%end;'; put '%mend mp_jsonout;'; put '/**'; put '@file mm_webout.sas'; put '@brief Send data to/from SAS Stored Processes'; put '@details This macro should be added to the start of each Stored Process,'; put '**immediately** followed by a call to:'; put '%mm_webout(FETCH)'; put 'This will read all the input data and create same-named SAS datasets in the'; put 'WORK library. You can then insert your code, and send data back using the'; put 'following syntax:'; put 'data some datasets; * make some data ;'; put 'retain some columns;'; put 'run;'; put '%mm_webout(OPEN)'; put '%mm_webout(ARR,some) * Array format, fast, suitable for large tables ;'; put '%mm_webout(OBJ,datasets) * Object format, easier to work with ;'; put 'Finally, wrap everything up send some helpful system variables too'; put '%mm_webout(CLOSE)'; put '@param [in] action Either FETCH, OPEN, ARR, OBJ or CLOSE'; put '@param [in] ds The dataset to send back to the frontend'; put '@param [out] dslabel= Value to use instead of table name for sending to JSON'; put '@param [in] fmt= (N) Setting Y converts all vars to their formatted values'; put '@param [out] fref= (_webout) The fileref to which to write the JSON'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] workobs= (0) When set to a positive integer, will create a new'; put 'output object (WORK) which contains this number of observations from all'; put 'tables in the WORK library.'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to output JSON'; put '

SAS Macros

'; put '@li mp_jsonout.sas'; put '

Related Macros

'; put '@li ms_webout.sas'; put '@li mv_webout.sas'; put '@version 9.3'; put '@author Allan Bowe'; put '**/'; put '%macro mm_webout(action,ds,dslabel=,fref=_webout,fmt=N,missing=NULL'; put ',showmeta=N,maxobs=MAX,workobs=0'; put ');'; put '%global _webin_file_count _webin_fileref1 _webin_name1 _program _debug'; put 'sasjs_tables;'; put '%local i tempds jsonengine;'; put '/* see https://github.com/sasjs/core/issues/41 */'; put '%if "%upcase(&SYSENCODING)" ne "UTF-8" %then %let jsonengine=PROCJSON;'; put '%else %let jsonengine=DATASTEP;'; put '%if &action=FETCH %then %do;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'options mprint notes mprintnest;'; put '%end;'; put '%let _webin_file_count=%eval(&_webin_file_count+0);'; put '/* now read in the data */'; put '%do i=1 %to &_webin_file_count;'; put '%if &_webin_file_count=1 %then %do;'; put '%let _webin_fileref1=&_webin_fileref;'; put '%let _webin_name1=&_webin_name;'; put '%end;'; put 'data _null_;'; put 'infile &&_webin_fileref&i termstr=crlf;'; put 'input;'; put 'call symputx(''input_statement'',_infile_);'; put 'putlog "&&_webin_name&i input statement: " _infile_;'; put 'stop;'; put 'data &&_webin_name&i;'; put 'infile &&_webin_fileref&i firstobs=2 dsd termstr=crlf encoding=''utf-8'';'; put 'input &input_statement;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'if _n_<20 then putlog _infile_;'; put '%end;'; put 'run;'; put '%let sasjs_tables=&sasjs_tables &&_webin_name&i;'; put '%end;'; put '%end;'; put '%else %if &action=OPEN %then %do;'; put '/* fix encoding */'; put 'OPTIONS NOBOMFILE;'; put '/**'; put '* check xengine type to avoid the below err message:'; put '* > Function is only valid for filerefs using the CACHE access method.'; put '*/'; put 'data _null_;'; put 'set sashelp.vextfl(where=(fileref="_WEBOUT"));'; put 'if xengine=''STREAM'' then do;'; put 'rc=stpsrv_header(''Content-type'',"text/html; encoding=utf-8");'; put 'end;'; put 'run;'; put '/* setup json */'; put 'data _null_;file &fref encoding=''utf-8'';'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutBEGIN<<'';'; put '%end;'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'run;'; put '%end;'; put '%else %if &action=ARR or &action=OBJ %then %do;'; put '%mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt,jref=&fref'; put ',engine=&jsonengine,missing=&missing,showmeta=&showmeta,maxobs=&maxobs'; put ')'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put '/* To avoid issues with _webout on EBI we use a temporary file */'; put 'filename _sjsref temp lrecl=131068;'; put '%if %str(&workobs) > 0 %then %do;'; put '/* if debug mode, send back first XX records of each work table also */'; put 'data;run;%let tempds=%scan(&syslast,2,.);'; put 'ods output Members=&tempds;'; put 'proc datasets library=WORK memtype=data;'; put '%local wtcnt;%let wtcnt=0;'; put 'data _null_;'; put 'set &tempds;'; put 'if not (upcase(name) =:"DATA"); /* ignore temp datasets */'; put 'i+1;'; put 'call symputx(cats(''wt'',i),name,''l'');'; put 'call symputx(''wtcnt'',i,''l'');'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put ",""WORK"":{";'; put '%do i=1 %to &wtcnt;'; put '%let wt=&&wt&i;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'dsid=open("WORK.&wt",''is'');'; put 'nlobs=attrn(dsid,''NLOBS'');'; put 'nvars=attrn(dsid,''NVARS'');'; put 'rc=close(dsid);'; put 'if &i>1 then put '',''@;'; put 'put " ""&wt"" : {";'; put 'put ''"nlobs":'' nlobs;'; put 'put '',"nvars":'' nvars;'; put '%mp_jsonout(OBJ,&wt,jref=_sjsref,dslabel=first10rows,showmeta=Y'; put ',maxobs=&workobs'; put ')'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put '%end;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put 'run;'; put '%end;'; put '/* close off json */'; put 'data _null_;file _sjsref mod encoding=''utf-8'';'; put 'length SYSPROCESSNAME syserrortext syswarningtext autoexec $512;'; put 'put ",""_DEBUG"" : ""&_debug"" ";'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'autoexec=quote(urlencode(trim(getoption(''autoexec''))));'; put 'put '',"AUTOEXEC" : '' autoexec;'; put 'put ",""MF_GETUSER"" : ""%mf_getuser()"" ";'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'put ",""SYSENCODING"" : ""&sysencoding"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSPROCESSID"" : ""&SYSPROCESSID"" ";'; put 'put ",""SYSPROCESSMODE"" : ""&SYSPROCESSMODE"" ";'; put 'SYSPROCESSNAME=quote(urlencode(cats(SYSPROCESSNAME)));'; put 'put ",""SYSPROCESSNAME"" : " SYSPROCESSNAME;'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put '',"SYSWARNINGTEXT" : '' syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'length memsize $32;'; put 'memsize="%sysfunc(INPUTN(%sysfunc(getoption(memsize)), best.),sizekmg.)";'; put 'memsize=quote(cats(memsize));'; put 'put '',"MEMSIZE" : '' memsize;'; put 'put "}" @;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutEND<<'';'; put '%end;'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjsref lrecl=1 recfm=n;'; put 'file &fref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjsref clear;'; put '%end;'; put '%mend mm_webout;'; put '%macro webout(action,ds,dslabel=,fmt=,missing=NULL,showmeta=NO,maxobs=MAX);'; put '%mm_webout(&action,ds=&ds,dslabel=&dslabel,fmt=&fmt'; put ',missing=&missing'; put ',showmeta=&showmeta'; put ',maxobs=&maxobs'; put ') %mend;'; put '/* provide additional debug info */'; put '%global _program;'; put '%put &=syscc;'; put '%put user=%mf_getuser();'; put '%put pgm=&_program;'; put '%put timestamp=%sysfunc(datetime(),datetime19.);'; put '* Service Variables start;'; put '* Service Variables end;'; put '* SAS Macros start;'; put '%macro mf_getapploc(pgm);'; put '%if "&pgm"="" %then %do;'; put '%if %symexist(_program) %then %let pgm=&_program;'; put '%else %do;'; put '%put &sysmacroname: No value provided and no _program variable available;'; put '%return;'; put '%end;'; put '%end;'; put '%local root;'; put '/**'; put '* First check we are not in the tests/macros folder (which has no subfolders)'; put '* or specifically in the testsetup or testteardown services'; put '*/'; put '%if %index(&pgm,/tests/macros/)'; put 'or %index(&pgm,/tests/testsetup)'; put 'or %index(&pgm,/tests/testteardown)'; put '%then %do;'; put '%let root=%substr(&pgm,1,%index(&pgm,/tests)-1);'; put '&root'; put '%return;'; put '%end;'; put '/**'; put '* Next, move up two levels to avoid matches on subfolder or service name'; put '*/'; put '%let root=%substr(&pgm,1,%length(&pgm)-%length(%scan(&pgm,-1,/))-1);'; put '%let root=%substr(&root,1,%length(&root)-%length(%scan(&root,-1,/))-1);'; put '%if %index(&root,/tests/) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/tests/)-1);'; put '%end;'; put '%else %if %index(&root,/services) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/services)-1);'; put '%end;'; put '%else %if %index(&root,/jobs) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/jobs)-1);'; put '%end;'; put '%else %put &sysmacroname: Could not find an app location from &pgm;'; put '&root'; put '%mend mf_getapploc ;'; put '%macro mf_getuniquefileref(prefix=_,maxtries=1000,lrecl=32767);'; put '%local rc fname;'; put '%if &prefix=0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%end;'; put '%else %do;'; put '%local x len;'; put '%let len=%eval(8-%length(&prefix));'; put '%let x=0;'; put '%do x=0 %to &maxtries;'; put '%let fname=&prefix%substr(%sysfunc(ranuni(0)),3,&len);'; put '%if %sysfunc(fileref(&fname)) > 0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%return;'; put '%end;'; put '%end;'; put '%put unable to find available fileref after &maxtries attempts;'; put '%end;'; put '%mend mf_getuniquefileref;'; put '%macro mp_abort(mac=mp_abort.sas, type=, msg=, iftrue=%str(1=1)'; put ', errds=work.mp_abort_errds'; put ', mode=REGULAR'; put ')/*/STORE SOURCE*/;'; put '%global sysprocessmode sysprocessname sasjs_stpsrv_header_loc sasjsprocessmode;'; put '%local fref fid i;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%put NOTE: /// mp_abort macro executing //;'; put '%if %length(&mac)>0 %then %put NOTE- called by &mac;'; put '%put NOTE - &msg;'; put '%if %symexist(_SYSINCLUDEFILEDEVICE)'; put '/* abort cancel FILE does not restart outside the INCLUDE on Viya 3.5 */'; put 'and %superq(SYSPROCESSNAME) ne %str(Compute Server)'; put '%then %do;'; put '%if "*&_SYSINCLUDEFILEDEVICE*" ne "**" %then %do;'; put 'data &errds;'; put 'iftrue=''1=1'';'; put 'length mac $100 msg $5000;'; put 'mac=symget(''mac'');'; put 'msg=symget(''msg'');'; put 'run;'; put 'data _null_;'; put 'abort cancel FILE;'; put 'run;'; put '%return;'; put '%end;'; put '%end;'; put '/* Web App Context */'; put '%if %symexist(_PROGRAM)'; put 'or %superq(SYSPROCESSNAME) = %str(Compute Server)'; put 'or &mode=INCLUDE'; put '%then %do;'; put 'options obs=max replace mprint;'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5"'; put '%then %do;'; put 'options nosyntaxcheck;'; put '%end;'; put '%if &mode=INCLUDE %then %do;'; put '%if %sysfunc(exist(&errds))=1 %then %do;'; put 'data _null_;'; put 'set &errds;'; put 'call symputx(''iftrue'',iftrue,''l'');'; put 'call symputx(''mac'',mac,''l'');'; put 'call symputx(''msg'',msg,''l'');'; put 'putlog (_all_)(=);'; put 'run;'; put '%if (&iftrue)=0 %then %return;'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: No include errors found;'; put '%return;'; put '%end;'; put '%end;'; put '/* extract log errs / warns, if exist */'; put '%local logloc logline;'; put '%global logmsg; /* capture global messages */'; put '%if %symexist(SYSPRINTTOLOG) %then %let logloc=&SYSPRINTTOLOG;'; put '%else %let logloc=%qsysfunc(getoption(LOG));'; put 'proc printto log=log;run;'; put '%let logline=0;'; put '%if %length(&logloc)>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input; putlog _infile_;'; put 'i=1;'; put 'retain logonce 0;'; put 'if ('; put '_infile_=:"%str(WARN)ING" or _infile_=:"%str(ERR)OR"'; put ') and logonce=0 then'; put 'do;'; put 'call symputx(''logline'',_n_);'; put 'logonce+1;'; put 'end;'; put 'run;'; put '/* capture log including lines BEFORE the err */'; put '%if &logline>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input;'; put 'i=1;'; put 'stoploop=0;'; put 'if _n_ ge &logline-15 and stoploop=0 then do until (i>22);'; put 'call symputx(''logmsg'',catx(''\n'',symget(''logmsg''),_infile_));'; put 'input;'; put 'i+1;'; put 'stoploop=1;'; put 'end;'; put 'if stoploop=1 then stop;'; put 'run;'; put '%end;'; put '%end;'; put '%if %symexist(SYS_JES_JOB_URI) %then %do;'; put '/* setup webout for Viya */'; put 'options nobomfile;'; put '%if "X&SYS_JES_JOB_URI.X"="XX" %then %do;'; put 'filename _webout temp lrecl=999999 mod;'; put '%end;'; put '%else %do;'; put 'filename _webout filesrvc parenturi="&SYS_JES_JOB_URI"'; put 'name="_webout.json" lrecl=999999 mod;'; put '%end;'; put '%end;'; put '%else %if %sysfunc(filename(fref,&sasjs_stpsrv_header_loc))=0 %then %do;'; put 'options nobomfile;'; put '/* set up http header for SASjs Server */'; put '%let fid=%sysfunc(fopen(&fref,A));'; put '%if &fid=0 %then %do;'; put '%put %str(ERR)OR: %sysfunc(sysmsg());'; put '%return;'; put '%end;'; put '%let rc=%sysfunc(fput(&fid,%str(Content-Type: application/json)));'; put '%let rc=%sysfunc(fwrite(&fid));'; put '%let rc=%sysfunc(fclose(&fid));'; put '%let rc=%sysfunc(filename(&fref));'; put '%end;'; put '/* send response in SASjs JSON format */'; put 'data _null_;'; put 'file _webout mod lrecl=32000 encoding=''utf-8'';'; put 'length msg syswarningtext syserrortext $32767 mode $10 ;'; put 'sasdatetime=datetime();'; put 'msg=symget(''msg'');'; put '%if &logline>0 %then %do;'; put 'msg=cats(msg,''\n\nLog Extract:\n'',symget(''logmsg''));'; put '%end;'; put '/* escape the escapes */'; put 'msg=tranwrd(msg,''\'',''\\'');'; put '/* escape the quotes */'; put 'msg=tranwrd(msg,''"'',''\"'');'; put '/* ditch the CRLFs as chrome complains */'; put 'msg=compress(msg,,''kw'');'; put '/* quote without quoting the quotes (which are escaped instead) */'; put 'msg=cats(''"'',msg,''"'');'; put 'if symexist(''_debug'') then debug=quote(trim(symget(''_debug'')));'; put 'else debug=''""'';'; put 'if symget(''sasjsprocessmode'')=''Stored Program'' then mode=''SASJS'';'; put 'if mode ne ''SASJS'' then put ''>>weboutBEGIN<<'';'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'put '',"sasjsAbort" : [{'';'; put 'put '' "MSG":'' msg ;'; put 'put '' ,"MAC": "'' "&mac" ''"}]'';'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'put '',"_DEBUG":'' debug ;'; put 'if symexist(''_metauser'') then do;'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put 'end;'; put 'if symexist(''SYS_JES_JOB_URI'') then do;'; put 'SYS_JES_JOB_URI=quote(trim(symget(''SYS_JES_JOB_URI'')));'; put 'put '',"SYS_JES_JOB_URI": '' SYS_JES_JOB_URI;'; put 'end;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put ",""SYSWARNINGTEXT"" : " syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'put "}" ;'; put 'if mode ne ''SASJS'' then put ''>>weboutEND<<'';'; put 'run;'; put '%put _all_;'; put '%if "&sysprocessmode " = "SAS Stored Process Server " %then %do;'; put 'data _null_;'; put 'putlog ''stpsrvset program err and syscc'';'; put 'rc=stpsrvset(''program error'', 0);'; put 'call symputx("syscc",0,"g");'; put 'run;'; put '%if &sysscp=WIN'; put 'and 1=0 /* deprecating this logic until we figure out a consistent abort */'; put 'and "%substr(%str(&sysvlong ),1,8)"="9.04.01M"'; put 'and "%substr(%str(&sysvlong ),9,1)">"5" %then %do;'; put '/* skip approach (below) does not work in windows m6+ envs */'; put 'endsas;'; put '%end;'; put '%else %do;'; put '/**'; put '* endsas kills 9.4m3 deployments by orphaning multibridges.'; put '* Abort variants are ungraceful (non zero return code)'; put '* This approach lets SAS run silently until the end :-)'; put '* Caution - fails when called within a %include within a macro'; put '* Use mp_include() to handle this.'; put '*/'; put 'filename skip temp;'; put 'data _null_;'; put 'file skip;'; put 'put ''%macro skip();'';'; put 'comment ''%mend skip; -> fix lint '';'; put 'put ''%macro skippy();'';'; put 'comment ''%mend skippy; -> fix lint '';'; put 'run;'; put '%inc skip;'; put '%end;'; put '%end;'; put '%else %if "&sysprocessmode " = "SAS Compute Server " %then %do;'; put '/* endsas kills the session making it harder to fetch results */'; put 'data _null_;'; put 'syswarningtext=symget(''syswarningtext'');'; put 'syserrortext=symget(''syserrortext'');'; put 'abort_msg=symget(''msg'');'; put 'syscc=symget(''syscc'');'; put 'sysuserid=symget(''sysuserid'');'; put 'iftrue=symget(''iftrue'');'; put 'put (_all_)(/=);'; put 'call symputx(''syscc'',0);'; put 'abort cancel nolist;'; put 'run;'; put '%end;'; put '%else %do;'; put '%abort cancel;'; put '%end;'; put '%end;'; put '%else %do;'; put '%put _all_;'; put '%abort cancel;'; put '%end;'; put '%mend mp_abort;'; put '/** @endcond */'; put '%macro mm_getstpcode('; put 'tree=/User Folders/sasdemo/somestp'; put ',name='; put ',outloc=0'; put ',outref=0'; put ',mDebug=1'; put ',showlog=NO'; put ');'; put '%local mD;'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing &sysmacroname..sas;'; put '%&mD.put _local_;'; put '%if %length(&name)>0 %then %let name=/&name;'; put '/* first, check if STP exists */'; put '%local tsuri;'; put '%let tsuri=stopifempty ;'; put 'data _null_;'; put 'format type uri tsuri value $200.;'; put 'call missing (of _all_);'; put 'path="&tree&name(StoredProcess)";'; put '/* first, find the STP ID */'; put 'if metadata_pathobj("",path,"StoredProcess",type,uri)>0 then do;'; put '/* get sourcecode */'; put 'cnt=1;'; put 'do while (metadata_getnasn(uri,"Notes",cnt,tsuri)>0);'; put 'rc=metadata_getattr(tsuri,"Name",value);'; put '&mD.put tsuri= value=;'; put 'if value="SourceCode" then do;'; put '/* found it! */'; put 'rc=metadata_getattr(tsuri,"Id",value);'; put 'call symputx(''tsuri'',value,''l'');'; put 'stop;'; put 'end;'; put 'cnt+1;'; put 'end;'; put 'end;'; put 'else put (_all_)(=);'; put 'run;'; put '%mp_abort(iftrue= (&tsuri=stopifempty)'; put ',mac=mm_getstpcode'; put ',msg=%str(&tree&name.(StoredProcess) not found!)'; put ')'; put '/**'; put '* Now we can extract the textstore'; put '*/'; put 'filename __getdoc temp lrecl=10000000;'; put 'proc metadata'; put 'in="$METAREPOSITORY'; put ''; put 'SAS1"'; put 'out=__getdoc ;'; put 'run;'; put '/* find the beginning of the text */'; put '%local start;'; put 'data _null_;'; put 'infile __getdoc lrecl=10000;'; put 'input;'; put 'start=index(_infile_,''StoredText="'');'; put 'if start then do;'; put 'call symputx("start",start+11);'; put '*putlog ''"'' _infile_ ''"'';'; put 'end;'; put 'stop;'; put '%local outeng;'; put '%if "&outloc"="0" %then %let outeng=TEMP;'; put '%else %let outeng="&outloc";'; put '%local fref;'; put '%if &outref=0 %then %let fref=%mf_getuniquefileref();'; put '%else %let fref=&outref;'; put '/* read the content, byte by byte, resolving escaped chars */'; put 'filename &fref &outeng lrecl=100000;'; put 'data _null_;'; put 'length filein 8 fileid 8;'; put 'filein = fopen("__getdoc","I",1,"B");'; put 'fileid = fopen("&fref","O",1,"B");'; put 'rec = "20"x;'; put 'length entity $6;'; put 'do while(fread(filein)=0);'; put 'x+1;'; put 'if x>&start then do;'; put 'rc = fget(filein,rec,1);'; put 'if rec=''"'' then leave;'; put 'else if rec="&" then do;'; put 'entity=rec;'; put 'do until (rec=";");'; put 'if fread(filein) ne 0 then goto getout;'; put 'rc = fget(filein,rec,1);'; put 'entity=cats(entity,rec);'; put 'end;'; put 'select (entity);'; put 'when (''&'' ) rec=''&'' ;'; put 'when (''<'' ) rec=''<'' ;'; put 'when (''>'' ) rec=''>'' ;'; put 'when (''''') rec="''" ;'; put 'when (''"'') rec=''"'' ;'; put 'when ('' '') rec=''0A''x;'; put 'when ('' '') rec=''0D''x;'; put 'when (''$'' ) rec=''$'' ;'; put 'when ('' '') rec=''09''x;'; put 'otherwise putlog "%str(WARN)ING: missing value for " entity=;'; put 'end;'; put 'rc =fput(fileid, substr(rec,1,1));'; put 'rc =fwrite(fileid);'; put 'end;'; put 'else do;'; put 'rc =fput(fileid,rec);'; put 'rc =fwrite(fileid);'; put 'end;'; put 'end;'; put 'end;'; put 'getout:'; put 'rc=fclose(filein);'; put 'rc=fclose(fileid);'; put 'run;'; put '%if &showlog=YES %then %do;'; put 'data _null_;'; put 'infile &fref lrecl=32767 end=last;'; put 'input;'; put 'if _n_=1 then putlog ''>>stpcodeBEGIN<<'';'; put 'putlog _infile_;'; put 'if last then putlog ''>>stpcodeEND<<'';'; put 'run;'; put '%end;'; put 'filename __getdoc clear;'; put '%if &outref=0 %then %do;'; put 'filename &fref clear;'; put '%end;'; put '%mend mm_getstpcode;'; put '%macro dc_getsettings();'; put '%global _program;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(syscc=&syscc on entry (&syswarningtext &syserrortext))'; put ')'; put '%if %length(&_PROGRAM)>1 %then %let root=&_program;'; put '%else %do;'; put '%global _metauser;'; put '%let _metauser=&sysuserid;'; put '/* to mimic a "real" _program we need to give a dummy role and stp name */'; put '%let root=/dummyRole/dummyName;'; put '%end;'; put '/* the DC precode is stored in the Admin folder in the root of'; put 'the project. Lets find that root. */'; put '%put &=root;'; put '%let root=%mf_getapploc();'; put '%put &=root;'; put '/* Now we know the root location we can retrieve the params */'; put '%let temploc=%sysfunc(pathname(work))/settings.txt;'; put '%mm_getstpcode(tree=&root/services/public'; put ',name=Data_Controller_Settings'; put ',outloc=&temploc'; put ')'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Unable to run getstpcode)'; put ')'; put 'filename _getsets "&temploc" lrecl=2000;'; put '/*'; put 'Do not use mp_include here - this puts a copy in every service, which creates'; put 'compilation problems when calling services from mp_include'; put '*/'; put '%inc _getsets/source2;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Problem running &sysmacroname (&syswarningtext &syserrortext))'; put ')'; put '%mend dc_getsettings;'; put '%macro mf_fmtdttm('; put ')/*/STORE SOURCE*/;'; put '%if "&sysver"="9.2" or "&sysver"="9.3"'; put 'or ("&sysver"="9.4" and "%substr(&SYSVLONG,9,1)" le "3")'; put 'or "%substr(&sysver,1,1)"="4"'; put 'or "%substr(&sysver,1,1)"="5"'; put '%then %do;DATETIME19.3%end;'; put '%else %do;E8601DT26.6%end;'; put '%mend mf_fmtdttm;'; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '%macro mp_init(prefix=SASJS'; put ')/*/STORE SOURCE*/;'; put '%if %symexist(SASJS_PREFIX) %then %return; /* only run once */'; put '%global'; put 'SASJS_PREFIX /* the ONLY hard-coded global macro variable in SASjs */'; put '&prefix._FUNCTIONS /* used in mcf_init() to track core function compilation */'; put '&prefix._INIT_NUM /* initialisation time as numeric */'; put '&prefix._INIT_DTTM /* initialisation time in E8601DT26.6 format */'; put '&prefix.WORK /* avoid typing %sysfunc(pathname(work)) every time */'; put ';'; put '%let sasjs_prefix=&prefix;'; put 'data _null_;'; put 'dttm=datetime();'; put 'call symputx("&sasjs_prefix._init_num",dttm,''g'');'; put 'call symputx("&sasjs_prefix._init_dttm",put(dttm,E8601DT26.6),''g'');'; put 'call symputx("&sasjs_prefix.work",pathname(''WORK''),''g'');'; put 'run;'; put 'options'; put 'compress=CHAR /* default is none so ensure we have something! */'; put 'datastmtchk=ALLKEYWORDS /* protection from overwriting input datasets */'; put 'errorcheck=STRICT /* catch errs in libname/filename statements */'; put 'fmterr /* ensure err when a format cannot be found */'; put 'mergenoby=%str(ERR)OR /* throw err when a merge has no BY variables */'; put 'missing=. /* changing this can cause hard to detect errs */'; put 'noquotelenmax /* avoid warnings for long strings */'; put 'noreplace /* avoid overwriting permanent datasets */'; put 'ps=max /* reduce log size slightly */'; put 'ls=max /* reduce log even more and avoid word truncation */'; put 'validmemname=COMPATIBLE /* avoid special characters etc in table names */'; put 'validvarname=V7 /* avoid special characters etc in variable names */'; put 'varinitchk=%str(ERR)OR /* avoid data mistakes from variable name typos */'; put 'varlenchk=%str(ERR)OR /* fail hard if truncation (data loss) can result */'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5" %then %do;'; put 'noautocorrect /* disallow misspelled procedure names */'; put 'dsoptions=note2err /* undocumented - convert bad NOTEs to ERRs */'; put '%end;'; put ';'; put '%mend mp_init;'; put '%macro mpeinit(fetch=YES);'; put '%global mpeinit'; put 'mpeadmins /* group with unrestricted Meditor access */'; put 'mpelocapprovals /* location for landing and staging files */'; put 'mpelib /* location of configuration tables for DC */'; put 'dc_repo_users /* location of user / group metadata */'; put 'dc_licence_key /* extracted in dc_getsettings */'; put 'dc_activation_key /* extracted in dc_getsettings */'; put 'dc_locale /* extracted in dc_getsettings */'; put 'dc_dttmtfmt /* can be overridden in dc_getsettings */'; put '_debug'; put ';'; put '%if &mpeinit=1 %then %return;'; put '%else %let mpeinit=1;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(Problem on service startup (&syswarningtext &syserrortext))'; put ')'; put '%mp_init()'; put '%if &fetch=YES %then %do;'; put '%webout(FETCH)'; put '%end;'; put '%global _CLIENTNAME;'; put '%mp_abort(iftrue= (&_CLIENTNAME=SAS Enterprise Guide)'; put ',mac=&_program..sas'; put ',msg=%str(Data Controller is a web app and should not be executed from EG)'; put ')'; put 'options urlencoding=utf8 nobomfile lrecl=32767;'; put '%let perf=%sysfunc(datetime());'; put '%put perfdiff: 0;'; put '%let dc_locale=SYSTEM; /* default if not set */'; put '/**'; put '* E8601DT26.6 has widest database support - but not all SAS flavours can'; put '* handle it. Override in the settings STP if needed.'; put '*/'; put 'data _null_;'; put 'dc_dttmtfmt=''"%sysfunc(datetime(),''!!"%mf_fmtdttm()"!!'')"dt'';'; put 'call symputx(''dc_dttmtfmt'',dc_dttmtfmt);'; put 'put dc_dttmtfmt=;'; put 'run;'; put '%put &=dc_dttmtfmt;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc prior to dc_getsettings)'; put ')'; put '%dc_getsettings()'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc after dc_getsettings)'; put ')'; put 'data _null_;'; put 'set &DC_LIBREF..mpe_config(where=('; put 'var_scope="DC"'; put 'and &dc_dttmtfmt lt tx_to'; put 'and var_active=1'; put '));'; put 'call symputx(var_name,var_value,''G'');'; put 'putlog var_name "=" var_value;'; put 'run;'; put '%let mpelib=&dc_libref;'; put '%let mpeadmins=&dc_admin_group;'; put '%let mpelocapprovals=&dc_staging_area;'; put '%let dc_repo_users=&dc_repo_users;'; put '%if &dc_locale ne SYSTEM %then %do;'; put 'options locale=&dc_locale;'; put '%end;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(Problem during compilation or with STP precode (&syswarningtext))'; put ')'; put '%mend mpeinit;'; put '%macro mf_mval(var);'; put '%if %symexist(&var) %then %do;'; put '%superq(&var)'; put '%end;'; put '%mend mf_mval;'; put '%macro mf_trimstr(basestr,trimstr);'; put '%local baselen trimlen trimval;'; put '/* return if basestr is shorter than trimstr (or 0) */'; put '%let baselen=%length(%superq(basestr));'; put '%let trimlen=%length(%superq(trimstr));'; put '%if &baselen < &trimlen or &baselen=0 %then %return;'; put '/* obtain the characters from the end of basestr */'; put '%let trimval=%qsubstr(%superq(basestr)'; put ',%length(%superq(basestr))-&trimlen+1'; put ',&trimlen);'; put '/* compare and if matching, chop it off! */'; put '%if %superq(basestr)=%superq(trimstr) %then %do;'; put '%return;'; put '%end;'; put '%else %if %superq(trimval)=%superq(trimstr) %then %do;'; put '%qsubstr(%superq(basestr),1,%length(%superq(basestr))-&trimlen)'; put '%end;'; put '%else %do;'; put '&basestr'; put '%end;'; put '%mend mf_trimstr;'; put '%macro mf_getplatform(switch'; put ')/*/STORE SOURCE*/;'; put '%local a b c;'; put '%if &switch.NONE=NONE %then %do;'; put '%if %symexist(sasjsprocessmode) %then %do;'; put '%if &sasjsprocessmode=Stored Program %then %do;'; put 'SASJS'; put '%return;'; put '%end;'; put '%end;'; put '%if %symexist(sysprocessmode) %then %do;'; put '%if "&sysprocessmode"="SAS Object Server"'; put 'or "&sysprocessmode"= "SAS Compute Server" %then %do;'; put 'SASVIYA'; put '%end;'; put '%else %if "&sysprocessmode"="SAS Stored Process Server"'; put 'or "&sysprocessmode"="SAS Workspace Server"'; put '%then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if %symexist(_metaport) or %symexist(_metauser) %then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if &switch=SASSTUDIO %then %do;'; put '/* return the version of SAS Studio else 0 */'; put '%if %mf_mval(_CLIENTAPP)=%str(SAS Studio) %then %do;'; put '%let a=%mf_mval(_CLIENTVERSION);'; put '%let b=%scan(&a,1,.);'; put '%if %eval(&b >2) %then %do;'; put '&b'; put '%end;'; put '%else 0;'; put '%end;'; put '%else 0;'; put '%end;'; put '%else %if &switch=VIYARESTAPI %then %do;'; put '%mf_trimstr(%sysfunc(getoption(servicesbaseurl)),/)'; put '%end;'; put '%mend mf_getplatform;'; put '%macro mpeterm();'; put '%local oldloc;'; put 'data _null_;'; put 'if symexist(''SYSPRINTTOLOG'') then oldloc=symget(''SYSPRINTTOLOG'');'; put 'else oldloc=getoption(''LOG'');'; put 'if subpad(oldloc,1,1) not in (''"'',"''",'' '') then oldloc=quote(cats(oldloc));'; put 'call symputx(''oldloc'',oldloc,''l'');'; put 'run;'; put '%if %length(&oldloc)>0 %then %do;'; put 'proc printto log=log;'; put 'run;'; put 'data _null_;'; put 'infile &oldloc;'; put 'input; putlog _infile_;'; put 'run;'; put '%end;'; put '%if %sysfunc(exist(&dc_libref..mpe_requests)) and %mf_getplatform() ne SASVIYA'; put '%then %do;'; put 'data ;'; put 'if 0 then set &dc_libref..mpe_requests;'; put 'request_dttm=%sysfunc(datetime());'; put 'request_user="%mf_getuser()";'; put 'request_service="%scan(&_program,-2,/)/%scan(&_program,-1,/)";'; put 'request_params='''';'; put 'output;stop;'; put 'proc append base=&dc_libref..mpe_requests data=&syslast force nowarn;'; put 'run;'; put '%end;'; put '%mend mpeterm;'; put '%macro mm_assignlib('; put 'libref'; put ',mAbort=HARD'; put ')/*/STORE SOURCE*/;'; put '%local mp_abort msg;'; put '%let mp_abort=0;'; put '%if %sysfunc(libref(&libref)) %then %do;'; put 'data _null_;'; put 'length liburi LibName msg $200;'; put 'call missing(of _all_);'; put 'nobj=metadata_getnobj("omsobj:SASLibrary?@Libref=''&libref''",1,liburi);'; put 'if nobj=1 then do;'; put 'rc=metadata_getattr(liburi,"Name",LibName);'; put '/* now try and assign it */'; put 'if libname("&libref",,''meta'',cats(''liburi="'',liburi,''";'')) ne 0 then do;'; put 'putlog "&libref could not be assigned";'; put 'putlog liburi=;'; put '/**'; put '* Fetch the system message for display in the abort modal. This is'; put '* not always helpful though. One example, previously received:'; put '* NOTE: Libref XX refers to the same library metadata as libref XX.'; put '*/'; put 'msg=sysmsg();'; put 'if msg=:''ERROR: Libref SAVE is not assigned.'' then do;'; put 'msg=catx(" ",'; put '"Could not assign %upcase(&libref).",'; put '"Please check metadata permissions! Libname:",libname,'; put '"Liburi:",liburi'; put ');'; put 'end;'; put 'else if msg="ERROR: User does not have appropriate authorization "!!'; put '"level for library SAVE."'; put 'then do;'; put 'msg=catx(" ",'; put '"ERROR: User does not have appropriate authorization level",'; put '"for library %upcase(&libref), libname:",libname,'; put '"Liburi:",liburi'; put ');'; put 'end;'; put 'call symputx(''msg'',msg,''l'');'; put 'if "&mabort"=''HARD'' then call symputx(''mp_abort'',1,''l'');'; put 'end;'; put 'else do;'; put 'put (_all_)(=);'; put 'call symputx(''libname'',libname,''L'');'; put 'call symputx(''liburi'',liburi,''L'');'; put 'end;'; put 'end;'; put 'else if nobj>1 then do;'; put 'if "&mabort"=''HARD'' then call symputx(''mp_abort'',1);'; put 'call symputx(''msg'',"More than one library with libref=&libref");'; put 'end;'; put 'else do;'; put 'if "&mabort"=''HARD'' then call symputx(''mp_abort'',1);'; put 'call symputx(''msg'',"Library &libref not found in metadata");'; put 'end;'; put 'run;'; put '%put NOTE: &msg;'; put '%end;'; put '%else %do;'; put '%put NOTE: Library &libref is already assigned;'; put '%end;'; put '%mp_abort(iftrue= (&mp_abort=1)'; put ',mac=mm_assignlib.sas'; put ',msg=%superq(msg)'; put ')'; put '%mend mm_assignlib;'; put '/** @cond */'; put '%macro mf_getengine(libref'; put ')/*/STORE SOURCE*/;'; put '%local dsid engnum rc engine;'; put '/* in case the parameter is a libref.tablename, pull off just the libref */'; put '%let libref = %upcase(%scan(&libref, 1, %str(.)));'; put '%let dsid=%sysfunc('; put 'open(sashelp.vlibnam(where=(libname="%upcase(&libref)")),i)'; put ');'; put '%if (&dsid ^= 0) %then %do;'; put '%let engnum=%sysfunc(varnum(&dsid,ENGINE));'; put '%let rc=%sysfunc(fetch(&dsid));'; put '%let engine=%sysfunc(getvarc(&dsid,&engnum));'; put '%put &libref. ENGINE is &engine.;'; put '%let rc= %sysfunc(close(&dsid));'; put '%end;'; put '%upcase(&engine)'; put '%mend mf_getengine;'; put '/** @endcond */'; put '%macro mm_assigndirectlib('; put 'libref'; put ',open_passthrough='; put ',sql_options='; put ',mDebug=0'; put ',mAbort=0'; put ')/*/STORE SOURCE*/;'; put '%local mD;'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing mm_assigndirectlib.sas;'; put '%&mD.put _local_;'; put '%if &mAbort=1 %then %let mAbort=;'; put '%else %let mAbort=%str(*);'; put '%&mD.put NOTE: Creating direct (non META) connection to &libref library;'; put '%local cur_engine;'; put '%let cur_engine=%mf_getengine(&libref);'; put '%if &cur_engine ne META and &cur_engine ne %then %do;'; put '%put NOTE: &libref already has a direct (&cur_engine) libname connection;'; put '%return;'; put '%end;'; put '%else %if %upcase(&libref)=WORK %then %do;'; put '%put NOTE: We already have a direct connection to WORK :-) ;'; put '%return;'; put '%end;'; put '/* need to determine the library ENGINE first */'; put '%local engine;'; put 'data _null_;'; put 'length lib_uri engine $256;'; put 'call missing (of _all_);'; put '/* get URI for the particular library */'; put 'rc1=metadata_getnobj("omsobj:SASLibrary?@Libref =''&libref''",1,lib_uri);'; put '/* get the Engine attribute of the previous object */'; put 'rc2=metadata_getattr(lib_uri,''Engine'',engine);'; put 'putlog "mm_assigndirectlib for &libref:" rc1= lib_uri= rc2= engine=;'; put 'call symputx("liburi",lib_uri,''l'');'; put 'call symputx("engine",engine,''l'');'; put 'run;'; put '/* now obtain engine specific connection details */'; put '%if &engine=BASE %then %do;'; put '%&mD.put NOTE: Retrieving BASE library path;'; put 'data _null_;'; put 'length up_uri $256 path cat_path $1024;'; put 'retain cat_path;'; put 'call missing (of _all_);'; put '/* get all the filepaths of the UsingPackages association */'; put 'i=1;'; put 'rc3=metadata_getnasn("&liburi",''UsingPackages'',i,up_uri);'; put 'do while (rc3>0);'; put '/* get the DirectoryName attribute of the previous object */'; put 'rc4=metadata_getattr(up_uri,''DirectoryName'',path);'; put 'if i=1 then path = ''("''!!trim(path)!!''" '';'; put 'else path ='' "''!!trim(path)!!''" '';'; put 'cat_path = trim(cat_path) !! " " !! trim(path) ;'; put 'i+1;'; put 'rc3=metadata_getnasn("&liburi",''UsingPackages'',i,up_uri);'; put 'end;'; put 'cat_path = trim(cat_path) !! ")";'; put '&mD.putlog "NOTE: Getting physical path for &libref library";'; put '&mD.putlog rc3= up_uri= rc4= cat_path= path=;'; put '&mD.putlog "NOTE: Libname cmd will be:";'; put '&mD.putlog "libname &libref" cat_path;'; put 'call symputx("filepath",cat_path,''l'');'; put 'run;'; put '%if %sysevalf(&sysver<9.4) %then %do;'; put 'libname &libref &filepath;'; put '%end;'; put '%else %do;'; put '/* apply the new filelocks option to cater for temporary locks */'; put 'libname &libref &filepath filelockwait=5;'; put '%end;'; put '%end;'; put '%else %if &engine=REMOTE %then %do;'; put 'data x;'; put 'length rcCon rcProp rc k 3 uriCon uriProp PropertyValue PropertyName'; put 'Delimiter $256 properties $2048;'; put 'retain properties;'; put 'rcCon = metadata_getnasn("&liburi", "LibraryConnection", 1, uriCon);'; put 'rcProp = metadata_getnasn(uriCon, "Properties", 1, uriProp);'; put 'k = 1;'; put 'rcProp = metadata_getnasn(uriCon, "Properties", k, uriProp);'; put 'do while (rcProp > 0);'; put 'rc = metadata_getattr(uriProp , "DefaultValue",PropertyValue);'; put 'rc = metadata_getattr(uriProp , "PropertyName",PropertyName);'; put 'rc = metadata_getattr(uriProp , "Delimiter",Delimiter);'; put 'properties = trim(properties) !! " " !! trim(PropertyName)'; put '!! trim(Delimiter) !! trim(PropertyValue);'; put 'output;'; put 'k+1;'; put 'rcProp = metadata_getnasn(uriCon, "Properties", k, uriProp);'; put 'end;'; put '%&mD.put NOTE: Getting properties for REMOTE SHARE &libref library;'; put '&mD.put _all_;'; put '%&mD.put NOTE: Libname cmd will be:;'; put '%&mD.put libname &libref &engine &properties slibref=&libref;'; put 'call symputx ("properties",trim(properties),''l'');'; put 'run;'; put 'libname &libref &engine &properties slibref=&libref;'; put '%end;'; put '%else %if &engine=OLEDB %then %do;'; put '%&mD.put NOTE: Retrieving OLEDB connection details;'; put 'data _null_;'; put 'length domain datasource provider properties schema'; put 'connx_uri domain_uri conprop_uri lib_uri schema_uri value $256.;'; put 'call missing (of _all_);'; put '/* get source connection ID */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,connx_uri);'; put '/* get connection domain */'; put 'rc1=metadata_getnasn(connx_uri,''Domain'',1,domain_uri);'; put 'rc2=metadata_getattr(domain_uri,''Name'',domain);'; put '&mD.putlog / ''NOTE: '' // ''NOTE- connection id: '' connx_uri ;'; put '&mD.putlog ''NOTE- domain: '' domain;'; put '/* get DSN and PROVIDER from connection properties */'; put 'i=0;'; put 'do until (rc<0);'; put 'i+1;'; put 'rc=metadata_getnasn(connx_uri,''Properties'',i,conprop_uri);'; put 'rc2=metadata_getattr(conprop_uri,''Name'',value);'; put 'if value=''Connection.OLE.Property.DATASOURCE.Name.xmlKey.txt'' then do;'; put 'rc3=metadata_getattr(conprop_uri,''DefaultValue'',datasource);'; put 'end;'; put 'else if value=''Connection.OLE.Property.PROVIDER.Name.xmlKey.txt'' then do;'; put 'rc4=metadata_getattr(conprop_uri,''DefaultValue'',provider);'; put 'end;'; put 'else if value=''Connection.OLE.Property.PROPERTIES.Name.xmlKey.txt'' then'; put 'do;'; put 'rc5=metadata_getattr(conprop_uri,''DefaultValue'',properties);'; put 'end;'; put 'end;'; put '&mD.putlog ''NOTE- dsn/provider/properties: '' /'; put 'datasource provider properties;'; put '&mD.putlog ''NOTE- schema: '' schema // ''NOTE-'';'; put '/* get SCHEMA */'; put 'rc6=metadata_getnasn("&liburi",''UsingPackages'',1,lib_uri);'; put 'rc7=metadata_getattr(lib_uri,''SchemaName'',schema);'; put 'call symputx(''SQL_domain'',domain,''l'');'; put 'call symputx(''SQL_dsn'',datasource,''l'');'; put 'call symputx(''SQL_provider'',provider,''l'');'; put 'call symputx(''SQL_properties'',properties,''l'');'; put 'call symputx(''SQL_schema'',schema,''l'');'; put 'run;'; put '%if %length(&open_passthrough)>0 %then %do;'; put 'proc sql &sql_options;'; put 'connect to OLEDB as &open_passthrough(INSERT_SQL=YES'; put '/* need additional properties to make this work */'; put 'properties=(''Integrated Security''=SSPI'; put '''Persist Security Info''=True'; put '%sysfunc(compress(%str(&SQL_properties),%str(())))'; put ')'; put 'DATASOURCE=&sql_dsn PROMPT=NO'; put 'PROVIDER=&sql_provider SCHEMA=&sql_schema CONNECTION = GLOBAL);'; put '%end;'; put '%else %do;'; put 'LIBNAME &libref OLEDB PROPERTIES=&sql_properties'; put 'DATASOURCE=&sql_dsn PROVIDER=&sql_provider SCHEMA=&sql_schema'; put '%if %length(&sql_domain)>0 %then %do;'; put 'authdomain="&sql_domain"'; put '%end;'; put 'connection=shared;'; put '%end;'; put '%end;'; put '%else %if &engine=ODBC %then %do;'; put '%&mD.put NOTE: Retrieving ODBC connection details;'; put 'data _null_;'; put 'length connx_uri conprop_uri value datasource up_uri schema domprop_uri authdomain $256.;'; put 'call missing (of _all_);'; put '/* get source connection ID */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,connx_uri);'; put '/* get connection properties */'; put 'i=0;'; put 'do until (rc2<0);'; put 'i+1;'; put 'rc2=metadata_getnasn(connx_uri,''Properties'',i,conprop_uri);'; put 'rc3=metadata_getattr(conprop_uri,''Name'',value);'; put 'if value=''Connection.ODBC.Property.DATASRC.Name.xmlKey.txt'' then do;'; put 'rc4=metadata_getattr(conprop_uri,''DefaultValue'',datasource);'; put 'rc2=-1;'; put 'end;'; put 'end;'; put '/* get auth domain */'; put 'autrc=metadata_getnasn(connx_uri,"Domain",1,domprop_uri);'; put 'arc=metadata_getattr(domprop_uri,"Name",authdomain);'; put 'if not missing(authdomain) then authdomain=cats(''AUTHDOMAIN='',authdomain);'; put 'call symputx(''authdomain'',authdomain,''l'');'; put '/* get SCHEMA */'; put 'rc6=metadata_getnasn("&liburi",''UsingPackages'',1,up_uri);'; put 'rc7=metadata_getattr(up_uri,''SchemaName'',schema);'; put '&mD.put rc= connx_uri= rc2= conprop_uri= rc3= value= rc4= datasource='; put 'rc6= up_uri= rc7= schema=;'; put 'call symputx(''SQL_schema'',schema,''l'');'; put 'call symputx(''SQL_dsn'',datasource,''l'');'; put 'run;'; put '%if %length(&open_passthrough)>0 %then %do;'; put 'proc sql &sql_options;'; put 'connect to ODBC as &open_passthrough'; put '(INSERT_SQL=YES DATASRC=&sql_dsn. CONNECTION=global);'; put '%end;'; put '%else %do;'; put 'libname &libref ODBC DATASRC=&sql_dsn SCHEMA=&sql_schema &authdomain;'; put '%end;'; put '%end;'; put '%else %if &engine=POSTGRES %then %do;'; put '%put NOTE: Obtaining POSTGRES library details;'; put 'data _null_;'; put 'length database ignore_read_only_columns direct_exe preserve_col_names'; put 'preserve_tab_names server schema authdomain user password'; put 'prop name value uri urisrc $256.;'; put 'call missing (of _all_);'; put '/* get database value */'; put 'prop=''Connection.DBMS.Property.DB.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,database,"");'; put 'if database^='''' then database=''database=''!!quote(trim(database));'; put 'call symputx(''database'',database,''l'');'; put '/* get IGNORE_READ_ONLY_COLUMNS value */'; put 'prop=''Library.DBMS.Property.DBIROC.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,ignore_read_only_columns,"");'; put 'if ignore_read_only_columns^='''' then ignore_read_only_columns='; put '''ignore_read_only_columns=''!!ignore_read_only_columns;'; put 'call symputx(''ignore_read_only_columns'',ignore_read_only_columns,''l'');'; put '/* get DIRECT_EXE value */'; put 'prop=''Library.DBMS.Property.DirectExe.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,direct_exe,"");'; put 'if direct_exe^='''' then direct_exe=''direct_exe=''!!direct_exe;'; put 'call symputx(''direct_exe'',direct_exe,''l'');'; put '/* get PRESERVE_COL_NAMES value */'; put 'prop=''Library.DBMS.Property.PreserveColNames.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,preserve_col_names,"");'; put 'if preserve_col_names^='''' then preserve_col_names='; put '''preserve_col_names=''!!preserve_col_names;'; put 'call symputx(''preserve_col_names'',preserve_col_names,''l'');'; put '/* get PRESERVE_TAB_NAMES value */'; put '/* be careful with PRESERVE_TAB_NAMES=YES - it will mean your table will'; put 'become case sensitive!! */'; put 'prop=''Library.DBMS.Property.PreserveTabNames.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,preserve_tab_names,"");'; put 'if preserve_tab_names^='''' then preserve_tab_names='; put '''preserve_tab_names=''!!preserve_tab_names;'; put 'call symputx(''preserve_tab_names'',preserve_tab_names,''l'');'; put '/* get SERVER value */'; put 'if metadata_getnasn("&liburi","LibraryConnection",1,uri)>0 then do;'; put 'prop=''Connection.DBMS.Property.SERVER.Name.xmlKey.txt'';'; put 'rc=metadata_getprop(uri,prop,server,"");'; put 'end;'; put 'if server^='''' then server=''server=''!!quote(cats(server));'; put 'call symputx(''server'',server,''l'');'; put '/* get SCHEMA value */'; put 'if metadata_getnasn("&liburi","UsingPackages",1,uri)>0 then do;'; put 'rc=metadata_getattr(uri,"SchemaName",schema);'; put 'end;'; put 'if schema^='''' then schema=''schema=''!!schema;'; put 'call symputx(''schema'',schema,''l'');'; put '/* get AUTHDOMAIN value */'; put '/* this is only useful if the user account contains that auth domain'; put 'if metadata_getnasn("&liburi","DefaultLogin",1,uri)>0 then do;'; put 'rc=metadata_getnasn(uri,"Domain",1,urisrc);'; put 'rc=metadata_getattr(urisrc,"Name",authdomain);'; put 'end;'; put 'if authdomain^='''' then authdomain=''authdomain=''!!quote(trim(authdomain));'; put '*/'; put 'call symputx(''authdomain'',authdomain,''l'');'; put '/* get user & pass */'; put 'if authdomain='''' & metadata_getnasn("&liburi","DefaultLogin",1,uri)>0 then'; put 'do;'; put 'rc=metadata_getattr(uri,"UserID",user);'; put 'rc=metadata_getattr(uri,"Password",password);'; put 'end;'; put 'if user^='''' then do;'; put 'user=''user=''!!quote(trim(user));'; put 'password=''password=''!!quote(trim(password));'; put 'end;'; put 'call symputx(''user'',user,''l'');'; put 'call symputx(''password'',password,''l'');'; put '&md.put _all_;'; put 'run;'; put '%if %length(&open_passthrough)>0 %then %do;'; put '%put %str(WARN)ING: Passthrough option for postgres not yet supported;'; put '%return;'; put '%end;'; put '%else %do;'; put '%if &mdebug=1 %then %do;'; put '%put NOTE: Executing the following:/;'; put '%put NOTE- libname &libref POSTGRES &database &ignore_read_only_columns;'; put '%put NOTE- &direct_exe &preserve_col_names &preserve_tab_names;'; put '%put NOTE- &server &schema &authdomain &user &password //;'; put '%end;'; put 'libname &libref POSTGRES &database &ignore_read_only_columns &direct_exe'; put '&preserve_col_names &preserve_tab_names &server &schema &authdomain'; put '&user &password;'; put '%end;'; put '%end;'; put '%else %if &engine=ORACLE %then %do;'; put '%put NOTE: Obtaining &engine library details;'; put 'data _null_;'; put 'length assocuri1 assocuri2 assocuri3 authdomain path schema $256;'; put 'call missing (of _all_);'; put '/* get auth domain */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,assocuri1);'; put 'rc=metadata_getnasn(assocuri1,''Domain'',1,assocuri2);'; put 'rc=metadata_getattr(assocuri2,"Name",authdomain);'; put 'call symputx(''authdomain'',authdomain,''l'');'; put '/* path */'; put 'rc=metadata_getprop(assocuri1,'; put '''Connection.Oracle.Property.PATH.Name.xmlKey.txt'',path);'; put 'call symputx(''path'',path,''l'');'; put '/* schema */'; put 'rc=metadata_getnasn("&liburi",''UsingPackages'',1,assocuri3);'; put 'rc=metadata_getattr(assocuri3,''SchemaName'',schema);'; put 'call symputx(''schema'',schema,''l'');'; put 'run;'; put '%put NOTE: Executing the following:/; %put NOTE-;'; put '%put NOTE- libname &libref ORACLE path=&path schema=&schema;'; put '%put NOTE- authdomain=&authdomain;'; put '%put NOTE-;'; put 'libname &libref ORACLE path=&path schema=&schema authdomain=&authdomain;'; put '%end;'; put '%else %if &engine=SQLSVR %then %do;'; put '%put NOTE: Obtaining &engine library details;'; put 'data _null;'; put 'length assocuri1 assocuri2 assocuri3 authdomain path schema userid'; put 'passwd $256;'; put 'call missing (of _all_);'; put 'rc=metadata_getnasn("&liburi",''DefaultLogin'',1,assocuri1);'; put 'rc=metadata_getattr(assocuri1,"UserID",userid);'; put 'rc=metadata_getattr(assocuri1,"Password",passwd);'; put 'call symputx(''user'',userid,''l'');'; put 'call symputx(''pass'',passwd,''l'');'; put '/* path */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,assocuri2);'; put 'rc=metadata_getprop(assocuri2,'; put '''Connection.SQL.Property.Datasrc.Name.xmlKey.txt'',path);'; put 'call symputx(''path'',path,''l'');'; put '/* schema */'; put 'rc=metadata_getnasn("&liburi",''UsingPackages'',1,assocuri3);'; put 'rc=metadata_getattr(assocuri3,''SchemaName'',schema);'; put 'call symputx(''schema'',schema,''l'');'; put 'run;'; put '%put NOTE: Executing the following:/; %put NOTE-;'; put '%put NOTE- libname &libref SQLSVR datasrc=&path schema=&schema ;'; put '%put NOTE- user="&user" pass="XXX";'; put '%put NOTE-;'; put 'libname &libref SQLSVR datasrc=&path schema=&schema user="&user" pass="&pass";'; put '%end;'; put '%else %if &engine=TERADATA %then %do;'; put '%put NOTE: Obtaining &engine library details;'; put 'data _null;'; put 'length assocuri1 assocuri2 assocuri3 authdomain path schema userid'; put 'passwd $256;'; put 'call missing (of _all_);'; put '/* get auth domain */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,assocuri1);'; put 'rc=metadata_getnasn(assocuri1,''Domain'',1,assocuri2);'; put 'rc=metadata_getattr(assocuri2,"Name",authdomain);'; put 'call symputx(''authdomain'',authdomain,''l'');'; put '/*'; put 'rc=metadata_getnasn("&liburi",''DefaultLogin'',1,assocuri1);'; put 'rc=metadata_getattr(assocuri1,"UserID",userid);'; put 'rc=metadata_getattr(assocuri1,"Password",passwd);'; put 'call symputx(''user'',userid,''l'');'; put 'call symputx(''pass'',passwd,''l'');'; put '*/'; put '/* path */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,assocuri2);'; put 'rc=metadata_getprop(assocuri2,'; put '''Connection.Teradata.Property.SERVER.Name.xmlKey.txt'',path);'; put 'call symputx(''path'',path,''l'');'; put '/* schema */'; put 'rc=metadata_getnasn("&liburi",''UsingPackages'',1,assocuri3);'; put 'rc=metadata_getattr(assocuri3,''SchemaName'',schema);'; put 'call symputx(''schema'',schema,''l'');'; put 'run;'; put '%put NOTE: Executing the following:/; %put NOTE-;'; put '%put NOTE- libname &libref TERADATA server="&path" schema=&schema ;'; put '%put NOTe- authdomain=&authdomain;'; put '%put NOTE-;'; put 'libname &libref TERADATA server="&path" schema=&schema authdomain=&authdomain;'; put '%end;'; put '%else %if &engine= %then %do;'; put '%put NOTE: Libref &libref is not registered in metadata;'; put '%&mAbort.mp_abort('; put 'msg=%str(ERR)OR: Libref &libref is not registered in metadata'; put ',mac=mm_assigndirectlib.sas);'; put '%return;'; put '%end;'; put '%else %do;'; put '%put %str(WARN)ING: Engine &engine is currently unsupported;'; put '%put %str(WARN)ING- Please contact your support team.;'; put '%return;'; put '%end;'; put '%mend mm_assigndirectlib;'; put '%macro mm_getrepos('; put 'outds=work.mm_getrepos'; put ')/*/STORE SOURCE*/;'; put '* use a temporary fileref to hold the response;'; put 'filename response temp;'; put '/* get list of libraries */'; put 'proc metadata in='; put '"1"'; put 'out=response;'; put 'run;'; put '/* write the response to the log for debugging */'; put '/*'; put 'data _null_;'; put 'infile response lrecl=1048576;'; put 'input;'; put 'put _infile_;'; put 'run;'; put '*/'; put '/* create an XML map to read the response */'; put 'filename sxlemap temp;'; put 'data _null_;'; put 'file sxlemap;'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository";'; put 'put "";'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Id";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Name";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Desc";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@DefaultNS";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@RepositoryType";'; put 'put "characterstring20";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@RepositoryFormat";'; put 'put "characterstring10";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@Access";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@CurrentAccess";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@PauseState";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Path";'; put 'put "";'; put 'put "characterstring256";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Engine";'; put 'put "";'; put 'put "characterstring8";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Options";'; put 'put "";'; put 'put "characterstring32";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@MetadataCreated";'; put 'put "characterstring24";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@MetadataUpdated";'; put 'put "characterstring24";'; put 'put '''';'; put 'put ''
'';'; put 'run;'; put 'libname _XML_ xml xmlfileref=response xmlmap=sxlemap;'; put 'proc sort data= _XML_.SASRepos out=&outds;'; put 'by name;'; put 'run;'; put '/* clear references */'; put 'filename sxlemap clear;'; put 'filename response clear;'; put 'libname _XML_ clear;'; put '%mend mm_getrepos;'; put '%macro dc_assignlib(type,libref,passthru=);'; put '%put &sysmacroname entry vars:;'; put '%put _local_;'; put '/* take current repository */'; put '%local repo repocnt x;'; put '%let repo=%sysfunc(getoption(metarepository));'; put '/* get list of repositories and filter */'; put '%mm_getrepos(outds=repos)'; put '%let repocnt=0;'; put 'data repos;'; put 'set repos;'; put 'where repositorytype in(''CUSTOM'',''FOUNDATION'')'; put '& upcase(name) ne "%upcase(&repo)";'; put 'keep id name ;'; put 'call symputx(cats(''repo'',_n_),name,''l'');'; put 'call symputx(''repocnt'',_n_,''l'');'; put 'run;'; put '/* find out which of these repositories has the libref we are searching for */'; put '%local lib_uri;'; put '%let lib_uri=NOTFOUND;'; put 'data _null_; /* check default repo first */'; put 'length lib_uri $256;'; put 'call missing (of _all_);'; put 'rc=metadata_getnobj("omsobj:SASLibrary?@Libref =''&libref''",1,lib_uri);'; put 'call symputx(''lib_uri'',coalescec(lib_uri,''NOTFOUND''),''l'');'; put 'run;'; put '%if &lib_uri=NOTFOUND %then %do;'; put '%do x=1 %to &repocnt;'; put 'options metarepository=&&repo&x;'; put 'data _null_;'; put 'length lib_uri $256;'; put 'call missing (of _all_);'; put 'rc=metadata_getnobj("omsobj:SASLibrary?@Libref =''&libref''",1,lib_uri);'; put 'call symputx(''lib_uri'',coalescec(lib_uri,''NOTFOUND''),''l'');'; put 'run;'; put '%if &lib_uri ne NOTFOUND %then %let x=%eval(&repocnt+1);'; put '%end;'; put '%end;'; put '%if &type=READ %then %do;'; put '%mm_assignlib(&libref)'; put '%end;'; put '%else %if &type=WRITE %then %do;'; put '%mm_assigndirectlib(&libref,open_passthrough=&passthru)'; put '%end;'; put 'options metarepository=&repo;'; put '%mend dc_assignlib;'; put '%macro mddl_sas_cntlout(libds=WORK.CNTLOUT);'; put 'proc sql;'; put 'create table &libds('; put 'TYPE char(1) label='; put '''Format Type: either N (num fmt), C (char fmt), I (num infmt) or J (char infmt)'''; put ',FMTNAME char(32) label=''Format name'''; put ',FMTROW num label='; put '''CALCULATED Position of record by FMTNAME (reqd for multilabel formats)'''; put ',START char(32767) label=''Starting value for format'''; put '/*'; put 'Keep lengths of START and END the same to avoid this err:'; put '"Start is greater than end: -<."'; put 'Similar usage note: https://support.sas.com/kb/69/330.html'; put '*/'; put ',END char(32767) label=''Ending value for format'''; put ',LABEL char(32767) label=''Format value label'''; put ',MIN num length=3 label=''Minimum length'''; put ',MAX num length=3 label=''Maximum length'''; put ',DEFAULT num length=3 label=''Default length'''; put ',LENGTH num length=3 label=''Format length'''; put ',FUZZ num label=''Fuzz value'''; put ',PREFIX char(2) label=''Prefix characters'''; put ',MULT num label=''Multiplier'''; put ',FILL char(1) label=''Fill character'''; put ',NOEDIT num length=3 label=''Is picture string noedit?'''; put ',SEXCL char(1) label=''Start exclusion'''; put ',EEXCL char(1) label=''End exclusion'''; put ',HLO char(13) label='; put '''More info: https://core.sasjs.io/mddl__sas__cntlout_8sas_source.html'''; put ',DECSEP char(1) label=''Decimal separator'''; put ',DIG3SEP char(1) label=''Three-digit separator'''; put ',DATATYPE char(8) label=''Date/time/datetime?'''; put ',LANGUAGE char(8) label=''Language for date strings'''; put ');'; put '%local lib;'; put '%let libds=%upcase(&libds);'; put '%if %index(&libds,.)=0 %then %let lib=WORK;'; put '%else %let lib=%scan(&libds,1,.);'; put 'proc datasets lib=&lib noprint;'; put 'modify %scan(&libds,-1,.);'; put 'index create'; put 'pk_cntlout=(type fmtname fmtrow)'; put '/nomiss unique;'; put 'quit;'; put '%mend mddl_sas_cntlout;'; put '%macro mf_existds(libds'; put ')/*/STORE SOURCE*/;'; put '%if %sysfunc(exist(&libds)) ne 1 & %sysfunc(exist(&libds,VIEW)) ne 1 %then 0;'; put '%else 1;'; put '%mend mf_existds;'; put '%macro mf_existfileref(fref'; put ')/*/STORE SOURCE*/;'; put '%local rc;'; put '%let rc=%sysfunc(fileref(&fref));'; put '%if &rc=0 %then %do;'; put '1'; put '%end;'; put '%else %if &rc<0 %then %do;'; put '%put &sysmacroname: Fileref &fref exists but the underlying file does not;'; put '1'; put '%end;'; put '%else %do;'; put '0'; put '%end;'; put '%mend mf_existfileref;'; put '%macro mf_getvarcount(libds,typefilter=A'; put ')/*/STORE SOURCE*/;'; put '%local dsid nvars rc outcnt x;'; put '%let dsid=%sysfunc(open(&libds));'; put '%let nvars=.;'; put '%let outcnt=0;'; put '%let typefilter=%upcase(&typefilter);'; put '%if &dsid %then %do;'; put '%let nvars=%sysfunc(attrn(&dsid,NVARS));'; put '%if &typefilter=A %then %let outcnt=&nvars;'; put '%else %if &nvars>0 %then %do x=1 %to &nvars;'; put '/* increment based on variable type */'; put '%if %sysfunc(vartype(&dsid,&x))=&typefilter %then %do;'; put '%let outcnt=%eval(&outcnt+1);'; put '%end;'; put '%end;'; put '%let rc=%sysfunc(close(&dsid));'; put '%end;'; put '%else %do;'; put '%put unable to open &libds (rc=&dsid);'; put '%let rc=%sysfunc(close(&dsid));'; put '%end;'; put '&outcnt'; put '%mend mf_getvarcount;'; put '%macro mf_getuniquename(prefix=MC);'; put '&prefix.%substr(%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32-%length(&prefix))'; put '%mend mf_getuniquename;'; put '%macro mf_isblank(param'; put ')/*/STORE SOURCE*/;'; put '%sysevalf(%superq(param)=,boolean)'; put '%mend mf_isblank;'; put '%macro mp_dropmembers('; put 'list /* space separated list of datasets / views */'; put ',libref=WORK /* can only drop from a single library at a time */'; put ',iftrue=%str(1=1)'; put ')/*/STORE SOURCE*/;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%if %mf_isblank(&list) %then %do;'; put '%put NOTE: nothing to drop!;'; put '%return;'; put '%end;'; put 'proc datasets lib=&libref nolist;'; put 'delete &list;'; put 'delete &list /mtype=view;'; put 'run;'; put '%mend mp_dropmembers;'; put '%macro mp_getconstraints(lib=WORK'; put ',ds='; put ',outds=mp_getconstraints'; put ',mdebug=0'; put ')/*/STORE SOURCE*/;'; put '%let lib=%upcase(&lib);'; put '%let ds=%upcase(&ds);'; put '/**'; put '* Cater for environments where sashelp.vcncolu is not available'; put '*/'; put '%if %sysfunc(exist(sashelp.vcncolu,view))=0 %then %do;'; put 'proc sql;'; put 'create table &outds('; put 'libref char(8)'; put ',TABLE_NAME char(32)'; put ',constraint_type char(8) label=''Constraint Type'''; put ',constraint_name char(32) label=''Constraint Name'''; put ',column_name char(32) label=''Column'''; put ',constraint_order num'; put ');'; put '%return;'; put '%end;'; put '/**'; put '* Neither dictionary tables nor sashelp provides a constraint order column,'; put '* however they DO arrive in the correct order. So, create the col.'; put '**/'; put '%local vw;'; put '%let vw=%mf_getuniquename(prefix=mp_getconstraints_vw_);'; put 'data &vw /view=&vw;'; put 'set sashelp.vcncolu;'; put 'where table_catalog="&lib";'; put '/* use retain approach to reset the constraint order with each constraint */'; put 'length tmp $1000;'; put 'retain tmp;'; put 'drop tmp;'; put 'if tmp ne catx(''|'',table_catalog,table_name,constraint_name) then do;'; put 'constraint_order=1;'; put 'end;'; put 'else constraint_order+1;'; put 'tmp=catx(''|'',table_catalog, table_name,constraint_name);'; put 'run;'; put '/* must use SQL as proc datasets does not support length changes */'; put 'proc sql noprint;'; put 'create table &outds as'; put 'select upcase(a.TABLE_CATALOG) as libref'; put ',upcase(a.TABLE_NAME) as TABLE_NAME'; put ',a.constraint_type'; put ',a.constraint_name'; put ',b.column_name'; put ',b.constraint_order'; put 'from dictionary.TABLE_CONSTRAINTS a'; put 'left join &vw b'; put 'on upcase(a.TABLE_CATALOG)=upcase(b.TABLE_CATALOG)'; put 'and upcase(a.TABLE_NAME)=upcase(b.TABLE_NAME)'; put 'and a.constraint_name=b.constraint_name'; put '/**'; put '* We cannot apply this clause to the underlying dictionary table. See:'; put '* https://communities.sas.com/t5/SAS-Programming/Unexpected-Where-Clause-behaviour-in-dictionary-TABLE/m-p/771554#M244867'; put '* cannot use`where calculated libref="&lib"` either as it will STILL execute'; put '* all the underlying constraint queries, causing exception errors in some'; put '* cases: https://github.com/sasjs/core/issues/283'; put '*/'; put 'where a.TABLE_CATALOG="&lib"'; put '%if "&ds" ne "" %then %do;'; put 'and upcase(a.TABLE_NAME)="&ds"'; put 'and upcase(b.TABLE_NAME)="&ds"'; put '%end;'; put 'order by libref, table_name, constraint_name, constraint_order'; put ';'; put '/* tidy up */'; put '%mp_dropmembers('; put '&vw,'; put 'iftrue=(&mdebug=0)'; put ')'; put '%mend mp_getconstraints;'; put '%macro mp_getddl(libref,ds,fref=getddl,flavour=SAS,showlog=NO,schema='; put ',applydttm=NO'; put ')/*/STORE SOURCE*/;'; put '/* check fileref is assigned */'; put '%if %mf_existfileref(&fref)=0 %then %do;'; put 'filename &fref temp ;'; put '%end;'; put '%if %length(&libref)=0 %then %let libref=WORK;'; put '%let flavour=%upcase(&flavour);'; put 'proc sql noprint;'; put 'create table _data_ as'; put 'select * from dictionary.tables'; put 'where upcase(libname)="%upcase(&libref)"'; put 'and memtype=''DATA'' /* views not currently supported */'; put '%if %length(&ds)>0 %then %do;'; put 'and upcase(memname)="%upcase(&ds)"'; put '%end;'; put ';'; put '%local tabinfo; %let tabinfo=&syslast;'; put 'create table _data_ as'; put 'select * from dictionary.columns'; put 'where upcase(libname)="%upcase(&libref)"'; put '%if %length(&ds)>0 %then %do;'; put 'and upcase(memname)="%upcase(&ds)"'; put '%end;'; put ';'; put '%local colinfo; %let colinfo=&syslast;'; put '%local dsnlist;'; put 'select distinct upcase(memname) into: dsnlist'; put 'separated by '' '''; put 'from &syslast'; put ';'; put 'create table _data_ as'; put 'select * from dictionary.indexes'; put 'where upcase(libname)="%upcase(&libref)"'; put '%if %length(&ds)>0 %then %do;'; put 'and upcase(memname)="%upcase(&ds)"'; put '%end;'; put 'order by idxusage, indxname, indxpos'; put ';'; put '%local idxinfo; %let idxinfo=&syslast;'; put '/* Extract all Primary Key and Unique data constraints */'; put '%mp_getconstraints(lib=%upcase(&libref),ds=%upcase(&ds),outds=_data_)'; put '%local colconst; %let colconst=&syslast;'; put '%macro addConst();'; put '%global constraints_used;'; put 'data _null_;'; put 'length ctype $11 constraint_name_orig $256 constraints_used $5000;'; put 'set &colconst('; put 'where=(table_name="&curds" and constraint_type in (''PRIMARY'',''UNIQUE''))'; put ') end=last;'; put 'file &fref mod;'; put 'by constraint_type constraint_name;'; put 'retain constraints_used;'; put 'constraint_name_orig=constraint_name;'; put 'if upcase(strip(constraint_type)) = ''PRIMARY'' then ctype=''PRIMARY KEY'';'; put 'else ctype=strip(constraint_type);'; put '%if &flavour=TSQL %then %do;'; put 'column_name=catt(''['',column_name,'']'');'; put 'constraint_name=catt(''['',constraint_name,'']'');'; put '%end;'; put '%else %if &flavour=PGSQL %then %do;'; put 'column_name=catt(''"'',column_name,''"'');'; put 'constraint_name=catt(''"'',constraint_name,''"'');'; put '%end;'; put 'if first.constraint_name then do;'; put 'constraints_used = catx('' '', constraints_used, constraint_name_orig);'; put 'put " ,CONSTRAINT " constraint_name ctype "(" ;'; put 'put '' '' column_name;'; put 'end;'; put 'else put '' ,'' column_name;'; put 'if last.constraint_name then do;'; put 'put " )";'; put 'call symput(''constraints_used'',strip(constraints_used));'; put 'end;'; put 'run;'; put '%put &=constraints_used;'; put '%mend addConst;'; put 'data _null_;'; put 'file &fref mod;'; put 'put "/* DDL generated by &sysuserid on %sysfunc(datetime(),datetime19.) */";'; put 'run;'; put '%local x curds;'; put '%if &flavour=SAS %then %do;'; put '%do x=1 %to %sysfunc(countw(&dsnlist));'; put '%let curds=%scan(&dsnlist,&x);'; put 'data _null_;'; put 'file &fref mod;'; put 'put "/* SAS Flavour DDL for %upcase(&libref).&curds */";'; put 'put "proc sql;";'; put 'run;'; put 'data _null_;'; put 'file &fref mod;'; put 'length lab $1024 typ $20;'; put 'set &colinfo (where=(upcase(memname)="&curds")) end=last;'; put 'if _n_=1 then do;'; put 'if memtype=''DATA'' then do;'; put 'put "create table &libref..&curds(";'; put 'end;'; put 'else do;'; put '/* just a placeholder - we filter out views at the top */'; put 'put "create view &libref..&curds(";'; put 'end;'; put 'put " "@@;'; put 'end;'; put 'else put " ,"@@;'; put 'if length(format)>1 then fmt=" format="!!cats(format);'; put 'if length(label)>1 then'; put 'lab=" label="!!cats("''",tranwrd(label,"''","''''"),"''");'; put 'if notnull=''yes'' then notnul='' not null'';'; put 'if type=''char'' then typ=cats(''char('',length,'')'');'; put 'else if length ne 8 then typ=''num length=''!!cats(length);'; put 'else typ=''num'';'; put 'put name typ fmt notnul lab;'; put 'run;'; put '/* Extra step for data constraints */'; put '%addConst()'; put 'data _null_;'; put 'file &fref mod;'; put 'put '');'';'; put 'run;'; put '/* Create Unique Indexes, but only if they were not already defined within'; put 'the Constraints section. */'; put 'data _null_;'; put '*length ds $128;'; put 'set &idxinfo('; put 'where=('; put 'memname="&curds"'; put 'and unique=''yes'''; put 'and indxname not in ('; put '%sysfunc(tranwrd("&constraints_used",%str( ),%str(",")))'; put ')'; put ')'; put ');'; put 'file &fref mod;'; put 'by idxusage indxname;'; put '/* ds=cats(libname,''.'',memname); */'; put 'if first.indxname then do;'; put 'put ''CREATE UNIQUE INDEX '' indxname "ON &libref..&curds (" ;'; put 'put '' '' name ;'; put 'end;'; put 'else put '' ,'' name ;'; put '*else put '' ,'' name ;'; put 'if last.indxname then do;'; put 'put '');'';'; put 'end;'; put 'run;'; put '/*'; put 'ods output IntegrityConstraints=ic;'; put 'proc contents data=testali out2=info;'; put 'run;'; put '*/'; put '%end;'; put '%end;'; put '%else %if &flavour=TSQL %then %do;'; put '/* if schema does not exist, set to be same as libref */'; put '%local schemaactual;'; put 'proc sql noprint;'; put 'select sysvalue into: schemaactual'; put 'from dictionary.libnames'; put 'where upcase(libname)="&libref" and engine=''SQLSVR'';'; put '%let schema=%sysfunc(coalescec(&schemaactual,&schema,&libref));'; put '%do x=1 %to %sysfunc(countw(&dsnlist));'; put '%let curds=%scan(&dsnlist,&x);'; put 'data _null_;'; put 'file &fref mod;'; put 'put "/* TSQL Flavour DDL for &schema..&curds */";'; put 'data _null_;'; put 'file &fref mod;'; put 'set &colinfo (where=(upcase(memname)="&curds")) end=last;'; put 'if _n_=1 then do;'; put 'if memtype=''DATA'' then do;'; put 'put "create table [&schema].[&curds](";'; put 'end;'; put 'else do;'; put '/* just a placeholder - we filter out views at the top */'; put 'put "create view [&schema].[&curds](";'; put 'end;'; put 'put " "@@;'; put 'end;'; put 'else put " ,"@@;'; put 'format=upcase(format);'; put 'if 1=0 then; /* dummy if */'; put '%if &applydttm=YES %then %do;'; put 'else if format=:''DATETIME'' then fmt=''[datetime2](7) '';'; put '%end;'; put 'else if type=''num'' then fmt=''[decimal](18,2)'';'; put 'else if length le 8000 then fmt=''[varchar](''!!cats(length)!!'')'';'; put 'else fmt=cats(''[varchar](max)'');'; put 'if notnull=''yes'' then notnul='' NOT NULL'';'; put 'put "[" name +(-1) "]" fmt notnul;'; put 'run;'; put '/* Extra step for data constraints */'; put '%addConst()'; put '/* Create Unique Indexes, but only if they were not already defined within'; put 'the Constraints section. */'; put 'data _null_;'; put '*length ds $128;'; put 'set &idxinfo('; put 'where=('; put 'memname="&curds"'; put 'and unique=''yes'''; put 'and indxname not in ('; put '%sysfunc(tranwrd("&constraints_used",%str( ),%str(",")))'; put ')'; put ')'; put ');'; put 'file &fref mod;'; put 'by idxusage indxname;'; put '*ds=cats(libname,''.'',memname);'; put 'if first.indxname then do;'; put '/* add nonclustered in case of multiple unique indexes */'; put 'put '' ,index ['' indxname +(-1) ''] UNIQUE NONCLUSTERED ('';'; put 'put '' ['' name +(-1) '']'';'; put 'end;'; put 'else put '' ,['' name +(-1) '']'';'; put 'if last.indxname then do;'; put 'put '' )'';'; put 'end;'; put 'run;'; put 'data _null_;'; put 'file &fref mod;'; put 'put '')'';'; put 'put ''GO'';'; put 'run;'; put '/* add extended properties for labels */'; put 'data _null_;'; put 'file &fref mod;'; put 'length nm $64 lab $1024;'; put 'set &colinfo (where=(upcase(memname)="&curds" and label ne '''')) end=last;'; put 'nm=cats("N''",tranwrd(name,"''","''''"),"''");'; put 'lab=cats("N''",tranwrd(label,"''","''''"),"''");'; put 'put '' '';'; put 'put "EXEC sys.sp_addextendedproperty ";'; put 'put " @name=N''MS_Description'',@value=" lab ;'; put 'put " ,@level0type=N''SCHEMA'',@level0name=N''&schema'' ";'; put 'put " ,@level1type=N''TABLE'',@level1name=N''&curds''";'; put 'put " ,@level2type=N''COLUMN'',@level2name=" nm ;'; put 'if last then put ''GO'';'; put 'run;'; put '%end;'; put '%end;'; put '%else %if &flavour=PGSQL %then %do;'; put '/* if schema does not exist, set to be same as libref */'; put '%local schemaactual;'; put 'proc sql noprint;'; put 'select sysvalue into: schemaactual'; put 'from dictionary.libnames'; put 'where upcase(libname)="&libref" and engine=''POSTGRES'';'; put '%let schema=%sysfunc(coalescec(&schemaactual,&schema,&libref));'; put 'data _null_;'; put 'file &fref mod;'; put 'put "CREATE SCHEMA &schema;";'; put '%do x=1 %to %sysfunc(countw(&dsnlist));'; put '%let curds=%scan(&dsnlist,&x);'; put '%local curdsvarcount;'; put '%let curdsvarcount=%mf_getvarcount(&libref..&curds);'; put '%if &curdsvarcount>1600 %then %do;'; put 'data _null_;'; put 'file &fref mod;'; put 'put "/* &libref..&curds contains &curdsvarcount vars */";'; put 'put "/* Postgres cannot create tables with over 1600 vars */";'; put 'put "/* No DDL will be generated for this table";'; put 'run;'; put '%end;'; put '%else %do;'; put 'data _null_;'; put 'file &fref mod;'; put 'put "/* Postgres Flavour DDL for &schema..&curds */";'; put 'data _null_;'; put 'file &fref mod;'; put 'set &colinfo (where=(upcase(memname)="&curds")) end=last;'; put 'length fmt $32;'; put 'if _n_=1 then do;'; put 'if memtype=''DATA'' then do;'; put 'put "CREATE TABLE &schema..&curds (";'; put 'end;'; put 'else do;'; put '/* just a placeholder - we filter out views at the top */'; put 'put "CREATE VIEW &schema..&curds (";'; put 'end;'; put 'put " "@@;'; put 'end;'; put 'else put " ,"@@;'; put 'format=upcase(format);'; put 'if 1=0 then; /* dummy if */'; put '%if &applydttm=YES %then %do;'; put 'else if format=:''DATETIME'' then fmt='' TIMESTAMP '';'; put '%end;'; put 'else if type=''num'' then fmt='' DOUBLE PRECISION'';'; put 'else fmt=''VARCHAR(''!!cats(length)!!'')'';'; put 'if notnull=''yes'' then notnul='' NOT NULL'';'; put '/* quote column names in case they represent reserved words */'; put 'name2=quote(trim(name));'; put 'put name2 fmt notnul;'; put 'run;'; put '/* Extra step for data constraints */'; put '%addConst()'; put 'data _null_;'; put 'file &fref mod;'; put 'put '');'';'; put 'run;'; put '/* Create Unique Indexes, but only if they were not already defined within'; put 'the Constraints section. */'; put 'data _null_;'; put '*length ds $128;'; put 'set &idxinfo('; put 'where=('; put 'memname="&curds"'; put 'and unique=''yes'''; put 'and indxname not in ('; put '%sysfunc(tranwrd("&constraints_used",%str( ),%str(",")))'; put ')'; put ')'; put ');'; put 'file &fref mod;'; put 'by idxusage indxname;'; put 'if first.indxname then do;'; put 'put ''CREATE UNIQUE INDEX "'' indxname +(-1) ''" '' "ON &schema..&curds(";'; put 'put '' "'' name +(-1) ''"'' ;'; put 'end;'; put 'else put '' ,"'' name +(-1) ''"'';'; put 'if last.indxname then do;'; put 'put '');'';'; put 'end;'; put 'run;'; put '%end;'; put '%end;'; put '%end;'; put '%if %upcase(&showlog)=YES %then %do;'; put 'options ps=max;'; put 'data _null_;'; put 'infile &fref;'; put 'input;'; put 'putlog _infile_;'; put 'run;'; put '%end;'; put '%mend mp_getddl;'; put '%macro mfs_httpheader(header_name'; put ',header_value'; put ')/*/STORE SOURCE*/;'; put '%global sasjs_stpsrv_header_loc;'; put '%local fref fid i;'; put '%if %sysfunc(filename(fref,&sasjs_stpsrv_header_loc)) ne 0 %then %do;'; put '%put &=fref &=sasjs_stpsrv_header_loc;'; put '%put %str(ERR)OR: %sysfunc(sysmsg());'; put '%return;'; put '%end;'; put '%let fid=%sysfunc(fopen(&fref,A));'; put '%if &fid=0 %then %do;'; put '%put %str(ERR)OR: %sysfunc(sysmsg());'; put '%return;'; put '%end;'; put '%let rc=%sysfunc(fput(&fid,%str(&header_name): %str(&header_value)));'; put '%let rc=%sysfunc(fwrite(&fid));'; put '%let rc=%sysfunc(fclose(&fid));'; put '%let rc=%sysfunc(filename(&fref));'; put '%mend mfs_httpheader;'; put '%macro mp_binarycopy('; put 'inloc= /* full path and filename of the object to be copied */'; put ',outloc= /* full path and filename of object to be created */'; put ',inref=____in /* override default to use own filerefs */'; put ',outref=____out /* override default to use own filerefs */'; put ',mode=CREATE'; put ',iftrue=%str(1=1)'; put ')/*/STORE SOURCE*/;'; put '%local mod;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%if &mode=APPEND %then %let mod=mod;'; put '/* these IN and OUT filerefs can point to anything */'; put '%if &inref = ____in %then %do;'; put 'filename &inref &inloc lrecl=1048576 ;'; put '%end;'; put '%if &outref=____out %then %do;'; put 'filename &outref &outloc lrecl=1048576 &mod;'; put '%end;'; put '/* copy the file byte-for-byte */'; put 'data _null_;'; put 'infile &inref lrecl=1 recfm=n;'; put 'file &outref &mod recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put '%if &inref = ____in %then %do;'; put 'filename &inref clear;'; put '%end;'; put '%if &outref=____out %then %do;'; put 'filename &outref clear;'; put '%end;'; put '%mend mp_binarycopy;'; put '%macro mp_streamfile('; put 'contenttype=TEXT'; put ',inloc='; put ',inref=0'; put ',iftrue=%str(1=1)'; put ',outname='; put ',outref=_webout'; put ')/*/STORE SOURCE*/;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%let contentype=%upcase(&contenttype);'; put '%let outref=%upcase(&outref);'; put '%local platform; %let platform=%mf_getplatform();'; put '/**'; put '* check engine type to avoid the below err message:'; put '* > Function is only valid for filerefs using the CACHE access method.'; put '*/'; put '%local streamweb;'; put '%let streamweb=0;'; put 'data _null_;'; put 'set sashelp.vextfl(where=(upcase(fileref)="&outref"));'; put 'if xengine=''STREAM'' then call symputx(''streamweb'',1,''l'');'; put 'run;'; put '%if &contentype=CSV %then %do;'; put '%if (&platform=SASMETA and &streamweb=1) %then %do;'; put 'data _null_;'; put 'rc=stpsrv_header(''Content-Type'',''application/csv'');'; put 'rc=stpsrv_header(''Content-disposition'',"attachment; filename=&outname");'; put 'run;'; put '%end;'; put '%else %if &platform=SASVIYA %then %do;'; put 'filename &outref filesrvc parenturi="&SYS_JES_JOB_URI" name=''_webout.txt'''; put 'contenttype=''application/csv'''; put 'contentdisp="attachment; filename=&outname";'; put '%end;'; put '%else %if &platform=SASJS %then %do;'; put '%mfs_httpheader(Content-Type,application/csv)'; put '%mfs_httpheader(Content-disposition,%str(attachment; filename=&outname))'; put '%end;'; put '%end;'; put '%else %if &contentype=EXCEL %then %do;'; put '/* suitable for XLS format */'; put '%if (&platform=SASMETA and &streamweb=1) %then %do;'; put 'data _null_;'; put 'rc=stpsrv_header(''Content-Type'',''application/vnd.ms-excel'');'; put 'rc=stpsrv_header(''Content-disposition'',"attachment; filename=&outname");'; put 'run;'; put '%end;'; put '%else %if &platform=SASVIYA %then %do;'; put 'filename &outref filesrvc parenturi="&SYS_JES_JOB_URI" name=''_webout.xls'''; put 'contenttype=''application/vnd.ms-excel'''; put 'contentdisp="attachment; filename=&outname";'; put '%end;'; put '%else %if &platform=SASJS %then %do;'; put '%mfs_httpheader(Content-Type,application/vnd.ms-excel)'; put '%mfs_httpheader(Content-disposition,%str(attachment; filename=&outname))'; put '%end;'; put '%end;'; put '%else %if &contentype=GIF or &contentype=JPEG or &contentype=PNG %then %do;'; put '%if (&platform=SASMETA and &streamweb=1) %then %do;'; put 'data _null_;'; put 'rc=stpsrv_header(''Content-Type'',"image/%lowcase(&contenttype)");'; put 'run;'; put '%end;'; put '%else %if &platform=SASVIYA %then %do;'; put 'filename &outref filesrvc parenturi="&SYS_JES_JOB_URI"'; put 'contenttype="image/%lowcase(&contenttype)";'; put '%end;'; put '%else %if &platform=SASJS %then %do;'; put '%mfs_httpheader(Content-Type,image/%lowcase(&contenttype))'; put '%end;'; put '%end;'; put '%else %if &contentype=HTML or &contenttype=MARKDOWN %then %do;'; put '%if (&platform=SASMETA and &streamweb=1) %then %do;'; put 'data _null_;'; put 'rc=stpsrv_header(''Content-Type'',"text/%lowcase(&contenttype)");'; put 'rc=stpsrv_header(''Content-disposition'',"attachment; filename=&outname");'; put 'run;'; put '%end;'; put '%else %if &platform=SASVIYA %then %do;'; put 'filename &outref filesrvc parenturi="&SYS_JES_JOB_URI" name="_webout.json"'; put 'contenttype="text/%lowcase(&contenttype)"'; put 'contentdisp="attachment; filename=&outname";'; put '%end;'; put '%else %if &platform=SASJS %then %do;'; put '%mfs_httpheader(Content-Type,text/%lowcase(&contenttype))'; put '%mfs_httpheader(Content-disposition,%str(attachment; filename=&outname))'; put '%end;'; put '%end;'; put '%else %if &contentype=TEXT %then %do;'; put '%if (&platform=SASMETA and &streamweb=1) %then %do;'; put 'data _null_;'; put 'rc=stpsrv_header(''Content-Type'',''application/text'');'; put 'rc=stpsrv_header(''Content-disposition'',"attachment; filename=&outname");'; put 'run;'; put '%end;'; put '%else %if &platform=SASVIYA %then %do;'; put 'filename &outref filesrvc parenturi="&SYS_JES_JOB_URI" name=''_webout.txt'''; put 'contenttype=''application/text'''; put 'contentdisp="attachment; filename=&outname";'; put '%end;'; put '%else %if &platform=SASJS %then %do;'; put '%mfs_httpheader(Content-Type,application/text)'; put '%mfs_httpheader(Content-disposition,%str(attachment; filename=&outname))'; put '%end;'; put '%end;'; put '%else %if &contentype=WOFF or &contentype=WOFF2 or &contentype=TTF %then %do;'; put '%if (&platform=SASMETA and &streamweb=1) %then %do;'; put 'data _null_;'; put 'rc=stpsrv_header(''Content-Type'',"font/%lowcase(&contenttype)");'; put 'run;'; put '%end;'; put '%else %if &platform=SASVIYA %then %do;'; put 'filename &outref filesrvc parenturi="&SYS_JES_JOB_URI"'; put 'contenttype="font/%lowcase(&contenttype)";'; put '%end;'; put '%else %if &platform=SASJS %then %do;'; put '%mfs_httpheader(Content-Type,font/%lowcase(&contenttype))'; put '%end;'; put '%end;'; put '%else %if &contentype=XLSX %then %do;'; put '%if (&platform=SASMETA and &streamweb=1) %then %do;'; put 'data _null_;'; put 'rc=stpsrv_header(''Content-Type'','; put '''application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'');'; put 'rc=stpsrv_header(''Content-disposition'',"attachment; filename=&outname");'; put 'run;'; put '%end;'; put '%else %if &platform=SASVIYA %then %do;'; put 'filename &outref filesrvc parenturi="&SYS_JES_JOB_URI" name=''_webout.xls'''; put 'contenttype='; put '''application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'''; put 'contentdisp="attachment; filename=&outname";'; put '%end;'; put '%else %if &platform=SASJS %then %do;'; put '%mfs_httpheader(Content-Type'; put ',application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'; put ')'; put '%mfs_httpheader(Content-disposition,%str(attachment; filename=&outname))'; put '%end;'; put '%end;'; put '%else %if &contentype=ZIP %then %do;'; put '%if (&platform=SASMETA and &streamweb=1) %then %do;'; put 'data _null_;'; put 'rc=stpsrv_header(''Content-Type'',''application/zip'');'; put 'rc=stpsrv_header(''Content-disposition'',"attachment; filename=&outname");'; put 'run;'; put '%end;'; put '%else %if &platform=SASVIYA %then %do;'; put 'filename &outref filesrvc parenturi="&SYS_JES_JOB_URI" name=''_webout.zip'''; put 'contenttype=''application/zip'''; put 'contentdisp="attachment; filename=&outname";'; put '%end;'; put '%else %if &platform=SASJS %then %do;'; put '%mfs_httpheader(Content-Type,application/zip)'; put '%mfs_httpheader(Content-disposition,%str(attachment; filename=&outname))'; put '%end;'; put '%end;'; put '%else %do;'; put '%put %str(ERR)OR: Content Type &contenttype NOT SUPPORTED by &sysmacroname!;'; put '%end;'; put '%if &inref ne 0 %then %do;'; put '%mp_binarycopy(inref=&inref,outref=&outref)'; put '%end;'; put '%else %do;'; put '%mp_binarycopy(inloc="&inloc",outref=&outref)'; put '%end;'; put '%mend mp_streamfile;'; put '* SAS Macros end;'; put '* SAS Includes start;'; put '* SAS Includes end;'; put '* Binary Files start;'; put '* Binary Files end;'; put '* ServiceInit start;'; put 'options noquotelenmax ps=max;'; put '/* create dummy macros to prevent stpbegin / stpend from corrupting sessions */'; put '%macro stpbegin();'; put '%put NOTE: the STPBEGIN macro should not be used for web apps!;'; put '%mend stpbegin;'; put '%macro stpend();'; put '%put NOTE: the STPEND macro should not be used for web apps!;'; put '%mend stpend;'; put '* ServiceInit end;'; put '* Service start;'; put '/**'; put '@file'; put '@brief Download DDL for a table or entire library in a particular flavour.'; put '@details'; put '

SAS Macros

'; put '@li mddl_sas_cntlout.sas'; put '@li dc_assignlib.sas'; put '@li mf_existds.sas'; put '@li mp_abort.sas'; put '@li mp_getddl.sas'; put '@li mp_streamfile.sas'; put '@version 9.2'; put '@author 4GL Apps Ltd'; put '@copyright 4GL Apps Ltd. This code may only be used within Data Controller'; put 'and may not be re-distributed or re-sold without the express permission of'; put '4GL Apps Ltd.'; put '**/'; put '%global libref ds flavour;'; put '%let flavour=%sysfunc(coalescec(&flavour,SAS));'; put '%mpeinit()'; put '%dc_assignlib(READ,&libref)'; put 'data _null_;'; put '/* check if the request is for a format catalog */'; put 'ds=symget(''ds'');'; put 'if subpad(cats(reverse(ds)),1,3)=:''CF-'' then do;'; put 'ds=scan(ds,1,''-'');'; put 'libds=cats(symget(''libref''),''.'',ds);'; put 'putlog "Format Catalog Captured";'; put 'call execute(''%mddl_sas_cntlout(libds=work.fmtextract)'');'; put 'call symputx(''libref'',''work'');'; put 'call symputx(''ds'',''fmtextract'');'; put 'end;'; put 'putlog (_all_)(=);'; put 'run;'; put '%mp_abort(iftrue=("exist&ds" ne "exist" and %mf_existds(libds=&libref..&ds)<1)'; put ',mac=&_program'; put ',msg=%str(Dataset &libref..&ds was not found)'; put ')'; put '%let tmploc=%sysfunc(pathname(work))/temp.txt;'; put 'filename tmp "&tmploc";'; put '%mp_getddl(&libref,&ds,flavour=&flavour, fref=tmp, applydttm=YES)'; put '%mp_streamfile(contenttype=TEXT'; put ',inloc=%str(&tmploc)'; put ',outname=&libref._&ds..ddl'; put ')'; put '* Service end;'; run; %mm_createwebservice(path=&appLoc/&path, name=&service, code=sascode, server=&serverName, replace=yes) filename sascode clear; %let service=getgroups; filename sascode temp lrecl=32767; data _null_; file sascode; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '/**'; put '@file mp_jsonout.sas'; put '@brief Writes JSON in SASjs format to a fileref'; put '@details This macro can be used to OPEN a JSON stream and send one or more'; put 'tables as arrays of rows, where each row can be an object or a nested array.'; put 'There are two engines available - DATASTEP or PROCJSON.'; put 'PROC JSON is fast but will produce errs like the ones below if'; put 'special chars are encountered.'; put '> (ERR)OR: Some code points did not transcode.'; put '> An object or array close is not valid at this point in the JSON text.'; put '> Date value out of range'; put 'If this happens, try running with ENGINE=DATASTEP.'; put 'The DATASTEP engine is used to handle special SAS missing numerics, and'; put 'can also convert entire datasets to formatted values. Output JSON is always'; put 'in UTF-8.'; put 'Usage:'; put 'filename tmp temp;'; put 'data class; set sashelp.class;run;'; put '%mp_jsonout(OPEN,jref=tmp)'; put '%mp_jsonout(OBJ,class,jref=tmp)'; put '%mp_jsonout(OBJ,class,dslabel=class2,jref=tmp,showmeta=Y)'; put '%mp_jsonout(CLOSE,jref=tmp)'; put 'data _null_;'; put 'infile tmp;'; put 'input;putlog _infile_;'; put 'run;'; put 'If you are building web apps with SAS then you are strongly encouraged to use'; put 'the mX_createwebservice macros in combination with the'; put '[sasjs adapter](https://github.com/sasjs/adapter).'; put 'For more information see https://sasjs.io'; put '@param [in] action Valid values:'; put '@li OPEN - opens the JSON'; put '@li OBJ - sends a table with each row as an object'; put '@li ARR - sends a table with each row in an array'; put '@li CLOSE - closes the JSON'; put '@param [in] ds The dataset to send. Must be a work table.'; put '@param [out] jref= (_webout) The fileref to which to send the JSON'; put '@param [out] dslabel= The name to give the table in the exported JSON'; put '@param [in] fmt= (Y) Whether to keep (Y) or strip (N) formats from the table'; put '@param [in] engine= (DATASTEP) Which engine to use to send the JSON. Options:'; put '@li PROCJSON (default)'; put '@li DATASTEP (more reliable when data has non standard characters)'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to JSON'; put '

Related Files

'; put '@li mp_ds2fmtds.sas'; put '@version 9.2'; put '@author Allan Bowe'; put '@source https://github.com/sasjs/core'; put '**/'; put '%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y'; put ',engine=DATASTEP'; put ',missing=NULL'; put ',showmeta=N'; put ',maxobs=MAX'; put ')/*/STORE SOURCE*/;'; put '%local tempds colinfo fmtds i numcols numobs stmt_obs lastobs optval'; put 'tmpds1 tmpds2 tmpds3 tmpds4;'; put '%let numcols=0;'; put '%if &maxobs ne MAX %then %let stmt_obs=%str(if _n_>&maxobs then stop;);'; put '%if &action=OPEN %then %do;'; put 'options nobomfile;'; put 'data _null_;file &jref encoding=''utf-8'' lrecl=200;'; put 'put ''{"PROCESSED_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''"'';'; put 'run;'; put '%end;'; put '%else %if (&action=ARR or &action=OBJ) %then %do;'; put '/* force variable names to always be uppercase in the JSON */'; put 'options validvarname=upcase;'; put '/* To avoid issues with _webout on EBI - such as encoding diffs and truncation'; put '(https://support.sas.com/kb/49/325.html) we use temporary files */'; put 'filename _sjs1 temp lrecl=200 ;'; put 'data _null_; file _sjs1 encoding=''utf-8'';'; put 'put ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":";'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjs1 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs1 clear;'; put '/* grab col defs */'; put 'proc contents noprint data=&ds'; put 'out=_data_(keep=name type length format formatl formatd varnum label);'; put 'run;'; put '%let colinfo=%scan(&syslast,2,.);'; put 'proc sort data=&colinfo;'; put 'by varnum;'; put 'run;'; put '/* move meta to mac vars */'; put 'data &colinfo;'; put 'if _n_=1 then call symputx(''numcols'',nobs,''l'');'; put 'set &colinfo end=last nobs=nobs;'; put 'name=upcase(name);'; put '/* fix formats */'; put 'if type=2 or type=6 then do;'; put 'typelong=''char'';'; put 'length fmt $49.;'; put 'if format='''' then fmt=cats(''$'',length,''.'');'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else fmt=cats(format,formatl,''.'');'; put 'end;'; put 'else do;'; put 'typelong=''num'';'; put 'if format='''' then fmt=''best.'';'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else if formatd=0 then fmt=cats(format,formatl,''.'');'; put 'else fmt=cats(format,formatl,''.'',formatd);'; put 'end;'; put '/* 32 char unique name */'; put 'newname=''sasjs''!!substr(cats(put(md5(name),$hex32.)),1,27);'; put 'call symputx(cats(''name'',_n_),name,''l'');'; put 'call symputx(cats(''newname'',_n_),newname,''l'');'; put 'call symputx(cats(''length'',_n_),length,''l'');'; put 'call symputx(cats(''fmt'',_n_),fmt,''l'');'; put 'call symputx(cats(''type'',_n_),type,''l'');'; put 'call symputx(cats(''typelong'',_n_),typelong,''l'');'; put 'call symputx(cats(''label'',_n_),coalescec(label,name),''l'');'; put '/* overwritten when fmt=Y and a custom format exists in catalog */'; put 'if typelong=''num'' then call symputx(cats(''fmtlen'',_n_),200,''l'');'; put 'else call symputx(cats(''fmtlen'',_n_),min(32767,ceil((length+10)*1.5)),''l'');'; put 'run;'; put '%let tempds=%substr(_%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql;'; put 'select count(*) into: lastobs from &ds;'; put '%if &maxobs ne MAX %then %let lastobs=%sysfunc(min(&lastobs,&maxobs));'; put '%if &engine=PROCJSON %then %do;'; put '%if &missing=STRING %then %do;'; put '%put &sysmacroname: Special Missings not supported in proc json.;'; put '%put &sysmacroname: Switching to DATASTEP engine;'; put '%goto datastep;'; put '%end;'; put 'data &tempds;'; put 'set &ds;'; put '&stmt_obs;'; put '%if &fmt=N %then format _numeric_ best32.;;'; put '/* PRETTY is necessary to avoid line truncation in large files */'; put 'filename _sjs2 temp lrecl=131068 encoding=''utf-8'';'; put 'proc json out=_sjs2 pretty'; put '%if &action=ARR %then nokeys ;'; put ';export &tempds / nosastags fmtnumeric;'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs2 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs2 clear;'; put '%end;'; put '%else %if &engine=DATASTEP %then %do;'; put '%datastep:'; put '%if %sysfunc(exist(&ds)) ne 1 & %sysfunc(exist(&ds,VIEW)) ne 1'; put '%then %do;'; put '%put &sysmacroname: &ds NOT FOUND!!!;'; put '%return;'; put '%end;'; put '%if &fmt=Y %then %do;'; put '/**'; put '* Extract format definitions'; put '* First, by getting library locations from dictionary.formats'; put '* Then, by exporting the width using proc format'; put '* Cannot use maxw from sashelp.vformat as not always populated'; put '* Cannot use fmtinfo() as not supported in all flavours'; put '*/'; put '%let tmpds1=%substr(fmtsum%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds2=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds3=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds4=%substr(col%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql noprint;'; put 'create table &tmpds1 as'; put 'select cats(libname,''.'',memname) as FMTCAT,'; put 'FMTNAME'; put 'from dictionary.formats'; put 'where fmttype=''F'' and libname is not null'; put 'and fmtname in (select format from &colinfo where format is not null)'; put 'order by 1;'; put 'create table &tmpds2('; put 'FMTNAME char(32),'; put 'LENGTH num'; put ');'; put '%local catlist cat fmtlist i;'; put 'select distinct fmtcat into: catlist separated by '' '' from &tmpds1;'; put '%do i=1 %to %sysfunc(countw(&catlist,%str( )));'; put '%let cat=%scan(&catlist,&i,%str( ));'; put 'proc sql;'; put 'select distinct fmtname into: fmtlist separated by '' '''; put 'from &tmpds1 where fmtcat="&cat";'; put 'proc format lib=&cat cntlout=&tmpds3(keep=fmtname length);'; put 'select &fmtlist;'; put 'run;'; put 'proc sql;'; put 'insert into &tmpds2 select distinct fmtname,length from &tmpds3;'; put '%end;'; put 'proc sql;'; put 'create table &tmpds4 as'; put 'select a.*, b.length as MAXW'; put 'from &colinfo a'; put 'left join &tmpds2 b'; put 'on cats(a.format)=cats(upcase(b.fmtname))'; put 'order by a.varnum;'; put 'data _null_;'; put 'set &tmpds4;'; put 'if not missing(maxw);'; put 'call symputx('; put 'cats(''fmtlen'',_n_),'; put '/* vars need extra padding due to JSON escaping of special chars */'; put 'min(32767,ceil((max(length,maxw)+10)*1.5))'; put ',''l'''; put ');'; put 'run;'; put '/* configure varlenchk - as we are explicitly shortening the variables */'; put '%let optval=%sysfunc(getoption(varlenchk));'; put 'options varlenchk=NOWARN;'; put 'data _data_(compress=char);'; put '/* shorten the new vars */'; put 'length'; put '%do i=1 %to &numcols;'; put '&&name&i $&&fmtlen&i'; put '%end;'; put ';'; put '/* rename on entry */'; put 'set &ds(rename=('; put '%do i=1 %to &numcols;'; put '&&name&i=&&newname&i'; put '%end;'; put '));'; put '&stmt_obs;'; put 'drop'; put '%do i=1 %to &numcols;'; put '&&newname&i'; put '%end;'; put ';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=num %then %do;'; put '&&name&i=cats(put(&&newname&i,&&fmt&i));'; put '%end;'; put '%else %do;'; put '&&name&i=put(&&newname&i,&&fmt&i);'; put '%end;'; put '%end;'; put 'if _error_ then do;'; put 'call symputx(''syscc'',1012);'; put 'stop;'; put 'end;'; put 'run;'; put '%let fmtds=&syslast;'; put 'options varlenchk=&optval;'; put '%end;'; put 'proc format; /* credit yabwon for special null removal */'; put 'value bart (default=40)'; put '%if &missing=NULL %then %do;'; put '._ - .z = null'; put '%end;'; put '%else %do;'; put '._ = [quote()]'; put '. = null'; put '.a - .z = [quote()]'; put '%end;'; put 'other = [best.];'; put 'data &tempds;'; put 'attrib _all_ label='''';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'length &&name&i $&&fmtlen&i...;'; put 'format &&name&i $&&fmtlen&i...;'; put '%end;'; put '%end;'; put '%if &fmt=Y %then %do;'; put 'set &fmtds;'; put '%end;'; put '%else %do;'; put 'set &ds;'; put '%end;'; put '&stmt_obs;'; put 'format _numeric_ bart.;'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'if findc(&&name&i,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put '&&name&i=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,&&name&i)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else &&name&i=quote(cats(&&name&i));'; put '%end;'; put '%end;'; put 'run;'; put 'filename _sjs3 temp lrecl=131068 ;'; put 'data _null_;'; put 'file _sjs3 encoding=''utf-8'';'; put 'if _n_=1 then put "[";'; put 'set &tempds;'; put 'if _n_>1 then put "," @; put'; put '%if &action=ARR %then "[" ; %else "{" ;'; put '%do i=1 %to &numcols;'; put '%if &i>1 %then "," ;'; put '%if &action=OBJ %then """&&name&i"":" ;'; put '"&&name&i"n /* name literal for reserved variable names */'; put '%end;'; put '%if &action=ARR %then "]" ; %else "}" ; ;'; put '/* close out the table */'; put 'data _null_;'; put 'file _sjs3 mod encoding=''utf-8'';'; put 'put '']'';'; put 'run;'; put 'data _null_;'; put 'infile _sjs3 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs3 clear;'; put '%end;'; put 'proc sql;'; put 'drop table &colinfo, &tempds;'; put '%if %substr(&showmeta,1,1)=Y %then %do;'; put 'filename _sjs4 temp lrecl=131068 encoding=''utf-8'';'; put 'data _null_;'; put 'file _sjs4;'; put 'length label $350;'; put 'put ", ""$%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":{""vars"":{";'; put 'do i=1 to &numcols;'; put 'name=quote(trim(symget(cats(''name'',i))));'; put 'format=quote(trim(symget(cats(''fmt'',i))));'; put 'label=quote(prxchange(''s/\\/\\\\/'',-1,trim(symget(cats(''label'',i)))));'; put 'length=quote(trim(symget(cats(''length'',i))));'; put 'type=quote(trim(symget(cats(''typelong'',i))));'; put 'if i>1 then put "," @@;'; put 'put name '':{"format":'' format '',"label":'' label'; put ''',"length":'' length '',"type":'' type ''}'';'; put 'end;'; put 'put ''}}'';'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs4 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs4 clear;'; put '%end;'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put 'data _null_; file &jref encoding=''utf-8'' mod ;'; put 'put "}";'; put 'run;'; put '%end;'; put '%mend mp_jsonout;'; put '/**'; put '@file mm_webout.sas'; put '@brief Send data to/from SAS Stored Processes'; put '@details This macro should be added to the start of each Stored Process,'; put '**immediately** followed by a call to:'; put '%mm_webout(FETCH)'; put 'This will read all the input data and create same-named SAS datasets in the'; put 'WORK library. You can then insert your code, and send data back using the'; put 'following syntax:'; put 'data some datasets; * make some data ;'; put 'retain some columns;'; put 'run;'; put '%mm_webout(OPEN)'; put '%mm_webout(ARR,some) * Array format, fast, suitable for large tables ;'; put '%mm_webout(OBJ,datasets) * Object format, easier to work with ;'; put 'Finally, wrap everything up send some helpful system variables too'; put '%mm_webout(CLOSE)'; put '@param [in] action Either FETCH, OPEN, ARR, OBJ or CLOSE'; put '@param [in] ds The dataset to send back to the frontend'; put '@param [out] dslabel= Value to use instead of table name for sending to JSON'; put '@param [in] fmt= (N) Setting Y converts all vars to their formatted values'; put '@param [out] fref= (_webout) The fileref to which to write the JSON'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] workobs= (0) When set to a positive integer, will create a new'; put 'output object (WORK) which contains this number of observations from all'; put 'tables in the WORK library.'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to output JSON'; put '

SAS Macros

'; put '@li mp_jsonout.sas'; put '

Related Macros

'; put '@li ms_webout.sas'; put '@li mv_webout.sas'; put '@version 9.3'; put '@author Allan Bowe'; put '**/'; put '%macro mm_webout(action,ds,dslabel=,fref=_webout,fmt=N,missing=NULL'; put ',showmeta=N,maxobs=MAX,workobs=0'; put ');'; put '%global _webin_file_count _webin_fileref1 _webin_name1 _program _debug'; put 'sasjs_tables;'; put '%local i tempds jsonengine;'; put '/* see https://github.com/sasjs/core/issues/41 */'; put '%if "%upcase(&SYSENCODING)" ne "UTF-8" %then %let jsonengine=PROCJSON;'; put '%else %let jsonengine=DATASTEP;'; put '%if &action=FETCH %then %do;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'options mprint notes mprintnest;'; put '%end;'; put '%let _webin_file_count=%eval(&_webin_file_count+0);'; put '/* now read in the data */'; put '%do i=1 %to &_webin_file_count;'; put '%if &_webin_file_count=1 %then %do;'; put '%let _webin_fileref1=&_webin_fileref;'; put '%let _webin_name1=&_webin_name;'; put '%end;'; put 'data _null_;'; put 'infile &&_webin_fileref&i termstr=crlf;'; put 'input;'; put 'call symputx(''input_statement'',_infile_);'; put 'putlog "&&_webin_name&i input statement: " _infile_;'; put 'stop;'; put 'data &&_webin_name&i;'; put 'infile &&_webin_fileref&i firstobs=2 dsd termstr=crlf encoding=''utf-8'';'; put 'input &input_statement;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'if _n_<20 then putlog _infile_;'; put '%end;'; put 'run;'; put '%let sasjs_tables=&sasjs_tables &&_webin_name&i;'; put '%end;'; put '%end;'; put '%else %if &action=OPEN %then %do;'; put '/* fix encoding */'; put 'OPTIONS NOBOMFILE;'; put '/**'; put '* check xengine type to avoid the below err message:'; put '* > Function is only valid for filerefs using the CACHE access method.'; put '*/'; put 'data _null_;'; put 'set sashelp.vextfl(where=(fileref="_WEBOUT"));'; put 'if xengine=''STREAM'' then do;'; put 'rc=stpsrv_header(''Content-type'',"text/html; encoding=utf-8");'; put 'end;'; put 'run;'; put '/* setup json */'; put 'data _null_;file &fref encoding=''utf-8'';'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutBEGIN<<'';'; put '%end;'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'run;'; put '%end;'; put '%else %if &action=ARR or &action=OBJ %then %do;'; put '%mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt,jref=&fref'; put ',engine=&jsonengine,missing=&missing,showmeta=&showmeta,maxobs=&maxobs'; put ')'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put '/* To avoid issues with _webout on EBI we use a temporary file */'; put 'filename _sjsref temp lrecl=131068;'; put '%if %str(&workobs) > 0 %then %do;'; put '/* if debug mode, send back first XX records of each work table also */'; put 'data;run;%let tempds=%scan(&syslast,2,.);'; put 'ods output Members=&tempds;'; put 'proc datasets library=WORK memtype=data;'; put '%local wtcnt;%let wtcnt=0;'; put 'data _null_;'; put 'set &tempds;'; put 'if not (upcase(name) =:"DATA"); /* ignore temp datasets */'; put 'i+1;'; put 'call symputx(cats(''wt'',i),name,''l'');'; put 'call symputx(''wtcnt'',i,''l'');'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put ",""WORK"":{";'; put '%do i=1 %to &wtcnt;'; put '%let wt=&&wt&i;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'dsid=open("WORK.&wt",''is'');'; put 'nlobs=attrn(dsid,''NLOBS'');'; put 'nvars=attrn(dsid,''NVARS'');'; put 'rc=close(dsid);'; put 'if &i>1 then put '',''@;'; put 'put " ""&wt"" : {";'; put 'put ''"nlobs":'' nlobs;'; put 'put '',"nvars":'' nvars;'; put '%mp_jsonout(OBJ,&wt,jref=_sjsref,dslabel=first10rows,showmeta=Y'; put ',maxobs=&workobs'; put ')'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put '%end;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put 'run;'; put '%end;'; put '/* close off json */'; put 'data _null_;file _sjsref mod encoding=''utf-8'';'; put 'length SYSPROCESSNAME syserrortext syswarningtext autoexec $512;'; put 'put ",""_DEBUG"" : ""&_debug"" ";'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'autoexec=quote(urlencode(trim(getoption(''autoexec''))));'; put 'put '',"AUTOEXEC" : '' autoexec;'; put 'put ",""MF_GETUSER"" : ""%mf_getuser()"" ";'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'put ",""SYSENCODING"" : ""&sysencoding"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSPROCESSID"" : ""&SYSPROCESSID"" ";'; put 'put ",""SYSPROCESSMODE"" : ""&SYSPROCESSMODE"" ";'; put 'SYSPROCESSNAME=quote(urlencode(cats(SYSPROCESSNAME)));'; put 'put ",""SYSPROCESSNAME"" : " SYSPROCESSNAME;'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put '',"SYSWARNINGTEXT" : '' syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'length memsize $32;'; put 'memsize="%sysfunc(INPUTN(%sysfunc(getoption(memsize)), best.),sizekmg.)";'; put 'memsize=quote(cats(memsize));'; put 'put '',"MEMSIZE" : '' memsize;'; put 'put "}" @;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutEND<<'';'; put '%end;'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjsref lrecl=1 recfm=n;'; put 'file &fref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjsref clear;'; put '%end;'; put '%mend mm_webout;'; put '%macro webout(action,ds,dslabel=,fmt=,missing=NULL,showmeta=NO,maxobs=MAX);'; put '%mm_webout(&action,ds=&ds,dslabel=&dslabel,fmt=&fmt'; put ',missing=&missing'; put ',showmeta=&showmeta'; put ',maxobs=&maxobs'; put ') %mend;'; put '/* provide additional debug info */'; put '%global _program;'; put '%put &=syscc;'; put '%put user=%mf_getuser();'; put '%put pgm=&_program;'; put '%put timestamp=%sysfunc(datetime(),datetime19.);'; put '* Service Variables start;'; put '* Service Variables end;'; put '* SAS Macros start;'; put '%macro mf_getapploc(pgm);'; put '%if "&pgm"="" %then %do;'; put '%if %symexist(_program) %then %let pgm=&_program;'; put '%else %do;'; put '%put &sysmacroname: No value provided and no _program variable available;'; put '%return;'; put '%end;'; put '%end;'; put '%local root;'; put '/**'; put '* First check we are not in the tests/macros folder (which has no subfolders)'; put '* or specifically in the testsetup or testteardown services'; put '*/'; put '%if %index(&pgm,/tests/macros/)'; put 'or %index(&pgm,/tests/testsetup)'; put 'or %index(&pgm,/tests/testteardown)'; put '%then %do;'; put '%let root=%substr(&pgm,1,%index(&pgm,/tests)-1);'; put '&root'; put '%return;'; put '%end;'; put '/**'; put '* Next, move up two levels to avoid matches on subfolder or service name'; put '*/'; put '%let root=%substr(&pgm,1,%length(&pgm)-%length(%scan(&pgm,-1,/))-1);'; put '%let root=%substr(&root,1,%length(&root)-%length(%scan(&root,-1,/))-1);'; put '%if %index(&root,/tests/) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/tests/)-1);'; put '%end;'; put '%else %if %index(&root,/services) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/services)-1);'; put '%end;'; put '%else %if %index(&root,/jobs) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/jobs)-1);'; put '%end;'; put '%else %put &sysmacroname: Could not find an app location from &pgm;'; put '&root'; put '%mend mf_getapploc ;'; put '%macro mf_getuniquefileref(prefix=_,maxtries=1000,lrecl=32767);'; put '%local rc fname;'; put '%if &prefix=0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%end;'; put '%else %do;'; put '%local x len;'; put '%let len=%eval(8-%length(&prefix));'; put '%let x=0;'; put '%do x=0 %to &maxtries;'; put '%let fname=&prefix%substr(%sysfunc(ranuni(0)),3,&len);'; put '%if %sysfunc(fileref(&fname)) > 0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%return;'; put '%end;'; put '%end;'; put '%put unable to find available fileref after &maxtries attempts;'; put '%end;'; put '%mend mf_getuniquefileref;'; put '%macro mp_abort(mac=mp_abort.sas, type=, msg=, iftrue=%str(1=1)'; put ', errds=work.mp_abort_errds'; put ', mode=REGULAR'; put ')/*/STORE SOURCE*/;'; put '%global sysprocessmode sysprocessname sasjs_stpsrv_header_loc sasjsprocessmode;'; put '%local fref fid i;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%put NOTE: /// mp_abort macro executing //;'; put '%if %length(&mac)>0 %then %put NOTE- called by &mac;'; put '%put NOTE - &msg;'; put '%if %symexist(_SYSINCLUDEFILEDEVICE)'; put '/* abort cancel FILE does not restart outside the INCLUDE on Viya 3.5 */'; put 'and %superq(SYSPROCESSNAME) ne %str(Compute Server)'; put '%then %do;'; put '%if "*&_SYSINCLUDEFILEDEVICE*" ne "**" %then %do;'; put 'data &errds;'; put 'iftrue=''1=1'';'; put 'length mac $100 msg $5000;'; put 'mac=symget(''mac'');'; put 'msg=symget(''msg'');'; put 'run;'; put 'data _null_;'; put 'abort cancel FILE;'; put 'run;'; put '%return;'; put '%end;'; put '%end;'; put '/* Web App Context */'; put '%if %symexist(_PROGRAM)'; put 'or %superq(SYSPROCESSNAME) = %str(Compute Server)'; put 'or &mode=INCLUDE'; put '%then %do;'; put 'options obs=max replace mprint;'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5"'; put '%then %do;'; put 'options nosyntaxcheck;'; put '%end;'; put '%if &mode=INCLUDE %then %do;'; put '%if %sysfunc(exist(&errds))=1 %then %do;'; put 'data _null_;'; put 'set &errds;'; put 'call symputx(''iftrue'',iftrue,''l'');'; put 'call symputx(''mac'',mac,''l'');'; put 'call symputx(''msg'',msg,''l'');'; put 'putlog (_all_)(=);'; put 'run;'; put '%if (&iftrue)=0 %then %return;'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: No include errors found;'; put '%return;'; put '%end;'; put '%end;'; put '/* extract log errs / warns, if exist */'; put '%local logloc logline;'; put '%global logmsg; /* capture global messages */'; put '%if %symexist(SYSPRINTTOLOG) %then %let logloc=&SYSPRINTTOLOG;'; put '%else %let logloc=%qsysfunc(getoption(LOG));'; put 'proc printto log=log;run;'; put '%let logline=0;'; put '%if %length(&logloc)>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input; putlog _infile_;'; put 'i=1;'; put 'retain logonce 0;'; put 'if ('; put '_infile_=:"%str(WARN)ING" or _infile_=:"%str(ERR)OR"'; put ') and logonce=0 then'; put 'do;'; put 'call symputx(''logline'',_n_);'; put 'logonce+1;'; put 'end;'; put 'run;'; put '/* capture log including lines BEFORE the err */'; put '%if &logline>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input;'; put 'i=1;'; put 'stoploop=0;'; put 'if _n_ ge &logline-15 and stoploop=0 then do until (i>22);'; put 'call symputx(''logmsg'',catx(''\n'',symget(''logmsg''),_infile_));'; put 'input;'; put 'i+1;'; put 'stoploop=1;'; put 'end;'; put 'if stoploop=1 then stop;'; put 'run;'; put '%end;'; put '%end;'; put '%if %symexist(SYS_JES_JOB_URI) %then %do;'; put '/* setup webout for Viya */'; put 'options nobomfile;'; put '%if "X&SYS_JES_JOB_URI.X"="XX" %then %do;'; put 'filename _webout temp lrecl=999999 mod;'; put '%end;'; put '%else %do;'; put 'filename _webout filesrvc parenturi="&SYS_JES_JOB_URI"'; put 'name="_webout.json" lrecl=999999 mod;'; put '%end;'; put '%end;'; put '%else %if %sysfunc(filename(fref,&sasjs_stpsrv_header_loc))=0 %then %do;'; put 'options nobomfile;'; put '/* set up http header for SASjs Server */'; put '%let fid=%sysfunc(fopen(&fref,A));'; put '%if &fid=0 %then %do;'; put '%put %str(ERR)OR: %sysfunc(sysmsg());'; put '%return;'; put '%end;'; put '%let rc=%sysfunc(fput(&fid,%str(Content-Type: application/json)));'; put '%let rc=%sysfunc(fwrite(&fid));'; put '%let rc=%sysfunc(fclose(&fid));'; put '%let rc=%sysfunc(filename(&fref));'; put '%end;'; put '/* send response in SASjs JSON format */'; put 'data _null_;'; put 'file _webout mod lrecl=32000 encoding=''utf-8'';'; put 'length msg syswarningtext syserrortext $32767 mode $10 ;'; put 'sasdatetime=datetime();'; put 'msg=symget(''msg'');'; put '%if &logline>0 %then %do;'; put 'msg=cats(msg,''\n\nLog Extract:\n'',symget(''logmsg''));'; put '%end;'; put '/* escape the escapes */'; put 'msg=tranwrd(msg,''\'',''\\'');'; put '/* escape the quotes */'; put 'msg=tranwrd(msg,''"'',''\"'');'; put '/* ditch the CRLFs as chrome complains */'; put 'msg=compress(msg,,''kw'');'; put '/* quote without quoting the quotes (which are escaped instead) */'; put 'msg=cats(''"'',msg,''"'');'; put 'if symexist(''_debug'') then debug=quote(trim(symget(''_debug'')));'; put 'else debug=''""'';'; put 'if symget(''sasjsprocessmode'')=''Stored Program'' then mode=''SASJS'';'; put 'if mode ne ''SASJS'' then put ''>>weboutBEGIN<<'';'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'put '',"sasjsAbort" : [{'';'; put 'put '' "MSG":'' msg ;'; put 'put '' ,"MAC": "'' "&mac" ''"}]'';'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'put '',"_DEBUG":'' debug ;'; put 'if symexist(''_metauser'') then do;'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put 'end;'; put 'if symexist(''SYS_JES_JOB_URI'') then do;'; put 'SYS_JES_JOB_URI=quote(trim(symget(''SYS_JES_JOB_URI'')));'; put 'put '',"SYS_JES_JOB_URI": '' SYS_JES_JOB_URI;'; put 'end;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put ",""SYSWARNINGTEXT"" : " syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'put "}" ;'; put 'if mode ne ''SASJS'' then put ''>>weboutEND<<'';'; put 'run;'; put '%put _all_;'; put '%if "&sysprocessmode " = "SAS Stored Process Server " %then %do;'; put 'data _null_;'; put 'putlog ''stpsrvset program err and syscc'';'; put 'rc=stpsrvset(''program error'', 0);'; put 'call symputx("syscc",0,"g");'; put 'run;'; put '%if &sysscp=WIN'; put 'and 1=0 /* deprecating this logic until we figure out a consistent abort */'; put 'and "%substr(%str(&sysvlong ),1,8)"="9.04.01M"'; put 'and "%substr(%str(&sysvlong ),9,1)">"5" %then %do;'; put '/* skip approach (below) does not work in windows m6+ envs */'; put 'endsas;'; put '%end;'; put '%else %do;'; put '/**'; put '* endsas kills 9.4m3 deployments by orphaning multibridges.'; put '* Abort variants are ungraceful (non zero return code)'; put '* This approach lets SAS run silently until the end :-)'; put '* Caution - fails when called within a %include within a macro'; put '* Use mp_include() to handle this.'; put '*/'; put 'filename skip temp;'; put 'data _null_;'; put 'file skip;'; put 'put ''%macro skip();'';'; put 'comment ''%mend skip; -> fix lint '';'; put 'put ''%macro skippy();'';'; put 'comment ''%mend skippy; -> fix lint '';'; put 'run;'; put '%inc skip;'; put '%end;'; put '%end;'; put '%else %if "&sysprocessmode " = "SAS Compute Server " %then %do;'; put '/* endsas kills the session making it harder to fetch results */'; put 'data _null_;'; put 'syswarningtext=symget(''syswarningtext'');'; put 'syserrortext=symget(''syserrortext'');'; put 'abort_msg=symget(''msg'');'; put 'syscc=symget(''syscc'');'; put 'sysuserid=symget(''sysuserid'');'; put 'iftrue=symget(''iftrue'');'; put 'put (_all_)(/=);'; put 'call symputx(''syscc'',0);'; put 'abort cancel nolist;'; put 'run;'; put '%end;'; put '%else %do;'; put '%abort cancel;'; put '%end;'; put '%end;'; put '%else %do;'; put '%put _all_;'; put '%abort cancel;'; put '%end;'; put '%mend mp_abort;'; put '/** @endcond */'; put '%macro mm_getstpcode('; put 'tree=/User Folders/sasdemo/somestp'; put ',name='; put ',outloc=0'; put ',outref=0'; put ',mDebug=1'; put ',showlog=NO'; put ');'; put '%local mD;'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing &sysmacroname..sas;'; put '%&mD.put _local_;'; put '%if %length(&name)>0 %then %let name=/&name;'; put '/* first, check if STP exists */'; put '%local tsuri;'; put '%let tsuri=stopifempty ;'; put 'data _null_;'; put 'format type uri tsuri value $200.;'; put 'call missing (of _all_);'; put 'path="&tree&name(StoredProcess)";'; put '/* first, find the STP ID */'; put 'if metadata_pathobj("",path,"StoredProcess",type,uri)>0 then do;'; put '/* get sourcecode */'; put 'cnt=1;'; put 'do while (metadata_getnasn(uri,"Notes",cnt,tsuri)>0);'; put 'rc=metadata_getattr(tsuri,"Name",value);'; put '&mD.put tsuri= value=;'; put 'if value="SourceCode" then do;'; put '/* found it! */'; put 'rc=metadata_getattr(tsuri,"Id",value);'; put 'call symputx(''tsuri'',value,''l'');'; put 'stop;'; put 'end;'; put 'cnt+1;'; put 'end;'; put 'end;'; put 'else put (_all_)(=);'; put 'run;'; put '%mp_abort(iftrue= (&tsuri=stopifempty)'; put ',mac=mm_getstpcode'; put ',msg=%str(&tree&name.(StoredProcess) not found!)'; put ')'; put '/**'; put '* Now we can extract the textstore'; put '*/'; put 'filename __getdoc temp lrecl=10000000;'; put 'proc metadata'; put 'in="$METAREPOSITORY'; put ''; put 'SAS1"'; put 'out=__getdoc ;'; put 'run;'; put '/* find the beginning of the text */'; put '%local start;'; put 'data _null_;'; put 'infile __getdoc lrecl=10000;'; put 'input;'; put 'start=index(_infile_,''StoredText="'');'; put 'if start then do;'; put 'call symputx("start",start+11);'; put '*putlog ''"'' _infile_ ''"'';'; put 'end;'; put 'stop;'; put '%local outeng;'; put '%if "&outloc"="0" %then %let outeng=TEMP;'; put '%else %let outeng="&outloc";'; put '%local fref;'; put '%if &outref=0 %then %let fref=%mf_getuniquefileref();'; put '%else %let fref=&outref;'; put '/* read the content, byte by byte, resolving escaped chars */'; put 'filename &fref &outeng lrecl=100000;'; put 'data _null_;'; put 'length filein 8 fileid 8;'; put 'filein = fopen("__getdoc","I",1,"B");'; put 'fileid = fopen("&fref","O",1,"B");'; put 'rec = "20"x;'; put 'length entity $6;'; put 'do while(fread(filein)=0);'; put 'x+1;'; put 'if x>&start then do;'; put 'rc = fget(filein,rec,1);'; put 'if rec=''"'' then leave;'; put 'else if rec="&" then do;'; put 'entity=rec;'; put 'do until (rec=";");'; put 'if fread(filein) ne 0 then goto getout;'; put 'rc = fget(filein,rec,1);'; put 'entity=cats(entity,rec);'; put 'end;'; put 'select (entity);'; put 'when (''&'' ) rec=''&'' ;'; put 'when (''<'' ) rec=''<'' ;'; put 'when (''>'' ) rec=''>'' ;'; put 'when (''''') rec="''" ;'; put 'when (''"'') rec=''"'' ;'; put 'when ('' '') rec=''0A''x;'; put 'when ('' '') rec=''0D''x;'; put 'when (''$'' ) rec=''$'' ;'; put 'when ('' '') rec=''09''x;'; put 'otherwise putlog "%str(WARN)ING: missing value for " entity=;'; put 'end;'; put 'rc =fput(fileid, substr(rec,1,1));'; put 'rc =fwrite(fileid);'; put 'end;'; put 'else do;'; put 'rc =fput(fileid,rec);'; put 'rc =fwrite(fileid);'; put 'end;'; put 'end;'; put 'end;'; put 'getout:'; put 'rc=fclose(filein);'; put 'rc=fclose(fileid);'; put 'run;'; put '%if &showlog=YES %then %do;'; put 'data _null_;'; put 'infile &fref lrecl=32767 end=last;'; put 'input;'; put 'if _n_=1 then putlog ''>>stpcodeBEGIN<<'';'; put 'putlog _infile_;'; put 'if last then putlog ''>>stpcodeEND<<'';'; put 'run;'; put '%end;'; put 'filename __getdoc clear;'; put '%if &outref=0 %then %do;'; put 'filename &fref clear;'; put '%end;'; put '%mend mm_getstpcode;'; put '%macro dc_getsettings();'; put '%global _program;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(syscc=&syscc on entry (&syswarningtext &syserrortext))'; put ')'; put '%if %length(&_PROGRAM)>1 %then %let root=&_program;'; put '%else %do;'; put '%global _metauser;'; put '%let _metauser=&sysuserid;'; put '/* to mimic a "real" _program we need to give a dummy role and stp name */'; put '%let root=/dummyRole/dummyName;'; put '%end;'; put '/* the DC precode is stored in the Admin folder in the root of'; put 'the project. Lets find that root. */'; put '%put &=root;'; put '%let root=%mf_getapploc();'; put '%put &=root;'; put '/* Now we know the root location we can retrieve the params */'; put '%let temploc=%sysfunc(pathname(work))/settings.txt;'; put '%mm_getstpcode(tree=&root/services/public'; put ',name=Data_Controller_Settings'; put ',outloc=&temploc'; put ')'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Unable to run getstpcode)'; put ')'; put 'filename _getsets "&temploc" lrecl=2000;'; put '/*'; put 'Do not use mp_include here - this puts a copy in every service, which creates'; put 'compilation problems when calling services from mp_include'; put '*/'; put '%inc _getsets/source2;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Problem running &sysmacroname (&syswarningtext &syserrortext))'; put ')'; put '%mend dc_getsettings;'; put '%macro mf_fmtdttm('; put ')/*/STORE SOURCE*/;'; put '%if "&sysver"="9.2" or "&sysver"="9.3"'; put 'or ("&sysver"="9.4" and "%substr(&SYSVLONG,9,1)" le "3")'; put 'or "%substr(&sysver,1,1)"="4"'; put 'or "%substr(&sysver,1,1)"="5"'; put '%then %do;DATETIME19.3%end;'; put '%else %do;E8601DT26.6%end;'; put '%mend mf_fmtdttm;'; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '%macro mp_init(prefix=SASJS'; put ')/*/STORE SOURCE*/;'; put '%if %symexist(SASJS_PREFIX) %then %return; /* only run once */'; put '%global'; put 'SASJS_PREFIX /* the ONLY hard-coded global macro variable in SASjs */'; put '&prefix._FUNCTIONS /* used in mcf_init() to track core function compilation */'; put '&prefix._INIT_NUM /* initialisation time as numeric */'; put '&prefix._INIT_DTTM /* initialisation time in E8601DT26.6 format */'; put '&prefix.WORK /* avoid typing %sysfunc(pathname(work)) every time */'; put ';'; put '%let sasjs_prefix=&prefix;'; put 'data _null_;'; put 'dttm=datetime();'; put 'call symputx("&sasjs_prefix._init_num",dttm,''g'');'; put 'call symputx("&sasjs_prefix._init_dttm",put(dttm,E8601DT26.6),''g'');'; put 'call symputx("&sasjs_prefix.work",pathname(''WORK''),''g'');'; put 'run;'; put 'options'; put 'compress=CHAR /* default is none so ensure we have something! */'; put 'datastmtchk=ALLKEYWORDS /* protection from overwriting input datasets */'; put 'errorcheck=STRICT /* catch errs in libname/filename statements */'; put 'fmterr /* ensure err when a format cannot be found */'; put 'mergenoby=%str(ERR)OR /* throw err when a merge has no BY variables */'; put 'missing=. /* changing this can cause hard to detect errs */'; put 'noquotelenmax /* avoid warnings for long strings */'; put 'noreplace /* avoid overwriting permanent datasets */'; put 'ps=max /* reduce log size slightly */'; put 'ls=max /* reduce log even more and avoid word truncation */'; put 'validmemname=COMPATIBLE /* avoid special characters etc in table names */'; put 'validvarname=V7 /* avoid special characters etc in variable names */'; put 'varinitchk=%str(ERR)OR /* avoid data mistakes from variable name typos */'; put 'varlenchk=%str(ERR)OR /* fail hard if truncation (data loss) can result */'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5" %then %do;'; put 'noautocorrect /* disallow misspelled procedure names */'; put 'dsoptions=note2err /* undocumented - convert bad NOTEs to ERRs */'; put '%end;'; put ';'; put '%mend mp_init;'; put '%macro mpeinit(fetch=YES);'; put '%global mpeinit'; put 'mpeadmins /* group with unrestricted Meditor access */'; put 'mpelocapprovals /* location for landing and staging files */'; put 'mpelib /* location of configuration tables for DC */'; put 'dc_repo_users /* location of user / group metadata */'; put 'dc_licence_key /* extracted in dc_getsettings */'; put 'dc_activation_key /* extracted in dc_getsettings */'; put 'dc_locale /* extracted in dc_getsettings */'; put 'dc_dttmtfmt /* can be overridden in dc_getsettings */'; put '_debug'; put ';'; put '%if &mpeinit=1 %then %return;'; put '%else %let mpeinit=1;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(Problem on service startup (&syswarningtext &syserrortext))'; put ')'; put '%mp_init()'; put '%if &fetch=YES %then %do;'; put '%webout(FETCH)'; put '%end;'; put '%global _CLIENTNAME;'; put '%mp_abort(iftrue= (&_CLIENTNAME=SAS Enterprise Guide)'; put ',mac=&_program..sas'; put ',msg=%str(Data Controller is a web app and should not be executed from EG)'; put ')'; put 'options urlencoding=utf8 nobomfile lrecl=32767;'; put '%let perf=%sysfunc(datetime());'; put '%put perfdiff: 0;'; put '%let dc_locale=SYSTEM; /* default if not set */'; put '/**'; put '* E8601DT26.6 has widest database support - but not all SAS flavours can'; put '* handle it. Override in the settings STP if needed.'; put '*/'; put 'data _null_;'; put 'dc_dttmtfmt=''"%sysfunc(datetime(),''!!"%mf_fmtdttm()"!!'')"dt'';'; put 'call symputx(''dc_dttmtfmt'',dc_dttmtfmt);'; put 'put dc_dttmtfmt=;'; put 'run;'; put '%put &=dc_dttmtfmt;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc prior to dc_getsettings)'; put ')'; put '%dc_getsettings()'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc after dc_getsettings)'; put ')'; put 'data _null_;'; put 'set &DC_LIBREF..mpe_config(where=('; put 'var_scope="DC"'; put 'and &dc_dttmtfmt lt tx_to'; put 'and var_active=1'; put '));'; put 'call symputx(var_name,var_value,''G'');'; put 'putlog var_name "=" var_value;'; put 'run;'; put '%let mpelib=&dc_libref;'; put '%let mpeadmins=&dc_admin_group;'; put '%let mpelocapprovals=&dc_staging_area;'; put '%let dc_repo_users=&dc_repo_users;'; put '%if &dc_locale ne SYSTEM %then %do;'; put 'options locale=&dc_locale;'; put '%end;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(Problem during compilation or with STP precode (&syswarningtext))'; put ')'; put '%mend mpeinit;'; put '%macro mf_mval(var);'; put '%if %symexist(&var) %then %do;'; put '%superq(&var)'; put '%end;'; put '%mend mf_mval;'; put '%macro mf_trimstr(basestr,trimstr);'; put '%local baselen trimlen trimval;'; put '/* return if basestr is shorter than trimstr (or 0) */'; put '%let baselen=%length(%superq(basestr));'; put '%let trimlen=%length(%superq(trimstr));'; put '%if &baselen < &trimlen or &baselen=0 %then %return;'; put '/* obtain the characters from the end of basestr */'; put '%let trimval=%qsubstr(%superq(basestr)'; put ',%length(%superq(basestr))-&trimlen+1'; put ',&trimlen);'; put '/* compare and if matching, chop it off! */'; put '%if %superq(basestr)=%superq(trimstr) %then %do;'; put '%return;'; put '%end;'; put '%else %if %superq(trimval)=%superq(trimstr) %then %do;'; put '%qsubstr(%superq(basestr),1,%length(%superq(basestr))-&trimlen)'; put '%end;'; put '%else %do;'; put '&basestr'; put '%end;'; put '%mend mf_trimstr;'; put '%macro mf_getplatform(switch'; put ')/*/STORE SOURCE*/;'; put '%local a b c;'; put '%if &switch.NONE=NONE %then %do;'; put '%if %symexist(sasjsprocessmode) %then %do;'; put '%if &sasjsprocessmode=Stored Program %then %do;'; put 'SASJS'; put '%return;'; put '%end;'; put '%end;'; put '%if %symexist(sysprocessmode) %then %do;'; put '%if "&sysprocessmode"="SAS Object Server"'; put 'or "&sysprocessmode"= "SAS Compute Server" %then %do;'; put 'SASVIYA'; put '%end;'; put '%else %if "&sysprocessmode"="SAS Stored Process Server"'; put 'or "&sysprocessmode"="SAS Workspace Server"'; put '%then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if %symexist(_metaport) or %symexist(_metauser) %then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if &switch=SASSTUDIO %then %do;'; put '/* return the version of SAS Studio else 0 */'; put '%if %mf_mval(_CLIENTAPP)=%str(SAS Studio) %then %do;'; put '%let a=%mf_mval(_CLIENTVERSION);'; put '%let b=%scan(&a,1,.);'; put '%if %eval(&b >2) %then %do;'; put '&b'; put '%end;'; put '%else 0;'; put '%end;'; put '%else 0;'; put '%end;'; put '%else %if &switch=VIYARESTAPI %then %do;'; put '%mf_trimstr(%sysfunc(getoption(servicesbaseurl)),/)'; put '%end;'; put '%mend mf_getplatform;'; put '%macro mpeterm();'; put '%local oldloc;'; put 'data _null_;'; put 'if symexist(''SYSPRINTTOLOG'') then oldloc=symget(''SYSPRINTTOLOG'');'; put 'else oldloc=getoption(''LOG'');'; put 'if subpad(oldloc,1,1) not in (''"'',"''",'' '') then oldloc=quote(cats(oldloc));'; put 'call symputx(''oldloc'',oldloc,''l'');'; put 'run;'; put '%if %length(&oldloc)>0 %then %do;'; put 'proc printto log=log;'; put 'run;'; put 'data _null_;'; put 'infile &oldloc;'; put 'input; putlog _infile_;'; put 'run;'; put '%end;'; put '%if %sysfunc(exist(&dc_libref..mpe_requests)) and %mf_getplatform() ne SASVIYA'; put '%then %do;'; put 'data ;'; put 'if 0 then set &dc_libref..mpe_requests;'; put 'request_dttm=%sysfunc(datetime());'; put 'request_user="%mf_getuser()";'; put 'request_service="%scan(&_program,-2,/)/%scan(&_program,-1,/)";'; put 'request_params='''';'; put 'output;stop;'; put 'proc append base=&dc_libref..mpe_requests data=&syslast force nowarn;'; put 'run;'; put '%end;'; put '%mend mpeterm;'; put '%macro mm_getGroups('; put 'user='; put ',outds=work.mm_getGroups'; put ',repo=foundation'; put ',mDebug=0'; put ')/*/STORE SOURCE*/;'; put '%local mD oldrepo;'; put '%let oldrepo=%sysfunc(getoption(metarepository));'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing mm_getGroups.sas;'; put '%&mD.put _local_;'; put '/* on some sites, user / group info is in a different metadata repo to the'; put 'default */'; put '%if &oldrepo ne &repo %then %do;'; put 'options metarepository=&repo;'; put '%end;'; put '%if %length(&user)=0 %then %do;'; put 'data &outds (keep=groupuri groupname groupdesc);'; put 'length groupuri groupname groupdesc group_or_role $256;'; put 'call missing(of _all_);'; put 'i+1;'; put 'do while'; put '(metadata_getnobj("omsobj:IdentityGroup?@Id contains ''.''",i,groupuri)>0);'; put 'rc=metadata_getattr(groupuri, "Name", groupname);'; put 'rc=metadata_getattr(groupuri, "Desc", groupdesc);'; put 'rc=metadata_getattr(groupuri,"PublicType",group_or_role);'; put 'if Group_or_Role = ''UserGroup'' then output;'; put 'i+1;'; put 'end;'; put 'run;'; put '%end;'; put '%else %do;'; put 'data &outds (keep=groupuri groupname groupdesc);'; put 'length uri groupuri groupname groupdesc group_or_role $256;'; put 'call missing(of _all_);'; put 'rc=metadata_getnobj("omsobj:Person?@Name=''&user''",1,uri);'; put 'if rc<=0 then do;'; put 'putlog "%str(WARN)ING: rc=" rc "&user not found "'; put '", or there was an issue reading the repository.";'; put 'stop;'; put 'end;'; put 'a=1;'; put 'grpassn=metadata_getnasn(uri,"IdentityGroups",a,groupuri);'; put 'if grpassn in (-3,-4) then do;'; put 'putlog "%str(WARN)ING: No metadata groups found for &user";'; put 'output;'; put 'end;'; put 'else do while (grpassn > 0);'; put 'rc=metadata_getattr(groupuri, "Name", groupname);'; put 'rc=metadata_getattr(groupuri, "Desc", groupdesc);'; put 'a+1;'; put 'rc=metadata_getattr(groupuri,"PublicType",group_or_role);'; put 'if Group_or_Role = ''UserGroup'' then output;'; put 'grpassn=metadata_getnasn(uri,"IdentityGroups",a,groupuri);'; put 'end;'; put 'run;'; put '%end;'; put '%if &oldrepo ne &repo %then %do;'; put 'options metarepository=&oldrepo;'; put '%end;'; put '%mend mm_getGroups;'; put '%macro mm_getrepos('; put 'outds=work.mm_getrepos'; put ')/*/STORE SOURCE*/;'; put '* use a temporary fileref to hold the response;'; put 'filename response temp;'; put '/* get list of libraries */'; put 'proc metadata in='; put '"1"'; put 'out=response;'; put 'run;'; put '/* write the response to the log for debugging */'; put '/*'; put 'data _null_;'; put 'infile response lrecl=1048576;'; put 'input;'; put 'put _infile_;'; put 'run;'; put '*/'; put '/* create an XML map to read the response */'; put 'filename sxlemap temp;'; put 'data _null_;'; put 'file sxlemap;'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository";'; put 'put "";'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Id";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Name";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Desc";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@DefaultNS";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@RepositoryType";'; put 'put "characterstring20";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@RepositoryFormat";'; put 'put "characterstring10";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@Access";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@CurrentAccess";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@PauseState";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Path";'; put 'put "";'; put 'put "characterstring256";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Engine";'; put 'put "";'; put 'put "characterstring8";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Options";'; put 'put "";'; put 'put "characterstring32";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@MetadataCreated";'; put 'put "characterstring24";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@MetadataUpdated";'; put 'put "characterstring24";'; put 'put '''';'; put 'put ''
'';'; put 'run;'; put 'libname _XML_ xml xmlfileref=response xmlmap=sxlemap;'; put 'proc sort data= _XML_.SASRepos out=&outds;'; put 'by name;'; put 'run;'; put '/* clear references */'; put 'filename sxlemap clear;'; put 'filename response clear;'; put 'libname _XML_ clear;'; put '%mend mm_getrepos;'; put '%macro dc_getgroups(outds=mm_getlibs);'; put '%local repo repocnt x;'; put '%let repo=%sysfunc(getoption(metarepository));'; put '/* get list of repositories and filter */'; put '%mm_getrepos(outds=repos)'; put '%let repocnt=0;'; put 'data repos;'; put 'set repos;'; put 'where repositorytype in(''CUSTOM'',''FOUNDATION'')'; put '& upcase(name) ne "%upcase(&repo)";'; put 'keep id name ;'; put 'call symputx(cats(''repo'',_n_),name,''l'');'; put 'call symputx(''repocnt'',_n_,''l'');'; put 'run;'; put '%put _local_;'; put '%mm_getgroups(outds=&outds,repo=foundation)'; put '%do x=1 %to &repocnt;'; put '%mm_getgroups(outds=&outds.a, repo=&&repo&x)'; put 'proc append base=&outds data=&outds.a;'; put 'run;'; put '%end;'; put 'proc sort data=&outds noduprec;'; put 'by groupname;'; put 'run;'; put '%mend dc_getgroups;'; put '* SAS Macros end;'; put '* SAS Includes start;'; put '* SAS Includes end;'; put '* Binary Files start;'; put '* Binary Files end;'; put '* ServiceInit start;'; put 'options noquotelenmax ps=max;'; put '/* create dummy macros to prevent stpbegin / stpend from corrupting sessions */'; put '%macro stpbegin();'; put '%put NOTE: the STPBEGIN macro should not be used for web apps!;'; put '%mend stpbegin;'; put '%macro stpend();'; put '%put NOTE: the STPEND macro should not be used for web apps!;'; put '%mend stpend;'; put '* ServiceInit end;'; put '* Service start;'; put '/**'; put '@file getgroups.sas'; put '@brief List all SAS Groups'; put '@details Gets a list of all SAS Groups. Runs without mpeinit() so that it'; put 'can be available to the sasjs/server configurator'; put '

SAS Macros

'; put '@li dc_getgroups.sas'; put '

Data Outputs

'; put '
groups
'; put '|NAME:$32.|DESCRIPTION:$64.|GROUPID:best.|'; put '|---|---|---|'; put '|`SomeGroup `|`A group `|`1`|'; put '|`Another Group`|`this is a different group`|`2`|'; put '|`admin`|`Administrators `|`3`|'; put '@version 9.3'; put '@author 4GL Apps Ltd'; put '@copyright 4GL Apps Ltd. This code may only be used within Data Controller'; put 'and may not be re-distributed or re-sold without the express permission of'; put '4GL Apps Ltd.'; put '**/'; put '%dc_getgroups(outds=groups)'; put '%webout(OPEN)'; put '%webout(OBJ,groups)'; put '%webout(CLOSE)'; put '* Service end;'; run; %mm_createwebservice(path=&appLoc/&path, name=&service, code=sascode, server=&serverName, replace=yes) filename sascode clear; %let service=getrawdata; filename sascode temp lrecl=32767; data _null_; file sascode; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '/**'; put '@file mp_jsonout.sas'; put '@brief Writes JSON in SASjs format to a fileref'; put '@details This macro can be used to OPEN a JSON stream and send one or more'; put 'tables as arrays of rows, where each row can be an object or a nested array.'; put 'There are two engines available - DATASTEP or PROCJSON.'; put 'PROC JSON is fast but will produce errs like the ones below if'; put 'special chars are encountered.'; put '> (ERR)OR: Some code points did not transcode.'; put '> An object or array close is not valid at this point in the JSON text.'; put '> Date value out of range'; put 'If this happens, try running with ENGINE=DATASTEP.'; put 'The DATASTEP engine is used to handle special SAS missing numerics, and'; put 'can also convert entire datasets to formatted values. Output JSON is always'; put 'in UTF-8.'; put 'Usage:'; put 'filename tmp temp;'; put 'data class; set sashelp.class;run;'; put '%mp_jsonout(OPEN,jref=tmp)'; put '%mp_jsonout(OBJ,class,jref=tmp)'; put '%mp_jsonout(OBJ,class,dslabel=class2,jref=tmp,showmeta=Y)'; put '%mp_jsonout(CLOSE,jref=tmp)'; put 'data _null_;'; put 'infile tmp;'; put 'input;putlog _infile_;'; put 'run;'; put 'If you are building web apps with SAS then you are strongly encouraged to use'; put 'the mX_createwebservice macros in combination with the'; put '[sasjs adapter](https://github.com/sasjs/adapter).'; put 'For more information see https://sasjs.io'; put '@param [in] action Valid values:'; put '@li OPEN - opens the JSON'; put '@li OBJ - sends a table with each row as an object'; put '@li ARR - sends a table with each row in an array'; put '@li CLOSE - closes the JSON'; put '@param [in] ds The dataset to send. Must be a work table.'; put '@param [out] jref= (_webout) The fileref to which to send the JSON'; put '@param [out] dslabel= The name to give the table in the exported JSON'; put '@param [in] fmt= (Y) Whether to keep (Y) or strip (N) formats from the table'; put '@param [in] engine= (DATASTEP) Which engine to use to send the JSON. Options:'; put '@li PROCJSON (default)'; put '@li DATASTEP (more reliable when data has non standard characters)'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to JSON'; put '

Related Files

'; put '@li mp_ds2fmtds.sas'; put '@version 9.2'; put '@author Allan Bowe'; put '@source https://github.com/sasjs/core'; put '**/'; put '%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y'; put ',engine=DATASTEP'; put ',missing=NULL'; put ',showmeta=N'; put ',maxobs=MAX'; put ')/*/STORE SOURCE*/;'; put '%local tempds colinfo fmtds i numcols numobs stmt_obs lastobs optval'; put 'tmpds1 tmpds2 tmpds3 tmpds4;'; put '%let numcols=0;'; put '%if &maxobs ne MAX %then %let stmt_obs=%str(if _n_>&maxobs then stop;);'; put '%if &action=OPEN %then %do;'; put 'options nobomfile;'; put 'data _null_;file &jref encoding=''utf-8'' lrecl=200;'; put 'put ''{"PROCESSED_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''"'';'; put 'run;'; put '%end;'; put '%else %if (&action=ARR or &action=OBJ) %then %do;'; put '/* force variable names to always be uppercase in the JSON */'; put 'options validvarname=upcase;'; put '/* To avoid issues with _webout on EBI - such as encoding diffs and truncation'; put '(https://support.sas.com/kb/49/325.html) we use temporary files */'; put 'filename _sjs1 temp lrecl=200 ;'; put 'data _null_; file _sjs1 encoding=''utf-8'';'; put 'put ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":";'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjs1 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs1 clear;'; put '/* grab col defs */'; put 'proc contents noprint data=&ds'; put 'out=_data_(keep=name type length format formatl formatd varnum label);'; put 'run;'; put '%let colinfo=%scan(&syslast,2,.);'; put 'proc sort data=&colinfo;'; put 'by varnum;'; put 'run;'; put '/* move meta to mac vars */'; put 'data &colinfo;'; put 'if _n_=1 then call symputx(''numcols'',nobs,''l'');'; put 'set &colinfo end=last nobs=nobs;'; put 'name=upcase(name);'; put '/* fix formats */'; put 'if type=2 or type=6 then do;'; put 'typelong=''char'';'; put 'length fmt $49.;'; put 'if format='''' then fmt=cats(''$'',length,''.'');'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else fmt=cats(format,formatl,''.'');'; put 'end;'; put 'else do;'; put 'typelong=''num'';'; put 'if format='''' then fmt=''best.'';'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else if formatd=0 then fmt=cats(format,formatl,''.'');'; put 'else fmt=cats(format,formatl,''.'',formatd);'; put 'end;'; put '/* 32 char unique name */'; put 'newname=''sasjs''!!substr(cats(put(md5(name),$hex32.)),1,27);'; put 'call symputx(cats(''name'',_n_),name,''l'');'; put 'call symputx(cats(''newname'',_n_),newname,''l'');'; put 'call symputx(cats(''length'',_n_),length,''l'');'; put 'call symputx(cats(''fmt'',_n_),fmt,''l'');'; put 'call symputx(cats(''type'',_n_),type,''l'');'; put 'call symputx(cats(''typelong'',_n_),typelong,''l'');'; put 'call symputx(cats(''label'',_n_),coalescec(label,name),''l'');'; put '/* overwritten when fmt=Y and a custom format exists in catalog */'; put 'if typelong=''num'' then call symputx(cats(''fmtlen'',_n_),200,''l'');'; put 'else call symputx(cats(''fmtlen'',_n_),min(32767,ceil((length+10)*1.5)),''l'');'; put 'run;'; put '%let tempds=%substr(_%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql;'; put 'select count(*) into: lastobs from &ds;'; put '%if &maxobs ne MAX %then %let lastobs=%sysfunc(min(&lastobs,&maxobs));'; put '%if &engine=PROCJSON %then %do;'; put '%if &missing=STRING %then %do;'; put '%put &sysmacroname: Special Missings not supported in proc json.;'; put '%put &sysmacroname: Switching to DATASTEP engine;'; put '%goto datastep;'; put '%end;'; put 'data &tempds;'; put 'set &ds;'; put '&stmt_obs;'; put '%if &fmt=N %then format _numeric_ best32.;;'; put '/* PRETTY is necessary to avoid line truncation in large files */'; put 'filename _sjs2 temp lrecl=131068 encoding=''utf-8'';'; put 'proc json out=_sjs2 pretty'; put '%if &action=ARR %then nokeys ;'; put ';export &tempds / nosastags fmtnumeric;'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs2 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs2 clear;'; put '%end;'; put '%else %if &engine=DATASTEP %then %do;'; put '%datastep:'; put '%if %sysfunc(exist(&ds)) ne 1 & %sysfunc(exist(&ds,VIEW)) ne 1'; put '%then %do;'; put '%put &sysmacroname: &ds NOT FOUND!!!;'; put '%return;'; put '%end;'; put '%if &fmt=Y %then %do;'; put '/**'; put '* Extract format definitions'; put '* First, by getting library locations from dictionary.formats'; put '* Then, by exporting the width using proc format'; put '* Cannot use maxw from sashelp.vformat as not always populated'; put '* Cannot use fmtinfo() as not supported in all flavours'; put '*/'; put '%let tmpds1=%substr(fmtsum%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds2=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds3=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds4=%substr(col%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql noprint;'; put 'create table &tmpds1 as'; put 'select cats(libname,''.'',memname) as FMTCAT,'; put 'FMTNAME'; put 'from dictionary.formats'; put 'where fmttype=''F'' and libname is not null'; put 'and fmtname in (select format from &colinfo where format is not null)'; put 'order by 1;'; put 'create table &tmpds2('; put 'FMTNAME char(32),'; put 'LENGTH num'; put ');'; put '%local catlist cat fmtlist i;'; put 'select distinct fmtcat into: catlist separated by '' '' from &tmpds1;'; put '%do i=1 %to %sysfunc(countw(&catlist,%str( )));'; put '%let cat=%scan(&catlist,&i,%str( ));'; put 'proc sql;'; put 'select distinct fmtname into: fmtlist separated by '' '''; put 'from &tmpds1 where fmtcat="&cat";'; put 'proc format lib=&cat cntlout=&tmpds3(keep=fmtname length);'; put 'select &fmtlist;'; put 'run;'; put 'proc sql;'; put 'insert into &tmpds2 select distinct fmtname,length from &tmpds3;'; put '%end;'; put 'proc sql;'; put 'create table &tmpds4 as'; put 'select a.*, b.length as MAXW'; put 'from &colinfo a'; put 'left join &tmpds2 b'; put 'on cats(a.format)=cats(upcase(b.fmtname))'; put 'order by a.varnum;'; put 'data _null_;'; put 'set &tmpds4;'; put 'if not missing(maxw);'; put 'call symputx('; put 'cats(''fmtlen'',_n_),'; put '/* vars need extra padding due to JSON escaping of special chars */'; put 'min(32767,ceil((max(length,maxw)+10)*1.5))'; put ',''l'''; put ');'; put 'run;'; put '/* configure varlenchk - as we are explicitly shortening the variables */'; put '%let optval=%sysfunc(getoption(varlenchk));'; put 'options varlenchk=NOWARN;'; put 'data _data_(compress=char);'; put '/* shorten the new vars */'; put 'length'; put '%do i=1 %to &numcols;'; put '&&name&i $&&fmtlen&i'; put '%end;'; put ';'; put '/* rename on entry */'; put 'set &ds(rename=('; put '%do i=1 %to &numcols;'; put '&&name&i=&&newname&i'; put '%end;'; put '));'; put '&stmt_obs;'; put 'drop'; put '%do i=1 %to &numcols;'; put '&&newname&i'; put '%end;'; put ';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=num %then %do;'; put '&&name&i=cats(put(&&newname&i,&&fmt&i));'; put '%end;'; put '%else %do;'; put '&&name&i=put(&&newname&i,&&fmt&i);'; put '%end;'; put '%end;'; put 'if _error_ then do;'; put 'call symputx(''syscc'',1012);'; put 'stop;'; put 'end;'; put 'run;'; put '%let fmtds=&syslast;'; put 'options varlenchk=&optval;'; put '%end;'; put 'proc format; /* credit yabwon for special null removal */'; put 'value bart (default=40)'; put '%if &missing=NULL %then %do;'; put '._ - .z = null'; put '%end;'; put '%else %do;'; put '._ = [quote()]'; put '. = null'; put '.a - .z = [quote()]'; put '%end;'; put 'other = [best.];'; put 'data &tempds;'; put 'attrib _all_ label='''';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'length &&name&i $&&fmtlen&i...;'; put 'format &&name&i $&&fmtlen&i...;'; put '%end;'; put '%end;'; put '%if &fmt=Y %then %do;'; put 'set &fmtds;'; put '%end;'; put '%else %do;'; put 'set &ds;'; put '%end;'; put '&stmt_obs;'; put 'format _numeric_ bart.;'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'if findc(&&name&i,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put '&&name&i=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,&&name&i)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else &&name&i=quote(cats(&&name&i));'; put '%end;'; put '%end;'; put 'run;'; put 'filename _sjs3 temp lrecl=131068 ;'; put 'data _null_;'; put 'file _sjs3 encoding=''utf-8'';'; put 'if _n_=1 then put "[";'; put 'set &tempds;'; put 'if _n_>1 then put "," @; put'; put '%if &action=ARR %then "[" ; %else "{" ;'; put '%do i=1 %to &numcols;'; put '%if &i>1 %then "," ;'; put '%if &action=OBJ %then """&&name&i"":" ;'; put '"&&name&i"n /* name literal for reserved variable names */'; put '%end;'; put '%if &action=ARR %then "]" ; %else "}" ; ;'; put '/* close out the table */'; put 'data _null_;'; put 'file _sjs3 mod encoding=''utf-8'';'; put 'put '']'';'; put 'run;'; put 'data _null_;'; put 'infile _sjs3 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs3 clear;'; put '%end;'; put 'proc sql;'; put 'drop table &colinfo, &tempds;'; put '%if %substr(&showmeta,1,1)=Y %then %do;'; put 'filename _sjs4 temp lrecl=131068 encoding=''utf-8'';'; put 'data _null_;'; put 'file _sjs4;'; put 'length label $350;'; put 'put ", ""$%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":{""vars"":{";'; put 'do i=1 to &numcols;'; put 'name=quote(trim(symget(cats(''name'',i))));'; put 'format=quote(trim(symget(cats(''fmt'',i))));'; put 'label=quote(prxchange(''s/\\/\\\\/'',-1,trim(symget(cats(''label'',i)))));'; put 'length=quote(trim(symget(cats(''length'',i))));'; put 'type=quote(trim(symget(cats(''typelong'',i))));'; put 'if i>1 then put "," @@;'; put 'put name '':{"format":'' format '',"label":'' label'; put ''',"length":'' length '',"type":'' type ''}'';'; put 'end;'; put 'put ''}}'';'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs4 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs4 clear;'; put '%end;'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put 'data _null_; file &jref encoding=''utf-8'' mod ;'; put 'put "}";'; put 'run;'; put '%end;'; put '%mend mp_jsonout;'; put '/**'; put '@file mm_webout.sas'; put '@brief Send data to/from SAS Stored Processes'; put '@details This macro should be added to the start of each Stored Process,'; put '**immediately** followed by a call to:'; put '%mm_webout(FETCH)'; put 'This will read all the input data and create same-named SAS datasets in the'; put 'WORK library. You can then insert your code, and send data back using the'; put 'following syntax:'; put 'data some datasets; * make some data ;'; put 'retain some columns;'; put 'run;'; put '%mm_webout(OPEN)'; put '%mm_webout(ARR,some) * Array format, fast, suitable for large tables ;'; put '%mm_webout(OBJ,datasets) * Object format, easier to work with ;'; put 'Finally, wrap everything up send some helpful system variables too'; put '%mm_webout(CLOSE)'; put '@param [in] action Either FETCH, OPEN, ARR, OBJ or CLOSE'; put '@param [in] ds The dataset to send back to the frontend'; put '@param [out] dslabel= Value to use instead of table name for sending to JSON'; put '@param [in] fmt= (N) Setting Y converts all vars to their formatted values'; put '@param [out] fref= (_webout) The fileref to which to write the JSON'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] workobs= (0) When set to a positive integer, will create a new'; put 'output object (WORK) which contains this number of observations from all'; put 'tables in the WORK library.'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to output JSON'; put '

SAS Macros

'; put '@li mp_jsonout.sas'; put '

Related Macros

'; put '@li ms_webout.sas'; put '@li mv_webout.sas'; put '@version 9.3'; put '@author Allan Bowe'; put '**/'; put '%macro mm_webout(action,ds,dslabel=,fref=_webout,fmt=N,missing=NULL'; put ',showmeta=N,maxobs=MAX,workobs=0'; put ');'; put '%global _webin_file_count _webin_fileref1 _webin_name1 _program _debug'; put 'sasjs_tables;'; put '%local i tempds jsonengine;'; put '/* see https://github.com/sasjs/core/issues/41 */'; put '%if "%upcase(&SYSENCODING)" ne "UTF-8" %then %let jsonengine=PROCJSON;'; put '%else %let jsonengine=DATASTEP;'; put '%if &action=FETCH %then %do;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'options mprint notes mprintnest;'; put '%end;'; put '%let _webin_file_count=%eval(&_webin_file_count+0);'; put '/* now read in the data */'; put '%do i=1 %to &_webin_file_count;'; put '%if &_webin_file_count=1 %then %do;'; put '%let _webin_fileref1=&_webin_fileref;'; put '%let _webin_name1=&_webin_name;'; put '%end;'; put 'data _null_;'; put 'infile &&_webin_fileref&i termstr=crlf;'; put 'input;'; put 'call symputx(''input_statement'',_infile_);'; put 'putlog "&&_webin_name&i input statement: " _infile_;'; put 'stop;'; put 'data &&_webin_name&i;'; put 'infile &&_webin_fileref&i firstobs=2 dsd termstr=crlf encoding=''utf-8'';'; put 'input &input_statement;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'if _n_<20 then putlog _infile_;'; put '%end;'; put 'run;'; put '%let sasjs_tables=&sasjs_tables &&_webin_name&i;'; put '%end;'; put '%end;'; put '%else %if &action=OPEN %then %do;'; put '/* fix encoding */'; put 'OPTIONS NOBOMFILE;'; put '/**'; put '* check xengine type to avoid the below err message:'; put '* > Function is only valid for filerefs using the CACHE access method.'; put '*/'; put 'data _null_;'; put 'set sashelp.vextfl(where=(fileref="_WEBOUT"));'; put 'if xengine=''STREAM'' then do;'; put 'rc=stpsrv_header(''Content-type'',"text/html; encoding=utf-8");'; put 'end;'; put 'run;'; put '/* setup json */'; put 'data _null_;file &fref encoding=''utf-8'';'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutBEGIN<<'';'; put '%end;'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'run;'; put '%end;'; put '%else %if &action=ARR or &action=OBJ %then %do;'; put '%mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt,jref=&fref'; put ',engine=&jsonengine,missing=&missing,showmeta=&showmeta,maxobs=&maxobs'; put ')'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put '/* To avoid issues with _webout on EBI we use a temporary file */'; put 'filename _sjsref temp lrecl=131068;'; put '%if %str(&workobs) > 0 %then %do;'; put '/* if debug mode, send back first XX records of each work table also */'; put 'data;run;%let tempds=%scan(&syslast,2,.);'; put 'ods output Members=&tempds;'; put 'proc datasets library=WORK memtype=data;'; put '%local wtcnt;%let wtcnt=0;'; put 'data _null_;'; put 'set &tempds;'; put 'if not (upcase(name) =:"DATA"); /* ignore temp datasets */'; put 'i+1;'; put 'call symputx(cats(''wt'',i),name,''l'');'; put 'call symputx(''wtcnt'',i,''l'');'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put ",""WORK"":{";'; put '%do i=1 %to &wtcnt;'; put '%let wt=&&wt&i;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'dsid=open("WORK.&wt",''is'');'; put 'nlobs=attrn(dsid,''NLOBS'');'; put 'nvars=attrn(dsid,''NVARS'');'; put 'rc=close(dsid);'; put 'if &i>1 then put '',''@;'; put 'put " ""&wt"" : {";'; put 'put ''"nlobs":'' nlobs;'; put 'put '',"nvars":'' nvars;'; put '%mp_jsonout(OBJ,&wt,jref=_sjsref,dslabel=first10rows,showmeta=Y'; put ',maxobs=&workobs'; put ')'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put '%end;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put 'run;'; put '%end;'; put '/* close off json */'; put 'data _null_;file _sjsref mod encoding=''utf-8'';'; put 'length SYSPROCESSNAME syserrortext syswarningtext autoexec $512;'; put 'put ",""_DEBUG"" : ""&_debug"" ";'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'autoexec=quote(urlencode(trim(getoption(''autoexec''))));'; put 'put '',"AUTOEXEC" : '' autoexec;'; put 'put ",""MF_GETUSER"" : ""%mf_getuser()"" ";'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'put ",""SYSENCODING"" : ""&sysencoding"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSPROCESSID"" : ""&SYSPROCESSID"" ";'; put 'put ",""SYSPROCESSMODE"" : ""&SYSPROCESSMODE"" ";'; put 'SYSPROCESSNAME=quote(urlencode(cats(SYSPROCESSNAME)));'; put 'put ",""SYSPROCESSNAME"" : " SYSPROCESSNAME;'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put '',"SYSWARNINGTEXT" : '' syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'length memsize $32;'; put 'memsize="%sysfunc(INPUTN(%sysfunc(getoption(memsize)), best.),sizekmg.)";'; put 'memsize=quote(cats(memsize));'; put 'put '',"MEMSIZE" : '' memsize;'; put 'put "}" @;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutEND<<'';'; put '%end;'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjsref lrecl=1 recfm=n;'; put 'file &fref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjsref clear;'; put '%end;'; put '%mend mm_webout;'; put '%macro webout(action,ds,dslabel=,fmt=,missing=NULL,showmeta=NO,maxobs=MAX);'; put '%mm_webout(&action,ds=&ds,dslabel=&dslabel,fmt=&fmt'; put ',missing=&missing'; put ',showmeta=&showmeta'; put ',maxobs=&maxobs'; put ') %mend;'; put '/* provide additional debug info */'; put '%global _program;'; put '%put &=syscc;'; put '%put user=%mf_getuser();'; put '%put pgm=&_program;'; put '%put timestamp=%sysfunc(datetime(),datetime19.);'; put '* Service Variables start;'; put '* Service Variables end;'; put '* SAS Macros start;'; put '%macro mf_getapploc(pgm);'; put '%if "&pgm"="" %then %do;'; put '%if %symexist(_program) %then %let pgm=&_program;'; put '%else %do;'; put '%put &sysmacroname: No value provided and no _program variable available;'; put '%return;'; put '%end;'; put '%end;'; put '%local root;'; put '/**'; put '* First check we are not in the tests/macros folder (which has no subfolders)'; put '* or specifically in the testsetup or testteardown services'; put '*/'; put '%if %index(&pgm,/tests/macros/)'; put 'or %index(&pgm,/tests/testsetup)'; put 'or %index(&pgm,/tests/testteardown)'; put '%then %do;'; put '%let root=%substr(&pgm,1,%index(&pgm,/tests)-1);'; put '&root'; put '%return;'; put '%end;'; put '/**'; put '* Next, move up two levels to avoid matches on subfolder or service name'; put '*/'; put '%let root=%substr(&pgm,1,%length(&pgm)-%length(%scan(&pgm,-1,/))-1);'; put '%let root=%substr(&root,1,%length(&root)-%length(%scan(&root,-1,/))-1);'; put '%if %index(&root,/tests/) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/tests/)-1);'; put '%end;'; put '%else %if %index(&root,/services) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/services)-1);'; put '%end;'; put '%else %if %index(&root,/jobs) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/jobs)-1);'; put '%end;'; put '%else %put &sysmacroname: Could not find an app location from &pgm;'; put '&root'; put '%mend mf_getapploc ;'; put '%macro mf_getuniquefileref(prefix=_,maxtries=1000,lrecl=32767);'; put '%local rc fname;'; put '%if &prefix=0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%end;'; put '%else %do;'; put '%local x len;'; put '%let len=%eval(8-%length(&prefix));'; put '%let x=0;'; put '%do x=0 %to &maxtries;'; put '%let fname=&prefix%substr(%sysfunc(ranuni(0)),3,&len);'; put '%if %sysfunc(fileref(&fname)) > 0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%return;'; put '%end;'; put '%end;'; put '%put unable to find available fileref after &maxtries attempts;'; put '%end;'; put '%mend mf_getuniquefileref;'; put '%macro mp_abort(mac=mp_abort.sas, type=, msg=, iftrue=%str(1=1)'; put ', errds=work.mp_abort_errds'; put ', mode=REGULAR'; put ')/*/STORE SOURCE*/;'; put '%global sysprocessmode sysprocessname sasjs_stpsrv_header_loc sasjsprocessmode;'; put '%local fref fid i;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%put NOTE: /// mp_abort macro executing //;'; put '%if %length(&mac)>0 %then %put NOTE- called by &mac;'; put '%put NOTE - &msg;'; put '%if %symexist(_SYSINCLUDEFILEDEVICE)'; put '/* abort cancel FILE does not restart outside the INCLUDE on Viya 3.5 */'; put 'and %superq(SYSPROCESSNAME) ne %str(Compute Server)'; put '%then %do;'; put '%if "*&_SYSINCLUDEFILEDEVICE*" ne "**" %then %do;'; put 'data &errds;'; put 'iftrue=''1=1'';'; put 'length mac $100 msg $5000;'; put 'mac=symget(''mac'');'; put 'msg=symget(''msg'');'; put 'run;'; put 'data _null_;'; put 'abort cancel FILE;'; put 'run;'; put '%return;'; put '%end;'; put '%end;'; put '/* Web App Context */'; put '%if %symexist(_PROGRAM)'; put 'or %superq(SYSPROCESSNAME) = %str(Compute Server)'; put 'or &mode=INCLUDE'; put '%then %do;'; put 'options obs=max replace mprint;'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5"'; put '%then %do;'; put 'options nosyntaxcheck;'; put '%end;'; put '%if &mode=INCLUDE %then %do;'; put '%if %sysfunc(exist(&errds))=1 %then %do;'; put 'data _null_;'; put 'set &errds;'; put 'call symputx(''iftrue'',iftrue,''l'');'; put 'call symputx(''mac'',mac,''l'');'; put 'call symputx(''msg'',msg,''l'');'; put 'putlog (_all_)(=);'; put 'run;'; put '%if (&iftrue)=0 %then %return;'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: No include errors found;'; put '%return;'; put '%end;'; put '%end;'; put '/* extract log errs / warns, if exist */'; put '%local logloc logline;'; put '%global logmsg; /* capture global messages */'; put '%if %symexist(SYSPRINTTOLOG) %then %let logloc=&SYSPRINTTOLOG;'; put '%else %let logloc=%qsysfunc(getoption(LOG));'; put 'proc printto log=log;run;'; put '%let logline=0;'; put '%if %length(&logloc)>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input; putlog _infile_;'; put 'i=1;'; put 'retain logonce 0;'; put 'if ('; put '_infile_=:"%str(WARN)ING" or _infile_=:"%str(ERR)OR"'; put ') and logonce=0 then'; put 'do;'; put 'call symputx(''logline'',_n_);'; put 'logonce+1;'; put 'end;'; put 'run;'; put '/* capture log including lines BEFORE the err */'; put '%if &logline>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input;'; put 'i=1;'; put 'stoploop=0;'; put 'if _n_ ge &logline-15 and stoploop=0 then do until (i>22);'; put 'call symputx(''logmsg'',catx(''\n'',symget(''logmsg''),_infile_));'; put 'input;'; put 'i+1;'; put 'stoploop=1;'; put 'end;'; put 'if stoploop=1 then stop;'; put 'run;'; put '%end;'; put '%end;'; put '%if %symexist(SYS_JES_JOB_URI) %then %do;'; put '/* setup webout for Viya */'; put 'options nobomfile;'; put '%if "X&SYS_JES_JOB_URI.X"="XX" %then %do;'; put 'filename _webout temp lrecl=999999 mod;'; put '%end;'; put '%else %do;'; put 'filename _webout filesrvc parenturi="&SYS_JES_JOB_URI"'; put 'name="_webout.json" lrecl=999999 mod;'; put '%end;'; put '%end;'; put '%else %if %sysfunc(filename(fref,&sasjs_stpsrv_header_loc))=0 %then %do;'; put 'options nobomfile;'; put '/* set up http header for SASjs Server */'; put '%let fid=%sysfunc(fopen(&fref,A));'; put '%if &fid=0 %then %do;'; put '%put %str(ERR)OR: %sysfunc(sysmsg());'; put '%return;'; put '%end;'; put '%let rc=%sysfunc(fput(&fid,%str(Content-Type: application/json)));'; put '%let rc=%sysfunc(fwrite(&fid));'; put '%let rc=%sysfunc(fclose(&fid));'; put '%let rc=%sysfunc(filename(&fref));'; put '%end;'; put '/* send response in SASjs JSON format */'; put 'data _null_;'; put 'file _webout mod lrecl=32000 encoding=''utf-8'';'; put 'length msg syswarningtext syserrortext $32767 mode $10 ;'; put 'sasdatetime=datetime();'; put 'msg=symget(''msg'');'; put '%if &logline>0 %then %do;'; put 'msg=cats(msg,''\n\nLog Extract:\n'',symget(''logmsg''));'; put '%end;'; put '/* escape the escapes */'; put 'msg=tranwrd(msg,''\'',''\\'');'; put '/* escape the quotes */'; put 'msg=tranwrd(msg,''"'',''\"'');'; put '/* ditch the CRLFs as chrome complains */'; put 'msg=compress(msg,,''kw'');'; put '/* quote without quoting the quotes (which are escaped instead) */'; put 'msg=cats(''"'',msg,''"'');'; put 'if symexist(''_debug'') then debug=quote(trim(symget(''_debug'')));'; put 'else debug=''""'';'; put 'if symget(''sasjsprocessmode'')=''Stored Program'' then mode=''SASJS'';'; put 'if mode ne ''SASJS'' then put ''>>weboutBEGIN<<'';'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'put '',"sasjsAbort" : [{'';'; put 'put '' "MSG":'' msg ;'; put 'put '' ,"MAC": "'' "&mac" ''"}]'';'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'put '',"_DEBUG":'' debug ;'; put 'if symexist(''_metauser'') then do;'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put 'end;'; put 'if symexist(''SYS_JES_JOB_URI'') then do;'; put 'SYS_JES_JOB_URI=quote(trim(symget(''SYS_JES_JOB_URI'')));'; put 'put '',"SYS_JES_JOB_URI": '' SYS_JES_JOB_URI;'; put 'end;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put ",""SYSWARNINGTEXT"" : " syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'put "}" ;'; put 'if mode ne ''SASJS'' then put ''>>weboutEND<<'';'; put 'run;'; put '%put _all_;'; put '%if "&sysprocessmode " = "SAS Stored Process Server " %then %do;'; put 'data _null_;'; put 'putlog ''stpsrvset program err and syscc'';'; put 'rc=stpsrvset(''program error'', 0);'; put 'call symputx("syscc",0,"g");'; put 'run;'; put '%if &sysscp=WIN'; put 'and 1=0 /* deprecating this logic until we figure out a consistent abort */'; put 'and "%substr(%str(&sysvlong ),1,8)"="9.04.01M"'; put 'and "%substr(%str(&sysvlong ),9,1)">"5" %then %do;'; put '/* skip approach (below) does not work in windows m6+ envs */'; put 'endsas;'; put '%end;'; put '%else %do;'; put '/**'; put '* endsas kills 9.4m3 deployments by orphaning multibridges.'; put '* Abort variants are ungraceful (non zero return code)'; put '* This approach lets SAS run silently until the end :-)'; put '* Caution - fails when called within a %include within a macro'; put '* Use mp_include() to handle this.'; put '*/'; put 'filename skip temp;'; put 'data _null_;'; put 'file skip;'; put 'put ''%macro skip();'';'; put 'comment ''%mend skip; -> fix lint '';'; put 'put ''%macro skippy();'';'; put 'comment ''%mend skippy; -> fix lint '';'; put 'run;'; put '%inc skip;'; put '%end;'; put '%end;'; put '%else %if "&sysprocessmode " = "SAS Compute Server " %then %do;'; put '/* endsas kills the session making it harder to fetch results */'; put 'data _null_;'; put 'syswarningtext=symget(''syswarningtext'');'; put 'syserrortext=symget(''syserrortext'');'; put 'abort_msg=symget(''msg'');'; put 'syscc=symget(''syscc'');'; put 'sysuserid=symget(''sysuserid'');'; put 'iftrue=symget(''iftrue'');'; put 'put (_all_)(/=);'; put 'call symputx(''syscc'',0);'; put 'abort cancel nolist;'; put 'run;'; put '%end;'; put '%else %do;'; put '%abort cancel;'; put '%end;'; put '%end;'; put '%else %do;'; put '%put _all_;'; put '%abort cancel;'; put '%end;'; put '%mend mp_abort;'; put '/** @endcond */'; put '%macro mm_getstpcode('; put 'tree=/User Folders/sasdemo/somestp'; put ',name='; put ',outloc=0'; put ',outref=0'; put ',mDebug=1'; put ',showlog=NO'; put ');'; put '%local mD;'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing &sysmacroname..sas;'; put '%&mD.put _local_;'; put '%if %length(&name)>0 %then %let name=/&name;'; put '/* first, check if STP exists */'; put '%local tsuri;'; put '%let tsuri=stopifempty ;'; put 'data _null_;'; put 'format type uri tsuri value $200.;'; put 'call missing (of _all_);'; put 'path="&tree&name(StoredProcess)";'; put '/* first, find the STP ID */'; put 'if metadata_pathobj("",path,"StoredProcess",type,uri)>0 then do;'; put '/* get sourcecode */'; put 'cnt=1;'; put 'do while (metadata_getnasn(uri,"Notes",cnt,tsuri)>0);'; put 'rc=metadata_getattr(tsuri,"Name",value);'; put '&mD.put tsuri= value=;'; put 'if value="SourceCode" then do;'; put '/* found it! */'; put 'rc=metadata_getattr(tsuri,"Id",value);'; put 'call symputx(''tsuri'',value,''l'');'; put 'stop;'; put 'end;'; put 'cnt+1;'; put 'end;'; put 'end;'; put 'else put (_all_)(=);'; put 'run;'; put '%mp_abort(iftrue= (&tsuri=stopifempty)'; put ',mac=mm_getstpcode'; put ',msg=%str(&tree&name.(StoredProcess) not found!)'; put ')'; put '/**'; put '* Now we can extract the textstore'; put '*/'; put 'filename __getdoc temp lrecl=10000000;'; put 'proc metadata'; put 'in="$METAREPOSITORY'; put ''; put 'SAS1"'; put 'out=__getdoc ;'; put 'run;'; put '/* find the beginning of the text */'; put '%local start;'; put 'data _null_;'; put 'infile __getdoc lrecl=10000;'; put 'input;'; put 'start=index(_infile_,''StoredText="'');'; put 'if start then do;'; put 'call symputx("start",start+11);'; put '*putlog ''"'' _infile_ ''"'';'; put 'end;'; put 'stop;'; put '%local outeng;'; put '%if "&outloc"="0" %then %let outeng=TEMP;'; put '%else %let outeng="&outloc";'; put '%local fref;'; put '%if &outref=0 %then %let fref=%mf_getuniquefileref();'; put '%else %let fref=&outref;'; put '/* read the content, byte by byte, resolving escaped chars */'; put 'filename &fref &outeng lrecl=100000;'; put 'data _null_;'; put 'length filein 8 fileid 8;'; put 'filein = fopen("__getdoc","I",1,"B");'; put 'fileid = fopen("&fref","O",1,"B");'; put 'rec = "20"x;'; put 'length entity $6;'; put 'do while(fread(filein)=0);'; put 'x+1;'; put 'if x>&start then do;'; put 'rc = fget(filein,rec,1);'; put 'if rec=''"'' then leave;'; put 'else if rec="&" then do;'; put 'entity=rec;'; put 'do until (rec=";");'; put 'if fread(filein) ne 0 then goto getout;'; put 'rc = fget(filein,rec,1);'; put 'entity=cats(entity,rec);'; put 'end;'; put 'select (entity);'; put 'when (''&'' ) rec=''&'' ;'; put 'when (''<'' ) rec=''<'' ;'; put 'when (''>'' ) rec=''>'' ;'; put 'when (''''') rec="''" ;'; put 'when (''"'') rec=''"'' ;'; put 'when ('' '') rec=''0A''x;'; put 'when ('' '') rec=''0D''x;'; put 'when (''$'' ) rec=''$'' ;'; put 'when ('' '') rec=''09''x;'; put 'otherwise putlog "%str(WARN)ING: missing value for " entity=;'; put 'end;'; put 'rc =fput(fileid, substr(rec,1,1));'; put 'rc =fwrite(fileid);'; put 'end;'; put 'else do;'; put 'rc =fput(fileid,rec);'; put 'rc =fwrite(fileid);'; put 'end;'; put 'end;'; put 'end;'; put 'getout:'; put 'rc=fclose(filein);'; put 'rc=fclose(fileid);'; put 'run;'; put '%if &showlog=YES %then %do;'; put 'data _null_;'; put 'infile &fref lrecl=32767 end=last;'; put 'input;'; put 'if _n_=1 then putlog ''>>stpcodeBEGIN<<'';'; put 'putlog _infile_;'; put 'if last then putlog ''>>stpcodeEND<<'';'; put 'run;'; put '%end;'; put 'filename __getdoc clear;'; put '%if &outref=0 %then %do;'; put 'filename &fref clear;'; put '%end;'; put '%mend mm_getstpcode;'; put '%macro dc_getsettings();'; put '%global _program;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(syscc=&syscc on entry (&syswarningtext &syserrortext))'; put ')'; put '%if %length(&_PROGRAM)>1 %then %let root=&_program;'; put '%else %do;'; put '%global _metauser;'; put '%let _metauser=&sysuserid;'; put '/* to mimic a "real" _program we need to give a dummy role and stp name */'; put '%let root=/dummyRole/dummyName;'; put '%end;'; put '/* the DC precode is stored in the Admin folder in the root of'; put 'the project. Lets find that root. */'; put '%put &=root;'; put '%let root=%mf_getapploc();'; put '%put &=root;'; put '/* Now we know the root location we can retrieve the params */'; put '%let temploc=%sysfunc(pathname(work))/settings.txt;'; put '%mm_getstpcode(tree=&root/services/public'; put ',name=Data_Controller_Settings'; put ',outloc=&temploc'; put ')'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Unable to run getstpcode)'; put ')'; put 'filename _getsets "&temploc" lrecl=2000;'; put '/*'; put 'Do not use mp_include here - this puts a copy in every service, which creates'; put 'compilation problems when calling services from mp_include'; put '*/'; put '%inc _getsets/source2;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Problem running &sysmacroname (&syswarningtext &syserrortext))'; put ')'; put '%mend dc_getsettings;'; put '%macro mf_fmtdttm('; put ')/*/STORE SOURCE*/;'; put '%if "&sysver"="9.2" or "&sysver"="9.3"'; put 'or ("&sysver"="9.4" and "%substr(&SYSVLONG,9,1)" le "3")'; put 'or "%substr(&sysver,1,1)"="4"'; put 'or "%substr(&sysver,1,1)"="5"'; put '%then %do;DATETIME19.3%end;'; put '%else %do;E8601DT26.6%end;'; put '%mend mf_fmtdttm;'; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '%macro mp_init(prefix=SASJS'; put ')/*/STORE SOURCE*/;'; put '%if %symexist(SASJS_PREFIX) %then %return; /* only run once */'; put '%global'; put 'SASJS_PREFIX /* the ONLY hard-coded global macro variable in SASjs */'; put '&prefix._FUNCTIONS /* used in mcf_init() to track core function compilation */'; put '&prefix._INIT_NUM /* initialisation time as numeric */'; put '&prefix._INIT_DTTM /* initialisation time in E8601DT26.6 format */'; put '&prefix.WORK /* avoid typing %sysfunc(pathname(work)) every time */'; put ';'; put '%let sasjs_prefix=&prefix;'; put 'data _null_;'; put 'dttm=datetime();'; put 'call symputx("&sasjs_prefix._init_num",dttm,''g'');'; put 'call symputx("&sasjs_prefix._init_dttm",put(dttm,E8601DT26.6),''g'');'; put 'call symputx("&sasjs_prefix.work",pathname(''WORK''),''g'');'; put 'run;'; put 'options'; put 'compress=CHAR /* default is none so ensure we have something! */'; put 'datastmtchk=ALLKEYWORDS /* protection from overwriting input datasets */'; put 'errorcheck=STRICT /* catch errs in libname/filename statements */'; put 'fmterr /* ensure err when a format cannot be found */'; put 'mergenoby=%str(ERR)OR /* throw err when a merge has no BY variables */'; put 'missing=. /* changing this can cause hard to detect errs */'; put 'noquotelenmax /* avoid warnings for long strings */'; put 'noreplace /* avoid overwriting permanent datasets */'; put 'ps=max /* reduce log size slightly */'; put 'ls=max /* reduce log even more and avoid word truncation */'; put 'validmemname=COMPATIBLE /* avoid special characters etc in table names */'; put 'validvarname=V7 /* avoid special characters etc in variable names */'; put 'varinitchk=%str(ERR)OR /* avoid data mistakes from variable name typos */'; put 'varlenchk=%str(ERR)OR /* fail hard if truncation (data loss) can result */'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5" %then %do;'; put 'noautocorrect /* disallow misspelled procedure names */'; put 'dsoptions=note2err /* undocumented - convert bad NOTEs to ERRs */'; put '%end;'; put ';'; put '%mend mp_init;'; put '%macro mpeinit(fetch=YES);'; put '%global mpeinit'; put 'mpeadmins /* group with unrestricted Meditor access */'; put 'mpelocapprovals /* location for landing and staging files */'; put 'mpelib /* location of configuration tables for DC */'; put 'dc_repo_users /* location of user / group metadata */'; put 'dc_licence_key /* extracted in dc_getsettings */'; put 'dc_activation_key /* extracted in dc_getsettings */'; put 'dc_locale /* extracted in dc_getsettings */'; put 'dc_dttmtfmt /* can be overridden in dc_getsettings */'; put '_debug'; put ';'; put '%if &mpeinit=1 %then %return;'; put '%else %let mpeinit=1;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(Problem on service startup (&syswarningtext &syserrortext))'; put ')'; put '%mp_init()'; put '%if &fetch=YES %then %do;'; put '%webout(FETCH)'; put '%end;'; put '%global _CLIENTNAME;'; put '%mp_abort(iftrue= (&_CLIENTNAME=SAS Enterprise Guide)'; put ',mac=&_program..sas'; put ',msg=%str(Data Controller is a web app and should not be executed from EG)'; put ')'; put 'options urlencoding=utf8 nobomfile lrecl=32767;'; put '%let perf=%sysfunc(datetime());'; put '%put perfdiff: 0;'; put '%let dc_locale=SYSTEM; /* default if not set */'; put '/**'; put '* E8601DT26.6 has widest database support - but not all SAS flavours can'; put '* handle it. Override in the settings STP if needed.'; put '*/'; put 'data _null_;'; put 'dc_dttmtfmt=''"%sysfunc(datetime(),''!!"%mf_fmtdttm()"!!'')"dt'';'; put 'call symputx(''dc_dttmtfmt'',dc_dttmtfmt);'; put 'put dc_dttmtfmt=;'; put 'run;'; put '%put &=dc_dttmtfmt;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc prior to dc_getsettings)'; put ')'; put '%dc_getsettings()'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc after dc_getsettings)'; put ')'; put 'data _null_;'; put 'set &DC_LIBREF..mpe_config(where=('; put 'var_scope="DC"'; put 'and &dc_dttmtfmt lt tx_to'; put 'and var_active=1'; put '));'; put 'call symputx(var_name,var_value,''G'');'; put 'putlog var_name "=" var_value;'; put 'run;'; put '%let mpelib=&dc_libref;'; put '%let mpeadmins=&dc_admin_group;'; put '%let mpelocapprovals=&dc_staging_area;'; put '%let dc_repo_users=&dc_repo_users;'; put '%if &dc_locale ne SYSTEM %then %do;'; put 'options locale=&dc_locale;'; put '%end;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(Problem during compilation or with STP precode (&syswarningtext))'; put ')'; put '%mend mpeinit;'; put '%macro mf_mval(var);'; put '%if %symexist(&var) %then %do;'; put '%superq(&var)'; put '%end;'; put '%mend mf_mval;'; put '%macro mf_trimstr(basestr,trimstr);'; put '%local baselen trimlen trimval;'; put '/* return if basestr is shorter than trimstr (or 0) */'; put '%let baselen=%length(%superq(basestr));'; put '%let trimlen=%length(%superq(trimstr));'; put '%if &baselen < &trimlen or &baselen=0 %then %return;'; put '/* obtain the characters from the end of basestr */'; put '%let trimval=%qsubstr(%superq(basestr)'; put ',%length(%superq(basestr))-&trimlen+1'; put ',&trimlen);'; put '/* compare and if matching, chop it off! */'; put '%if %superq(basestr)=%superq(trimstr) %then %do;'; put '%return;'; put '%end;'; put '%else %if %superq(trimval)=%superq(trimstr) %then %do;'; put '%qsubstr(%superq(basestr),1,%length(%superq(basestr))-&trimlen)'; put '%end;'; put '%else %do;'; put '&basestr'; put '%end;'; put '%mend mf_trimstr;'; put '%macro mf_getplatform(switch'; put ')/*/STORE SOURCE*/;'; put '%local a b c;'; put '%if &switch.NONE=NONE %then %do;'; put '%if %symexist(sasjsprocessmode) %then %do;'; put '%if &sasjsprocessmode=Stored Program %then %do;'; put 'SASJS'; put '%return;'; put '%end;'; put '%end;'; put '%if %symexist(sysprocessmode) %then %do;'; put '%if "&sysprocessmode"="SAS Object Server"'; put 'or "&sysprocessmode"= "SAS Compute Server" %then %do;'; put 'SASVIYA'; put '%end;'; put '%else %if "&sysprocessmode"="SAS Stored Process Server"'; put 'or "&sysprocessmode"="SAS Workspace Server"'; put '%then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if %symexist(_metaport) or %symexist(_metauser) %then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if &switch=SASSTUDIO %then %do;'; put '/* return the version of SAS Studio else 0 */'; put '%if %mf_mval(_CLIENTAPP)=%str(SAS Studio) %then %do;'; put '%let a=%mf_mval(_CLIENTVERSION);'; put '%let b=%scan(&a,1,.);'; put '%if %eval(&b >2) %then %do;'; put '&b'; put '%end;'; put '%else 0;'; put '%end;'; put '%else 0;'; put '%end;'; put '%else %if &switch=VIYARESTAPI %then %do;'; put '%mf_trimstr(%sysfunc(getoption(servicesbaseurl)),/)'; put '%end;'; put '%mend mf_getplatform;'; put '%macro mpeterm();'; put '%local oldloc;'; put 'data _null_;'; put 'if symexist(''SYSPRINTTOLOG'') then oldloc=symget(''SYSPRINTTOLOG'');'; put 'else oldloc=getoption(''LOG'');'; put 'if subpad(oldloc,1,1) not in (''"'',"''",'' '') then oldloc=quote(cats(oldloc));'; put 'call symputx(''oldloc'',oldloc,''l'');'; put 'run;'; put '%if %length(&oldloc)>0 %then %do;'; put 'proc printto log=log;'; put 'run;'; put 'data _null_;'; put 'infile &oldloc;'; put 'input; putlog _infile_;'; put 'run;'; put '%end;'; put '%if %sysfunc(exist(&dc_libref..mpe_requests)) and %mf_getplatform() ne SASVIYA'; put '%then %do;'; put 'data ;'; put 'if 0 then set &dc_libref..mpe_requests;'; put 'request_dttm=%sysfunc(datetime());'; put 'request_user="%mf_getuser()";'; put 'request_service="%scan(&_program,-2,/)/%scan(&_program,-1,/)";'; put 'request_params='''';'; put 'output;stop;'; put 'proc append base=&dc_libref..mpe_requests data=&syslast force nowarn;'; put 'run;'; put '%end;'; put '%mend mpeterm;'; put '%macro mm_getGroups('; put 'user='; put ',outds=work.mm_getGroups'; put ',repo=foundation'; put ',mDebug=0'; put ')/*/STORE SOURCE*/;'; put '%local mD oldrepo;'; put '%let oldrepo=%sysfunc(getoption(metarepository));'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing mm_getGroups.sas;'; put '%&mD.put _local_;'; put '/* on some sites, user / group info is in a different metadata repo to the'; put 'default */'; put '%if &oldrepo ne &repo %then %do;'; put 'options metarepository=&repo;'; put '%end;'; put '%if %length(&user)=0 %then %do;'; put 'data &outds (keep=groupuri groupname groupdesc);'; put 'length groupuri groupname groupdesc group_or_role $256;'; put 'call missing(of _all_);'; put 'i+1;'; put 'do while'; put '(metadata_getnobj("omsobj:IdentityGroup?@Id contains ''.''",i,groupuri)>0);'; put 'rc=metadata_getattr(groupuri, "Name", groupname);'; put 'rc=metadata_getattr(groupuri, "Desc", groupdesc);'; put 'rc=metadata_getattr(groupuri,"PublicType",group_or_role);'; put 'if Group_or_Role = ''UserGroup'' then output;'; put 'i+1;'; put 'end;'; put 'run;'; put '%end;'; put '%else %do;'; put 'data &outds (keep=groupuri groupname groupdesc);'; put 'length uri groupuri groupname groupdesc group_or_role $256;'; put 'call missing(of _all_);'; put 'rc=metadata_getnobj("omsobj:Person?@Name=''&user''",1,uri);'; put 'if rc<=0 then do;'; put 'putlog "%str(WARN)ING: rc=" rc "&user not found "'; put '", or there was an issue reading the repository.";'; put 'stop;'; put 'end;'; put 'a=1;'; put 'grpassn=metadata_getnasn(uri,"IdentityGroups",a,groupuri);'; put 'if grpassn in (-3,-4) then do;'; put 'putlog "%str(WARN)ING: No metadata groups found for &user";'; put 'output;'; put 'end;'; put 'else do while (grpassn > 0);'; put 'rc=metadata_getattr(groupuri, "Name", groupname);'; put 'rc=metadata_getattr(groupuri, "Desc", groupdesc);'; put 'a+1;'; put 'rc=metadata_getattr(groupuri,"PublicType",group_or_role);'; put 'if Group_or_Role = ''UserGroup'' then output;'; put 'grpassn=metadata_getnasn(uri,"IdentityGroups",a,groupuri);'; put 'end;'; put 'run;'; put '%end;'; put '%if &oldrepo ne &repo %then %do;'; put 'options metarepository=&oldrepo;'; put '%end;'; put '%mend mm_getGroups;'; put '%macro dc_getusergroups(user=,outds=mm_getgroups);'; put '%global dc_repo_users;'; put '%if &dc_repo_users= %then %let dc_repo_users=foundation;'; put '%mm_getgroups(user=&user,outds=&outds,repo=&dc_repo_users)'; put '%mend dc_getusergroups;'; put '%macro mpe_getgroups(user=,outds=);'; put '%if not %symexist(dc_repo_users) %then %let dc_repo_users=foundation;'; put '%dc_getusergroups(user=&user,outds=&outds)'; put 'data;'; put 'length groupname groupdesc $256;'; put 'set &dc_libref..mpe_groups;'; put 'where &dc_dttmtfmt. lt tx_to;'; put 'where also upcase(user_name)="%upcase(&user)";'; put 'groupname=group_name;'; put 'groupdesc=group_desc;'; put 'keep groupname groupdesc;'; put 'run;'; put 'data &outds;'; put 'set &syslast &outds(keep=groupname groupdesc);'; put 'run;'; put '%mend mpe_getgroups;'; put '%macro mf_getuniquename(prefix=MC);'; put '&prefix.%substr(%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32-%length(&prefix))'; put '%mend mf_getuniquename;'; put '%macro mf_getattrn('; put 'libds'; put ',attr'; put ')/*/STORE SOURCE*/;'; put '%local dsid rc;'; put '%let dsid=%sysfunc(open(&libds,is));'; put '%if &dsid = 0 %then %do;'; put '%put %str(WARN)ING: Cannot open %trim(&libds), system message below;'; put '%put %sysfunc(sysmsg());'; put '-1'; put '%end;'; put '%else %do;'; put '%sysfunc(attrn(&dsid,&attr))'; put '%let rc=%sysfunc(close(&dsid));'; put '%end;'; put '%mend mf_getattrn;'; put '%macro mf_nobs(libds'; put ')/*/STORE SOURCE*/;'; put '%mf_getattrn(&libds,NLOBS)'; put '%mend mf_nobs;'; put '%macro mp_filtergenerate(inds,outref=filter);'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(syscc=&syscc - on macro entry)'; put ')'; put 'filename &outref temp;'; put '%if %mf_nobs(&inds)=0 %then %do;'; put '/* ensure we have a default filter */'; put 'data _null_;'; put 'file &outref;'; put 'put ''1=1'';'; put 'run;'; put '%end;'; put '%else %do;'; put 'proc sort data=&inds;'; put 'by SUBGROUP_ID;'; put 'run;'; put 'data _null_;'; put 'file &outref lrecl=32800;'; put 'set &inds end=last;'; put 'by SUBGROUP_ID;'; put 'if _n_=1 then put ''(('';'; put 'else if first.SUBGROUP_ID then put +1 GROUP_LOGIC ''('';'; put 'else put +2 SUBGROUP_LOGIC;'; put 'put +4 VARIABLE_NM OPERATOR_NM RAW_VALUE;'; put 'if last.SUBGROUP_ID then put '')''@;'; put 'if last then put '')'';'; put 'run;'; put '%end;'; put '%mend mp_filtergenerate;'; put '%macro mpe_filtermaster(mode,libds,'; put 'dclib=,'; put 'filter_rk=-1,'; put 'outref=0,'; put 'outds=work.query'; put ');'; put '%put &sysmacroname entry vars:;'; put '%put _local_;'; put '%let mode=%upcase(&mode);'; put '%let libds=%upcase(&libds);'; put '%mp_abort(iftrue= ('; put '&mode ne EDIT and &mode ne VIEW and &mode ne DLOAD and &mode ne ULOAD'; put ')'; put ',mac=&sysmacroname'; put ',msg=%str(Invalid MODE: &mode)'; put ')'; put '%mp_abort(iftrue= (&outref = 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Please provide a fileref!)'; put ')'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(syscc=&syscc)'; put ')'; put 'filename &outref temp;'; put '/* ensure outputs exist */'; put 'data _null_;'; put 'file &outref;'; put 'put '' '';'; put 'run;'; put 'data &outds;'; put 'set &dclib..mpe_filtersource;'; put 'stop;'; put 'run;'; put '/**'; put '* Deal with FILTER_RK first'; put '*/'; put '%if &filter_rk gt 0 %then %do;'; put 'data _null_;'; put 'file &outref;'; put 'put ''( ''@@;'; put 'set &dclib..mpe_filteranytable(where=(filter_rk=&filter_rk));'; put 'call symputx(''filter_hash'',filter_hash,''l'');'; put 'run;'; put 'proc sort data=&dclib..mpe_filtersource(where=(filter_hash="&filter_hash"))'; put 'out=&outds(drop=filter_hash filter_line processed_dttm);'; put 'by filter_line;'; put 'run;'; put '%mp_filtergenerate(&outds,outref=&outref)'; put '%end;'; put '/* Now filter for current records if the MODE is EDIT or DLOAD */'; put '%local varfrom varto;'; put '%let varfrom=0;'; put 'proc sql;'; put 'select coalescec(var_txfrom,''0''), var_txto into: varfrom,:varto'; put 'from &dclib..MPE_TABLES'; put 'where &dc_dttmtfmt. lt tx_to'; put 'and libref="%scan(&libds,1,.)" and dsn="%scan(&libds,2,.)";'; put '%put &=varfrom;'; put '%put &=varto;'; put '/**'; put '* Check if the date variables were mentioned in the query'; put '* This is a trigger for serving a historical view instead of current'; put '* we skip this part when checking an ULOAD as there are no date vars'; put '*/'; put '%if &varfrom ne 0 and (&mode=EDIT or &mode=DLOAD) %then %do;'; put '%local validityvars;'; put 'proc sql;'; put 'select count(*) into: validityvars'; put 'from &outds'; put 'where variable_nm in ("&varfrom","&varto");'; put '%if &validityvars=0 %then %do;'; put 'data _null_;'; put 'file &outref mod;'; put 'length filter_text $32767;'; put 'varfrom=symget(''varfrom'');'; put 'varto=symget(''varto'');'; put 'filter_text=catx('' '','; put '''("%sysfunc(datetime(),'',"%mf_fmtdttm()",'')"dt <'',varto,'')'''; put ');'; put 'if &filter_rk > 0 then put ''AND '' filter_text;'; put 'else put filter_text;'; put 'run;'; put '%end;'; put '%end;'; put '/**'; put '* Now do Row Level Security based on the MPE_ROW_LEVEL_SECURITY table'; put '*/'; put '/* first determine users group membership */'; put '%mpe_getgroups(user=%mf_getuser(),outds=work.groups)'; put '%local admin_check;'; put 'proc sql;'; put 'select count(*) into: admin_check'; put 'from work.groups'; put 'where groupname="&mpeadmins";'; put '%put &sysmacroname: &=admin_check &=mpeadmins;'; put '%if &admin_check=0 %then %do;'; put '%local scopeval;'; put '%if &mode=DLOAD %then %let scopeval=VIEW;'; put '%if &mode=ULOAD %then %let scopeval=EDIT;'; put '%else %let scopeval=&mode;'; put '/* extract relevant rows */'; put '%local rlsds;'; put '%let rlsds=%mf_getuniquename();'; put 'proc sql;'; put 'create table work.&rlsds as'; put 'select rls_group,'; put 'rls_group_logic as group_logic,'; put 'rls_subgroup_logic as subgroup_logic,'; put 'rls_subgroup_id as subgroup_id,'; put 'rls_variable_nm as variable_nm,'; put 'rls_operator_nm as operator_nm,'; put 'rls_raw_value as raw_value'; put 'from &mpelib..mpe_row_level_security'; put 'where &dc_dttmtfmt. lt tx_to'; put 'and rls_scope in ("&scopeval",''ALL'')'; put 'and upcase(rls_group) in (select upcase(groupname) from work.groups)'; put 'and rls_libref="%scan(&libds,1,.)"'; put 'and rls_table="%scan(&libds,2,.)"'; put 'and rls_active=1'; put 'order by rls_group,rls_subgroup_id;'; put '%if &sqlobs>0 %then %do;'; put '/* check if we currently have filter or not */'; put 'data ;'; put 'infile &outref end=eof;'; put 'input;'; put 'if _n_=1 and eof and cats(_infile_)='''' then newfilter=1;'; put 'output;'; put 'stop;'; put 'run;'; put 'data _null_;'; put 'set &syslast;'; put 'file &outref mod;'; put 'if newfilter=1 then put ''('';'; put 'else put ''AND ('';'; put 'run;'; put '/* loop through and apply filters for each group membership */'; put '%local fref ds;'; put '%let fref=%mf_getuniquefileref();'; put '%let ds=%mf_getuniquename();'; put 'proc sql noprint;'; put 'select distinct rls_group into : group1 -'; put 'from work.&rlsds;'; put '%do i=1 %to &sqlobs;'; put 'data work.&ds;'; put 'set work.&rlsds;'; put 'where rls_group="&&group&i";'; put 'drop rls_group;'; put 'run;'; put '%mp_filtergenerate(&ds,outref=&fref)'; put 'data _null_;'; put 'infile &fref;'; put 'file &outref mod;'; put 'input;'; put 'if &i>1 and _n_=1 then put '' OR '';'; put 'put _infile_;'; put 'run;'; put '%end;'; put 'data _null_;'; put 'file &outref mod;'; put 'put '')'';'; put 'run;'; put '%end; /* &sqlobs>0 */'; put '%else %do;'; put '%put &sysmacroname: no matching groups;'; put 'data _null_;'; put 'set work.groups;'; put 'putlog (_all_)(=);'; put 'run;'; put '%end;'; put '%mp_abort(iftrue= (&syscc>0)'; put ',mac=&sysmacroname'; put ',msg=%str(Row Level Security Generation Error)'; put ')'; put '%end; /* &admin_check=0 */'; put '%put leaving &sysmacroname with the following query:;'; put '%local empty;'; put '%let empty=0;'; put 'data _null_;'; put 'infile &outref end=eof;'; put 'input;'; put 'putlog _infile_;'; put 'if _n_=1 and eof and cats(_infile_)='''' then do;'; put 'put ''1=1'';'; put 'call symputx(''empty'',1,''l'');'; put 'end;'; put 'run;'; put '%if &empty=1 %then %do;'; put 'data _null_;'; put 'file &outref;'; put 'put ''1=1'';'; put 'run;'; put '%end;'; put '%mend mpe_filtermaster;'; put '%macro mm_assignlib('; put 'libref'; put ',mAbort=HARD'; put ')/*/STORE SOURCE*/;'; put '%local mp_abort msg;'; put '%let mp_abort=0;'; put '%if %sysfunc(libref(&libref)) %then %do;'; put 'data _null_;'; put 'length liburi LibName msg $200;'; put 'call missing(of _all_);'; put 'nobj=metadata_getnobj("omsobj:SASLibrary?@Libref=''&libref''",1,liburi);'; put 'if nobj=1 then do;'; put 'rc=metadata_getattr(liburi,"Name",LibName);'; put '/* now try and assign it */'; put 'if libname("&libref",,''meta'',cats(''liburi="'',liburi,''";'')) ne 0 then do;'; put 'putlog "&libref could not be assigned";'; put 'putlog liburi=;'; put '/**'; put '* Fetch the system message for display in the abort modal. This is'; put '* not always helpful though. One example, previously received:'; put '* NOTE: Libref XX refers to the same library metadata as libref XX.'; put '*/'; put 'msg=sysmsg();'; put 'if msg=:''ERROR: Libref SAVE is not assigned.'' then do;'; put 'msg=catx(" ",'; put '"Could not assign %upcase(&libref).",'; put '"Please check metadata permissions! Libname:",libname,'; put '"Liburi:",liburi'; put ');'; put 'end;'; put 'else if msg="ERROR: User does not have appropriate authorization "!!'; put '"level for library SAVE."'; put 'then do;'; put 'msg=catx(" ",'; put '"ERROR: User does not have appropriate authorization level",'; put '"for library %upcase(&libref), libname:",libname,'; put '"Liburi:",liburi'; put ');'; put 'end;'; put 'call symputx(''msg'',msg,''l'');'; put 'if "&mabort"=''HARD'' then call symputx(''mp_abort'',1,''l'');'; put 'end;'; put 'else do;'; put 'put (_all_)(=);'; put 'call symputx(''libname'',libname,''L'');'; put 'call symputx(''liburi'',liburi,''L'');'; put 'end;'; put 'end;'; put 'else if nobj>1 then do;'; put 'if "&mabort"=''HARD'' then call symputx(''mp_abort'',1);'; put 'call symputx(''msg'',"More than one library with libref=&libref");'; put 'end;'; put 'else do;'; put 'if "&mabort"=''HARD'' then call symputx(''mp_abort'',1);'; put 'call symputx(''msg'',"Library &libref not found in metadata");'; put 'end;'; put 'run;'; put '%put NOTE: &msg;'; put '%end;'; put '%else %do;'; put '%put NOTE: Library &libref is already assigned;'; put '%end;'; put '%mp_abort(iftrue= (&mp_abort=1)'; put ',mac=mm_assignlib.sas'; put ',msg=%superq(msg)'; put ')'; put '%mend mm_assignlib;'; put '/** @cond */'; put '%macro mf_getengine(libref'; put ')/*/STORE SOURCE*/;'; put '%local dsid engnum rc engine;'; put '/* in case the parameter is a libref.tablename, pull off just the libref */'; put '%let libref = %upcase(%scan(&libref, 1, %str(.)));'; put '%let dsid=%sysfunc('; put 'open(sashelp.vlibnam(where=(libname="%upcase(&libref)")),i)'; put ');'; put '%if (&dsid ^= 0) %then %do;'; put '%let engnum=%sysfunc(varnum(&dsid,ENGINE));'; put '%let rc=%sysfunc(fetch(&dsid));'; put '%let engine=%sysfunc(getvarc(&dsid,&engnum));'; put '%put &libref. ENGINE is &engine.;'; put '%let rc= %sysfunc(close(&dsid));'; put '%end;'; put '%upcase(&engine)'; put '%mend mf_getengine;'; put '/** @endcond */'; put '%macro mm_assigndirectlib('; put 'libref'; put ',open_passthrough='; put ',sql_options='; put ',mDebug=0'; put ',mAbort=0'; put ')/*/STORE SOURCE*/;'; put '%local mD;'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing mm_assigndirectlib.sas;'; put '%&mD.put _local_;'; put '%if &mAbort=1 %then %let mAbort=;'; put '%else %let mAbort=%str(*);'; put '%&mD.put NOTE: Creating direct (non META) connection to &libref library;'; put '%local cur_engine;'; put '%let cur_engine=%mf_getengine(&libref);'; put '%if &cur_engine ne META and &cur_engine ne %then %do;'; put '%put NOTE: &libref already has a direct (&cur_engine) libname connection;'; put '%return;'; put '%end;'; put '%else %if %upcase(&libref)=WORK %then %do;'; put '%put NOTE: We already have a direct connection to WORK :-) ;'; put '%return;'; put '%end;'; put '/* need to determine the library ENGINE first */'; put '%local engine;'; put 'data _null_;'; put 'length lib_uri engine $256;'; put 'call missing (of _all_);'; put '/* get URI for the particular library */'; put 'rc1=metadata_getnobj("omsobj:SASLibrary?@Libref =''&libref''",1,lib_uri);'; put '/* get the Engine attribute of the previous object */'; put 'rc2=metadata_getattr(lib_uri,''Engine'',engine);'; put 'putlog "mm_assigndirectlib for &libref:" rc1= lib_uri= rc2= engine=;'; put 'call symputx("liburi",lib_uri,''l'');'; put 'call symputx("engine",engine,''l'');'; put 'run;'; put '/* now obtain engine specific connection details */'; put '%if &engine=BASE %then %do;'; put '%&mD.put NOTE: Retrieving BASE library path;'; put 'data _null_;'; put 'length up_uri $256 path cat_path $1024;'; put 'retain cat_path;'; put 'call missing (of _all_);'; put '/* get all the filepaths of the UsingPackages association */'; put 'i=1;'; put 'rc3=metadata_getnasn("&liburi",''UsingPackages'',i,up_uri);'; put 'do while (rc3>0);'; put '/* get the DirectoryName attribute of the previous object */'; put 'rc4=metadata_getattr(up_uri,''DirectoryName'',path);'; put 'if i=1 then path = ''("''!!trim(path)!!''" '';'; put 'else path ='' "''!!trim(path)!!''" '';'; put 'cat_path = trim(cat_path) !! " " !! trim(path) ;'; put 'i+1;'; put 'rc3=metadata_getnasn("&liburi",''UsingPackages'',i,up_uri);'; put 'end;'; put 'cat_path = trim(cat_path) !! ")";'; put '&mD.putlog "NOTE: Getting physical path for &libref library";'; put '&mD.putlog rc3= up_uri= rc4= cat_path= path=;'; put '&mD.putlog "NOTE: Libname cmd will be:";'; put '&mD.putlog "libname &libref" cat_path;'; put 'call symputx("filepath",cat_path,''l'');'; put 'run;'; put '%if %sysevalf(&sysver<9.4) %then %do;'; put 'libname &libref &filepath;'; put '%end;'; put '%else %do;'; put '/* apply the new filelocks option to cater for temporary locks */'; put 'libname &libref &filepath filelockwait=5;'; put '%end;'; put '%end;'; put '%else %if &engine=REMOTE %then %do;'; put 'data x;'; put 'length rcCon rcProp rc k 3 uriCon uriProp PropertyValue PropertyName'; put 'Delimiter $256 properties $2048;'; put 'retain properties;'; put 'rcCon = metadata_getnasn("&liburi", "LibraryConnection", 1, uriCon);'; put 'rcProp = metadata_getnasn(uriCon, "Properties", 1, uriProp);'; put 'k = 1;'; put 'rcProp = metadata_getnasn(uriCon, "Properties", k, uriProp);'; put 'do while (rcProp > 0);'; put 'rc = metadata_getattr(uriProp , "DefaultValue",PropertyValue);'; put 'rc = metadata_getattr(uriProp , "PropertyName",PropertyName);'; put 'rc = metadata_getattr(uriProp , "Delimiter",Delimiter);'; put 'properties = trim(properties) !! " " !! trim(PropertyName)'; put '!! trim(Delimiter) !! trim(PropertyValue);'; put 'output;'; put 'k+1;'; put 'rcProp = metadata_getnasn(uriCon, "Properties", k, uriProp);'; put 'end;'; put '%&mD.put NOTE: Getting properties for REMOTE SHARE &libref library;'; put '&mD.put _all_;'; put '%&mD.put NOTE: Libname cmd will be:;'; put '%&mD.put libname &libref &engine &properties slibref=&libref;'; put 'call symputx ("properties",trim(properties),''l'');'; put 'run;'; put 'libname &libref &engine &properties slibref=&libref;'; put '%end;'; put '%else %if &engine=OLEDB %then %do;'; put '%&mD.put NOTE: Retrieving OLEDB connection details;'; put 'data _null_;'; put 'length domain datasource provider properties schema'; put 'connx_uri domain_uri conprop_uri lib_uri schema_uri value $256.;'; put 'call missing (of _all_);'; put '/* get source connection ID */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,connx_uri);'; put '/* get connection domain */'; put 'rc1=metadata_getnasn(connx_uri,''Domain'',1,domain_uri);'; put 'rc2=metadata_getattr(domain_uri,''Name'',domain);'; put '&mD.putlog / ''NOTE: '' // ''NOTE- connection id: '' connx_uri ;'; put '&mD.putlog ''NOTE- domain: '' domain;'; put '/* get DSN and PROVIDER from connection properties */'; put 'i=0;'; put 'do until (rc<0);'; put 'i+1;'; put 'rc=metadata_getnasn(connx_uri,''Properties'',i,conprop_uri);'; put 'rc2=metadata_getattr(conprop_uri,''Name'',value);'; put 'if value=''Connection.OLE.Property.DATASOURCE.Name.xmlKey.txt'' then do;'; put 'rc3=metadata_getattr(conprop_uri,''DefaultValue'',datasource);'; put 'end;'; put 'else if value=''Connection.OLE.Property.PROVIDER.Name.xmlKey.txt'' then do;'; put 'rc4=metadata_getattr(conprop_uri,''DefaultValue'',provider);'; put 'end;'; put 'else if value=''Connection.OLE.Property.PROPERTIES.Name.xmlKey.txt'' then'; put 'do;'; put 'rc5=metadata_getattr(conprop_uri,''DefaultValue'',properties);'; put 'end;'; put 'end;'; put '&mD.putlog ''NOTE- dsn/provider/properties: '' /'; put 'datasource provider properties;'; put '&mD.putlog ''NOTE- schema: '' schema // ''NOTE-'';'; put '/* get SCHEMA */'; put 'rc6=metadata_getnasn("&liburi",''UsingPackages'',1,lib_uri);'; put 'rc7=metadata_getattr(lib_uri,''SchemaName'',schema);'; put 'call symputx(''SQL_domain'',domain,''l'');'; put 'call symputx(''SQL_dsn'',datasource,''l'');'; put 'call symputx(''SQL_provider'',provider,''l'');'; put 'call symputx(''SQL_properties'',properties,''l'');'; put 'call symputx(''SQL_schema'',schema,''l'');'; put 'run;'; put '%if %length(&open_passthrough)>0 %then %do;'; put 'proc sql &sql_options;'; put 'connect to OLEDB as &open_passthrough(INSERT_SQL=YES'; put '/* need additional properties to make this work */'; put 'properties=(''Integrated Security''=SSPI'; put '''Persist Security Info''=True'; put '%sysfunc(compress(%str(&SQL_properties),%str(())))'; put ')'; put 'DATASOURCE=&sql_dsn PROMPT=NO'; put 'PROVIDER=&sql_provider SCHEMA=&sql_schema CONNECTION = GLOBAL);'; put '%end;'; put '%else %do;'; put 'LIBNAME &libref OLEDB PROPERTIES=&sql_properties'; put 'DATASOURCE=&sql_dsn PROVIDER=&sql_provider SCHEMA=&sql_schema'; put '%if %length(&sql_domain)>0 %then %do;'; put 'authdomain="&sql_domain"'; put '%end;'; put 'connection=shared;'; put '%end;'; put '%end;'; put '%else %if &engine=ODBC %then %do;'; put '%&mD.put NOTE: Retrieving ODBC connection details;'; put 'data _null_;'; put 'length connx_uri conprop_uri value datasource up_uri schema domprop_uri authdomain $256.;'; put 'call missing (of _all_);'; put '/* get source connection ID */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,connx_uri);'; put '/* get connection properties */'; put 'i=0;'; put 'do until (rc2<0);'; put 'i+1;'; put 'rc2=metadata_getnasn(connx_uri,''Properties'',i,conprop_uri);'; put 'rc3=metadata_getattr(conprop_uri,''Name'',value);'; put 'if value=''Connection.ODBC.Property.DATASRC.Name.xmlKey.txt'' then do;'; put 'rc4=metadata_getattr(conprop_uri,''DefaultValue'',datasource);'; put 'rc2=-1;'; put 'end;'; put 'end;'; put '/* get auth domain */'; put 'autrc=metadata_getnasn(connx_uri,"Domain",1,domprop_uri);'; put 'arc=metadata_getattr(domprop_uri,"Name",authdomain);'; put 'if not missing(authdomain) then authdomain=cats(''AUTHDOMAIN='',authdomain);'; put 'call symputx(''authdomain'',authdomain,''l'');'; put '/* get SCHEMA */'; put 'rc6=metadata_getnasn("&liburi",''UsingPackages'',1,up_uri);'; put 'rc7=metadata_getattr(up_uri,''SchemaName'',schema);'; put '&mD.put rc= connx_uri= rc2= conprop_uri= rc3= value= rc4= datasource='; put 'rc6= up_uri= rc7= schema=;'; put 'call symputx(''SQL_schema'',schema,''l'');'; put 'call symputx(''SQL_dsn'',datasource,''l'');'; put 'run;'; put '%if %length(&open_passthrough)>0 %then %do;'; put 'proc sql &sql_options;'; put 'connect to ODBC as &open_passthrough'; put '(INSERT_SQL=YES DATASRC=&sql_dsn. CONNECTION=global);'; put '%end;'; put '%else %do;'; put 'libname &libref ODBC DATASRC=&sql_dsn SCHEMA=&sql_schema &authdomain;'; put '%end;'; put '%end;'; put '%else %if &engine=POSTGRES %then %do;'; put '%put NOTE: Obtaining POSTGRES library details;'; put 'data _null_;'; put 'length database ignore_read_only_columns direct_exe preserve_col_names'; put 'preserve_tab_names server schema authdomain user password'; put 'prop name value uri urisrc $256.;'; put 'call missing (of _all_);'; put '/* get database value */'; put 'prop=''Connection.DBMS.Property.DB.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,database,"");'; put 'if database^='''' then database=''database=''!!quote(trim(database));'; put 'call symputx(''database'',database,''l'');'; put '/* get IGNORE_READ_ONLY_COLUMNS value */'; put 'prop=''Library.DBMS.Property.DBIROC.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,ignore_read_only_columns,"");'; put 'if ignore_read_only_columns^='''' then ignore_read_only_columns='; put '''ignore_read_only_columns=''!!ignore_read_only_columns;'; put 'call symputx(''ignore_read_only_columns'',ignore_read_only_columns,''l'');'; put '/* get DIRECT_EXE value */'; put 'prop=''Library.DBMS.Property.DirectExe.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,direct_exe,"");'; put 'if direct_exe^='''' then direct_exe=''direct_exe=''!!direct_exe;'; put 'call symputx(''direct_exe'',direct_exe,''l'');'; put '/* get PRESERVE_COL_NAMES value */'; put 'prop=''Library.DBMS.Property.PreserveColNames.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,preserve_col_names,"");'; put 'if preserve_col_names^='''' then preserve_col_names='; put '''preserve_col_names=''!!preserve_col_names;'; put 'call symputx(''preserve_col_names'',preserve_col_names,''l'');'; put '/* get PRESERVE_TAB_NAMES value */'; put '/* be careful with PRESERVE_TAB_NAMES=YES - it will mean your table will'; put 'become case sensitive!! */'; put 'prop=''Library.DBMS.Property.PreserveTabNames.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,preserve_tab_names,"");'; put 'if preserve_tab_names^='''' then preserve_tab_names='; put '''preserve_tab_names=''!!preserve_tab_names;'; put 'call symputx(''preserve_tab_names'',preserve_tab_names,''l'');'; put '/* get SERVER value */'; put 'if metadata_getnasn("&liburi","LibraryConnection",1,uri)>0 then do;'; put 'prop=''Connection.DBMS.Property.SERVER.Name.xmlKey.txt'';'; put 'rc=metadata_getprop(uri,prop,server,"");'; put 'end;'; put 'if server^='''' then server=''server=''!!quote(cats(server));'; put 'call symputx(''server'',server,''l'');'; put '/* get SCHEMA value */'; put 'if metadata_getnasn("&liburi","UsingPackages",1,uri)>0 then do;'; put 'rc=metadata_getattr(uri,"SchemaName",schema);'; put 'end;'; put 'if schema^='''' then schema=''schema=''!!schema;'; put 'call symputx(''schema'',schema,''l'');'; put '/* get AUTHDOMAIN value */'; put '/* this is only useful if the user account contains that auth domain'; put 'if metadata_getnasn("&liburi","DefaultLogin",1,uri)>0 then do;'; put 'rc=metadata_getnasn(uri,"Domain",1,urisrc);'; put 'rc=metadata_getattr(urisrc,"Name",authdomain);'; put 'end;'; put 'if authdomain^='''' then authdomain=''authdomain=''!!quote(trim(authdomain));'; put '*/'; put 'call symputx(''authdomain'',authdomain,''l'');'; put '/* get user & pass */'; put 'if authdomain='''' & metadata_getnasn("&liburi","DefaultLogin",1,uri)>0 then'; put 'do;'; put 'rc=metadata_getattr(uri,"UserID",user);'; put 'rc=metadata_getattr(uri,"Password",password);'; put 'end;'; put 'if user^='''' then do;'; put 'user=''user=''!!quote(trim(user));'; put 'password=''password=''!!quote(trim(password));'; put 'end;'; put 'call symputx(''user'',user,''l'');'; put 'call symputx(''password'',password,''l'');'; put '&md.put _all_;'; put 'run;'; put '%if %length(&open_passthrough)>0 %then %do;'; put '%put %str(WARN)ING: Passthrough option for postgres not yet supported;'; put '%return;'; put '%end;'; put '%else %do;'; put '%if &mdebug=1 %then %do;'; put '%put NOTE: Executing the following:/;'; put '%put NOTE- libname &libref POSTGRES &database &ignore_read_only_columns;'; put '%put NOTE- &direct_exe &preserve_col_names &preserve_tab_names;'; put '%put NOTE- &server &schema &authdomain &user &password //;'; put '%end;'; put 'libname &libref POSTGRES &database &ignore_read_only_columns &direct_exe'; put '&preserve_col_names &preserve_tab_names &server &schema &authdomain'; put '&user &password;'; put '%end;'; put '%end;'; put '%else %if &engine=ORACLE %then %do;'; put '%put NOTE: Obtaining &engine library details;'; put 'data _null_;'; put 'length assocuri1 assocuri2 assocuri3 authdomain path schema $256;'; put 'call missing (of _all_);'; put '/* get auth domain */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,assocuri1);'; put 'rc=metadata_getnasn(assocuri1,''Domain'',1,assocuri2);'; put 'rc=metadata_getattr(assocuri2,"Name",authdomain);'; put 'call symputx(''authdomain'',authdomain,''l'');'; put '/* path */'; put 'rc=metadata_getprop(assocuri1,'; put '''Connection.Oracle.Property.PATH.Name.xmlKey.txt'',path);'; put 'call symputx(''path'',path,''l'');'; put '/* schema */'; put 'rc=metadata_getnasn("&liburi",''UsingPackages'',1,assocuri3);'; put 'rc=metadata_getattr(assocuri3,''SchemaName'',schema);'; put 'call symputx(''schema'',schema,''l'');'; put 'run;'; put '%put NOTE: Executing the following:/; %put NOTE-;'; put '%put NOTE- libname &libref ORACLE path=&path schema=&schema;'; put '%put NOTE- authdomain=&authdomain;'; put '%put NOTE-;'; put 'libname &libref ORACLE path=&path schema=&schema authdomain=&authdomain;'; put '%end;'; put '%else %if &engine=SQLSVR %then %do;'; put '%put NOTE: Obtaining &engine library details;'; put 'data _null;'; put 'length assocuri1 assocuri2 assocuri3 authdomain path schema userid'; put 'passwd $256;'; put 'call missing (of _all_);'; put 'rc=metadata_getnasn("&liburi",''DefaultLogin'',1,assocuri1);'; put 'rc=metadata_getattr(assocuri1,"UserID",userid);'; put 'rc=metadata_getattr(assocuri1,"Password",passwd);'; put 'call symputx(''user'',userid,''l'');'; put 'call symputx(''pass'',passwd,''l'');'; put '/* path */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,assocuri2);'; put 'rc=metadata_getprop(assocuri2,'; put '''Connection.SQL.Property.Datasrc.Name.xmlKey.txt'',path);'; put 'call symputx(''path'',path,''l'');'; put '/* schema */'; put 'rc=metadata_getnasn("&liburi",''UsingPackages'',1,assocuri3);'; put 'rc=metadata_getattr(assocuri3,''SchemaName'',schema);'; put 'call symputx(''schema'',schema,''l'');'; put 'run;'; put '%put NOTE: Executing the following:/; %put NOTE-;'; put '%put NOTE- libname &libref SQLSVR datasrc=&path schema=&schema ;'; put '%put NOTE- user="&user" pass="XXX";'; put '%put NOTE-;'; put 'libname &libref SQLSVR datasrc=&path schema=&schema user="&user" pass="&pass";'; put '%end;'; put '%else %if &engine=TERADATA %then %do;'; put '%put NOTE: Obtaining &engine library details;'; put 'data _null;'; put 'length assocuri1 assocuri2 assocuri3 authdomain path schema userid'; put 'passwd $256;'; put 'call missing (of _all_);'; put '/* get auth domain */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,assocuri1);'; put 'rc=metadata_getnasn(assocuri1,''Domain'',1,assocuri2);'; put 'rc=metadata_getattr(assocuri2,"Name",authdomain);'; put 'call symputx(''authdomain'',authdomain,''l'');'; put '/*'; put 'rc=metadata_getnasn("&liburi",''DefaultLogin'',1,assocuri1);'; put 'rc=metadata_getattr(assocuri1,"UserID",userid);'; put 'rc=metadata_getattr(assocuri1,"Password",passwd);'; put 'call symputx(''user'',userid,''l'');'; put 'call symputx(''pass'',passwd,''l'');'; put '*/'; put '/* path */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,assocuri2);'; put 'rc=metadata_getprop(assocuri2,'; put '''Connection.Teradata.Property.SERVER.Name.xmlKey.txt'',path);'; put 'call symputx(''path'',path,''l'');'; put '/* schema */'; put 'rc=metadata_getnasn("&liburi",''UsingPackages'',1,assocuri3);'; put 'rc=metadata_getattr(assocuri3,''SchemaName'',schema);'; put 'call symputx(''schema'',schema,''l'');'; put 'run;'; put '%put NOTE: Executing the following:/; %put NOTE-;'; put '%put NOTE- libname &libref TERADATA server="&path" schema=&schema ;'; put '%put NOTe- authdomain=&authdomain;'; put '%put NOTE-;'; put 'libname &libref TERADATA server="&path" schema=&schema authdomain=&authdomain;'; put '%end;'; put '%else %if &engine= %then %do;'; put '%put NOTE: Libref &libref is not registered in metadata;'; put '%&mAbort.mp_abort('; put 'msg=%str(ERR)OR: Libref &libref is not registered in metadata'; put ',mac=mm_assigndirectlib.sas);'; put '%return;'; put '%end;'; put '%else %do;'; put '%put %str(WARN)ING: Engine &engine is currently unsupported;'; put '%put %str(WARN)ING- Please contact your support team.;'; put '%return;'; put '%end;'; put '%mend mm_assigndirectlib;'; put '%macro mm_getrepos('; put 'outds=work.mm_getrepos'; put ')/*/STORE SOURCE*/;'; put '* use a temporary fileref to hold the response;'; put 'filename response temp;'; put '/* get list of libraries */'; put 'proc metadata in='; put '"1"'; put 'out=response;'; put 'run;'; put '/* write the response to the log for debugging */'; put '/*'; put 'data _null_;'; put 'infile response lrecl=1048576;'; put 'input;'; put 'put _infile_;'; put 'run;'; put '*/'; put '/* create an XML map to read the response */'; put 'filename sxlemap temp;'; put 'data _null_;'; put 'file sxlemap;'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository";'; put 'put "";'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Id";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Name";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Desc";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@DefaultNS";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@RepositoryType";'; put 'put "characterstring20";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@RepositoryFormat";'; put 'put "characterstring10";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@Access";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@CurrentAccess";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@PauseState";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Path";'; put 'put "";'; put 'put "characterstring256";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Engine";'; put 'put "";'; put 'put "characterstring8";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Options";'; put 'put "";'; put 'put "characterstring32";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@MetadataCreated";'; put 'put "characterstring24";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@MetadataUpdated";'; put 'put "characterstring24";'; put 'put '''';'; put 'put ''
'';'; put 'run;'; put 'libname _XML_ xml xmlfileref=response xmlmap=sxlemap;'; put 'proc sort data= _XML_.SASRepos out=&outds;'; put 'by name;'; put 'run;'; put '/* clear references */'; put 'filename sxlemap clear;'; put 'filename response clear;'; put 'libname _XML_ clear;'; put '%mend mm_getrepos;'; put '%macro dc_assignlib(type,libref,passthru=);'; put '%put &sysmacroname entry vars:;'; put '%put _local_;'; put '/* take current repository */'; put '%local repo repocnt x;'; put '%let repo=%sysfunc(getoption(metarepository));'; put '/* get list of repositories and filter */'; put '%mm_getrepos(outds=repos)'; put '%let repocnt=0;'; put 'data repos;'; put 'set repos;'; put 'where repositorytype in(''CUSTOM'',''FOUNDATION'')'; put '& upcase(name) ne "%upcase(&repo)";'; put 'keep id name ;'; put 'call symputx(cats(''repo'',_n_),name,''l'');'; put 'call symputx(''repocnt'',_n_,''l'');'; put 'run;'; put '/* find out which of these repositories has the libref we are searching for */'; put '%local lib_uri;'; put '%let lib_uri=NOTFOUND;'; put 'data _null_; /* check default repo first */'; put 'length lib_uri $256;'; put 'call missing (of _all_);'; put 'rc=metadata_getnobj("omsobj:SASLibrary?@Libref =''&libref''",1,lib_uri);'; put 'call symputx(''lib_uri'',coalescec(lib_uri,''NOTFOUND''),''l'');'; put 'run;'; put '%if &lib_uri=NOTFOUND %then %do;'; put '%do x=1 %to &repocnt;'; put 'options metarepository=&&repo&x;'; put 'data _null_;'; put 'length lib_uri $256;'; put 'call missing (of _all_);'; put 'rc=metadata_getnobj("omsobj:SASLibrary?@Libref =''&libref''",1,lib_uri);'; put 'call symputx(''lib_uri'',coalescec(lib_uri,''NOTFOUND''),''l'');'; put 'run;'; put '%if &lib_uri ne NOTFOUND %then %let x=%eval(&repocnt+1);'; put '%end;'; put '%end;'; put '%if &type=READ %then %do;'; put '%mm_assignlib(&libref)'; put '%end;'; put '%else %if &type=WRITE %then %do;'; put '%mm_assigndirectlib(&libref,open_passthrough=&passthru)'; put '%end;'; put 'options metarepository=&repo;'; put '%mend dc_assignlib;'; put '%macro mf_abort(mac=mf_abort.sas, msg=, iftrue=%str(1=1)'; put ')/des=''ungraceful abort'' /*STORE SOURCE*/;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%put NOTE: /// mf_abort macro executing //;'; put '%if %length(&mac)>0 %then %put NOTE- called by &mac;'; put '%put NOTE - &msg;'; put '%abort;'; put '%mend mf_abort;'; put '/** @endcond */'; put '%macro mf_verifymacvars('; put 'verifyVars /* list of macro variable NAMES */'; put ',makeUpcase=NO /* set to YES to make all the variable VALUES uppercase */'; put ',mAbort=SOFT'; put ')/*/STORE SOURCE*/;'; put '%local verifyIterator verifyVar abortmsg;'; put '%do verifyIterator=1 %to %sysfunc(countw(&verifyVars,%str( )));'; put '%let verifyVar=%qscan(&verifyVars,&verifyIterator,%str( ));'; put '%if not %symexist(&verifyvar) %then %do;'; put '%let abortmsg= Variable &verifyVar is MISSING;'; put '%goto exit_err;'; put '%end;'; put '%if %length(%trim(&&&verifyVar))=0 %then %do;'; put '%let abortmsg= Variable &verifyVar is EMPTY;'; put '%goto exit_err;'; put '%end;'; put '%if &makeupcase=YES %then %do;'; put '%let &verifyVar=%upcase(&&&verifyvar);'; put '%end;'; put '%end;'; put '%goto exit_success;'; put '%exit_err:'; put '%put &abortmsg;'; put '%mf_abort(iftrue=(&mabort ne SOFT),'; put 'mac=mf_verifymacvars,'; put 'msg=%str(&abortmsg)'; put ')'; put '0'; put '%return;'; put '%exit_success:'; put '1'; put '%mend mf_verifymacvars;'; put '/** @cond */'; put '%macro mf_existfeature(feature'; put ')/*/STORE SOURCE*/;'; put '%let feature=%upcase(&feature);'; put '%local platform;'; put '%let platform=%mf_getplatform();'; put '%if &feature= %then %do;'; put '%put No feature was requested for detection;'; put '%end;'; put '%else %if &feature=COLCONSTRAINTS %then %do;'; put '%if "%substr(&sysver,1,1)"="4" or "%substr(&sysver,1,1)"="5" %then 0;'; put '%else 1;'; put '%end;'; put '%else %if &feature=PROCLUA %then %do;'; put '/* https://blogs.sas.com/content/sasdummy/2015/08/03/using-lua-within-your-sas-programs */'; put '%if &platform=SASVIYA %then 1;'; put '%else %if "&sysver"="9.2" or "&sysver"="9.3" %then 0;'; put '%else %if "&SYSVLONG" < "9.04.01M3" %then 0;'; put '%else 1;'; put '%end;'; put '%else %if &feature=DBMS_MEMTYPE %then %do;'; put '/* does dbms_memtype exist in dictionary.tables? */'; put '%if "%substr(&sysver,1,1)"="4" or "%substr(&sysver,1,1)"="5" %then 0;'; put '%else 1;'; put '%end;'; put '%else %if &feature=EXPORTXLS %then %do;'; put '/* is it possible to PROC EXPORT an excel file? */'; put '%if "%substr(&sysver,1,1)"="4" or "%substr(&sysver,1,1)"="5" %then 1;'; put '%else %if %sysfunc(sysprod(SAS/ACCESS Interface to PC Files)) = 1 %then 1;'; put '%else 0;'; put '%end;'; put '%else %do;'; put '-1'; put '%put &sysmacroname: &feature not found;'; put '%end;'; put '%mend mf_existfeature;'; put '/** @endcond */'; put '%macro mp_ds2cards(base_ds, tgt_ds='; put ',cards_file="%sysfunc(pathname(work))/cardgen.sas"'; put ',maxobs=max'; put ',random_sample=NO'; put ',showlog=YES'; put ',outencoding='; put ',append=NO'; put ')/*/STORE SOURCE*/;'; put '%local i setds nvars;'; put '%if not %sysfunc(exist(&base_ds)) %then %do;'; put '%put %str(WARN)ING: &base_ds does not exist;'; put '%return;'; put '%end;'; put '%if %index(&base_ds,.)=0 %then %let base_ds=WORK.&base_ds;'; put '%if (&tgt_ds = ) %then %let tgt_ds=&base_ds;'; put '%if %index(&tgt_ds,.)=0 %then %let tgt_ds=WORK.%scan(&base_ds,2,.);'; put '%if ("&outencoding" ne "") %then %let outencoding=encoding="&outencoding";'; put '%if ("&append" = "" or "&append" = "NO") %then %let append=;'; put '%else %let append=mod;'; put '/* get varcount */'; put '%let nvars=0;'; put 'proc sql noprint;'; put 'select count(*) into: nvars from dictionary.columns'; put 'where upcase(libname)="%scan(%upcase(&base_ds),1)"'; put 'and upcase(memname)="%scan(%upcase(&base_ds),2)";'; put '%if &nvars=0 %then %do;'; put '%put %str(WARN)ING: Dataset &base_ds has no variables, will not be converted.;'; put '%return;'; put '%end;'; put '/* get indexes */'; put 'proc sort'; put 'data=sashelp.vindex('; put 'where=(upcase(libname)="%scan(%upcase(&base_ds),1)"'; put 'and upcase(memname)="%scan(%upcase(&base_ds),2)")'; put ')'; put 'out=_data_;'; put 'by indxname indxpos;'; put 'run;'; put '%local indexes;'; put 'data _null_;'; put 'set &syslast end=last;'; put 'if _n_=1 then call symputx(''indexes'',''(index=('',''l'');'; put 'by indxname indxpos;'; put 'length vars $32767 nom uni $8;'; put 'retain vars;'; put 'if first.indxname then do;'; put 'idxcnt+1;'; put 'nom='''';'; put 'uni='''';'; put 'vars=name;'; put 'end;'; put 'else vars=catx('' '',vars,name);'; put 'if last.indxname then do;'; put 'if nomiss=''yes'' then nom=''/nomiss'';'; put 'if unique=''yes'' then uni=''/unique'';'; put 'call symputx(''indexes'''; put ',catx('' '',symget(''indexes''),indxname,''=('',vars,'')'',nom,uni)'; put ',''l'');'; put 'end;'; put 'if last then call symputx(''indexes'',cats(symget(''indexes''),''))''),''l'');'; put 'run;'; put 'data;run;'; put '%let setds=&syslast;'; put 'proc sql'; put '%if %datatyp(&maxobs)=NUMERIC %then %do;'; put 'outobs=&maxobs;'; put '%end;'; put ';'; put 'create table &setds as select * from &base_ds'; put '%if &random_sample=YES %then %do;'; put 'order by ranuni(42)'; put '%end;'; put ';'; put 'reset outobs=max;'; put 'create table datalines1 as'; put 'select name,type,length,varnum,format,label from dictionary.columns'; put 'where upcase(libname)="%upcase(%scan(&base_ds,1))"'; put 'and upcase(memname)="%upcase(%scan(&base_ds,2))";'; put '/**'; put 'Due to long decimals cannot use best. format'; put 'So - use bestd. format and then use character functions to strip trailing'; put 'zeros, if NOT an integer or missing!! Cannot use int() as it upsets'; put 'note2err when there are missings.'; put 'resolved code = ifc( mod(coalesce(VARIABLE,0),1)=0'; put ',put(VARIABLE,best32.)'; put ',substrn(put(VARIABLE,bestd32.),1'; put ',findc(put(VARIABLE,bestd32.),''0'',''TBK'')));'; put '**/'; put 'data datalines_2;'; put 'format dataline $32000.;'; put 'set datalines1 (where=(upcase(name) not in'; put '(''PROCESSED_DTTM'',''VALID_FROM_DTTM'',''VALID_TO_DTTM'')));'; put 'if type=''num'' then dataline='; put 'cats(''ifc(mod(coalesce('',name,'',0),1)=0'; put ',put('',name,'',best32.-l)'; put ',substrn(put('',name,'',bestd32.-l),1'; put ',findc(put('',name,'',bestd32.-l),"0","TBK")))'');'; put '/**'; put '* binary data must be converted, to store in text format. It is identified'; put '* by the presence of the $HEX keyword in the format.'; put '*/'; put 'else if upcase(format)=:''$HEX'' then'; put 'dataline=cats(''put(trim('',name,''),'',format,'')'');'; put '/**'; put '* There is no easy way to store line breaks in a cards file.'; put '* To discuss this, use: https://github.com/sasjs/core/issues/80'; put '* Removing all nonprintables with kw (keep writeable)'; put '*/'; put 'else dataline=cats(''compress('',name,'', ,"kw")'');'; put 'run;'; put 'proc sql noprint;'; put 'select dataline into: datalines separated by '','' from datalines_2;'; put '%local'; put 'process_dttm_flg'; put 'valid_from_dttm_flg'; put 'valid_to_dttm_flg'; put ';'; put '%let process_dttm_flg = N;'; put '%let valid_from_dttm_flg = N;'; put '%let valid_to_dttm_flg = N;'; put 'data _null_;'; put 'set datalines1 ;'; put '/* build attrib statement */'; put 'if type=''char'' then type2=''$'';'; put 'if strip(format) ne '''' then format2=cats(''format='',format);'; put 'if strip(label) ne '''' then label2=cats(''label='',quote(trim(label)));'; put 'str1=catx('' '',(put(name,$33.)||''length='')'; put ',put(cats(type2,length),$7.)||format2,label2);'; put '/* Build input statement */'; put 'if upcase(format)=:''$HEX'' then type3='':''!!format;'; put 'else if type=''char'' then type3='':$char.'';'; put 'str2=put(name,$33.)||type3;'; put 'if(upcase(name) = "PROCESSED_DTTM") then'; put 'call symputx("process_dttm_flg", "Y", "L");'; put 'if(upcase(name) = "VALID_FROM_DTTM") then'; put 'call symputx("valid_from_dttm_flg", "Y", "L");'; put 'if(upcase(name) = "VALID_TO_DTTM") then'; put 'call symputx("valid_to_dttm_flg", "Y", "L");'; put 'call symputx(cats("attrib_stmt_", put(_N_, 8.)), str1, "L");'; put 'call symputx(cats("input_stmt_", put(_N_, 8.))'; put ', ifc(upcase(name) not in'; put '(''PROCESSED_DTTM'',''VALID_FROM_DTTM'',''VALID_TO_DTTM''), str2, ""), "L");'; put 'run;'; put 'data _null_;'; put 'file &cards_file. &outencoding lrecl=32767 termstr=nl &append;'; put 'length __attrib $32767;'; put 'if _n_=1 then do;'; put 'put ''/**'';'; put 'put '' @file'';'; put 'put " @brief Datalines for %upcase(%scan(&base_ds,2)) dataset";'; put 'put " @details Generated by %nrstr(%%)mp_ds2cards()";'; put 'put " Source: https://github.com/sasjs/core";'; put 'put '' @cond '';'; put 'put ''**/'';'; put 'put "data &tgt_ds &indexes;";'; put 'put "attrib ";'; put '%do i = 1 %to &nvars;'; put '__attrib=symget("attrib_stmt_&i");'; put 'put __attrib;'; put '%end;'; put 'put ";";'; put '%if &process_dttm_flg. eq Y %then %do;'; put 'put ''retain PROCESSED_DTTM %sysfunc(datetime());'';'; put '%end;'; put '%if &valid_from_dttm_flg. eq Y %then %do;'; put 'put ''retain VALID_FROM_DTTM &low_date;'';'; put '%end;'; put '%if &valid_to_dttm_flg. eq Y %then %do;'; put 'put ''retain VALID_TO_DTTM &high_date;'';'; put '%end;'; put 'if __nobs=0 then do;'; put 'put ''call missing(of _all_);/* avoid uninitialised notes */'';'; put 'put ''stop;'';'; put 'put ''run;'';'; put 'end;'; put 'else do;'; put 'put "infile cards dsd;";'; put 'put "input ";'; put '%do i = 1 %to &nvars.;'; put '%if(%length(&&input_stmt_&i..)) %then'; put 'put " &&input_stmt_&i..";'; put ';'; put '%end;'; put 'put ";";'; put 'put ''missing a b c d e f g h i j k l m n o p q r s t u v w x y z _;'';'; put 'put "datalines4;";'; put 'end;'; put 'end;'; put 'set &setds end=__lastobs nobs=__nobs;'; put '/* remove all formats for write purposes - some have long underlying decimals */'; put 'format _numeric_ best30.29;'; put 'length __dataline $32767;'; put '__dataline=catq(''cqtmb'',&datalines);'; put 'put __dataline;'; put 'if __lastobs then do;'; put 'put '';;;;'';'; put 'put ''run;'';'; put 'put ''/** @endcond **/'';'; put 'stop;'; put 'end;'; put 'run;'; put 'proc sql;'; put 'drop table &setds;'; put 'quit;'; put '%if &showlog=YES %then %do;'; put 'data _null_;'; put 'infile &cards_file lrecl=32767;'; put 'input;'; put 'put _infile_;'; put 'run;'; put '%end;'; put '%put NOTE: CARDS FILE SAVED IN:;'; put '%put NOTE-;%put NOTE-;'; put '%put NOTE- %sysfunc(dequote(&cards_file.));'; put '%put NOTE-;%put NOTE-;'; put '%mend mp_ds2cards;'; put '/** @endcond **/'; put '%macro mp_binarycopy('; put 'inloc= /* full path and filename of the object to be copied */'; put ',outloc= /* full path and filename of object to be created */'; put ',inref=____in /* override default to use own filerefs */'; put ',outref=____out /* override default to use own filerefs */'; put ',mode=CREATE'; put ',iftrue=%str(1=1)'; put ')/*/STORE SOURCE*/;'; put '%local mod;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%if &mode=APPEND %then %let mod=mod;'; put '/* these IN and OUT filerefs can point to anything */'; put '%if &inref = ____in %then %do;'; put 'filename &inref &inloc lrecl=1048576 ;'; put '%end;'; put '%if &outref=____out %then %do;'; put 'filename &outref &outloc lrecl=1048576 &mod;'; put '%end;'; put '/* copy the file byte-for-byte */'; put 'data _null_;'; put 'infile &inref lrecl=1 recfm=n;'; put 'file &outref &mod recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put '%if &inref = ____in %then %do;'; put 'filename &inref clear;'; put '%end;'; put '%if &outref=____out %then %do;'; put 'filename &outref clear;'; put '%end;'; put '%mend mp_binarycopy;'; put '%macro mddl_sas_cntlout(libds=WORK.CNTLOUT);'; put 'proc sql;'; put 'create table &libds('; put 'TYPE char(1) label='; put '''Format Type: either N (num fmt), C (char fmt), I (num infmt) or J (char infmt)'''; put ',FMTNAME char(32) label=''Format name'''; put ',FMTROW num label='; put '''CALCULATED Position of record by FMTNAME (reqd for multilabel formats)'''; put ',START char(32767) label=''Starting value for format'''; put '/*'; put 'Keep lengths of START and END the same to avoid this err:'; put '"Start is greater than end: -<."'; put 'Similar usage note: https://support.sas.com/kb/69/330.html'; put '*/'; put ',END char(32767) label=''Ending value for format'''; put ',LABEL char(32767) label=''Format value label'''; put ',MIN num length=3 label=''Minimum length'''; put ',MAX num length=3 label=''Maximum length'''; put ',DEFAULT num length=3 label=''Default length'''; put ',LENGTH num length=3 label=''Format length'''; put ',FUZZ num label=''Fuzz value'''; put ',PREFIX char(2) label=''Prefix characters'''; put ',MULT num label=''Multiplier'''; put ',FILL char(1) label=''Fill character'''; put ',NOEDIT num length=3 label=''Is picture string noedit?'''; put ',SEXCL char(1) label=''Start exclusion'''; put ',EEXCL char(1) label=''End exclusion'''; put ',HLO char(13) label='; put '''More info: https://core.sasjs.io/mddl__sas__cntlout_8sas_source.html'''; put ',DECSEP char(1) label=''Decimal separator'''; put ',DIG3SEP char(1) label=''Three-digit separator'''; put ',DATATYPE char(8) label=''Date/time/datetime?'''; put ',LANGUAGE char(8) label=''Language for date strings'''; put ');'; put '%local lib;'; put '%let libds=%upcase(&libds);'; put '%if %index(&libds,.)=0 %then %let lib=WORK;'; put '%else %let lib=%scan(&libds,1,.);'; put 'proc datasets lib=&lib noprint;'; put 'modify %scan(&libds,-1,.);'; put 'index create'; put 'pk_cntlout=(type fmtname fmtrow)'; put '/nomiss unique;'; put 'quit;'; put '%mend mddl_sas_cntlout;'; put '%macro mp_aligndecimal(var,width=8);'; put '%local tmpvar;'; put '%let tmpvar=%mf_getuniquename(prefix=aligndp);'; put 'length &tmpvar $&width;'; put 'if index(&var,''.'') then do;'; put '&tmpvar=cats(scan(&var,1,''.''));'; put '&tmpvar=right(&tmpvar);'; put '&var=&tmpvar!!''.''!!cats(scan(&var,2,''.''));'; put 'end;'; put 'else do;'; put '&tmpvar=cats(&var);'; put '&tmpvar=right(&tmpvar);'; put '&var=&tmpvar;'; put 'end;'; put 'drop &tmpvar;'; put '%mend mp_aligndecimal;'; put '%macro mp_cntlout('; put 'iftrue=(1=1)'; put ',libcat='; put ',cntlout=work.fmtextract'; put ',fmtlist=0'; put ')/*/STORE SOURCE*/;'; put '%local ddlds cntlds i;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%let ddlds=%mf_getuniquename();'; put '%let cntlds=%mf_getuniquename();'; put '%mddl_sas_cntlout(libds=&ddlds)'; put '%if %index(&libcat,-)>0 and %scan(&libcat,2,-)=FC %then %do;'; put '%let libcat=%scan(&libcat,1,-);'; put '%end;'; put 'proc format lib=&libcat cntlout=&cntlds;'; put '%if "&fmtlist" ne "0" and "&fmtlist" ne "" %then %do;'; put 'select'; put '%do i=1 %to %sysfunc(countw(&fmtlist,%str( )));'; put '%scan(&fmtlist,&i,%str( ))'; put '%end;'; put ';'; put '%end;'; put 'run;'; put 'data &cntlout/nonote2err;'; put 'if 0 then set &ddlds;'; put 'set &cntlds;'; put 'by type fmtname notsorted;'; put '/* align the numeric values to avoid overlapping ranges */'; put 'if type in ("I","N") then do;'; put '%mp_aligndecimal(start,width=16)'; put '%mp_aligndecimal(end,width=16)'; put 'end;'; put '/* create row marker. Data cannot be sorted without it! */'; put 'if first.fmtname then fmtrow=1;'; put 'else fmtrow+1;'; put 'run;'; put 'proc sort;'; put 'by type fmtname fmtrow;'; put 'run;'; put 'proc sql;'; put 'drop table &ddlds,&cntlds;'; put '%mend mp_cntlout;'; put '/** @endcond */'; put '%macro mfs_httpheader(header_name'; put ',header_value'; put ')/*/STORE SOURCE*/;'; put '%global sasjs_stpsrv_header_loc;'; put '%local fref fid i;'; put '%if %sysfunc(filename(fref,&sasjs_stpsrv_header_loc)) ne 0 %then %do;'; put '%put &=fref &=sasjs_stpsrv_header_loc;'; put '%put %str(ERR)OR: %sysfunc(sysmsg());'; put '%return;'; put '%end;'; put '%let fid=%sysfunc(fopen(&fref,A));'; put '%if &fid=0 %then %do;'; put '%put %str(ERR)OR: %sysfunc(sysmsg());'; put '%return;'; put '%end;'; put '%let rc=%sysfunc(fput(&fid,%str(&header_name): %str(&header_value)));'; put '%let rc=%sysfunc(fwrite(&fid));'; put '%let rc=%sysfunc(fclose(&fid));'; put '%let rc=%sysfunc(filename(&fref));'; put '%mend mfs_httpheader;'; put '%macro mp_streamfile('; put 'contenttype=TEXT'; put ',inloc='; put ',inref=0'; put ',iftrue=%str(1=1)'; put ',outname='; put ',outref=_webout'; put ')/*/STORE SOURCE*/;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%let contentype=%upcase(&contenttype);'; put '%let outref=%upcase(&outref);'; put '%local platform; %let platform=%mf_getplatform();'; put '/**'; put '* check engine type to avoid the below err message:'; put '* > Function is only valid for filerefs using the CACHE access method.'; put '*/'; put '%local streamweb;'; put '%let streamweb=0;'; put 'data _null_;'; put 'set sashelp.vextfl(where=(upcase(fileref)="&outref"));'; put 'if xengine=''STREAM'' then call symputx(''streamweb'',1,''l'');'; put 'run;'; put '%if &contentype=CSV %then %do;'; put '%if (&platform=SASMETA and &streamweb=1) %then %do;'; put 'data _null_;'; put 'rc=stpsrv_header(''Content-Type'',''application/csv'');'; put 'rc=stpsrv_header(''Content-disposition'',"attachment; filename=&outname");'; put 'run;'; put '%end;'; put '%else %if &platform=SASVIYA %then %do;'; put 'filename &outref filesrvc parenturi="&SYS_JES_JOB_URI" name=''_webout.txt'''; put 'contenttype=''application/csv'''; put 'contentdisp="attachment; filename=&outname";'; put '%end;'; put '%else %if &platform=SASJS %then %do;'; put '%mfs_httpheader(Content-Type,application/csv)'; put '%mfs_httpheader(Content-disposition,%str(attachment; filename=&outname))'; put '%end;'; put '%end;'; put '%else %if &contentype=EXCEL %then %do;'; put '/* suitable for XLS format */'; put '%if (&platform=SASMETA and &streamweb=1) %then %do;'; put 'data _null_;'; put 'rc=stpsrv_header(''Content-Type'',''application/vnd.ms-excel'');'; put 'rc=stpsrv_header(''Content-disposition'',"attachment; filename=&outname");'; put 'run;'; put '%end;'; put '%else %if &platform=SASVIYA %then %do;'; put 'filename &outref filesrvc parenturi="&SYS_JES_JOB_URI" name=''_webout.xls'''; put 'contenttype=''application/vnd.ms-excel'''; put 'contentdisp="attachment; filename=&outname";'; put '%end;'; put '%else %if &platform=SASJS %then %do;'; put '%mfs_httpheader(Content-Type,application/vnd.ms-excel)'; put '%mfs_httpheader(Content-disposition,%str(attachment; filename=&outname))'; put '%end;'; put '%end;'; put '%else %if &contentype=GIF or &contentype=JPEG or &contentype=PNG %then %do;'; put '%if (&platform=SASMETA and &streamweb=1) %then %do;'; put 'data _null_;'; put 'rc=stpsrv_header(''Content-Type'',"image/%lowcase(&contenttype)");'; put 'run;'; put '%end;'; put '%else %if &platform=SASVIYA %then %do;'; put 'filename &outref filesrvc parenturi="&SYS_JES_JOB_URI"'; put 'contenttype="image/%lowcase(&contenttype)";'; put '%end;'; put '%else %if &platform=SASJS %then %do;'; put '%mfs_httpheader(Content-Type,image/%lowcase(&contenttype))'; put '%end;'; put '%end;'; put '%else %if &contentype=HTML or &contenttype=MARKDOWN %then %do;'; put '%if (&platform=SASMETA and &streamweb=1) %then %do;'; put 'data _null_;'; put 'rc=stpsrv_header(''Content-Type'',"text/%lowcase(&contenttype)");'; put 'rc=stpsrv_header(''Content-disposition'',"attachment; filename=&outname");'; put 'run;'; put '%end;'; put '%else %if &platform=SASVIYA %then %do;'; put 'filename &outref filesrvc parenturi="&SYS_JES_JOB_URI" name="_webout.json"'; put 'contenttype="text/%lowcase(&contenttype)"'; put 'contentdisp="attachment; filename=&outname";'; put '%end;'; put '%else %if &platform=SASJS %then %do;'; put '%mfs_httpheader(Content-Type,text/%lowcase(&contenttype))'; put '%mfs_httpheader(Content-disposition,%str(attachment; filename=&outname))'; put '%end;'; put '%end;'; put '%else %if &contentype=TEXT %then %do;'; put '%if (&platform=SASMETA and &streamweb=1) %then %do;'; put 'data _null_;'; put 'rc=stpsrv_header(''Content-Type'',''application/text'');'; put 'rc=stpsrv_header(''Content-disposition'',"attachment; filename=&outname");'; put 'run;'; put '%end;'; put '%else %if &platform=SASVIYA %then %do;'; put 'filename &outref filesrvc parenturi="&SYS_JES_JOB_URI" name=''_webout.txt'''; put 'contenttype=''application/text'''; put 'contentdisp="attachment; filename=&outname";'; put '%end;'; put '%else %if &platform=SASJS %then %do;'; put '%mfs_httpheader(Content-Type,application/text)'; put '%mfs_httpheader(Content-disposition,%str(attachment; filename=&outname))'; put '%end;'; put '%end;'; put '%else %if &contentype=WOFF or &contentype=WOFF2 or &contentype=TTF %then %do;'; put '%if (&platform=SASMETA and &streamweb=1) %then %do;'; put 'data _null_;'; put 'rc=stpsrv_header(''Content-Type'',"font/%lowcase(&contenttype)");'; put 'run;'; put '%end;'; put '%else %if &platform=SASVIYA %then %do;'; put 'filename &outref filesrvc parenturi="&SYS_JES_JOB_URI"'; put 'contenttype="font/%lowcase(&contenttype)";'; put '%end;'; put '%else %if &platform=SASJS %then %do;'; put '%mfs_httpheader(Content-Type,font/%lowcase(&contenttype))'; put '%end;'; put '%end;'; put '%else %if &contentype=XLSX %then %do;'; put '%if (&platform=SASMETA and &streamweb=1) %then %do;'; put 'data _null_;'; put 'rc=stpsrv_header(''Content-Type'','; put '''application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'');'; put 'rc=stpsrv_header(''Content-disposition'',"attachment; filename=&outname");'; put 'run;'; put '%end;'; put '%else %if &platform=SASVIYA %then %do;'; put 'filename &outref filesrvc parenturi="&SYS_JES_JOB_URI" name=''_webout.xls'''; put 'contenttype='; put '''application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'''; put 'contentdisp="attachment; filename=&outname";'; put '%end;'; put '%else %if &platform=SASJS %then %do;'; put '%mfs_httpheader(Content-Type'; put ',application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'; put ')'; put '%mfs_httpheader(Content-disposition,%str(attachment; filename=&outname))'; put '%end;'; put '%end;'; put '%else %if &contentype=ZIP %then %do;'; put '%if (&platform=SASMETA and &streamweb=1) %then %do;'; put 'data _null_;'; put 'rc=stpsrv_header(''Content-Type'',''application/zip'');'; put 'rc=stpsrv_header(''Content-disposition'',"attachment; filename=&outname");'; put 'run;'; put '%end;'; put '%else %if &platform=SASVIYA %then %do;'; put 'filename &outref filesrvc parenturi="&SYS_JES_JOB_URI" name=''_webout.zip'''; put 'contenttype=''application/zip'''; put 'contentdisp="attachment; filename=&outname";'; put '%end;'; put '%else %if &platform=SASJS %then %do;'; put '%mfs_httpheader(Content-Type,application/zip)'; put '%mfs_httpheader(Content-disposition,%str(attachment; filename=&outname))'; put '%end;'; put '%end;'; put '%else %do;'; put '%put %str(ERR)OR: Content Type &contenttype NOT SUPPORTED by &sysmacroname!;'; put '%end;'; put '%if &inref ne 0 %then %do;'; put '%mp_binarycopy(inref=&inref,outref=&outref)'; put '%end;'; put '%else %do;'; put '%mp_binarycopy(inloc="&inloc",outref=&outref)'; put '%end;'; put '%mend mp_streamfile;'; put '* SAS Macros end;'; put '* SAS Includes start;'; put '* SAS Includes end;'; put '* Binary Files start;'; put '* Binary Files end;'; put '* ServiceInit start;'; put 'options noquotelenmax ps=max;'; put '/* create dummy macros to prevent stpbegin / stpend from corrupting sessions */'; put '%macro stpbegin();'; put '%put NOTE: the STPBEGIN macro should not be used for web apps!;'; put '%mend stpbegin;'; put '%macro stpend();'; put '%put NOTE: the STPEND macro should not be used for web apps!;'; put '%mend stpend;'; put '* ServiceInit end;'; put '* Service start;'; put '/**'; put '@file'; put '@brief Downloads data in a variety of formats'; put '@details To enable direct download, this service runs in a dedicated stream'; put 'as a GET request using URL parameters as inputs.'; put 'The inputs are:'; put '@li table - the libds of the table to be downloaded'; put '@li type - either SAS, CSV, EXCEL, MARKDOWN, WEBCSV or WEBTAB'; put '@li filter - the filter RK if used'; put '

SAS Macros

'; put '@li mf_verifymacvars.sas'; put '@li mf_getuser.sas'; put '@li mf_existfeature.sas'; put '@li dc_assignlib.sas'; put '@li mp_ds2cards.sas'; put '@li mp_abort.sas'; put '@li mp_binarycopy.sas'; put '@li mp_cntlout.sas'; put '@li mp_streamfile.sas'; put '@li mpe_filtermaster.sas'; put '@version 9.2'; put '@author 4GL Apps Ltd'; put '@copyright 4GL Apps Ltd. This code may only be used within Data Controller'; put 'and may not be re-distributed or re-sold without the express permission of'; put '4GL Apps Ltd.'; put '**/'; put '%global table type filter ds format is_fmt txfrom txto;'; put '%mpeinit()'; put '%let user=%mf_getuser();'; put '%let is_fmt=0;'; put '%mp_abort(iftrue= (%mf_verifymacvars(type table)=0)'; put ',mac=&_program..sas'; put ',msg=%str(Invalid inputs: type table)'; put ')'; put '%let libds=%upcase(&table); /* actual source */'; put '%let table=%upcase(&table); /* used as label for fmt catalogs */'; put '%let lib=%scan(&table,1,.);'; put '%let ds=%scan(&table,2,.);'; put '%dc_assignlib(READ,&lib)'; put 'data _null_;'; put 'set &mpelib..MPE_TABLES;'; put 'where upcase(libref)="&lib" and upcase(dsn)="&ds";'; put '/* if a TXTEMPORAL table then filter as such */'; put 'call symputx(''txfrom'',var_txfrom);'; put 'call symputx(''txto'',var_txto);'; put 'ds=symget(''ds'');'; put 'is_fmt=0;'; put 'if subpad(cats(reverse(ds)),1,3)=:''CF-'' then do;'; put 'ds=scan(ds,1,''-'');'; put 'table=cats("&lib..",ds);'; put 'putlog "Format Catalog Captured";'; put 'is_fmt=1;'; put 'call symputx(''libds'',''work.fmtextract'');'; put 'call symputx(''table'',table);'; put 'end;'; put 'call symputx(''is_fmt'',is_fmt);'; put 'putlog (_all_)(=);'; put 'run;'; put '%mp_cntlout('; put 'iftrue=(&is_fmt=1)'; put ',libcat=&table'; put ',fmtlist=0'; put ',cntlout=work.fmtextract'; put ')'; put '%put preparing query;'; put '%mpe_filtermaster(DLOAD,&libds,'; put 'dclib=&mpelib,'; put 'filter_rk=&filter,'; put 'outref=filtref,'; put 'outds=work.query'; put ')'; put '%put printing generated filterquery:;'; put 'data _null_;'; put 'infile filtref;'; put 'input;'; put 'putlog _infile_;'; put 'run;'; put 'options obs=200000;/* stop limit */'; put 'data staged(drop=&txfrom &txto);'; put 'set &libds;'; put 'where %inc filtref;;'; put 'run;'; put 'options obs=max;'; put 'options validvarname=upcase;'; put '%macro mpestp_getrawdata();'; put '%local outfile;'; put '%if &type=SAS %then %do;'; put '%let outfile=%sysfunc(pathname(work))/&table..sas;'; put '%mp_ds2cards(base_ds=staged'; put ', tgt_ds=&table'; put ', cards_file= "&outfile"'; put ', maxobs=100000)'; put '%let ext=sas;'; put '%let mimetype=text;'; put '%end;'; put '%else %if &type=CSV or (&type=EXCEL and %mf_existfeature(EXPORTXLS) ne 1)'; put '/* cannot proc export excel if PC Files is not licensed */'; put '%then %do;'; put '%let outfile=%sysfunc(pathname(work))/&table..csv;'; put 'PROC EXPORT DATA= staged'; put 'OUTFILE= "&outfile"'; put 'DBMS=csv REPLACE;'; put 'RUN;'; put '%let ext=csv;'; put '%let mimetype=csv;'; put '%end;'; put '%else %if &type=EXCEL %then %do;'; put '%let ext=xlsx;'; put '%let outfile=%sysfunc(pathname(work))/&table..&ext;'; put 'PROC EXPORT DATA= staged'; put 'OUTFILE= "&outfile"'; put 'DBMS=xlsx ;'; put 'RUN;'; put '%let mimetype=XLSX;'; put '%end;'; put '%else %if &type=MARKDOWN %then %do;'; put '%let ext=md;'; put '%let outfile=%sysfunc(pathname(work))/&table..&ext;'; put 'filename mdref "&outfile" lrecl=32767;'; put '%mp_ds2md(staged,outref=mdref,showlog=NO)'; put '%let mimetype=MARKDOWN;'; put '%end;'; put '%else %if &type=WEBCSV %then %do;'; put 'PROC EXPORT DATA= staged'; put 'OUTFILE= _webout'; put 'DBMS=csv REPLACE;'; put 'RUN;'; put '/* don''t set headers */'; put '%return;'; put '%end;'; put '%else %if &type=WEBTAB %then %do;'; put 'PROC EXPORT DATA= staged'; put 'OUTFILE= _webout'; put 'DBMS=tab REPLACE;'; put 'RUN;'; put '/* don''t set headers */'; put '%return;'; put '%end;'; put '%else %do;'; put '%mp_abort(msg=type &type not supported,mac=mpestp_getrawdata.sas);'; put '%end;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(syscc=&syscc)'; put ')'; put '%mp_streamfile(contenttype=&mimetype'; put ',inloc=%str(&outfile)'; put ',outname=&table..&ext'; put ')'; put '%mend mpestp_getrawdata;'; put '%mpestp_getrawdata()'; put '%mpeterm()'; put '* Service end;'; run; %mm_createwebservice(path=&appLoc/&path, name=&service, code=sascode, server=&serverName, replace=yes) filename sascode clear; %let service=refreshlibinfo; filename sascode temp lrecl=32767; data _null_; file sascode; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '/**'; put '@file mp_jsonout.sas'; put '@brief Writes JSON in SASjs format to a fileref'; put '@details This macro can be used to OPEN a JSON stream and send one or more'; put 'tables as arrays of rows, where each row can be an object or a nested array.'; put 'There are two engines available - DATASTEP or PROCJSON.'; put 'PROC JSON is fast but will produce errs like the ones below if'; put 'special chars are encountered.'; put '> (ERR)OR: Some code points did not transcode.'; put '> An object or array close is not valid at this point in the JSON text.'; put '> Date value out of range'; put 'If this happens, try running with ENGINE=DATASTEP.'; put 'The DATASTEP engine is used to handle special SAS missing numerics, and'; put 'can also convert entire datasets to formatted values. Output JSON is always'; put 'in UTF-8.'; put 'Usage:'; put 'filename tmp temp;'; put 'data class; set sashelp.class;run;'; put '%mp_jsonout(OPEN,jref=tmp)'; put '%mp_jsonout(OBJ,class,jref=tmp)'; put '%mp_jsonout(OBJ,class,dslabel=class2,jref=tmp,showmeta=Y)'; put '%mp_jsonout(CLOSE,jref=tmp)'; put 'data _null_;'; put 'infile tmp;'; put 'input;putlog _infile_;'; put 'run;'; put 'If you are building web apps with SAS then you are strongly encouraged to use'; put 'the mX_createwebservice macros in combination with the'; put '[sasjs adapter](https://github.com/sasjs/adapter).'; put 'For more information see https://sasjs.io'; put '@param [in] action Valid values:'; put '@li OPEN - opens the JSON'; put '@li OBJ - sends a table with each row as an object'; put '@li ARR - sends a table with each row in an array'; put '@li CLOSE - closes the JSON'; put '@param [in] ds The dataset to send. Must be a work table.'; put '@param [out] jref= (_webout) The fileref to which to send the JSON'; put '@param [out] dslabel= The name to give the table in the exported JSON'; put '@param [in] fmt= (Y) Whether to keep (Y) or strip (N) formats from the table'; put '@param [in] engine= (DATASTEP) Which engine to use to send the JSON. Options:'; put '@li PROCJSON (default)'; put '@li DATASTEP (more reliable when data has non standard characters)'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to JSON'; put '

Related Files

'; put '@li mp_ds2fmtds.sas'; put '@version 9.2'; put '@author Allan Bowe'; put '@source https://github.com/sasjs/core'; put '**/'; put '%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y'; put ',engine=DATASTEP'; put ',missing=NULL'; put ',showmeta=N'; put ',maxobs=MAX'; put ')/*/STORE SOURCE*/;'; put '%local tempds colinfo fmtds i numcols numobs stmt_obs lastobs optval'; put 'tmpds1 tmpds2 tmpds3 tmpds4;'; put '%let numcols=0;'; put '%if &maxobs ne MAX %then %let stmt_obs=%str(if _n_>&maxobs then stop;);'; put '%if &action=OPEN %then %do;'; put 'options nobomfile;'; put 'data _null_;file &jref encoding=''utf-8'' lrecl=200;'; put 'put ''{"PROCESSED_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''"'';'; put 'run;'; put '%end;'; put '%else %if (&action=ARR or &action=OBJ) %then %do;'; put '/* force variable names to always be uppercase in the JSON */'; put 'options validvarname=upcase;'; put '/* To avoid issues with _webout on EBI - such as encoding diffs and truncation'; put '(https://support.sas.com/kb/49/325.html) we use temporary files */'; put 'filename _sjs1 temp lrecl=200 ;'; put 'data _null_; file _sjs1 encoding=''utf-8'';'; put 'put ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":";'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjs1 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs1 clear;'; put '/* grab col defs */'; put 'proc contents noprint data=&ds'; put 'out=_data_(keep=name type length format formatl formatd varnum label);'; put 'run;'; put '%let colinfo=%scan(&syslast,2,.);'; put 'proc sort data=&colinfo;'; put 'by varnum;'; put 'run;'; put '/* move meta to mac vars */'; put 'data &colinfo;'; put 'if _n_=1 then call symputx(''numcols'',nobs,''l'');'; put 'set &colinfo end=last nobs=nobs;'; put 'name=upcase(name);'; put '/* fix formats */'; put 'if type=2 or type=6 then do;'; put 'typelong=''char'';'; put 'length fmt $49.;'; put 'if format='''' then fmt=cats(''$'',length,''.'');'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else fmt=cats(format,formatl,''.'');'; put 'end;'; put 'else do;'; put 'typelong=''num'';'; put 'if format='''' then fmt=''best.'';'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else if formatd=0 then fmt=cats(format,formatl,''.'');'; put 'else fmt=cats(format,formatl,''.'',formatd);'; put 'end;'; put '/* 32 char unique name */'; put 'newname=''sasjs''!!substr(cats(put(md5(name),$hex32.)),1,27);'; put 'call symputx(cats(''name'',_n_),name,''l'');'; put 'call symputx(cats(''newname'',_n_),newname,''l'');'; put 'call symputx(cats(''length'',_n_),length,''l'');'; put 'call symputx(cats(''fmt'',_n_),fmt,''l'');'; put 'call symputx(cats(''type'',_n_),type,''l'');'; put 'call symputx(cats(''typelong'',_n_),typelong,''l'');'; put 'call symputx(cats(''label'',_n_),coalescec(label,name),''l'');'; put '/* overwritten when fmt=Y and a custom format exists in catalog */'; put 'if typelong=''num'' then call symputx(cats(''fmtlen'',_n_),200,''l'');'; put 'else call symputx(cats(''fmtlen'',_n_),min(32767,ceil((length+10)*1.5)),''l'');'; put 'run;'; put '%let tempds=%substr(_%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql;'; put 'select count(*) into: lastobs from &ds;'; put '%if &maxobs ne MAX %then %let lastobs=%sysfunc(min(&lastobs,&maxobs));'; put '%if &engine=PROCJSON %then %do;'; put '%if &missing=STRING %then %do;'; put '%put &sysmacroname: Special Missings not supported in proc json.;'; put '%put &sysmacroname: Switching to DATASTEP engine;'; put '%goto datastep;'; put '%end;'; put 'data &tempds;'; put 'set &ds;'; put '&stmt_obs;'; put '%if &fmt=N %then format _numeric_ best32.;;'; put '/* PRETTY is necessary to avoid line truncation in large files */'; put 'filename _sjs2 temp lrecl=131068 encoding=''utf-8'';'; put 'proc json out=_sjs2 pretty'; put '%if &action=ARR %then nokeys ;'; put ';export &tempds / nosastags fmtnumeric;'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs2 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs2 clear;'; put '%end;'; put '%else %if &engine=DATASTEP %then %do;'; put '%datastep:'; put '%if %sysfunc(exist(&ds)) ne 1 & %sysfunc(exist(&ds,VIEW)) ne 1'; put '%then %do;'; put '%put &sysmacroname: &ds NOT FOUND!!!;'; put '%return;'; put '%end;'; put '%if &fmt=Y %then %do;'; put '/**'; put '* Extract format definitions'; put '* First, by getting library locations from dictionary.formats'; put '* Then, by exporting the width using proc format'; put '* Cannot use maxw from sashelp.vformat as not always populated'; put '* Cannot use fmtinfo() as not supported in all flavours'; put '*/'; put '%let tmpds1=%substr(fmtsum%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds2=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds3=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds4=%substr(col%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql noprint;'; put 'create table &tmpds1 as'; put 'select cats(libname,''.'',memname) as FMTCAT,'; put 'FMTNAME'; put 'from dictionary.formats'; put 'where fmttype=''F'' and libname is not null'; put 'and fmtname in (select format from &colinfo where format is not null)'; put 'order by 1;'; put 'create table &tmpds2('; put 'FMTNAME char(32),'; put 'LENGTH num'; put ');'; put '%local catlist cat fmtlist i;'; put 'select distinct fmtcat into: catlist separated by '' '' from &tmpds1;'; put '%do i=1 %to %sysfunc(countw(&catlist,%str( )));'; put '%let cat=%scan(&catlist,&i,%str( ));'; put 'proc sql;'; put 'select distinct fmtname into: fmtlist separated by '' '''; put 'from &tmpds1 where fmtcat="&cat";'; put 'proc format lib=&cat cntlout=&tmpds3(keep=fmtname length);'; put 'select &fmtlist;'; put 'run;'; put 'proc sql;'; put 'insert into &tmpds2 select distinct fmtname,length from &tmpds3;'; put '%end;'; put 'proc sql;'; put 'create table &tmpds4 as'; put 'select a.*, b.length as MAXW'; put 'from &colinfo a'; put 'left join &tmpds2 b'; put 'on cats(a.format)=cats(upcase(b.fmtname))'; put 'order by a.varnum;'; put 'data _null_;'; put 'set &tmpds4;'; put 'if not missing(maxw);'; put 'call symputx('; put 'cats(''fmtlen'',_n_),'; put '/* vars need extra padding due to JSON escaping of special chars */'; put 'min(32767,ceil((max(length,maxw)+10)*1.5))'; put ',''l'''; put ');'; put 'run;'; put '/* configure varlenchk - as we are explicitly shortening the variables */'; put '%let optval=%sysfunc(getoption(varlenchk));'; put 'options varlenchk=NOWARN;'; put 'data _data_(compress=char);'; put '/* shorten the new vars */'; put 'length'; put '%do i=1 %to &numcols;'; put '&&name&i $&&fmtlen&i'; put '%end;'; put ';'; put '/* rename on entry */'; put 'set &ds(rename=('; put '%do i=1 %to &numcols;'; put '&&name&i=&&newname&i'; put '%end;'; put '));'; put '&stmt_obs;'; put 'drop'; put '%do i=1 %to &numcols;'; put '&&newname&i'; put '%end;'; put ';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=num %then %do;'; put '&&name&i=cats(put(&&newname&i,&&fmt&i));'; put '%end;'; put '%else %do;'; put '&&name&i=put(&&newname&i,&&fmt&i);'; put '%end;'; put '%end;'; put 'if _error_ then do;'; put 'call symputx(''syscc'',1012);'; put 'stop;'; put 'end;'; put 'run;'; put '%let fmtds=&syslast;'; put 'options varlenchk=&optval;'; put '%end;'; put 'proc format; /* credit yabwon for special null removal */'; put 'value bart (default=40)'; put '%if &missing=NULL %then %do;'; put '._ - .z = null'; put '%end;'; put '%else %do;'; put '._ = [quote()]'; put '. = null'; put '.a - .z = [quote()]'; put '%end;'; put 'other = [best.];'; put 'data &tempds;'; put 'attrib _all_ label='''';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'length &&name&i $&&fmtlen&i...;'; put 'format &&name&i $&&fmtlen&i...;'; put '%end;'; put '%end;'; put '%if &fmt=Y %then %do;'; put 'set &fmtds;'; put '%end;'; put '%else %do;'; put 'set &ds;'; put '%end;'; put '&stmt_obs;'; put 'format _numeric_ bart.;'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'if findc(&&name&i,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put '&&name&i=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,&&name&i)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else &&name&i=quote(cats(&&name&i));'; put '%end;'; put '%end;'; put 'run;'; put 'filename _sjs3 temp lrecl=131068 ;'; put 'data _null_;'; put 'file _sjs3 encoding=''utf-8'';'; put 'if _n_=1 then put "[";'; put 'set &tempds;'; put 'if _n_>1 then put "," @; put'; put '%if &action=ARR %then "[" ; %else "{" ;'; put '%do i=1 %to &numcols;'; put '%if &i>1 %then "," ;'; put '%if &action=OBJ %then """&&name&i"":" ;'; put '"&&name&i"n /* name literal for reserved variable names */'; put '%end;'; put '%if &action=ARR %then "]" ; %else "}" ; ;'; put '/* close out the table */'; put 'data _null_;'; put 'file _sjs3 mod encoding=''utf-8'';'; put 'put '']'';'; put 'run;'; put 'data _null_;'; put 'infile _sjs3 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs3 clear;'; put '%end;'; put 'proc sql;'; put 'drop table &colinfo, &tempds;'; put '%if %substr(&showmeta,1,1)=Y %then %do;'; put 'filename _sjs4 temp lrecl=131068 encoding=''utf-8'';'; put 'data _null_;'; put 'file _sjs4;'; put 'length label $350;'; put 'put ", ""$%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":{""vars"":{";'; put 'do i=1 to &numcols;'; put 'name=quote(trim(symget(cats(''name'',i))));'; put 'format=quote(trim(symget(cats(''fmt'',i))));'; put 'label=quote(prxchange(''s/\\/\\\\/'',-1,trim(symget(cats(''label'',i)))));'; put 'length=quote(trim(symget(cats(''length'',i))));'; put 'type=quote(trim(symget(cats(''typelong'',i))));'; put 'if i>1 then put "," @@;'; put 'put name '':{"format":'' format '',"label":'' label'; put ''',"length":'' length '',"type":'' type ''}'';'; put 'end;'; put 'put ''}}'';'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs4 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs4 clear;'; put '%end;'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put 'data _null_; file &jref encoding=''utf-8'' mod ;'; put 'put "}";'; put 'run;'; put '%end;'; put '%mend mp_jsonout;'; put '/**'; put '@file mm_webout.sas'; put '@brief Send data to/from SAS Stored Processes'; put '@details This macro should be added to the start of each Stored Process,'; put '**immediately** followed by a call to:'; put '%mm_webout(FETCH)'; put 'This will read all the input data and create same-named SAS datasets in the'; put 'WORK library. You can then insert your code, and send data back using the'; put 'following syntax:'; put 'data some datasets; * make some data ;'; put 'retain some columns;'; put 'run;'; put '%mm_webout(OPEN)'; put '%mm_webout(ARR,some) * Array format, fast, suitable for large tables ;'; put '%mm_webout(OBJ,datasets) * Object format, easier to work with ;'; put 'Finally, wrap everything up send some helpful system variables too'; put '%mm_webout(CLOSE)'; put '@param [in] action Either FETCH, OPEN, ARR, OBJ or CLOSE'; put '@param [in] ds The dataset to send back to the frontend'; put '@param [out] dslabel= Value to use instead of table name for sending to JSON'; put '@param [in] fmt= (N) Setting Y converts all vars to their formatted values'; put '@param [out] fref= (_webout) The fileref to which to write the JSON'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] workobs= (0) When set to a positive integer, will create a new'; put 'output object (WORK) which contains this number of observations from all'; put 'tables in the WORK library.'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to output JSON'; put '

SAS Macros

'; put '@li mp_jsonout.sas'; put '

Related Macros

'; put '@li ms_webout.sas'; put '@li mv_webout.sas'; put '@version 9.3'; put '@author Allan Bowe'; put '**/'; put '%macro mm_webout(action,ds,dslabel=,fref=_webout,fmt=N,missing=NULL'; put ',showmeta=N,maxobs=MAX,workobs=0'; put ');'; put '%global _webin_file_count _webin_fileref1 _webin_name1 _program _debug'; put 'sasjs_tables;'; put '%local i tempds jsonengine;'; put '/* see https://github.com/sasjs/core/issues/41 */'; put '%if "%upcase(&SYSENCODING)" ne "UTF-8" %then %let jsonengine=PROCJSON;'; put '%else %let jsonengine=DATASTEP;'; put '%if &action=FETCH %then %do;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'options mprint notes mprintnest;'; put '%end;'; put '%let _webin_file_count=%eval(&_webin_file_count+0);'; put '/* now read in the data */'; put '%do i=1 %to &_webin_file_count;'; put '%if &_webin_file_count=1 %then %do;'; put '%let _webin_fileref1=&_webin_fileref;'; put '%let _webin_name1=&_webin_name;'; put '%end;'; put 'data _null_;'; put 'infile &&_webin_fileref&i termstr=crlf;'; put 'input;'; put 'call symputx(''input_statement'',_infile_);'; put 'putlog "&&_webin_name&i input statement: " _infile_;'; put 'stop;'; put 'data &&_webin_name&i;'; put 'infile &&_webin_fileref&i firstobs=2 dsd termstr=crlf encoding=''utf-8'';'; put 'input &input_statement;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'if _n_<20 then putlog _infile_;'; put '%end;'; put 'run;'; put '%let sasjs_tables=&sasjs_tables &&_webin_name&i;'; put '%end;'; put '%end;'; put '%else %if &action=OPEN %then %do;'; put '/* fix encoding */'; put 'OPTIONS NOBOMFILE;'; put '/**'; put '* check xengine type to avoid the below err message:'; put '* > Function is only valid for filerefs using the CACHE access method.'; put '*/'; put 'data _null_;'; put 'set sashelp.vextfl(where=(fileref="_WEBOUT"));'; put 'if xengine=''STREAM'' then do;'; put 'rc=stpsrv_header(''Content-type'',"text/html; encoding=utf-8");'; put 'end;'; put 'run;'; put '/* setup json */'; put 'data _null_;file &fref encoding=''utf-8'';'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutBEGIN<<'';'; put '%end;'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'run;'; put '%end;'; put '%else %if &action=ARR or &action=OBJ %then %do;'; put '%mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt,jref=&fref'; put ',engine=&jsonengine,missing=&missing,showmeta=&showmeta,maxobs=&maxobs'; put ')'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put '/* To avoid issues with _webout on EBI we use a temporary file */'; put 'filename _sjsref temp lrecl=131068;'; put '%if %str(&workobs) > 0 %then %do;'; put '/* if debug mode, send back first XX records of each work table also */'; put 'data;run;%let tempds=%scan(&syslast,2,.);'; put 'ods output Members=&tempds;'; put 'proc datasets library=WORK memtype=data;'; put '%local wtcnt;%let wtcnt=0;'; put 'data _null_;'; put 'set &tempds;'; put 'if not (upcase(name) =:"DATA"); /* ignore temp datasets */'; put 'i+1;'; put 'call symputx(cats(''wt'',i),name,''l'');'; put 'call symputx(''wtcnt'',i,''l'');'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put ",""WORK"":{";'; put '%do i=1 %to &wtcnt;'; put '%let wt=&&wt&i;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'dsid=open("WORK.&wt",''is'');'; put 'nlobs=attrn(dsid,''NLOBS'');'; put 'nvars=attrn(dsid,''NVARS'');'; put 'rc=close(dsid);'; put 'if &i>1 then put '',''@;'; put 'put " ""&wt"" : {";'; put 'put ''"nlobs":'' nlobs;'; put 'put '',"nvars":'' nvars;'; put '%mp_jsonout(OBJ,&wt,jref=_sjsref,dslabel=first10rows,showmeta=Y'; put ',maxobs=&workobs'; put ')'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put '%end;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put 'run;'; put '%end;'; put '/* close off json */'; put 'data _null_;file _sjsref mod encoding=''utf-8'';'; put 'length SYSPROCESSNAME syserrortext syswarningtext autoexec $512;'; put 'put ",""_DEBUG"" : ""&_debug"" ";'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'autoexec=quote(urlencode(trim(getoption(''autoexec''))));'; put 'put '',"AUTOEXEC" : '' autoexec;'; put 'put ",""MF_GETUSER"" : ""%mf_getuser()"" ";'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'put ",""SYSENCODING"" : ""&sysencoding"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSPROCESSID"" : ""&SYSPROCESSID"" ";'; put 'put ",""SYSPROCESSMODE"" : ""&SYSPROCESSMODE"" ";'; put 'SYSPROCESSNAME=quote(urlencode(cats(SYSPROCESSNAME)));'; put 'put ",""SYSPROCESSNAME"" : " SYSPROCESSNAME;'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put '',"SYSWARNINGTEXT" : '' syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'length memsize $32;'; put 'memsize="%sysfunc(INPUTN(%sysfunc(getoption(memsize)), best.),sizekmg.)";'; put 'memsize=quote(cats(memsize));'; put 'put '',"MEMSIZE" : '' memsize;'; put 'put "}" @;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutEND<<'';'; put '%end;'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjsref lrecl=1 recfm=n;'; put 'file &fref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjsref clear;'; put '%end;'; put '%mend mm_webout;'; put '%macro webout(action,ds,dslabel=,fmt=,missing=NULL,showmeta=NO,maxobs=MAX);'; put '%mm_webout(&action,ds=&ds,dslabel=&dslabel,fmt=&fmt'; put ',missing=&missing'; put ',showmeta=&showmeta'; put ',maxobs=&maxobs'; put ') %mend;'; put '/* provide additional debug info */'; put '%global _program;'; put '%put &=syscc;'; put '%put user=%mf_getuser();'; put '%put pgm=&_program;'; put '%put timestamp=%sysfunc(datetime(),datetime19.);'; put '* Service Variables start;'; put '* Service Variables end;'; put '* SAS Macros start;'; put '%macro mf_getapploc(pgm);'; put '%if "&pgm"="" %then %do;'; put '%if %symexist(_program) %then %let pgm=&_program;'; put '%else %do;'; put '%put &sysmacroname: No value provided and no _program variable available;'; put '%return;'; put '%end;'; put '%end;'; put '%local root;'; put '/**'; put '* First check we are not in the tests/macros folder (which has no subfolders)'; put '* or specifically in the testsetup or testteardown services'; put '*/'; put '%if %index(&pgm,/tests/macros/)'; put 'or %index(&pgm,/tests/testsetup)'; put 'or %index(&pgm,/tests/testteardown)'; put '%then %do;'; put '%let root=%substr(&pgm,1,%index(&pgm,/tests)-1);'; put '&root'; put '%return;'; put '%end;'; put '/**'; put '* Next, move up two levels to avoid matches on subfolder or service name'; put '*/'; put '%let root=%substr(&pgm,1,%length(&pgm)-%length(%scan(&pgm,-1,/))-1);'; put '%let root=%substr(&root,1,%length(&root)-%length(%scan(&root,-1,/))-1);'; put '%if %index(&root,/tests/) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/tests/)-1);'; put '%end;'; put '%else %if %index(&root,/services) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/services)-1);'; put '%end;'; put '%else %if %index(&root,/jobs) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/jobs)-1);'; put '%end;'; put '%else %put &sysmacroname: Could not find an app location from &pgm;'; put '&root'; put '%mend mf_getapploc ;'; put '%macro mf_getuniquefileref(prefix=_,maxtries=1000,lrecl=32767);'; put '%local rc fname;'; put '%if &prefix=0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%end;'; put '%else %do;'; put '%local x len;'; put '%let len=%eval(8-%length(&prefix));'; put '%let x=0;'; put '%do x=0 %to &maxtries;'; put '%let fname=&prefix%substr(%sysfunc(ranuni(0)),3,&len);'; put '%if %sysfunc(fileref(&fname)) > 0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%return;'; put '%end;'; put '%end;'; put '%put unable to find available fileref after &maxtries attempts;'; put '%end;'; put '%mend mf_getuniquefileref;'; put '%macro mp_abort(mac=mp_abort.sas, type=, msg=, iftrue=%str(1=1)'; put ', errds=work.mp_abort_errds'; put ', mode=REGULAR'; put ')/*/STORE SOURCE*/;'; put '%global sysprocessmode sysprocessname sasjs_stpsrv_header_loc sasjsprocessmode;'; put '%local fref fid i;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%put NOTE: /// mp_abort macro executing //;'; put '%if %length(&mac)>0 %then %put NOTE- called by &mac;'; put '%put NOTE - &msg;'; put '%if %symexist(_SYSINCLUDEFILEDEVICE)'; put '/* abort cancel FILE does not restart outside the INCLUDE on Viya 3.5 */'; put 'and %superq(SYSPROCESSNAME) ne %str(Compute Server)'; put '%then %do;'; put '%if "*&_SYSINCLUDEFILEDEVICE*" ne "**" %then %do;'; put 'data &errds;'; put 'iftrue=''1=1'';'; put 'length mac $100 msg $5000;'; put 'mac=symget(''mac'');'; put 'msg=symget(''msg'');'; put 'run;'; put 'data _null_;'; put 'abort cancel FILE;'; put 'run;'; put '%return;'; put '%end;'; put '%end;'; put '/* Web App Context */'; put '%if %symexist(_PROGRAM)'; put 'or %superq(SYSPROCESSNAME) = %str(Compute Server)'; put 'or &mode=INCLUDE'; put '%then %do;'; put 'options obs=max replace mprint;'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5"'; put '%then %do;'; put 'options nosyntaxcheck;'; put '%end;'; put '%if &mode=INCLUDE %then %do;'; put '%if %sysfunc(exist(&errds))=1 %then %do;'; put 'data _null_;'; put 'set &errds;'; put 'call symputx(''iftrue'',iftrue,''l'');'; put 'call symputx(''mac'',mac,''l'');'; put 'call symputx(''msg'',msg,''l'');'; put 'putlog (_all_)(=);'; put 'run;'; put '%if (&iftrue)=0 %then %return;'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: No include errors found;'; put '%return;'; put '%end;'; put '%end;'; put '/* extract log errs / warns, if exist */'; put '%local logloc logline;'; put '%global logmsg; /* capture global messages */'; put '%if %symexist(SYSPRINTTOLOG) %then %let logloc=&SYSPRINTTOLOG;'; put '%else %let logloc=%qsysfunc(getoption(LOG));'; put 'proc printto log=log;run;'; put '%let logline=0;'; put '%if %length(&logloc)>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input; putlog _infile_;'; put 'i=1;'; put 'retain logonce 0;'; put 'if ('; put '_infile_=:"%str(WARN)ING" or _infile_=:"%str(ERR)OR"'; put ') and logonce=0 then'; put 'do;'; put 'call symputx(''logline'',_n_);'; put 'logonce+1;'; put 'end;'; put 'run;'; put '/* capture log including lines BEFORE the err */'; put '%if &logline>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input;'; put 'i=1;'; put 'stoploop=0;'; put 'if _n_ ge &logline-15 and stoploop=0 then do until (i>22);'; put 'call symputx(''logmsg'',catx(''\n'',symget(''logmsg''),_infile_));'; put 'input;'; put 'i+1;'; put 'stoploop=1;'; put 'end;'; put 'if stoploop=1 then stop;'; put 'run;'; put '%end;'; put '%end;'; put '%if %symexist(SYS_JES_JOB_URI) %then %do;'; put '/* setup webout for Viya */'; put 'options nobomfile;'; put '%if "X&SYS_JES_JOB_URI.X"="XX" %then %do;'; put 'filename _webout temp lrecl=999999 mod;'; put '%end;'; put '%else %do;'; put 'filename _webout filesrvc parenturi="&SYS_JES_JOB_URI"'; put 'name="_webout.json" lrecl=999999 mod;'; put '%end;'; put '%end;'; put '%else %if %sysfunc(filename(fref,&sasjs_stpsrv_header_loc))=0 %then %do;'; put 'options nobomfile;'; put '/* set up http header for SASjs Server */'; put '%let fid=%sysfunc(fopen(&fref,A));'; put '%if &fid=0 %then %do;'; put '%put %str(ERR)OR: %sysfunc(sysmsg());'; put '%return;'; put '%end;'; put '%let rc=%sysfunc(fput(&fid,%str(Content-Type: application/json)));'; put '%let rc=%sysfunc(fwrite(&fid));'; put '%let rc=%sysfunc(fclose(&fid));'; put '%let rc=%sysfunc(filename(&fref));'; put '%end;'; put '/* send response in SASjs JSON format */'; put 'data _null_;'; put 'file _webout mod lrecl=32000 encoding=''utf-8'';'; put 'length msg syswarningtext syserrortext $32767 mode $10 ;'; put 'sasdatetime=datetime();'; put 'msg=symget(''msg'');'; put '%if &logline>0 %then %do;'; put 'msg=cats(msg,''\n\nLog Extract:\n'',symget(''logmsg''));'; put '%end;'; put '/* escape the escapes */'; put 'msg=tranwrd(msg,''\'',''\\'');'; put '/* escape the quotes */'; put 'msg=tranwrd(msg,''"'',''\"'');'; put '/* ditch the CRLFs as chrome complains */'; put 'msg=compress(msg,,''kw'');'; put '/* quote without quoting the quotes (which are escaped instead) */'; put 'msg=cats(''"'',msg,''"'');'; put 'if symexist(''_debug'') then debug=quote(trim(symget(''_debug'')));'; put 'else debug=''""'';'; put 'if symget(''sasjsprocessmode'')=''Stored Program'' then mode=''SASJS'';'; put 'if mode ne ''SASJS'' then put ''>>weboutBEGIN<<'';'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'put '',"sasjsAbort" : [{'';'; put 'put '' "MSG":'' msg ;'; put 'put '' ,"MAC": "'' "&mac" ''"}]'';'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'put '',"_DEBUG":'' debug ;'; put 'if symexist(''_metauser'') then do;'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put 'end;'; put 'if symexist(''SYS_JES_JOB_URI'') then do;'; put 'SYS_JES_JOB_URI=quote(trim(symget(''SYS_JES_JOB_URI'')));'; put 'put '',"SYS_JES_JOB_URI": '' SYS_JES_JOB_URI;'; put 'end;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put ",""SYSWARNINGTEXT"" : " syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'put "}" ;'; put 'if mode ne ''SASJS'' then put ''>>weboutEND<<'';'; put 'run;'; put '%put _all_;'; put '%if "&sysprocessmode " = "SAS Stored Process Server " %then %do;'; put 'data _null_;'; put 'putlog ''stpsrvset program err and syscc'';'; put 'rc=stpsrvset(''program error'', 0);'; put 'call symputx("syscc",0,"g");'; put 'run;'; put '%if &sysscp=WIN'; put 'and 1=0 /* deprecating this logic until we figure out a consistent abort */'; put 'and "%substr(%str(&sysvlong ),1,8)"="9.04.01M"'; put 'and "%substr(%str(&sysvlong ),9,1)">"5" %then %do;'; put '/* skip approach (below) does not work in windows m6+ envs */'; put 'endsas;'; put '%end;'; put '%else %do;'; put '/**'; put '* endsas kills 9.4m3 deployments by orphaning multibridges.'; put '* Abort variants are ungraceful (non zero return code)'; put '* This approach lets SAS run silently until the end :-)'; put '* Caution - fails when called within a %include within a macro'; put '* Use mp_include() to handle this.'; put '*/'; put 'filename skip temp;'; put 'data _null_;'; put 'file skip;'; put 'put ''%macro skip();'';'; put 'comment ''%mend skip; -> fix lint '';'; put 'put ''%macro skippy();'';'; put 'comment ''%mend skippy; -> fix lint '';'; put 'run;'; put '%inc skip;'; put '%end;'; put '%end;'; put '%else %if "&sysprocessmode " = "SAS Compute Server " %then %do;'; put '/* endsas kills the session making it harder to fetch results */'; put 'data _null_;'; put 'syswarningtext=symget(''syswarningtext'');'; put 'syserrortext=symget(''syserrortext'');'; put 'abort_msg=symget(''msg'');'; put 'syscc=symget(''syscc'');'; put 'sysuserid=symget(''sysuserid'');'; put 'iftrue=symget(''iftrue'');'; put 'put (_all_)(/=);'; put 'call symputx(''syscc'',0);'; put 'abort cancel nolist;'; put 'run;'; put '%end;'; put '%else %do;'; put '%abort cancel;'; put '%end;'; put '%end;'; put '%else %do;'; put '%put _all_;'; put '%abort cancel;'; put '%end;'; put '%mend mp_abort;'; put '/** @endcond */'; put '%macro mm_getstpcode('; put 'tree=/User Folders/sasdemo/somestp'; put ',name='; put ',outloc=0'; put ',outref=0'; put ',mDebug=1'; put ',showlog=NO'; put ');'; put '%local mD;'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing &sysmacroname..sas;'; put '%&mD.put _local_;'; put '%if %length(&name)>0 %then %let name=/&name;'; put '/* first, check if STP exists */'; put '%local tsuri;'; put '%let tsuri=stopifempty ;'; put 'data _null_;'; put 'format type uri tsuri value $200.;'; put 'call missing (of _all_);'; put 'path="&tree&name(StoredProcess)";'; put '/* first, find the STP ID */'; put 'if metadata_pathobj("",path,"StoredProcess",type,uri)>0 then do;'; put '/* get sourcecode */'; put 'cnt=1;'; put 'do while (metadata_getnasn(uri,"Notes",cnt,tsuri)>0);'; put 'rc=metadata_getattr(tsuri,"Name",value);'; put '&mD.put tsuri= value=;'; put 'if value="SourceCode" then do;'; put '/* found it! */'; put 'rc=metadata_getattr(tsuri,"Id",value);'; put 'call symputx(''tsuri'',value,''l'');'; put 'stop;'; put 'end;'; put 'cnt+1;'; put 'end;'; put 'end;'; put 'else put (_all_)(=);'; put 'run;'; put '%mp_abort(iftrue= (&tsuri=stopifempty)'; put ',mac=mm_getstpcode'; put ',msg=%str(&tree&name.(StoredProcess) not found!)'; put ')'; put '/**'; put '* Now we can extract the textstore'; put '*/'; put 'filename __getdoc temp lrecl=10000000;'; put 'proc metadata'; put 'in="$METAREPOSITORY'; put ''; put 'SAS1"'; put 'out=__getdoc ;'; put 'run;'; put '/* find the beginning of the text */'; put '%local start;'; put 'data _null_;'; put 'infile __getdoc lrecl=10000;'; put 'input;'; put 'start=index(_infile_,''StoredText="'');'; put 'if start then do;'; put 'call symputx("start",start+11);'; put '*putlog ''"'' _infile_ ''"'';'; put 'end;'; put 'stop;'; put '%local outeng;'; put '%if "&outloc"="0" %then %let outeng=TEMP;'; put '%else %let outeng="&outloc";'; put '%local fref;'; put '%if &outref=0 %then %let fref=%mf_getuniquefileref();'; put '%else %let fref=&outref;'; put '/* read the content, byte by byte, resolving escaped chars */'; put 'filename &fref &outeng lrecl=100000;'; put 'data _null_;'; put 'length filein 8 fileid 8;'; put 'filein = fopen("__getdoc","I",1,"B");'; put 'fileid = fopen("&fref","O",1,"B");'; put 'rec = "20"x;'; put 'length entity $6;'; put 'do while(fread(filein)=0);'; put 'x+1;'; put 'if x>&start then do;'; put 'rc = fget(filein,rec,1);'; put 'if rec=''"'' then leave;'; put 'else if rec="&" then do;'; put 'entity=rec;'; put 'do until (rec=";");'; put 'if fread(filein) ne 0 then goto getout;'; put 'rc = fget(filein,rec,1);'; put 'entity=cats(entity,rec);'; put 'end;'; put 'select (entity);'; put 'when (''&'' ) rec=''&'' ;'; put 'when (''<'' ) rec=''<'' ;'; put 'when (''>'' ) rec=''>'' ;'; put 'when (''''') rec="''" ;'; put 'when (''"'') rec=''"'' ;'; put 'when ('' '') rec=''0A''x;'; put 'when ('' '') rec=''0D''x;'; put 'when (''$'' ) rec=''$'' ;'; put 'when ('' '') rec=''09''x;'; put 'otherwise putlog "%str(WARN)ING: missing value for " entity=;'; put 'end;'; put 'rc =fput(fileid, substr(rec,1,1));'; put 'rc =fwrite(fileid);'; put 'end;'; put 'else do;'; put 'rc =fput(fileid,rec);'; put 'rc =fwrite(fileid);'; put 'end;'; put 'end;'; put 'end;'; put 'getout:'; put 'rc=fclose(filein);'; put 'rc=fclose(fileid);'; put 'run;'; put '%if &showlog=YES %then %do;'; put 'data _null_;'; put 'infile &fref lrecl=32767 end=last;'; put 'input;'; put 'if _n_=1 then putlog ''>>stpcodeBEGIN<<'';'; put 'putlog _infile_;'; put 'if last then putlog ''>>stpcodeEND<<'';'; put 'run;'; put '%end;'; put 'filename __getdoc clear;'; put '%if &outref=0 %then %do;'; put 'filename &fref clear;'; put '%end;'; put '%mend mm_getstpcode;'; put '%macro dc_getsettings();'; put '%global _program;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(syscc=&syscc on entry (&syswarningtext &syserrortext))'; put ')'; put '%if %length(&_PROGRAM)>1 %then %let root=&_program;'; put '%else %do;'; put '%global _metauser;'; put '%let _metauser=&sysuserid;'; put '/* to mimic a "real" _program we need to give a dummy role and stp name */'; put '%let root=/dummyRole/dummyName;'; put '%end;'; put '/* the DC precode is stored in the Admin folder in the root of'; put 'the project. Lets find that root. */'; put '%put &=root;'; put '%let root=%mf_getapploc();'; put '%put &=root;'; put '/* Now we know the root location we can retrieve the params */'; put '%let temploc=%sysfunc(pathname(work))/settings.txt;'; put '%mm_getstpcode(tree=&root/services/public'; put ',name=Data_Controller_Settings'; put ',outloc=&temploc'; put ')'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Unable to run getstpcode)'; put ')'; put 'filename _getsets "&temploc" lrecl=2000;'; put '/*'; put 'Do not use mp_include here - this puts a copy in every service, which creates'; put 'compilation problems when calling services from mp_include'; put '*/'; put '%inc _getsets/source2;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Problem running &sysmacroname (&syswarningtext &syserrortext))'; put ')'; put '%mend dc_getsettings;'; put '%macro mf_fmtdttm('; put ')/*/STORE SOURCE*/;'; put '%if "&sysver"="9.2" or "&sysver"="9.3"'; put 'or ("&sysver"="9.4" and "%substr(&SYSVLONG,9,1)" le "3")'; put 'or "%substr(&sysver,1,1)"="4"'; put 'or "%substr(&sysver,1,1)"="5"'; put '%then %do;DATETIME19.3%end;'; put '%else %do;E8601DT26.6%end;'; put '%mend mf_fmtdttm;'; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '%macro mp_init(prefix=SASJS'; put ')/*/STORE SOURCE*/;'; put '%if %symexist(SASJS_PREFIX) %then %return; /* only run once */'; put '%global'; put 'SASJS_PREFIX /* the ONLY hard-coded global macro variable in SASjs */'; put '&prefix._FUNCTIONS /* used in mcf_init() to track core function compilation */'; put '&prefix._INIT_NUM /* initialisation time as numeric */'; put '&prefix._INIT_DTTM /* initialisation time in E8601DT26.6 format */'; put '&prefix.WORK /* avoid typing %sysfunc(pathname(work)) every time */'; put ';'; put '%let sasjs_prefix=&prefix;'; put 'data _null_;'; put 'dttm=datetime();'; put 'call symputx("&sasjs_prefix._init_num",dttm,''g'');'; put 'call symputx("&sasjs_prefix._init_dttm",put(dttm,E8601DT26.6),''g'');'; put 'call symputx("&sasjs_prefix.work",pathname(''WORK''),''g'');'; put 'run;'; put 'options'; put 'compress=CHAR /* default is none so ensure we have something! */'; put 'datastmtchk=ALLKEYWORDS /* protection from overwriting input datasets */'; put 'errorcheck=STRICT /* catch errs in libname/filename statements */'; put 'fmterr /* ensure err when a format cannot be found */'; put 'mergenoby=%str(ERR)OR /* throw err when a merge has no BY variables */'; put 'missing=. /* changing this can cause hard to detect errs */'; put 'noquotelenmax /* avoid warnings for long strings */'; put 'noreplace /* avoid overwriting permanent datasets */'; put 'ps=max /* reduce log size slightly */'; put 'ls=max /* reduce log even more and avoid word truncation */'; put 'validmemname=COMPATIBLE /* avoid special characters etc in table names */'; put 'validvarname=V7 /* avoid special characters etc in variable names */'; put 'varinitchk=%str(ERR)OR /* avoid data mistakes from variable name typos */'; put 'varlenchk=%str(ERR)OR /* fail hard if truncation (data loss) can result */'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5" %then %do;'; put 'noautocorrect /* disallow misspelled procedure names */'; put 'dsoptions=note2err /* undocumented - convert bad NOTEs to ERRs */'; put '%end;'; put ';'; put '%mend mp_init;'; put '%macro mpeinit(fetch=YES);'; put '%global mpeinit'; put 'mpeadmins /* group with unrestricted Meditor access */'; put 'mpelocapprovals /* location for landing and staging files */'; put 'mpelib /* location of configuration tables for DC */'; put 'dc_repo_users /* location of user / group metadata */'; put 'dc_licence_key /* extracted in dc_getsettings */'; put 'dc_activation_key /* extracted in dc_getsettings */'; put 'dc_locale /* extracted in dc_getsettings */'; put 'dc_dttmtfmt /* can be overridden in dc_getsettings */'; put '_debug'; put ';'; put '%if &mpeinit=1 %then %return;'; put '%else %let mpeinit=1;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(Problem on service startup (&syswarningtext &syserrortext))'; put ')'; put '%mp_init()'; put '%if &fetch=YES %then %do;'; put '%webout(FETCH)'; put '%end;'; put '%global _CLIENTNAME;'; put '%mp_abort(iftrue= (&_CLIENTNAME=SAS Enterprise Guide)'; put ',mac=&_program..sas'; put ',msg=%str(Data Controller is a web app and should not be executed from EG)'; put ')'; put 'options urlencoding=utf8 nobomfile lrecl=32767;'; put '%let perf=%sysfunc(datetime());'; put '%put perfdiff: 0;'; put '%let dc_locale=SYSTEM; /* default if not set */'; put '/**'; put '* E8601DT26.6 has widest database support - but not all SAS flavours can'; put '* handle it. Override in the settings STP if needed.'; put '*/'; put 'data _null_;'; put 'dc_dttmtfmt=''"%sysfunc(datetime(),''!!"%mf_fmtdttm()"!!'')"dt'';'; put 'call symputx(''dc_dttmtfmt'',dc_dttmtfmt);'; put 'put dc_dttmtfmt=;'; put 'run;'; put '%put &=dc_dttmtfmt;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc prior to dc_getsettings)'; put ')'; put '%dc_getsettings()'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc after dc_getsettings)'; put ')'; put 'data _null_;'; put 'set &DC_LIBREF..mpe_config(where=('; put 'var_scope="DC"'; put 'and &dc_dttmtfmt lt tx_to'; put 'and var_active=1'; put '));'; put 'call symputx(var_name,var_value,''G'');'; put 'putlog var_name "=" var_value;'; put 'run;'; put '%let mpelib=&dc_libref;'; put '%let mpeadmins=&dc_admin_group;'; put '%let mpelocapprovals=&dc_staging_area;'; put '%let dc_repo_users=&dc_repo_users;'; put '%if &dc_locale ne SYSTEM %then %do;'; put 'options locale=&dc_locale;'; put '%end;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(Problem during compilation or with STP precode (&syswarningtext))'; put ')'; put '%mend mpeinit;'; put '%macro mf_mval(var);'; put '%if %symexist(&var) %then %do;'; put '%superq(&var)'; put '%end;'; put '%mend mf_mval;'; put '%macro mf_trimstr(basestr,trimstr);'; put '%local baselen trimlen trimval;'; put '/* return if basestr is shorter than trimstr (or 0) */'; put '%let baselen=%length(%superq(basestr));'; put '%let trimlen=%length(%superq(trimstr));'; put '%if &baselen < &trimlen or &baselen=0 %then %return;'; put '/* obtain the characters from the end of basestr */'; put '%let trimval=%qsubstr(%superq(basestr)'; put ',%length(%superq(basestr))-&trimlen+1'; put ',&trimlen);'; put '/* compare and if matching, chop it off! */'; put '%if %superq(basestr)=%superq(trimstr) %then %do;'; put '%return;'; put '%end;'; put '%else %if %superq(trimval)=%superq(trimstr) %then %do;'; put '%qsubstr(%superq(basestr),1,%length(%superq(basestr))-&trimlen)'; put '%end;'; put '%else %do;'; put '&basestr'; put '%end;'; put '%mend mf_trimstr;'; put '%macro mf_getplatform(switch'; put ')/*/STORE SOURCE*/;'; put '%local a b c;'; put '%if &switch.NONE=NONE %then %do;'; put '%if %symexist(sasjsprocessmode) %then %do;'; put '%if &sasjsprocessmode=Stored Program %then %do;'; put 'SASJS'; put '%return;'; put '%end;'; put '%end;'; put '%if %symexist(sysprocessmode) %then %do;'; put '%if "&sysprocessmode"="SAS Object Server"'; put 'or "&sysprocessmode"= "SAS Compute Server" %then %do;'; put 'SASVIYA'; put '%end;'; put '%else %if "&sysprocessmode"="SAS Stored Process Server"'; put 'or "&sysprocessmode"="SAS Workspace Server"'; put '%then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if %symexist(_metaport) or %symexist(_metauser) %then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if &switch=SASSTUDIO %then %do;'; put '/* return the version of SAS Studio else 0 */'; put '%if %mf_mval(_CLIENTAPP)=%str(SAS Studio) %then %do;'; put '%let a=%mf_mval(_CLIENTVERSION);'; put '%let b=%scan(&a,1,.);'; put '%if %eval(&b >2) %then %do;'; put '&b'; put '%end;'; put '%else 0;'; put '%end;'; put '%else 0;'; put '%end;'; put '%else %if &switch=VIYARESTAPI %then %do;'; put '%mf_trimstr(%sysfunc(getoption(servicesbaseurl)),/)'; put '%end;'; put '%mend mf_getplatform;'; put '%macro mpeterm();'; put '%local oldloc;'; put 'data _null_;'; put 'if symexist(''SYSPRINTTOLOG'') then oldloc=symget(''SYSPRINTTOLOG'');'; put 'else oldloc=getoption(''LOG'');'; put 'if subpad(oldloc,1,1) not in (''"'',"''",'' '') then oldloc=quote(cats(oldloc));'; put 'call symputx(''oldloc'',oldloc,''l'');'; put 'run;'; put '%if %length(&oldloc)>0 %then %do;'; put 'proc printto log=log;'; put 'run;'; put 'data _null_;'; put 'infile &oldloc;'; put 'input; putlog _infile_;'; put 'run;'; put '%end;'; put '%if %sysfunc(exist(&dc_libref..mpe_requests)) and %mf_getplatform() ne SASVIYA'; put '%then %do;'; put 'data ;'; put 'if 0 then set &dc_libref..mpe_requests;'; put 'request_dttm=%sysfunc(datetime());'; put 'request_user="%mf_getuser()";'; put 'request_service="%scan(&_program,-2,/)/%scan(&_program,-1,/)";'; put 'request_params='''';'; put 'output;stop;'; put 'proc append base=&dc_libref..mpe_requests data=&syslast force nowarn;'; put 'run;'; put '%end;'; put '%mend mpeterm;'; put '%macro mm_assignlib('; put 'libref'; put ',mAbort=HARD'; put ')/*/STORE SOURCE*/;'; put '%local mp_abort msg;'; put '%let mp_abort=0;'; put '%if %sysfunc(libref(&libref)) %then %do;'; put 'data _null_;'; put 'length liburi LibName msg $200;'; put 'call missing(of _all_);'; put 'nobj=metadata_getnobj("omsobj:SASLibrary?@Libref=''&libref''",1,liburi);'; put 'if nobj=1 then do;'; put 'rc=metadata_getattr(liburi,"Name",LibName);'; put '/* now try and assign it */'; put 'if libname("&libref",,''meta'',cats(''liburi="'',liburi,''";'')) ne 0 then do;'; put 'putlog "&libref could not be assigned";'; put 'putlog liburi=;'; put '/**'; put '* Fetch the system message for display in the abort modal. This is'; put '* not always helpful though. One example, previously received:'; put '* NOTE: Libref XX refers to the same library metadata as libref XX.'; put '*/'; put 'msg=sysmsg();'; put 'if msg=:''ERROR: Libref SAVE is not assigned.'' then do;'; put 'msg=catx(" ",'; put '"Could not assign %upcase(&libref).",'; put '"Please check metadata permissions! Libname:",libname,'; put '"Liburi:",liburi'; put ');'; put 'end;'; put 'else if msg="ERROR: User does not have appropriate authorization "!!'; put '"level for library SAVE."'; put 'then do;'; put 'msg=catx(" ",'; put '"ERROR: User does not have appropriate authorization level",'; put '"for library %upcase(&libref), libname:",libname,'; put '"Liburi:",liburi'; put ');'; put 'end;'; put 'call symputx(''msg'',msg,''l'');'; put 'if "&mabort"=''HARD'' then call symputx(''mp_abort'',1,''l'');'; put 'end;'; put 'else do;'; put 'put (_all_)(=);'; put 'call symputx(''libname'',libname,''L'');'; put 'call symputx(''liburi'',liburi,''L'');'; put 'end;'; put 'end;'; put 'else if nobj>1 then do;'; put 'if "&mabort"=''HARD'' then call symputx(''mp_abort'',1);'; put 'call symputx(''msg'',"More than one library with libref=&libref");'; put 'end;'; put 'else do;'; put 'if "&mabort"=''HARD'' then call symputx(''mp_abort'',1);'; put 'call symputx(''msg'',"Library &libref not found in metadata");'; put 'end;'; put 'run;'; put '%put NOTE: &msg;'; put '%end;'; put '%else %do;'; put '%put NOTE: Library &libref is already assigned;'; put '%end;'; put '%mp_abort(iftrue= (&mp_abort=1)'; put ',mac=mm_assignlib.sas'; put ',msg=%superq(msg)'; put ')'; put '%mend mm_assignlib;'; put '/** @cond */'; put '%macro mf_getengine(libref'; put ')/*/STORE SOURCE*/;'; put '%local dsid engnum rc engine;'; put '/* in case the parameter is a libref.tablename, pull off just the libref */'; put '%let libref = %upcase(%scan(&libref, 1, %str(.)));'; put '%let dsid=%sysfunc('; put 'open(sashelp.vlibnam(where=(libname="%upcase(&libref)")),i)'; put ');'; put '%if (&dsid ^= 0) %then %do;'; put '%let engnum=%sysfunc(varnum(&dsid,ENGINE));'; put '%let rc=%sysfunc(fetch(&dsid));'; put '%let engine=%sysfunc(getvarc(&dsid,&engnum));'; put '%put &libref. ENGINE is &engine.;'; put '%let rc= %sysfunc(close(&dsid));'; put '%end;'; put '%upcase(&engine)'; put '%mend mf_getengine;'; put '/** @endcond */'; put '%macro mm_assigndirectlib('; put 'libref'; put ',open_passthrough='; put ',sql_options='; put ',mDebug=0'; put ',mAbort=0'; put ')/*/STORE SOURCE*/;'; put '%local mD;'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing mm_assigndirectlib.sas;'; put '%&mD.put _local_;'; put '%if &mAbort=1 %then %let mAbort=;'; put '%else %let mAbort=%str(*);'; put '%&mD.put NOTE: Creating direct (non META) connection to &libref library;'; put '%local cur_engine;'; put '%let cur_engine=%mf_getengine(&libref);'; put '%if &cur_engine ne META and &cur_engine ne %then %do;'; put '%put NOTE: &libref already has a direct (&cur_engine) libname connection;'; put '%return;'; put '%end;'; put '%else %if %upcase(&libref)=WORK %then %do;'; put '%put NOTE: We already have a direct connection to WORK :-) ;'; put '%return;'; put '%end;'; put '/* need to determine the library ENGINE first */'; put '%local engine;'; put 'data _null_;'; put 'length lib_uri engine $256;'; put 'call missing (of _all_);'; put '/* get URI for the particular library */'; put 'rc1=metadata_getnobj("omsobj:SASLibrary?@Libref =''&libref''",1,lib_uri);'; put '/* get the Engine attribute of the previous object */'; put 'rc2=metadata_getattr(lib_uri,''Engine'',engine);'; put 'putlog "mm_assigndirectlib for &libref:" rc1= lib_uri= rc2= engine=;'; put 'call symputx("liburi",lib_uri,''l'');'; put 'call symputx("engine",engine,''l'');'; put 'run;'; put '/* now obtain engine specific connection details */'; put '%if &engine=BASE %then %do;'; put '%&mD.put NOTE: Retrieving BASE library path;'; put 'data _null_;'; put 'length up_uri $256 path cat_path $1024;'; put 'retain cat_path;'; put 'call missing (of _all_);'; put '/* get all the filepaths of the UsingPackages association */'; put 'i=1;'; put 'rc3=metadata_getnasn("&liburi",''UsingPackages'',i,up_uri);'; put 'do while (rc3>0);'; put '/* get the DirectoryName attribute of the previous object */'; put 'rc4=metadata_getattr(up_uri,''DirectoryName'',path);'; put 'if i=1 then path = ''("''!!trim(path)!!''" '';'; put 'else path ='' "''!!trim(path)!!''" '';'; put 'cat_path = trim(cat_path) !! " " !! trim(path) ;'; put 'i+1;'; put 'rc3=metadata_getnasn("&liburi",''UsingPackages'',i,up_uri);'; put 'end;'; put 'cat_path = trim(cat_path) !! ")";'; put '&mD.putlog "NOTE: Getting physical path for &libref library";'; put '&mD.putlog rc3= up_uri= rc4= cat_path= path=;'; put '&mD.putlog "NOTE: Libname cmd will be:";'; put '&mD.putlog "libname &libref" cat_path;'; put 'call symputx("filepath",cat_path,''l'');'; put 'run;'; put '%if %sysevalf(&sysver<9.4) %then %do;'; put 'libname &libref &filepath;'; put '%end;'; put '%else %do;'; put '/* apply the new filelocks option to cater for temporary locks */'; put 'libname &libref &filepath filelockwait=5;'; put '%end;'; put '%end;'; put '%else %if &engine=REMOTE %then %do;'; put 'data x;'; put 'length rcCon rcProp rc k 3 uriCon uriProp PropertyValue PropertyName'; put 'Delimiter $256 properties $2048;'; put 'retain properties;'; put 'rcCon = metadata_getnasn("&liburi", "LibraryConnection", 1, uriCon);'; put 'rcProp = metadata_getnasn(uriCon, "Properties", 1, uriProp);'; put 'k = 1;'; put 'rcProp = metadata_getnasn(uriCon, "Properties", k, uriProp);'; put 'do while (rcProp > 0);'; put 'rc = metadata_getattr(uriProp , "DefaultValue",PropertyValue);'; put 'rc = metadata_getattr(uriProp , "PropertyName",PropertyName);'; put 'rc = metadata_getattr(uriProp , "Delimiter",Delimiter);'; put 'properties = trim(properties) !! " " !! trim(PropertyName)'; put '!! trim(Delimiter) !! trim(PropertyValue);'; put 'output;'; put 'k+1;'; put 'rcProp = metadata_getnasn(uriCon, "Properties", k, uriProp);'; put 'end;'; put '%&mD.put NOTE: Getting properties for REMOTE SHARE &libref library;'; put '&mD.put _all_;'; put '%&mD.put NOTE: Libname cmd will be:;'; put '%&mD.put libname &libref &engine &properties slibref=&libref;'; put 'call symputx ("properties",trim(properties),''l'');'; put 'run;'; put 'libname &libref &engine &properties slibref=&libref;'; put '%end;'; put '%else %if &engine=OLEDB %then %do;'; put '%&mD.put NOTE: Retrieving OLEDB connection details;'; put 'data _null_;'; put 'length domain datasource provider properties schema'; put 'connx_uri domain_uri conprop_uri lib_uri schema_uri value $256.;'; put 'call missing (of _all_);'; put '/* get source connection ID */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,connx_uri);'; put '/* get connection domain */'; put 'rc1=metadata_getnasn(connx_uri,''Domain'',1,domain_uri);'; put 'rc2=metadata_getattr(domain_uri,''Name'',domain);'; put '&mD.putlog / ''NOTE: '' // ''NOTE- connection id: '' connx_uri ;'; put '&mD.putlog ''NOTE- domain: '' domain;'; put '/* get DSN and PROVIDER from connection properties */'; put 'i=0;'; put 'do until (rc<0);'; put 'i+1;'; put 'rc=metadata_getnasn(connx_uri,''Properties'',i,conprop_uri);'; put 'rc2=metadata_getattr(conprop_uri,''Name'',value);'; put 'if value=''Connection.OLE.Property.DATASOURCE.Name.xmlKey.txt'' then do;'; put 'rc3=metadata_getattr(conprop_uri,''DefaultValue'',datasource);'; put 'end;'; put 'else if value=''Connection.OLE.Property.PROVIDER.Name.xmlKey.txt'' then do;'; put 'rc4=metadata_getattr(conprop_uri,''DefaultValue'',provider);'; put 'end;'; put 'else if value=''Connection.OLE.Property.PROPERTIES.Name.xmlKey.txt'' then'; put 'do;'; put 'rc5=metadata_getattr(conprop_uri,''DefaultValue'',properties);'; put 'end;'; put 'end;'; put '&mD.putlog ''NOTE- dsn/provider/properties: '' /'; put 'datasource provider properties;'; put '&mD.putlog ''NOTE- schema: '' schema // ''NOTE-'';'; put '/* get SCHEMA */'; put 'rc6=metadata_getnasn("&liburi",''UsingPackages'',1,lib_uri);'; put 'rc7=metadata_getattr(lib_uri,''SchemaName'',schema);'; put 'call symputx(''SQL_domain'',domain,''l'');'; put 'call symputx(''SQL_dsn'',datasource,''l'');'; put 'call symputx(''SQL_provider'',provider,''l'');'; put 'call symputx(''SQL_properties'',properties,''l'');'; put 'call symputx(''SQL_schema'',schema,''l'');'; put 'run;'; put '%if %length(&open_passthrough)>0 %then %do;'; put 'proc sql &sql_options;'; put 'connect to OLEDB as &open_passthrough(INSERT_SQL=YES'; put '/* need additional properties to make this work */'; put 'properties=(''Integrated Security''=SSPI'; put '''Persist Security Info''=True'; put '%sysfunc(compress(%str(&SQL_properties),%str(())))'; put ')'; put 'DATASOURCE=&sql_dsn PROMPT=NO'; put 'PROVIDER=&sql_provider SCHEMA=&sql_schema CONNECTION = GLOBAL);'; put '%end;'; put '%else %do;'; put 'LIBNAME &libref OLEDB PROPERTIES=&sql_properties'; put 'DATASOURCE=&sql_dsn PROVIDER=&sql_provider SCHEMA=&sql_schema'; put '%if %length(&sql_domain)>0 %then %do;'; put 'authdomain="&sql_domain"'; put '%end;'; put 'connection=shared;'; put '%end;'; put '%end;'; put '%else %if &engine=ODBC %then %do;'; put '%&mD.put NOTE: Retrieving ODBC connection details;'; put 'data _null_;'; put 'length connx_uri conprop_uri value datasource up_uri schema domprop_uri authdomain $256.;'; put 'call missing (of _all_);'; put '/* get source connection ID */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,connx_uri);'; put '/* get connection properties */'; put 'i=0;'; put 'do until (rc2<0);'; put 'i+1;'; put 'rc2=metadata_getnasn(connx_uri,''Properties'',i,conprop_uri);'; put 'rc3=metadata_getattr(conprop_uri,''Name'',value);'; put 'if value=''Connection.ODBC.Property.DATASRC.Name.xmlKey.txt'' then do;'; put 'rc4=metadata_getattr(conprop_uri,''DefaultValue'',datasource);'; put 'rc2=-1;'; put 'end;'; put 'end;'; put '/* get auth domain */'; put 'autrc=metadata_getnasn(connx_uri,"Domain",1,domprop_uri);'; put 'arc=metadata_getattr(domprop_uri,"Name",authdomain);'; put 'if not missing(authdomain) then authdomain=cats(''AUTHDOMAIN='',authdomain);'; put 'call symputx(''authdomain'',authdomain,''l'');'; put '/* get SCHEMA */'; put 'rc6=metadata_getnasn("&liburi",''UsingPackages'',1,up_uri);'; put 'rc7=metadata_getattr(up_uri,''SchemaName'',schema);'; put '&mD.put rc= connx_uri= rc2= conprop_uri= rc3= value= rc4= datasource='; put 'rc6= up_uri= rc7= schema=;'; put 'call symputx(''SQL_schema'',schema,''l'');'; put 'call symputx(''SQL_dsn'',datasource,''l'');'; put 'run;'; put '%if %length(&open_passthrough)>0 %then %do;'; put 'proc sql &sql_options;'; put 'connect to ODBC as &open_passthrough'; put '(INSERT_SQL=YES DATASRC=&sql_dsn. CONNECTION=global);'; put '%end;'; put '%else %do;'; put 'libname &libref ODBC DATASRC=&sql_dsn SCHEMA=&sql_schema &authdomain;'; put '%end;'; put '%end;'; put '%else %if &engine=POSTGRES %then %do;'; put '%put NOTE: Obtaining POSTGRES library details;'; put 'data _null_;'; put 'length database ignore_read_only_columns direct_exe preserve_col_names'; put 'preserve_tab_names server schema authdomain user password'; put 'prop name value uri urisrc $256.;'; put 'call missing (of _all_);'; put '/* get database value */'; put 'prop=''Connection.DBMS.Property.DB.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,database,"");'; put 'if database^='''' then database=''database=''!!quote(trim(database));'; put 'call symputx(''database'',database,''l'');'; put '/* get IGNORE_READ_ONLY_COLUMNS value */'; put 'prop=''Library.DBMS.Property.DBIROC.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,ignore_read_only_columns,"");'; put 'if ignore_read_only_columns^='''' then ignore_read_only_columns='; put '''ignore_read_only_columns=''!!ignore_read_only_columns;'; put 'call symputx(''ignore_read_only_columns'',ignore_read_only_columns,''l'');'; put '/* get DIRECT_EXE value */'; put 'prop=''Library.DBMS.Property.DirectExe.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,direct_exe,"");'; put 'if direct_exe^='''' then direct_exe=''direct_exe=''!!direct_exe;'; put 'call symputx(''direct_exe'',direct_exe,''l'');'; put '/* get PRESERVE_COL_NAMES value */'; put 'prop=''Library.DBMS.Property.PreserveColNames.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,preserve_col_names,"");'; put 'if preserve_col_names^='''' then preserve_col_names='; put '''preserve_col_names=''!!preserve_col_names;'; put 'call symputx(''preserve_col_names'',preserve_col_names,''l'');'; put '/* get PRESERVE_TAB_NAMES value */'; put '/* be careful with PRESERVE_TAB_NAMES=YES - it will mean your table will'; put 'become case sensitive!! */'; put 'prop=''Library.DBMS.Property.PreserveTabNames.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,preserve_tab_names,"");'; put 'if preserve_tab_names^='''' then preserve_tab_names='; put '''preserve_tab_names=''!!preserve_tab_names;'; put 'call symputx(''preserve_tab_names'',preserve_tab_names,''l'');'; put '/* get SERVER value */'; put 'if metadata_getnasn("&liburi","LibraryConnection",1,uri)>0 then do;'; put 'prop=''Connection.DBMS.Property.SERVER.Name.xmlKey.txt'';'; put 'rc=metadata_getprop(uri,prop,server,"");'; put 'end;'; put 'if server^='''' then server=''server=''!!quote(cats(server));'; put 'call symputx(''server'',server,''l'');'; put '/* get SCHEMA value */'; put 'if metadata_getnasn("&liburi","UsingPackages",1,uri)>0 then do;'; put 'rc=metadata_getattr(uri,"SchemaName",schema);'; put 'end;'; put 'if schema^='''' then schema=''schema=''!!schema;'; put 'call symputx(''schema'',schema,''l'');'; put '/* get AUTHDOMAIN value */'; put '/* this is only useful if the user account contains that auth domain'; put 'if metadata_getnasn("&liburi","DefaultLogin",1,uri)>0 then do;'; put 'rc=metadata_getnasn(uri,"Domain",1,urisrc);'; put 'rc=metadata_getattr(urisrc,"Name",authdomain);'; put 'end;'; put 'if authdomain^='''' then authdomain=''authdomain=''!!quote(trim(authdomain));'; put '*/'; put 'call symputx(''authdomain'',authdomain,''l'');'; put '/* get user & pass */'; put 'if authdomain='''' & metadata_getnasn("&liburi","DefaultLogin",1,uri)>0 then'; put 'do;'; put 'rc=metadata_getattr(uri,"UserID",user);'; put 'rc=metadata_getattr(uri,"Password",password);'; put 'end;'; put 'if user^='''' then do;'; put 'user=''user=''!!quote(trim(user));'; put 'password=''password=''!!quote(trim(password));'; put 'end;'; put 'call symputx(''user'',user,''l'');'; put 'call symputx(''password'',password,''l'');'; put '&md.put _all_;'; put 'run;'; put '%if %length(&open_passthrough)>0 %then %do;'; put '%put %str(WARN)ING: Passthrough option for postgres not yet supported;'; put '%return;'; put '%end;'; put '%else %do;'; put '%if &mdebug=1 %then %do;'; put '%put NOTE: Executing the following:/;'; put '%put NOTE- libname &libref POSTGRES &database &ignore_read_only_columns;'; put '%put NOTE- &direct_exe &preserve_col_names &preserve_tab_names;'; put '%put NOTE- &server &schema &authdomain &user &password //;'; put '%end;'; put 'libname &libref POSTGRES &database &ignore_read_only_columns &direct_exe'; put '&preserve_col_names &preserve_tab_names &server &schema &authdomain'; put '&user &password;'; put '%end;'; put '%end;'; put '%else %if &engine=ORACLE %then %do;'; put '%put NOTE: Obtaining &engine library details;'; put 'data _null_;'; put 'length assocuri1 assocuri2 assocuri3 authdomain path schema $256;'; put 'call missing (of _all_);'; put '/* get auth domain */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,assocuri1);'; put 'rc=metadata_getnasn(assocuri1,''Domain'',1,assocuri2);'; put 'rc=metadata_getattr(assocuri2,"Name",authdomain);'; put 'call symputx(''authdomain'',authdomain,''l'');'; put '/* path */'; put 'rc=metadata_getprop(assocuri1,'; put '''Connection.Oracle.Property.PATH.Name.xmlKey.txt'',path);'; put 'call symputx(''path'',path,''l'');'; put '/* schema */'; put 'rc=metadata_getnasn("&liburi",''UsingPackages'',1,assocuri3);'; put 'rc=metadata_getattr(assocuri3,''SchemaName'',schema);'; put 'call symputx(''schema'',schema,''l'');'; put 'run;'; put '%put NOTE: Executing the following:/; %put NOTE-;'; put '%put NOTE- libname &libref ORACLE path=&path schema=&schema;'; put '%put NOTE- authdomain=&authdomain;'; put '%put NOTE-;'; put 'libname &libref ORACLE path=&path schema=&schema authdomain=&authdomain;'; put '%end;'; put '%else %if &engine=SQLSVR %then %do;'; put '%put NOTE: Obtaining &engine library details;'; put 'data _null;'; put 'length assocuri1 assocuri2 assocuri3 authdomain path schema userid'; put 'passwd $256;'; put 'call missing (of _all_);'; put 'rc=metadata_getnasn("&liburi",''DefaultLogin'',1,assocuri1);'; put 'rc=metadata_getattr(assocuri1,"UserID",userid);'; put 'rc=metadata_getattr(assocuri1,"Password",passwd);'; put 'call symputx(''user'',userid,''l'');'; put 'call symputx(''pass'',passwd,''l'');'; put '/* path */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,assocuri2);'; put 'rc=metadata_getprop(assocuri2,'; put '''Connection.SQL.Property.Datasrc.Name.xmlKey.txt'',path);'; put 'call symputx(''path'',path,''l'');'; put '/* schema */'; put 'rc=metadata_getnasn("&liburi",''UsingPackages'',1,assocuri3);'; put 'rc=metadata_getattr(assocuri3,''SchemaName'',schema);'; put 'call symputx(''schema'',schema,''l'');'; put 'run;'; put '%put NOTE: Executing the following:/; %put NOTE-;'; put '%put NOTE- libname &libref SQLSVR datasrc=&path schema=&schema ;'; put '%put NOTE- user="&user" pass="XXX";'; put '%put NOTE-;'; put 'libname &libref SQLSVR datasrc=&path schema=&schema user="&user" pass="&pass";'; put '%end;'; put '%else %if &engine=TERADATA %then %do;'; put '%put NOTE: Obtaining &engine library details;'; put 'data _null;'; put 'length assocuri1 assocuri2 assocuri3 authdomain path schema userid'; put 'passwd $256;'; put 'call missing (of _all_);'; put '/* get auth domain */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,assocuri1);'; put 'rc=metadata_getnasn(assocuri1,''Domain'',1,assocuri2);'; put 'rc=metadata_getattr(assocuri2,"Name",authdomain);'; put 'call symputx(''authdomain'',authdomain,''l'');'; put '/*'; put 'rc=metadata_getnasn("&liburi",''DefaultLogin'',1,assocuri1);'; put 'rc=metadata_getattr(assocuri1,"UserID",userid);'; put 'rc=metadata_getattr(assocuri1,"Password",passwd);'; put 'call symputx(''user'',userid,''l'');'; put 'call symputx(''pass'',passwd,''l'');'; put '*/'; put '/* path */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,assocuri2);'; put 'rc=metadata_getprop(assocuri2,'; put '''Connection.Teradata.Property.SERVER.Name.xmlKey.txt'',path);'; put 'call symputx(''path'',path,''l'');'; put '/* schema */'; put 'rc=metadata_getnasn("&liburi",''UsingPackages'',1,assocuri3);'; put 'rc=metadata_getattr(assocuri3,''SchemaName'',schema);'; put 'call symputx(''schema'',schema,''l'');'; put 'run;'; put '%put NOTE: Executing the following:/; %put NOTE-;'; put '%put NOTE- libname &libref TERADATA server="&path" schema=&schema ;'; put '%put NOTe- authdomain=&authdomain;'; put '%put NOTE-;'; put 'libname &libref TERADATA server="&path" schema=&schema authdomain=&authdomain;'; put '%end;'; put '%else %if &engine= %then %do;'; put '%put NOTE: Libref &libref is not registered in metadata;'; put '%&mAbort.mp_abort('; put 'msg=%str(ERR)OR: Libref &libref is not registered in metadata'; put ',mac=mm_assigndirectlib.sas);'; put '%return;'; put '%end;'; put '%else %do;'; put '%put %str(WARN)ING: Engine &engine is currently unsupported;'; put '%put %str(WARN)ING- Please contact your support team.;'; put '%return;'; put '%end;'; put '%mend mm_assigndirectlib;'; put '%macro mm_getrepos('; put 'outds=work.mm_getrepos'; put ')/*/STORE SOURCE*/;'; put '* use a temporary fileref to hold the response;'; put 'filename response temp;'; put '/* get list of libraries */'; put 'proc metadata in='; put '"1"'; put 'out=response;'; put 'run;'; put '/* write the response to the log for debugging */'; put '/*'; put 'data _null_;'; put 'infile response lrecl=1048576;'; put 'input;'; put 'put _infile_;'; put 'run;'; put '*/'; put '/* create an XML map to read the response */'; put 'filename sxlemap temp;'; put 'data _null_;'; put 'file sxlemap;'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository";'; put 'put "";'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Id";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Name";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Desc";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@DefaultNS";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@RepositoryType";'; put 'put "characterstring20";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@RepositoryFormat";'; put 'put "characterstring10";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@Access";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@CurrentAccess";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@PauseState";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Path";'; put 'put "";'; put 'put "characterstring256";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Engine";'; put 'put "";'; put 'put "characterstring8";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Options";'; put 'put "";'; put 'put "characterstring32";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@MetadataCreated";'; put 'put "characterstring24";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@MetadataUpdated";'; put 'put "characterstring24";'; put 'put '''';'; put 'put ''
'';'; put 'run;'; put 'libname _XML_ xml xmlfileref=response xmlmap=sxlemap;'; put 'proc sort data= _XML_.SASRepos out=&outds;'; put 'by name;'; put 'run;'; put '/* clear references */'; put 'filename sxlemap clear;'; put 'filename response clear;'; put 'libname _XML_ clear;'; put '%mend mm_getrepos;'; put '%macro dc_assignlib(type,libref,passthru=);'; put '%put &sysmacroname entry vars:;'; put '%put _local_;'; put '/* take current repository */'; put '%local repo repocnt x;'; put '%let repo=%sysfunc(getoption(metarepository));'; put '/* get list of repositories and filter */'; put '%mm_getrepos(outds=repos)'; put '%let repocnt=0;'; put 'data repos;'; put 'set repos;'; put 'where repositorytype in(''CUSTOM'',''FOUNDATION'')'; put '& upcase(name) ne "%upcase(&repo)";'; put 'keep id name ;'; put 'call symputx(cats(''repo'',_n_),name,''l'');'; put 'call symputx(''repocnt'',_n_,''l'');'; put 'run;'; put '/* find out which of these repositories has the libref we are searching for */'; put '%local lib_uri;'; put '%let lib_uri=NOTFOUND;'; put 'data _null_; /* check default repo first */'; put 'length lib_uri $256;'; put 'call missing (of _all_);'; put 'rc=metadata_getnobj("omsobj:SASLibrary?@Libref =''&libref''",1,lib_uri);'; put 'call symputx(''lib_uri'',coalescec(lib_uri,''NOTFOUND''),''l'');'; put 'run;'; put '%if &lib_uri=NOTFOUND %then %do;'; put '%do x=1 %to &repocnt;'; put 'options metarepository=&&repo&x;'; put 'data _null_;'; put 'length lib_uri $256;'; put 'call missing (of _all_);'; put 'rc=metadata_getnobj("omsobj:SASLibrary?@Libref =''&libref''",1,lib_uri);'; put 'call symputx(''lib_uri'',coalescec(lib_uri,''NOTFOUND''),''l'');'; put 'run;'; put '%if &lib_uri ne NOTFOUND %then %let x=%eval(&repocnt+1);'; put '%end;'; put '%end;'; put '%if &type=READ %then %do;'; put '%mm_assignlib(&libref)'; put '%end;'; put '%else %if &type=WRITE %then %do;'; put '%mm_assigndirectlib(&libref,open_passthrough=&passthru)'; put '%end;'; put 'options metarepository=&repo;'; put '%mend dc_assignlib;'; put '/** @cond */'; put '%macro mf_existvar(libds /* 2 part dataset name */'; put ', var /* variable name */'; put ')/*/STORE SOURCE*/;'; put '%local dsid rc;'; put '%let dsid=%sysfunc(open(&libds,is));'; put '%if &dsid=0 %then %do;'; put '%put %sysfunc(sysmsg());'; put '0'; put '%end;'; put '%else %if %length(&var)=0 %then %do;'; put '0'; put '%let rc=%sysfunc(close(&dsid));'; put '%end;'; put '%else %do;'; put '%sysfunc(varnum(&dsid,&var))'; put '%let rc=%sysfunc(close(&dsid));'; put '%end;'; put '%mend mf_existvar;'; put '/** @endcond */'; put '%macro mf_getattrn('; put 'libds'; put ',attr'; put ')/*/STORE SOURCE*/;'; put '%local dsid rc;'; put '%let dsid=%sysfunc(open(&libds,is));'; put '%if &dsid = 0 %then %do;'; put '%put %str(WARN)ING: Cannot open %trim(&libds), system message below;'; put '%put %sysfunc(sysmsg());'; put '-1'; put '%end;'; put '%else %do;'; put '%sysfunc(attrn(&dsid,&attr))'; put '%let rc=%sysfunc(close(&dsid));'; put '%end;'; put '%mend mf_getattrn;'; put '%macro mf_getvartype(libds /* two level name */'; put ', var /* variable name from which to return the type */'; put ')/*/STORE SOURCE*/;'; put '%local dsid vnum vtype rc;'; put '/* Open dataset */'; put '%let dsid = %sysfunc(open(&libds));'; put '%if &dsid. > 0 %then %do;'; put '/* Get variable number */'; put '%let vnum = %sysfunc(varnum(&dsid, &var));'; put '/* Get variable type (C/N) */'; put '%if(&vnum. > 0) %then %let vtype = %sysfunc(vartype(&dsid, &vnum.));'; put '%else %do;'; put '%put NOTE: Variable &var does not exist in &libds;'; put '%let vtype = %str( );'; put '%end;'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: dataset &libds not opened! (rc=&dsid);'; put '%put &sysmacroname: %sysfunc(sysmsg());'; put '%return;'; put '%end;'; put '/* Close dataset */'; put '%let rc = %sysfunc(close(&dsid));'; put '/* Return variable type */'; put '&vtype'; put '%mend mf_getvartype;'; put '%macro mf_getattrc('; put 'libds'; put ',attr'; put ')/*/STORE SOURCE*/;'; put '%local dsid rc;'; put '%let dsid=%sysfunc(open(&libds,is));'; put '%if &dsid = 0 %then %do;'; put '%put %str(WARN)ING: Cannot open %trim(&libds), system message below;'; put '%put %sysfunc(sysmsg());'; put '-1'; put '%end;'; put '%else %do;'; put '%sysfunc(attrc(&dsid,&attr))'; put '%let rc=%sysfunc(close(&dsid));'; put '%end;'; put '%mend mf_getattrc;'; put '%macro mp_lockfilecheck('; put 'libds'; put ')/*/STORE SOURCE*/;'; put 'data _null_;'; put 'if _n_=1 then putlog "&sysmacroname entry vars:";'; put 'set sashelp.vmacro;'; put 'where scope="&sysmacroname";'; put 'put name ''='' value;'; put 'run;'; put '%mp_abort(iftrue= (&syscc>0)'; put ',mac=checklock.sas'; put ',msg=Aborting with syscc=&syscc on entry.'; put ')'; put '%mp_abort(iftrue= ("&libds"="0")'; put ',mac=&sysmacroname'; put ',msg=%str(libds not provided)'; put ')'; put '%local msg lib ds;'; put '%let lib=%upcase(%scan(&libds,1,.));'; put '%let ds=%upcase(%scan(&libds,2,.));'; put '/* in DC, format catalogs are passed with a -FC suffix. No saslock here! */'; put '%if %scan(&libds,2,-)=FC %then %do;'; put '%put &sysmacroname: Format Catalog detected, no lockfile applied to &libds;'; put '%return;'; put '%end;'; put '/* do not proceed if no observations can be processed */'; put '%let msg=options obs = 0. syserrortext=%superq(syserrortext);'; put '%mp_abort(iftrue= (%sysfunc(getoption(OBS))=0)'; put ',mac=checklock.sas'; put ',msg=%superq(msg)'; put ')'; put 'data _null_;'; put 'putlog "Checking engine & member type";'; put 'run;'; put '%local engine memtype;'; put '%let memtype=%mf_getattrc(&libds,MTYPE);'; put '%let engine=%mf_getattrc(&libds,ENGINE);'; put '%if &engine ne V9 and &engine ne BASE %then %do;'; put 'data _null_;'; put 'putlog "Lib &lib is not assigned using BASE engine - uses &engine instead";'; put 'putlog "SAS lock check will not be performed";'; put 'run;'; put '%return;'; put '%end;'; put '%else %if &memtype ne DATA %then %do;'; put '%put NOTE: Cannot lock a VIEW!! Memtype=&memtype;'; put '%return;'; put '%end;'; put 'data _null_;'; put 'putlog "Engine = &engine, memtype=&memtype";'; put 'putlog "Attempting lock statement";'; put 'run;'; put 'lock &libds;'; put '%local abortme;'; put '%let abortme=0;'; put '%if &syscc>0 or &SYSLCKRC ne 0 %then %do;'; put '%let msg=Unable to apply lock on &libds (SYSLCKRC=&SYSLCKRC syscc=&syscc);'; put '%put %str(ERR)OR: &sysmacroname: &msg;'; put '%let abortme=1;'; put '%end;'; put 'lock &libds clear;'; put '%mp_abort(iftrue= (&abortme=1)'; put ',mac=&sysmacroname'; put ',msg=%superq(msg)'; put ')'; put '%mend mp_lockfilecheck;'; put '%macro mp_lockanytable('; put 'action'; put ',lib= WORK'; put ',ds=0'; put ',ref='; put ',ctl_ds=0'; put ',loops=25'; put ',loop_secs=1'; put ');'; put 'data _null_;'; put 'if _n_=1 then putlog "&sysmacroname entry vars:";'; put 'set sashelp.vmacro;'; put 'where scope="&sysmacroname";'; put 'put name ''='' value;'; put 'run;'; put '%mp_abort(iftrue= ("&ds"="0" and &action ne MAKETABLE)'; put ',mac=&sysmacroname'; put ',msg=%str(dataset was not provided)'; put ')'; put '%mp_abort(iftrue= (&ctl_ds=0)'; put ',mac=&sysmacroname'; put ',msg=%str(Control dataset was not provided)'; put ')'; put '/* set up lib & mac vars */'; put '%let lib=%upcase(&lib);'; put '%let ds=%upcase(&ds);'; put '%let action=%upcase(&action);'; put '%local user x trans msg abortme;'; put '%let user=%mf_getuser();'; put '%let abortme=0;'; put '%mp_abort(iftrue= (&action ne LOCK & &action ne UNLOCK & &action ne MAKETABLE)'; put ',mac=&sysmacroname'; put ',msg=%str(Invalid action (&action) provided)'; put ')'; put '/* if an err condition exists, exit before we even begin */'; put '%mp_abort(iftrue= (&syscc>0 and &action=LOCK)'; put ',mac=&sysmacroname'; put ',msg=%str(aborting due to syscc=&syscc on LOCK entry)'; put ')'; put '/* do not bother locking work tables (else may affect all WORK libraries) */'; put '%if (%upcase(&lib)=WORK or %str(&lib)=%str()) & &action ne MAKETABLE %then %do;'; put '%put NOTE: WORK libraries will not be registered in the locking system.;'; put '%return;'; put '%end;'; put '/* do not proceed if no observations can be processed */'; put '%mp_abort(iftrue= (%sysfunc(getoption(OBS))=0)'; put ',mac=&sysmacroname'; put ',msg=%str(cannot continue when options obs = 0)'; put ')'; put '%if &ACTION=LOCK %then %do;'; put '/* abort if a SAS lock is already in place, or cannot be applied */'; put '%mp_lockfilecheck(&lib..&ds)'; put '/* next, check there is a record for this table */'; put '%local record_exists_check;'; put 'proc sql noprint;'; put 'select count(*) into: record_exists_check from &ctl_ds'; put 'where LOCK_LIB ="&lib" and LOCK_DS="&ds";'; put 'quit;'; put '%if &syscc>0 %then %put syscc=&syscc sqlrc=&sqlrc;'; put '%if &record_exists_check=0 %then %do;'; put 'data _null_;'; put 'putlog "&sysmacroname: adding record to lock table..";'; put 'run;'; put 'data ;'; put 'if 0 then set &ctl_ds;'; put 'LOCK_LIB ="&lib";'; put 'LOCK_DS="&ds";'; put 'LOCK_STATUS_CD=''LOCKED'';'; put 'LOCK_START_DTTM="%sysfunc(datetime(),%mf_fmtdttm())"dt;'; put 'LOCK_USER_NM="&user";'; put 'LOCK_PID="&sysjobid";'; put 'LOCK_REF="&ref";'; put 'output;stop;'; put 'run;'; put '%let trans=&syslast;'; put 'proc append base=&ctl_ds data=&trans;'; put 'run;'; put '%end;'; put '/* if record does exist, perform lock attempts */'; put '%else %do x=1 %to &loops;'; put 'data _null_;'; put 'putlog "&sysmacroname: attempting lock (iteration &x) "@;'; put 'putlog "at %sysfunc(datetime(),datetime19.) ..";'; put 'run;'; put 'proc sql;'; put 'update &ctl_ds'; put 'set LOCK_STATUS_CD=''LOCKED'''; put ', LOCK_START_DTTM="%sysfunc(datetime(),%mf_fmtdttm())"dt'; put ', LOCK_USER_NM="&user"'; put ', LOCK_PID="&sysjobid"'; put ', LOCK_REF="&ref"'; put 'where LOCK_LIB ="&lib" and LOCK_DS="&ds";'; put 'quit;'; put '/**'; put '* NOTE - occasionally SQL server will return an err code (deadlocked'; put '* transaction). If so, ignore it, keep calm, and carry on..'; put '*/'; put '%if &syscc>0 %then %do;'; put 'data _null_;'; put 'putlog ''NOTE-'' / ''NOTE-'';'; put 'putlog "NOTE- &sysmacroname: Update failed. "@;'; put 'putlog "Resetting err conditions and re-attempting.";'; put 'putlog "NOTE- syscc=&syscc syserr=&syserr sqlrc=&sqlrc";'; put 'putlog ''NOTE-'' / ''NOTE-'';'; put 'run;'; put '%let syscc=0;'; put '%let sqlrc=0;'; put '%end;'; put '/* now check if the record was successfully updated */'; put '%local success_check;'; put 'proc sql noprint;'; put 'select count(*) into: success_check from &ctl_ds'; put 'where LOCK_LIB ="&lib" and LOCK_DS="&ds"'; put 'and LOCK_PID="&sysjobid" and LOCK_STATUS_CD=''LOCKED'';'; put 'quit;'; put '%if &success_check=0 %then %do;'; put '%if &x < &loops %then %do;'; put '/* pause before next check */'; put 'data _null_;'; put 'putlog ''NOTE-'' / ''NOTE-'';'; put 'putlog "NOTE- &sysmacroname: table locked, waiting "@;'; put 'putlog "%sysfunc(sleep(&loop_secs)) seconds.. ";'; put 'putlog "NOTE- (iteration &x of &loops)";'; put 'putlog ''NOTE-'' / ''NOTE-'';'; put 'run;'; put '%end;'; put '%else %do;'; put '%let msg=Unable to lock &lib..&ds via &ctl_ds after &loops attempts.\n'; put 'Please ask your administrator to investigate!;'; put '%let abortme=1;'; put '%end;'; put '%end;'; put '%else %do;'; put 'data _null_;'; put 'putlog ''NOTE-'' / ''NOTE-'';'; put 'putlog "NOTE- &sysmacroname: Table &lib..&ds locked at "@;'; put 'putlog " %sysfunc(datetime(),datetime19.) (iteration &x)"@;'; put 'putlog ''NOTE-'' / ''NOTE-'';'; put 'run;'; put '%if &syscc>0 %then %do;'; put '%put setting syscc(&syscc) back to 0;'; put '%let syscc=0;'; put '%end;'; put '%let x=&loops; /* no more iterations needed */'; put '%end;'; put '%end;'; put '%end;'; put '%else %if &ACTION=UNLOCK %then %do;'; put '%local status cnt;'; put '%let cnt=0;'; put 'proc sql noprint;'; put 'select count(*) into: cnt from &ctl_ds where LOCK_LIB ="&lib" & LOCK_DS="&ds";'; put '%if &cnt=0 %then %do;'; put '%put %str(WAR)NING: &lib..&ds was not previously locked in &ctl_ds!;'; put '%end;'; put '%else %do;'; put 'select LOCK_STATUS_CD into: status from &ctl_ds'; put 'where LOCK_LIB ="&lib" and LOCK_DS="&ds";'; put 'quit;'; put '%if &syscc>0 %then %put syscc=&syscc sqlrc=&sqlrc;'; put '%if &status=LOCKED %then %do;'; put 'data _null_;'; put 'putlog "&sysmacroname: unlocking &lib..&ds:";'; put 'run;'; put 'proc sql;'; put 'update &ctl_ds'; put 'set LOCK_STATUS_CD=''UNLOCKED'''; put ', LOCK_END_DTTM="%sysfunc(datetime(),%mf_fmtdttm())"dt'; put ', LOCK_USER_NM="&user"'; put ', LOCK_PID="&sysjobid"'; put ', LOCK_REF="&ref"'; put 'where LOCK_LIB ="&lib" and LOCK_DS="&ds";'; put 'quit;'; put '%end;'; put '%else %if &status=UNLOCKED %then %do;'; put '%put %str(WAR)NING: &lib..&ds is already unlocked!;'; put '%end;'; put '%else %do;'; put '%put NOTE: Unrecognised STATUS_CD (&status) in &ctl_ds;'; put '%let abortme=1;'; put '%end;'; put '%end;'; put '%end;'; put '%else %do;'; put '%let msg=lock_anytable given unsupported action (&action);'; put '%let abortme=1;'; put '%end;'; put '/* catch errs - mp_abort must be called outside of a logic block */'; put '%mp_abort(iftrue=(&abortme=1),'; put 'msg=%superq(msg),'; put 'mac=&sysmacroname'; put ')'; put '%exit_macro:'; put 'data _null_;'; put 'put "&sysmacroname: Exit vars: action=&action lib=&lib ds=&ds";'; put 'put " syscc=&syscc sqlrc=&sqlrc syserr=&syserr";'; put 'run;'; put '%mend mp_lockanytable;'; put '%macro bitemporal_closeouts('; put 'tech_from=tx_from_dttm'; put ',tech_to = tx_to_dttm /* Technical TO datetime variable.'; put 'Req''d on BASE table only. */'; put ',base_lib=WORK /* Libref of the BASE table. */'; put ',base_dsn=BASETABLE /* Name of BASE table. */'; put ',append_lib=WORK /* Libref of the STAGING table. */'; put ',append_dsn=APPENDTABLE /* Name of STAGING table. */'; put ',PK= name sex /* Business key, space separated. */'; put '/* Should INCLUDE BUS_FROM field if relevant. */'; put ',NOW=DEFINE'; put ',FILTER= /* supply a filter to limit the update */'; put ',outdest= /* supply an unquoted filepath/filename.ext to get'; put 'a text file containing the update statements */'; put ',loadtype='; put ',loadtarget=YES /* if <> YES will return without changing anything */'; put ');'; put '%put ENTERING &sysmacroname;'; put '%local x var start;'; put '%let start=%sysfunc(datetime());'; put '%dc_assignlib(WRITE,&base_lib)'; put '%dc_assignlib(WRITE,&append_lib)'; put '%if &now=DEFINE %then %let now=&dc_dttmtfmt.;'; put '%put &=now;'; put '/**'; put '* perform basic checks'; put '*/'; put '/* do tables exist? */'; put '%if not %sysfunc(exist(&base_lib..&base_dsn)) %then %do;'; put '%mp_abort(msg=&base_lib..&base_dsn does not exist)'; put '%end;'; put '%else %if %sysfunc(exist(&append_lib..&append_dsn))=0'; put 'and %sysfunc(exist(&append_lib..&append_dsn,VIEW))=0 %then %do;'; put '%mp_abort(msg=&append_lib..&append_dsn does not exist)'; put '%end;'; put '/* do TX columns exist? */'; put '%if &loadtype ne UPDATE %then %do;'; put '%if not %mf_existvar(&base_lib..&base_dsn,&tech_from) %then %do;'; put '%mp_abort(msg=&tech_from does not exist on &base_lib..&base_dsn)'; put '%end;'; put '%else %if not %mf_existvar(&base_lib..&base_dsn,&tech_to) %then %do;'; put '%mp_abort(msg=&tech_to does not exist on &base_lib..&base_dsn)'; put '%end;'; put '%end;'; put '/* do PK columns exist? */'; put '%do x=1 %to %sysfunc(countw(&PK));'; put '%let var=%scan(&pk,&x,%str( ));'; put '%if not %mf_existvar(&base_lib..&base_dsn,&var) %then %do;'; put '%mp_abort(msg=&var does not exist on &base_lib..&base_dsn)'; put '%end;'; put '%else %if not %mf_existvar(&append_lib..&append_dsn,&var) %then %do;'; put '%mp_abort(msg=&var does not exist on &append_lib..&append_dsn)'; put '%end;'; put '%end;'; put '/* check uniqueness */'; put 'proc sort data=&append_lib..&append_dsn'; put 'out=___closeout1 noduprecs dupout=___closeout1a;'; put 'by &pk;'; put 'run;'; put '%if %mf_getattrn(___closeout1a,NLOBS)>0 %then'; put '%put NOTE: dups on (&PK) in (&append_lib..&append_dsn);'; put '/* is &NOW value within a tolerance? Should not allow renegade closeouts.. */'; put '%local gap;'; put '%let gap=0;'; put 'data _null_;'; put 'now=&now;'; put 'gap=intck(''HOURS'',now,datetime());'; put 'call symputx(''gap'',gap,''l'');'; put 'run;'; put '%mf_abort('; put 'iftrue=(&gap > 24),'; put 'msg=NOW variable (&now) is not within a 24hr tolerance'; put ')'; put '/* have any warnings / errs occurred thus far? If so, abort */'; put '%mf_abort('; put 'iftrue=(&syscc>0),'; put 'msg=Aborted due to SYSCC=&SYSCC status'; put ')'; put '/**'; put '* Create closeout statements. These are sent as individual SQL statements'; put '* to ensure pass-through utilisation. The update_cnt variable monitors'; put '* how many records were actually updated on the target table.'; put '*/'; put '%local update_cnt;'; put '%let update_cnt=0;'; put 'filename tmp temp;'; put 'data _null_;'; put 'set ___closeout1;'; put 'file tmp;'; put 'if _n_=1 then put ''proc sql noprint;'' ;'; put 'length string $32767.;'; put '%if &loadtype=UPDATE %then %do;'; put 'put "delete from &base_lib..&base_dsn where 1";'; put '%end;'; put '%else %do;'; put 'now=symget(''now'');'; put 'put "update &base_lib..&base_dsn set &tech_to= " now @;'; put '%if %mf_existvar(&base_lib..&base_dsn,PROCESSED_DTTM) %then %do;'; put 'put " ,PROCESSED_DTTM=" now @;'; put '%end;'; put 'put " where " now " lt &tech_to ";'; put '%end;'; put '%do x=1 %to %sysfunc(countw(&PK));'; put '%let var=%scan(&pk,&x,%str( ));'; put '%if %mf_getvartype(&base_lib..&base_dsn,&var)=C %then %do;'; put '/* use single quotes to avoid ampersand resolution in data */'; put 'string=" & &var=''"!!trim(prxchange("s/''/''''/",-1,&var))!!"''";'; put '%end;'; put '%else %do;'; put 'string=cats(" & &var=",&var);'; put '%end;'; put 'put string;'; put '%end;'; put 'put "&filter ;";'; put 'put ''%let update_cnt=%eval(&update_cnt+&sqlobs);%put update_cnt=&update_cnt;'';'; put 'run;'; put 'data _null_;'; put 'infile tmp;'; put 'input;'; put 'putlog _infile_;'; put 'run;'; put '%if &loadtarget ne YES %then %return;'; put '/* ensure we have a lock */'; put '%mp_lockanytable(LOCK,'; put 'lib=&base_lib,ds=&base_dsn'; put ',ref=bitemporal_closeouts'; put ',ctl_ds=&mpelib..mpe_lockanytable'; put ')'; put 'options source2;'; put '%inc tmp;'; put 'filename tmp clear;'; put '/**'; put '* Update audit tracker'; put '*/'; put '%local newobs; %let newobs=%mf_getattrn(work.___closeout1,NLOBS);'; put '%local user; %let user=%mf_getuser();'; put 'proc sql;'; put 'insert into &mpelib..mpe_dataloads'; put 'set libref=%upcase("&base_lib")'; put ',DSN=%upcase("&base_dsn")'; put ',ETLSOURCE="&append_lib..&append_dsn contained &newobs records"'; put ',LOADTYPE="CLOSEOUT"'; put ',DELETED_RECORDS=&update_cnt'; put ',NEW_RECORDS=0'; put ',DURATION=%sysfunc(datetime())-&start'; put ',USER_NM="&user"'; put ',PROCESSED_DTTM=&now;'; put 'quit;'; put '%mend bitemporal_closeouts;'; put '%macro mf_existds(libds'; put ')/*/STORE SOURCE*/;'; put '%if %sysfunc(exist(&libds)) ne 1 & %sysfunc(exist(&libds,VIEW)) ne 1 %then 0;'; put '%else 1;'; put '%mend mf_existds;'; put '%macro mf_getschema(libref'; put ')/*/STORE SOURCE*/;'; put '%local dsid vnum rc schema;'; put '/* in case the parameter is a libref.tablename, pull off just the libref */'; put '%let libref = %upcase(%scan(&libref, 1, %str(.)));'; put '%let dsid=%sysfunc(open(sashelp.vlibnam(where=('; put 'libname="%upcase(&libref)" and sysname=''Schema/Owner'''; put ')),i));'; put '%if (&dsid ^= 0) %then %do;'; put '%let vnum=%sysfunc(varnum(&dsid,SYSVALUE));'; put '%let rc=%sysfunc(fetch(&dsid));'; put '%let schema=%sysfunc(getvarc(&dsid,&vnum));'; put '%put &libref. schema is &schema.;'; put '%let rc= %sysfunc(close(&dsid));'; put '%end;'; put '&schema'; put '%mend mf_getschema;'; put '/** @endcond */'; put '%macro mf_getuniquename(prefix=MC);'; put '&prefix.%substr(%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32-%length(&prefix))'; put '%mend mf_getuniquename;'; put '%macro mf_getvarlist(libds'; put ',dlm=%str( )'; put ',quote=no'; put ',typefilter=A'; put ')/*/STORE SOURCE*/;'; put '/* declare local vars */'; put '%local outvar dsid nvars x rc dlm q var vtype;'; put '/* credit Rowland Hale - byte34 is double quote, 39 is single quote */'; put '%if %upcase("e)=DOUBLE %then %let q=%qsysfunc(byte(34));'; put '%else %if %upcase("e)=SINGLE %then %let q=%qsysfunc(byte(39));'; put '/* open dataset in macro */'; put '%let dsid=%sysfunc(open(&libds));'; put '%if &dsid %then %do;'; put '%let nvars=%sysfunc(attrn(&dsid,NVARS));'; put '%if &nvars>0 %then %do;'; put '/* add variables with supplied delimeter */'; put '%do x=1 %to &nvars;'; put '/* get variable type */'; put '%let vtype=%sysfunc(vartype(&dsid,&x));'; put '%if &vtype=&typefilter or &typefilter=A %then %do;'; put '%let var=&q.%sysfunc(varname(&dsid,&x))&q.;'; put '%if &var=&q&q %then %do;'; put '%put &sysmacroname: Empty column found in &libds!;'; put '%let var=&q. &q.;'; put '%end;'; put '%if %quote(&outvar)=%quote() %then %let outvar=&var;'; put '%else %let outvar=&outvar.&dlm.&var.;'; put '%end;'; put '%end;'; put '%end;'; put '%let rc=%sysfunc(close(&dsid));'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: Unable to open &libds (rc=&dsid);'; put '%put &sysmacroname: SYSMSG= %sysfunc(sysmsg());'; put '%let rc=%sysfunc(close(&dsid));'; put '%end;'; put '%do;%unquote(&outvar)%end;'; put '%mend mf_getvarlist;'; put '%macro mf_abort(mac=mf_abort.sas, msg=, iftrue=%str(1=1)'; put ')/des=''ungraceful abort'' /*STORE SOURCE*/;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%put NOTE: /// mf_abort macro executing //;'; put '%if %length(&mac)>0 %then %put NOTE- called by &mac;'; put '%put NOTE - &msg;'; put '%abort;'; put '%mend mf_abort;'; put '/** @endcond */'; put '%macro mf_verifymacvars('; put 'verifyVars /* list of macro variable NAMES */'; put ',makeUpcase=NO /* set to YES to make all the variable VALUES uppercase */'; put ',mAbort=SOFT'; put ')/*/STORE SOURCE*/;'; put '%local verifyIterator verifyVar abortmsg;'; put '%do verifyIterator=1 %to %sysfunc(countw(&verifyVars,%str( )));'; put '%let verifyVar=%qscan(&verifyVars,&verifyIterator,%str( ));'; put '%if not %symexist(&verifyvar) %then %do;'; put '%let abortmsg= Variable &verifyVar is MISSING;'; put '%goto exit_err;'; put '%end;'; put '%if %length(%trim(&&&verifyVar))=0 %then %do;'; put '%let abortmsg= Variable &verifyVar is EMPTY;'; put '%goto exit_err;'; put '%end;'; put '%if &makeupcase=YES %then %do;'; put '%let &verifyVar=%upcase(&&&verifyvar);'; put '%end;'; put '%end;'; put '%goto exit_success;'; put '%exit_err:'; put '%put &abortmsg;'; put '%mf_abort(iftrue=(&mabort ne SOFT),'; put 'mac=mf_verifymacvars,'; put 'msg=%str(&abortmsg)'; put ')'; put '0'; put '%return;'; put '%exit_success:'; put '1'; put '%mend mf_verifymacvars;'; put '%macro mf_wordsInStr1ButNotStr2('; put 'Str1= /* string containing words to extract */'; put ',Str2= /* used to compare with the extract string */'; put ')/*/STORE SOURCE*/;'; put '%local count_base count_extr i i2 extr_word base_word match outvar;'; put '%if %length(&str1)=0 or %length(&str2)=0 %then %do;'; put '%put base string (str1)= &str1;'; put '%put compare string (str2) = &str2;'; put '%return;'; put '%end;'; put '%let count_base=%sysfunc(countw(&Str2));'; put '%let count_extr=%sysfunc(countw(&Str1));'; put '%do i=1 %to &count_extr;'; put '%let extr_word=%scan(&Str1,&i,%str( ));'; put '%let match=0;'; put '%do i2=1 %to &count_base;'; put '%let base_word=%scan(&Str2,&i2,%str( ));'; put '%if &extr_word=&base_word %then %let match=1;'; put '%end;'; put '%if &match=0 %then %let outvar=&outvar &extr_word;'; put '%end;'; put '&outvar'; put '%mend mf_wordsInStr1ButNotStr2;'; put '%macro mf_isblank(param'; put ')/*/STORE SOURCE*/;'; put '%sysevalf(%superq(param)=,boolean)'; put '%mend mf_isblank;'; put '%macro mp_dropmembers('; put 'list /* space separated list of datasets / views */'; put ',libref=WORK /* can only drop from a single library at a time */'; put ',iftrue=%str(1=1)'; put ')/*/STORE SOURCE*/;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%if %mf_isblank(&list) %then %do;'; put '%put NOTE: nothing to drop!;'; put '%return;'; put '%end;'; put 'proc datasets lib=&libref nolist;'; put 'delete &list;'; put 'delete &list /mtype=view;'; put 'run;'; put '%mend mp_dropmembers;'; put '%macro mf_getquotedstr(IN_STR'; put ',DLM=%str(,)'; put ',QUOTE=S'; put ',indlm=%str( )'; put ')/*/STORE SOURCE*/;'; put '/* credit Rowland Hale - byte34 is double quote, 39 is single quote */'; put '%if "e=S %then %let quote=%qsysfunc(byte(39));'; put '%else %if "e=D %then %let quote=%qsysfunc(byte(34));'; put '%else %if "e=N %then %let quote=;'; put '%local i item buffer;'; put '%let i=1;'; put '%do %while (%qscan(&IN_STR,&i,%str(&indlm)) ne %str() ) ;'; put '%let item=%qscan(&IN_STR,&i,%str(&indlm));'; put '%if %bquote("E) ne %then %let item="E%qtrim(&item)"E;'; put '%else %let item=%qtrim(&item);'; put '%if (&i = 1) %then %let buffer =%qtrim(&item);'; put '%else %let buffer =&buffer&DLM%qtrim(&item);'; put '%let i = %eval(&i+1);'; put '%end;'; put '%let buffer=%sysfunc(coalescec(%qtrim(&buffer),"E"E));'; put '&buffer'; put '%mend mf_getquotedstr;'; put '%macro mf_nobs(libds'; put ')/*/STORE SOURCE*/;'; put '%mf_getattrn(&libds,NLOBS)'; put '%mend mf_nobs;'; put '%macro mp_retainedkey('; put 'base_lib=WORK'; put ',base_dsn=BASETABLE'; put ',append_lib=WORK'; put ',append_dsn=APPENDTABLE'; put ',retained_key=DEFAULT_RK'; put ',business_key= PK1 PK2'; put ',check_uniqueness=NO'; put ',maxkeytable=0'; put ',locktable=0'; put ',outds=WORK.APPEND'; put ',filter_str='; put ');'; put '%put &sysmacroname entry vars:;'; put '%put _local_;'; put '%local base_libds app_libds key_field check maxkey idx_pk newkey_cnt iserr'; put 'msg x tempds1 tempds2 comma_pk appnobs checknobs dropvar tempvar idx_val;'; put '%let base_libds=%upcase(&base_lib..&base_dsn);'; put '%let app_libds=%upcase(&append_lib..&append_dsn);'; put '%let tempds1=%mf_getuniquename();'; put '%let tempds2=%mf_getuniquename();'; put '%let comma_pk=%mf_getquotedstr(in_str=%str(&business_key),dlm=%str(,),quote=);'; put '%let outds=%sysfunc(ifc(%index(&outds,.)=0,work.&outds,&outds));'; put '/* validation checks */'; put '%let iserr=0;'; put '%if &syscc>0 %then %do;'; put '%let iserr=1;'; put '%let msg=%str(SYSCC=&syscc on macro entry);'; put '%end;'; put '%else %if %sysfunc(exist(&base_libds))=0 %then %do;'; put '%let iserr=1;'; put '%let msg=%str(Base LIBDS (&base_libds) expected but NOT FOUND);'; put '%end;'; put '%else %if %sysfunc(exist(&app_libds))=0 %then %do;'; put '%let iserr=1;'; put '%let msg=%str(Append LIBDS (&app_libds) expected but NOT FOUND);'; put '%end;'; put '%else %if &maxkeytable ne 0 and %sysfunc(exist(&maxkeytable))=0 %then %do;'; put '%let iserr=1;'; put '%let msg=%str(Maxkeytable (&maxkeytable) expected but NOT FOUND);'; put '%end;'; put '%else %if &maxkeytable ne 0 and %sysfunc(exist(&locktable))=0 %then %do;'; put '%let iserr=1;'; put '%let msg=%str(Locktable (&locktable) expected but NOT FOUND);'; put '%end;'; put '%else %if %length(&business_key)=0 %then %do;'; put '%let iserr=1;'; put '%let msg=%str(Business key (&business_key) expected but NOT FOUND);'; put '%end;'; put '%do x=1 %to %sysfunc(countw(&business_key));'; put '/* check business key values exist */'; put '%let key_field=%scan(&business_key,&x,%str( ));'; put '%if not %mf_existvar(&app_libds,&key_field) %then %do;'; put '%let iserr=1;'; put '%let msg=Business key (&key_field) not found on &app_libds!;'; put '%goto err;'; put '%end;'; put '%else %if not %mf_existvar(&base_libds,&key_field) %then %do;'; put '%let iserr=1;'; put '%let msg=Business key (&key_field) not found on &base_libds!;'; put '%goto err;'; put '%end;'; put '%end;'; put '%err:'; put '%if &iserr=1 %then %do;'; put '/* err case so first perform an unlock of the base table before exiting */'; put '%mp_lockanytable('; put 'UNLOCK,lib=&base_lib,ds=&base_dsn,ref=%superq(msg),ctl_ds=&locktable'; put ')'; put '%end;'; put '%mp_abort(iftrue=(&iserr=1),mac=mp_retainedkey,msg=%superq(msg))'; put 'proc sql noprint;'; put 'select sum(max(&retained_key),0) into: maxkey from &base_libds;'; put '/**'; put '* get base table RK and bus field values for lookup'; put '*/'; put 'proc sql noprint;'; put 'create table &tempds1 as'; put 'select distinct &comma_pk,&retained_key'; put 'from &base_libds &filter_str'; put 'order by &comma_pk,&retained_key;'; put '%if &check_uniqueness=YES %then %do;'; put 'select count(*) into:checknobs'; put 'from (select distinct &comma_pk from &app_libds);'; put 'select count(*) into: appnobs from &app_libds; /* might be view */'; put '%if &checknobs ne &appnobs %then %do;'; put '%let msg=Source table &app_libds is not unique on (&business_key);'; put '%let iserr=1;'; put '%end;'; put '%end;'; put '%if &iserr=1 %then %do;'; put '/* err case so first perform an unlock of the base table before exiting */'; put '%mp_lockanytable('; put 'UNLOCK,lib=&base_lib,ds=&base_dsn,ref=%superq(msg),ctl_ds=&locktable'; put ')'; put '%end;'; put '%mp_abort(iftrue= (&iserr=1),mac=mp_retainedkey,msg=%superq(msg))'; put '%if %mf_existvar(&app_libds,&retained_key)'; put '%then %let dropvar=(drop=&retained_key);'; put '/* prepare interim table with retained key populated for matching keys */'; put 'proc sql noprint;'; put 'create table &tempds2 as'; put 'select b.&retained_key, a.*'; put 'from &app_libds &dropvar a'; put 'left join &tempds1 b'; put 'on 1'; put '%do idx_pk=1 %to %sysfunc(countw(&business_key));'; put '%let idx_val=%scan(&business_key,&idx_pk);'; put 'and a.&idx_val=b.&idx_val'; put '%end;'; put 'order by &retained_key;'; put '/* identify the number of entries without retained keys (new records) */'; put 'select count(*) into: newkey_cnt'; put 'from &tempds2'; put 'where missing(&retained_key);'; put 'quit;'; put '/**'; put '* Update maxkey table if link provided'; put '*/'; put '%if &maxkeytable ne 0 %then %do;'; put 'proc sql noprint;'; put 'select count(*) into: check from &maxkeytable'; put 'where upcase(keytable)="&base_libds";'; put '%mp_lockanytable(LOCK'; put ',lib=%scan(&maxkeytable,1,.)'; put ',ds=%scan(&maxkeytable,2,.)'; put ',ref=Updating maxkeyvalues with mp_retainedkey'; put ',ctl_ds=&locktable'; put ')'; put 'proc sql;'; put '%if &check=0 %then %do;'; put 'insert into &maxkeytable'; put 'set keytable="&base_libds"'; put ',keycolumn="&retained_key"'; put ',max_key=%eval(&maxkey+&newkey_cnt)'; put ',processed_dttm="%sysfunc(datetime(),%mf_fmtdttm())"dt;'; put '%end;'; put '%else %do;'; put 'update &maxkeytable'; put 'set max_key=%eval(&maxkey+&newkey_cnt)'; put ',processed_dttm="%sysfunc(datetime(),%mf_fmtdttm())"dt'; put 'where keytable="&base_libds";'; put '%end;'; put '%mp_lockanytable(UNLOCK'; put ',lib=%scan(&maxkeytable,1,.)'; put ',ds=%scan(&maxkeytable,2,.)'; put ',ref=Updating maxkeyvalues with maxkey=%eval(&maxkey+&newkey_cnt)'; put ',ctl_ds=&locktable'; put ')'; put '%end;'; put '/* fill in the missing retained key values */'; put '%let tempvar=%mf_getuniquename();'; put 'data &outds(drop=&tempvar);'; put 'retain &tempvar %eval(&maxkey+1);'; put 'set &tempds2;'; put 'if &retained_key =. then &retained_key=&tempvar;'; put '&tempvar=&tempvar+1;'; put 'run;'; put '%mend mp_retainedkey;'; put '/** @cond */'; put '%macro mp_storediffs(libds'; put ',origds'; put ',key'; put ',delds=0'; put ',appds=0'; put ',modds=0'; put ',outds=work.mp_storediffs'; put ',loadref=0'; put ',processed_dttm=0'; put ',mdebug=0'; put ')/*/STORE SOURCE*/;'; put '%local dbg;'; put '%if &mdebug=1 %then %do;'; put '%put &sysmacroname entry vars:;'; put '%put _local_;'; put '%end;'; put '%else %let dbg=*;'; put '/* set up unique and temporary vars */'; put '%local ds1 ds2 ds3 ds4 hashkey inds_auto inds_keep dslist vlist;'; put '%let ds1=%upcase(work.%mf_getuniquename(prefix=mpsd_ds1));'; put '%let ds2=%upcase(work.%mf_getuniquename(prefix=mpsd_ds2));'; put '%let ds3=%upcase(work.%mf_getuniquename(prefix=mpsd_ds3));'; put '%let ds4=%upcase(work.%mf_getuniquename(prefix=mpsd_ds4));'; put '%let hashkey=%upcase(%mf_getuniquename(prefix=mpsd_hashkey));'; put '%let inds_auto=%upcase(%mf_getuniquename(prefix=mpsd_inds_auto));'; put '%let inds_keep=%upcase(%mf_getuniquename(prefix=mpsd_inds_keep));'; put '%let dslist=&origds;'; put '%if &delds ne 0 %then %do;'; put '%let delds=%upcase(&delds);'; put '%if %scan(&delds,-1,.)=&delds %then %let delds=WORK.&delds;'; put '%let dslist=&dslist &delds;'; put '%end;'; put '%if &appds ne 0 %then %do;'; put '%let appds=%upcase(&appds);'; put '%if %scan(&appds,-1,.)=&appds %then %let appds=WORK.&appds;'; put '%let dslist=&dslist &appds;'; put '%end;'; put '%if &modds ne 0 %then %do;'; put '%let modds=%upcase(&modds);'; put '%if %scan(&modds,-1,.)=&modds %then %let modds=WORK.&modds;'; put '%let dslist=&dslist &modds;'; put '%end;'; put '%let origds=%upcase(&origds);'; put '%if %scan(&origds,-1,.)=&origds %then %let origds=WORK.&origds;'; put '%let key=%upcase(&key);'; put '/* hash the key and append all the tables (marking the source) */'; put 'data &ds1;'; put 'set &dslist indsname=&inds_auto;'; put '&hashkey=put(md5(catx(''|'',%mf_getquotedstr(&key,quote=N))),$hex32.);'; put '&inds_keep=upcase(&inds_auto);'; put 'proc sort;'; put 'by &inds_keep &hashkey;'; put 'run;'; put '/* transpose numeric & char vars */'; put 'proc transpose data=&ds1'; put 'out=&ds2(rename=(&hashkey=key_hash _name_=tgtvar_nm col1=newval_num));'; put 'by &inds_keep &hashkey;'; put 'var _numeric_;'; put 'run;'; put 'proc transpose data=&ds1'; put 'out=&ds3('; put 'rename=(&hashkey=key_hash _name_=tgtvar_nm col1=newval_char)'; put 'where=(tgtvar_nm not in ("&hashkey","&inds_keep"))'; put ');'; put 'by &inds_keep &hashkey;'; put 'var _character_;'; put 'run;'; put '%if %index(&libds,-)>0 and %scan(&libds,2,-)=FC %then %do;'; put '/* this is a format catalog - cannot query cols directly */'; put '%let vlist="TYPE","FMTNAME","FMTROW","START","END","LABEL","MIN","MAX"'; put ',"DEFAULT","LENGTH","FUZZ","PREFIX","MULT","FILL","NOEDIT","SEXCL"'; put ',"EEXCL","HLO","DECSEP","DIG3SEP","DATATYPE","LANGUAGE";'; put '%end;'; put '%else %let vlist=%mf_getvarlist(&libds,dlm=%str(,),quote=DOUBLE);'; put 'data &ds4;'; put 'length &inds_keep $41 tgtvar_nm $32 _label_ $256;'; put 'if _n_=1 then call missing(_label_);'; put 'drop _label_;'; put 'set &ds2 &ds3 indsname=&inds_auto;'; put 'tgtvar_nm=upcase(tgtvar_nm);'; put 'if tgtvar_nm in (%upcase(&vlist));'; put 'if upcase(&inds_auto)="&ds2" then tgtvar_type=''N'';'; put 'else if upcase(&inds_auto)="&ds3" then tgtvar_type=''C'';'; put 'else do;'; put 'putlog ''ERR'' +(-1) "OR: unidentified vartype input!" &inds_auto;'; put 'call symputx(''syscc'',98);'; put 'end;'; put 'if &inds_keep="&appds" then move_type=''A'';'; put 'else if &inds_keep="&delds" then move_type=''D'';'; put 'else if &inds_keep="&modds" then move_type=''M'';'; put 'else if &inds_keep="&origds" then move_type=''O'';'; put 'else do;'; put 'putlog ''ERR'' +(-1) "OR: unidentified movetype input!" &inds_keep;'; put 'call symputx(''syscc'',99);'; put 'end;'; put 'tgtvar_nm=upcase(tgtvar_nm);'; put 'if tgtvar_nm in (%mf_getquotedstr(&key)) then is_pk=1;'; put 'else is_pk=0;'; put 'drop &inds_keep;'; put 'run;'; put '%if "&loadref"="0" %then %let loadref=%sysfunc(uuidgen());'; put '%if &processed_dttm=0 %then %let processed_dttm=%sysfunc(datetime());'; put '%let libds=%upcase(&libds);'; put '/* join orig vals for modified & deleted */'; put 'proc sql;'; put 'create table &outds as'; put 'select "&loadref" as load_ref length=36'; put ',&processed_dttm as processed_dttm format=E8601DT26.6'; put ',"%scan(&libds,1,.)" as libref length=8'; put ',"%scan(&libds,2,.)" as dsn length=32'; put ',b.key_hash length=32'; put ',b.move_type length=1'; put ',b.tgtvar_nm length=32'; put ',b.is_pk'; put ',case when b.move_type ne ''M'' then -1'; put 'when a.newval_num=b.newval_num and a.newval_char=b.newval_char then 0'; put 'else 1'; put 'end as is_diff'; put ',b.tgtvar_type length=1'; put ',case when b.move_type=''D'' then b.newval_num'; put 'else a.newval_num'; put 'end as oldval_num format=best32.'; put ',case when b.move_type=''D'' then .'; put 'else b.newval_num'; put 'end as newval_num format=best32.'; put ',case when b.move_type=''D'' then b.newval_char'; put 'else a.newval_char'; put 'end as oldval_char length=32765'; put ',case when b.move_type=''D'' then '''''; put 'else b.newval_char'; put 'end as newval_char length=32765'; put 'from &ds4(where=(move_type=''O'')) as a'; put 'right join &ds4(where=(move_type ne ''O'')) as b'; put 'on a.tgtvar_nm=b.tgtvar_nm'; put 'and a.key_hash=b.key_hash'; put 'order by move_type, key_hash,is_pk desc, tgtvar_nm;'; put '%if &mdebug=0 %then %do;'; put 'proc sql;'; put 'drop table &ds1, &ds2, &ds3, &ds4;'; put '%end;'; put '%mend mp_storediffs;'; put '/** @endcond */'; put '%macro bitemporal_dataloader('; put 'bus_from= /* Business FROM datetime variable. Req''d on'; put 'STAGING & BASE tables.*/'; put ',bus_to = /* Business TO datetime variable. Req''d on'; put 'STAGING & BASE tables. */'; put ',bus_from_override= /* Provide a hard coded BUS_FROM datetime value.*/'; put ',bus_to_override= /* provide a hard coded BUS_TO datetime value */'; put ',tech_from= /* Technical FROM datetime variable. Req''d on'; put 'BASE table only. */'; put ',tech_to = /* Technical TO datetime variable. Req''d on BASE'; put 'table only. */'; put ',processed= 0'; put ',base_lib=WORK /* Libref of the BASE table. */'; put ',base_dsn=BASETABLE /* Name of BASE table. */'; put ',append_lib=WORK /* Libref of the STAGING table. */'; put ',append_dsn=APPENDTABLE'; put ',high_date=''01JAN5999:00:00:00''dt /* High date to close out records */'; put ',PK= name sex'; put ',RK_UNDERLYING='; put ',KEEPVARS= /* Provides option for removing unwanted vars from append table */'; put ',RK_UPDATE_MAXKEYTABLE=NO /* If switching (or mix matching) with regular'; put 'SCD2 loader then set this switch to YES to'; put 'ensure the MAXKEYTABLE is updated with the'; put 'current maximum RK value for the target table'; put '*/'; put ',CHECK_UNIQUENESS=YES /* Perform a check of the APPEND table to ensure it is'; put 'unique on its business key */'; put ',ETLSOURCE=demo /* supply a value ($50.) to show as ETLSOURCE in'; put '&dclib..DATALOADS */'; put ',LOADTYPE=BITEMPORAL'; put ',RK_MAXKEYTABLE= mpe_maxkeyvalues'; put ',LOG=1 /* Switch to 0 to prevent records being added to'; put '&mpelib..mpe_DATALOADS (ie when testing)*/'; put ',DELETE_COL= _____DELETE__THIS__RECORD_____'; put '/* If this variable is found in the append dataset'; put 'then records are closed out (or deleted) in the'; put 'append table where that variable= "Yes" */'; put ',LOADTARGET=YES /* set to anything but uppercase YES to switch off'; put 'target table load and generate temp tables only */'; put ',CLOSE_VARS='; put '/*a problem with regular SCD2 or TXTEMPORAL loads is that there is'; put 'no facility to close out removed records (all records are'; put 'assumed new or changed). But how does one determine which'; put 'records are removed? Short of loading the entire table'; put 'each time? This parameter allows a set of variables'; put '(this should be a subset of the PK) to be declared, and'; put 'the macro will determine which records in the base table'; put 'need to be closed out ahead of the load.'; put 'For instance, given the following:'; put 'Base Table Staging Table'; put 'DATE ENTITY AMOUNT DATE ENTITY AMOUNT'; put 'JAN ACME4 66 JAN ACME4 66'; put 'FEB ACME4 99 FEB ACME4 99'; put 'FEB ACME1 22'; put 'By supplying DATE in CLOSE_VARS and DATE ENTITY as the PK,'; put 'the "FEB PAG 22" record would get closed out.'; put '*/'; put ',config_table=&dclib..MPE_CONFIG'; put ',dclib=&dc_libref'; put ',outds_del=work.outds_del'; put ',outds_add=work.outds_add'; put ',outds_mod=work.outds_mod'; put ',outds_audit=0'; put ');'; put '/* when changing this macro, update the version num here */'; put '%local ver;'; put '%let ver=32;'; put '%put &sysmacroname entry vars:;'; put '%put _local_;'; put '%dc_assignlib(WRITE,&base_lib) /* may not already be assigned */'; put '/* return straight away if nothing to load */'; put '%let nobs= %mf_getattrn(&append_lib..&append_dsn,NLOBS);'; put '%if &nobs=-1 %then %do;'; put 'proc sql noprint; select count(*) into: nobs from &append_lib..&append_dsn;'; put '%end;'; put '%if &nobs=0 %then %do;'; put '%put NOTE:; %put NOTE-;%put NOTE-;%put NOTE-;'; put '%put NOTE- Base dataset &append_lib..&append_dsn is empty. Nothing to upload!;'; put '%put NOTE-;%put NOTE-;%put NOTE-;'; put '%return;'; put '%end;'; put '/* hard exit if err condition exists */'; put '%mp_abort(iftrue= (&syscc > 0)'; put ',mac=bitemporal_dataloader'; put ',msg=%str(Bitemporal transform / job aborted due to SYSCC=&SYSCC status;)'; put ')'; put '%local engine_type;'; put '%let engine_type=%mf_getengine(&base_lib);'; put '%if (&engine_type=REDSHIFT or &engine_type=POSTGRES) and %length(&CLOSE_VARS)>0'; put '%then %do;'; put '%put NOTE:; %put NOTE-;%put NOTE-;%put NOTE-;'; put '%put NOTE- CLOSE_VARS functionality not yet supported in &engine_type;'; put '%put NOTE-;%put NOTE-;%put NOTE-;'; put '%return;'; put '%end;'; put '/**'; put '* The metadata functions (eg mf_existvar) will fail if the base table has a'; put '* SAS lock. So, make a snapshot of the base table for further use.'; put '* Also, make output tables (regardless).'; put '*/'; put '%local basecopy;'; put '%let basecopy=%mf_getuniquename(prefix=basecopy);'; put 'data &basecopy &outds_mod &outds_add &outds_del;'; put 'set &base_lib..&base_dsn;'; put 'stop;'; put 'run;'; put '%mp_abort(iftrue= (&syscc > 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc after base table copy - aborting due to table lock)'; put ')'; put '%local cols idx_pk md5_col ;'; put '%let md5_col=___TMP___md5;'; put '%let check_uniqueness=%upcase(&check_uniqueness);'; put '%let RK_UPDATE_MAXKEYTABLE=%upcase(&RK_UPDATE_MAXKEYTABLE);'; put '%let high_date=%unquote(&high_date);'; put '%let loadtype=%upcase(&loadtype);'; put '/* ensure irrelevant variables are cleared */'; put '%if &loadtype=BUSTEMPORAL %then %do;'; put '%let tech_from=;'; put '%let tech_to=;'; put '%end;'; put '%else %if &loadtype=TXTEMPORAL or &loadtype=UPDATE %then %do;'; put '%let bus_from=;'; put '%let bus_to=;'; put '%end;'; put '/* ensure relevant variables are supplied */'; put '%mp_abort(iftrue=(&loadtype=BITEMPORAL & %mf_verifymacvars(bus_from bus_to)=0)'; put ',mac=bitemporal_dataloader'; put ',msg=%str(Missing BUS_FROM / BUS_TO)'; put ')'; put '%mp_abort(iftrue=(&loadtype=TXTEMPORAL & %mf_verifymacvars(tech_from tech_to)=0)'; put ',mac=bitemporal_dataloader'; put ',msg=%str(Missing TECH_FROM / TECH_TO)'; put ')'; put '/**'; put '* drop any tables (may be defined as views or vice versa preventing overwrite)'; put '*/'; put '%mp_dropmembers(append bitemp0_append bitemp_cols)'; put '/* SQL Server requires its own time values */'; put '/* 9.2 will only give picture format down to seconds. 9.3 allows'; put 'milliseconds by using lower S and defining the decimal in the format name..*/'; put 'PROC FORMAT;'; put 'picture MyMSdt other=''%0Y-%0m-%0dT%0H:%0M:%0S'' (datatype=datetime);'; put 'RUN;'; put '%local dbnow;'; put '%let dbnow="%sysfunc(datetime(),%mf_fmtdttm())"dt;'; put 'data _null_;'; put '/* convert space separated macvar to comma separated for SQL processing */'; put 'call symputx(''PK_COMMA'',tranwrd(compbl("&pk"),'' '','',''),''L'');'; put 'call symputx(''PK_CNT'',countw("&pk",'' ''),''L'');'; put 'now=&dbnow;'; put 'call symputx(''NOW'',now,''L'');'; put 'call symputx(''SQLNOW'',cats("''",put(now,MyMSdt.),"''"),''L'');'; put 'length etlsource $100;'; put 'etlsource=subpad(symget(''etlsource''),1,100);'; put 'call symputx(''etlsource'',etlsource,''l'');'; put 'run;'; put '/**'; put '* Even if no PROCESSED var provided, assume that any variable named'; put '* PROCESSED_DTTM should be updated'; put '*/'; put '%if &processed=0 %then %do;'; put '%if %mf_existvar(&basecopy,PROCESSED_DTTM)'; put '%then %let processed=PROCESSED_DTTM;'; put '%else %let processed=;'; put '%end;'; put '/* extract colnames for md5 creation / change tracking */'; put 'proc contents noprint data=&base_lib..&base_dsn'; put 'out=work.bitemp_cols (keep=name type length varnum format:);'; put 'run;'; put 'proc sql noprint;'; put 'select name into: cols separated by '','''; put 'from work.bitemp_cols'; put 'where upcase(name) not in'; put '(%upcase("&bus_from","&bus_to"'; put ',"&tech_from","&tech_to"'; put ',"&processed","&delete_col")) ;'; put 'select case when type in (2,6) then cats(''put(md5(trim('',name,'')),$hex32.)'')'; put '/* multiply by 1 to strip precision errors (eg 0 != 0) */'; put '/* but ONLY if not missing, else will lose any special missing values */'; put 'else cats(''put(md5(trim(put(ifn(missing('''; put ',name,''),'',name,'','',name,''*1),binary64.))),$hex32.)'') end'; put 'into: stripcols separated by ''||'''; put 'from work.bitemp_cols'; put 'where upcase(name) not in'; put '(%upcase("&bus_from","&bus_to"'; put ',"&tech_from","&tech_to"'; put ',"&processed","&delete_col")) ;'; put '/* set default formats*/'; put '%let bus_from_fmt = datetime19.;'; put '%let bus_to_fmt = datetime19.;'; put '%let processed_fmt = datetime19.;'; put '%let tech_from_fmt = format=datetime19.;'; put '%let tech_to_fmt = format=datetime19.;'; put '%put &=stripcols;'; put '%put &=pk;'; put 'data _null_;'; put 'set work.bitemp_cols;'; put 'if type=2 or type=6 then do;'; put 'length fmt $49.;'; put 'if format='''' then fmt=cats(''$'',length,''.'');'; put 'else fmt=cats(format,formatl,''.'');'; put 'end;'; put 'else do;'; put 'if format='''' then fmt=cats(length,''.'');'; put 'else fmt=cats(format,formatl,''.'',formatd);'; put 'end;'; put 'if upcase(name)="%upcase(&bus_from)" then'; put 'call symputx(''bus_from_fmt'',fmt,''L'');'; put 'else if upcase(name)="%upcase(&bus_to)" then'; put 'call symputx(''bus_to_fmt'',fmt,''L'');'; put 'else if upcase(name)="%upcase(&tech_from)" then'; put 'call symputx(''tech_from_fmt'',"format="!!fmt,''L'');'; put 'else if upcase(name)="%upcase(&tech_to)" then'; put 'call symputx(''tech_to_fmt'',"format="!!fmt,''L'');'; put 'else if upcase(name)="%upcase(&processed)" then'; put 'call symputx(''processed_fmt'',fmt,''L'');'; put 'run;'; put '%if %index(%quote(&cols),___TMP___) %then %do;'; put '%let msg=%str(Table contains a variable name containing "___TMP___".%trim('; put ') This may conflict with temp variable generation!!);'; put '%mp_abort(msg=&msg,mac=bitemporal_dataloader);'; put '%let syscc=5;'; put '%return;'; put '%end;'; put '/* if transaction dates appear on the APPEND table, need to remove them */'; put '%local drop_tx_dates /* used in append table */'; put 'drop_tx_dates_noobs /* used to take the base table structure */;'; put '%if %mf_existvar(&append_lib..&append_dsn, &tech_from)'; put '%then %let drop_tx_dates=&tech_from;'; put '%if %mf_existvar(&append_lib..&append_dsn, &tech_to)'; put '%then %let drop_tx_dates=&drop_tx_dates &tech_to;'; put '%if %length(%trim(&drop_tx_dates))>0'; put '%then %let drop_tx_dates=(drop=&drop_tx_dates);'; put '%if %mf_existvar(&basecopy, &tech_from)'; put '%then %let drop_tx_dates_noobs=&tech_from;'; put '%if %mf_existvar(&basecopy, &tech_to)'; put '%then %let drop_tx_dates_noobs=&drop_tx_dates_noobs &tech_to;'; put '%if %length(%trim(&drop_tx_dates_noobs))>0'; put '%then %let drop_tx_dates_noobs=(drop=&drop_tx_dates_noobs obs=0);'; put '%else %let drop_tx_dates_noobs=(obs=0);'; put '/**'; put '* Lock the table. This is necessary as we are doing a two part update (first'; put '* closing records then appending new records). It is theoretically possible'; put '* that an upload may occur whilst preparing the staging tables. And the'; put '* staging tables are about to be prepared..'; put '*/'; put '%if &LOADTARGET = YES %then %do;'; put '%put locking &base_lib..&base_dsn;'; put '%mp_lockanytable(LOCK,'; put 'lib=&base_lib,ds=&base_dsn,ref=&ETLSOURCE,ctl_ds=&dclib..mpe_lockanytable'; put ')'; put '%if "&outds_audit" ne "0" %then %do;'; put '%put locking &outds_audit;'; put '%mp_lockanytable(LOCK'; put ',lib=%scan(&outds_audit,1,.)'; put ',ds=%scan(&outds_audit,2,.)'; put ',ref=&ETLSOURCE'; put ',ctl_ds=&dclib..mpe_lockanytable'; put ')'; put '%end;'; put '%end;'; put '%else %do;'; put '/* not an actual load, so avoid updating the max key table in next step. */'; put '%let rk_update_maxkeytable=NO;'; put '%end;'; put '%if %length(&RK_UNDERLYING)>0 %then %do;'; put '%mp_retainedkey('; put 'base_lib=&base_lib'; put ',base_dsn=&base_dsn'; put ',append_lib=&append_lib'; put ',append_dsn=&append_dsn'; put ',retained_key=&pk'; put ',business_key=&rk_underlying'; put ',check_uniqueness=&CHECK_UNIQUENESS'; put ',outds=work.append'; put '%if &rk_update_maxkeytable=NO %then %do;'; put ',maxkeytable=0'; put '%end;'; put '%else %do;'; put ',maxkeytable=&dclib..&RK_MAXKEYTABLE'; put '%end;'; put ',locktable=&dclib..mpe_lockanytable'; put '%if &loadtype=BITEMPORAL or &loadtype=TXTEMPORAL %then %do;'; put ',filter_str=%str( (where=( &now < &tech_to)) )'; put '%end;'; put ')'; put '%end;'; put '%else %do;'; put 'proc sql;'; put 'create view work.append as select * from &append_lib..&append_dsn;'; put '%end;'; put '/**'; put '* generate md5 for append table'; put '*/'; put '/* it is possible the source dataset has additional (unwanted) columns.'; put 'Drop if specified; */'; put '%if %length(&keepvars)>0 %then %do;'; put '/* remove tech dates from keepvars as they are generated later */'; put '%let keepvars=%sysfunc(tranwrd(%str( &keepvars ),%str( &tech_from ),%str( )));'; put '%let keepvars=%sysfunc(tranwrd(%str( &keepvars ),%str( &tech_to ),%str( )));'; put '%let keepvars=(keep=&keepvars &bus_from &bus_to &processed &md5_col);'; put '%end;'; put '/* CAS varchar types cause append issues here, so perform autoconvert'; put 'by creating empty local table first */'; put 'data;'; put 'set &base_lib..&base_dsn &drop_tx_dates_noobs;'; put 'run;'; put '%local emptybasetable; %let emptybasetable=&syslast;'; put 'data work.bitemp0_append &keepvars &outds_del(drop=&md5_col )'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5" %then %do;'; put '/nonote2err'; put '%end;'; put ';'; put '/* apply formats for bitemporal vars but not tx dates which are added later */'; put '%if %length(&keepvars)>0 and &loadtype=BITEMPORAL %then %do;'; put 'format &bus_from &bus_from_fmt;'; put 'format &bus_to &bus_to_fmt;'; put '%end;'; put 'set &emptybasetable /* base table reqd in case append has fewer cols */'; put 'work.append &drop_tx_dates;'; put '%if %length(%str(&bus_from_override))>0 %then %do;'; put '&bus_from= %unquote(&bus_from_override) ;'; put '%end;'; put '%if %length(%str(&bus_to_override))>0 %then %do;'; put '&bus_to= %unquote(&bus_to_override) ;'; put '%end;'; put 'length &md5_col $32;'; put '&md5_col=put(md5(&stripcols),hex32.);'; put '%if %length(&processed)>0 %then %do;'; put 'format &processed &processed_fmt;'; put '&processed=&now;'; put '%end;'; put '/**'; put '* If a delete column exists then create the delete dataset'; put '*/'; put '%if %mf_existvar(&append_lib..&append_dsn, &delete_col) %then %do;'; put 'drop &delete_col;'; put 'if upcase(&delete_col) = "YES" then output &outds_del ;'; put 'else output work.bitemp0_append ;'; put 'run;'; put '%if %mf_getattrn(&outds_del,NLOBS)>0 %then %do;'; put '%bitemporal_closeouts('; put 'tech_from=&tech_from'; put ',tech_to = &tech_to'; put ',base_lib=&base_lib'; put ',base_dsn=&base_dsn'; put ',append_lib=work'; put ',append_dsn=%scan(&outds_del,-1,.)'; put ',PK=&bus_from &pk'; put ',NOW=&dbnow'; put ',loadtarget=&loadtarget'; put ',loadtype=&loadtype'; put ')'; put '%end;'; put '%end;'; put '%else %do;'; put 'output work.bitemp0_append;'; put 'run;'; put '%end;'; put '%mp_abort(iftrue= (&syscc gt 0 at line 494)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc)'; put ')'; put '%if %length(&close_vars)>0 %then %do;'; put '/**'; put '* need to close out records that are not provided'; put '*/'; put 'proc sql;'; put 'create table bitemp1_closevars1 as'; put 'select distinct a.%mf_getquotedstr(in_str=&pk,dlm=%str(,a.),quote=)'; put 'from &base_lib..&base_dsn a'; put 'inner join work.bitemp0_append b'; put 'on 1=1'; put '/* join on closevars key */'; put '%do idx_pk=1 %to %sysfunc(countw(&close_vars));'; put '%let idx_val=%scan(&close_vars,&idx_pk);'; put 'and a.&idx_val=b.&idx_val'; put '%end;'; put '/* filter base on tech dates if necessary */'; put '%if &loadtype=TXTEMPORAL %then %do;'; put 'where a.&tech_from <=&now and &now < a.&tech_to'; put '%end;'; put ';'; put 'create table bitemp1_closevars2 as'; put 'select distinct a.*'; put 'from bitemp1_closevars1 a'; put 'left join work.bitemp0_append b'; put 'on 1=1'; put '/* join on primary key */'; put '%do idx_pk=1 %to %sysfunc(countw(&pk));'; put '%let idx_val=%scan(&pk,&idx_pk);'; put 'and a.&idx_val=b.&idx_val'; put '%end;'; put '/* identify removed records by null value in a field in PK but not close_vars'; put '*/'; put 'where b.%scan('; put '%mf_wordsInStr1ButNotStr2(Str1=&pk,Str2=&close_vars),1,%str( )'; put ') IS NULL'; put ';'; put '%if %mf_getattrn(bitemp1_closevars2,NLOBS)>0 %then %do;'; put '%bitemporal_closeouts('; put 'tech_from=&tech_from'; put ',tech_to = &tech_to'; put ',base_lib=&base_lib'; put ',base_dsn=&base_dsn'; put ',append_lib=work'; put ',append_dsn=bitemp1_closevars2'; put ',PK=&bus_from &pk'; put ',NOW=&dbnow'; put ',loadtarget=&loadtarget'; put ',loadtype=&loadtype'; put ')'; put '%end;'; put '%end;'; put '/* return if nothing to load (was just deletes) */'; put '%if %mf_getattrn(work.bitemp0_append,NLOBS)=0 %then %do;'; put '%put NOTE:; %put NOTE-;%put NOTE-;%put NOTE-;'; put '%put NOTE- No updates - just deletes!;'; put '%put NOTE-;%put NOTE-;%put NOTE-;'; put '%end;'; put '/**'; put '* If applying manual overrides to business dates, then the input table MUST'; put '* be unique on the PK. Check, and if not - abort.'; put '*/'; put '%local msg;'; put '%if %length(&bus_from_override.&bus_to_override)>0 or &CHECK_UNIQUENESS=YES'; put '%then %do;'; put 'proc sort data=work.bitemp0_append out=work.bitemp0_check nodupkey;'; put 'by &pk;'; put 'run;'; put '%if %mf_getattrn(work.bitemp0_check,NLOBS)'; put 'ne %mf_getattrn(work.bitemp0_append,NLOBS)'; put '%then %do;'; put '%let msg=INPUT table &append_lib..&append_dsn is not unique on PK (&pk);'; put '%mp_lockanytable(UNLOCK,lib=&base_lib,ds=&base_dsn,ref=&ETLSOURCE (&msg),'; put 'ctl_ds=&dclib..mpe_lockanytable'; put ')'; put '%mp_lockanytable(UNLOCK'; put ',lib=%scan(&outds_audit,1,.)'; put ',ds=%scan(&outds_audit,2,.)'; put ',ref=&ETLSOURCE'; put ',ctl_ds=&dclib..mpe_lockanytable'; put ')'; put '%mp_abort(msg=&msg,mac=bitemporal_dataloader.sas);'; put '%end;'; put '%end;'; put '/**'; put '* extract from BASE table. Only want matching records, as could be very BIG.'; put '* New records are subsequently identified via left join and test for nulls.'; put '*/'; put '%local temp_table temp_table2 base_table baselib_schema;'; put '%put DCNOTE: Extracting matching observations from &base_lib..&base_dsn;'; put '%if &engine_type=OLEDB %then %do;'; put '%let temp_table=##BITEMP_&base_dsn;'; put '%if &loadtype=BITEMPORAL or &loadtype=TXTEMPORAL %then'; put '%let base_table=(select * from [dbo].&base_dsn'; put 'where convert(datetime,&SQLNOW) < &tech_to );'; put '%else %let base_table=[dbo].&base_dsn;'; put 'proc sql;'; put 'create table &base_lib.."&temp_table"n as'; put 'select * from work.bitemp0_append;'; put '/* open up a connection for pass through SQL */'; put '%dc_assignlib(WRITE,&base_lib,passthru=myAlias)'; put 'create table work.bitemp0_base as select * from connection to myAlias('; put '%end;'; put '%else %if &engine_type=REDSHIFT or &engine_type=POSTGRES %then %do;'; put '/* grab schema */'; put '%let baselib_schema=%mf_getschema(&base_lib);'; put '%if &baselib_schema.X ne X %then %let baselib_schema=&baselib_schema..;'; put '/* grab redshift config */'; put '%local redcnt; %let redcnt=0;'; put '%if &engine_type=REDSHIFT %then %do;'; put 'data _null_;'; put 'set &config_table(where=(var_scope=''DCBL_REDSH'' and var_active=1));'; put 'x+1;'; put 'call symputx(cats(''rednm'',x),var_value,''l'');'; put 'call symputx(cats(''redval'',x),var_value,''l'');'; put 'call symputx(''redcnt'',x,''l'');'; put 'run;'; put '%end;'; put '/* cannot persist temp tables so must create a temporary permanent table */'; put '%let temp_table=%mf_getuniquename(prefix=XDCTEMP);'; put '%if &loadtype=BITEMPORAL or &loadtype=TXTEMPORAL %then'; put '%let base_table=(select * from &baselib_schema.&base_dsn'; put 'where timestamp &sqlnow < &tech_to );'; put '%else %let base_table=&baselib_schema.&base_dsn;'; put '/* make empty table first - must clone & drop extra cols as autoload is bad */'; put '%dc_assignlib(WRITE,&base_lib,passthru=myAlias)'; put 'exec (create table &temp_table (like &baselib_schema.&base_dsn)) by myAlias;'; put '%if &engine_type=REDSHIFT %then %do;'; put 'exec (alter table &temp_table alter sortkey none) by myAlias;'; put '%end;'; put '%local dropcols;'; put '%let dropcols=%mf_wordsinstr1butnotstr2('; put 'str1=%upcase(%mf_getvarlist(&basecopy))'; put ',str2=%upcase(&pk)'; put ');'; put '%if %length(&dropcols>0) %then %do idx_pk=1 %to %sysfunc(countw(&dropcols));'; put '%put &=dropcols;'; put '%let idx_val=%scan(&dropcols,&idx_pk);'; put 'exec(alter table &temp_table drop column &idx_val;) by myAlias;'; put '%end;'; put 'exec (alter table &temp_table add column &md5_col varchar(32);) by myAlias;'; put '/* create view to strip formats and avoid warns in log */'; put 'data work.vw_bitemp0/view=work.vw_bitemp0;'; put 'set work.bitemp0_append(keep=&pk &md5_col);'; put 'format _all_;'; put 'run;'; put 'proc append base=&base_lib..&temp_table'; put '%if &engine_type=REDSHIFT %then %do;'; put '('; put '%do idx_pk=1 %to &redcnt;'; put '&&rednm&idx_pk = &&redval&idxpk'; put '%end;'; put ')'; put '%end;'; put 'data=work.vw_bitemp0 force nowarn;'; put 'run;'; put '/* open up a connection for pass through SQL */'; put '%dc_assignlib(WRITE,&base_lib,passthru=myAlias)'; put 'create table work.bitemp0_base as select * from connection to myAlias('; put '%end;'; put '%else %if &engine_type=CAS %then %do;'; put '%if &loadtype=BITEMPORAL or &loadtype=TXTEMPORAL %then'; put '%let base_table=&base_lib..&base_dsn'; put '(where=(&tech_from <=&now and &now < &tech_to));'; put '%else %let base_table=&base_lib..&base_dsn;'; put '%let temp_table=CASUSER.%mf_getuniquename(prefix=DC);'; put 'data &temp_table;'; put 'set work.bitemp0_append;'; put 'run;'; put '%let bitemp0base=CASUSER.%mf_getuniquename(prefix=DC);'; put 'proc fedsql sessref=dcsession;'; put 'create table &bitemp0base{options replace=true} as'; put '%end;'; put '%else %do;'; put '%let temp_table=work.bitemp0_append;'; put '%if &loadtype=BITEMPORAL or &loadtype=TXTEMPORAL %then'; put '%let base_table=&base_lib..&base_dsn'; put '(where=(&tech_from <=&now and &now < &tech_to));'; put '%else %let base_table=&base_lib..&base_dsn;'; put 'proc sql;'; put 'create table work.bitemp0_base as'; put '%end;'; put 'select a.&md5_col /* this identifies NEW records */'; put ', b.*'; put '/* assume first PK field cannot be null (if defined in a PK constraint then'; put 'it definitely cannot be null) */'; put ', case when b.%scan(&pk,1) IS NULL then 1 else 0 end as ___TMP___NEW_FLG'; put 'from &baselib_schema.&temp_table a'; put 'left join &base_table b'; put 'on 1=1'; put '%do idx_pk=1 %to &pk_cnt;'; put '%let idx_val=%scan(&pk,&idx_pk);'; put 'and a.&idx_val=b.&idx_val'; put '%end;'; put '%if &engine_type=OLEDB or &engine_type=REDSHIFT or &engine_type=POSTGRES'; put '%then %do;'; put '); proc sql; drop table &base_lib.."&temp_table"n;'; put '%end;'; put '%else %if &engine_type=CAS %then %do;'; put ';'; put 'quit;'; put 'data work.bitemp0_base;'; put 'set &bitemp0base;'; put 'run;'; put 'proc sql;'; put 'drop table &temp_table;'; put 'drop table &bitemp0base;'; put '%end;'; put '%else %do;'; put ';'; put '%end;'; put '/**'; put '* matching & changed records are those without NULL key values'; put '* &idx_val resolves to rightmost PK value (loop above)'; put '*/'; put '%put syscc (line525)=&syscc, sqlrc=&sqlrc;'; put '%mp_abort(iftrue= (&syscc gt 0 or &sqlrc>0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc sqlrc=&sqlrc)'; put ')'; put '%put hashcols2=&stripcols;'; put 'proc sql;'; put 'create table work.bitemp1_current(drop=___TMP___NEW_FLG) as'; put 'select *'; put ', put(md5(&stripcols),$hex32.) as &md5_col'; put 'from work.bitemp0_base (drop=&md5_col)'; put 'where ___TMP___NEW_FLG=0;'; put '/**'; put '* NEW records were identified in ___TMP___NEW_FLG in bitemp0_base'; put '*/'; put 'proc sql;'; put 'create table &outds_add'; put '(drop=&md5_col'; put '%if %mf_existvar(work.bitemp0_base, &delete_col) %then %do;'; put '&delete_col'; put '%end;'; put ')'; put 'as select a.*'; put '%if &loadtype=BITEMPORAL or &loadtype=TXTEMPORAL %then %do;'; put ',&now as &tech_from &tech_from_fmt'; put ',&high_date as &tech_to &tech_to_fmt'; put '%end;'; put 'from work.bitemp0_append a /* STAGING records (mix of existing & new) */'; put ', work.bitemp0_base b /* BASE records (contains null values for new) */'; put 'where a.&md5_col=b.&md5_col /* took staging md5 across in left join */'; put 'and b.___TMP___NEW_FLG=1; /* NEW records also identified in bitemp0_base */'; put '/**'; put '* identify INSERTS. These are records with the same business key but'; put '* the bus_from and bus_to value are higher / lower (respectively)'; put '* such that the existing record needs to be SPLIT to surround the new'; put '* record.'; put '* eg: OLD RECORD from=1 to=10'; put '* NEW RECORD from=5 to=7'; put '*'; put '* APPENDED RECORDS:'; put '* - from=1 to=5'; put '* - from=5 to=7'; put '* - from=7 to=10'; put '*/'; put '/* inserts cannot happen with TXTEMPORAL */'; put '%if &loadtype=BITEMPORAL or &loadtype=BUSTEMPORAL %then %do;'; put '/* IDENTIFY */'; put 'create table work.bitemp3_inserts as'; put 'select b.*'; put ',a.&bus_from as ___TMP___from'; put ',a.&bus_to as ___TMP___to'; put 'from work.bitemp0_append a'; put ',work.bitemp1_current b'; put 'where a.&bus_from > b.&bus_from'; put 'and a.&bus_to < b.&bus_to'; put '%do idx_pk=1 %to &pk_cnt;'; put '%let idx_val=%scan(&pk,&idx_pk);'; put 'and a.&idx_val=b.&idx_val'; put '%end;'; put 'order by'; put '/* compress blanks and then insert commas (as the datetime fields may'; put 'not be in use) */'; put '%sysfunc(tranwrd(%sysfunc(compbl('; put '&pk &bus_from &bus_to &processed'; put ')),%str( ), %str(,)))'; put ';'; put '/* SPLIT */'; put 'data work.bitemp3a_inserts (drop=___TMP___from ___TMP___retain ___TMP___to) ;'; put 'set work.bitemp3_inserts;'; put 'by &pk &bus_from &bus_to &processed;'; put 'if first.&idx_val then do;'; put '___TMP___retain=&bus_to;'; put '&bus_to=___TMP___from;'; put 'output;'; put '&bus_to=___TMP___retain;'; put 'end;'; put 'if last.&idx_val then do;'; put '&bus_from=___TMP___to;'; put 'output;'; put 'end;'; put 'run;'; put '%end;'; put '%else %do;'; put '/* TX temporal load */'; put 'data work.bitemp3a_inserts;'; put 'set work.bitemp1_current;'; put 'stop;'; put 'run;'; put '%end;'; put '/* APPEND */'; put 'proc sql;'; put 'create view work.bitemp3a_view as'; put 'select * from work.bitemp1_current'; put 'where &md5_col not in (select &md5_col from work.bitemp3a_inserts);'; put 'data bitemp3b_newbase;'; put 'set work.bitemp3a_inserts work.bitemp3a_view;'; put 'run;'; put '/** do not use! this converts short numerics into 8 bytes'; put 'proc sql;'; put 'create table work.bitemp3b_newbase as'; put 'select * from work.bitemp3a_inserts'; put 'union corr'; put 'select * from work.bitemp1_current'; put 'where &md5_col not in (select &md5_col from work.bitemp3a_inserts);'; put '*/'; put '/**'; put '* identify CHANGED records from staging.'; put '* Same business key with different temporal dates or md5 value'; put '* This table must be overlayed onto / into existing business history'; put '*/'; put 'proc sql;'; put 'create table work.bitemp4_updated as select distinct a.*'; put 'from work.bitemp0_append a'; put ',work.bitemp3b_newbase b'; put 'where 1=1'; put '%do idx_pk=1 %to &pk_cnt;'; put '%let idx_val=%scan(&pk,&idx_pk);'; put 'and a.&idx_val=b.&idx_val'; put '%end;'; put 'and ( a.&md5_col ne b.&md5_col'; put '%if &loadtype=BITEMPORAL or &loadtype=BUSTEMPORAL %then %do;'; put 'OR (a.&bus_from ne b.&bus_from or a.&bus_to ne b.&bus_to)'; put '%end;'; put ')'; put ';'; put '/**'; put '* This section would have been one simple step with union all'; put '* but that converts short numerics into 8 bytes!'; put '* so, convoluted alternative to retain the same functionality.'; put '*/'; put '/* base records */'; put 'create view work.bitemp4_prep1 as'; put 'select ''BASE'' as ___TMP___'; put ',b.*'; put 'from work.bitemp4_updated a'; put ',work.bitemp3b_newbase b'; put 'where 1'; put '%do idx_pk=1 %to &pk_cnt;'; put '%let idx_val=%scan(&pk,&idx_pk);'; put 'and a.&idx_val=b.&idx_val'; put '%end;'; put ';'; put '/* updated records */'; put 'create view work.bitemp4_prep2 as'; put 'select ''STAG'' as ___TMP___ ,*'; put 'from work.bitemp4_updated;'; put '/* ensure we only keep columns that appear in both */'; put '%local bp1 bp2 bp3 bp4;'; put '%let bp1=%mf_getvarlist(bitemp4_prep1);'; put '%let bp2=%mf_getvarlist(bitemp4_prep2);'; put '%let bp3=%mf_wordsInStr1ButNotStr2(Str1=&bp1,Str2=&bp2);'; put '%let bp4=%mf_wordsInStr1ButNotStr2(Str1=&bp2,Str2=&bp1);'; put 'data work.bitemp4_prep3/view=bitemp4_prep3;'; put 'set bitemp4_prep1 bitemp4_prep2;'; put '%if %length(XX&bp3&bp4)>2 %then %do;'; put 'drop &bp3 &bp4 ;'; put '%end;'; put 'run;'; put '/* remove duplicates */'; put 'proc sql;'; put 'create table work.bitemp4a_allrecs as'; put 'select distinct *'; put 'from work.bitemp4_prep3'; put 'order by'; put '/* compress blanks and then insert commas (as the datetime fields'; put 'may not be in use) */'; put '%sysfunc(tranwrd(%sysfunc(compbl('; put '&pk &bus_from &bus_to &processed'; put ')),%str( ), %str(,)))'; put ';'; put '%if &loadtype=BITEMPORAL or &loadtype=BUSTEMPORAL %then %do;'; put '/* this section aligns the business dates'; put '(eg for inserts or overlaps in the range) */'; put 'data work.bitemp4b_firstpass (drop=___TMP___cond ___TMP___from ___TMP___to );'; put 'set work.bitemp4a_allrecs;'; put 'by &pk &bus_from &bus_to &processed;'; put 'retain ___TMP___cond ''Name of Condition'';'; put 'retain ___TMP___from ___TMP___to 0;'; put '___TMP___md5lag=lag(&md5_col);'; put '/* reset retained variables */'; put 'if first.&idx_val then do;'; put 'call missing (___TMP___cond, ___TMP___from, ___TMP___to,___TMP___md5lag);'; put 'end;'; put 'else do;'; put '/* if record is identical, carry forward bus_from (and bus_to if higher)*/'; put 'if &md5_col=___TMP___md5lag then do;'; put '&bus_from=___TMP___from;'; put 'if &bus_to<___TMP___to then &bus_to=___TMP___to;'; put 'end;'; put 'end;'; put 'if ___TMP___=''STAG'' then do;'; put '/* need to carry forward the closing record */'; put '___TMP___cond=''Condition 1'';'; put 'end;'; put 'else if ___TMP___cond=''Condition 1'' then do;'; put '/* else ensure bus_from starts from prior record bus_to */'; put 'if &md5_col ne ___TMP___md5lag and &bus_from <= ___TMP___to'; put 'then &bus_from= ___TMP___to;'; put '/* new record may replace old record entirely */'; put 'if &bus_to <= &bus_from then delete;'; put 'else call missing (___TMP___cond, ___TMP___from, ___TMP___to);'; put 'end;'; put '___TMP___from=&bus_from;'; put '___TMP___to=&bus_to;'; put 'run;'; put '%end;'; put '%else %do;'; put '/* keep staged records only */'; put 'data work.bitemp4b_firstpass;'; put 'set work.bitemp4a_allrecs;'; put 'if ___TMP___=''STAG'';'; put 'run;'; put '%end;'; put '/* next phase is to pass through in reverse - so set up the sort statement */'; put '%local byvar;'; put '%do idx_pk=1 %to &pk_cnt;'; put '%let byvar=&byvar descending %scan(&pk,&idx_pk);'; put '%end;'; put '%if &loadtype=BITEMPORAL or &loadtype=BUSTEMPORAL'; put '%then %let byvar=&byvar descending &bus_from descending &bus_to;'; put '/* if matching bus dates supplied, need to ensure we also have a sort'; put 'between BASE and STAGING tables */'; put '%let byvar=&byvar descending ___TMP___;'; put 'proc sort data=work.bitemp4b_firstpass out=work.bitemp4c_sort ;'; put 'by &byvar;'; put 'run;'; put '/**'; put '* Now (in reverse) pass back business start dates'; put '*/'; put 'data work.bitemp4d_secondpass;'; put '%if &loadtype=BITEMPORAL or &loadtype=TXTEMPORAL %then %do;'; put '&tech_from=&now;'; put '&tech_to=&high_date;'; put '%end;'; put 'set work.bitemp4c_sort ;'; put 'by &byvar;'; put 'retain ___TMP___cond ''Name of Condition'';'; put 'retain ___TMP___from ___TMP___to 0;'; put '%if &loadtype=BITEMPORAL or &loadtype=BUSTEMPORAL %then %do;'; put '/* put / _all_ /;*/'; put '___TMP___md5lag=lag(&md5_col);'; put 'if first.&idx_val then do;'; put '/* reset retained variables */'; put 'call missing (___TMP___cond,___TMP___from,___TMP___to,___TMP___md5lag);'; put 'end;'; put 'else do;'; put '/* if record is identical, carry back bus_to */'; put 'if &md5_col=___TMP___md5lag then &bus_to=___TMP___to;'; put 'end;'; put 'if ___TMP___=''STAG'' then do;'; put '/* need to carry forward the closing record */'; put '___TMP___cond=''Condition 2'';'; put 'end;'; put 'else if ___TMP___cond=''Condition 2'' then do;'; put '/* else ensure bus_to stops at subsequent record bus_from */'; put 'if &md5_col ne ___TMP___md5lag and &bus_to >= ___TMP___from'; put 'then &bus_to= ___TMP___from;'; put '/* new record may replace old record entirely */'; put 'if &bus_from >= &bus_to then delete;'; put 'if &bus_from=___TMP___from and &bus_to=___TMP___to then delete;'; put 'else call missing (___TMP___cond, ___TMP___from, ___TMP___to);'; put 'end;'; put '___TMP___from=&bus_from;'; put '___TMP___to=&bus_to;'; put '%end;'; put 'run;'; put '%put syscc (line600)=&syscc;'; put '/**'; put 'There may still be some records (eg old business history) which have not'; put 'changed.'; put 'Need to identify these and remove from the append so they are not updated'; put 'unnecessarily. This is done by generating a new md5 (which INCLUDES the'; put 'business key) and any matching / identical records are split out (from those'; put 'that need to be updated).'; put '*/'; put '%if &loadtype=BITEMPORAL %then %do;'; put '%let cat_string=catx(''|'' ,&bus_from,&bus_to);'; put 'data bitemp5a_lkp (keep=&md5_col);'; put 'set bitemp0_base;'; put '/* for BITEMPORAL we need to compare business dates also */'; put '&md5_col=put(md5(&cat_string!!''|''!!&stripcols),$hex32.);'; put 'run;'; put 'data bitemp5b_updates;'; put 'set bitemp4d_secondpass;'; put 'if _n_=1 then do;'; put 'dcl hash md5_lkp(dataset:''bitemp5a_lkp'');'; put 'md5_lkp.definekey("&md5_col");'; put 'md5_lkp.definedone();'; put 'end;'; put '/* drop old md5 col as will rebuild with new business dates */'; put '&md5_col=put(md5(&cat_string!!''|''!!&stripcols),$hex32.) ;'; put 'if md5_lkp.check()=0 then delete;'; put 'run;'; put 'proc sql;'; put '/* get min bus from as will update (close out) all records from this point'; put '(for that PK)*/'; put 'create table work.bitemp5d_subquery as'; put 'select &pk_comma, min(&bus_from)as &bus_from, max(&bus_to) as &bus_to'; put 'from work.bitemp5b_updates'; put 'group by &pk_comma;'; put '/* index has a huge efficiency impact on upcoming nested subquery */'; put 'create index index1 on work.bitemp5d_subquery(&pk_comma,&bus_from, &bus_to);'; put '%let lastds=work.bitemp5b_updates;'; put '%end;'; put '%else %if &loadtype=TXTEMPORAL or &loadtype=UPDATE %then %do;'; put 'proc sql;'; put 'create table work.bitemp5d_subquery as'; put 'select distinct &pk_comma'; put 'from bitemp4d_secondpass;'; put '%let lastds=work.bitemp4d_secondpass;'; put '%end;'; put '%else %let lastds=work.bitemp4d_secondpass;'; put '/* create single append table (an overlapped pre-sert may be classed as'; put 'both an update AND a new record). Also create temp views that may be'; put 'used for pre-load analysis. */'; put 'data &outds_mod;'; put 'set &lastds(drop=___TMP___: &md5_col);'; put 'run;'; put 'data bitemp6_allrecs / view=bitemp6_allrecs;'; put 'set &outds_mod /* UPDATED records */'; put '&outds_add /* NEW records */;'; put 'run;'; put 'proc sort data=work.bitemp6_allrecs'; put 'out=work.bitemp6_unique'; put 'noduprec'; put 'dupout=work.xx_BADBADBAD;'; put 'by _all_;'; put 'run;'; put '/* we have all our temp tables now so exit if this is all that is needed */'; put '%if &LOADTARGET ne YES %then %return;'; put '/* also exit if an err condition exists */'; put '%if &syscc>0 %then %do;'; put '%put syscc=&syscc;'; put '%mp_lockanytable(UNLOCK,lib=&base_lib,ds=&base_dsn,ref=&ETLSOURCE,'; put 'ctl_ds=&dclib..mpe_lockanytable'; put ')'; put '%if "&outds_audit" ne "0" %then %do;'; put '%mp_lockanytable(UNLOCK'; put ',lib=%scan(&outds_audit,1,.)'; put ',ds=%scan(&outds_audit,2,.)'; put ',ref=&ETLSOURCE'; put ',ctl_ds=&dclib..mpe_lockanytable'; put ')'; put '%end;'; put '%end;'; put '%mp_abort(iftrue= (&syscc>0)'; put ',mac=&sysmacroname in &_program'; put ',msg=%str(Bitemporal transform / job aborted due to SYSCC=&SYSCC status)'; put ')'; put '/* final check - abort if a lock has appeared on the target or audit table */'; put '%mp_lockfilecheck(libds=&base_lib..&base_dsn)'; put '%if %mf_existds(&outds_audit) %then %do;'; put '%mp_lockfilecheck(libds=&outds_audit)'; put '%end;'; put '/**'; put '* STAGING TABLES PREPARED, ERR CONDITION TESTED FOR.. NOW TO LOAD!!'; put '*/'; put '/**'; put '* First, CLOSE OUT changed records (if not a REPLACE)'; put '* Note that SAS does not support ANSI standard for UPDATE with a join condition.'; put '* However - this can be worked around using a nested subquery..'; put '*/'; put 'data _null_;'; put 'putlog "&sysmacroname: CLOSEOUTS commencing";'; put 'run;'; put '%if %mf_getattrn(&lastds,NLOBS)=0 %then %do;'; put 'data _null_;'; put 'putlog "&sysmacroname: No closeouts needed";'; put 'run;'; put '%end;'; put '%else %if &engine_type=CAS %then %do;'; put '%mp_abort(iftrue= (&loadtype=BITEMPORAL or &loadtype=TXTEMPORAL)'; put ',mac=&sysmacroname in &_program'; put ',msg=%str(&loadtype not yet supported in CAS engine)'; put ')'; put '/* create temp table for deletions */'; put '%local delds;%let delds=%mf_getuniquename(prefix=DC);'; put 'data casuser.&delds;'; put 'set work.bitemp5d_subquery;'; put 'run;'; put '/* delete the records */'; put 'proc cas ;'; put 'table.deleteRows / table={'; put 'caslib="&base_lib",'; put 'name="&base_dsn",'; put 'where="1=1",'; put 'whereTable={caslib=''CASUSER'',name="&delds"}'; put '};'; put 'quit;'; put '/* drop temp table */'; put 'proc sql;'; put 'drop table CASUSER.&delds;'; put '%end;'; put '%else %if (&loadtype=BITEMPORAL or &loadtype=TXTEMPORAL or &loadtype=UPDATE)'; put '%then %do;'; put 'data _null_;'; put 'putlog "&sysmacroname: &loadtype operation using &engine_type engine";'; put 'run;'; put '%local flexinow;'; put 'proc sql;'; put '/* if OLEDB then create a temp table for efficiency */'; put '%local innertable;'; put '%if &engine_type=OLEDB %then %do;'; put '%let innertable=[##BITEMP_&base_dsn];'; put '%let top_table=[dbo].&base_dsn;'; put '%let flexinow=&SQLNOW;'; put 'create table &base_lib.."##BITEMP_&base_dsn"n as'; put 'select * from work.bitemp5d_subquery;'; put '/* open up a connection for pass through SQL */'; put '%dc_assignlib(WRITE,&base_lib,passthru=myAlias)'; put 'execute('; put '%end;'; put '%else %if &engine_type=REDSHIFT or &engine_type=POSTGRES %then %do;'; put '%let innertable=%mf_getuniquename(prefix=XDCTEMP);'; put '%let top_table=&baselib_schema.&base_dsn;'; put '%let flexinow=timestamp &SQLNOW;'; put '/* make empty table first - must clone & drop extra cols'; put 'as autoload is bad */'; put '%dc_assignlib(WRITE,&base_lib,passthru=myAlias)'; put 'exec (create table &innertable (like &baselib_schema.&base_dsn)) by myAlias;'; put '%if &engine_type=REDSHIFT %then %do;'; put 'exec (alter table &innertable alter sortkey none) by myAlias;'; put '%end;'; put '%let dropcols=%mf_wordsinstr1butnotstr2('; put 'str1=%upcase(%mf_getvarlist(&basecopy))'; put ',str2=%upcase(%mf_getvarlist(work.bitemp5d_subquery))'; put ');'; put '%if %length(&dropcols>0) %then %do idx_pk=1 %to %sysfunc(countw(&dropcols));'; put '%put &=dropcols;'; put '%let idx_val=%scan(&dropcols,&idx_pk);'; put 'exec(alter table &innertable drop column &idx_val;) by myAlias;;'; put '%end;'; put '/* create view to strip formats and avoid warns in log */'; put 'data work.vw_bitemp5d/view=work.vw_bitemp5d;'; put 'set work.bitemp5d_subquery;'; put 'format _all_;'; put 'run;'; put 'proc append base=&base_lib..&innertable ('; put '%do idx_pk=1 %to &redcnt;'; put '&&rednm&idx_pk = &&redval&idxpk'; put '%end;'; put ')'; put 'data=work.vw_bitemp5d force nowarn;'; put 'run;'; put '/* open up a connection for pass through SQL */'; put '%dc_assignlib(WRITE,&base_lib,passthru=myAlias)'; put 'execute('; put '%end;'; put '%else %do;'; put '%let innertable=bitemp5d_subquery;'; put '%let top_table=&base_lib..&base_dsn;'; put '%let flexinow=&now;'; put '%end;'; put '%if &loadtype=BITEMPORAL or &loadtype=TXTEMPORAL %then %do;'; put 'update &top_table set &tech_to=&flexinow'; put '%if %length(&processed)>0 %then %do;'; put ',&processed=&flexinow'; put '%end;'; put 'where &tech_from <= &flexinow and &flexinow < &tech_to and'; put '%end;'; put '%else %if &loadtype=UPDATE %then %do;'; put '/* changed records are deleted then re-appended when doing UPDATEs */'; put 'delete from &top_table where'; put '%end;'; put '%else %do;'; put '%put %str(ERR)OR: BUSTEMPORAL NOT YET SUPPORTED;'; put '%let syscc=5;'; put '%mp_lockanytable(UNLOCK,lib=&base_lib,ds=&base_dsn,ref=&ETLSOURCE,'; put 'ctl_ds=&dclib..mpe_lockanytable'; put ')'; put '%mp_lockanytable(UNLOCK'; put ',lib=%scan(&outds_audit,1,.)'; put ',ds=%scan(&outds_audit,2,.)'; put ',ref=&ETLSOURCE'; put ',ctl_ds=&dclib..mpe_lockanytable'; put ')'; put '%goto end_of_macro;'; put '%end;'; put '/* perform join inside query as per'; put 'http://stackoverflow.com/questions/24629793/update-with-a-proc-sql */'; put 'exists( select 1 from &baselib_schema.&innertable where'; put '/* loop PK join */'; put '%do idx_pk=1 %to &pk_cnt;'; put '%let idx_val=%scan(&pk,&idx_pk);'; put '&base_dsn..&idx_val=&innertable..&idx_val and'; put '%end;'; put '%if &loadtype=BITEMPORAL %then %do;'; put '&base_dsn..&bus_from >= &innertable..&bus_from'; put 'and &base_dsn..&bus_to <= &innertable..&bus_to and'; put '%end;'; put '/* close the statement */'; put '1=1);'; put '%if &engine_type=OLEDB or &engine_type=REDSHIFT or &engine_type=POSTGRES'; put '%then %do;'; put ') by myAlias;'; put 'execute (drop table &baselib_schema.&innertable) by myAlias;'; put '%end;'; put '%end;'; put 'quit;'; put 'data _null_;'; put 'putlog "&sysmacroname: Closeout complete";'; put 'run;'; put '/**'; put '* Append the new / updated records'; put '*/'; put '%if &engine_type=CAS %then %do;'; put '/* get varchar variables ready for casting */'; put '%local vcfmt vcrename vcassign vcdrop;'; put 'data _null_;'; put 'set work.bitemp_cols(where=(type=6)) end=last;'; put 'length vcrename vcassign vcdrop vcfmt $32767 rancol $32;'; put 'retain vcrename vcassign vcdrop vcfmt;'; put 'if _n_=1 then vcrename=''(rename=('';'; put 'rancol=resolve(''%mf_getuniquename()'');'; put 'vcfmt=trim(vcfmt)!!''length ''!!cats(name)!!'' varchar(*);'';'; put 'vcrename=trim(vcrename)!!'' ''!!cats(name,''='',rancol);'; put 'vcassign=cats(vcassign,name,''='',rancol,'';'');'; put 'vcdrop=cats(vcdrop,''drop ''!!rancol,'';'');'; put 'if last then do;'; put 'vcrename=cats(vcrename,''))'');'; put 'call symputx(''vcfmt'',vcfmt);'; put 'call symputx(''vcrename'',vcrename);'; put 'call symputx(''vcassign'',vcassign);'; put 'call symputx(''vcdrop'',vcdrop);'; put 'end;'; put 'run;'; put '/* prepare a temp cas table with varchars casted */'; put '%let tmp=%mf_getuniquename();'; put 'data casuser.&tmp ;'; put '&vcfmt'; put 'set work.bitemp6_unique &vcrename;'; put '&vcassign'; put '&vcdrop'; put 'run;'; put '/* load the table with varchars applied*/'; put 'data &base_lib..&base_dsn (append=yes )/sessref=dcsession ;'; put 'set casuser.&tmp;'; put 'run;'; put '/* drop temp table */'; put 'proc sql;'; put 'drop table CASUSER.&tmp;'; put '/* this code will not work as regular tables do not have varchars */'; put '/*'; put 'proc casutil;'; put 'load data=work.bitemp6_unique'; put 'outcaslib="&base_lib" casout="&base_dsn" append ;'; put 'quit;'; put '*/'; put '%end;'; put '%else %if &engine_type=REDSHIFT or &engine_type=POSTGRES %then %do;'; put 'proc append base=&base_lib..&base_dsn'; put '%if &engine_type=REDSHIFT %then %do;'; put '('; put '%do idx_pk=1 %to &redcnt;'; put '&&rednm&idx_pk = &&redval&idxpk'; put '%end;'; put ')'; put '%end;'; put 'data=bitemp6_unique force nowarn;'; put 'run;'; put '%end;'; put '%else %do;'; put 'proc append base=&base_lib..&base_dsn data=bitemp6_unique force nowarn; run;'; put '%end;'; put '%mp_lockanytable(UNLOCK,lib=&base_lib,ds=&base_dsn,ref=&ETLSOURCE,'; put 'ctl_ds=&dclib..mpe_lockanytable'; put ')'; put '/* final check on syscc */'; put '%mp_abort(iftrue= (&syscc >4)'; put ',mac=&_program'; put ',msg=%str(!!Upload NOT successful!! Failed on actual update / append stage..)'; put ')'; put '%if &outds_audit ne 0 and &LOADTARGET=YES %then %do;'; put 'data work.vw_outds_orig /view=work.vw_outds_orig;'; put 'set work.bitemp0_base (drop=&md5_col);'; put 'where ___TMP___NEW_FLG=0;'; put 'drop ___TMP___NEW_FLG;'; put 'run;'; put '/* update the AUDIT table */'; put '%if %mf_existds(&outds_audit) %then %do;'; put 'options mprint;'; put '%mp_storediffs(&base_lib..&base_dsn'; put ',work.vw_outds_orig'; put ',&pk &bus_from'; put ',delds=&outds_del'; put ',modds=&outds_mod'; put ',appds=&outds_add'; put ',outds=work.mp_storediffs'; put ',processed_dttm=&now'; put ',loadref=%superq(etlsource)'; put ')'; put '/* exclude unchanged values in modified rows */'; put 'data work.mp_storediffs;'; put 'set work.mp_storediffs;'; put 'if MOVE_TYPE="M" and IS_PK=0 and IS_DIFF=0 then delete;'; put '* putlog load_ref= libref= dsn= key_hash= tgtvar_nm=;'; put 'run;'; put 'proc append base=&outds_audit data=work.mp_storediffs;'; put 'run;'; put '%mp_lockanytable(UNLOCK'; put ',lib=%scan(&outds_audit,1,.)'; put ',ds=%scan(&outds_audit,2,.)'; put ',ref=&ETLSOURCE'; put ',ctl_ds=&dclib..mpe_lockanytable'; put ')'; put '%end;'; put '%end;'; put '%mp_abort(iftrue= (&syscc >4)'; put ',mac=bitemporal_dataloader'; put ',msg=%str(Problem in audit stage (&outds_audit))'; put ')'; put '%let user=%mf_getUser();'; put '/**'; put 'Notify as appropriate EMAILS DISABLED'; put '%sumo_alerts(ALERT_EVENT=UPDATE'; put ', ALERT_TARGET=&base_lib..&base_dsn'; put ', from_user= &user);'; put '*/'; put '/* monitor BiTemporal usage */'; put '%if &log=1 %then %do;'; put '%put syscc=&syscc;'; put '/* do not perform duration calc in pass through */'; put '%local dur;'; put 'data _null_;'; put 'now=symget(''now'');'; put 'dur=%sysfunc(datetime())-&now;'; put 'call symputx(''dur'',dur,''l'');'; put 'run;'; put 'proc sql;'; put 'insert into &dclib..mpe_dataloads'; put 'set libref=%upcase("&base_lib")'; put ',DSN=%upcase("&base_dsn")'; put ',ETLSOURCE="&ETLSOURCE"'; put ',LOADTYPE="&loadtype"'; put ',CHANGED_RECORDS=%mf_getattrn(&lastds,NLOBS)'; put ',NEW_RECORDS=%mf_getattrn(&outds_add,NLOBS)'; put ',DELETED_RECORDS=%mf_getattrn(&outds_del,NLOBS)'; put ',DURATION=&dur'; put ',MAC_VER="v&ver"'; put ',user_nm="&user"'; put ',PROCESSED_DTTM=&now;'; put 'quit;'; put '%put syscc=&syscc;'; put '%end;'; put '%end_of_macro:'; put '%mend bitemporal_dataloader;'; put '%macro mm_getlibs('; put 'outds=work.mm_getLibs'; put ')/*/STORE SOURCE*/;'; put '/*'; put 'flags:'; put 'OMI_SUCCINCT (2048) Do not return attributes with null values.'; put 'OMI_GET_METADATA (256) Executes a GetMetadata call for each object that'; put 'is returned by the GetMetadataObjects method.'; put 'OMI_ALL_SIMPLE (8) Gets all of the attributes of the requested object.'; put '*/'; put 'data _null_;'; put 'flags=2048+256+8;'; put 'call symputx(''flags'',flags,''l'');'; put 'run;'; put '* use a temporary fileref to hold the response;'; put 'filename response temp;'; put '/* get list of libraries */'; put 'proc metadata in='; put ''''; put '$METAREPOSITORY'; put 'SASLibrary'; put ''; put 'SAS'; put '&flags'; put ''; put ''''; put 'out=response;'; put 'run;'; put '/* write the response to the log for debugging */'; put 'data _null_;'; put 'infile response lrecl=32767;'; put 'input;'; put 'put _infile_;'; put 'run;'; put '/* create an XML map to read the response */'; put 'filename sxlemap temp;'; put 'data _null_;'; put 'file sxlemap;'; put 'put '''';'; put 'put '''';'; put 'put ''//Objects/SASLibrary'';'; put 'put ''>17'';'; put 'put ''//Objects/SASLibrary/@Id'';'; put 'put ''256>'';'; put 'put ''//Objects/SASLibrary/@Name'';'; put 'put ''8'';'; put 'put ''//Objects/SASLibrary/@Libref'';'; put 'put ''>12'';'; put 'put ''//Objects/SASLibrary/@Engine'';'; put 'put ''
'';'; put 'run;'; put 'libname _XML_ xml xmlfileref=response xmlmap=sxlemap;'; put '/* sort the response by library name */'; put 'proc sort data=_XML_.saslibrary out=&outds;'; put 'by libraryname;'; put 'run;'; put '/* clear references */'; put 'filename sxlemap clear;'; put 'filename response clear;'; put 'libname _XML_ clear;'; put '%mend mm_getlibs;'; put '%macro dc_getlibs(outds=mm_getlibs);'; put '/* take current repository */'; put '%local repo repocnt x;'; put '%let repo=%sysfunc(getoption(metarepository));'; put '/* get list of repositories and filter */'; put '%mm_getrepos(outds=repos)'; put '%let repocnt=0;'; put 'data repos;'; put 'set repos;'; put 'where repositorytype in(''CUSTOM'',''FOUNDATION'')'; put '& upcase(name) ne "%upcase(&repo)";'; put 'keep id name ;'; put 'call symputx(''repo''!!cats(_n_),name,''l'');'; put 'call symputx(''repocnt'',_n_,''l'');'; put 'run;'; put '%put _local_;'; put '%mm_getlibs(outds=&outds)'; put '%do x=1 %to &repocnt;'; put 'options metarepository=&&repo&x;'; put '%mm_getlibs(outds=&outds.a)'; put 'proc append base=&outds data=&outds.a;'; put 'run;'; put '%end;'; put 'options metarepository=&repo;'; put '%mend dc_getlibs;'; put '%macro mpe_refreshlibs(lib=0);'; put '%dc_getlibs(outds=work.mm_getLibs)'; put 'proc sort data=mm_getlibs;'; put 'by libraryref libraryname;'; put 'run;'; put 'data libs0;'; put 'set mm_getlibs;'; put 'by libraryref;'; put '%if &lib ne 0 %then %do;'; put 'where upcase(libraryref)="%upcase(&lib)";'; put '%end;'; put 'if "%mf_getplatform()"="SASMETA" then do;'; put '/* note - invalid libraries can result in exception errors. If this happens,'; put 'configure the dc_viewlib_check variable to NO in Data Controller Settings */'; put 'rc=libname(libraryref,,''meta'',cats(''library="'',libraryname,''";''));'; put 'drop rc;'; put 'if rc ne 0 then do;'; put 'putlog "NOTE: Library " libraryname " does not exist!!";'; put 'putlog (_all_) (=);'; put 'delete;'; put 'end;'; put 'end;'; put 'if not first.libraryref then delete;'; put 'run;'; put 'proc sql;'; put 'create table libs1 as'; put 'select distinct libname'; put ',engine'; put ',path'; put ',level'; put ',sysname'; put ',sysvalue'; put 'from dictionary.libnames'; put 'order by libname, level,engine,path;'; put 'data libs2;'; put 'set libs1;'; put 'length tran $1024;'; put 'if missing(sysname) then sysname=''Missing'';'; put 'select(sysname);'; put 'when(''Access Permission'') tran=''Permissions'';'; put 'when(''Owner Name'') tran=''Owner'';'; put 'when(''Schema/Owner'') tran=''schema'';'; put 'otherwise tran=sysname;'; put 'end;'; put 'run;'; put 'proc transpose data=libs2 out=libs3;'; put 'by libname level engine path;'; put 'var sysvalue;'; put 'id tran;'; put 'run;'; put 'data libs4(rename=(libname=libref));'; put 'length paths $8192 perms owners schemas $500 permissions owner schema $1024;'; put 'if _n_=1 then call missing (of _all_);'; put 'set libs3;'; put 'by libname;'; put 'if engine=''V9'' then engine=''BASE'';'; put 'if first.libname then do;'; put 'retain paths perms owners schemas;'; put 'paths=''(''!!quote(trim(path));'; put 'perms=permissions;'; put 'owners=owner;'; put 'schemas=schema;'; put 'end;'; put 'else do;'; put 'paths=trim(paths)!!'' ''!!quote(trim(path));'; put 'perms=trim(perms)!!'',''!!trim(permissions);'; put 'owners=trim(owners)!!'',''!!trim(owner);'; put 'schemas=trim(schemas)!!'' ''!!trim(schema);'; put 'end;'; put 'if last.libname then do;'; put 'paths=trim(paths)!!'')'';'; put 'schemas=cats(schemas);'; put 'output;'; put 'end;'; put 'keep libname engine paths perms owners schemas;'; put 'run;'; put 'proc sql;'; put 'create table libs5 as'; put 'select a.libref'; put ',coalescec(b.engine,a.engine) as engine length=32'; put ',b.libraryname as libname'; put ',a.paths'; put ',a.perms'; put ',a.owners'; put ',a.schemas'; put ',b.libraryid as libid'; put 'from libs4 a'; put 'left join libs0 b'; put 'on upcase(a.libref)=upcase(b.libraryref)'; put 'where libref not in (''SASWORK'',''WORK'',''SASUSER'',''CASUSER'',''TEMP'',''STPSAMP'''; put ',''MAPSGFK'');'; put '%bitemporal_dataloader(base_lib=&dc_libref'; put ',base_dsn=MPE_DATACATALOG_LIBS'; put ',append_dsn=libs5'; put ',PK=LIBREF'; put ',etlsource=&_program'; put ',loadtype=TXTEMPORAL'; put ',tech_from=TX_FROM'; put ',tech_to=TX_TO'; put ',dclib=&dc_libref'; put ')'; put '%mend mpe_refreshlibs;'; put '/** @cond */'; put '%macro mf_existfeature(feature'; put ')/*/STORE SOURCE*/;'; put '%let feature=%upcase(&feature);'; put '%local platform;'; put '%let platform=%mf_getplatform();'; put '%if &feature= %then %do;'; put '%put No feature was requested for detection;'; put '%end;'; put '%else %if &feature=COLCONSTRAINTS %then %do;'; put '%if "%substr(&sysver,1,1)"="4" or "%substr(&sysver,1,1)"="5" %then 0;'; put '%else 1;'; put '%end;'; put '%else %if &feature=PROCLUA %then %do;'; put '/* https://blogs.sas.com/content/sasdummy/2015/08/03/using-lua-within-your-sas-programs */'; put '%if &platform=SASVIYA %then 1;'; put '%else %if "&sysver"="9.2" or "&sysver"="9.3" %then 0;'; put '%else %if "&SYSVLONG" < "9.04.01M3" %then 0;'; put '%else 1;'; put '%end;'; put '%else %if &feature=DBMS_MEMTYPE %then %do;'; put '/* does dbms_memtype exist in dictionary.tables? */'; put '%if "%substr(&sysver,1,1)"="4" or "%substr(&sysver,1,1)"="5" %then 0;'; put '%else 1;'; put '%end;'; put '%else %if &feature=EXPORTXLS %then %do;'; put '/* is it possible to PROC EXPORT an excel file? */'; put '%if "%substr(&sysver,1,1)"="4" or "%substr(&sysver,1,1)"="5" %then 1;'; put '%else %if %sysfunc(sysprod(SAS/ACCESS Interface to PC Files)) = 1 %then 1;'; put '%else 0;'; put '%end;'; put '%else %do;'; put '-1'; put '%put &sysmacroname: &feature not found;'; put '%end;'; put '%mend mf_existfeature;'; put '/** @endcond */'; put '%macro mp_getconstraints(lib=WORK'; put ',ds='; put ',outds=mp_getconstraints'; put ',mdebug=0'; put ')/*/STORE SOURCE*/;'; put '%let lib=%upcase(&lib);'; put '%let ds=%upcase(&ds);'; put '/**'; put '* Cater for environments where sashelp.vcncolu is not available'; put '*/'; put '%if %sysfunc(exist(sashelp.vcncolu,view))=0 %then %do;'; put 'proc sql;'; put 'create table &outds('; put 'libref char(8)'; put ',TABLE_NAME char(32)'; put ',constraint_type char(8) label=''Constraint Type'''; put ',constraint_name char(32) label=''Constraint Name'''; put ',column_name char(32) label=''Column'''; put ',constraint_order num'; put ');'; put '%return;'; put '%end;'; put '/**'; put '* Neither dictionary tables nor sashelp provides a constraint order column,'; put '* however they DO arrive in the correct order. So, create the col.'; put '**/'; put '%local vw;'; put '%let vw=%mf_getuniquename(prefix=mp_getconstraints_vw_);'; put 'data &vw /view=&vw;'; put 'set sashelp.vcncolu;'; put 'where table_catalog="&lib";'; put '/* use retain approach to reset the constraint order with each constraint */'; put 'length tmp $1000;'; put 'retain tmp;'; put 'drop tmp;'; put 'if tmp ne catx(''|'',table_catalog,table_name,constraint_name) then do;'; put 'constraint_order=1;'; put 'end;'; put 'else constraint_order+1;'; put 'tmp=catx(''|'',table_catalog, table_name,constraint_name);'; put 'run;'; put '/* must use SQL as proc datasets does not support length changes */'; put 'proc sql noprint;'; put 'create table &outds as'; put 'select upcase(a.TABLE_CATALOG) as libref'; put ',upcase(a.TABLE_NAME) as TABLE_NAME'; put ',a.constraint_type'; put ',a.constraint_name'; put ',b.column_name'; put ',b.constraint_order'; put 'from dictionary.TABLE_CONSTRAINTS a'; put 'left join &vw b'; put 'on upcase(a.TABLE_CATALOG)=upcase(b.TABLE_CATALOG)'; put 'and upcase(a.TABLE_NAME)=upcase(b.TABLE_NAME)'; put 'and a.constraint_name=b.constraint_name'; put '/**'; put '* We cannot apply this clause to the underlying dictionary table. See:'; put '* https://communities.sas.com/t5/SAS-Programming/Unexpected-Where-Clause-behaviour-in-dictionary-TABLE/m-p/771554#M244867'; put '* cannot use`where calculated libref="&lib"` either as it will STILL execute'; put '* all the underlying constraint queries, causing exception errors in some'; put '* cases: https://github.com/sasjs/core/issues/283'; put '*/'; put 'where a.TABLE_CATALOG="&lib"'; put '%if "&ds" ne "" %then %do;'; put 'and upcase(a.TABLE_NAME)="&ds"'; put 'and upcase(b.TABLE_NAME)="&ds"'; put '%end;'; put 'order by libref, table_name, constraint_name, constraint_order'; put ';'; put '/* tidy up */'; put '%mp_dropmembers('; put '&vw,'; put 'iftrue=(&mdebug=0)'; put ')'; put '%mend mp_getconstraints;'; put '%macro mpe_refreshtables(lib,ds=#all);'; put '%let lib=%upcase(&lib);'; put '%let ds=%upcase(&ds);'; put '%local engine; %let engine=%mf_getengine(&lib);'; put '%local schema; %let schema=%mf_getschema(&lib);'; put '%put running &sysmacroname &lib(&engine &schema) for &ds;'; put 'proc sql;'; put 'create table cols as'; put 'select libname as libref'; put ',upcase(memname) as dsn'; put ',memtype'; put ',upcase(name) as name'; put ',type'; put ',length'; put ',varnum'; put ',label'; put ',format'; put ',idxusage'; put ',notnull'; put 'from dictionary.columns'; put 'where upcase(libname)="&lib"'; put '%if &ds ne #ALL %then %do;'; put 'and upcase(memname)="&ds"'; put '%end;'; put ';'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc afer &lib cols extraction)'; put ')'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc afer &lib indexes extraction)'; put ')'; put '%if &engine=SQLSVR %then %do;'; put 'proc sql;'; put 'connect using &lib;'; put 'create table work.indexes as'; put 'select * from connection to &lib('; put 'select'; put 's.name as SchemaName,'; put 't.name as memname,'; put 'tc.name as name,'; put 'ic.key_ordinal as KeyOrderNr'; put 'from'; put 'sys.schemas s'; put 'inner join sys.tables t on s.schema_id=t.schema_id'; put 'inner join sys.indexes i on t.object_id=i.object_id'; put 'inner join sys.index_columns ic on i.object_id=ic.object_id'; put 'and i.index_id=ic.index_id'; put 'inner join sys.columns tc on ic.object_id=tc.object_id'; put 'and ic.column_id=tc.column_id'; put 'where i.is_primary_key=1'; put 'and s.name=%str(%'')&schema%str(%'')'; put 'order by t.name, ic.key_ordinal ;'; put ');disconnect from &lib;'; put 'create table finalcols as'; put 'select a.*'; put ',case when b.name is not null then 1 else 0 end as pk_ind'; put 'from work.cols a'; put 'left join work.indexes b'; put 'on a.dsn=b.memname'; put 'and upcase(a.name)=upcase(b.name)'; put 'order by libref,dsn;'; put '%end;'; put '%else %do;'; put '%local dsn;'; put '%if &ds = #ALL %then %let dsn=;'; put '%mp_getconstraints(lib=&lib.,ds=&dsn,outds=work.constraints)'; put '/* extract cols that are clearly primary keys */'; put 'proc sql;'; put 'create table work.pk4sure as'; put 'select libref'; put ',table_name'; put ',constraint_name'; put ',constraint_order'; put ',column_name as name'; put 'from work.constraints'; put 'where constraint_type=''PRIMARY'''; put 'order by 1,2,3,4;'; put '/* extract unique constraints where every col is also NOT NULL */'; put 'proc sql;'; put 'create table work.sum as'; put 'select a.libref'; put ',a.table_name'; put ',a.constraint_name'; put ',count(a.column_name) as unq_cnt'; put ',count(b.column_name) as nul_cnt'; put 'from work.constraints(where=(constraint_type =''UNIQUE'')) a'; put 'left join work.constraints(where=(constraint_type =''NOT NULL'')) b'; put 'on a.libref=b.libref'; put 'and a.table_name=b.table_name'; put 'and a.column_name=b.column_name'; put 'group by 1,2,3'; put 'having unq_cnt=nul_cnt;'; put '/* extract cols from the relevant unique constraints */'; put 'create table work.pkdefault as'; put 'select a.libref'; put ',a.table_name'; put ',a.constraint_name'; put ',b.constraint_order'; put ',b.column_name as name'; put 'from work.sum a'; put 'left join work.constraints(where=(constraint_type =''UNIQUE'')) b'; put 'on a.libref=b.libref'; put 'and a.table_name=b.table_name'; put 'and a.constraint_name=b.constraint_name'; put 'order by 1,2,3,4;'; put '/* extract cols from the relevant unique INDEXES */'; put 'create table work.pkfromindex as'; put 'select libname as libref'; put ',memname as table_name'; put ',indxname as constraint_name'; put ',indxpos as constraint_order'; put ',name'; put 'from dictionary.indexes'; put 'where nomiss=''yes'' and unique=''yes'' and upcase(libname)="&lib"'; put '%if &ds ne #ALL %then %do;'; put 'and upcase(memname)="&ds"'; put '%end;'; put 'order by 1,2,3,4;'; put '/* create one table */'; put 'data work.finalpks;'; put 'set pkdefault pk4sure pkfromindex;'; put 'pk_ind=1;'; put '/* if there are multiple unique constraints, take the first */'; put 'by libref table_name constraint_name;'; put 'retain keepme;'; put 'if first.table_name then keepme=1;'; put 'if first.constraint_name and not first.table_name then keepme=0;'; put 'if keepme=1;'; put 'run;'; put '/* join back to starting table */'; put 'proc sql;'; put 'create table finalcols as'; put 'select a.*'; put ',b.constraint_order'; put ',case when b.pk_ind=1 then 1 else 0 end as pk_ind'; put 'from work.cols a'; put 'left join work.finalpks b'; put 'on a.libref=b.libref'; put 'and a.dsn=b.table_name'; put 'and upcase(a.name)=upcase(b.name)'; put 'order by libref,dsn,constraint_order;'; put '%end;'; put '/* load columns */'; put '%bitemporal_dataloader(base_lib=&mpelib'; put ',base_dsn=mpe_datacatalog_vars'; put ',append_dsn=finalcols'; put ',PK=LIBREF DSN NAME'; put ',etlsource=&sysmacroname'; put ',loadtype=TXTEMPORAL'; put ',tech_from=TX_FROM'; put ',tech_to=TX_TO'; put '%if &ds ne #ALL %then %do;'; put ',close_vars=LIBREF DSN'; put '%end;'; put ',dclib=&mpelib'; put ')'; put '/* prepare tables */'; put 'proc sql;'; put 'create table work.tabs as select'; put 'libname as libref'; put ',upcase(memname) as dsn'; put ',memtype'; put '%if %mf_existfeature(DBMS_MEMTYPE)=1 %then %do;'; put ',dbms_memtype'; put '%end;'; put '%else %do;'; put ',''n/a'' as dbms_memtype format=$32.'; put '%end;'; put ',typemem'; put ',memlabel'; put ',nvar'; put ',compress'; put 'from dictionary.tables'; put 'where upcase(libname)="&lib"'; put '%if &ds ne #ALL %then %do;'; put 'and upcase(memname)="&ds"'; put '%end;'; put ';'; put 'data tabs2;'; put 'set finalcols;'; put 'length pk_fields $512;'; put 'retain pk_fields;'; put 'by libref dsn;'; put 'if first.dsn then pk_fields='''';'; put 'if pk_ind=1 then pk_fields=catx('' '',pk_fields,name);'; put 'if last.dsn then output;'; put 'run;'; put 'proc sql;'; put 'create table work.finaltabs as'; put 'select a.libref'; put ',a.dsn'; put ',a.memtype'; put ',a.dbms_memtype'; put ',a.typemem'; put ',a.memlabel'; put ',a.nvar'; put ',a.compress'; put ',b.pk_fields'; put 'from work.tabs a'; put 'left join work.tabs2 b'; put 'on a.libref=b.libref'; put 'and a.dsn=b.dsn;'; put '%bitemporal_dataloader(base_lib=&mpelib'; put ',base_dsn=mpe_datacatalog_tabs'; put ',append_dsn=finaltabs'; put ',PK=LIBREF DSN'; put ',etlsource=&sysmacroname'; put ',loadtype=TXTEMPORAL'; put ',tech_from=TX_FROM'; put ',tech_to=TX_TO'; put ',dclib=&mpelib'; put '%if &ds ne #ALL %then %do;'; put ',close_vars=LIBREF'; put '%end;'; put ')'; put '/* prepare table frequently changing attributes */'; put 'proc sql;'; put '%if &engine=SQLSVR %then %do;'; put 'connect using &lib;'; put 'create table work.attrs as select * from connection to &lib('; put 'SELECT SCHEMA_NAME(schema_id) as ''schema'', name, create_date, modify_date'; put 'FROM sys.tables ;'; put ');'; put 'create table work.nobs as select * from connection to &lib('; put 'SELECT SCHEMA_NAME(A.schema_id) AS ''schema'''; put ',A.Name, AVG(B.rows) AS ''RowCount'''; put 'FROM sys.objects A'; put 'INNER JOIN sys.partitions B ON A.object_id = B.object_id'; put 'WHERE A.type = ''U'''; put 'GROUP BY A.schema_id, A.Name'; put ');'; put 'disconnect from &lib;'; put 'create table statustabs as select'; put 'a.libref'; put ',a.dsn'; put ',b.create_date as crdate'; put ',b.modify_date as modate'; put ',. as filesize'; put ',c.RowCount as nobs'; put 'from work.tabs a'; put 'left join work.attrs(where=(schema="&schema")) b'; put 'on upcase(a.dsn)=upcase(b.name)'; put 'left join work.nobs(where=(schema="&schema")) c'; put 'on upcase(a.dsn)=upcase(c.name);'; put '%end;'; put '%else %do;'; put 'create table statustabs as select'; put 'libname as libref'; put ',upcase(memname) as dsn'; put ',crdate'; put ',modate'; put ',filesize'; put ',nobs'; put 'from dictionary.tables'; put 'where upcase(libname)="&lib"'; put '%if &ds ne #ALL %then %do;'; put 'and upcase(memname)="&ds"'; put '%end;'; put ';'; put '%end;'; put '%bitemporal_dataloader(base_lib=&mpelib'; put ',base_dsn=mpe_datastatus_tabs'; put ',append_dsn=statustabs'; put ',PK=LIBREF DSN'; put ',etlsource=&sysmacroname'; put ',loadtype=TXTEMPORAL'; put ',tech_from=TX_FROM'; put ',tech_to=TX_TO'; put ',dclib=&mpelib'; put '%if &ds ne #ALL %then %do;'; put ',close_vars=LIBREF'; put '%end;'; put ')'; put '%if &ds = #ALL %then %do;'; put 'proc sql;'; put 'create table statuslibs as select'; put 'libref'; put ',sum(filesize) as libsize'; put ',count(*) as table_cnt'; put 'from statustabs'; put 'group by 1;'; put '%bitemporal_dataloader(base_lib=&mpelib'; put ',base_dsn=mpe_datastatus_libs'; put ',append_dsn=statuslibs'; put ',PK=LIBREF'; put ',etlsource=&sysmacroname'; put ',loadtype=TXTEMPORAL'; put ',tech_from=TX_FROM'; put ',tech_to=TX_TO'; put ',dclib=&mpelib'; put ')'; put '%end;'; put '%mend mpe_refreshtables;'; put '%macro dc_refreshcatalog(libref);'; put '/* take current repository */'; put '%local repo repocnt xx;'; put '%let repo=%sysfunc(getoption(metarepository));'; put '/* get list of repositories and filter */'; put '%mm_getrepos(outds=repos)'; put '%let repocnt=0;'; put 'data repos;'; put 'set repos;'; put 'put (_all_)(=);'; put 'where repositorytype in(''CUSTOM'',''FOUNDATION'');'; put 'keep id name ;'; put 'call symputx(cats(''repo'',_n_),name,''l'');'; put 'call symputx(''repocnt'',_n_,''l'');'; put 'run;'; put '%put &sysmacroname #&libref#;'; put '%if #&libref# ne ## %then %do;'; put '%put &sysmacroname: assigning specific libref, &libref;'; put '%dc_assignlib(WRITE,&libref) /* write just in order to assign direct lib */'; put '%mpe_refreshlibs(lib=&libref)'; put '%mpe_refreshtables(&libref)'; put '%end;'; put '%else %do xx=1 %to &repocnt;'; put 'options metarepository=&&repo&xx;'; put '%mpe_refreshlibs()'; put '/* get libs to be ignored */'; put '%local ignorelist;'; put 'proc sql noprint;'; put 'select var_value into: ignorelist'; put 'from &mpelib..MPE_CONFIG'; put 'where var_scope=''DC_CATALOG'''; put 'and var_name="DC_IGNORELIBS"'; put 'and &dc_dttmtfmt. < TX_TO'; put 'and var_active=1;'; put '/* get all libs */'; put '%let libcnt=0;'; put 'data libraries;'; put 'set &mpelib..mpe_datacatalog_libs;'; put 'where &dc_dttmtfmt. le TX_TO;'; put 'if index("&ignorelist",''|''!!upcase(trim(libref))!!''|'')=0;'; put 'i+1;'; put 'call symputx(cats(''lib'',i),libref);'; put 'call symputx(''libcnt'',i);'; put 'run;'; put '%local i;'; put '%do i=1 %to &libcnt;'; put '%dc_assignlib(WRITE,&&lib&i)'; put '%mpe_refreshtables(&&lib&i)'; put '%end;'; put '%end;'; put 'options metarepository=&repo;'; put '%mend dc_refreshcatalog;'; put '* SAS Macros end;'; put '* SAS Includes start;'; put '* SAS Includes end;'; put '* Binary Files start;'; put '* Binary Files end;'; put '* ServiceInit start;'; put 'options noquotelenmax ps=max;'; put '/* create dummy macros to prevent stpbegin / stpend from corrupting sessions */'; put '%macro stpbegin();'; put '%put NOTE: the STPBEGIN macro should not be used for web apps!;'; put '%mend stpbegin;'; put '%macro stpend();'; put '%put NOTE: the STPEND macro should not be used for web apps!;'; put '%mend stpend;'; put '* ServiceInit end;'; put '* Service start;'; put '/**'; put '@file refreshlibinfo.sas'; put '@brief Refresh the Data Catalog for a particular library'; put '@details When showing library info in the VIEW menu, the data is taken from'; put 'the Data Catalog tables. These may be empty or outdated, and so this service'; put 'allows end users to run a refresh of the data.'; put '

Service Inputs

'; put '
lib2refresh
'; put 'Should contain the libref to be refreshed.'; put '|libref:$8.|'; put '|---|'; put '|SOMELIB|'; put '

Service Outputs

'; put '
libinfo
'; put '|engine $|libname $|paths $|perms $|owners $|schemas $ |libid $|libsize $|table_cnt |'; put '|---|---|---|---|---|---|---|---|---|'; put '|V9|SOMELIB|"some/path"|rwxrwxr-x|sassrv|` `|` `|636MB|33|'; put '

SAS Macros

'; put '@li dc_assignlib.sas'; put '@li dc_refreshcatalog.sas'; put '@li mp_abort.sas'; put '@version 9.3'; put '@author 4GL Apps Ltd'; put '@copyright 4GL Apps Ltd. This code may only be used within Data Controller'; put 'and may not be re-distributed or re-sold without the express permission of'; put '4GL Apps Ltd.'; put '**/'; put '%mpeinit()'; put '%webout(FETCH)'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',msg=%str(syscc=&syscc Problem on startup)'; put ')'; put '%let libref=;'; put 'data _null_;'; put 'set work.lib2refresh;'; put 'call symputx(''libref'',libref);'; put 'run;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',msg=%str(syscc=&syscc Problem with inputs - was lib2refresh object sent?)'; put ')'; put '%dc_assignlib(WRITE,&libref)'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',msg=%str(syscc=&syscc after lib assignment)'; put ')'; put '%dc_refreshcatalog(&libref)'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',msg=%str(syscc=&syscc Problem when running the catalog refresh)'; put ')'; put '/* get libinfo */'; put 'proc sql;'; put 'create table work.libinfo as'; put 'select a.engine,'; put 'a.libname,'; put 'a.paths,'; put 'a.perms,'; put 'a.owners,'; put 'a.schemas,'; put 'a.libid,'; put 'b.libsize,'; put 'b.table_cnt'; put 'from &mpelib..mpe_datacatalog_libs(where=(&dc_dttmtfmt. lt tx_to)) a'; put 'inner join &mpelib..mpe_datastatus_libs(where=(&dc_dttmtfmt. lt tx_to)) b'; put 'on a.libref=b.libref'; put 'where a.libref="&libref";'; put '%webout(OPEN)'; put '%webout(OBJ,libinfo)'; put '%webout(CLOSE)'; put '* Service end;'; run; %mm_createwebservice(path=&appLoc/&path, name=&service, code=sascode, server=&serverName, replace=yes) filename sascode clear; %let service=registeruser; filename sascode temp lrecl=32767; data _null_; file sascode; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '/**'; put '@file mp_jsonout.sas'; put '@brief Writes JSON in SASjs format to a fileref'; put '@details This macro can be used to OPEN a JSON stream and send one or more'; put 'tables as arrays of rows, where each row can be an object or a nested array.'; put 'There are two engines available - DATASTEP or PROCJSON.'; put 'PROC JSON is fast but will produce errs like the ones below if'; put 'special chars are encountered.'; put '> (ERR)OR: Some code points did not transcode.'; put '> An object or array close is not valid at this point in the JSON text.'; put '> Date value out of range'; put 'If this happens, try running with ENGINE=DATASTEP.'; put 'The DATASTEP engine is used to handle special SAS missing numerics, and'; put 'can also convert entire datasets to formatted values. Output JSON is always'; put 'in UTF-8.'; put 'Usage:'; put 'filename tmp temp;'; put 'data class; set sashelp.class;run;'; put '%mp_jsonout(OPEN,jref=tmp)'; put '%mp_jsonout(OBJ,class,jref=tmp)'; put '%mp_jsonout(OBJ,class,dslabel=class2,jref=tmp,showmeta=Y)'; put '%mp_jsonout(CLOSE,jref=tmp)'; put 'data _null_;'; put 'infile tmp;'; put 'input;putlog _infile_;'; put 'run;'; put 'If you are building web apps with SAS then you are strongly encouraged to use'; put 'the mX_createwebservice macros in combination with the'; put '[sasjs adapter](https://github.com/sasjs/adapter).'; put 'For more information see https://sasjs.io'; put '@param [in] action Valid values:'; put '@li OPEN - opens the JSON'; put '@li OBJ - sends a table with each row as an object'; put '@li ARR - sends a table with each row in an array'; put '@li CLOSE - closes the JSON'; put '@param [in] ds The dataset to send. Must be a work table.'; put '@param [out] jref= (_webout) The fileref to which to send the JSON'; put '@param [out] dslabel= The name to give the table in the exported JSON'; put '@param [in] fmt= (Y) Whether to keep (Y) or strip (N) formats from the table'; put '@param [in] engine= (DATASTEP) Which engine to use to send the JSON. Options:'; put '@li PROCJSON (default)'; put '@li DATASTEP (more reliable when data has non standard characters)'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to JSON'; put '

Related Files

'; put '@li mp_ds2fmtds.sas'; put '@version 9.2'; put '@author Allan Bowe'; put '@source https://github.com/sasjs/core'; put '**/'; put '%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y'; put ',engine=DATASTEP'; put ',missing=NULL'; put ',showmeta=N'; put ',maxobs=MAX'; put ')/*/STORE SOURCE*/;'; put '%local tempds colinfo fmtds i numcols numobs stmt_obs lastobs optval'; put 'tmpds1 tmpds2 tmpds3 tmpds4;'; put '%let numcols=0;'; put '%if &maxobs ne MAX %then %let stmt_obs=%str(if _n_>&maxobs then stop;);'; put '%if &action=OPEN %then %do;'; put 'options nobomfile;'; put 'data _null_;file &jref encoding=''utf-8'' lrecl=200;'; put 'put ''{"PROCESSED_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''"'';'; put 'run;'; put '%end;'; put '%else %if (&action=ARR or &action=OBJ) %then %do;'; put '/* force variable names to always be uppercase in the JSON */'; put 'options validvarname=upcase;'; put '/* To avoid issues with _webout on EBI - such as encoding diffs and truncation'; put '(https://support.sas.com/kb/49/325.html) we use temporary files */'; put 'filename _sjs1 temp lrecl=200 ;'; put 'data _null_; file _sjs1 encoding=''utf-8'';'; put 'put ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":";'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjs1 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs1 clear;'; put '/* grab col defs */'; put 'proc contents noprint data=&ds'; put 'out=_data_(keep=name type length format formatl formatd varnum label);'; put 'run;'; put '%let colinfo=%scan(&syslast,2,.);'; put 'proc sort data=&colinfo;'; put 'by varnum;'; put 'run;'; put '/* move meta to mac vars */'; put 'data &colinfo;'; put 'if _n_=1 then call symputx(''numcols'',nobs,''l'');'; put 'set &colinfo end=last nobs=nobs;'; put 'name=upcase(name);'; put '/* fix formats */'; put 'if type=2 or type=6 then do;'; put 'typelong=''char'';'; put 'length fmt $49.;'; put 'if format='''' then fmt=cats(''$'',length,''.'');'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else fmt=cats(format,formatl,''.'');'; put 'end;'; put 'else do;'; put 'typelong=''num'';'; put 'if format='''' then fmt=''best.'';'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else if formatd=0 then fmt=cats(format,formatl,''.'');'; put 'else fmt=cats(format,formatl,''.'',formatd);'; put 'end;'; put '/* 32 char unique name */'; put 'newname=''sasjs''!!substr(cats(put(md5(name),$hex32.)),1,27);'; put 'call symputx(cats(''name'',_n_),name,''l'');'; put 'call symputx(cats(''newname'',_n_),newname,''l'');'; put 'call symputx(cats(''length'',_n_),length,''l'');'; put 'call symputx(cats(''fmt'',_n_),fmt,''l'');'; put 'call symputx(cats(''type'',_n_),type,''l'');'; put 'call symputx(cats(''typelong'',_n_),typelong,''l'');'; put 'call symputx(cats(''label'',_n_),coalescec(label,name),''l'');'; put '/* overwritten when fmt=Y and a custom format exists in catalog */'; put 'if typelong=''num'' then call symputx(cats(''fmtlen'',_n_),200,''l'');'; put 'else call symputx(cats(''fmtlen'',_n_),min(32767,ceil((length+10)*1.5)),''l'');'; put 'run;'; put '%let tempds=%substr(_%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql;'; put 'select count(*) into: lastobs from &ds;'; put '%if &maxobs ne MAX %then %let lastobs=%sysfunc(min(&lastobs,&maxobs));'; put '%if &engine=PROCJSON %then %do;'; put '%if &missing=STRING %then %do;'; put '%put &sysmacroname: Special Missings not supported in proc json.;'; put '%put &sysmacroname: Switching to DATASTEP engine;'; put '%goto datastep;'; put '%end;'; put 'data &tempds;'; put 'set &ds;'; put '&stmt_obs;'; put '%if &fmt=N %then format _numeric_ best32.;;'; put '/* PRETTY is necessary to avoid line truncation in large files */'; put 'filename _sjs2 temp lrecl=131068 encoding=''utf-8'';'; put 'proc json out=_sjs2 pretty'; put '%if &action=ARR %then nokeys ;'; put ';export &tempds / nosastags fmtnumeric;'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs2 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs2 clear;'; put '%end;'; put '%else %if &engine=DATASTEP %then %do;'; put '%datastep:'; put '%if %sysfunc(exist(&ds)) ne 1 & %sysfunc(exist(&ds,VIEW)) ne 1'; put '%then %do;'; put '%put &sysmacroname: &ds NOT FOUND!!!;'; put '%return;'; put '%end;'; put '%if &fmt=Y %then %do;'; put '/**'; put '* Extract format definitions'; put '* First, by getting library locations from dictionary.formats'; put '* Then, by exporting the width using proc format'; put '* Cannot use maxw from sashelp.vformat as not always populated'; put '* Cannot use fmtinfo() as not supported in all flavours'; put '*/'; put '%let tmpds1=%substr(fmtsum%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds2=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds3=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds4=%substr(col%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql noprint;'; put 'create table &tmpds1 as'; put 'select cats(libname,''.'',memname) as FMTCAT,'; put 'FMTNAME'; put 'from dictionary.formats'; put 'where fmttype=''F'' and libname is not null'; put 'and fmtname in (select format from &colinfo where format is not null)'; put 'order by 1;'; put 'create table &tmpds2('; put 'FMTNAME char(32),'; put 'LENGTH num'; put ');'; put '%local catlist cat fmtlist i;'; put 'select distinct fmtcat into: catlist separated by '' '' from &tmpds1;'; put '%do i=1 %to %sysfunc(countw(&catlist,%str( )));'; put '%let cat=%scan(&catlist,&i,%str( ));'; put 'proc sql;'; put 'select distinct fmtname into: fmtlist separated by '' '''; put 'from &tmpds1 where fmtcat="&cat";'; put 'proc format lib=&cat cntlout=&tmpds3(keep=fmtname length);'; put 'select &fmtlist;'; put 'run;'; put 'proc sql;'; put 'insert into &tmpds2 select distinct fmtname,length from &tmpds3;'; put '%end;'; put 'proc sql;'; put 'create table &tmpds4 as'; put 'select a.*, b.length as MAXW'; put 'from &colinfo a'; put 'left join &tmpds2 b'; put 'on cats(a.format)=cats(upcase(b.fmtname))'; put 'order by a.varnum;'; put 'data _null_;'; put 'set &tmpds4;'; put 'if not missing(maxw);'; put 'call symputx('; put 'cats(''fmtlen'',_n_),'; put '/* vars need extra padding due to JSON escaping of special chars */'; put 'min(32767,ceil((max(length,maxw)+10)*1.5))'; put ',''l'''; put ');'; put 'run;'; put '/* configure varlenchk - as we are explicitly shortening the variables */'; put '%let optval=%sysfunc(getoption(varlenchk));'; put 'options varlenchk=NOWARN;'; put 'data _data_(compress=char);'; put '/* shorten the new vars */'; put 'length'; put '%do i=1 %to &numcols;'; put '&&name&i $&&fmtlen&i'; put '%end;'; put ';'; put '/* rename on entry */'; put 'set &ds(rename=('; put '%do i=1 %to &numcols;'; put '&&name&i=&&newname&i'; put '%end;'; put '));'; put '&stmt_obs;'; put 'drop'; put '%do i=1 %to &numcols;'; put '&&newname&i'; put '%end;'; put ';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=num %then %do;'; put '&&name&i=cats(put(&&newname&i,&&fmt&i));'; put '%end;'; put '%else %do;'; put '&&name&i=put(&&newname&i,&&fmt&i);'; put '%end;'; put '%end;'; put 'if _error_ then do;'; put 'call symputx(''syscc'',1012);'; put 'stop;'; put 'end;'; put 'run;'; put '%let fmtds=&syslast;'; put 'options varlenchk=&optval;'; put '%end;'; put 'proc format; /* credit yabwon for special null removal */'; put 'value bart (default=40)'; put '%if &missing=NULL %then %do;'; put '._ - .z = null'; put '%end;'; put '%else %do;'; put '._ = [quote()]'; put '. = null'; put '.a - .z = [quote()]'; put '%end;'; put 'other = [best.];'; put 'data &tempds;'; put 'attrib _all_ label='''';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'length &&name&i $&&fmtlen&i...;'; put 'format &&name&i $&&fmtlen&i...;'; put '%end;'; put '%end;'; put '%if &fmt=Y %then %do;'; put 'set &fmtds;'; put '%end;'; put '%else %do;'; put 'set &ds;'; put '%end;'; put '&stmt_obs;'; put 'format _numeric_ bart.;'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'if findc(&&name&i,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put '&&name&i=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,&&name&i)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else &&name&i=quote(cats(&&name&i));'; put '%end;'; put '%end;'; put 'run;'; put 'filename _sjs3 temp lrecl=131068 ;'; put 'data _null_;'; put 'file _sjs3 encoding=''utf-8'';'; put 'if _n_=1 then put "[";'; put 'set &tempds;'; put 'if _n_>1 then put "," @; put'; put '%if &action=ARR %then "[" ; %else "{" ;'; put '%do i=1 %to &numcols;'; put '%if &i>1 %then "," ;'; put '%if &action=OBJ %then """&&name&i"":" ;'; put '"&&name&i"n /* name literal for reserved variable names */'; put '%end;'; put '%if &action=ARR %then "]" ; %else "}" ; ;'; put '/* close out the table */'; put 'data _null_;'; put 'file _sjs3 mod encoding=''utf-8'';'; put 'put '']'';'; put 'run;'; put 'data _null_;'; put 'infile _sjs3 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs3 clear;'; put '%end;'; put 'proc sql;'; put 'drop table &colinfo, &tempds;'; put '%if %substr(&showmeta,1,1)=Y %then %do;'; put 'filename _sjs4 temp lrecl=131068 encoding=''utf-8'';'; put 'data _null_;'; put 'file _sjs4;'; put 'length label $350;'; put 'put ", ""$%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":{""vars"":{";'; put 'do i=1 to &numcols;'; put 'name=quote(trim(symget(cats(''name'',i))));'; put 'format=quote(trim(symget(cats(''fmt'',i))));'; put 'label=quote(prxchange(''s/\\/\\\\/'',-1,trim(symget(cats(''label'',i)))));'; put 'length=quote(trim(symget(cats(''length'',i))));'; put 'type=quote(trim(symget(cats(''typelong'',i))));'; put 'if i>1 then put "," @@;'; put 'put name '':{"format":'' format '',"label":'' label'; put ''',"length":'' length '',"type":'' type ''}'';'; put 'end;'; put 'put ''}}'';'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs4 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs4 clear;'; put '%end;'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put 'data _null_; file &jref encoding=''utf-8'' mod ;'; put 'put "}";'; put 'run;'; put '%end;'; put '%mend mp_jsonout;'; put '/**'; put '@file mm_webout.sas'; put '@brief Send data to/from SAS Stored Processes'; put '@details This macro should be added to the start of each Stored Process,'; put '**immediately** followed by a call to:'; put '%mm_webout(FETCH)'; put 'This will read all the input data and create same-named SAS datasets in the'; put 'WORK library. You can then insert your code, and send data back using the'; put 'following syntax:'; put 'data some datasets; * make some data ;'; put 'retain some columns;'; put 'run;'; put '%mm_webout(OPEN)'; put '%mm_webout(ARR,some) * Array format, fast, suitable for large tables ;'; put '%mm_webout(OBJ,datasets) * Object format, easier to work with ;'; put 'Finally, wrap everything up send some helpful system variables too'; put '%mm_webout(CLOSE)'; put '@param [in] action Either FETCH, OPEN, ARR, OBJ or CLOSE'; put '@param [in] ds The dataset to send back to the frontend'; put '@param [out] dslabel= Value to use instead of table name for sending to JSON'; put '@param [in] fmt= (N) Setting Y converts all vars to their formatted values'; put '@param [out] fref= (_webout) The fileref to which to write the JSON'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] workobs= (0) When set to a positive integer, will create a new'; put 'output object (WORK) which contains this number of observations from all'; put 'tables in the WORK library.'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to output JSON'; put '

SAS Macros

'; put '@li mp_jsonout.sas'; put '

Related Macros

'; put '@li ms_webout.sas'; put '@li mv_webout.sas'; put '@version 9.3'; put '@author Allan Bowe'; put '**/'; put '%macro mm_webout(action,ds,dslabel=,fref=_webout,fmt=N,missing=NULL'; put ',showmeta=N,maxobs=MAX,workobs=0'; put ');'; put '%global _webin_file_count _webin_fileref1 _webin_name1 _program _debug'; put 'sasjs_tables;'; put '%local i tempds jsonengine;'; put '/* see https://github.com/sasjs/core/issues/41 */'; put '%if "%upcase(&SYSENCODING)" ne "UTF-8" %then %let jsonengine=PROCJSON;'; put '%else %let jsonengine=DATASTEP;'; put '%if &action=FETCH %then %do;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'options mprint notes mprintnest;'; put '%end;'; put '%let _webin_file_count=%eval(&_webin_file_count+0);'; put '/* now read in the data */'; put '%do i=1 %to &_webin_file_count;'; put '%if &_webin_file_count=1 %then %do;'; put '%let _webin_fileref1=&_webin_fileref;'; put '%let _webin_name1=&_webin_name;'; put '%end;'; put 'data _null_;'; put 'infile &&_webin_fileref&i termstr=crlf;'; put 'input;'; put 'call symputx(''input_statement'',_infile_);'; put 'putlog "&&_webin_name&i input statement: " _infile_;'; put 'stop;'; put 'data &&_webin_name&i;'; put 'infile &&_webin_fileref&i firstobs=2 dsd termstr=crlf encoding=''utf-8'';'; put 'input &input_statement;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'if _n_<20 then putlog _infile_;'; put '%end;'; put 'run;'; put '%let sasjs_tables=&sasjs_tables &&_webin_name&i;'; put '%end;'; put '%end;'; put '%else %if &action=OPEN %then %do;'; put '/* fix encoding */'; put 'OPTIONS NOBOMFILE;'; put '/**'; put '* check xengine type to avoid the below err message:'; put '* > Function is only valid for filerefs using the CACHE access method.'; put '*/'; put 'data _null_;'; put 'set sashelp.vextfl(where=(fileref="_WEBOUT"));'; put 'if xengine=''STREAM'' then do;'; put 'rc=stpsrv_header(''Content-type'',"text/html; encoding=utf-8");'; put 'end;'; put 'run;'; put '/* setup json */'; put 'data _null_;file &fref encoding=''utf-8'';'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutBEGIN<<'';'; put '%end;'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'run;'; put '%end;'; put '%else %if &action=ARR or &action=OBJ %then %do;'; put '%mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt,jref=&fref'; put ',engine=&jsonengine,missing=&missing,showmeta=&showmeta,maxobs=&maxobs'; put ')'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put '/* To avoid issues with _webout on EBI we use a temporary file */'; put 'filename _sjsref temp lrecl=131068;'; put '%if %str(&workobs) > 0 %then %do;'; put '/* if debug mode, send back first XX records of each work table also */'; put 'data;run;%let tempds=%scan(&syslast,2,.);'; put 'ods output Members=&tempds;'; put 'proc datasets library=WORK memtype=data;'; put '%local wtcnt;%let wtcnt=0;'; put 'data _null_;'; put 'set &tempds;'; put 'if not (upcase(name) =:"DATA"); /* ignore temp datasets */'; put 'i+1;'; put 'call symputx(cats(''wt'',i),name,''l'');'; put 'call symputx(''wtcnt'',i,''l'');'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put ",""WORK"":{";'; put '%do i=1 %to &wtcnt;'; put '%let wt=&&wt&i;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'dsid=open("WORK.&wt",''is'');'; put 'nlobs=attrn(dsid,''NLOBS'');'; put 'nvars=attrn(dsid,''NVARS'');'; put 'rc=close(dsid);'; put 'if &i>1 then put '',''@;'; put 'put " ""&wt"" : {";'; put 'put ''"nlobs":'' nlobs;'; put 'put '',"nvars":'' nvars;'; put '%mp_jsonout(OBJ,&wt,jref=_sjsref,dslabel=first10rows,showmeta=Y'; put ',maxobs=&workobs'; put ')'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put '%end;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put 'run;'; put '%end;'; put '/* close off json */'; put 'data _null_;file _sjsref mod encoding=''utf-8'';'; put 'length SYSPROCESSNAME syserrortext syswarningtext autoexec $512;'; put 'put ",""_DEBUG"" : ""&_debug"" ";'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'autoexec=quote(urlencode(trim(getoption(''autoexec''))));'; put 'put '',"AUTOEXEC" : '' autoexec;'; put 'put ",""MF_GETUSER"" : ""%mf_getuser()"" ";'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'put ",""SYSENCODING"" : ""&sysencoding"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSPROCESSID"" : ""&SYSPROCESSID"" ";'; put 'put ",""SYSPROCESSMODE"" : ""&SYSPROCESSMODE"" ";'; put 'SYSPROCESSNAME=quote(urlencode(cats(SYSPROCESSNAME)));'; put 'put ",""SYSPROCESSNAME"" : " SYSPROCESSNAME;'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put '',"SYSWARNINGTEXT" : '' syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'length memsize $32;'; put 'memsize="%sysfunc(INPUTN(%sysfunc(getoption(memsize)), best.),sizekmg.)";'; put 'memsize=quote(cats(memsize));'; put 'put '',"MEMSIZE" : '' memsize;'; put 'put "}" @;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutEND<<'';'; put '%end;'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjsref lrecl=1 recfm=n;'; put 'file &fref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjsref clear;'; put '%end;'; put '%mend mm_webout;'; put '%macro webout(action,ds,dslabel=,fmt=,missing=NULL,showmeta=NO,maxobs=MAX);'; put '%mm_webout(&action,ds=&ds,dslabel=&dslabel,fmt=&fmt'; put ',missing=&missing'; put ',showmeta=&showmeta'; put ',maxobs=&maxobs'; put ') %mend;'; put '/* provide additional debug info */'; put '%global _program;'; put '%put &=syscc;'; put '%put user=%mf_getuser();'; put '%put pgm=&_program;'; put '%put timestamp=%sysfunc(datetime(),datetime19.);'; put '* Service Variables start;'; put '* Service Variables end;'; put '* SAS Macros start;'; put '%macro mf_getapploc(pgm);'; put '%if "&pgm"="" %then %do;'; put '%if %symexist(_program) %then %let pgm=&_program;'; put '%else %do;'; put '%put &sysmacroname: No value provided and no _program variable available;'; put '%return;'; put '%end;'; put '%end;'; put '%local root;'; put '/**'; put '* First check we are not in the tests/macros folder (which has no subfolders)'; put '* or specifically in the testsetup or testteardown services'; put '*/'; put '%if %index(&pgm,/tests/macros/)'; put 'or %index(&pgm,/tests/testsetup)'; put 'or %index(&pgm,/tests/testteardown)'; put '%then %do;'; put '%let root=%substr(&pgm,1,%index(&pgm,/tests)-1);'; put '&root'; put '%return;'; put '%end;'; put '/**'; put '* Next, move up two levels to avoid matches on subfolder or service name'; put '*/'; put '%let root=%substr(&pgm,1,%length(&pgm)-%length(%scan(&pgm,-1,/))-1);'; put '%let root=%substr(&root,1,%length(&root)-%length(%scan(&root,-1,/))-1);'; put '%if %index(&root,/tests/) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/tests/)-1);'; put '%end;'; put '%else %if %index(&root,/services) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/services)-1);'; put '%end;'; put '%else %if %index(&root,/jobs) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/jobs)-1);'; put '%end;'; put '%else %put &sysmacroname: Could not find an app location from &pgm;'; put '&root'; put '%mend mf_getapploc ;'; put '%macro mf_getuniquefileref(prefix=_,maxtries=1000,lrecl=32767);'; put '%local rc fname;'; put '%if &prefix=0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%end;'; put '%else %do;'; put '%local x len;'; put '%let len=%eval(8-%length(&prefix));'; put '%let x=0;'; put '%do x=0 %to &maxtries;'; put '%let fname=&prefix%substr(%sysfunc(ranuni(0)),3,&len);'; put '%if %sysfunc(fileref(&fname)) > 0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%return;'; put '%end;'; put '%end;'; put '%put unable to find available fileref after &maxtries attempts;'; put '%end;'; put '%mend mf_getuniquefileref;'; put '%macro mp_abort(mac=mp_abort.sas, type=, msg=, iftrue=%str(1=1)'; put ', errds=work.mp_abort_errds'; put ', mode=REGULAR'; put ')/*/STORE SOURCE*/;'; put '%global sysprocessmode sysprocessname sasjs_stpsrv_header_loc sasjsprocessmode;'; put '%local fref fid i;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%put NOTE: /// mp_abort macro executing //;'; put '%if %length(&mac)>0 %then %put NOTE- called by &mac;'; put '%put NOTE - &msg;'; put '%if %symexist(_SYSINCLUDEFILEDEVICE)'; put '/* abort cancel FILE does not restart outside the INCLUDE on Viya 3.5 */'; put 'and %superq(SYSPROCESSNAME) ne %str(Compute Server)'; put '%then %do;'; put '%if "*&_SYSINCLUDEFILEDEVICE*" ne "**" %then %do;'; put 'data &errds;'; put 'iftrue=''1=1'';'; put 'length mac $100 msg $5000;'; put 'mac=symget(''mac'');'; put 'msg=symget(''msg'');'; put 'run;'; put 'data _null_;'; put 'abort cancel FILE;'; put 'run;'; put '%return;'; put '%end;'; put '%end;'; put '/* Web App Context */'; put '%if %symexist(_PROGRAM)'; put 'or %superq(SYSPROCESSNAME) = %str(Compute Server)'; put 'or &mode=INCLUDE'; put '%then %do;'; put 'options obs=max replace mprint;'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5"'; put '%then %do;'; put 'options nosyntaxcheck;'; put '%end;'; put '%if &mode=INCLUDE %then %do;'; put '%if %sysfunc(exist(&errds))=1 %then %do;'; put 'data _null_;'; put 'set &errds;'; put 'call symputx(''iftrue'',iftrue,''l'');'; put 'call symputx(''mac'',mac,''l'');'; put 'call symputx(''msg'',msg,''l'');'; put 'putlog (_all_)(=);'; put 'run;'; put '%if (&iftrue)=0 %then %return;'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: No include errors found;'; put '%return;'; put '%end;'; put '%end;'; put '/* extract log errs / warns, if exist */'; put '%local logloc logline;'; put '%global logmsg; /* capture global messages */'; put '%if %symexist(SYSPRINTTOLOG) %then %let logloc=&SYSPRINTTOLOG;'; put '%else %let logloc=%qsysfunc(getoption(LOG));'; put 'proc printto log=log;run;'; put '%let logline=0;'; put '%if %length(&logloc)>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input; putlog _infile_;'; put 'i=1;'; put 'retain logonce 0;'; put 'if ('; put '_infile_=:"%str(WARN)ING" or _infile_=:"%str(ERR)OR"'; put ') and logonce=0 then'; put 'do;'; put 'call symputx(''logline'',_n_);'; put 'logonce+1;'; put 'end;'; put 'run;'; put '/* capture log including lines BEFORE the err */'; put '%if &logline>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input;'; put 'i=1;'; put 'stoploop=0;'; put 'if _n_ ge &logline-15 and stoploop=0 then do until (i>22);'; put 'call symputx(''logmsg'',catx(''\n'',symget(''logmsg''),_infile_));'; put 'input;'; put 'i+1;'; put 'stoploop=1;'; put 'end;'; put 'if stoploop=1 then stop;'; put 'run;'; put '%end;'; put '%end;'; put '%if %symexist(SYS_JES_JOB_URI) %then %do;'; put '/* setup webout for Viya */'; put 'options nobomfile;'; put '%if "X&SYS_JES_JOB_URI.X"="XX" %then %do;'; put 'filename _webout temp lrecl=999999 mod;'; put '%end;'; put '%else %do;'; put 'filename _webout filesrvc parenturi="&SYS_JES_JOB_URI"'; put 'name="_webout.json" lrecl=999999 mod;'; put '%end;'; put '%end;'; put '%else %if %sysfunc(filename(fref,&sasjs_stpsrv_header_loc))=0 %then %do;'; put 'options nobomfile;'; put '/* set up http header for SASjs Server */'; put '%let fid=%sysfunc(fopen(&fref,A));'; put '%if &fid=0 %then %do;'; put '%put %str(ERR)OR: %sysfunc(sysmsg());'; put '%return;'; put '%end;'; put '%let rc=%sysfunc(fput(&fid,%str(Content-Type: application/json)));'; put '%let rc=%sysfunc(fwrite(&fid));'; put '%let rc=%sysfunc(fclose(&fid));'; put '%let rc=%sysfunc(filename(&fref));'; put '%end;'; put '/* send response in SASjs JSON format */'; put 'data _null_;'; put 'file _webout mod lrecl=32000 encoding=''utf-8'';'; put 'length msg syswarningtext syserrortext $32767 mode $10 ;'; put 'sasdatetime=datetime();'; put 'msg=symget(''msg'');'; put '%if &logline>0 %then %do;'; put 'msg=cats(msg,''\n\nLog Extract:\n'',symget(''logmsg''));'; put '%end;'; put '/* escape the escapes */'; put 'msg=tranwrd(msg,''\'',''\\'');'; put '/* escape the quotes */'; put 'msg=tranwrd(msg,''"'',''\"'');'; put '/* ditch the CRLFs as chrome complains */'; put 'msg=compress(msg,,''kw'');'; put '/* quote without quoting the quotes (which are escaped instead) */'; put 'msg=cats(''"'',msg,''"'');'; put 'if symexist(''_debug'') then debug=quote(trim(symget(''_debug'')));'; put 'else debug=''""'';'; put 'if symget(''sasjsprocessmode'')=''Stored Program'' then mode=''SASJS'';'; put 'if mode ne ''SASJS'' then put ''>>weboutBEGIN<<'';'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'put '',"sasjsAbort" : [{'';'; put 'put '' "MSG":'' msg ;'; put 'put '' ,"MAC": "'' "&mac" ''"}]'';'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'put '',"_DEBUG":'' debug ;'; put 'if symexist(''_metauser'') then do;'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put 'end;'; put 'if symexist(''SYS_JES_JOB_URI'') then do;'; put 'SYS_JES_JOB_URI=quote(trim(symget(''SYS_JES_JOB_URI'')));'; put 'put '',"SYS_JES_JOB_URI": '' SYS_JES_JOB_URI;'; put 'end;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put ",""SYSWARNINGTEXT"" : " syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'put "}" ;'; put 'if mode ne ''SASJS'' then put ''>>weboutEND<<'';'; put 'run;'; put '%put _all_;'; put '%if "&sysprocessmode " = "SAS Stored Process Server " %then %do;'; put 'data _null_;'; put 'putlog ''stpsrvset program err and syscc'';'; put 'rc=stpsrvset(''program error'', 0);'; put 'call symputx("syscc",0,"g");'; put 'run;'; put '%if &sysscp=WIN'; put 'and 1=0 /* deprecating this logic until we figure out a consistent abort */'; put 'and "%substr(%str(&sysvlong ),1,8)"="9.04.01M"'; put 'and "%substr(%str(&sysvlong ),9,1)">"5" %then %do;'; put '/* skip approach (below) does not work in windows m6+ envs */'; put 'endsas;'; put '%end;'; put '%else %do;'; put '/**'; put '* endsas kills 9.4m3 deployments by orphaning multibridges.'; put '* Abort variants are ungraceful (non zero return code)'; put '* This approach lets SAS run silently until the end :-)'; put '* Caution - fails when called within a %include within a macro'; put '* Use mp_include() to handle this.'; put '*/'; put 'filename skip temp;'; put 'data _null_;'; put 'file skip;'; put 'put ''%macro skip();'';'; put 'comment ''%mend skip; -> fix lint '';'; put 'put ''%macro skippy();'';'; put 'comment ''%mend skippy; -> fix lint '';'; put 'run;'; put '%inc skip;'; put '%end;'; put '%end;'; put '%else %if "&sysprocessmode " = "SAS Compute Server " %then %do;'; put '/* endsas kills the session making it harder to fetch results */'; put 'data _null_;'; put 'syswarningtext=symget(''syswarningtext'');'; put 'syserrortext=symget(''syserrortext'');'; put 'abort_msg=symget(''msg'');'; put 'syscc=symget(''syscc'');'; put 'sysuserid=symget(''sysuserid'');'; put 'iftrue=symget(''iftrue'');'; put 'put (_all_)(/=);'; put 'call symputx(''syscc'',0);'; put 'abort cancel nolist;'; put 'run;'; put '%end;'; put '%else %do;'; put '%abort cancel;'; put '%end;'; put '%end;'; put '%else %do;'; put '%put _all_;'; put '%abort cancel;'; put '%end;'; put '%mend mp_abort;'; put '/** @endcond */'; put '%macro mm_getstpcode('; put 'tree=/User Folders/sasdemo/somestp'; put ',name='; put ',outloc=0'; put ',outref=0'; put ',mDebug=1'; put ',showlog=NO'; put ');'; put '%local mD;'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing &sysmacroname..sas;'; put '%&mD.put _local_;'; put '%if %length(&name)>0 %then %let name=/&name;'; put '/* first, check if STP exists */'; put '%local tsuri;'; put '%let tsuri=stopifempty ;'; put 'data _null_;'; put 'format type uri tsuri value $200.;'; put 'call missing (of _all_);'; put 'path="&tree&name(StoredProcess)";'; put '/* first, find the STP ID */'; put 'if metadata_pathobj("",path,"StoredProcess",type,uri)>0 then do;'; put '/* get sourcecode */'; put 'cnt=1;'; put 'do while (metadata_getnasn(uri,"Notes",cnt,tsuri)>0);'; put 'rc=metadata_getattr(tsuri,"Name",value);'; put '&mD.put tsuri= value=;'; put 'if value="SourceCode" then do;'; put '/* found it! */'; put 'rc=metadata_getattr(tsuri,"Id",value);'; put 'call symputx(''tsuri'',value,''l'');'; put 'stop;'; put 'end;'; put 'cnt+1;'; put 'end;'; put 'end;'; put 'else put (_all_)(=);'; put 'run;'; put '%mp_abort(iftrue= (&tsuri=stopifempty)'; put ',mac=mm_getstpcode'; put ',msg=%str(&tree&name.(StoredProcess) not found!)'; put ')'; put '/**'; put '* Now we can extract the textstore'; put '*/'; put 'filename __getdoc temp lrecl=10000000;'; put 'proc metadata'; put 'in="$METAREPOSITORY'; put ''; put 'SAS1"'; put 'out=__getdoc ;'; put 'run;'; put '/* find the beginning of the text */'; put '%local start;'; put 'data _null_;'; put 'infile __getdoc lrecl=10000;'; put 'input;'; put 'start=index(_infile_,''StoredText="'');'; put 'if start then do;'; put 'call symputx("start",start+11);'; put '*putlog ''"'' _infile_ ''"'';'; put 'end;'; put 'stop;'; put '%local outeng;'; put '%if "&outloc"="0" %then %let outeng=TEMP;'; put '%else %let outeng="&outloc";'; put '%local fref;'; put '%if &outref=0 %then %let fref=%mf_getuniquefileref();'; put '%else %let fref=&outref;'; put '/* read the content, byte by byte, resolving escaped chars */'; put 'filename &fref &outeng lrecl=100000;'; put 'data _null_;'; put 'length filein 8 fileid 8;'; put 'filein = fopen("__getdoc","I",1,"B");'; put 'fileid = fopen("&fref","O",1,"B");'; put 'rec = "20"x;'; put 'length entity $6;'; put 'do while(fread(filein)=0);'; put 'x+1;'; put 'if x>&start then do;'; put 'rc = fget(filein,rec,1);'; put 'if rec=''"'' then leave;'; put 'else if rec="&" then do;'; put 'entity=rec;'; put 'do until (rec=";");'; put 'if fread(filein) ne 0 then goto getout;'; put 'rc = fget(filein,rec,1);'; put 'entity=cats(entity,rec);'; put 'end;'; put 'select (entity);'; put 'when (''&'' ) rec=''&'' ;'; put 'when (''<'' ) rec=''<'' ;'; put 'when (''>'' ) rec=''>'' ;'; put 'when (''''') rec="''" ;'; put 'when (''"'') rec=''"'' ;'; put 'when ('' '') rec=''0A''x;'; put 'when ('' '') rec=''0D''x;'; put 'when (''$'' ) rec=''$'' ;'; put 'when ('' '') rec=''09''x;'; put 'otherwise putlog "%str(WARN)ING: missing value for " entity=;'; put 'end;'; put 'rc =fput(fileid, substr(rec,1,1));'; put 'rc =fwrite(fileid);'; put 'end;'; put 'else do;'; put 'rc =fput(fileid,rec);'; put 'rc =fwrite(fileid);'; put 'end;'; put 'end;'; put 'end;'; put 'getout:'; put 'rc=fclose(filein);'; put 'rc=fclose(fileid);'; put 'run;'; put '%if &showlog=YES %then %do;'; put 'data _null_;'; put 'infile &fref lrecl=32767 end=last;'; put 'input;'; put 'if _n_=1 then putlog ''>>stpcodeBEGIN<<'';'; put 'putlog _infile_;'; put 'if last then putlog ''>>stpcodeEND<<'';'; put 'run;'; put '%end;'; put 'filename __getdoc clear;'; put '%if &outref=0 %then %do;'; put 'filename &fref clear;'; put '%end;'; put '%mend mm_getstpcode;'; put '%macro dc_getsettings();'; put '%global _program;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(syscc=&syscc on entry (&syswarningtext &syserrortext))'; put ')'; put '%if %length(&_PROGRAM)>1 %then %let root=&_program;'; put '%else %do;'; put '%global _metauser;'; put '%let _metauser=&sysuserid;'; put '/* to mimic a "real" _program we need to give a dummy role and stp name */'; put '%let root=/dummyRole/dummyName;'; put '%end;'; put '/* the DC precode is stored in the Admin folder in the root of'; put 'the project. Lets find that root. */'; put '%put &=root;'; put '%let root=%mf_getapploc();'; put '%put &=root;'; put '/* Now we know the root location we can retrieve the params */'; put '%let temploc=%sysfunc(pathname(work))/settings.txt;'; put '%mm_getstpcode(tree=&root/services/public'; put ',name=Data_Controller_Settings'; put ',outloc=&temploc'; put ')'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Unable to run getstpcode)'; put ')'; put 'filename _getsets "&temploc" lrecl=2000;'; put '/*'; put 'Do not use mp_include here - this puts a copy in every service, which creates'; put 'compilation problems when calling services from mp_include'; put '*/'; put '%inc _getsets/source2;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Problem running &sysmacroname (&syswarningtext &syserrortext))'; put ')'; put '%mend dc_getsettings;'; put '%macro mf_fmtdttm('; put ')/*/STORE SOURCE*/;'; put '%if "&sysver"="9.2" or "&sysver"="9.3"'; put 'or ("&sysver"="9.4" and "%substr(&SYSVLONG,9,1)" le "3")'; put 'or "%substr(&sysver,1,1)"="4"'; put 'or "%substr(&sysver,1,1)"="5"'; put '%then %do;DATETIME19.3%end;'; put '%else %do;E8601DT26.6%end;'; put '%mend mf_fmtdttm;'; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '%macro mp_init(prefix=SASJS'; put ')/*/STORE SOURCE*/;'; put '%if %symexist(SASJS_PREFIX) %then %return; /* only run once */'; put '%global'; put 'SASJS_PREFIX /* the ONLY hard-coded global macro variable in SASjs */'; put '&prefix._FUNCTIONS /* used in mcf_init() to track core function compilation */'; put '&prefix._INIT_NUM /* initialisation time as numeric */'; put '&prefix._INIT_DTTM /* initialisation time in E8601DT26.6 format */'; put '&prefix.WORK /* avoid typing %sysfunc(pathname(work)) every time */'; put ';'; put '%let sasjs_prefix=&prefix;'; put 'data _null_;'; put 'dttm=datetime();'; put 'call symputx("&sasjs_prefix._init_num",dttm,''g'');'; put 'call symputx("&sasjs_prefix._init_dttm",put(dttm,E8601DT26.6),''g'');'; put 'call symputx("&sasjs_prefix.work",pathname(''WORK''),''g'');'; put 'run;'; put 'options'; put 'compress=CHAR /* default is none so ensure we have something! */'; put 'datastmtchk=ALLKEYWORDS /* protection from overwriting input datasets */'; put 'errorcheck=STRICT /* catch errs in libname/filename statements */'; put 'fmterr /* ensure err when a format cannot be found */'; put 'mergenoby=%str(ERR)OR /* throw err when a merge has no BY variables */'; put 'missing=. /* changing this can cause hard to detect errs */'; put 'noquotelenmax /* avoid warnings for long strings */'; put 'noreplace /* avoid overwriting permanent datasets */'; put 'ps=max /* reduce log size slightly */'; put 'ls=max /* reduce log even more and avoid word truncation */'; put 'validmemname=COMPATIBLE /* avoid special characters etc in table names */'; put 'validvarname=V7 /* avoid special characters etc in variable names */'; put 'varinitchk=%str(ERR)OR /* avoid data mistakes from variable name typos */'; put 'varlenchk=%str(ERR)OR /* fail hard if truncation (data loss) can result */'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5" %then %do;'; put 'noautocorrect /* disallow misspelled procedure names */'; put 'dsoptions=note2err /* undocumented - convert bad NOTEs to ERRs */'; put '%end;'; put ';'; put '%mend mp_init;'; put '%macro mpeinit(fetch=YES);'; put '%global mpeinit'; put 'mpeadmins /* group with unrestricted Meditor access */'; put 'mpelocapprovals /* location for landing and staging files */'; put 'mpelib /* location of configuration tables for DC */'; put 'dc_repo_users /* location of user / group metadata */'; put 'dc_licence_key /* extracted in dc_getsettings */'; put 'dc_activation_key /* extracted in dc_getsettings */'; put 'dc_locale /* extracted in dc_getsettings */'; put 'dc_dttmtfmt /* can be overridden in dc_getsettings */'; put '_debug'; put ';'; put '%if &mpeinit=1 %then %return;'; put '%else %let mpeinit=1;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(Problem on service startup (&syswarningtext &syserrortext))'; put ')'; put '%mp_init()'; put '%if &fetch=YES %then %do;'; put '%webout(FETCH)'; put '%end;'; put '%global _CLIENTNAME;'; put '%mp_abort(iftrue= (&_CLIENTNAME=SAS Enterprise Guide)'; put ',mac=&_program..sas'; put ',msg=%str(Data Controller is a web app and should not be executed from EG)'; put ')'; put 'options urlencoding=utf8 nobomfile lrecl=32767;'; put '%let perf=%sysfunc(datetime());'; put '%put perfdiff: 0;'; put '%let dc_locale=SYSTEM; /* default if not set */'; put '/**'; put '* E8601DT26.6 has widest database support - but not all SAS flavours can'; put '* handle it. Override in the settings STP if needed.'; put '*/'; put 'data _null_;'; put 'dc_dttmtfmt=''"%sysfunc(datetime(),''!!"%mf_fmtdttm()"!!'')"dt'';'; put 'call symputx(''dc_dttmtfmt'',dc_dttmtfmt);'; put 'put dc_dttmtfmt=;'; put 'run;'; put '%put &=dc_dttmtfmt;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc prior to dc_getsettings)'; put ')'; put '%dc_getsettings()'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc after dc_getsettings)'; put ')'; put 'data _null_;'; put 'set &DC_LIBREF..mpe_config(where=('; put 'var_scope="DC"'; put 'and &dc_dttmtfmt lt tx_to'; put 'and var_active=1'; put '));'; put 'call symputx(var_name,var_value,''G'');'; put 'putlog var_name "=" var_value;'; put 'run;'; put '%let mpelib=&dc_libref;'; put '%let mpeadmins=&dc_admin_group;'; put '%let mpelocapprovals=&dc_staging_area;'; put '%let dc_repo_users=&dc_repo_users;'; put '%if &dc_locale ne SYSTEM %then %do;'; put 'options locale=&dc_locale;'; put '%end;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(Problem during compilation or with STP precode (&syswarningtext))'; put ')'; put '%mend mpeinit;'; put '%macro mf_mval(var);'; put '%if %symexist(&var) %then %do;'; put '%superq(&var)'; put '%end;'; put '%mend mf_mval;'; put '%macro mf_trimstr(basestr,trimstr);'; put '%local baselen trimlen trimval;'; put '/* return if basestr is shorter than trimstr (or 0) */'; put '%let baselen=%length(%superq(basestr));'; put '%let trimlen=%length(%superq(trimstr));'; put '%if &baselen < &trimlen or &baselen=0 %then %return;'; put '/* obtain the characters from the end of basestr */'; put '%let trimval=%qsubstr(%superq(basestr)'; put ',%length(%superq(basestr))-&trimlen+1'; put ',&trimlen);'; put '/* compare and if matching, chop it off! */'; put '%if %superq(basestr)=%superq(trimstr) %then %do;'; put '%return;'; put '%end;'; put '%else %if %superq(trimval)=%superq(trimstr) %then %do;'; put '%qsubstr(%superq(basestr),1,%length(%superq(basestr))-&trimlen)'; put '%end;'; put '%else %do;'; put '&basestr'; put '%end;'; put '%mend mf_trimstr;'; put '%macro mf_getplatform(switch'; put ')/*/STORE SOURCE*/;'; put '%local a b c;'; put '%if &switch.NONE=NONE %then %do;'; put '%if %symexist(sasjsprocessmode) %then %do;'; put '%if &sasjsprocessmode=Stored Program %then %do;'; put 'SASJS'; put '%return;'; put '%end;'; put '%end;'; put '%if %symexist(sysprocessmode) %then %do;'; put '%if "&sysprocessmode"="SAS Object Server"'; put 'or "&sysprocessmode"= "SAS Compute Server" %then %do;'; put 'SASVIYA'; put '%end;'; put '%else %if "&sysprocessmode"="SAS Stored Process Server"'; put 'or "&sysprocessmode"="SAS Workspace Server"'; put '%then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if %symexist(_metaport) or %symexist(_metauser) %then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if &switch=SASSTUDIO %then %do;'; put '/* return the version of SAS Studio else 0 */'; put '%if %mf_mval(_CLIENTAPP)=%str(SAS Studio) %then %do;'; put '%let a=%mf_mval(_CLIENTVERSION);'; put '%let b=%scan(&a,1,.);'; put '%if %eval(&b >2) %then %do;'; put '&b'; put '%end;'; put '%else 0;'; put '%end;'; put '%else 0;'; put '%end;'; put '%else %if &switch=VIYARESTAPI %then %do;'; put '%mf_trimstr(%sysfunc(getoption(servicesbaseurl)),/)'; put '%end;'; put '%mend mf_getplatform;'; put '%macro mpeterm();'; put '%local oldloc;'; put 'data _null_;'; put 'if symexist(''SYSPRINTTOLOG'') then oldloc=symget(''SYSPRINTTOLOG'');'; put 'else oldloc=getoption(''LOG'');'; put 'if subpad(oldloc,1,1) not in (''"'',"''",'' '') then oldloc=quote(cats(oldloc));'; put 'call symputx(''oldloc'',oldloc,''l'');'; put 'run;'; put '%if %length(&oldloc)>0 %then %do;'; put 'proc printto log=log;'; put 'run;'; put 'data _null_;'; put 'infile &oldloc;'; put 'input; putlog _infile_;'; put 'run;'; put '%end;'; put '%if %sysfunc(exist(&dc_libref..mpe_requests)) and %mf_getplatform() ne SASVIYA'; put '%then %do;'; put 'data ;'; put 'if 0 then set &dc_libref..mpe_requests;'; put 'request_dttm=%sysfunc(datetime());'; put 'request_user="%mf_getuser()";'; put 'request_service="%scan(&_program,-2,/)/%scan(&_program,-1,/)";'; put 'request_params='''';'; put 'output;stop;'; put 'proc append base=&dc_libref..mpe_requests data=&syslast force nowarn;'; put 'run;'; put '%end;'; put '%mend mpeterm;'; put '* SAS Macros end;'; put '* SAS Includes start;'; put '* SAS Includes end;'; put '* Binary Files start;'; put '* Binary Files end;'; put '* ServiceInit start;'; put 'options noquotelenmax ps=max;'; put '/* create dummy macros to prevent stpbegin / stpend from corrupting sessions */'; put '%macro stpbegin();'; put '%put NOTE: the STPBEGIN macro should not be used for web apps!;'; put '%mend stpbegin;'; put '%macro stpend();'; put '%put NOTE: the STPEND macro should not be used for web apps!;'; put '%mend stpend;'; put '* ServiceInit end;'; put '* Service start;'; put '/**'; put '@file registeruser.sas'; put '@brief Registers a new user in Data Controller'; put '@details New users are logged after accepting EULA terms.'; put '

SAS Macros

'; put '@li mf_getuser.sas'; put '@li mp_abort.sas'; put '@li mpeinit.sas'; put '@version 9.2'; put '@author 4GL Apps Ltd'; put '@copyright 4GL Apps Ltd. This code may only be used within Data Controller'; put 'and may not be re-distributed or re-sold without the express permission of'; put '4GL Apps Ltd.'; put '**/'; put '%mpeinit()'; put '%let userid=%mf_getuser();'; put '/* confirm the user is not registered */'; put '%let isRegistered=0;'; put 'proc sql noprint;'; put 'select count(*) into: isregistered'; put 'from &mpelib..mpe_users'; put 'where user_id="&userid";'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(Problem accessing &mpelib..mpe_users table)'; put ')'; put '%mp_abort(iftrue= (&isregistered > 0)'; put ',mac=&_program..sas'; put ',msg=%str(User &userid is already registered on &mpelib..mpe_users!)'; put ')'; put 'data work.append;'; put 'if 0 then set &mpelib..mpe_users;'; put 'user_id=symget(''userid'');'; put 'registered_dt=today();'; put 'last_seen_dt=today();'; put 'run;'; put 'proc append base=&mpelib..mpe_users data=work.append;'; put '%let isRegistered=0;'; put 'proc sql noprint;'; put 'select count(*) into: isregistered'; put 'from &mpelib..mpe_users'; put 'where user_id="&userid";'; put '%mp_abort(iftrue= (&syscc ne 0 or &isregistered ne 1)'; put ',mac=&_program..sas'; put ',msg=%str(Problem appending to &mpelib..mpe_users table)'; put ')'; put 'data work.return;'; put 'msg=''SUCCESS'';'; put 'run;'; put '%webout(OPEN)'; put '%webout(OBJ,return)'; put '%webout(CLOSE)'; put '* Service end;'; run; %mm_createwebservice(path=&appLoc/&path, name=&service, code=sascode, server=&serverName, replace=yes) filename sascode clear; %let service=startupservice; filename sascode temp lrecl=32767; data _null_; file sascode; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '/**'; put '@file mp_jsonout.sas'; put '@brief Writes JSON in SASjs format to a fileref'; put '@details This macro can be used to OPEN a JSON stream and send one or more'; put 'tables as arrays of rows, where each row can be an object or a nested array.'; put 'There are two engines available - DATASTEP or PROCJSON.'; put 'PROC JSON is fast but will produce errs like the ones below if'; put 'special chars are encountered.'; put '> (ERR)OR: Some code points did not transcode.'; put '> An object or array close is not valid at this point in the JSON text.'; put '> Date value out of range'; put 'If this happens, try running with ENGINE=DATASTEP.'; put 'The DATASTEP engine is used to handle special SAS missing numerics, and'; put 'can also convert entire datasets to formatted values. Output JSON is always'; put 'in UTF-8.'; put 'Usage:'; put 'filename tmp temp;'; put 'data class; set sashelp.class;run;'; put '%mp_jsonout(OPEN,jref=tmp)'; put '%mp_jsonout(OBJ,class,jref=tmp)'; put '%mp_jsonout(OBJ,class,dslabel=class2,jref=tmp,showmeta=Y)'; put '%mp_jsonout(CLOSE,jref=tmp)'; put 'data _null_;'; put 'infile tmp;'; put 'input;putlog _infile_;'; put 'run;'; put 'If you are building web apps with SAS then you are strongly encouraged to use'; put 'the mX_createwebservice macros in combination with the'; put '[sasjs adapter](https://github.com/sasjs/adapter).'; put 'For more information see https://sasjs.io'; put '@param [in] action Valid values:'; put '@li OPEN - opens the JSON'; put '@li OBJ - sends a table with each row as an object'; put '@li ARR - sends a table with each row in an array'; put '@li CLOSE - closes the JSON'; put '@param [in] ds The dataset to send. Must be a work table.'; put '@param [out] jref= (_webout) The fileref to which to send the JSON'; put '@param [out] dslabel= The name to give the table in the exported JSON'; put '@param [in] fmt= (Y) Whether to keep (Y) or strip (N) formats from the table'; put '@param [in] engine= (DATASTEP) Which engine to use to send the JSON. Options:'; put '@li PROCJSON (default)'; put '@li DATASTEP (more reliable when data has non standard characters)'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to JSON'; put '

Related Files

'; put '@li mp_ds2fmtds.sas'; put '@version 9.2'; put '@author Allan Bowe'; put '@source https://github.com/sasjs/core'; put '**/'; put '%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y'; put ',engine=DATASTEP'; put ',missing=NULL'; put ',showmeta=N'; put ',maxobs=MAX'; put ')/*/STORE SOURCE*/;'; put '%local tempds colinfo fmtds i numcols numobs stmt_obs lastobs optval'; put 'tmpds1 tmpds2 tmpds3 tmpds4;'; put '%let numcols=0;'; put '%if &maxobs ne MAX %then %let stmt_obs=%str(if _n_>&maxobs then stop;);'; put '%if &action=OPEN %then %do;'; put 'options nobomfile;'; put 'data _null_;file &jref encoding=''utf-8'' lrecl=200;'; put 'put ''{"PROCESSED_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''"'';'; put 'run;'; put '%end;'; put '%else %if (&action=ARR or &action=OBJ) %then %do;'; put '/* force variable names to always be uppercase in the JSON */'; put 'options validvarname=upcase;'; put '/* To avoid issues with _webout on EBI - such as encoding diffs and truncation'; put '(https://support.sas.com/kb/49/325.html) we use temporary files */'; put 'filename _sjs1 temp lrecl=200 ;'; put 'data _null_; file _sjs1 encoding=''utf-8'';'; put 'put ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":";'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjs1 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs1 clear;'; put '/* grab col defs */'; put 'proc contents noprint data=&ds'; put 'out=_data_(keep=name type length format formatl formatd varnum label);'; put 'run;'; put '%let colinfo=%scan(&syslast,2,.);'; put 'proc sort data=&colinfo;'; put 'by varnum;'; put 'run;'; put '/* move meta to mac vars */'; put 'data &colinfo;'; put 'if _n_=1 then call symputx(''numcols'',nobs,''l'');'; put 'set &colinfo end=last nobs=nobs;'; put 'name=upcase(name);'; put '/* fix formats */'; put 'if type=2 or type=6 then do;'; put 'typelong=''char'';'; put 'length fmt $49.;'; put 'if format='''' then fmt=cats(''$'',length,''.'');'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else fmt=cats(format,formatl,''.'');'; put 'end;'; put 'else do;'; put 'typelong=''num'';'; put 'if format='''' then fmt=''best.'';'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else if formatd=0 then fmt=cats(format,formatl,''.'');'; put 'else fmt=cats(format,formatl,''.'',formatd);'; put 'end;'; put '/* 32 char unique name */'; put 'newname=''sasjs''!!substr(cats(put(md5(name),$hex32.)),1,27);'; put 'call symputx(cats(''name'',_n_),name,''l'');'; put 'call symputx(cats(''newname'',_n_),newname,''l'');'; put 'call symputx(cats(''length'',_n_),length,''l'');'; put 'call symputx(cats(''fmt'',_n_),fmt,''l'');'; put 'call symputx(cats(''type'',_n_),type,''l'');'; put 'call symputx(cats(''typelong'',_n_),typelong,''l'');'; put 'call symputx(cats(''label'',_n_),coalescec(label,name),''l'');'; put '/* overwritten when fmt=Y and a custom format exists in catalog */'; put 'if typelong=''num'' then call symputx(cats(''fmtlen'',_n_),200,''l'');'; put 'else call symputx(cats(''fmtlen'',_n_),min(32767,ceil((length+10)*1.5)),''l'');'; put 'run;'; put '%let tempds=%substr(_%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql;'; put 'select count(*) into: lastobs from &ds;'; put '%if &maxobs ne MAX %then %let lastobs=%sysfunc(min(&lastobs,&maxobs));'; put '%if &engine=PROCJSON %then %do;'; put '%if &missing=STRING %then %do;'; put '%put &sysmacroname: Special Missings not supported in proc json.;'; put '%put &sysmacroname: Switching to DATASTEP engine;'; put '%goto datastep;'; put '%end;'; put 'data &tempds;'; put 'set &ds;'; put '&stmt_obs;'; put '%if &fmt=N %then format _numeric_ best32.;;'; put '/* PRETTY is necessary to avoid line truncation in large files */'; put 'filename _sjs2 temp lrecl=131068 encoding=''utf-8'';'; put 'proc json out=_sjs2 pretty'; put '%if &action=ARR %then nokeys ;'; put ';export &tempds / nosastags fmtnumeric;'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs2 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs2 clear;'; put '%end;'; put '%else %if &engine=DATASTEP %then %do;'; put '%datastep:'; put '%if %sysfunc(exist(&ds)) ne 1 & %sysfunc(exist(&ds,VIEW)) ne 1'; put '%then %do;'; put '%put &sysmacroname: &ds NOT FOUND!!!;'; put '%return;'; put '%end;'; put '%if &fmt=Y %then %do;'; put '/**'; put '* Extract format definitions'; put '* First, by getting library locations from dictionary.formats'; put '* Then, by exporting the width using proc format'; put '* Cannot use maxw from sashelp.vformat as not always populated'; put '* Cannot use fmtinfo() as not supported in all flavours'; put '*/'; put '%let tmpds1=%substr(fmtsum%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds2=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds3=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds4=%substr(col%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql noprint;'; put 'create table &tmpds1 as'; put 'select cats(libname,''.'',memname) as FMTCAT,'; put 'FMTNAME'; put 'from dictionary.formats'; put 'where fmttype=''F'' and libname is not null'; put 'and fmtname in (select format from &colinfo where format is not null)'; put 'order by 1;'; put 'create table &tmpds2('; put 'FMTNAME char(32),'; put 'LENGTH num'; put ');'; put '%local catlist cat fmtlist i;'; put 'select distinct fmtcat into: catlist separated by '' '' from &tmpds1;'; put '%do i=1 %to %sysfunc(countw(&catlist,%str( )));'; put '%let cat=%scan(&catlist,&i,%str( ));'; put 'proc sql;'; put 'select distinct fmtname into: fmtlist separated by '' '''; put 'from &tmpds1 where fmtcat="&cat";'; put 'proc format lib=&cat cntlout=&tmpds3(keep=fmtname length);'; put 'select &fmtlist;'; put 'run;'; put 'proc sql;'; put 'insert into &tmpds2 select distinct fmtname,length from &tmpds3;'; put '%end;'; put 'proc sql;'; put 'create table &tmpds4 as'; put 'select a.*, b.length as MAXW'; put 'from &colinfo a'; put 'left join &tmpds2 b'; put 'on cats(a.format)=cats(upcase(b.fmtname))'; put 'order by a.varnum;'; put 'data _null_;'; put 'set &tmpds4;'; put 'if not missing(maxw);'; put 'call symputx('; put 'cats(''fmtlen'',_n_),'; put '/* vars need extra padding due to JSON escaping of special chars */'; put 'min(32767,ceil((max(length,maxw)+10)*1.5))'; put ',''l'''; put ');'; put 'run;'; put '/* configure varlenchk - as we are explicitly shortening the variables */'; put '%let optval=%sysfunc(getoption(varlenchk));'; put 'options varlenchk=NOWARN;'; put 'data _data_(compress=char);'; put '/* shorten the new vars */'; put 'length'; put '%do i=1 %to &numcols;'; put '&&name&i $&&fmtlen&i'; put '%end;'; put ';'; put '/* rename on entry */'; put 'set &ds(rename=('; put '%do i=1 %to &numcols;'; put '&&name&i=&&newname&i'; put '%end;'; put '));'; put '&stmt_obs;'; put 'drop'; put '%do i=1 %to &numcols;'; put '&&newname&i'; put '%end;'; put ';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=num %then %do;'; put '&&name&i=cats(put(&&newname&i,&&fmt&i));'; put '%end;'; put '%else %do;'; put '&&name&i=put(&&newname&i,&&fmt&i);'; put '%end;'; put '%end;'; put 'if _error_ then do;'; put 'call symputx(''syscc'',1012);'; put 'stop;'; put 'end;'; put 'run;'; put '%let fmtds=&syslast;'; put 'options varlenchk=&optval;'; put '%end;'; put 'proc format; /* credit yabwon for special null removal */'; put 'value bart (default=40)'; put '%if &missing=NULL %then %do;'; put '._ - .z = null'; put '%end;'; put '%else %do;'; put '._ = [quote()]'; put '. = null'; put '.a - .z = [quote()]'; put '%end;'; put 'other = [best.];'; put 'data &tempds;'; put 'attrib _all_ label='''';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'length &&name&i $&&fmtlen&i...;'; put 'format &&name&i $&&fmtlen&i...;'; put '%end;'; put '%end;'; put '%if &fmt=Y %then %do;'; put 'set &fmtds;'; put '%end;'; put '%else %do;'; put 'set &ds;'; put '%end;'; put '&stmt_obs;'; put 'format _numeric_ bart.;'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'if findc(&&name&i,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put '&&name&i=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,&&name&i)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else &&name&i=quote(cats(&&name&i));'; put '%end;'; put '%end;'; put 'run;'; put 'filename _sjs3 temp lrecl=131068 ;'; put 'data _null_;'; put 'file _sjs3 encoding=''utf-8'';'; put 'if _n_=1 then put "[";'; put 'set &tempds;'; put 'if _n_>1 then put "," @; put'; put '%if &action=ARR %then "[" ; %else "{" ;'; put '%do i=1 %to &numcols;'; put '%if &i>1 %then "," ;'; put '%if &action=OBJ %then """&&name&i"":" ;'; put '"&&name&i"n /* name literal for reserved variable names */'; put '%end;'; put '%if &action=ARR %then "]" ; %else "}" ; ;'; put '/* close out the table */'; put 'data _null_;'; put 'file _sjs3 mod encoding=''utf-8'';'; put 'put '']'';'; put 'run;'; put 'data _null_;'; put 'infile _sjs3 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs3 clear;'; put '%end;'; put 'proc sql;'; put 'drop table &colinfo, &tempds;'; put '%if %substr(&showmeta,1,1)=Y %then %do;'; put 'filename _sjs4 temp lrecl=131068 encoding=''utf-8'';'; put 'data _null_;'; put 'file _sjs4;'; put 'length label $350;'; put 'put ", ""$%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":{""vars"":{";'; put 'do i=1 to &numcols;'; put 'name=quote(trim(symget(cats(''name'',i))));'; put 'format=quote(trim(symget(cats(''fmt'',i))));'; put 'label=quote(prxchange(''s/\\/\\\\/'',-1,trim(symget(cats(''label'',i)))));'; put 'length=quote(trim(symget(cats(''length'',i))));'; put 'type=quote(trim(symget(cats(''typelong'',i))));'; put 'if i>1 then put "," @@;'; put 'put name '':{"format":'' format '',"label":'' label'; put ''',"length":'' length '',"type":'' type ''}'';'; put 'end;'; put 'put ''}}'';'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs4 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs4 clear;'; put '%end;'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put 'data _null_; file &jref encoding=''utf-8'' mod ;'; put 'put "}";'; put 'run;'; put '%end;'; put '%mend mp_jsonout;'; put '/**'; put '@file mm_webout.sas'; put '@brief Send data to/from SAS Stored Processes'; put '@details This macro should be added to the start of each Stored Process,'; put '**immediately** followed by a call to:'; put '%mm_webout(FETCH)'; put 'This will read all the input data and create same-named SAS datasets in the'; put 'WORK library. You can then insert your code, and send data back using the'; put 'following syntax:'; put 'data some datasets; * make some data ;'; put 'retain some columns;'; put 'run;'; put '%mm_webout(OPEN)'; put '%mm_webout(ARR,some) * Array format, fast, suitable for large tables ;'; put '%mm_webout(OBJ,datasets) * Object format, easier to work with ;'; put 'Finally, wrap everything up send some helpful system variables too'; put '%mm_webout(CLOSE)'; put '@param [in] action Either FETCH, OPEN, ARR, OBJ or CLOSE'; put '@param [in] ds The dataset to send back to the frontend'; put '@param [out] dslabel= Value to use instead of table name for sending to JSON'; put '@param [in] fmt= (N) Setting Y converts all vars to their formatted values'; put '@param [out] fref= (_webout) The fileref to which to write the JSON'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] workobs= (0) When set to a positive integer, will create a new'; put 'output object (WORK) which contains this number of observations from all'; put 'tables in the WORK library.'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to output JSON'; put '

SAS Macros

'; put '@li mp_jsonout.sas'; put '

Related Macros

'; put '@li ms_webout.sas'; put '@li mv_webout.sas'; put '@version 9.3'; put '@author Allan Bowe'; put '**/'; put '%macro mm_webout(action,ds,dslabel=,fref=_webout,fmt=N,missing=NULL'; put ',showmeta=N,maxobs=MAX,workobs=0'; put ');'; put '%global _webin_file_count _webin_fileref1 _webin_name1 _program _debug'; put 'sasjs_tables;'; put '%local i tempds jsonengine;'; put '/* see https://github.com/sasjs/core/issues/41 */'; put '%if "%upcase(&SYSENCODING)" ne "UTF-8" %then %let jsonengine=PROCJSON;'; put '%else %let jsonengine=DATASTEP;'; put '%if &action=FETCH %then %do;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'options mprint notes mprintnest;'; put '%end;'; put '%let _webin_file_count=%eval(&_webin_file_count+0);'; put '/* now read in the data */'; put '%do i=1 %to &_webin_file_count;'; put '%if &_webin_file_count=1 %then %do;'; put '%let _webin_fileref1=&_webin_fileref;'; put '%let _webin_name1=&_webin_name;'; put '%end;'; put 'data _null_;'; put 'infile &&_webin_fileref&i termstr=crlf;'; put 'input;'; put 'call symputx(''input_statement'',_infile_);'; put 'putlog "&&_webin_name&i input statement: " _infile_;'; put 'stop;'; put 'data &&_webin_name&i;'; put 'infile &&_webin_fileref&i firstobs=2 dsd termstr=crlf encoding=''utf-8'';'; put 'input &input_statement;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'if _n_<20 then putlog _infile_;'; put '%end;'; put 'run;'; put '%let sasjs_tables=&sasjs_tables &&_webin_name&i;'; put '%end;'; put '%end;'; put '%else %if &action=OPEN %then %do;'; put '/* fix encoding */'; put 'OPTIONS NOBOMFILE;'; put '/**'; put '* check xengine type to avoid the below err message:'; put '* > Function is only valid for filerefs using the CACHE access method.'; put '*/'; put 'data _null_;'; put 'set sashelp.vextfl(where=(fileref="_WEBOUT"));'; put 'if xengine=''STREAM'' then do;'; put 'rc=stpsrv_header(''Content-type'',"text/html; encoding=utf-8");'; put 'end;'; put 'run;'; put '/* setup json */'; put 'data _null_;file &fref encoding=''utf-8'';'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutBEGIN<<'';'; put '%end;'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'run;'; put '%end;'; put '%else %if &action=ARR or &action=OBJ %then %do;'; put '%mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt,jref=&fref'; put ',engine=&jsonengine,missing=&missing,showmeta=&showmeta,maxobs=&maxobs'; put ')'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put '/* To avoid issues with _webout on EBI we use a temporary file */'; put 'filename _sjsref temp lrecl=131068;'; put '%if %str(&workobs) > 0 %then %do;'; put '/* if debug mode, send back first XX records of each work table also */'; put 'data;run;%let tempds=%scan(&syslast,2,.);'; put 'ods output Members=&tempds;'; put 'proc datasets library=WORK memtype=data;'; put '%local wtcnt;%let wtcnt=0;'; put 'data _null_;'; put 'set &tempds;'; put 'if not (upcase(name) =:"DATA"); /* ignore temp datasets */'; put 'i+1;'; put 'call symputx(cats(''wt'',i),name,''l'');'; put 'call symputx(''wtcnt'',i,''l'');'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put ",""WORK"":{";'; put '%do i=1 %to &wtcnt;'; put '%let wt=&&wt&i;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'dsid=open("WORK.&wt",''is'');'; put 'nlobs=attrn(dsid,''NLOBS'');'; put 'nvars=attrn(dsid,''NVARS'');'; put 'rc=close(dsid);'; put 'if &i>1 then put '',''@;'; put 'put " ""&wt"" : {";'; put 'put ''"nlobs":'' nlobs;'; put 'put '',"nvars":'' nvars;'; put '%mp_jsonout(OBJ,&wt,jref=_sjsref,dslabel=first10rows,showmeta=Y'; put ',maxobs=&workobs'; put ')'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put '%end;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put 'run;'; put '%end;'; put '/* close off json */'; put 'data _null_;file _sjsref mod encoding=''utf-8'';'; put 'length SYSPROCESSNAME syserrortext syswarningtext autoexec $512;'; put 'put ",""_DEBUG"" : ""&_debug"" ";'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'autoexec=quote(urlencode(trim(getoption(''autoexec''))));'; put 'put '',"AUTOEXEC" : '' autoexec;'; put 'put ",""MF_GETUSER"" : ""%mf_getuser()"" ";'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'put ",""SYSENCODING"" : ""&sysencoding"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSPROCESSID"" : ""&SYSPROCESSID"" ";'; put 'put ",""SYSPROCESSMODE"" : ""&SYSPROCESSMODE"" ";'; put 'SYSPROCESSNAME=quote(urlencode(cats(SYSPROCESSNAME)));'; put 'put ",""SYSPROCESSNAME"" : " SYSPROCESSNAME;'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put '',"SYSWARNINGTEXT" : '' syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'length memsize $32;'; put 'memsize="%sysfunc(INPUTN(%sysfunc(getoption(memsize)), best.),sizekmg.)";'; put 'memsize=quote(cats(memsize));'; put 'put '',"MEMSIZE" : '' memsize;'; put 'put "}" @;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutEND<<'';'; put '%end;'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjsref lrecl=1 recfm=n;'; put 'file &fref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjsref clear;'; put '%end;'; put '%mend mm_webout;'; put '%macro webout(action,ds,dslabel=,fmt=,missing=NULL,showmeta=NO,maxobs=MAX);'; put '%mm_webout(&action,ds=&ds,dslabel=&dslabel,fmt=&fmt'; put ',missing=&missing'; put ',showmeta=&showmeta'; put ',maxobs=&maxobs'; put ') %mend;'; put '/* provide additional debug info */'; put '%global _program;'; put '%put &=syscc;'; put '%put user=%mf_getuser();'; put '%put pgm=&_program;'; put '%put timestamp=%sysfunc(datetime(),datetime19.);'; put '* Service Variables start;'; put '* Service Variables end;'; put '* SAS Macros start;'; put '%macro mf_getapploc(pgm);'; put '%if "&pgm"="" %then %do;'; put '%if %symexist(_program) %then %let pgm=&_program;'; put '%else %do;'; put '%put &sysmacroname: No value provided and no _program variable available;'; put '%return;'; put '%end;'; put '%end;'; put '%local root;'; put '/**'; put '* First check we are not in the tests/macros folder (which has no subfolders)'; put '* or specifically in the testsetup or testteardown services'; put '*/'; put '%if %index(&pgm,/tests/macros/)'; put 'or %index(&pgm,/tests/testsetup)'; put 'or %index(&pgm,/tests/testteardown)'; put '%then %do;'; put '%let root=%substr(&pgm,1,%index(&pgm,/tests)-1);'; put '&root'; put '%return;'; put '%end;'; put '/**'; put '* Next, move up two levels to avoid matches on subfolder or service name'; put '*/'; put '%let root=%substr(&pgm,1,%length(&pgm)-%length(%scan(&pgm,-1,/))-1);'; put '%let root=%substr(&root,1,%length(&root)-%length(%scan(&root,-1,/))-1);'; put '%if %index(&root,/tests/) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/tests/)-1);'; put '%end;'; put '%else %if %index(&root,/services) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/services)-1);'; put '%end;'; put '%else %if %index(&root,/jobs) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/jobs)-1);'; put '%end;'; put '%else %put &sysmacroname: Could not find an app location from &pgm;'; put '&root'; put '%mend mf_getapploc ;'; put '%macro mf_getuniquefileref(prefix=_,maxtries=1000,lrecl=32767);'; put '%local rc fname;'; put '%if &prefix=0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%end;'; put '%else %do;'; put '%local x len;'; put '%let len=%eval(8-%length(&prefix));'; put '%let x=0;'; put '%do x=0 %to &maxtries;'; put '%let fname=&prefix%substr(%sysfunc(ranuni(0)),3,&len);'; put '%if %sysfunc(fileref(&fname)) > 0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%return;'; put '%end;'; put '%end;'; put '%put unable to find available fileref after &maxtries attempts;'; put '%end;'; put '%mend mf_getuniquefileref;'; put '%macro mp_abort(mac=mp_abort.sas, type=, msg=, iftrue=%str(1=1)'; put ', errds=work.mp_abort_errds'; put ', mode=REGULAR'; put ')/*/STORE SOURCE*/;'; put '%global sysprocessmode sysprocessname sasjs_stpsrv_header_loc sasjsprocessmode;'; put '%local fref fid i;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%put NOTE: /// mp_abort macro executing //;'; put '%if %length(&mac)>0 %then %put NOTE- called by &mac;'; put '%put NOTE - &msg;'; put '%if %symexist(_SYSINCLUDEFILEDEVICE)'; put '/* abort cancel FILE does not restart outside the INCLUDE on Viya 3.5 */'; put 'and %superq(SYSPROCESSNAME) ne %str(Compute Server)'; put '%then %do;'; put '%if "*&_SYSINCLUDEFILEDEVICE*" ne "**" %then %do;'; put 'data &errds;'; put 'iftrue=''1=1'';'; put 'length mac $100 msg $5000;'; put 'mac=symget(''mac'');'; put 'msg=symget(''msg'');'; put 'run;'; put 'data _null_;'; put 'abort cancel FILE;'; put 'run;'; put '%return;'; put '%end;'; put '%end;'; put '/* Web App Context */'; put '%if %symexist(_PROGRAM)'; put 'or %superq(SYSPROCESSNAME) = %str(Compute Server)'; put 'or &mode=INCLUDE'; put '%then %do;'; put 'options obs=max replace mprint;'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5"'; put '%then %do;'; put 'options nosyntaxcheck;'; put '%end;'; put '%if &mode=INCLUDE %then %do;'; put '%if %sysfunc(exist(&errds))=1 %then %do;'; put 'data _null_;'; put 'set &errds;'; put 'call symputx(''iftrue'',iftrue,''l'');'; put 'call symputx(''mac'',mac,''l'');'; put 'call symputx(''msg'',msg,''l'');'; put 'putlog (_all_)(=);'; put 'run;'; put '%if (&iftrue)=0 %then %return;'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: No include errors found;'; put '%return;'; put '%end;'; put '%end;'; put '/* extract log errs / warns, if exist */'; put '%local logloc logline;'; put '%global logmsg; /* capture global messages */'; put '%if %symexist(SYSPRINTTOLOG) %then %let logloc=&SYSPRINTTOLOG;'; put '%else %let logloc=%qsysfunc(getoption(LOG));'; put 'proc printto log=log;run;'; put '%let logline=0;'; put '%if %length(&logloc)>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input; putlog _infile_;'; put 'i=1;'; put 'retain logonce 0;'; put 'if ('; put '_infile_=:"%str(WARN)ING" or _infile_=:"%str(ERR)OR"'; put ') and logonce=0 then'; put 'do;'; put 'call symputx(''logline'',_n_);'; put 'logonce+1;'; put 'end;'; put 'run;'; put '/* capture log including lines BEFORE the err */'; put '%if &logline>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input;'; put 'i=1;'; put 'stoploop=0;'; put 'if _n_ ge &logline-15 and stoploop=0 then do until (i>22);'; put 'call symputx(''logmsg'',catx(''\n'',symget(''logmsg''),_infile_));'; put 'input;'; put 'i+1;'; put 'stoploop=1;'; put 'end;'; put 'if stoploop=1 then stop;'; put 'run;'; put '%end;'; put '%end;'; put '%if %symexist(SYS_JES_JOB_URI) %then %do;'; put '/* setup webout for Viya */'; put 'options nobomfile;'; put '%if "X&SYS_JES_JOB_URI.X"="XX" %then %do;'; put 'filename _webout temp lrecl=999999 mod;'; put '%end;'; put '%else %do;'; put 'filename _webout filesrvc parenturi="&SYS_JES_JOB_URI"'; put 'name="_webout.json" lrecl=999999 mod;'; put '%end;'; put '%end;'; put '%else %if %sysfunc(filename(fref,&sasjs_stpsrv_header_loc))=0 %then %do;'; put 'options nobomfile;'; put '/* set up http header for SASjs Server */'; put '%let fid=%sysfunc(fopen(&fref,A));'; put '%if &fid=0 %then %do;'; put '%put %str(ERR)OR: %sysfunc(sysmsg());'; put '%return;'; put '%end;'; put '%let rc=%sysfunc(fput(&fid,%str(Content-Type: application/json)));'; put '%let rc=%sysfunc(fwrite(&fid));'; put '%let rc=%sysfunc(fclose(&fid));'; put '%let rc=%sysfunc(filename(&fref));'; put '%end;'; put '/* send response in SASjs JSON format */'; put 'data _null_;'; put 'file _webout mod lrecl=32000 encoding=''utf-8'';'; put 'length msg syswarningtext syserrortext $32767 mode $10 ;'; put 'sasdatetime=datetime();'; put 'msg=symget(''msg'');'; put '%if &logline>0 %then %do;'; put 'msg=cats(msg,''\n\nLog Extract:\n'',symget(''logmsg''));'; put '%end;'; put '/* escape the escapes */'; put 'msg=tranwrd(msg,''\'',''\\'');'; put '/* escape the quotes */'; put 'msg=tranwrd(msg,''"'',''\"'');'; put '/* ditch the CRLFs as chrome complains */'; put 'msg=compress(msg,,''kw'');'; put '/* quote without quoting the quotes (which are escaped instead) */'; put 'msg=cats(''"'',msg,''"'');'; put 'if symexist(''_debug'') then debug=quote(trim(symget(''_debug'')));'; put 'else debug=''""'';'; put 'if symget(''sasjsprocessmode'')=''Stored Program'' then mode=''SASJS'';'; put 'if mode ne ''SASJS'' then put ''>>weboutBEGIN<<'';'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'put '',"sasjsAbort" : [{'';'; put 'put '' "MSG":'' msg ;'; put 'put '' ,"MAC": "'' "&mac" ''"}]'';'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'put '',"_DEBUG":'' debug ;'; put 'if symexist(''_metauser'') then do;'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put 'end;'; put 'if symexist(''SYS_JES_JOB_URI'') then do;'; put 'SYS_JES_JOB_URI=quote(trim(symget(''SYS_JES_JOB_URI'')));'; put 'put '',"SYS_JES_JOB_URI": '' SYS_JES_JOB_URI;'; put 'end;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put ",""SYSWARNINGTEXT"" : " syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'put "}" ;'; put 'if mode ne ''SASJS'' then put ''>>weboutEND<<'';'; put 'run;'; put '%put _all_;'; put '%if "&sysprocessmode " = "SAS Stored Process Server " %then %do;'; put 'data _null_;'; put 'putlog ''stpsrvset program err and syscc'';'; put 'rc=stpsrvset(''program error'', 0);'; put 'call symputx("syscc",0,"g");'; put 'run;'; put '%if &sysscp=WIN'; put 'and 1=0 /* deprecating this logic until we figure out a consistent abort */'; put 'and "%substr(%str(&sysvlong ),1,8)"="9.04.01M"'; put 'and "%substr(%str(&sysvlong ),9,1)">"5" %then %do;'; put '/* skip approach (below) does not work in windows m6+ envs */'; put 'endsas;'; put '%end;'; put '%else %do;'; put '/**'; put '* endsas kills 9.4m3 deployments by orphaning multibridges.'; put '* Abort variants are ungraceful (non zero return code)'; put '* This approach lets SAS run silently until the end :-)'; put '* Caution - fails when called within a %include within a macro'; put '* Use mp_include() to handle this.'; put '*/'; put 'filename skip temp;'; put 'data _null_;'; put 'file skip;'; put 'put ''%macro skip();'';'; put 'comment ''%mend skip; -> fix lint '';'; put 'put ''%macro skippy();'';'; put 'comment ''%mend skippy; -> fix lint '';'; put 'run;'; put '%inc skip;'; put '%end;'; put '%end;'; put '%else %if "&sysprocessmode " = "SAS Compute Server " %then %do;'; put '/* endsas kills the session making it harder to fetch results */'; put 'data _null_;'; put 'syswarningtext=symget(''syswarningtext'');'; put 'syserrortext=symget(''syserrortext'');'; put 'abort_msg=symget(''msg'');'; put 'syscc=symget(''syscc'');'; put 'sysuserid=symget(''sysuserid'');'; put 'iftrue=symget(''iftrue'');'; put 'put (_all_)(/=);'; put 'call symputx(''syscc'',0);'; put 'abort cancel nolist;'; put 'run;'; put '%end;'; put '%else %do;'; put '%abort cancel;'; put '%end;'; put '%end;'; put '%else %do;'; put '%put _all_;'; put '%abort cancel;'; put '%end;'; put '%mend mp_abort;'; put '/** @endcond */'; put '%macro mm_getstpcode('; put 'tree=/User Folders/sasdemo/somestp'; put ',name='; put ',outloc=0'; put ',outref=0'; put ',mDebug=1'; put ',showlog=NO'; put ');'; put '%local mD;'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing &sysmacroname..sas;'; put '%&mD.put _local_;'; put '%if %length(&name)>0 %then %let name=/&name;'; put '/* first, check if STP exists */'; put '%local tsuri;'; put '%let tsuri=stopifempty ;'; put 'data _null_;'; put 'format type uri tsuri value $200.;'; put 'call missing (of _all_);'; put 'path="&tree&name(StoredProcess)";'; put '/* first, find the STP ID */'; put 'if metadata_pathobj("",path,"StoredProcess",type,uri)>0 then do;'; put '/* get sourcecode */'; put 'cnt=1;'; put 'do while (metadata_getnasn(uri,"Notes",cnt,tsuri)>0);'; put 'rc=metadata_getattr(tsuri,"Name",value);'; put '&mD.put tsuri= value=;'; put 'if value="SourceCode" then do;'; put '/* found it! */'; put 'rc=metadata_getattr(tsuri,"Id",value);'; put 'call symputx(''tsuri'',value,''l'');'; put 'stop;'; put 'end;'; put 'cnt+1;'; put 'end;'; put 'end;'; put 'else put (_all_)(=);'; put 'run;'; put '%mp_abort(iftrue= (&tsuri=stopifempty)'; put ',mac=mm_getstpcode'; put ',msg=%str(&tree&name.(StoredProcess) not found!)'; put ')'; put '/**'; put '* Now we can extract the textstore'; put '*/'; put 'filename __getdoc temp lrecl=10000000;'; put 'proc metadata'; put 'in="$METAREPOSITORY'; put ''; put 'SAS1"'; put 'out=__getdoc ;'; put 'run;'; put '/* find the beginning of the text */'; put '%local start;'; put 'data _null_;'; put 'infile __getdoc lrecl=10000;'; put 'input;'; put 'start=index(_infile_,''StoredText="'');'; put 'if start then do;'; put 'call symputx("start",start+11);'; put '*putlog ''"'' _infile_ ''"'';'; put 'end;'; put 'stop;'; put '%local outeng;'; put '%if "&outloc"="0" %then %let outeng=TEMP;'; put '%else %let outeng="&outloc";'; put '%local fref;'; put '%if &outref=0 %then %let fref=%mf_getuniquefileref();'; put '%else %let fref=&outref;'; put '/* read the content, byte by byte, resolving escaped chars */'; put 'filename &fref &outeng lrecl=100000;'; put 'data _null_;'; put 'length filein 8 fileid 8;'; put 'filein = fopen("__getdoc","I",1,"B");'; put 'fileid = fopen("&fref","O",1,"B");'; put 'rec = "20"x;'; put 'length entity $6;'; put 'do while(fread(filein)=0);'; put 'x+1;'; put 'if x>&start then do;'; put 'rc = fget(filein,rec,1);'; put 'if rec=''"'' then leave;'; put 'else if rec="&" then do;'; put 'entity=rec;'; put 'do until (rec=";");'; put 'if fread(filein) ne 0 then goto getout;'; put 'rc = fget(filein,rec,1);'; put 'entity=cats(entity,rec);'; put 'end;'; put 'select (entity);'; put 'when (''&'' ) rec=''&'' ;'; put 'when (''<'' ) rec=''<'' ;'; put 'when (''>'' ) rec=''>'' ;'; put 'when (''''') rec="''" ;'; put 'when (''"'') rec=''"'' ;'; put 'when ('' '') rec=''0A''x;'; put 'when ('' '') rec=''0D''x;'; put 'when (''$'' ) rec=''$'' ;'; put 'when ('' '') rec=''09''x;'; put 'otherwise putlog "%str(WARN)ING: missing value for " entity=;'; put 'end;'; put 'rc =fput(fileid, substr(rec,1,1));'; put 'rc =fwrite(fileid);'; put 'end;'; put 'else do;'; put 'rc =fput(fileid,rec);'; put 'rc =fwrite(fileid);'; put 'end;'; put 'end;'; put 'end;'; put 'getout:'; put 'rc=fclose(filein);'; put 'rc=fclose(fileid);'; put 'run;'; put '%if &showlog=YES %then %do;'; put 'data _null_;'; put 'infile &fref lrecl=32767 end=last;'; put 'input;'; put 'if _n_=1 then putlog ''>>stpcodeBEGIN<<'';'; put 'putlog _infile_;'; put 'if last then putlog ''>>stpcodeEND<<'';'; put 'run;'; put '%end;'; put 'filename __getdoc clear;'; put '%if &outref=0 %then %do;'; put 'filename &fref clear;'; put '%end;'; put '%mend mm_getstpcode;'; put '%macro dc_getsettings();'; put '%global _program;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(syscc=&syscc on entry (&syswarningtext &syserrortext))'; put ')'; put '%if %length(&_PROGRAM)>1 %then %let root=&_program;'; put '%else %do;'; put '%global _metauser;'; put '%let _metauser=&sysuserid;'; put '/* to mimic a "real" _program we need to give a dummy role and stp name */'; put '%let root=/dummyRole/dummyName;'; put '%end;'; put '/* the DC precode is stored in the Admin folder in the root of'; put 'the project. Lets find that root. */'; put '%put &=root;'; put '%let root=%mf_getapploc();'; put '%put &=root;'; put '/* Now we know the root location we can retrieve the params */'; put '%let temploc=%sysfunc(pathname(work))/settings.txt;'; put '%mm_getstpcode(tree=&root/services/public'; put ',name=Data_Controller_Settings'; put ',outloc=&temploc'; put ')'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Unable to run getstpcode)'; put ')'; put 'filename _getsets "&temploc" lrecl=2000;'; put '/*'; put 'Do not use mp_include here - this puts a copy in every service, which creates'; put 'compilation problems when calling services from mp_include'; put '*/'; put '%inc _getsets/source2;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Problem running &sysmacroname (&syswarningtext &syserrortext))'; put ')'; put '%mend dc_getsettings;'; put '%macro mf_fmtdttm('; put ')/*/STORE SOURCE*/;'; put '%if "&sysver"="9.2" or "&sysver"="9.3"'; put 'or ("&sysver"="9.4" and "%substr(&SYSVLONG,9,1)" le "3")'; put 'or "%substr(&sysver,1,1)"="4"'; put 'or "%substr(&sysver,1,1)"="5"'; put '%then %do;DATETIME19.3%end;'; put '%else %do;E8601DT26.6%end;'; put '%mend mf_fmtdttm;'; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '%macro mp_init(prefix=SASJS'; put ')/*/STORE SOURCE*/;'; put '%if %symexist(SASJS_PREFIX) %then %return; /* only run once */'; put '%global'; put 'SASJS_PREFIX /* the ONLY hard-coded global macro variable in SASjs */'; put '&prefix._FUNCTIONS /* used in mcf_init() to track core function compilation */'; put '&prefix._INIT_NUM /* initialisation time as numeric */'; put '&prefix._INIT_DTTM /* initialisation time in E8601DT26.6 format */'; put '&prefix.WORK /* avoid typing %sysfunc(pathname(work)) every time */'; put ';'; put '%let sasjs_prefix=&prefix;'; put 'data _null_;'; put 'dttm=datetime();'; put 'call symputx("&sasjs_prefix._init_num",dttm,''g'');'; put 'call symputx("&sasjs_prefix._init_dttm",put(dttm,E8601DT26.6),''g'');'; put 'call symputx("&sasjs_prefix.work",pathname(''WORK''),''g'');'; put 'run;'; put 'options'; put 'compress=CHAR /* default is none so ensure we have something! */'; put 'datastmtchk=ALLKEYWORDS /* protection from overwriting input datasets */'; put 'errorcheck=STRICT /* catch errs in libname/filename statements */'; put 'fmterr /* ensure err when a format cannot be found */'; put 'mergenoby=%str(ERR)OR /* throw err when a merge has no BY variables */'; put 'missing=. /* changing this can cause hard to detect errs */'; put 'noquotelenmax /* avoid warnings for long strings */'; put 'noreplace /* avoid overwriting permanent datasets */'; put 'ps=max /* reduce log size slightly */'; put 'ls=max /* reduce log even more and avoid word truncation */'; put 'validmemname=COMPATIBLE /* avoid special characters etc in table names */'; put 'validvarname=V7 /* avoid special characters etc in variable names */'; put 'varinitchk=%str(ERR)OR /* avoid data mistakes from variable name typos */'; put 'varlenchk=%str(ERR)OR /* fail hard if truncation (data loss) can result */'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5" %then %do;'; put 'noautocorrect /* disallow misspelled procedure names */'; put 'dsoptions=note2err /* undocumented - convert bad NOTEs to ERRs */'; put '%end;'; put ';'; put '%mend mp_init;'; put '%macro mpeinit(fetch=YES);'; put '%global mpeinit'; put 'mpeadmins /* group with unrestricted Meditor access */'; put 'mpelocapprovals /* location for landing and staging files */'; put 'mpelib /* location of configuration tables for DC */'; put 'dc_repo_users /* location of user / group metadata */'; put 'dc_licence_key /* extracted in dc_getsettings */'; put 'dc_activation_key /* extracted in dc_getsettings */'; put 'dc_locale /* extracted in dc_getsettings */'; put 'dc_dttmtfmt /* can be overridden in dc_getsettings */'; put '_debug'; put ';'; put '%if &mpeinit=1 %then %return;'; put '%else %let mpeinit=1;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(Problem on service startup (&syswarningtext &syserrortext))'; put ')'; put '%mp_init()'; put '%if &fetch=YES %then %do;'; put '%webout(FETCH)'; put '%end;'; put '%global _CLIENTNAME;'; put '%mp_abort(iftrue= (&_CLIENTNAME=SAS Enterprise Guide)'; put ',mac=&_program..sas'; put ',msg=%str(Data Controller is a web app and should not be executed from EG)'; put ')'; put 'options urlencoding=utf8 nobomfile lrecl=32767;'; put '%let perf=%sysfunc(datetime());'; put '%put perfdiff: 0;'; put '%let dc_locale=SYSTEM; /* default if not set */'; put '/**'; put '* E8601DT26.6 has widest database support - but not all SAS flavours can'; put '* handle it. Override in the settings STP if needed.'; put '*/'; put 'data _null_;'; put 'dc_dttmtfmt=''"%sysfunc(datetime(),''!!"%mf_fmtdttm()"!!'')"dt'';'; put 'call symputx(''dc_dttmtfmt'',dc_dttmtfmt);'; put 'put dc_dttmtfmt=;'; put 'run;'; put '%put &=dc_dttmtfmt;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc prior to dc_getsettings)'; put ')'; put '%dc_getsettings()'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc after dc_getsettings)'; put ')'; put 'data _null_;'; put 'set &DC_LIBREF..mpe_config(where=('; put 'var_scope="DC"'; put 'and &dc_dttmtfmt lt tx_to'; put 'and var_active=1'; put '));'; put 'call symputx(var_name,var_value,''G'');'; put 'putlog var_name "=" var_value;'; put 'run;'; put '%let mpelib=&dc_libref;'; put '%let mpeadmins=&dc_admin_group;'; put '%let mpelocapprovals=&dc_staging_area;'; put '%let dc_repo_users=&dc_repo_users;'; put '%if &dc_locale ne SYSTEM %then %do;'; put 'options locale=&dc_locale;'; put '%end;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(Problem during compilation or with STP precode (&syswarningtext))'; put ')'; put '%mend mpeinit;'; put '%macro mf_mval(var);'; put '%if %symexist(&var) %then %do;'; put '%superq(&var)'; put '%end;'; put '%mend mf_mval;'; put '%macro mf_trimstr(basestr,trimstr);'; put '%local baselen trimlen trimval;'; put '/* return if basestr is shorter than trimstr (or 0) */'; put '%let baselen=%length(%superq(basestr));'; put '%let trimlen=%length(%superq(trimstr));'; put '%if &baselen < &trimlen or &baselen=0 %then %return;'; put '/* obtain the characters from the end of basestr */'; put '%let trimval=%qsubstr(%superq(basestr)'; put ',%length(%superq(basestr))-&trimlen+1'; put ',&trimlen);'; put '/* compare and if matching, chop it off! */'; put '%if %superq(basestr)=%superq(trimstr) %then %do;'; put '%return;'; put '%end;'; put '%else %if %superq(trimval)=%superq(trimstr) %then %do;'; put '%qsubstr(%superq(basestr),1,%length(%superq(basestr))-&trimlen)'; put '%end;'; put '%else %do;'; put '&basestr'; put '%end;'; put '%mend mf_trimstr;'; put '%macro mf_getplatform(switch'; put ')/*/STORE SOURCE*/;'; put '%local a b c;'; put '%if &switch.NONE=NONE %then %do;'; put '%if %symexist(sasjsprocessmode) %then %do;'; put '%if &sasjsprocessmode=Stored Program %then %do;'; put 'SASJS'; put '%return;'; put '%end;'; put '%end;'; put '%if %symexist(sysprocessmode) %then %do;'; put '%if "&sysprocessmode"="SAS Object Server"'; put 'or "&sysprocessmode"= "SAS Compute Server" %then %do;'; put 'SASVIYA'; put '%end;'; put '%else %if "&sysprocessmode"="SAS Stored Process Server"'; put 'or "&sysprocessmode"="SAS Workspace Server"'; put '%then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if %symexist(_metaport) or %symexist(_metauser) %then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if &switch=SASSTUDIO %then %do;'; put '/* return the version of SAS Studio else 0 */'; put '%if %mf_mval(_CLIENTAPP)=%str(SAS Studio) %then %do;'; put '%let a=%mf_mval(_CLIENTVERSION);'; put '%let b=%scan(&a,1,.);'; put '%if %eval(&b >2) %then %do;'; put '&b'; put '%end;'; put '%else 0;'; put '%end;'; put '%else 0;'; put '%end;'; put '%else %if &switch=VIYARESTAPI %then %do;'; put '%mf_trimstr(%sysfunc(getoption(servicesbaseurl)),/)'; put '%end;'; put '%mend mf_getplatform;'; put '%macro mpeterm();'; put '%local oldloc;'; put 'data _null_;'; put 'if symexist(''SYSPRINTTOLOG'') then oldloc=symget(''SYSPRINTTOLOG'');'; put 'else oldloc=getoption(''LOG'');'; put 'if subpad(oldloc,1,1) not in (''"'',"''",'' '') then oldloc=quote(cats(oldloc));'; put 'call symputx(''oldloc'',oldloc,''l'');'; put 'run;'; put '%if %length(&oldloc)>0 %then %do;'; put 'proc printto log=log;'; put 'run;'; put 'data _null_;'; put 'infile &oldloc;'; put 'input; putlog _infile_;'; put 'run;'; put '%end;'; put '%if %sysfunc(exist(&dc_libref..mpe_requests)) and %mf_getplatform() ne SASVIYA'; put '%then %do;'; put 'data ;'; put 'if 0 then set &dc_libref..mpe_requests;'; put 'request_dttm=%sysfunc(datetime());'; put 'request_user="%mf_getuser()";'; put 'request_service="%scan(&_program,-2,/)/%scan(&_program,-1,/)";'; put 'request_params='''';'; put 'output;stop;'; put 'proc append base=&dc_libref..mpe_requests data=&syslast force nowarn;'; put 'run;'; put '%end;'; put '%mend mpeterm;'; put '%macro mm_getGroups('; put 'user='; put ',outds=work.mm_getGroups'; put ',repo=foundation'; put ',mDebug=0'; put ')/*/STORE SOURCE*/;'; put '%local mD oldrepo;'; put '%let oldrepo=%sysfunc(getoption(metarepository));'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing mm_getGroups.sas;'; put '%&mD.put _local_;'; put '/* on some sites, user / group info is in a different metadata repo to the'; put 'default */'; put '%if &oldrepo ne &repo %then %do;'; put 'options metarepository=&repo;'; put '%end;'; put '%if %length(&user)=0 %then %do;'; put 'data &outds (keep=groupuri groupname groupdesc);'; put 'length groupuri groupname groupdesc group_or_role $256;'; put 'call missing(of _all_);'; put 'i+1;'; put 'do while'; put '(metadata_getnobj("omsobj:IdentityGroup?@Id contains ''.''",i,groupuri)>0);'; put 'rc=metadata_getattr(groupuri, "Name", groupname);'; put 'rc=metadata_getattr(groupuri, "Desc", groupdesc);'; put 'rc=metadata_getattr(groupuri,"PublicType",group_or_role);'; put 'if Group_or_Role = ''UserGroup'' then output;'; put 'i+1;'; put 'end;'; put 'run;'; put '%end;'; put '%else %do;'; put 'data &outds (keep=groupuri groupname groupdesc);'; put 'length uri groupuri groupname groupdesc group_or_role $256;'; put 'call missing(of _all_);'; put 'rc=metadata_getnobj("omsobj:Person?@Name=''&user''",1,uri);'; put 'if rc<=0 then do;'; put 'putlog "%str(WARN)ING: rc=" rc "&user not found "'; put '", or there was an issue reading the repository.";'; put 'stop;'; put 'end;'; put 'a=1;'; put 'grpassn=metadata_getnasn(uri,"IdentityGroups",a,groupuri);'; put 'if grpassn in (-3,-4) then do;'; put 'putlog "%str(WARN)ING: No metadata groups found for &user";'; put 'output;'; put 'end;'; put 'else do while (grpassn > 0);'; put 'rc=metadata_getattr(groupuri, "Name", groupname);'; put 'rc=metadata_getattr(groupuri, "Desc", groupdesc);'; put 'a+1;'; put 'rc=metadata_getattr(groupuri,"PublicType",group_or_role);'; put 'if Group_or_Role = ''UserGroup'' then output;'; put 'grpassn=metadata_getnasn(uri,"IdentityGroups",a,groupuri);'; put 'end;'; put 'run;'; put '%end;'; put '%if &oldrepo ne &repo %then %do;'; put 'options metarepository=&oldrepo;'; put '%end;'; put '%mend mm_getGroups;'; put '%macro dc_getusergroups(user=,outds=mm_getgroups);'; put '%global dc_repo_users;'; put '%if &dc_repo_users= %then %let dc_repo_users=foundation;'; put '%mm_getgroups(user=&user,outds=&outds,repo=&dc_repo_users)'; put '%mend dc_getusergroups;'; put '%macro mpe_getgroups(user=,outds=);'; put '%if not %symexist(dc_repo_users) %then %let dc_repo_users=foundation;'; put '%dc_getusergroups(user=&user,outds=&outds)'; put 'data;'; put 'length groupname groupdesc $256;'; put 'set &dc_libref..mpe_groups;'; put 'where &dc_dttmtfmt. lt tx_to;'; put 'where also upcase(user_name)="%upcase(&user)";'; put 'groupname=group_name;'; put 'groupdesc=group_desc;'; put 'keep groupname groupdesc;'; put 'run;'; put 'data &outds;'; put 'set &syslast &outds(keep=groupname groupdesc);'; put 'run;'; put '%mend mpe_getgroups;'; put '* SAS Macros end;'; put '* SAS Includes start;'; put '* SAS Includes end;'; put '* Binary Files start;'; put '* Binary Files end;'; put '* ServiceInit start;'; put 'options noquotelenmax ps=max;'; put '/* create dummy macros to prevent stpbegin / stpend from corrupting sessions */'; put '%macro stpbegin();'; put '%put NOTE: the STPBEGIN macro should not be used for web apps!;'; put '%mend stpbegin;'; put '%macro stpend();'; put '%put NOTE: the STPEND macro should not be used for web apps!;'; put '%mend stpend;'; put '* ServiceInit end;'; put '* Service start;'; put '/**'; put '@file startupservice.sas'; put '@brief List the libraries and tables the mp-editor user can access'; put '@details If user is in a control group (&mpeadmins, configured in mpeinit.sas)'; put 'then they have access to all libraries / tables. Otherwise a join is made'; put 'to the &mpelib..mpe_security table.'; put '

SAS Macros

'; put '@li mf_getuser.sas'; put '@li mpe_getgroups.sas'; put '@li mp_abort.sas'; put '@li mpeinit.sas'; put '@version 9.2'; put '@author 4GL Apps Ltd'; put '@copyright 4GL Apps Ltd. This code may only be used within Data Controller'; put 'and may not be re-distributed or re-sold without the express permission of'; put '4GL Apps Ltd.'; put '**/'; put '%mpeinit()'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(Issue on startup in startupService)'; put ')'; put '%let userid=%mf_getuser();'; put '%put userid is &userid;'; put '%mpe_getgroups(user=&userid,outds=groups)'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(Issue with Groups syscc=&syscc for user &userid)'; put ')'; put '/* check if user is an admin */'; put '%let admin_check=0;'; put 'proc sql noprint;'; put 'select count(*) into: admin_check'; put 'from groups'; put 'where groupname="&mpeadmins";'; put '/* check if user is registered or not */'; put '%let isRegistered=0;'; put 'select count(*) into: isregistered'; put 'from &mpelib..mpe_users'; put 'where user_id="&userid";'; put '/* get number of registered users */'; put '%let registerCount=0;'; put 'select count(*) into: registercount'; put 'from &mpelib..mpe_users'; put 'where last_seen_dt>%sysfunc(today())-365;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(Problem accessing &mpelib..mpe_users table)'; put ')'; put '%global dc_restrict_editrecord;'; put 'data work.globvars;'; put 'dclib="&mpelib";'; put 'sas9lineage_enabled=1;'; put 'isadmin=&admin_check;'; put 'isregistered=&isregistered;'; put 'registercount=®isterCount;'; put 'dc_admin_group="&mpeadmins";'; put '/* fetched from mpe_config in dc_getsettings */'; put 'licence_key="&dc_licence_key";'; put 'activation_key="&dc_activation_key";'; put 'dc_restrict_editrecord="&dc_restrict_editrecord";'; put 'run;'; put '%macro mstp_mpeditorstartup();'; put 'data _null_;'; put 'putlog "entering &sysmacroname";'; put 'run;'; put 'proc sql noprint;'; put '/* update last seen, if seen */'; put '%if &isregistered>0 %then %do;'; put 'update &mpelib..mpe_users'; put 'set last_seen_dt=%sysfunc(today())'; put 'where user_id="&userid";'; put '%end;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(Problem updating &mpelib..mpe_users table)'; put ')'; put '%local all_cnt;'; put 'select count(*) into: all_cnt'; put 'from &mpelib..mpe_security'; put 'where &dc_dttmtfmt. lt tx_to'; put 'and ACCESS_LEVEL in (''EDIT'')'; put 'and libref=''*ALL*'''; put 'and SAS_GROUP in (select groupname from groups);'; put '%if &admin_check >0 or &all_cnt>0 %then %do;'; put 'create table sasDatasets as'; put 'select distinct libref, dsn'; put 'from &mpelib..mpe_tables'; put 'where &dc_dttmtfmt. lt tx_to'; put 'order by 1;'; put '%end;'; put '%else %do;'; put 'create table sasDatasets as'; put 'select distinct a.libref,a.dsn'; put 'from &mpelib..mpe_tables a'; put 'left join &mpelib..mpe_security b'; put 'on a.libref=b.libref'; put 'and a.dsn=b.dsn'; put 'where &dc_dttmtfmt. lt a.tx_to'; put 'and &dc_dttmtfmt. lt b.tx_to'; put 'and b.ACCESS_LEVEL in (''EDIT'')'; put 'and b.SAS_GROUP in (select groupname from groups)'; put 'order by 1;'; put '%end;'; put '%mend mstp_mpeditorstartup;'; put '%mstp_mpeditorstartup()'; put 'create table saslibs as'; put 'select distinct libref'; put 'from &syslast;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(issue with security validation)'; put ')'; put 'proc sql;'; put 'create table work.xlmaps as'; put 'select distinct a.XLMAP_ID'; put ',b.XLMAP_DESCRIPTION'; put ',coalescec(b.XLMAP_TARGETLIBDS,"&mpelib..MPE_XLMAP_DATA")'; put 'as XLMAP_TARGETLIBDS'; put 'from &mpelib..MPE_XLMAP_RULES a'; put 'left join &mpelib..MPE_XLMAP_INFO(where=(&dc_dttmtfmt. lt tx_to)) b'; put 'on a.XLMAP_ID=b.XLMAP_ID'; put 'where &dc_dttmtfmt. lt a.tx_to;'; put '/* we don''t want the XLMAP target datasets to be directly editable */'; put 'delete from sasdatasets'; put 'where cats(libref,''.'',dsn) in (select XLMAP_TARGETLIBDS from xlmaps);'; put '%webout(OPEN)'; put '%webout(OBJ,sasDatasets)'; put '%webout(OBJ,saslibs)'; put '%webout(OBJ,globvars)'; put '%webout(ARR,xlmaps)'; put '%webout(CLOSE)'; put '%mpeterm()'; put '* Service end;'; run; %mm_createwebservice(path=&appLoc/&path, name=&service, code=sascode, server=&serverName, replace=yes) filename sascode clear; %let service=validatefilter; filename sascode temp lrecl=32767; data _null_; file sascode; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '/**'; put '@file mp_jsonout.sas'; put '@brief Writes JSON in SASjs format to a fileref'; put '@details This macro can be used to OPEN a JSON stream and send one or more'; put 'tables as arrays of rows, where each row can be an object or a nested array.'; put 'There are two engines available - DATASTEP or PROCJSON.'; put 'PROC JSON is fast but will produce errs like the ones below if'; put 'special chars are encountered.'; put '> (ERR)OR: Some code points did not transcode.'; put '> An object or array close is not valid at this point in the JSON text.'; put '> Date value out of range'; put 'If this happens, try running with ENGINE=DATASTEP.'; put 'The DATASTEP engine is used to handle special SAS missing numerics, and'; put 'can also convert entire datasets to formatted values. Output JSON is always'; put 'in UTF-8.'; put 'Usage:'; put 'filename tmp temp;'; put 'data class; set sashelp.class;run;'; put '%mp_jsonout(OPEN,jref=tmp)'; put '%mp_jsonout(OBJ,class,jref=tmp)'; put '%mp_jsonout(OBJ,class,dslabel=class2,jref=tmp,showmeta=Y)'; put '%mp_jsonout(CLOSE,jref=tmp)'; put 'data _null_;'; put 'infile tmp;'; put 'input;putlog _infile_;'; put 'run;'; put 'If you are building web apps with SAS then you are strongly encouraged to use'; put 'the mX_createwebservice macros in combination with the'; put '[sasjs adapter](https://github.com/sasjs/adapter).'; put 'For more information see https://sasjs.io'; put '@param [in] action Valid values:'; put '@li OPEN - opens the JSON'; put '@li OBJ - sends a table with each row as an object'; put '@li ARR - sends a table with each row in an array'; put '@li CLOSE - closes the JSON'; put '@param [in] ds The dataset to send. Must be a work table.'; put '@param [out] jref= (_webout) The fileref to which to send the JSON'; put '@param [out] dslabel= The name to give the table in the exported JSON'; put '@param [in] fmt= (Y) Whether to keep (Y) or strip (N) formats from the table'; put '@param [in] engine= (DATASTEP) Which engine to use to send the JSON. Options:'; put '@li PROCJSON (default)'; put '@li DATASTEP (more reliable when data has non standard characters)'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to JSON'; put '

Related Files

'; put '@li mp_ds2fmtds.sas'; put '@version 9.2'; put '@author Allan Bowe'; put '@source https://github.com/sasjs/core'; put '**/'; put '%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y'; put ',engine=DATASTEP'; put ',missing=NULL'; put ',showmeta=N'; put ',maxobs=MAX'; put ')/*/STORE SOURCE*/;'; put '%local tempds colinfo fmtds i numcols numobs stmt_obs lastobs optval'; put 'tmpds1 tmpds2 tmpds3 tmpds4;'; put '%let numcols=0;'; put '%if &maxobs ne MAX %then %let stmt_obs=%str(if _n_>&maxobs then stop;);'; put '%if &action=OPEN %then %do;'; put 'options nobomfile;'; put 'data _null_;file &jref encoding=''utf-8'' lrecl=200;'; put 'put ''{"PROCESSED_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''"'';'; put 'run;'; put '%end;'; put '%else %if (&action=ARR or &action=OBJ) %then %do;'; put '/* force variable names to always be uppercase in the JSON */'; put 'options validvarname=upcase;'; put '/* To avoid issues with _webout on EBI - such as encoding diffs and truncation'; put '(https://support.sas.com/kb/49/325.html) we use temporary files */'; put 'filename _sjs1 temp lrecl=200 ;'; put 'data _null_; file _sjs1 encoding=''utf-8'';'; put 'put ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":";'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjs1 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs1 clear;'; put '/* grab col defs */'; put 'proc contents noprint data=&ds'; put 'out=_data_(keep=name type length format formatl formatd varnum label);'; put 'run;'; put '%let colinfo=%scan(&syslast,2,.);'; put 'proc sort data=&colinfo;'; put 'by varnum;'; put 'run;'; put '/* move meta to mac vars */'; put 'data &colinfo;'; put 'if _n_=1 then call symputx(''numcols'',nobs,''l'');'; put 'set &colinfo end=last nobs=nobs;'; put 'name=upcase(name);'; put '/* fix formats */'; put 'if type=2 or type=6 then do;'; put 'typelong=''char'';'; put 'length fmt $49.;'; put 'if format='''' then fmt=cats(''$'',length,''.'');'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else fmt=cats(format,formatl,''.'');'; put 'end;'; put 'else do;'; put 'typelong=''num'';'; put 'if format='''' then fmt=''best.'';'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else if formatd=0 then fmt=cats(format,formatl,''.'');'; put 'else fmt=cats(format,formatl,''.'',formatd);'; put 'end;'; put '/* 32 char unique name */'; put 'newname=''sasjs''!!substr(cats(put(md5(name),$hex32.)),1,27);'; put 'call symputx(cats(''name'',_n_),name,''l'');'; put 'call symputx(cats(''newname'',_n_),newname,''l'');'; put 'call symputx(cats(''length'',_n_),length,''l'');'; put 'call symputx(cats(''fmt'',_n_),fmt,''l'');'; put 'call symputx(cats(''type'',_n_),type,''l'');'; put 'call symputx(cats(''typelong'',_n_),typelong,''l'');'; put 'call symputx(cats(''label'',_n_),coalescec(label,name),''l'');'; put '/* overwritten when fmt=Y and a custom format exists in catalog */'; put 'if typelong=''num'' then call symputx(cats(''fmtlen'',_n_),200,''l'');'; put 'else call symputx(cats(''fmtlen'',_n_),min(32767,ceil((length+10)*1.5)),''l'');'; put 'run;'; put '%let tempds=%substr(_%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql;'; put 'select count(*) into: lastobs from &ds;'; put '%if &maxobs ne MAX %then %let lastobs=%sysfunc(min(&lastobs,&maxobs));'; put '%if &engine=PROCJSON %then %do;'; put '%if &missing=STRING %then %do;'; put '%put &sysmacroname: Special Missings not supported in proc json.;'; put '%put &sysmacroname: Switching to DATASTEP engine;'; put '%goto datastep;'; put '%end;'; put 'data &tempds;'; put 'set &ds;'; put '&stmt_obs;'; put '%if &fmt=N %then format _numeric_ best32.;;'; put '/* PRETTY is necessary to avoid line truncation in large files */'; put 'filename _sjs2 temp lrecl=131068 encoding=''utf-8'';'; put 'proc json out=_sjs2 pretty'; put '%if &action=ARR %then nokeys ;'; put ';export &tempds / nosastags fmtnumeric;'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs2 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs2 clear;'; put '%end;'; put '%else %if &engine=DATASTEP %then %do;'; put '%datastep:'; put '%if %sysfunc(exist(&ds)) ne 1 & %sysfunc(exist(&ds,VIEW)) ne 1'; put '%then %do;'; put '%put &sysmacroname: &ds NOT FOUND!!!;'; put '%return;'; put '%end;'; put '%if &fmt=Y %then %do;'; put '/**'; put '* Extract format definitions'; put '* First, by getting library locations from dictionary.formats'; put '* Then, by exporting the width using proc format'; put '* Cannot use maxw from sashelp.vformat as not always populated'; put '* Cannot use fmtinfo() as not supported in all flavours'; put '*/'; put '%let tmpds1=%substr(fmtsum%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds2=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds3=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds4=%substr(col%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql noprint;'; put 'create table &tmpds1 as'; put 'select cats(libname,''.'',memname) as FMTCAT,'; put 'FMTNAME'; put 'from dictionary.formats'; put 'where fmttype=''F'' and libname is not null'; put 'and fmtname in (select format from &colinfo where format is not null)'; put 'order by 1;'; put 'create table &tmpds2('; put 'FMTNAME char(32),'; put 'LENGTH num'; put ');'; put '%local catlist cat fmtlist i;'; put 'select distinct fmtcat into: catlist separated by '' '' from &tmpds1;'; put '%do i=1 %to %sysfunc(countw(&catlist,%str( )));'; put '%let cat=%scan(&catlist,&i,%str( ));'; put 'proc sql;'; put 'select distinct fmtname into: fmtlist separated by '' '''; put 'from &tmpds1 where fmtcat="&cat";'; put 'proc format lib=&cat cntlout=&tmpds3(keep=fmtname length);'; put 'select &fmtlist;'; put 'run;'; put 'proc sql;'; put 'insert into &tmpds2 select distinct fmtname,length from &tmpds3;'; put '%end;'; put 'proc sql;'; put 'create table &tmpds4 as'; put 'select a.*, b.length as MAXW'; put 'from &colinfo a'; put 'left join &tmpds2 b'; put 'on cats(a.format)=cats(upcase(b.fmtname))'; put 'order by a.varnum;'; put 'data _null_;'; put 'set &tmpds4;'; put 'if not missing(maxw);'; put 'call symputx('; put 'cats(''fmtlen'',_n_),'; put '/* vars need extra padding due to JSON escaping of special chars */'; put 'min(32767,ceil((max(length,maxw)+10)*1.5))'; put ',''l'''; put ');'; put 'run;'; put '/* configure varlenchk - as we are explicitly shortening the variables */'; put '%let optval=%sysfunc(getoption(varlenchk));'; put 'options varlenchk=NOWARN;'; put 'data _data_(compress=char);'; put '/* shorten the new vars */'; put 'length'; put '%do i=1 %to &numcols;'; put '&&name&i $&&fmtlen&i'; put '%end;'; put ';'; put '/* rename on entry */'; put 'set &ds(rename=('; put '%do i=1 %to &numcols;'; put '&&name&i=&&newname&i'; put '%end;'; put '));'; put '&stmt_obs;'; put 'drop'; put '%do i=1 %to &numcols;'; put '&&newname&i'; put '%end;'; put ';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=num %then %do;'; put '&&name&i=cats(put(&&newname&i,&&fmt&i));'; put '%end;'; put '%else %do;'; put '&&name&i=put(&&newname&i,&&fmt&i);'; put '%end;'; put '%end;'; put 'if _error_ then do;'; put 'call symputx(''syscc'',1012);'; put 'stop;'; put 'end;'; put 'run;'; put '%let fmtds=&syslast;'; put 'options varlenchk=&optval;'; put '%end;'; put 'proc format; /* credit yabwon for special null removal */'; put 'value bart (default=40)'; put '%if &missing=NULL %then %do;'; put '._ - .z = null'; put '%end;'; put '%else %do;'; put '._ = [quote()]'; put '. = null'; put '.a - .z = [quote()]'; put '%end;'; put 'other = [best.];'; put 'data &tempds;'; put 'attrib _all_ label='''';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'length &&name&i $&&fmtlen&i...;'; put 'format &&name&i $&&fmtlen&i...;'; put '%end;'; put '%end;'; put '%if &fmt=Y %then %do;'; put 'set &fmtds;'; put '%end;'; put '%else %do;'; put 'set &ds;'; put '%end;'; put '&stmt_obs;'; put 'format _numeric_ bart.;'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'if findc(&&name&i,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put '&&name&i=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,&&name&i)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else &&name&i=quote(cats(&&name&i));'; put '%end;'; put '%end;'; put 'run;'; put 'filename _sjs3 temp lrecl=131068 ;'; put 'data _null_;'; put 'file _sjs3 encoding=''utf-8'';'; put 'if _n_=1 then put "[";'; put 'set &tempds;'; put 'if _n_>1 then put "," @; put'; put '%if &action=ARR %then "[" ; %else "{" ;'; put '%do i=1 %to &numcols;'; put '%if &i>1 %then "," ;'; put '%if &action=OBJ %then """&&name&i"":" ;'; put '"&&name&i"n /* name literal for reserved variable names */'; put '%end;'; put '%if &action=ARR %then "]" ; %else "}" ; ;'; put '/* close out the table */'; put 'data _null_;'; put 'file _sjs3 mod encoding=''utf-8'';'; put 'put '']'';'; put 'run;'; put 'data _null_;'; put 'infile _sjs3 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs3 clear;'; put '%end;'; put 'proc sql;'; put 'drop table &colinfo, &tempds;'; put '%if %substr(&showmeta,1,1)=Y %then %do;'; put 'filename _sjs4 temp lrecl=131068 encoding=''utf-8'';'; put 'data _null_;'; put 'file _sjs4;'; put 'length label $350;'; put 'put ", ""$%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":{""vars"":{";'; put 'do i=1 to &numcols;'; put 'name=quote(trim(symget(cats(''name'',i))));'; put 'format=quote(trim(symget(cats(''fmt'',i))));'; put 'label=quote(prxchange(''s/\\/\\\\/'',-1,trim(symget(cats(''label'',i)))));'; put 'length=quote(trim(symget(cats(''length'',i))));'; put 'type=quote(trim(symget(cats(''typelong'',i))));'; put 'if i>1 then put "," @@;'; put 'put name '':{"format":'' format '',"label":'' label'; put ''',"length":'' length '',"type":'' type ''}'';'; put 'end;'; put 'put ''}}'';'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs4 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs4 clear;'; put '%end;'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put 'data _null_; file &jref encoding=''utf-8'' mod ;'; put 'put "}";'; put 'run;'; put '%end;'; put '%mend mp_jsonout;'; put '/**'; put '@file mm_webout.sas'; put '@brief Send data to/from SAS Stored Processes'; put '@details This macro should be added to the start of each Stored Process,'; put '**immediately** followed by a call to:'; put '%mm_webout(FETCH)'; put 'This will read all the input data and create same-named SAS datasets in the'; put 'WORK library. You can then insert your code, and send data back using the'; put 'following syntax:'; put 'data some datasets; * make some data ;'; put 'retain some columns;'; put 'run;'; put '%mm_webout(OPEN)'; put '%mm_webout(ARR,some) * Array format, fast, suitable for large tables ;'; put '%mm_webout(OBJ,datasets) * Object format, easier to work with ;'; put 'Finally, wrap everything up send some helpful system variables too'; put '%mm_webout(CLOSE)'; put '@param [in] action Either FETCH, OPEN, ARR, OBJ or CLOSE'; put '@param [in] ds The dataset to send back to the frontend'; put '@param [out] dslabel= Value to use instead of table name for sending to JSON'; put '@param [in] fmt= (N) Setting Y converts all vars to their formatted values'; put '@param [out] fref= (_webout) The fileref to which to write the JSON'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] workobs= (0) When set to a positive integer, will create a new'; put 'output object (WORK) which contains this number of observations from all'; put 'tables in the WORK library.'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to output JSON'; put '

SAS Macros

'; put '@li mp_jsonout.sas'; put '

Related Macros

'; put '@li ms_webout.sas'; put '@li mv_webout.sas'; put '@version 9.3'; put '@author Allan Bowe'; put '**/'; put '%macro mm_webout(action,ds,dslabel=,fref=_webout,fmt=N,missing=NULL'; put ',showmeta=N,maxobs=MAX,workobs=0'; put ');'; put '%global _webin_file_count _webin_fileref1 _webin_name1 _program _debug'; put 'sasjs_tables;'; put '%local i tempds jsonengine;'; put '/* see https://github.com/sasjs/core/issues/41 */'; put '%if "%upcase(&SYSENCODING)" ne "UTF-8" %then %let jsonengine=PROCJSON;'; put '%else %let jsonengine=DATASTEP;'; put '%if &action=FETCH %then %do;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'options mprint notes mprintnest;'; put '%end;'; put '%let _webin_file_count=%eval(&_webin_file_count+0);'; put '/* now read in the data */'; put '%do i=1 %to &_webin_file_count;'; put '%if &_webin_file_count=1 %then %do;'; put '%let _webin_fileref1=&_webin_fileref;'; put '%let _webin_name1=&_webin_name;'; put '%end;'; put 'data _null_;'; put 'infile &&_webin_fileref&i termstr=crlf;'; put 'input;'; put 'call symputx(''input_statement'',_infile_);'; put 'putlog "&&_webin_name&i input statement: " _infile_;'; put 'stop;'; put 'data &&_webin_name&i;'; put 'infile &&_webin_fileref&i firstobs=2 dsd termstr=crlf encoding=''utf-8'';'; put 'input &input_statement;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'if _n_<20 then putlog _infile_;'; put '%end;'; put 'run;'; put '%let sasjs_tables=&sasjs_tables &&_webin_name&i;'; put '%end;'; put '%end;'; put '%else %if &action=OPEN %then %do;'; put '/* fix encoding */'; put 'OPTIONS NOBOMFILE;'; put '/**'; put '* check xengine type to avoid the below err message:'; put '* > Function is only valid for filerefs using the CACHE access method.'; put '*/'; put 'data _null_;'; put 'set sashelp.vextfl(where=(fileref="_WEBOUT"));'; put 'if xengine=''STREAM'' then do;'; put 'rc=stpsrv_header(''Content-type'',"text/html; encoding=utf-8");'; put 'end;'; put 'run;'; put '/* setup json */'; put 'data _null_;file &fref encoding=''utf-8'';'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutBEGIN<<'';'; put '%end;'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'run;'; put '%end;'; put '%else %if &action=ARR or &action=OBJ %then %do;'; put '%mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt,jref=&fref'; put ',engine=&jsonengine,missing=&missing,showmeta=&showmeta,maxobs=&maxobs'; put ')'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put '/* To avoid issues with _webout on EBI we use a temporary file */'; put 'filename _sjsref temp lrecl=131068;'; put '%if %str(&workobs) > 0 %then %do;'; put '/* if debug mode, send back first XX records of each work table also */'; put 'data;run;%let tempds=%scan(&syslast,2,.);'; put 'ods output Members=&tempds;'; put 'proc datasets library=WORK memtype=data;'; put '%local wtcnt;%let wtcnt=0;'; put 'data _null_;'; put 'set &tempds;'; put 'if not (upcase(name) =:"DATA"); /* ignore temp datasets */'; put 'i+1;'; put 'call symputx(cats(''wt'',i),name,''l'');'; put 'call symputx(''wtcnt'',i,''l'');'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put ",""WORK"":{";'; put '%do i=1 %to &wtcnt;'; put '%let wt=&&wt&i;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'dsid=open("WORK.&wt",''is'');'; put 'nlobs=attrn(dsid,''NLOBS'');'; put 'nvars=attrn(dsid,''NVARS'');'; put 'rc=close(dsid);'; put 'if &i>1 then put '',''@;'; put 'put " ""&wt"" : {";'; put 'put ''"nlobs":'' nlobs;'; put 'put '',"nvars":'' nvars;'; put '%mp_jsonout(OBJ,&wt,jref=_sjsref,dslabel=first10rows,showmeta=Y'; put ',maxobs=&workobs'; put ')'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put '%end;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put 'run;'; put '%end;'; put '/* close off json */'; put 'data _null_;file _sjsref mod encoding=''utf-8'';'; put 'length SYSPROCESSNAME syserrortext syswarningtext autoexec $512;'; put 'put ",""_DEBUG"" : ""&_debug"" ";'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'autoexec=quote(urlencode(trim(getoption(''autoexec''))));'; put 'put '',"AUTOEXEC" : '' autoexec;'; put 'put ",""MF_GETUSER"" : ""%mf_getuser()"" ";'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'put ",""SYSENCODING"" : ""&sysencoding"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSPROCESSID"" : ""&SYSPROCESSID"" ";'; put 'put ",""SYSPROCESSMODE"" : ""&SYSPROCESSMODE"" ";'; put 'SYSPROCESSNAME=quote(urlencode(cats(SYSPROCESSNAME)));'; put 'put ",""SYSPROCESSNAME"" : " SYSPROCESSNAME;'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put '',"SYSWARNINGTEXT" : '' syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'length memsize $32;'; put 'memsize="%sysfunc(INPUTN(%sysfunc(getoption(memsize)), best.),sizekmg.)";'; put 'memsize=quote(cats(memsize));'; put 'put '',"MEMSIZE" : '' memsize;'; put 'put "}" @;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutEND<<'';'; put '%end;'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjsref lrecl=1 recfm=n;'; put 'file &fref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjsref clear;'; put '%end;'; put '%mend mm_webout;'; put '%macro webout(action,ds,dslabel=,fmt=,missing=NULL,showmeta=NO,maxobs=MAX);'; put '%mm_webout(&action,ds=&ds,dslabel=&dslabel,fmt=&fmt'; put ',missing=&missing'; put ',showmeta=&showmeta'; put ',maxobs=&maxobs'; put ') %mend;'; put '/* provide additional debug info */'; put '%global _program;'; put '%put &=syscc;'; put '%put user=%mf_getuser();'; put '%put pgm=&_program;'; put '%put timestamp=%sysfunc(datetime(),datetime19.);'; put '* Service Variables start;'; put '* Service Variables end;'; put '* SAS Macros start;'; put '%macro mf_getapploc(pgm);'; put '%if "&pgm"="" %then %do;'; put '%if %symexist(_program) %then %let pgm=&_program;'; put '%else %do;'; put '%put &sysmacroname: No value provided and no _program variable available;'; put '%return;'; put '%end;'; put '%end;'; put '%local root;'; put '/**'; put '* First check we are not in the tests/macros folder (which has no subfolders)'; put '* or specifically in the testsetup or testteardown services'; put '*/'; put '%if %index(&pgm,/tests/macros/)'; put 'or %index(&pgm,/tests/testsetup)'; put 'or %index(&pgm,/tests/testteardown)'; put '%then %do;'; put '%let root=%substr(&pgm,1,%index(&pgm,/tests)-1);'; put '&root'; put '%return;'; put '%end;'; put '/**'; put '* Next, move up two levels to avoid matches on subfolder or service name'; put '*/'; put '%let root=%substr(&pgm,1,%length(&pgm)-%length(%scan(&pgm,-1,/))-1);'; put '%let root=%substr(&root,1,%length(&root)-%length(%scan(&root,-1,/))-1);'; put '%if %index(&root,/tests/) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/tests/)-1);'; put '%end;'; put '%else %if %index(&root,/services) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/services)-1);'; put '%end;'; put '%else %if %index(&root,/jobs) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/jobs)-1);'; put '%end;'; put '%else %put &sysmacroname: Could not find an app location from &pgm;'; put '&root'; put '%mend mf_getapploc ;'; put '%macro mf_getuniquefileref(prefix=_,maxtries=1000,lrecl=32767);'; put '%local rc fname;'; put '%if &prefix=0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%end;'; put '%else %do;'; put '%local x len;'; put '%let len=%eval(8-%length(&prefix));'; put '%let x=0;'; put '%do x=0 %to &maxtries;'; put '%let fname=&prefix%substr(%sysfunc(ranuni(0)),3,&len);'; put '%if %sysfunc(fileref(&fname)) > 0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%return;'; put '%end;'; put '%end;'; put '%put unable to find available fileref after &maxtries attempts;'; put '%end;'; put '%mend mf_getuniquefileref;'; put '%macro mp_abort(mac=mp_abort.sas, type=, msg=, iftrue=%str(1=1)'; put ', errds=work.mp_abort_errds'; put ', mode=REGULAR'; put ')/*/STORE SOURCE*/;'; put '%global sysprocessmode sysprocessname sasjs_stpsrv_header_loc sasjsprocessmode;'; put '%local fref fid i;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%put NOTE: /// mp_abort macro executing //;'; put '%if %length(&mac)>0 %then %put NOTE- called by &mac;'; put '%put NOTE - &msg;'; put '%if %symexist(_SYSINCLUDEFILEDEVICE)'; put '/* abort cancel FILE does not restart outside the INCLUDE on Viya 3.5 */'; put 'and %superq(SYSPROCESSNAME) ne %str(Compute Server)'; put '%then %do;'; put '%if "*&_SYSINCLUDEFILEDEVICE*" ne "**" %then %do;'; put 'data &errds;'; put 'iftrue=''1=1'';'; put 'length mac $100 msg $5000;'; put 'mac=symget(''mac'');'; put 'msg=symget(''msg'');'; put 'run;'; put 'data _null_;'; put 'abort cancel FILE;'; put 'run;'; put '%return;'; put '%end;'; put '%end;'; put '/* Web App Context */'; put '%if %symexist(_PROGRAM)'; put 'or %superq(SYSPROCESSNAME) = %str(Compute Server)'; put 'or &mode=INCLUDE'; put '%then %do;'; put 'options obs=max replace mprint;'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5"'; put '%then %do;'; put 'options nosyntaxcheck;'; put '%end;'; put '%if &mode=INCLUDE %then %do;'; put '%if %sysfunc(exist(&errds))=1 %then %do;'; put 'data _null_;'; put 'set &errds;'; put 'call symputx(''iftrue'',iftrue,''l'');'; put 'call symputx(''mac'',mac,''l'');'; put 'call symputx(''msg'',msg,''l'');'; put 'putlog (_all_)(=);'; put 'run;'; put '%if (&iftrue)=0 %then %return;'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: No include errors found;'; put '%return;'; put '%end;'; put '%end;'; put '/* extract log errs / warns, if exist */'; put '%local logloc logline;'; put '%global logmsg; /* capture global messages */'; put '%if %symexist(SYSPRINTTOLOG) %then %let logloc=&SYSPRINTTOLOG;'; put '%else %let logloc=%qsysfunc(getoption(LOG));'; put 'proc printto log=log;run;'; put '%let logline=0;'; put '%if %length(&logloc)>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input; putlog _infile_;'; put 'i=1;'; put 'retain logonce 0;'; put 'if ('; put '_infile_=:"%str(WARN)ING" or _infile_=:"%str(ERR)OR"'; put ') and logonce=0 then'; put 'do;'; put 'call symputx(''logline'',_n_);'; put 'logonce+1;'; put 'end;'; put 'run;'; put '/* capture log including lines BEFORE the err */'; put '%if &logline>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input;'; put 'i=1;'; put 'stoploop=0;'; put 'if _n_ ge &logline-15 and stoploop=0 then do until (i>22);'; put 'call symputx(''logmsg'',catx(''\n'',symget(''logmsg''),_infile_));'; put 'input;'; put 'i+1;'; put 'stoploop=1;'; put 'end;'; put 'if stoploop=1 then stop;'; put 'run;'; put '%end;'; put '%end;'; put '%if %symexist(SYS_JES_JOB_URI) %then %do;'; put '/* setup webout for Viya */'; put 'options nobomfile;'; put '%if "X&SYS_JES_JOB_URI.X"="XX" %then %do;'; put 'filename _webout temp lrecl=999999 mod;'; put '%end;'; put '%else %do;'; put 'filename _webout filesrvc parenturi="&SYS_JES_JOB_URI"'; put 'name="_webout.json" lrecl=999999 mod;'; put '%end;'; put '%end;'; put '%else %if %sysfunc(filename(fref,&sasjs_stpsrv_header_loc))=0 %then %do;'; put 'options nobomfile;'; put '/* set up http header for SASjs Server */'; put '%let fid=%sysfunc(fopen(&fref,A));'; put '%if &fid=0 %then %do;'; put '%put %str(ERR)OR: %sysfunc(sysmsg());'; put '%return;'; put '%end;'; put '%let rc=%sysfunc(fput(&fid,%str(Content-Type: application/json)));'; put '%let rc=%sysfunc(fwrite(&fid));'; put '%let rc=%sysfunc(fclose(&fid));'; put '%let rc=%sysfunc(filename(&fref));'; put '%end;'; put '/* send response in SASjs JSON format */'; put 'data _null_;'; put 'file _webout mod lrecl=32000 encoding=''utf-8'';'; put 'length msg syswarningtext syserrortext $32767 mode $10 ;'; put 'sasdatetime=datetime();'; put 'msg=symget(''msg'');'; put '%if &logline>0 %then %do;'; put 'msg=cats(msg,''\n\nLog Extract:\n'',symget(''logmsg''));'; put '%end;'; put '/* escape the escapes */'; put 'msg=tranwrd(msg,''\'',''\\'');'; put '/* escape the quotes */'; put 'msg=tranwrd(msg,''"'',''\"'');'; put '/* ditch the CRLFs as chrome complains */'; put 'msg=compress(msg,,''kw'');'; put '/* quote without quoting the quotes (which are escaped instead) */'; put 'msg=cats(''"'',msg,''"'');'; put 'if symexist(''_debug'') then debug=quote(trim(symget(''_debug'')));'; put 'else debug=''""'';'; put 'if symget(''sasjsprocessmode'')=''Stored Program'' then mode=''SASJS'';'; put 'if mode ne ''SASJS'' then put ''>>weboutBEGIN<<'';'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'put '',"sasjsAbort" : [{'';'; put 'put '' "MSG":'' msg ;'; put 'put '' ,"MAC": "'' "&mac" ''"}]'';'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'put '',"_DEBUG":'' debug ;'; put 'if symexist(''_metauser'') then do;'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put 'end;'; put 'if symexist(''SYS_JES_JOB_URI'') then do;'; put 'SYS_JES_JOB_URI=quote(trim(symget(''SYS_JES_JOB_URI'')));'; put 'put '',"SYS_JES_JOB_URI": '' SYS_JES_JOB_URI;'; put 'end;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put ",""SYSWARNINGTEXT"" : " syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'put "}" ;'; put 'if mode ne ''SASJS'' then put ''>>weboutEND<<'';'; put 'run;'; put '%put _all_;'; put '%if "&sysprocessmode " = "SAS Stored Process Server " %then %do;'; put 'data _null_;'; put 'putlog ''stpsrvset program err and syscc'';'; put 'rc=stpsrvset(''program error'', 0);'; put 'call symputx("syscc",0,"g");'; put 'run;'; put '%if &sysscp=WIN'; put 'and 1=0 /* deprecating this logic until we figure out a consistent abort */'; put 'and "%substr(%str(&sysvlong ),1,8)"="9.04.01M"'; put 'and "%substr(%str(&sysvlong ),9,1)">"5" %then %do;'; put '/* skip approach (below) does not work in windows m6+ envs */'; put 'endsas;'; put '%end;'; put '%else %do;'; put '/**'; put '* endsas kills 9.4m3 deployments by orphaning multibridges.'; put '* Abort variants are ungraceful (non zero return code)'; put '* This approach lets SAS run silently until the end :-)'; put '* Caution - fails when called within a %include within a macro'; put '* Use mp_include() to handle this.'; put '*/'; put 'filename skip temp;'; put 'data _null_;'; put 'file skip;'; put 'put ''%macro skip();'';'; put 'comment ''%mend skip; -> fix lint '';'; put 'put ''%macro skippy();'';'; put 'comment ''%mend skippy; -> fix lint '';'; put 'run;'; put '%inc skip;'; put '%end;'; put '%end;'; put '%else %if "&sysprocessmode " = "SAS Compute Server " %then %do;'; put '/* endsas kills the session making it harder to fetch results */'; put 'data _null_;'; put 'syswarningtext=symget(''syswarningtext'');'; put 'syserrortext=symget(''syserrortext'');'; put 'abort_msg=symget(''msg'');'; put 'syscc=symget(''syscc'');'; put 'sysuserid=symget(''sysuserid'');'; put 'iftrue=symget(''iftrue'');'; put 'put (_all_)(/=);'; put 'call symputx(''syscc'',0);'; put 'abort cancel nolist;'; put 'run;'; put '%end;'; put '%else %do;'; put '%abort cancel;'; put '%end;'; put '%end;'; put '%else %do;'; put '%put _all_;'; put '%abort cancel;'; put '%end;'; put '%mend mp_abort;'; put '/** @endcond */'; put '%macro mm_getstpcode('; put 'tree=/User Folders/sasdemo/somestp'; put ',name='; put ',outloc=0'; put ',outref=0'; put ',mDebug=1'; put ',showlog=NO'; put ');'; put '%local mD;'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing &sysmacroname..sas;'; put '%&mD.put _local_;'; put '%if %length(&name)>0 %then %let name=/&name;'; put '/* first, check if STP exists */'; put '%local tsuri;'; put '%let tsuri=stopifempty ;'; put 'data _null_;'; put 'format type uri tsuri value $200.;'; put 'call missing (of _all_);'; put 'path="&tree&name(StoredProcess)";'; put '/* first, find the STP ID */'; put 'if metadata_pathobj("",path,"StoredProcess",type,uri)>0 then do;'; put '/* get sourcecode */'; put 'cnt=1;'; put 'do while (metadata_getnasn(uri,"Notes",cnt,tsuri)>0);'; put 'rc=metadata_getattr(tsuri,"Name",value);'; put '&mD.put tsuri= value=;'; put 'if value="SourceCode" then do;'; put '/* found it! */'; put 'rc=metadata_getattr(tsuri,"Id",value);'; put 'call symputx(''tsuri'',value,''l'');'; put 'stop;'; put 'end;'; put 'cnt+1;'; put 'end;'; put 'end;'; put 'else put (_all_)(=);'; put 'run;'; put '%mp_abort(iftrue= (&tsuri=stopifempty)'; put ',mac=mm_getstpcode'; put ',msg=%str(&tree&name.(StoredProcess) not found!)'; put ')'; put '/**'; put '* Now we can extract the textstore'; put '*/'; put 'filename __getdoc temp lrecl=10000000;'; put 'proc metadata'; put 'in="$METAREPOSITORY'; put ''; put 'SAS1"'; put 'out=__getdoc ;'; put 'run;'; put '/* find the beginning of the text */'; put '%local start;'; put 'data _null_;'; put 'infile __getdoc lrecl=10000;'; put 'input;'; put 'start=index(_infile_,''StoredText="'');'; put 'if start then do;'; put 'call symputx("start",start+11);'; put '*putlog ''"'' _infile_ ''"'';'; put 'end;'; put 'stop;'; put '%local outeng;'; put '%if "&outloc"="0" %then %let outeng=TEMP;'; put '%else %let outeng="&outloc";'; put '%local fref;'; put '%if &outref=0 %then %let fref=%mf_getuniquefileref();'; put '%else %let fref=&outref;'; put '/* read the content, byte by byte, resolving escaped chars */'; put 'filename &fref &outeng lrecl=100000;'; put 'data _null_;'; put 'length filein 8 fileid 8;'; put 'filein = fopen("__getdoc","I",1,"B");'; put 'fileid = fopen("&fref","O",1,"B");'; put 'rec = "20"x;'; put 'length entity $6;'; put 'do while(fread(filein)=0);'; put 'x+1;'; put 'if x>&start then do;'; put 'rc = fget(filein,rec,1);'; put 'if rec=''"'' then leave;'; put 'else if rec="&" then do;'; put 'entity=rec;'; put 'do until (rec=";");'; put 'if fread(filein) ne 0 then goto getout;'; put 'rc = fget(filein,rec,1);'; put 'entity=cats(entity,rec);'; put 'end;'; put 'select (entity);'; put 'when (''&'' ) rec=''&'' ;'; put 'when (''<'' ) rec=''<'' ;'; put 'when (''>'' ) rec=''>'' ;'; put 'when (''''') rec="''" ;'; put 'when (''"'') rec=''"'' ;'; put 'when ('' '') rec=''0A''x;'; put 'when ('' '') rec=''0D''x;'; put 'when (''$'' ) rec=''$'' ;'; put 'when ('' '') rec=''09''x;'; put 'otherwise putlog "%str(WARN)ING: missing value for " entity=;'; put 'end;'; put 'rc =fput(fileid, substr(rec,1,1));'; put 'rc =fwrite(fileid);'; put 'end;'; put 'else do;'; put 'rc =fput(fileid,rec);'; put 'rc =fwrite(fileid);'; put 'end;'; put 'end;'; put 'end;'; put 'getout:'; put 'rc=fclose(filein);'; put 'rc=fclose(fileid);'; put 'run;'; put '%if &showlog=YES %then %do;'; put 'data _null_;'; put 'infile &fref lrecl=32767 end=last;'; put 'input;'; put 'if _n_=1 then putlog ''>>stpcodeBEGIN<<'';'; put 'putlog _infile_;'; put 'if last then putlog ''>>stpcodeEND<<'';'; put 'run;'; put '%end;'; put 'filename __getdoc clear;'; put '%if &outref=0 %then %do;'; put 'filename &fref clear;'; put '%end;'; put '%mend mm_getstpcode;'; put '%macro dc_getsettings();'; put '%global _program;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(syscc=&syscc on entry (&syswarningtext &syserrortext))'; put ')'; put '%if %length(&_PROGRAM)>1 %then %let root=&_program;'; put '%else %do;'; put '%global _metauser;'; put '%let _metauser=&sysuserid;'; put '/* to mimic a "real" _program we need to give a dummy role and stp name */'; put '%let root=/dummyRole/dummyName;'; put '%end;'; put '/* the DC precode is stored in the Admin folder in the root of'; put 'the project. Lets find that root. */'; put '%put &=root;'; put '%let root=%mf_getapploc();'; put '%put &=root;'; put '/* Now we know the root location we can retrieve the params */'; put '%let temploc=%sysfunc(pathname(work))/settings.txt;'; put '%mm_getstpcode(tree=&root/services/public'; put ',name=Data_Controller_Settings'; put ',outloc=&temploc'; put ')'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Unable to run getstpcode)'; put ')'; put 'filename _getsets "&temploc" lrecl=2000;'; put '/*'; put 'Do not use mp_include here - this puts a copy in every service, which creates'; put 'compilation problems when calling services from mp_include'; put '*/'; put '%inc _getsets/source2;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Problem running &sysmacroname (&syswarningtext &syserrortext))'; put ')'; put '%mend dc_getsettings;'; put '%macro mf_fmtdttm('; put ')/*/STORE SOURCE*/;'; put '%if "&sysver"="9.2" or "&sysver"="9.3"'; put 'or ("&sysver"="9.4" and "%substr(&SYSVLONG,9,1)" le "3")'; put 'or "%substr(&sysver,1,1)"="4"'; put 'or "%substr(&sysver,1,1)"="5"'; put '%then %do;DATETIME19.3%end;'; put '%else %do;E8601DT26.6%end;'; put '%mend mf_fmtdttm;'; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '%macro mp_init(prefix=SASJS'; put ')/*/STORE SOURCE*/;'; put '%if %symexist(SASJS_PREFIX) %then %return; /* only run once */'; put '%global'; put 'SASJS_PREFIX /* the ONLY hard-coded global macro variable in SASjs */'; put '&prefix._FUNCTIONS /* used in mcf_init() to track core function compilation */'; put '&prefix._INIT_NUM /* initialisation time as numeric */'; put '&prefix._INIT_DTTM /* initialisation time in E8601DT26.6 format */'; put '&prefix.WORK /* avoid typing %sysfunc(pathname(work)) every time */'; put ';'; put '%let sasjs_prefix=&prefix;'; put 'data _null_;'; put 'dttm=datetime();'; put 'call symputx("&sasjs_prefix._init_num",dttm,''g'');'; put 'call symputx("&sasjs_prefix._init_dttm",put(dttm,E8601DT26.6),''g'');'; put 'call symputx("&sasjs_prefix.work",pathname(''WORK''),''g'');'; put 'run;'; put 'options'; put 'compress=CHAR /* default is none so ensure we have something! */'; put 'datastmtchk=ALLKEYWORDS /* protection from overwriting input datasets */'; put 'errorcheck=STRICT /* catch errs in libname/filename statements */'; put 'fmterr /* ensure err when a format cannot be found */'; put 'mergenoby=%str(ERR)OR /* throw err when a merge has no BY variables */'; put 'missing=. /* changing this can cause hard to detect errs */'; put 'noquotelenmax /* avoid warnings for long strings */'; put 'noreplace /* avoid overwriting permanent datasets */'; put 'ps=max /* reduce log size slightly */'; put 'ls=max /* reduce log even more and avoid word truncation */'; put 'validmemname=COMPATIBLE /* avoid special characters etc in table names */'; put 'validvarname=V7 /* avoid special characters etc in variable names */'; put 'varinitchk=%str(ERR)OR /* avoid data mistakes from variable name typos */'; put 'varlenchk=%str(ERR)OR /* fail hard if truncation (data loss) can result */'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5" %then %do;'; put 'noautocorrect /* disallow misspelled procedure names */'; put 'dsoptions=note2err /* undocumented - convert bad NOTEs to ERRs */'; put '%end;'; put ';'; put '%mend mp_init;'; put '%macro mpeinit(fetch=YES);'; put '%global mpeinit'; put 'mpeadmins /* group with unrestricted Meditor access */'; put 'mpelocapprovals /* location for landing and staging files */'; put 'mpelib /* location of configuration tables for DC */'; put 'dc_repo_users /* location of user / group metadata */'; put 'dc_licence_key /* extracted in dc_getsettings */'; put 'dc_activation_key /* extracted in dc_getsettings */'; put 'dc_locale /* extracted in dc_getsettings */'; put 'dc_dttmtfmt /* can be overridden in dc_getsettings */'; put '_debug'; put ';'; put '%if &mpeinit=1 %then %return;'; put '%else %let mpeinit=1;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(Problem on service startup (&syswarningtext &syserrortext))'; put ')'; put '%mp_init()'; put '%if &fetch=YES %then %do;'; put '%webout(FETCH)'; put '%end;'; put '%global _CLIENTNAME;'; put '%mp_abort(iftrue= (&_CLIENTNAME=SAS Enterprise Guide)'; put ',mac=&_program..sas'; put ',msg=%str(Data Controller is a web app and should not be executed from EG)'; put ')'; put 'options urlencoding=utf8 nobomfile lrecl=32767;'; put '%let perf=%sysfunc(datetime());'; put '%put perfdiff: 0;'; put '%let dc_locale=SYSTEM; /* default if not set */'; put '/**'; put '* E8601DT26.6 has widest database support - but not all SAS flavours can'; put '* handle it. Override in the settings STP if needed.'; put '*/'; put 'data _null_;'; put 'dc_dttmtfmt=''"%sysfunc(datetime(),''!!"%mf_fmtdttm()"!!'')"dt'';'; put 'call symputx(''dc_dttmtfmt'',dc_dttmtfmt);'; put 'put dc_dttmtfmt=;'; put 'run;'; put '%put &=dc_dttmtfmt;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc prior to dc_getsettings)'; put ')'; put '%dc_getsettings()'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc after dc_getsettings)'; put ')'; put 'data _null_;'; put 'set &DC_LIBREF..mpe_config(where=('; put 'var_scope="DC"'; put 'and &dc_dttmtfmt lt tx_to'; put 'and var_active=1'; put '));'; put 'call symputx(var_name,var_value,''G'');'; put 'putlog var_name "=" var_value;'; put 'run;'; put '%let mpelib=&dc_libref;'; put '%let mpeadmins=&dc_admin_group;'; put '%let mpelocapprovals=&dc_staging_area;'; put '%let dc_repo_users=&dc_repo_users;'; put '%if &dc_locale ne SYSTEM %then %do;'; put 'options locale=&dc_locale;'; put '%end;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(Problem during compilation or with STP precode (&syswarningtext))'; put ')'; put '%mend mpeinit;'; put '%macro mf_mval(var);'; put '%if %symexist(&var) %then %do;'; put '%superq(&var)'; put '%end;'; put '%mend mf_mval;'; put '%macro mf_trimstr(basestr,trimstr);'; put '%local baselen trimlen trimval;'; put '/* return if basestr is shorter than trimstr (or 0) */'; put '%let baselen=%length(%superq(basestr));'; put '%let trimlen=%length(%superq(trimstr));'; put '%if &baselen < &trimlen or &baselen=0 %then %return;'; put '/* obtain the characters from the end of basestr */'; put '%let trimval=%qsubstr(%superq(basestr)'; put ',%length(%superq(basestr))-&trimlen+1'; put ',&trimlen);'; put '/* compare and if matching, chop it off! */'; put '%if %superq(basestr)=%superq(trimstr) %then %do;'; put '%return;'; put '%end;'; put '%else %if %superq(trimval)=%superq(trimstr) %then %do;'; put '%qsubstr(%superq(basestr),1,%length(%superq(basestr))-&trimlen)'; put '%end;'; put '%else %do;'; put '&basestr'; put '%end;'; put '%mend mf_trimstr;'; put '%macro mf_getplatform(switch'; put ')/*/STORE SOURCE*/;'; put '%local a b c;'; put '%if &switch.NONE=NONE %then %do;'; put '%if %symexist(sasjsprocessmode) %then %do;'; put '%if &sasjsprocessmode=Stored Program %then %do;'; put 'SASJS'; put '%return;'; put '%end;'; put '%end;'; put '%if %symexist(sysprocessmode) %then %do;'; put '%if "&sysprocessmode"="SAS Object Server"'; put 'or "&sysprocessmode"= "SAS Compute Server" %then %do;'; put 'SASVIYA'; put '%end;'; put '%else %if "&sysprocessmode"="SAS Stored Process Server"'; put 'or "&sysprocessmode"="SAS Workspace Server"'; put '%then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if %symexist(_metaport) or %symexist(_metauser) %then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if &switch=SASSTUDIO %then %do;'; put '/* return the version of SAS Studio else 0 */'; put '%if %mf_mval(_CLIENTAPP)=%str(SAS Studio) %then %do;'; put '%let a=%mf_mval(_CLIENTVERSION);'; put '%let b=%scan(&a,1,.);'; put '%if %eval(&b >2) %then %do;'; put '&b'; put '%end;'; put '%else 0;'; put '%end;'; put '%else 0;'; put '%end;'; put '%else %if &switch=VIYARESTAPI %then %do;'; put '%mf_trimstr(%sysfunc(getoption(servicesbaseurl)),/)'; put '%end;'; put '%mend mf_getplatform;'; put '%macro mpeterm();'; put '%local oldloc;'; put 'data _null_;'; put 'if symexist(''SYSPRINTTOLOG'') then oldloc=symget(''SYSPRINTTOLOG'');'; put 'else oldloc=getoption(''LOG'');'; put 'if subpad(oldloc,1,1) not in (''"'',"''",'' '') then oldloc=quote(cats(oldloc));'; put 'call symputx(''oldloc'',oldloc,''l'');'; put 'run;'; put '%if %length(&oldloc)>0 %then %do;'; put 'proc printto log=log;'; put 'run;'; put 'data _null_;'; put 'infile &oldloc;'; put 'input; putlog _infile_;'; put 'run;'; put '%end;'; put '%if %sysfunc(exist(&dc_libref..mpe_requests)) and %mf_getplatform() ne SASVIYA'; put '%then %do;'; put 'data ;'; put 'if 0 then set &dc_libref..mpe_requests;'; put 'request_dttm=%sysfunc(datetime());'; put 'request_user="%mf_getuser()";'; put 'request_service="%scan(&_program,-2,/)/%scan(&_program,-1,/)";'; put 'request_params='''';'; put 'output;stop;'; put 'proc append base=&dc_libref..mpe_requests data=&syslast force nowarn;'; put 'run;'; put '%end;'; put '%mend mpeterm;'; put '%macro mf_isblank(param'; put ')/*/STORE SOURCE*/;'; put '%sysevalf(%superq(param)=,boolean)'; put '%mend mf_isblank;'; put '%macro mp_dropmembers('; put 'list /* space separated list of datasets / views */'; put ',libref=WORK /* can only drop from a single library at a time */'; put ',iftrue=%str(1=1)'; put ')/*/STORE SOURCE*/;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%if %mf_isblank(&list) %then %do;'; put '%put NOTE: nothing to drop!;'; put '%return;'; put '%end;'; put 'proc datasets lib=&libref nolist;'; put 'delete &list;'; put 'delete &list /mtype=view;'; put 'run;'; put '%mend mp_dropmembers;'; put '%macro removecolsfromwork(col);'; put '/* only an issue if debug mode enabled */'; put '%global _debug;'; put '%if &_debug ge 131 %then %do;'; put '%let col=%upcase(&col);'; put '%local memlist;'; put 'proc sql noprint;'; put 'select distinct memname into: memlist'; put 'separated by '' '''; put 'from dictionary.columns'; put 'where libname=''WORK'' and upcase(name)="&col";'; put '%if %mf_isblank(&memlist) %then %return;'; put '%mp_dropmembers(list=&memlist)'; put '%end;'; put '%mend removecolsfromwork;'; put '%macro mm_assignlib('; put 'libref'; put ',mAbort=HARD'; put ')/*/STORE SOURCE*/;'; put '%local mp_abort msg;'; put '%let mp_abort=0;'; put '%if %sysfunc(libref(&libref)) %then %do;'; put 'data _null_;'; put 'length liburi LibName msg $200;'; put 'call missing(of _all_);'; put 'nobj=metadata_getnobj("omsobj:SASLibrary?@Libref=''&libref''",1,liburi);'; put 'if nobj=1 then do;'; put 'rc=metadata_getattr(liburi,"Name",LibName);'; put '/* now try and assign it */'; put 'if libname("&libref",,''meta'',cats(''liburi="'',liburi,''";'')) ne 0 then do;'; put 'putlog "&libref could not be assigned";'; put 'putlog liburi=;'; put '/**'; put '* Fetch the system message for display in the abort modal. This is'; put '* not always helpful though. One example, previously received:'; put '* NOTE: Libref XX refers to the same library metadata as libref XX.'; put '*/'; put 'msg=sysmsg();'; put 'if msg=:''ERROR: Libref SAVE is not assigned.'' then do;'; put 'msg=catx(" ",'; put '"Could not assign %upcase(&libref).",'; put '"Please check metadata permissions! Libname:",libname,'; put '"Liburi:",liburi'; put ');'; put 'end;'; put 'else if msg="ERROR: User does not have appropriate authorization "!!'; put '"level for library SAVE."'; put 'then do;'; put 'msg=catx(" ",'; put '"ERROR: User does not have appropriate authorization level",'; put '"for library %upcase(&libref), libname:",libname,'; put '"Liburi:",liburi'; put ');'; put 'end;'; put 'call symputx(''msg'',msg,''l'');'; put 'if "&mabort"=''HARD'' then call symputx(''mp_abort'',1,''l'');'; put 'end;'; put 'else do;'; put 'put (_all_)(=);'; put 'call symputx(''libname'',libname,''L'');'; put 'call symputx(''liburi'',liburi,''L'');'; put 'end;'; put 'end;'; put 'else if nobj>1 then do;'; put 'if "&mabort"=''HARD'' then call symputx(''mp_abort'',1);'; put 'call symputx(''msg'',"More than one library with libref=&libref");'; put 'end;'; put 'else do;'; put 'if "&mabort"=''HARD'' then call symputx(''mp_abort'',1);'; put 'call symputx(''msg'',"Library &libref not found in metadata");'; put 'end;'; put 'run;'; put '%put NOTE: &msg;'; put '%end;'; put '%else %do;'; put '%put NOTE: Library &libref is already assigned;'; put '%end;'; put '%mp_abort(iftrue= (&mp_abort=1)'; put ',mac=mm_assignlib.sas'; put ',msg=%superq(msg)'; put ')'; put '%mend mm_assignlib;'; put '/** @cond */'; put '%macro mf_getengine(libref'; put ')/*/STORE SOURCE*/;'; put '%local dsid engnum rc engine;'; put '/* in case the parameter is a libref.tablename, pull off just the libref */'; put '%let libref = %upcase(%scan(&libref, 1, %str(.)));'; put '%let dsid=%sysfunc('; put 'open(sashelp.vlibnam(where=(libname="%upcase(&libref)")),i)'; put ');'; put '%if (&dsid ^= 0) %then %do;'; put '%let engnum=%sysfunc(varnum(&dsid,ENGINE));'; put '%let rc=%sysfunc(fetch(&dsid));'; put '%let engine=%sysfunc(getvarc(&dsid,&engnum));'; put '%put &libref. ENGINE is &engine.;'; put '%let rc= %sysfunc(close(&dsid));'; put '%end;'; put '%upcase(&engine)'; put '%mend mf_getengine;'; put '/** @endcond */'; put '%macro mm_assigndirectlib('; put 'libref'; put ',open_passthrough='; put ',sql_options='; put ',mDebug=0'; put ',mAbort=0'; put ')/*/STORE SOURCE*/;'; put '%local mD;'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing mm_assigndirectlib.sas;'; put '%&mD.put _local_;'; put '%if &mAbort=1 %then %let mAbort=;'; put '%else %let mAbort=%str(*);'; put '%&mD.put NOTE: Creating direct (non META) connection to &libref library;'; put '%local cur_engine;'; put '%let cur_engine=%mf_getengine(&libref);'; put '%if &cur_engine ne META and &cur_engine ne %then %do;'; put '%put NOTE: &libref already has a direct (&cur_engine) libname connection;'; put '%return;'; put '%end;'; put '%else %if %upcase(&libref)=WORK %then %do;'; put '%put NOTE: We already have a direct connection to WORK :-) ;'; put '%return;'; put '%end;'; put '/* need to determine the library ENGINE first */'; put '%local engine;'; put 'data _null_;'; put 'length lib_uri engine $256;'; put 'call missing (of _all_);'; put '/* get URI for the particular library */'; put 'rc1=metadata_getnobj("omsobj:SASLibrary?@Libref =''&libref''",1,lib_uri);'; put '/* get the Engine attribute of the previous object */'; put 'rc2=metadata_getattr(lib_uri,''Engine'',engine);'; put 'putlog "mm_assigndirectlib for &libref:" rc1= lib_uri= rc2= engine=;'; put 'call symputx("liburi",lib_uri,''l'');'; put 'call symputx("engine",engine,''l'');'; put 'run;'; put '/* now obtain engine specific connection details */'; put '%if &engine=BASE %then %do;'; put '%&mD.put NOTE: Retrieving BASE library path;'; put 'data _null_;'; put 'length up_uri $256 path cat_path $1024;'; put 'retain cat_path;'; put 'call missing (of _all_);'; put '/* get all the filepaths of the UsingPackages association */'; put 'i=1;'; put 'rc3=metadata_getnasn("&liburi",''UsingPackages'',i,up_uri);'; put 'do while (rc3>0);'; put '/* get the DirectoryName attribute of the previous object */'; put 'rc4=metadata_getattr(up_uri,''DirectoryName'',path);'; put 'if i=1 then path = ''("''!!trim(path)!!''" '';'; put 'else path ='' "''!!trim(path)!!''" '';'; put 'cat_path = trim(cat_path) !! " " !! trim(path) ;'; put 'i+1;'; put 'rc3=metadata_getnasn("&liburi",''UsingPackages'',i,up_uri);'; put 'end;'; put 'cat_path = trim(cat_path) !! ")";'; put '&mD.putlog "NOTE: Getting physical path for &libref library";'; put '&mD.putlog rc3= up_uri= rc4= cat_path= path=;'; put '&mD.putlog "NOTE: Libname cmd will be:";'; put '&mD.putlog "libname &libref" cat_path;'; put 'call symputx("filepath",cat_path,''l'');'; put 'run;'; put '%if %sysevalf(&sysver<9.4) %then %do;'; put 'libname &libref &filepath;'; put '%end;'; put '%else %do;'; put '/* apply the new filelocks option to cater for temporary locks */'; put 'libname &libref &filepath filelockwait=5;'; put '%end;'; put '%end;'; put '%else %if &engine=REMOTE %then %do;'; put 'data x;'; put 'length rcCon rcProp rc k 3 uriCon uriProp PropertyValue PropertyName'; put 'Delimiter $256 properties $2048;'; put 'retain properties;'; put 'rcCon = metadata_getnasn("&liburi", "LibraryConnection", 1, uriCon);'; put 'rcProp = metadata_getnasn(uriCon, "Properties", 1, uriProp);'; put 'k = 1;'; put 'rcProp = metadata_getnasn(uriCon, "Properties", k, uriProp);'; put 'do while (rcProp > 0);'; put 'rc = metadata_getattr(uriProp , "DefaultValue",PropertyValue);'; put 'rc = metadata_getattr(uriProp , "PropertyName",PropertyName);'; put 'rc = metadata_getattr(uriProp , "Delimiter",Delimiter);'; put 'properties = trim(properties) !! " " !! trim(PropertyName)'; put '!! trim(Delimiter) !! trim(PropertyValue);'; put 'output;'; put 'k+1;'; put 'rcProp = metadata_getnasn(uriCon, "Properties", k, uriProp);'; put 'end;'; put '%&mD.put NOTE: Getting properties for REMOTE SHARE &libref library;'; put '&mD.put _all_;'; put '%&mD.put NOTE: Libname cmd will be:;'; put '%&mD.put libname &libref &engine &properties slibref=&libref;'; put 'call symputx ("properties",trim(properties),''l'');'; put 'run;'; put 'libname &libref &engine &properties slibref=&libref;'; put '%end;'; put '%else %if &engine=OLEDB %then %do;'; put '%&mD.put NOTE: Retrieving OLEDB connection details;'; put 'data _null_;'; put 'length domain datasource provider properties schema'; put 'connx_uri domain_uri conprop_uri lib_uri schema_uri value $256.;'; put 'call missing (of _all_);'; put '/* get source connection ID */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,connx_uri);'; put '/* get connection domain */'; put 'rc1=metadata_getnasn(connx_uri,''Domain'',1,domain_uri);'; put 'rc2=metadata_getattr(domain_uri,''Name'',domain);'; put '&mD.putlog / ''NOTE: '' // ''NOTE- connection id: '' connx_uri ;'; put '&mD.putlog ''NOTE- domain: '' domain;'; put '/* get DSN and PROVIDER from connection properties */'; put 'i=0;'; put 'do until (rc<0);'; put 'i+1;'; put 'rc=metadata_getnasn(connx_uri,''Properties'',i,conprop_uri);'; put 'rc2=metadata_getattr(conprop_uri,''Name'',value);'; put 'if value=''Connection.OLE.Property.DATASOURCE.Name.xmlKey.txt'' then do;'; put 'rc3=metadata_getattr(conprop_uri,''DefaultValue'',datasource);'; put 'end;'; put 'else if value=''Connection.OLE.Property.PROVIDER.Name.xmlKey.txt'' then do;'; put 'rc4=metadata_getattr(conprop_uri,''DefaultValue'',provider);'; put 'end;'; put 'else if value=''Connection.OLE.Property.PROPERTIES.Name.xmlKey.txt'' then'; put 'do;'; put 'rc5=metadata_getattr(conprop_uri,''DefaultValue'',properties);'; put 'end;'; put 'end;'; put '&mD.putlog ''NOTE- dsn/provider/properties: '' /'; put 'datasource provider properties;'; put '&mD.putlog ''NOTE- schema: '' schema // ''NOTE-'';'; put '/* get SCHEMA */'; put 'rc6=metadata_getnasn("&liburi",''UsingPackages'',1,lib_uri);'; put 'rc7=metadata_getattr(lib_uri,''SchemaName'',schema);'; put 'call symputx(''SQL_domain'',domain,''l'');'; put 'call symputx(''SQL_dsn'',datasource,''l'');'; put 'call symputx(''SQL_provider'',provider,''l'');'; put 'call symputx(''SQL_properties'',properties,''l'');'; put 'call symputx(''SQL_schema'',schema,''l'');'; put 'run;'; put '%if %length(&open_passthrough)>0 %then %do;'; put 'proc sql &sql_options;'; put 'connect to OLEDB as &open_passthrough(INSERT_SQL=YES'; put '/* need additional properties to make this work */'; put 'properties=(''Integrated Security''=SSPI'; put '''Persist Security Info''=True'; put '%sysfunc(compress(%str(&SQL_properties),%str(())))'; put ')'; put 'DATASOURCE=&sql_dsn PROMPT=NO'; put 'PROVIDER=&sql_provider SCHEMA=&sql_schema CONNECTION = GLOBAL);'; put '%end;'; put '%else %do;'; put 'LIBNAME &libref OLEDB PROPERTIES=&sql_properties'; put 'DATASOURCE=&sql_dsn PROVIDER=&sql_provider SCHEMA=&sql_schema'; put '%if %length(&sql_domain)>0 %then %do;'; put 'authdomain="&sql_domain"'; put '%end;'; put 'connection=shared;'; put '%end;'; put '%end;'; put '%else %if &engine=ODBC %then %do;'; put '%&mD.put NOTE: Retrieving ODBC connection details;'; put 'data _null_;'; put 'length connx_uri conprop_uri value datasource up_uri schema domprop_uri authdomain $256.;'; put 'call missing (of _all_);'; put '/* get source connection ID */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,connx_uri);'; put '/* get connection properties */'; put 'i=0;'; put 'do until (rc2<0);'; put 'i+1;'; put 'rc2=metadata_getnasn(connx_uri,''Properties'',i,conprop_uri);'; put 'rc3=metadata_getattr(conprop_uri,''Name'',value);'; put 'if value=''Connection.ODBC.Property.DATASRC.Name.xmlKey.txt'' then do;'; put 'rc4=metadata_getattr(conprop_uri,''DefaultValue'',datasource);'; put 'rc2=-1;'; put 'end;'; put 'end;'; put '/* get auth domain */'; put 'autrc=metadata_getnasn(connx_uri,"Domain",1,domprop_uri);'; put 'arc=metadata_getattr(domprop_uri,"Name",authdomain);'; put 'if not missing(authdomain) then authdomain=cats(''AUTHDOMAIN='',authdomain);'; put 'call symputx(''authdomain'',authdomain,''l'');'; put '/* get SCHEMA */'; put 'rc6=metadata_getnasn("&liburi",''UsingPackages'',1,up_uri);'; put 'rc7=metadata_getattr(up_uri,''SchemaName'',schema);'; put '&mD.put rc= connx_uri= rc2= conprop_uri= rc3= value= rc4= datasource='; put 'rc6= up_uri= rc7= schema=;'; put 'call symputx(''SQL_schema'',schema,''l'');'; put 'call symputx(''SQL_dsn'',datasource,''l'');'; put 'run;'; put '%if %length(&open_passthrough)>0 %then %do;'; put 'proc sql &sql_options;'; put 'connect to ODBC as &open_passthrough'; put '(INSERT_SQL=YES DATASRC=&sql_dsn. CONNECTION=global);'; put '%end;'; put '%else %do;'; put 'libname &libref ODBC DATASRC=&sql_dsn SCHEMA=&sql_schema &authdomain;'; put '%end;'; put '%end;'; put '%else %if &engine=POSTGRES %then %do;'; put '%put NOTE: Obtaining POSTGRES library details;'; put 'data _null_;'; put 'length database ignore_read_only_columns direct_exe preserve_col_names'; put 'preserve_tab_names server schema authdomain user password'; put 'prop name value uri urisrc $256.;'; put 'call missing (of _all_);'; put '/* get database value */'; put 'prop=''Connection.DBMS.Property.DB.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,database,"");'; put 'if database^='''' then database=''database=''!!quote(trim(database));'; put 'call symputx(''database'',database,''l'');'; put '/* get IGNORE_READ_ONLY_COLUMNS value */'; put 'prop=''Library.DBMS.Property.DBIROC.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,ignore_read_only_columns,"");'; put 'if ignore_read_only_columns^='''' then ignore_read_only_columns='; put '''ignore_read_only_columns=''!!ignore_read_only_columns;'; put 'call symputx(''ignore_read_only_columns'',ignore_read_only_columns,''l'');'; put '/* get DIRECT_EXE value */'; put 'prop=''Library.DBMS.Property.DirectExe.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,direct_exe,"");'; put 'if direct_exe^='''' then direct_exe=''direct_exe=''!!direct_exe;'; put 'call symputx(''direct_exe'',direct_exe,''l'');'; put '/* get PRESERVE_COL_NAMES value */'; put 'prop=''Library.DBMS.Property.PreserveColNames.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,preserve_col_names,"");'; put 'if preserve_col_names^='''' then preserve_col_names='; put '''preserve_col_names=''!!preserve_col_names;'; put 'call symputx(''preserve_col_names'',preserve_col_names,''l'');'; put '/* get PRESERVE_TAB_NAMES value */'; put '/* be careful with PRESERVE_TAB_NAMES=YES - it will mean your table will'; put 'become case sensitive!! */'; put 'prop=''Library.DBMS.Property.PreserveTabNames.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,preserve_tab_names,"");'; put 'if preserve_tab_names^='''' then preserve_tab_names='; put '''preserve_tab_names=''!!preserve_tab_names;'; put 'call symputx(''preserve_tab_names'',preserve_tab_names,''l'');'; put '/* get SERVER value */'; put 'if metadata_getnasn("&liburi","LibraryConnection",1,uri)>0 then do;'; put 'prop=''Connection.DBMS.Property.SERVER.Name.xmlKey.txt'';'; put 'rc=metadata_getprop(uri,prop,server,"");'; put 'end;'; put 'if server^='''' then server=''server=''!!quote(cats(server));'; put 'call symputx(''server'',server,''l'');'; put '/* get SCHEMA value */'; put 'if metadata_getnasn("&liburi","UsingPackages",1,uri)>0 then do;'; put 'rc=metadata_getattr(uri,"SchemaName",schema);'; put 'end;'; put 'if schema^='''' then schema=''schema=''!!schema;'; put 'call symputx(''schema'',schema,''l'');'; put '/* get AUTHDOMAIN value */'; put '/* this is only useful if the user account contains that auth domain'; put 'if metadata_getnasn("&liburi","DefaultLogin",1,uri)>0 then do;'; put 'rc=metadata_getnasn(uri,"Domain",1,urisrc);'; put 'rc=metadata_getattr(urisrc,"Name",authdomain);'; put 'end;'; put 'if authdomain^='''' then authdomain=''authdomain=''!!quote(trim(authdomain));'; put '*/'; put 'call symputx(''authdomain'',authdomain,''l'');'; put '/* get user & pass */'; put 'if authdomain='''' & metadata_getnasn("&liburi","DefaultLogin",1,uri)>0 then'; put 'do;'; put 'rc=metadata_getattr(uri,"UserID",user);'; put 'rc=metadata_getattr(uri,"Password",password);'; put 'end;'; put 'if user^='''' then do;'; put 'user=''user=''!!quote(trim(user));'; put 'password=''password=''!!quote(trim(password));'; put 'end;'; put 'call symputx(''user'',user,''l'');'; put 'call symputx(''password'',password,''l'');'; put '&md.put _all_;'; put 'run;'; put '%if %length(&open_passthrough)>0 %then %do;'; put '%put %str(WARN)ING: Passthrough option for postgres not yet supported;'; put '%return;'; put '%end;'; put '%else %do;'; put '%if &mdebug=1 %then %do;'; put '%put NOTE: Executing the following:/;'; put '%put NOTE- libname &libref POSTGRES &database &ignore_read_only_columns;'; put '%put NOTE- &direct_exe &preserve_col_names &preserve_tab_names;'; put '%put NOTE- &server &schema &authdomain &user &password //;'; put '%end;'; put 'libname &libref POSTGRES &database &ignore_read_only_columns &direct_exe'; put '&preserve_col_names &preserve_tab_names &server &schema &authdomain'; put '&user &password;'; put '%end;'; put '%end;'; put '%else %if &engine=ORACLE %then %do;'; put '%put NOTE: Obtaining &engine library details;'; put 'data _null_;'; put 'length assocuri1 assocuri2 assocuri3 authdomain path schema $256;'; put 'call missing (of _all_);'; put '/* get auth domain */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,assocuri1);'; put 'rc=metadata_getnasn(assocuri1,''Domain'',1,assocuri2);'; put 'rc=metadata_getattr(assocuri2,"Name",authdomain);'; put 'call symputx(''authdomain'',authdomain,''l'');'; put '/* path */'; put 'rc=metadata_getprop(assocuri1,'; put '''Connection.Oracle.Property.PATH.Name.xmlKey.txt'',path);'; put 'call symputx(''path'',path,''l'');'; put '/* schema */'; put 'rc=metadata_getnasn("&liburi",''UsingPackages'',1,assocuri3);'; put 'rc=metadata_getattr(assocuri3,''SchemaName'',schema);'; put 'call symputx(''schema'',schema,''l'');'; put 'run;'; put '%put NOTE: Executing the following:/; %put NOTE-;'; put '%put NOTE- libname &libref ORACLE path=&path schema=&schema;'; put '%put NOTE- authdomain=&authdomain;'; put '%put NOTE-;'; put 'libname &libref ORACLE path=&path schema=&schema authdomain=&authdomain;'; put '%end;'; put '%else %if &engine=SQLSVR %then %do;'; put '%put NOTE: Obtaining &engine library details;'; put 'data _null;'; put 'length assocuri1 assocuri2 assocuri3 authdomain path schema userid'; put 'passwd $256;'; put 'call missing (of _all_);'; put 'rc=metadata_getnasn("&liburi",''DefaultLogin'',1,assocuri1);'; put 'rc=metadata_getattr(assocuri1,"UserID",userid);'; put 'rc=metadata_getattr(assocuri1,"Password",passwd);'; put 'call symputx(''user'',userid,''l'');'; put 'call symputx(''pass'',passwd,''l'');'; put '/* path */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,assocuri2);'; put 'rc=metadata_getprop(assocuri2,'; put '''Connection.SQL.Property.Datasrc.Name.xmlKey.txt'',path);'; put 'call symputx(''path'',path,''l'');'; put '/* schema */'; put 'rc=metadata_getnasn("&liburi",''UsingPackages'',1,assocuri3);'; put 'rc=metadata_getattr(assocuri3,''SchemaName'',schema);'; put 'call symputx(''schema'',schema,''l'');'; put 'run;'; put '%put NOTE: Executing the following:/; %put NOTE-;'; put '%put NOTE- libname &libref SQLSVR datasrc=&path schema=&schema ;'; put '%put NOTE- user="&user" pass="XXX";'; put '%put NOTE-;'; put 'libname &libref SQLSVR datasrc=&path schema=&schema user="&user" pass="&pass";'; put '%end;'; put '%else %if &engine=TERADATA %then %do;'; put '%put NOTE: Obtaining &engine library details;'; put 'data _null;'; put 'length assocuri1 assocuri2 assocuri3 authdomain path schema userid'; put 'passwd $256;'; put 'call missing (of _all_);'; put '/* get auth domain */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,assocuri1);'; put 'rc=metadata_getnasn(assocuri1,''Domain'',1,assocuri2);'; put 'rc=metadata_getattr(assocuri2,"Name",authdomain);'; put 'call symputx(''authdomain'',authdomain,''l'');'; put '/*'; put 'rc=metadata_getnasn("&liburi",''DefaultLogin'',1,assocuri1);'; put 'rc=metadata_getattr(assocuri1,"UserID",userid);'; put 'rc=metadata_getattr(assocuri1,"Password",passwd);'; put 'call symputx(''user'',userid,''l'');'; put 'call symputx(''pass'',passwd,''l'');'; put '*/'; put '/* path */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,assocuri2);'; put 'rc=metadata_getprop(assocuri2,'; put '''Connection.Teradata.Property.SERVER.Name.xmlKey.txt'',path);'; put 'call symputx(''path'',path,''l'');'; put '/* schema */'; put 'rc=metadata_getnasn("&liburi",''UsingPackages'',1,assocuri3);'; put 'rc=metadata_getattr(assocuri3,''SchemaName'',schema);'; put 'call symputx(''schema'',schema,''l'');'; put 'run;'; put '%put NOTE: Executing the following:/; %put NOTE-;'; put '%put NOTE- libname &libref TERADATA server="&path" schema=&schema ;'; put '%put NOTe- authdomain=&authdomain;'; put '%put NOTE-;'; put 'libname &libref TERADATA server="&path" schema=&schema authdomain=&authdomain;'; put '%end;'; put '%else %if &engine= %then %do;'; put '%put NOTE: Libref &libref is not registered in metadata;'; put '%&mAbort.mp_abort('; put 'msg=%str(ERR)OR: Libref &libref is not registered in metadata'; put ',mac=mm_assigndirectlib.sas);'; put '%return;'; put '%end;'; put '%else %do;'; put '%put %str(WARN)ING: Engine &engine is currently unsupported;'; put '%put %str(WARN)ING- Please contact your support team.;'; put '%return;'; put '%end;'; put '%mend mm_assigndirectlib;'; put '%macro mm_getrepos('; put 'outds=work.mm_getrepos'; put ')/*/STORE SOURCE*/;'; put '* use a temporary fileref to hold the response;'; put 'filename response temp;'; put '/* get list of libraries */'; put 'proc metadata in='; put '"1"'; put 'out=response;'; put 'run;'; put '/* write the response to the log for debugging */'; put '/*'; put 'data _null_;'; put 'infile response lrecl=1048576;'; put 'input;'; put 'put _infile_;'; put 'run;'; put '*/'; put '/* create an XML map to read the response */'; put 'filename sxlemap temp;'; put 'data _null_;'; put 'file sxlemap;'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository";'; put 'put "";'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Id";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Name";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Desc";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@DefaultNS";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@RepositoryType";'; put 'put "characterstring20";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@RepositoryFormat";'; put 'put "characterstring10";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@Access";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@CurrentAccess";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@PauseState";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Path";'; put 'put "";'; put 'put "characterstring256";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Engine";'; put 'put "";'; put 'put "characterstring8";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Options";'; put 'put "";'; put 'put "characterstring32";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@MetadataCreated";'; put 'put "characterstring24";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@MetadataUpdated";'; put 'put "characterstring24";'; put 'put '''';'; put 'put ''
'';'; put 'run;'; put 'libname _XML_ xml xmlfileref=response xmlmap=sxlemap;'; put 'proc sort data= _XML_.SASRepos out=&outds;'; put 'by name;'; put 'run;'; put '/* clear references */'; put 'filename sxlemap clear;'; put 'filename response clear;'; put 'libname _XML_ clear;'; put '%mend mm_getrepos;'; put '%macro dc_assignlib(type,libref,passthru=);'; put '%put &sysmacroname entry vars:;'; put '%put _local_;'; put '/* take current repository */'; put '%local repo repocnt x;'; put '%let repo=%sysfunc(getoption(metarepository));'; put '/* get list of repositories and filter */'; put '%mm_getrepos(outds=repos)'; put '%let repocnt=0;'; put 'data repos;'; put 'set repos;'; put 'where repositorytype in(''CUSTOM'',''FOUNDATION'')'; put '& upcase(name) ne "%upcase(&repo)";'; put 'keep id name ;'; put 'call symputx(cats(''repo'',_n_),name,''l'');'; put 'call symputx(''repocnt'',_n_,''l'');'; put 'run;'; put '/* find out which of these repositories has the libref we are searching for */'; put '%local lib_uri;'; put '%let lib_uri=NOTFOUND;'; put 'data _null_; /* check default repo first */'; put 'length lib_uri $256;'; put 'call missing (of _all_);'; put 'rc=metadata_getnobj("omsobj:SASLibrary?@Libref =''&libref''",1,lib_uri);'; put 'call symputx(''lib_uri'',coalescec(lib_uri,''NOTFOUND''),''l'');'; put 'run;'; put '%if &lib_uri=NOTFOUND %then %do;'; put '%do x=1 %to &repocnt;'; put 'options metarepository=&&repo&x;'; put 'data _null_;'; put 'length lib_uri $256;'; put 'call missing (of _all_);'; put 'rc=metadata_getnobj("omsobj:SASLibrary?@Libref =''&libref''",1,lib_uri);'; put 'call symputx(''lib_uri'',coalescec(lib_uri,''NOTFOUND''),''l'');'; put 'run;'; put '%if &lib_uri ne NOTFOUND %then %let x=%eval(&repocnt+1);'; put '%end;'; put '%end;'; put '%if &type=READ %then %do;'; put '%mm_assignlib(&libref)'; put '%end;'; put '%else %if &type=WRITE %then %do;'; put '%mm_assigndirectlib(&libref,open_passthrough=&passthru)'; put '%end;'; put 'options metarepository=&repo;'; put '%mend dc_assignlib;'; put '%macro mf_getattrn('; put 'libds'; put ',attr'; put ')/*/STORE SOURCE*/;'; put '%local dsid rc;'; put '%let dsid=%sysfunc(open(&libds,is));'; put '%if &dsid = 0 %then %do;'; put '%put %str(WARN)ING: Cannot open %trim(&libds), system message below;'; put '%put %sysfunc(sysmsg());'; put '-1'; put '%end;'; put '%else %do;'; put '%sysfunc(attrn(&dsid,&attr))'; put '%let rc=%sysfunc(close(&dsid));'; put '%end;'; put '%mend mf_getattrn;'; put '%macro mf_getvalue(libds,variable,filter=1'; put ')/*/STORE SOURCE*/;'; put '%if %mf_getattrn(&libds,NLOBS)>0 %then %do;'; put '%local dsid rc &variable;'; put '%let dsid=%sysfunc(open(&libds(where=(&filter))));'; put '%syscall set(dsid);'; put '%let rc = %sysfunc(fetch(&dsid));'; put '%let rc = %sysfunc(close(&dsid));'; put '%trim(&&&variable)'; put '%end;'; put '%mend mf_getvalue;'; put '%macro mddl_sas_cntlout(libds=WORK.CNTLOUT);'; put 'proc sql;'; put 'create table &libds('; put 'TYPE char(1) label='; put '''Format Type: either N (num fmt), C (char fmt), I (num infmt) or J (char infmt)'''; put ',FMTNAME char(32) label=''Format name'''; put ',FMTROW num label='; put '''CALCULATED Position of record by FMTNAME (reqd for multilabel formats)'''; put ',START char(32767) label=''Starting value for format'''; put '/*'; put 'Keep lengths of START and END the same to avoid this err:'; put '"Start is greater than end: -<."'; put 'Similar usage note: https://support.sas.com/kb/69/330.html'; put '*/'; put ',END char(32767) label=''Ending value for format'''; put ',LABEL char(32767) label=''Format value label'''; put ',MIN num length=3 label=''Minimum length'''; put ',MAX num length=3 label=''Maximum length'''; put ',DEFAULT num length=3 label=''Default length'''; put ',LENGTH num length=3 label=''Format length'''; put ',FUZZ num label=''Fuzz value'''; put ',PREFIX char(2) label=''Prefix characters'''; put ',MULT num label=''Multiplier'''; put ',FILL char(1) label=''Fill character'''; put ',NOEDIT num length=3 label=''Is picture string noedit?'''; put ',SEXCL char(1) label=''Start exclusion'''; put ',EEXCL char(1) label=''End exclusion'''; put ',HLO char(13) label='; put '''More info: https://core.sasjs.io/mddl__sas__cntlout_8sas_source.html'''; put ',DECSEP char(1) label=''Decimal separator'''; put ',DIG3SEP char(1) label=''Three-digit separator'''; put ',DATATYPE char(8) label=''Date/time/datetime?'''; put ',LANGUAGE char(8) label=''Language for date strings'''; put ');'; put '%local lib;'; put '%let libds=%upcase(&libds);'; put '%if %index(&libds,.)=0 %then %let lib=WORK;'; put '%else %let lib=%scan(&libds,1,.);'; put 'proc datasets lib=&lib noprint;'; put 'modify %scan(&libds,-1,.);'; put 'index create'; put 'pk_cntlout=(type fmtname fmtrow)'; put '/nomiss unique;'; put 'quit;'; put '%mend mddl_sas_cntlout;'; put '%macro mf_getuniquename(prefix=MC);'; put '&prefix.%substr(%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32-%length(&prefix))'; put '%mend mf_getuniquename;'; put '%macro mf_islibds(libds'; put ')/*/STORE SOURCE*/;'; put '%local regex;'; put '%let regex=%sysfunc(prxparse(%str(/^[_a-z]\w{0,7}\.[_a-z]\w{0,31}$/i)));'; put '%sysfunc(prxmatch(®ex,&libds))'; put '%mend mf_islibds;'; put '%macro mf_nobs(libds'; put ')/*/STORE SOURCE*/;'; put '%mf_getattrn(&libds,NLOBS)'; put '%mend mf_nobs;'; put '%macro mf_getvarlist(libds'; put ',dlm=%str( )'; put ',quote=no'; put ',typefilter=A'; put ')/*/STORE SOURCE*/;'; put '/* declare local vars */'; put '%local outvar dsid nvars x rc dlm q var vtype;'; put '/* credit Rowland Hale - byte34 is double quote, 39 is single quote */'; put '%if %upcase("e)=DOUBLE %then %let q=%qsysfunc(byte(34));'; put '%else %if %upcase("e)=SINGLE %then %let q=%qsysfunc(byte(39));'; put '/* open dataset in macro */'; put '%let dsid=%sysfunc(open(&libds));'; put '%if &dsid %then %do;'; put '%let nvars=%sysfunc(attrn(&dsid,NVARS));'; put '%if &nvars>0 %then %do;'; put '/* add variables with supplied delimeter */'; put '%do x=1 %to &nvars;'; put '/* get variable type */'; put '%let vtype=%sysfunc(vartype(&dsid,&x));'; put '%if &vtype=&typefilter or &typefilter=A %then %do;'; put '%let var=&q.%sysfunc(varname(&dsid,&x))&q.;'; put '%if &var=&q&q %then %do;'; put '%put &sysmacroname: Empty column found in &libds!;'; put '%let var=&q. &q.;'; put '%end;'; put '%if %quote(&outvar)=%quote() %then %let outvar=&var;'; put '%else %let outvar=&outvar.&dlm.&var.;'; put '%end;'; put '%end;'; put '%end;'; put '%let rc=%sysfunc(close(&dsid));'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: Unable to open &libds (rc=&dsid);'; put '%put &sysmacroname: SYSMSG= %sysfunc(sysmsg());'; put '%let rc=%sysfunc(close(&dsid));'; put '%end;'; put '%do;%unquote(&outvar)%end;'; put '%mend mf_getvarlist;'; put '%macro mf_getvartype(libds /* two level name */'; put ', var /* variable name from which to return the type */'; put ')/*/STORE SOURCE*/;'; put '%local dsid vnum vtype rc;'; put '/* Open dataset */'; put '%let dsid = %sysfunc(open(&libds));'; put '%if &dsid. > 0 %then %do;'; put '/* Get variable number */'; put '%let vnum = %sysfunc(varnum(&dsid, &var));'; put '/* Get variable type (C/N) */'; put '%if(&vnum. > 0) %then %let vtype = %sysfunc(vartype(&dsid, &vnum.));'; put '%else %do;'; put '%put NOTE: Variable &var does not exist in &libds;'; put '%let vtype = %str( );'; put '%end;'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: dataset &libds not opened! (rc=&dsid);'; put '%put &sysmacroname: %sysfunc(sysmsg());'; put '%return;'; put '%end;'; put '/* Close dataset */'; put '%let rc = %sysfunc(close(&dsid));'; put '/* Return variable type */'; put '&vtype'; put '%mend mf_getvartype;'; put '%macro mp_filtergenerate(inds,outref=filter);'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(syscc=&syscc - on macro entry)'; put ')'; put 'filename &outref temp;'; put '%if %mf_nobs(&inds)=0 %then %do;'; put '/* ensure we have a default filter */'; put 'data _null_;'; put 'file &outref;'; put 'put ''1=1'';'; put 'run;'; put '%end;'; put '%else %do;'; put 'proc sort data=&inds;'; put 'by SUBGROUP_ID;'; put 'run;'; put 'data _null_;'; put 'file &outref lrecl=32800;'; put 'set &inds end=last;'; put 'by SUBGROUP_ID;'; put 'if _n_=1 then put ''(('';'; put 'else if first.SUBGROUP_ID then put +1 GROUP_LOGIC ''('';'; put 'else put +2 SUBGROUP_LOGIC;'; put 'put +4 VARIABLE_NM OPERATOR_NM RAW_VALUE;'; put 'if last.SUBGROUP_ID then put '')''@;'; put 'if last then put '')'';'; put 'run;'; put '%end;'; put '%mend mp_filtergenerate;'; put '%macro mp_filtervalidate(inref,targetds,abort=YES,outds=work.mp_filtervalidate);'; put '%mp_abort(iftrue= (&syscc ne 0 or &syserr ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(syscc=&syscc / syserr=&syserr - on macro entry)'; put ')'; put '%local fref1;'; put '%let fref1=%mf_getuniquefileref();'; put 'data _null_;'; put 'file &fref1;'; put 'infile &inref end=eof;'; put 'if _n_=1 then do;'; put 'put "proc sql;";'; put 'put "validate select * from &targetds";'; put 'put "where " ;'; put 'end;'; put 'input;'; put 'put _infile_;'; put 'putlog _infile_;'; put 'if eof then put ";quit;";'; put 'run;'; put '%inc &fref1;'; put 'data &outds;'; put 'if &sqlrc or &syscc or &syserr then do;'; put 'REASON_CD=''VALIDATION_ERR''!!''OR: ''!!'; put 'coalescec(symget(''SYSERRORTEXT''),symget(''SYSWARNINGTEXT''));'; put 'output;'; put 'end;'; put 'else stop;'; put 'run;'; put 'filename &fref1 clear;'; put '%if %mf_nobs(&outds)>0 %then %do;'; put '%if &abort=YES %then %do;'; put 'data _null_;'; put 'set &outds;'; put 'call symputx(''REASON_CD'',reason_cd,''l'');'; put 'stop;'; put 'run;'; put '%mp_abort('; put 'mac=&sysmacroname,'; put 'msg=%str(Filter validation issues.)'; put ')'; put '%end;'; put '%let syscc=1008;'; put '%end;'; put '%mend mp_filtervalidate;'; put '%macro mp_filtercheck(inds,targetds=,outds=work.badrecords,abort=YES);'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(syscc=&syscc - on macro entry)'; put ')'; put '/* Validate input column */'; put '%local vtype;'; put '%let vtype=%mf_getvartype(&inds,RAW_VALUE);'; put '%mp_abort(iftrue=(&abort=YES and &vtype ne C),'; put 'mac=&sysmacroname,'; put 'msg=%str(%str(ERR)OR: RAW_VALUE must be character)'; put ')'; put '%if &vtype ne C %then %do;'; put '%put &sysmacroname: RAW_VALUE must be character;'; put '%let syscc=42;'; put '%return;'; put '%end;'; put '/**'; put '* Sanitise the values based on valid value lists, then strip out'; put '* quotes, commas, periods and spaces.'; put '*/'; put '%local reason_cd nobs;'; put '%let nobs=0;'; put 'data &outds;'; put '/*length GROUP_LOGIC SUBGROUP_LOGIC $3 SUBGROUP_ID 8 VARIABLE_NM $32'; put 'OPERATOR_NM $10 RAW_VALUE $4000;*/'; put 'set &inds end=last;'; put 'length reason_cd $4032 vtype vtype2 $1 vnum dsid 8 tmp $4000;'; put 'drop tmp;'; put '/* quick check to ensure column exists */'; put 'if upcase(VARIABLE_NM) not in'; put '(%upcase(%mf_getvarlist(&targetds,dlm=%str(,),quote=SINGLE)))'; put 'then do;'; put 'REASON_CD="Variable "!!cats(variable_nm)!!" not in &targetds";'; put 'putlog REASON_CD= VARIABLE_NM=;'; put 'call symputx(''reason_cd'',reason_cd,''l'');'; put 'call symputx(''nobs'',_n_,''l'');'; put 'output;'; put 'return;'; put 'end;'; put '/* need to open the dataset to get the column type */'; put 'retain dsid;'; put 'if _n_=1 then dsid=open("&targetds","i");'; put 'if dsid>0 then do;'; put 'vnum=varnum(dsid,VARIABLE_NM);'; put 'if vnum<1 then do;'; put '/* should not happen as was also tested for above */'; put 'REASON_CD=cats("Variable (",VARIABLE_NM,") not found in &targetds");'; put 'putlog REASON_CD= dsid=;'; put 'call symputx(''reason_cd'',reason_cd,''l'');'; put 'call symputx(''nobs'',_n_,''l'');'; put 'output;'; put 'goto endstep;'; put 'end;'; put '/* now we can get the type */'; put 'else vtype=vartype(dsid,vnum);'; put 'end;'; put 'else do;'; put 'REASON_CD=cats("Could not open &targetds");'; put 'putlog REASON_CD= dsid=;'; put 'call symputx(''reason_cd'',reason_cd,''l'');'; put 'call symputx(''nobs'',_n_,''l'');'; put 'output;'; put 'stop;'; put 'end;'; put '/* closed list checks */'; put 'if GROUP_LOGIC not in (''AND'',''OR'') then do;'; put 'REASON_CD=''GROUP_LOGIC should be AND/OR, not:''!!cats(GROUP_LOGIC);'; put 'putlog REASON_CD= GROUP_LOGIC=;'; put 'call symputx(''reason_cd'',reason_cd,''l'');'; put 'call symputx(''nobs'',_n_,''l'');'; put 'output;'; put 'end;'; put 'if SUBGROUP_LOGIC not in (''AND'',''OR'') then do;'; put 'REASON_CD=''SUBGROUP_LOGIC should be AND/OR, not:''!!cats(SUBGROUP_LOGIC);'; put 'putlog REASON_CD= SUBGROUP_LOGIC=;'; put 'call symputx(''reason_cd'',reason_cd,''l'');'; put 'call symputx(''nobs'',_n_,''l'');'; put 'output;'; put 'end;'; put 'if mod(SUBGROUP_ID,1) ne 0 then do;'; put 'REASON_CD=''SUBGROUP_ID should be integer, not ''!!cats(subgroup_id);'; put 'putlog REASON_CD= SUBGROUP_ID=;'; put 'call symputx(''reason_cd'',reason_cd,''l'');'; put 'call symputx(''nobs'',_n_,''l'');'; put 'output;'; put 'end;'; put 'if OPERATOR_NM not in'; put '(''='',''>'',''<'',''<='',''>='',''NE'',''GE'',''LE'',''BETWEEN'',''IN'',''NOT IN'',''CONTAINS'')'; put 'then do;'; put 'REASON_CD=''Invalid OPERATOR_NM: ''!!cats(OPERATOR_NM);'; put 'putlog REASON_CD= OPERATOR_NM=;'; put 'call symputx(''reason_cd'',reason_cd,''l'');'; put 'call symputx(''nobs'',_n_,''l'');'; put 'output;'; put 'end;'; put '/* special missing logic */'; put 'if vtype=''N'' & OPERATOR_NM in (''='',''>'',''<'',''<='',''>='',''NE'',''GE'',''LE'') then do;'; put 'if cats(upcase(raw_value)) in ('; put '''.'',''.A'',''.B'',''.C'',''.D'',''.E'',''.F'',''.G'',''.H'',''.I'',''.J'',''.K'',''.L'',''.M'',''.N'''; put '''.N'',''.O'',''.P'',''.Q'',''.R'',''.S'',''.T'',''.U'',''.V'',''.W'',''.X'',''.Y'',''.Z'',''._'''; put ')'; put 'then do;'; put '/* valid numeric - exit data step loop */'; put 'return;'; put 'end;'; put 'else if subpad(upcase(raw_value),1,1) in ('; put '''A'',''B'',''C'',''D'',''E'',''F'',''G'',''H'',''I'',''J'',''K'',''L'',''M'',''N'''; put '''N'',''O'',''P'',''Q'',''R'',''S'',''T'',''U'',''V'',''W'',''X'',''Y'',''Z'',''_'''; put ')'; put 'then do;'; put '/* check if the raw_value contains a valid variable NAME */'; put 'vnum=varnum(dsid,subpad(raw_value,1,32));'; put 'if vnum>0 then do;'; put '/* now we can get the type */'; put 'vtype2=vartype(dsid,vnum);'; put '/* check type matches */'; put 'if vtype2=vtype then do;'; put '/* valid target var - exit loop */'; put 'return;'; put 'end;'; put 'else do;'; put 'REASON_CD=cats("Compared Type (",vtype2,") is not (",vtype,")");'; put 'putlog REASON_CD= dsid=;'; put 'call symputx(''reason_cd'',reason_cd,''l'');'; put 'call symputx(''nobs'',_n_,''l'');'; put 'output;'; put 'goto endstep;'; put 'end;'; put 'end;'; put 'end;'; put 'end;'; put '/* special logic */'; put 'if OPERATOR_NM in (''IN'',''NOT IN'',''BETWEEN'') then do;'; put 'if OPERATOR_NM=''BETWEEN'' then raw_value1=tranwrd(raw_value,'' AND '','','');'; put 'else do;'; put 'if substr(raw_value,1,1) ne ''('''; put 'or substr(cats(reverse(raw_value)),1,1) ne '')'''; put 'then do;'; put 'REASON_CD=''Missing start/end bracket in RAW_VALUE'';'; put 'putlog REASON_CD= OPERATOR_NM= raw_value= raw_value1= ;'; put 'call symputx(''reason_cd'',reason_cd,''l'');'; put 'call symputx(''nobs'',_n_,''l'');'; put 'output;'; put 'end;'; put 'else raw_value1=substr(raw_value,2,max(length(raw_value)-2,0));'; put 'end;'; put '/* we now have a comma seperated list of values */'; put 'if vtype=''N'' then do i=1 to countc(raw_value1, '','')+1;'; put 'tmp=scan(raw_value1,i,'','');'; put 'if cats(tmp) ne ''.'' and input(tmp, ?? 8.) eq . then do;'; put 'if OPERATOR_NM =''BETWEEN'' and subpad(upcase(tmp),1,1) in ('; put '''A'',''B'',''C'',''D'',''E'',''F'',''G'',''H'',''I'',''J'',''K'',''L'',''M'',''N'''; put '''N'',''O'',''P'',''Q'',''R'',''S'',''T'',''U'',''V'',''W'',''X'',''Y'',''Z'',''_'''; put ')'; put 'then do;'; put '/* check if the raw_value contains a valid variable NAME */'; put '/* is not valid syntax for IN or NOT IN */'; put 'vnum=varnum(dsid,subpad(tmp,1,32));'; put 'if vnum>0 then do;'; put '/* now we can get the type */'; put 'vtype2=vartype(dsid,vnum);'; put '/* check type matches */'; put 'if vtype2=vtype then do;'; put '/* valid target var - exit loop */'; put 'return;'; put 'end;'; put 'else do;'; put 'REASON_CD=cats("Compared Type (",vtype2,") is not (",vtype,")");'; put 'putlog REASON_CD= dsid=;'; put 'call symputx(''reason_cd'',reason_cd,''l'');'; put 'call symputx(''nobs'',_n_,''l'');'; put 'output;'; put 'goto endstep;'; put 'end;'; put 'end;'; put 'end;'; put 'REASON_CD=''Non Numeric value provided'';'; put 'putlog REASON_CD= OPERATOR_NM= raw_value= raw_value1= ;'; put 'call symputx(''reason_cd'',reason_cd,''l'');'; put 'call symputx(''nobs'',_n_,''l'');'; put 'output;'; put 'end;'; put 'return;'; put 'end;'; put 'end;'; put 'else raw_value1=raw_value;'; put '/* remove nested literals eg '''' */'; put 'raw_value1=tranwrd(raw_value1,"''''",'''');'; put '/* now match string literals (always single quotes) */'; put 'raw_value2=raw_value1;'; put 'regex = prxparse("s/(\'').*?(\'')//");'; put 'call prxchange(regex,-1,raw_value2);'; put '/* remove commas and periods*/'; put 'raw_value3=compress(raw_value2,'',.'');'; put '/* output records that contain values other than digits and spaces */'; put 'if notdigit(compress(raw_value3,'' ''))>0 then do;'; put 'if vtype=''C'' and subpad(upcase(raw_value),1,1) in ('; put '''A'',''B'',''C'',''D'',''E'',''F'',''G'',''H'',''I'',''J'',''K'',''L'',''M'',''N'''; put '''N'',''O'',''P'',''Q'',''R'',''S'',''T'',''U'',''V'',''W'',''X'',''Y'',''Z'',''_'''; put ')'; put 'then do;'; put '/* check if the raw_value contains a valid variable NAME */'; put 'vnum=varnum(dsid,subpad(raw_value,1,32));'; put 'if vnum>0 then do;'; put '/* now we can get the type */'; put 'vtype2=vartype(dsid,vnum);'; put '/* check type matches */'; put 'if vtype2=vtype then do;'; put '/* valid target var - exit loop */'; put 'return;'; put 'end;'; put 'else do;'; put 'REASON_CD=cats("Compared Char Type (",vtype2,") is not (",vtype,")");'; put 'putlog REASON_CD= dsid=;'; put 'call symputx(''reason_cd'',reason_cd,''l'');'; put 'call symputx(''nobs'',_n_,''l'');'; put 'output;'; put 'goto endstep;'; put 'end;'; put 'end;'; put 'end;'; put 'putlog raw_value3= $hex32.;'; put 'REASON_CD=cats(''Invalid RAW_VALUE:'',raw_value);'; put 'putlog (_all_)(=);'; put 'call symputx(''reason_cd'',reason_cd,''l'');'; put 'call symputx(''nobs'',_n_,''l'');'; put 'output;'; put 'end;'; put 'endstep:'; put 'if last then rc=close(dsid);'; put 'run;'; put 'data _null_;'; put 'set &outds end=last;'; put 'putlog (_all_)(=);'; put 'run;'; put '%mp_abort(iftrue=(&abort=YES and &nobs>0),'; put 'mac=&sysmacroname,'; put 'msg=%str(Data issue: %superq(reason_cd))'; put ')'; put '%if &nobs>0 %then %do;'; put '%let syscc=1008;'; put '%return;'; put '%end;'; put '/**'; put '* syntax checking passed but it does not mean the filter is valid'; put '* for that we can run a proc sql validate query'; put '*/'; put '%local fref1;'; put '%let fref1=%mf_getuniquefileref();'; put '%mp_filtergenerate(&inds,outref=&fref1)'; put '/* this macro will also set syscc to 1008 if any issues found */'; put '%mp_filtervalidate(&fref1,&targetds,outds=&outds,abort=&abort)'; put '%mend mp_filtercheck;'; put '%macro mp_md5(cvars=,nvars=);'; put '%local i var sep;'; put 'put(md5('; put '%do i=1 %to %sysfunc(countw(&cvars));'; put '%let var=%scan(&cvars,&i,%str( ));'; put '&sep put(md5(trim(&var)),$hex32.)'; put '%let sep=!!;'; put '%end;'; put '%do i=1 %to %sysfunc(countw(&nvars));'; put '%let var=%scan(&nvars,&i,%str( ));'; put '/* multiply by 1 to strip precision errors (eg 0 != 0) */'; put '/* but ONLY if not missing, else will lose any special missing values */'; put '&sep put(md5(trim(put(ifn(missing(&var),&var,&var*1),binary64.))),$hex32.)'; put '%let sep=!!;'; put '%end;'; put '),$hex32.)'; put '%mend mp_md5;'; put '%macro mp_hashdataset('; put 'libds,'; put 'outds=work._data_,'; put 'salt=,'; put 'iftrue=%str(1=1)'; put ')/*/STORE SOURCE*/;'; put '%local keyvar /* roll up the md5 */'; put 'prevkeyvar /* retain prev record md5 */'; put 'lastvar /* last var in input ds */'; put 'cvars nvars;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '/* avoid naming conflict for hash key vars */'; put '%let keyvar=%mf_getuniquename();'; put '%let prevkeyvar=%mf_getuniquename();'; put '%let lastvar=%mf_getuniquename();'; put '%if %mf_getattrn(&libds,NLOBS)=0 %then %do;'; put 'data &outds;'; put 'length hashkey $32;'; put 'hashkey=put(md5("&salt"),$hex32.);'; put 'output;'; put 'stop;'; put 'run;'; put '%put &sysmacroname: Dataset &libds is empty, or is not a dataset;'; put '%put &sysmacroname: hashkey of &outds is based on salt (&salt) only;'; put '%end;'; put '%else %if %mf_getattrn(&libds,NLOBS)<0 %then %do;'; put '%put %str(ERR)OR: Dataset &libds is not a dataset;'; put '%end;'; put '%else %do;'; put 'data &outds(rename=(&keyvar=hashkey) keep=&keyvar)'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5" %then %do;'; put '/nonote2err'; put '%end;'; put ';'; put 'length &prevkeyvar &keyvar $32;'; put 'retain &prevkeyvar;'; put 'if _n_=1 then &prevkeyvar=put(md5("&salt"),$hex32.);'; put 'set &libds end=&lastvar;'; put '/* hash should include previous row */'; put '&keyvar=%mp_md5('; put 'cvars=%mf_getvarlist(&libds,typefilter=C) &prevkeyvar,'; put 'nvars=%mf_getvarlist(&libds,typefilter=N)'; put ');'; put '&prevkeyvar=&keyvar;'; put 'if &lastvar then output;'; put 'run;'; put '%end;'; put '%mend mp_hashdataset;'; put '/** @cond */'; put '%macro mf_existvar(libds /* 2 part dataset name */'; put ', var /* variable name */'; put ')/*/STORE SOURCE*/;'; put '%local dsid rc;'; put '%let dsid=%sysfunc(open(&libds,is));'; put '%if &dsid=0 %then %do;'; put '%put %sysfunc(sysmsg());'; put '0'; put '%end;'; put '%else %if %length(&var)=0 %then %do;'; put '0'; put '%let rc=%sysfunc(close(&dsid));'; put '%end;'; put '%else %do;'; put '%sysfunc(varnum(&dsid,&var))'; put '%let rc=%sysfunc(close(&dsid));'; put '%end;'; put '%mend mf_existvar;'; put '/** @endcond */'; put '%macro mf_getquotedstr(IN_STR'; put ',DLM=%str(,)'; put ',QUOTE=S'; put ',indlm=%str( )'; put ')/*/STORE SOURCE*/;'; put '/* credit Rowland Hale - byte34 is double quote, 39 is single quote */'; put '%if "e=S %then %let quote=%qsysfunc(byte(39));'; put '%else %if "e=D %then %let quote=%qsysfunc(byte(34));'; put '%else %if "e=N %then %let quote=;'; put '%local i item buffer;'; put '%let i=1;'; put '%do %while (%qscan(&IN_STR,&i,%str(&indlm)) ne %str() ) ;'; put '%let item=%qscan(&IN_STR,&i,%str(&indlm));'; put '%if %bquote("E) ne %then %let item="E%qtrim(&item)"E;'; put '%else %let item=%qtrim(&item);'; put '%if (&i = 1) %then %let buffer =%qtrim(&item);'; put '%else %let buffer =&buffer&DLM%qtrim(&item);'; put '%let i = %eval(&i+1);'; put '%end;'; put '%let buffer=%sysfunc(coalescec(%qtrim(&buffer),"E"E));'; put '&buffer'; put '%mend mf_getquotedstr;'; put '%macro mf_getattrc('; put 'libds'; put ',attr'; put ')/*/STORE SOURCE*/;'; put '%local dsid rc;'; put '%let dsid=%sysfunc(open(&libds,is));'; put '%if &dsid = 0 %then %do;'; put '%put %str(WARN)ING: Cannot open %trim(&libds), system message below;'; put '%put %sysfunc(sysmsg());'; put '-1'; put '%end;'; put '%else %do;'; put '%sysfunc(attrc(&dsid,&attr))'; put '%let rc=%sysfunc(close(&dsid));'; put '%end;'; put '%mend mf_getattrc;'; put '%macro mp_lockfilecheck('; put 'libds'; put ')/*/STORE SOURCE*/;'; put 'data _null_;'; put 'if _n_=1 then putlog "&sysmacroname entry vars:";'; put 'set sashelp.vmacro;'; put 'where scope="&sysmacroname";'; put 'put name ''='' value;'; put 'run;'; put '%mp_abort(iftrue= (&syscc>0)'; put ',mac=checklock.sas'; put ',msg=Aborting with syscc=&syscc on entry.'; put ')'; put '%mp_abort(iftrue= ("&libds"="0")'; put ',mac=&sysmacroname'; put ',msg=%str(libds not provided)'; put ')'; put '%local msg lib ds;'; put '%let lib=%upcase(%scan(&libds,1,.));'; put '%let ds=%upcase(%scan(&libds,2,.));'; put '/* in DC, format catalogs are passed with a -FC suffix. No saslock here! */'; put '%if %scan(&libds,2,-)=FC %then %do;'; put '%put &sysmacroname: Format Catalog detected, no lockfile applied to &libds;'; put '%return;'; put '%end;'; put '/* do not proceed if no observations can be processed */'; put '%let msg=options obs = 0. syserrortext=%superq(syserrortext);'; put '%mp_abort(iftrue= (%sysfunc(getoption(OBS))=0)'; put ',mac=checklock.sas'; put ',msg=%superq(msg)'; put ')'; put 'data _null_;'; put 'putlog "Checking engine & member type";'; put 'run;'; put '%local engine memtype;'; put '%let memtype=%mf_getattrc(&libds,MTYPE);'; put '%let engine=%mf_getattrc(&libds,ENGINE);'; put '%if &engine ne V9 and &engine ne BASE %then %do;'; put 'data _null_;'; put 'putlog "Lib &lib is not assigned using BASE engine - uses &engine instead";'; put 'putlog "SAS lock check will not be performed";'; put 'run;'; put '%return;'; put '%end;'; put '%else %if &memtype ne DATA %then %do;'; put '%put NOTE: Cannot lock a VIEW!! Memtype=&memtype;'; put '%return;'; put '%end;'; put 'data _null_;'; put 'putlog "Engine = &engine, memtype=&memtype";'; put 'putlog "Attempting lock statement";'; put 'run;'; put 'lock &libds;'; put '%local abortme;'; put '%let abortme=0;'; put '%if &syscc>0 or &SYSLCKRC ne 0 %then %do;'; put '%let msg=Unable to apply lock on &libds (SYSLCKRC=&SYSLCKRC syscc=&syscc);'; put '%put %str(ERR)OR: &sysmacroname: &msg;'; put '%let abortme=1;'; put '%end;'; put 'lock &libds clear;'; put '%mp_abort(iftrue= (&abortme=1)'; put ',mac=&sysmacroname'; put ',msg=%superq(msg)'; put ')'; put '%mend mp_lockfilecheck;'; put '%macro mp_lockanytable('; put 'action'; put ',lib= WORK'; put ',ds=0'; put ',ref='; put ',ctl_ds=0'; put ',loops=25'; put ',loop_secs=1'; put ');'; put 'data _null_;'; put 'if _n_=1 then putlog "&sysmacroname entry vars:";'; put 'set sashelp.vmacro;'; put 'where scope="&sysmacroname";'; put 'put name ''='' value;'; put 'run;'; put '%mp_abort(iftrue= ("&ds"="0" and &action ne MAKETABLE)'; put ',mac=&sysmacroname'; put ',msg=%str(dataset was not provided)'; put ')'; put '%mp_abort(iftrue= (&ctl_ds=0)'; put ',mac=&sysmacroname'; put ',msg=%str(Control dataset was not provided)'; put ')'; put '/* set up lib & mac vars */'; put '%let lib=%upcase(&lib);'; put '%let ds=%upcase(&ds);'; put '%let action=%upcase(&action);'; put '%local user x trans msg abortme;'; put '%let user=%mf_getuser();'; put '%let abortme=0;'; put '%mp_abort(iftrue= (&action ne LOCK & &action ne UNLOCK & &action ne MAKETABLE)'; put ',mac=&sysmacroname'; put ',msg=%str(Invalid action (&action) provided)'; put ')'; put '/* if an err condition exists, exit before we even begin */'; put '%mp_abort(iftrue= (&syscc>0 and &action=LOCK)'; put ',mac=&sysmacroname'; put ',msg=%str(aborting due to syscc=&syscc on LOCK entry)'; put ')'; put '/* do not bother locking work tables (else may affect all WORK libraries) */'; put '%if (%upcase(&lib)=WORK or %str(&lib)=%str()) & &action ne MAKETABLE %then %do;'; put '%put NOTE: WORK libraries will not be registered in the locking system.;'; put '%return;'; put '%end;'; put '/* do not proceed if no observations can be processed */'; put '%mp_abort(iftrue= (%sysfunc(getoption(OBS))=0)'; put ',mac=&sysmacroname'; put ',msg=%str(cannot continue when options obs = 0)'; put ')'; put '%if &ACTION=LOCK %then %do;'; put '/* abort if a SAS lock is already in place, or cannot be applied */'; put '%mp_lockfilecheck(&lib..&ds)'; put '/* next, check there is a record for this table */'; put '%local record_exists_check;'; put 'proc sql noprint;'; put 'select count(*) into: record_exists_check from &ctl_ds'; put 'where LOCK_LIB ="&lib" and LOCK_DS="&ds";'; put 'quit;'; put '%if &syscc>0 %then %put syscc=&syscc sqlrc=&sqlrc;'; put '%if &record_exists_check=0 %then %do;'; put 'data _null_;'; put 'putlog "&sysmacroname: adding record to lock table..";'; put 'run;'; put 'data ;'; put 'if 0 then set &ctl_ds;'; put 'LOCK_LIB ="&lib";'; put 'LOCK_DS="&ds";'; put 'LOCK_STATUS_CD=''LOCKED'';'; put 'LOCK_START_DTTM="%sysfunc(datetime(),%mf_fmtdttm())"dt;'; put 'LOCK_USER_NM="&user";'; put 'LOCK_PID="&sysjobid";'; put 'LOCK_REF="&ref";'; put 'output;stop;'; put 'run;'; put '%let trans=&syslast;'; put 'proc append base=&ctl_ds data=&trans;'; put 'run;'; put '%end;'; put '/* if record does exist, perform lock attempts */'; put '%else %do x=1 %to &loops;'; put 'data _null_;'; put 'putlog "&sysmacroname: attempting lock (iteration &x) "@;'; put 'putlog "at %sysfunc(datetime(),datetime19.) ..";'; put 'run;'; put 'proc sql;'; put 'update &ctl_ds'; put 'set LOCK_STATUS_CD=''LOCKED'''; put ', LOCK_START_DTTM="%sysfunc(datetime(),%mf_fmtdttm())"dt'; put ', LOCK_USER_NM="&user"'; put ', LOCK_PID="&sysjobid"'; put ', LOCK_REF="&ref"'; put 'where LOCK_LIB ="&lib" and LOCK_DS="&ds";'; put 'quit;'; put '/**'; put '* NOTE - occasionally SQL server will return an err code (deadlocked'; put '* transaction). If so, ignore it, keep calm, and carry on..'; put '*/'; put '%if &syscc>0 %then %do;'; put 'data _null_;'; put 'putlog ''NOTE-'' / ''NOTE-'';'; put 'putlog "NOTE- &sysmacroname: Update failed. "@;'; put 'putlog "Resetting err conditions and re-attempting.";'; put 'putlog "NOTE- syscc=&syscc syserr=&syserr sqlrc=&sqlrc";'; put 'putlog ''NOTE-'' / ''NOTE-'';'; put 'run;'; put '%let syscc=0;'; put '%let sqlrc=0;'; put '%end;'; put '/* now check if the record was successfully updated */'; put '%local success_check;'; put 'proc sql noprint;'; put 'select count(*) into: success_check from &ctl_ds'; put 'where LOCK_LIB ="&lib" and LOCK_DS="&ds"'; put 'and LOCK_PID="&sysjobid" and LOCK_STATUS_CD=''LOCKED'';'; put 'quit;'; put '%if &success_check=0 %then %do;'; put '%if &x < &loops %then %do;'; put '/* pause before next check */'; put 'data _null_;'; put 'putlog ''NOTE-'' / ''NOTE-'';'; put 'putlog "NOTE- &sysmacroname: table locked, waiting "@;'; put 'putlog "%sysfunc(sleep(&loop_secs)) seconds.. ";'; put 'putlog "NOTE- (iteration &x of &loops)";'; put 'putlog ''NOTE-'' / ''NOTE-'';'; put 'run;'; put '%end;'; put '%else %do;'; put '%let msg=Unable to lock &lib..&ds via &ctl_ds after &loops attempts.\n'; put 'Please ask your administrator to investigate!;'; put '%let abortme=1;'; put '%end;'; put '%end;'; put '%else %do;'; put 'data _null_;'; put 'putlog ''NOTE-'' / ''NOTE-'';'; put 'putlog "NOTE- &sysmacroname: Table &lib..&ds locked at "@;'; put 'putlog " %sysfunc(datetime(),datetime19.) (iteration &x)"@;'; put 'putlog ''NOTE-'' / ''NOTE-'';'; put 'run;'; put '%if &syscc>0 %then %do;'; put '%put setting syscc(&syscc) back to 0;'; put '%let syscc=0;'; put '%end;'; put '%let x=&loops; /* no more iterations needed */'; put '%end;'; put '%end;'; put '%end;'; put '%else %if &ACTION=UNLOCK %then %do;'; put '%local status cnt;'; put '%let cnt=0;'; put 'proc sql noprint;'; put 'select count(*) into: cnt from &ctl_ds where LOCK_LIB ="&lib" & LOCK_DS="&ds";'; put '%if &cnt=0 %then %do;'; put '%put %str(WAR)NING: &lib..&ds was not previously locked in &ctl_ds!;'; put '%end;'; put '%else %do;'; put 'select LOCK_STATUS_CD into: status from &ctl_ds'; put 'where LOCK_LIB ="&lib" and LOCK_DS="&ds";'; put 'quit;'; put '%if &syscc>0 %then %put syscc=&syscc sqlrc=&sqlrc;'; put '%if &status=LOCKED %then %do;'; put 'data _null_;'; put 'putlog "&sysmacroname: unlocking &lib..&ds:";'; put 'run;'; put 'proc sql;'; put 'update &ctl_ds'; put 'set LOCK_STATUS_CD=''UNLOCKED'''; put ', LOCK_END_DTTM="%sysfunc(datetime(),%mf_fmtdttm())"dt'; put ', LOCK_USER_NM="&user"'; put ', LOCK_PID="&sysjobid"'; put ', LOCK_REF="&ref"'; put 'where LOCK_LIB ="&lib" and LOCK_DS="&ds";'; put 'quit;'; put '%end;'; put '%else %if &status=UNLOCKED %then %do;'; put '%put %str(WAR)NING: &lib..&ds is already unlocked!;'; put '%end;'; put '%else %do;'; put '%put NOTE: Unrecognised STATUS_CD (&status) in &ctl_ds;'; put '%let abortme=1;'; put '%end;'; put '%end;'; put '%end;'; put '%else %do;'; put '%let msg=lock_anytable given unsupported action (&action);'; put '%let abortme=1;'; put '%end;'; put '/* catch errs - mp_abort must be called outside of a logic block */'; put '%mp_abort(iftrue=(&abortme=1),'; put 'msg=%superq(msg),'; put 'mac=&sysmacroname'; put ')'; put '%exit_macro:'; put 'data _null_;'; put 'put "&sysmacroname: Exit vars: action=&action lib=&lib ds=&ds";'; put 'put " syscc=&syscc sqlrc=&sqlrc syserr=&syserr";'; put 'run;'; put '%mend mp_lockanytable;'; put '%macro mp_retainedkey('; put 'base_lib=WORK'; put ',base_dsn=BASETABLE'; put ',append_lib=WORK'; put ',append_dsn=APPENDTABLE'; put ',retained_key=DEFAULT_RK'; put ',business_key= PK1 PK2'; put ',check_uniqueness=NO'; put ',maxkeytable=0'; put ',locktable=0'; put ',outds=WORK.APPEND'; put ',filter_str='; put ');'; put '%put &sysmacroname entry vars:;'; put '%put _local_;'; put '%local base_libds app_libds key_field check maxkey idx_pk newkey_cnt iserr'; put 'msg x tempds1 tempds2 comma_pk appnobs checknobs dropvar tempvar idx_val;'; put '%let base_libds=%upcase(&base_lib..&base_dsn);'; put '%let app_libds=%upcase(&append_lib..&append_dsn);'; put '%let tempds1=%mf_getuniquename();'; put '%let tempds2=%mf_getuniquename();'; put '%let comma_pk=%mf_getquotedstr(in_str=%str(&business_key),dlm=%str(,),quote=);'; put '%let outds=%sysfunc(ifc(%index(&outds,.)=0,work.&outds,&outds));'; put '/* validation checks */'; put '%let iserr=0;'; put '%if &syscc>0 %then %do;'; put '%let iserr=1;'; put '%let msg=%str(SYSCC=&syscc on macro entry);'; put '%end;'; put '%else %if %sysfunc(exist(&base_libds))=0 %then %do;'; put '%let iserr=1;'; put '%let msg=%str(Base LIBDS (&base_libds) expected but NOT FOUND);'; put '%end;'; put '%else %if %sysfunc(exist(&app_libds))=0 %then %do;'; put '%let iserr=1;'; put '%let msg=%str(Append LIBDS (&app_libds) expected but NOT FOUND);'; put '%end;'; put '%else %if &maxkeytable ne 0 and %sysfunc(exist(&maxkeytable))=0 %then %do;'; put '%let iserr=1;'; put '%let msg=%str(Maxkeytable (&maxkeytable) expected but NOT FOUND);'; put '%end;'; put '%else %if &maxkeytable ne 0 and %sysfunc(exist(&locktable))=0 %then %do;'; put '%let iserr=1;'; put '%let msg=%str(Locktable (&locktable) expected but NOT FOUND);'; put '%end;'; put '%else %if %length(&business_key)=0 %then %do;'; put '%let iserr=1;'; put '%let msg=%str(Business key (&business_key) expected but NOT FOUND);'; put '%end;'; put '%do x=1 %to %sysfunc(countw(&business_key));'; put '/* check business key values exist */'; put '%let key_field=%scan(&business_key,&x,%str( ));'; put '%if not %mf_existvar(&app_libds,&key_field) %then %do;'; put '%let iserr=1;'; put '%let msg=Business key (&key_field) not found on &app_libds!;'; put '%goto err;'; put '%end;'; put '%else %if not %mf_existvar(&base_libds,&key_field) %then %do;'; put '%let iserr=1;'; put '%let msg=Business key (&key_field) not found on &base_libds!;'; put '%goto err;'; put '%end;'; put '%end;'; put '%err:'; put '%if &iserr=1 %then %do;'; put '/* err case so first perform an unlock of the base table before exiting */'; put '%mp_lockanytable('; put 'UNLOCK,lib=&base_lib,ds=&base_dsn,ref=%superq(msg),ctl_ds=&locktable'; put ')'; put '%end;'; put '%mp_abort(iftrue=(&iserr=1),mac=mp_retainedkey,msg=%superq(msg))'; put 'proc sql noprint;'; put 'select sum(max(&retained_key),0) into: maxkey from &base_libds;'; put '/**'; put '* get base table RK and bus field values for lookup'; put '*/'; put 'proc sql noprint;'; put 'create table &tempds1 as'; put 'select distinct &comma_pk,&retained_key'; put 'from &base_libds &filter_str'; put 'order by &comma_pk,&retained_key;'; put '%if &check_uniqueness=YES %then %do;'; put 'select count(*) into:checknobs'; put 'from (select distinct &comma_pk from &app_libds);'; put 'select count(*) into: appnobs from &app_libds; /* might be view */'; put '%if &checknobs ne &appnobs %then %do;'; put '%let msg=Source table &app_libds is not unique on (&business_key);'; put '%let iserr=1;'; put '%end;'; put '%end;'; put '%if &iserr=1 %then %do;'; put '/* err case so first perform an unlock of the base table before exiting */'; put '%mp_lockanytable('; put 'UNLOCK,lib=&base_lib,ds=&base_dsn,ref=%superq(msg),ctl_ds=&locktable'; put ')'; put '%end;'; put '%mp_abort(iftrue= (&iserr=1),mac=mp_retainedkey,msg=%superq(msg))'; put '%if %mf_existvar(&app_libds,&retained_key)'; put '%then %let dropvar=(drop=&retained_key);'; put '/* prepare interim table with retained key populated for matching keys */'; put 'proc sql noprint;'; put 'create table &tempds2 as'; put 'select b.&retained_key, a.*'; put 'from &app_libds &dropvar a'; put 'left join &tempds1 b'; put 'on 1'; put '%do idx_pk=1 %to %sysfunc(countw(&business_key));'; put '%let idx_val=%scan(&business_key,&idx_pk);'; put 'and a.&idx_val=b.&idx_val'; put '%end;'; put 'order by &retained_key;'; put '/* identify the number of entries without retained keys (new records) */'; put 'select count(*) into: newkey_cnt'; put 'from &tempds2'; put 'where missing(&retained_key);'; put 'quit;'; put '/**'; put '* Update maxkey table if link provided'; put '*/'; put '%if &maxkeytable ne 0 %then %do;'; put 'proc sql noprint;'; put 'select count(*) into: check from &maxkeytable'; put 'where upcase(keytable)="&base_libds";'; put '%mp_lockanytable(LOCK'; put ',lib=%scan(&maxkeytable,1,.)'; put ',ds=%scan(&maxkeytable,2,.)'; put ',ref=Updating maxkeyvalues with mp_retainedkey'; put ',ctl_ds=&locktable'; put ')'; put 'proc sql;'; put '%if &check=0 %then %do;'; put 'insert into &maxkeytable'; put 'set keytable="&base_libds"'; put ',keycolumn="&retained_key"'; put ',max_key=%eval(&maxkey+&newkey_cnt)'; put ',processed_dttm="%sysfunc(datetime(),%mf_fmtdttm())"dt;'; put '%end;'; put '%else %do;'; put 'update &maxkeytable'; put 'set max_key=%eval(&maxkey+&newkey_cnt)'; put ',processed_dttm="%sysfunc(datetime(),%mf_fmtdttm())"dt'; put 'where keytable="&base_libds";'; put '%end;'; put '%mp_lockanytable(UNLOCK'; put ',lib=%scan(&maxkeytable,1,.)'; put ',ds=%scan(&maxkeytable,2,.)'; put ',ref=Updating maxkeyvalues with maxkey=%eval(&maxkey+&newkey_cnt)'; put ',ctl_ds=&locktable'; put ')'; put '%end;'; put '/* fill in the missing retained key values */'; put '%let tempvar=%mf_getuniquename();'; put 'data &outds(drop=&tempvar);'; put 'retain &tempvar %eval(&maxkey+1);'; put 'set &tempds2;'; put 'if &retained_key =. then &retained_key=&tempvar;'; put '&tempvar=&tempvar+1;'; put 'run;'; put '%mend mp_retainedkey;'; put '%macro mp_filterstore(libds=,'; put 'queryds=work.filterquery,'; put 'filter_summary=PERM.FILTER_SUMMARY,'; put 'filter_detail=PERM.FILTER_DETAIL,'; put 'lock_table=PERM.LOCK_TABLE,'; put 'maxkeytable=PERM.MAXKEYTABLE,'; put 'outresult=work.result,'; put 'outquery=work.query,'; put 'mdebug=1'; put ');'; put '%put &sysmacroname entry vars:;'; put '%put _local_;'; put '%local ds0 ds1 ds2 ds3 ds4 filter_hash orig_libds;'; put '%let libds=%upcase(&libds);'; put '%let orig_libds=&libds;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=mp_filterstore'; put ',msg=%str(syscc=&syscc on macro entry)'; put ')'; put '%mp_abort(iftrue= (%mf_islibds(&filter_summary)=0)'; put ',mac=mp_filterstore'; put ',msg=%str(Invalid filter_summary value: &filter_summary)'; put ')'; put '%mp_abort(iftrue= (%mf_islibds(&filter_detail)=0)'; put ',mac=mp_filterstore'; put ',msg=%str(Invalid filter_detail value: &filter_detail)'; put ')'; put '%mp_abort(iftrue= (%mf_islibds(&lock_table)=0)'; put ',mac=mp_filterstore'; put ',msg=%str(Invalid lock_table value: &lock_table)'; put ')'; put '/**'; put '* validate query'; put '* use format catalog export, if a format'; put '*/'; put '%if "%substr(&libds,%length(&libds)-2,3)"="-FC" %then %do;'; put '%let libds=%scan(&libds,1,-); /* chop off -FC extension */'; put '%let ds0=%mf_getuniquename(prefix=fmtds_);'; put '%let libds=&ds0;'; put '/*'; put 'There is no need to export the entire format catalog here - the validations'; put 'are done against the data model, not the data values. So we can simply'; put 'hardcode the structure based on the cntlout dataset.'; put '*/'; put '%mddl_sas_cntlout(libds=&ds0)'; put '%end;'; put '%mp_filtercheck(&queryds,targetds=&libds,abort=YES)'; put '/* hash the result */'; put '%let ds1=%mf_getuniquename(prefix=hashds);'; put '%mp_hashdataset(&queryds,outds=&ds1,salt=&orig_libds)'; put '%let filter_hash=%upcase(%mf_getvalue(&ds1,hashkey));'; put '%if &mdebug=1 %then %do;'; put 'data _null_;'; put 'putlog "filter_hash=&filter_hash";'; put 'set &ds1;'; put 'putlog (_all_)(=);'; put 'run;'; put '%end;'; put '/* check if data already exists for this hash */'; put 'data &outresult;'; put 'set &filter_summary;'; put 'where filter_hash="&filter_hash";'; put 'run;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=mp_filterstore'; put ',msg=%str(syscc=&syscc after hash check)'; put ')'; put '%mp_abort(iftrue= ("&filter_hash "=" ")'; put ',mac=mp_filterstore'; put ',msg=%str(problem with filter_hash generation)'; put ')'; put '%if %mf_nobs(&outresult)=0 %then %do;'; put '/* first update summary table */'; put '%let ds3=%mf_getuniquename(prefix=filtersum);'; put 'data work.&ds3;'; put 'if 0 then set &filter_summary;'; put 'filter_table="&orig_libds";'; put 'filter_hash="&filter_hash";'; put 'PROCESSED_DTTM=%sysfunc(datetime());'; put 'output;'; put 'stop;'; put 'run;'; put '%mp_lockanytable(LOCK,'; put 'lib=%scan(&filter_summary,1,.)'; put ',ds=%scan(&filter_summary,2,.)'; put ',ref=MP_FILTERSTORE summary update - &filter_hash'; put ',ctl_ds=&lock_table'; put ')'; put '%let ds4=%mf_getuniquename(prefix=filtersumappend);'; put '%mp_retainedkey('; put 'base_lib=%scan(&filter_summary,1,.)'; put ',base_dsn=%scan(&filter_summary,2,.)'; put ',append_lib=work'; put ',append_dsn=&ds3'; put ',retained_key=filter_rk'; put ',business_key=filter_hash'; put ',maxkeytable=&maxkeytable'; put ',locktable=&lock_table'; put ',outds=work.&ds4'; put ')'; put 'proc append base=&filter_summary data=&ds4;'; put 'run;'; put '%mp_lockanytable(UNLOCK,'; put 'lib=%scan(&filter_summary,1,.)'; put ',ds=%scan(&filter_summary,2,.)'; put ',ref=MP_FILTERSTORE summary update - &filter_hash'; put ',ctl_ds=&lock_table'; put ')'; put '%if &syscc ne 0 %then %do;'; put 'data _null_;'; put 'set &ds4;'; put 'putlog (_all_)(=);'; put 'run;'; put '%goto err;'; put '%end;'; put 'data &outresult;'; put 'set &filter_summary;'; put 'where filter_hash="&filter_hash";'; put 'run;'; put '/* Next, update detail table */'; put '%let ds2=%mf_getuniquename(prefix=filterdetail);'; put 'data &ds2;'; put 'if 0 then set &filter_detail;'; put 'set &queryds;'; put 'format filter_hash $hex32. filter_line 8.;'; put 'filter_hash="&filter_hash";'; put 'filter_line=_n_;'; put 'PROCESSED_DTTM=%sysfunc(datetime());'; put 'run;'; put '%mp_lockanytable(LOCK,'; put 'lib=%scan(&filter_detail,1,.)'; put ',ds=%scan(&filter_detail,2,.)'; put ',ref=MP_FILTERSTORE update - &filter_hash'; put ',ctl_ds=&lock_table'; put ')'; put 'proc append base=&filter_detail data=&ds2;'; put 'run;'; put '%mp_lockanytable(UNLOCK,'; put 'lib=%scan(&filter_detail,1,.)'; put ',ds=%scan(&filter_detail,2,.)'; put ',ref=MP_FILTERSTORE detail update &filter_hash'; put ',ctl_ds=&lock_table'; put ')'; put '%if &syscc ne 0 %then %do;'; put 'data _null_;'; put 'set &ds2;'; put 'putlog (_all_)(=);'; put 'run;'; put '%goto err;'; put '%end;'; put '%end;'; put 'proc sort data=&filter_detail(where=(filter_hash="&filter_hash")) out=&outquery;'; put 'by filter_line;'; put 'run;'; put '%err:'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=mp_filterstore'; put ',msg=%str(syscc=&syscc on macro exit)'; put ')'; put '%mend mp_filterstore;'; put '* SAS Macros end;'; put '* SAS Includes start;'; put '* SAS Includes end;'; put '* Binary Files start;'; put '* Binary Files end;'; put '* ServiceInit start;'; put 'options noquotelenmax ps=max;'; put '/* create dummy macros to prevent stpbegin / stpend from corrupting sessions */'; put '%macro stpbegin();'; put '%put NOTE: the STPBEGIN macro should not be used for web apps!;'; put '%mend stpbegin;'; put '%macro stpend();'; put '%put NOTE: the STPEND macro should not be used for web apps!;'; put '%mend stpend;'; put '* ServiceInit end;'; put '* Service start;'; put '/**'; put '@file'; put '@brief Validates a filter clause before it gets hashified, returns the RK'; put '@details Used to generate a FILTER_RK from an input query dataset.'; put 'Raw values are stored in dc.mpe_filtersource and the meta values are stored'; put 'in dc.mpe_filteranytable'; put '

Service Inputs

'; put '
IWANT
'; put '|FILTER_TABLE:$41.|'; put '|---|'; put '|DC258467.MPE_X_TEST|'; put '
FILTERQUERY
'; put '|GROUP_LOGIC:$3|SUBGROUP_LOGIC:$3|SUBGROUP_ID:8.|VARIABLE_NM:$32|OPERATOR_NM:$10|RAW_VALUE:$32767|'; put '|---|---|---|---|---|---|'; put '|AND|AND|1|SOME_BESTNUM|>|1|'; put '|AND|AND|1|SOME_TIME|=|77333|'; put '

Service Outputs

'; put '
result
'; put '@li FILTER_HASH'; put '@li FILTER_RK'; put '@li FILTER_TABLE'; put '

SAS Macros

'; put '@li dc_assignlib.sas'; put '@li mf_getvalue.sas'; put '@li mp_filterstore.sas'; put '@li removecolsfromwork.sas'; put '@version 9.2'; put '@author 4GL Apps Ltd'; put '@copyright 4GL Apps Ltd. This code may only be used within Data Controller'; put 'and may not be re-distributed or re-sold without the express permission of'; put '4GL Apps Ltd.'; put '**/'; put '%mpeinit()'; put '%let ds=%upcase(%mf_getvalue(work.iwant,filter_table));'; put '%dc_assignlib(WRITE,%scan(&ds,1,.))'; put '%mp_filterstore('; put 'libds=&ds,'; put 'queryds=work.filterquery,'; put 'filter_summary=&dc_libref..mpe_filteranytable,'; put 'filter_detail=&dc_libref..mpe_filtersource,'; put 'lock_table=&dc_libref..mpe_lockanytable,'; put 'maxkeytable=&dc_libref..mpe_maxkeyvalues,'; put 'outresult=work.result,'; put 'outquery=work.query, /* not used */'; put 'mdebug=1'; put ')'; put '%removecolsfromwork(___TMP___MD5)'; put 'proc sql;'; put 'alter table work.result drop PROCESSED_DTTM;'; put '%webout(OPEN)'; put '%webout(OBJ,result)'; put '%webout(CLOSE)'; put '%mpeterm()'; put '* Service end;'; run; %mm_createwebservice(path=&appLoc/&path, name=&service, code=sascode, server=&serverName, replace=yes) filename sascode clear; %let service=viewdata; filename sascode temp lrecl=32767; data _null_; file sascode; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '/**'; put '@file mp_jsonout.sas'; put '@brief Writes JSON in SASjs format to a fileref'; put '@details This macro can be used to OPEN a JSON stream and send one or more'; put 'tables as arrays of rows, where each row can be an object or a nested array.'; put 'There are two engines available - DATASTEP or PROCJSON.'; put 'PROC JSON is fast but will produce errs like the ones below if'; put 'special chars are encountered.'; put '> (ERR)OR: Some code points did not transcode.'; put '> An object or array close is not valid at this point in the JSON text.'; put '> Date value out of range'; put 'If this happens, try running with ENGINE=DATASTEP.'; put 'The DATASTEP engine is used to handle special SAS missing numerics, and'; put 'can also convert entire datasets to formatted values. Output JSON is always'; put 'in UTF-8.'; put 'Usage:'; put 'filename tmp temp;'; put 'data class; set sashelp.class;run;'; put '%mp_jsonout(OPEN,jref=tmp)'; put '%mp_jsonout(OBJ,class,jref=tmp)'; put '%mp_jsonout(OBJ,class,dslabel=class2,jref=tmp,showmeta=Y)'; put '%mp_jsonout(CLOSE,jref=tmp)'; put 'data _null_;'; put 'infile tmp;'; put 'input;putlog _infile_;'; put 'run;'; put 'If you are building web apps with SAS then you are strongly encouraged to use'; put 'the mX_createwebservice macros in combination with the'; put '[sasjs adapter](https://github.com/sasjs/adapter).'; put 'For more information see https://sasjs.io'; put '@param [in] action Valid values:'; put '@li OPEN - opens the JSON'; put '@li OBJ - sends a table with each row as an object'; put '@li ARR - sends a table with each row in an array'; put '@li CLOSE - closes the JSON'; put '@param [in] ds The dataset to send. Must be a work table.'; put '@param [out] jref= (_webout) The fileref to which to send the JSON'; put '@param [out] dslabel= The name to give the table in the exported JSON'; put '@param [in] fmt= (Y) Whether to keep (Y) or strip (N) formats from the table'; put '@param [in] engine= (DATASTEP) Which engine to use to send the JSON. Options:'; put '@li PROCJSON (default)'; put '@li DATASTEP (more reliable when data has non standard characters)'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to JSON'; put '

Related Files

'; put '@li mp_ds2fmtds.sas'; put '@version 9.2'; put '@author Allan Bowe'; put '@source https://github.com/sasjs/core'; put '**/'; put '%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y'; put ',engine=DATASTEP'; put ',missing=NULL'; put ',showmeta=N'; put ',maxobs=MAX'; put ')/*/STORE SOURCE*/;'; put '%local tempds colinfo fmtds i numcols numobs stmt_obs lastobs optval'; put 'tmpds1 tmpds2 tmpds3 tmpds4;'; put '%let numcols=0;'; put '%if &maxobs ne MAX %then %let stmt_obs=%str(if _n_>&maxobs then stop;);'; put '%if &action=OPEN %then %do;'; put 'options nobomfile;'; put 'data _null_;file &jref encoding=''utf-8'' lrecl=200;'; put 'put ''{"PROCESSED_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''"'';'; put 'run;'; put '%end;'; put '%else %if (&action=ARR or &action=OBJ) %then %do;'; put '/* force variable names to always be uppercase in the JSON */'; put 'options validvarname=upcase;'; put '/* To avoid issues with _webout on EBI - such as encoding diffs and truncation'; put '(https://support.sas.com/kb/49/325.html) we use temporary files */'; put 'filename _sjs1 temp lrecl=200 ;'; put 'data _null_; file _sjs1 encoding=''utf-8'';'; put 'put ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":";'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjs1 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs1 clear;'; put '/* grab col defs */'; put 'proc contents noprint data=&ds'; put 'out=_data_(keep=name type length format formatl formatd varnum label);'; put 'run;'; put '%let colinfo=%scan(&syslast,2,.);'; put 'proc sort data=&colinfo;'; put 'by varnum;'; put 'run;'; put '/* move meta to mac vars */'; put 'data &colinfo;'; put 'if _n_=1 then call symputx(''numcols'',nobs,''l'');'; put 'set &colinfo end=last nobs=nobs;'; put 'name=upcase(name);'; put '/* fix formats */'; put 'if type=2 or type=6 then do;'; put 'typelong=''char'';'; put 'length fmt $49.;'; put 'if format='''' then fmt=cats(''$'',length,''.'');'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else fmt=cats(format,formatl,''.'');'; put 'end;'; put 'else do;'; put 'typelong=''num'';'; put 'if format='''' then fmt=''best.'';'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else if formatd=0 then fmt=cats(format,formatl,''.'');'; put 'else fmt=cats(format,formatl,''.'',formatd);'; put 'end;'; put '/* 32 char unique name */'; put 'newname=''sasjs''!!substr(cats(put(md5(name),$hex32.)),1,27);'; put 'call symputx(cats(''name'',_n_),name,''l'');'; put 'call symputx(cats(''newname'',_n_),newname,''l'');'; put 'call symputx(cats(''length'',_n_),length,''l'');'; put 'call symputx(cats(''fmt'',_n_),fmt,''l'');'; put 'call symputx(cats(''type'',_n_),type,''l'');'; put 'call symputx(cats(''typelong'',_n_),typelong,''l'');'; put 'call symputx(cats(''label'',_n_),coalescec(label,name),''l'');'; put '/* overwritten when fmt=Y and a custom format exists in catalog */'; put 'if typelong=''num'' then call symputx(cats(''fmtlen'',_n_),200,''l'');'; put 'else call symputx(cats(''fmtlen'',_n_),min(32767,ceil((length+10)*1.5)),''l'');'; put 'run;'; put '%let tempds=%substr(_%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql;'; put 'select count(*) into: lastobs from &ds;'; put '%if &maxobs ne MAX %then %let lastobs=%sysfunc(min(&lastobs,&maxobs));'; put '%if &engine=PROCJSON %then %do;'; put '%if &missing=STRING %then %do;'; put '%put &sysmacroname: Special Missings not supported in proc json.;'; put '%put &sysmacroname: Switching to DATASTEP engine;'; put '%goto datastep;'; put '%end;'; put 'data &tempds;'; put 'set &ds;'; put '&stmt_obs;'; put '%if &fmt=N %then format _numeric_ best32.;;'; put '/* PRETTY is necessary to avoid line truncation in large files */'; put 'filename _sjs2 temp lrecl=131068 encoding=''utf-8'';'; put 'proc json out=_sjs2 pretty'; put '%if &action=ARR %then nokeys ;'; put ';export &tempds / nosastags fmtnumeric;'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs2 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs2 clear;'; put '%end;'; put '%else %if &engine=DATASTEP %then %do;'; put '%datastep:'; put '%if %sysfunc(exist(&ds)) ne 1 & %sysfunc(exist(&ds,VIEW)) ne 1'; put '%then %do;'; put '%put &sysmacroname: &ds NOT FOUND!!!;'; put '%return;'; put '%end;'; put '%if &fmt=Y %then %do;'; put '/**'; put '* Extract format definitions'; put '* First, by getting library locations from dictionary.formats'; put '* Then, by exporting the width using proc format'; put '* Cannot use maxw from sashelp.vformat as not always populated'; put '* Cannot use fmtinfo() as not supported in all flavours'; put '*/'; put '%let tmpds1=%substr(fmtsum%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds2=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds3=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds4=%substr(col%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql noprint;'; put 'create table &tmpds1 as'; put 'select cats(libname,''.'',memname) as FMTCAT,'; put 'FMTNAME'; put 'from dictionary.formats'; put 'where fmttype=''F'' and libname is not null'; put 'and fmtname in (select format from &colinfo where format is not null)'; put 'order by 1;'; put 'create table &tmpds2('; put 'FMTNAME char(32),'; put 'LENGTH num'; put ');'; put '%local catlist cat fmtlist i;'; put 'select distinct fmtcat into: catlist separated by '' '' from &tmpds1;'; put '%do i=1 %to %sysfunc(countw(&catlist,%str( )));'; put '%let cat=%scan(&catlist,&i,%str( ));'; put 'proc sql;'; put 'select distinct fmtname into: fmtlist separated by '' '''; put 'from &tmpds1 where fmtcat="&cat";'; put 'proc format lib=&cat cntlout=&tmpds3(keep=fmtname length);'; put 'select &fmtlist;'; put 'run;'; put 'proc sql;'; put 'insert into &tmpds2 select distinct fmtname,length from &tmpds3;'; put '%end;'; put 'proc sql;'; put 'create table &tmpds4 as'; put 'select a.*, b.length as MAXW'; put 'from &colinfo a'; put 'left join &tmpds2 b'; put 'on cats(a.format)=cats(upcase(b.fmtname))'; put 'order by a.varnum;'; put 'data _null_;'; put 'set &tmpds4;'; put 'if not missing(maxw);'; put 'call symputx('; put 'cats(''fmtlen'',_n_),'; put '/* vars need extra padding due to JSON escaping of special chars */'; put 'min(32767,ceil((max(length,maxw)+10)*1.5))'; put ',''l'''; put ');'; put 'run;'; put '/* configure varlenchk - as we are explicitly shortening the variables */'; put '%let optval=%sysfunc(getoption(varlenchk));'; put 'options varlenchk=NOWARN;'; put 'data _data_(compress=char);'; put '/* shorten the new vars */'; put 'length'; put '%do i=1 %to &numcols;'; put '&&name&i $&&fmtlen&i'; put '%end;'; put ';'; put '/* rename on entry */'; put 'set &ds(rename=('; put '%do i=1 %to &numcols;'; put '&&name&i=&&newname&i'; put '%end;'; put '));'; put '&stmt_obs;'; put 'drop'; put '%do i=1 %to &numcols;'; put '&&newname&i'; put '%end;'; put ';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=num %then %do;'; put '&&name&i=cats(put(&&newname&i,&&fmt&i));'; put '%end;'; put '%else %do;'; put '&&name&i=put(&&newname&i,&&fmt&i);'; put '%end;'; put '%end;'; put 'if _error_ then do;'; put 'call symputx(''syscc'',1012);'; put 'stop;'; put 'end;'; put 'run;'; put '%let fmtds=&syslast;'; put 'options varlenchk=&optval;'; put '%end;'; put 'proc format; /* credit yabwon for special null removal */'; put 'value bart (default=40)'; put '%if &missing=NULL %then %do;'; put '._ - .z = null'; put '%end;'; put '%else %do;'; put '._ = [quote()]'; put '. = null'; put '.a - .z = [quote()]'; put '%end;'; put 'other = [best.];'; put 'data &tempds;'; put 'attrib _all_ label='''';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'length &&name&i $&&fmtlen&i...;'; put 'format &&name&i $&&fmtlen&i...;'; put '%end;'; put '%end;'; put '%if &fmt=Y %then %do;'; put 'set &fmtds;'; put '%end;'; put '%else %do;'; put 'set &ds;'; put '%end;'; put '&stmt_obs;'; put 'format _numeric_ bart.;'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'if findc(&&name&i,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put '&&name&i=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,&&name&i)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else &&name&i=quote(cats(&&name&i));'; put '%end;'; put '%end;'; put 'run;'; put 'filename _sjs3 temp lrecl=131068 ;'; put 'data _null_;'; put 'file _sjs3 encoding=''utf-8'';'; put 'if _n_=1 then put "[";'; put 'set &tempds;'; put 'if _n_>1 then put "," @; put'; put '%if &action=ARR %then "[" ; %else "{" ;'; put '%do i=1 %to &numcols;'; put '%if &i>1 %then "," ;'; put '%if &action=OBJ %then """&&name&i"":" ;'; put '"&&name&i"n /* name literal for reserved variable names */'; put '%end;'; put '%if &action=ARR %then "]" ; %else "}" ; ;'; put '/* close out the table */'; put 'data _null_;'; put 'file _sjs3 mod encoding=''utf-8'';'; put 'put '']'';'; put 'run;'; put 'data _null_;'; put 'infile _sjs3 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs3 clear;'; put '%end;'; put 'proc sql;'; put 'drop table &colinfo, &tempds;'; put '%if %substr(&showmeta,1,1)=Y %then %do;'; put 'filename _sjs4 temp lrecl=131068 encoding=''utf-8'';'; put 'data _null_;'; put 'file _sjs4;'; put 'length label $350;'; put 'put ", ""$%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":{""vars"":{";'; put 'do i=1 to &numcols;'; put 'name=quote(trim(symget(cats(''name'',i))));'; put 'format=quote(trim(symget(cats(''fmt'',i))));'; put 'label=quote(prxchange(''s/\\/\\\\/'',-1,trim(symget(cats(''label'',i)))));'; put 'length=quote(trim(symget(cats(''length'',i))));'; put 'type=quote(trim(symget(cats(''typelong'',i))));'; put 'if i>1 then put "," @@;'; put 'put name '':{"format":'' format '',"label":'' label'; put ''',"length":'' length '',"type":'' type ''}'';'; put 'end;'; put 'put ''}}'';'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs4 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs4 clear;'; put '%end;'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put 'data _null_; file &jref encoding=''utf-8'' mod ;'; put 'put "}";'; put 'run;'; put '%end;'; put '%mend mp_jsonout;'; put '/**'; put '@file mm_webout.sas'; put '@brief Send data to/from SAS Stored Processes'; put '@details This macro should be added to the start of each Stored Process,'; put '**immediately** followed by a call to:'; put '%mm_webout(FETCH)'; put 'This will read all the input data and create same-named SAS datasets in the'; put 'WORK library. You can then insert your code, and send data back using the'; put 'following syntax:'; put 'data some datasets; * make some data ;'; put 'retain some columns;'; put 'run;'; put '%mm_webout(OPEN)'; put '%mm_webout(ARR,some) * Array format, fast, suitable for large tables ;'; put '%mm_webout(OBJ,datasets) * Object format, easier to work with ;'; put 'Finally, wrap everything up send some helpful system variables too'; put '%mm_webout(CLOSE)'; put '@param [in] action Either FETCH, OPEN, ARR, OBJ or CLOSE'; put '@param [in] ds The dataset to send back to the frontend'; put '@param [out] dslabel= Value to use instead of table name for sending to JSON'; put '@param [in] fmt= (N) Setting Y converts all vars to their formatted values'; put '@param [out] fref= (_webout) The fileref to which to write the JSON'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] workobs= (0) When set to a positive integer, will create a new'; put 'output object (WORK) which contains this number of observations from all'; put 'tables in the WORK library.'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to output JSON'; put '

SAS Macros

'; put '@li mp_jsonout.sas'; put '

Related Macros

'; put '@li ms_webout.sas'; put '@li mv_webout.sas'; put '@version 9.3'; put '@author Allan Bowe'; put '**/'; put '%macro mm_webout(action,ds,dslabel=,fref=_webout,fmt=N,missing=NULL'; put ',showmeta=N,maxobs=MAX,workobs=0'; put ');'; put '%global _webin_file_count _webin_fileref1 _webin_name1 _program _debug'; put 'sasjs_tables;'; put '%local i tempds jsonengine;'; put '/* see https://github.com/sasjs/core/issues/41 */'; put '%if "%upcase(&SYSENCODING)" ne "UTF-8" %then %let jsonengine=PROCJSON;'; put '%else %let jsonengine=DATASTEP;'; put '%if &action=FETCH %then %do;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'options mprint notes mprintnest;'; put '%end;'; put '%let _webin_file_count=%eval(&_webin_file_count+0);'; put '/* now read in the data */'; put '%do i=1 %to &_webin_file_count;'; put '%if &_webin_file_count=1 %then %do;'; put '%let _webin_fileref1=&_webin_fileref;'; put '%let _webin_name1=&_webin_name;'; put '%end;'; put 'data _null_;'; put 'infile &&_webin_fileref&i termstr=crlf;'; put 'input;'; put 'call symputx(''input_statement'',_infile_);'; put 'putlog "&&_webin_name&i input statement: " _infile_;'; put 'stop;'; put 'data &&_webin_name&i;'; put 'infile &&_webin_fileref&i firstobs=2 dsd termstr=crlf encoding=''utf-8'';'; put 'input &input_statement;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'if _n_<20 then putlog _infile_;'; put '%end;'; put 'run;'; put '%let sasjs_tables=&sasjs_tables &&_webin_name&i;'; put '%end;'; put '%end;'; put '%else %if &action=OPEN %then %do;'; put '/* fix encoding */'; put 'OPTIONS NOBOMFILE;'; put '/**'; put '* check xengine type to avoid the below err message:'; put '* > Function is only valid for filerefs using the CACHE access method.'; put '*/'; put 'data _null_;'; put 'set sashelp.vextfl(where=(fileref="_WEBOUT"));'; put 'if xengine=''STREAM'' then do;'; put 'rc=stpsrv_header(''Content-type'',"text/html; encoding=utf-8");'; put 'end;'; put 'run;'; put '/* setup json */'; put 'data _null_;file &fref encoding=''utf-8'';'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutBEGIN<<'';'; put '%end;'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'run;'; put '%end;'; put '%else %if &action=ARR or &action=OBJ %then %do;'; put '%mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt,jref=&fref'; put ',engine=&jsonengine,missing=&missing,showmeta=&showmeta,maxobs=&maxobs'; put ')'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put '/* To avoid issues with _webout on EBI we use a temporary file */'; put 'filename _sjsref temp lrecl=131068;'; put '%if %str(&workobs) > 0 %then %do;'; put '/* if debug mode, send back first XX records of each work table also */'; put 'data;run;%let tempds=%scan(&syslast,2,.);'; put 'ods output Members=&tempds;'; put 'proc datasets library=WORK memtype=data;'; put '%local wtcnt;%let wtcnt=0;'; put 'data _null_;'; put 'set &tempds;'; put 'if not (upcase(name) =:"DATA"); /* ignore temp datasets */'; put 'i+1;'; put 'call symputx(cats(''wt'',i),name,''l'');'; put 'call symputx(''wtcnt'',i,''l'');'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put ",""WORK"":{";'; put '%do i=1 %to &wtcnt;'; put '%let wt=&&wt&i;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'dsid=open("WORK.&wt",''is'');'; put 'nlobs=attrn(dsid,''NLOBS'');'; put 'nvars=attrn(dsid,''NVARS'');'; put 'rc=close(dsid);'; put 'if &i>1 then put '',''@;'; put 'put " ""&wt"" : {";'; put 'put ''"nlobs":'' nlobs;'; put 'put '',"nvars":'' nvars;'; put '%mp_jsonout(OBJ,&wt,jref=_sjsref,dslabel=first10rows,showmeta=Y'; put ',maxobs=&workobs'; put ')'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put '%end;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put 'run;'; put '%end;'; put '/* close off json */'; put 'data _null_;file _sjsref mod encoding=''utf-8'';'; put 'length SYSPROCESSNAME syserrortext syswarningtext autoexec $512;'; put 'put ",""_DEBUG"" : ""&_debug"" ";'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'autoexec=quote(urlencode(trim(getoption(''autoexec''))));'; put 'put '',"AUTOEXEC" : '' autoexec;'; put 'put ",""MF_GETUSER"" : ""%mf_getuser()"" ";'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'put ",""SYSENCODING"" : ""&sysencoding"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSPROCESSID"" : ""&SYSPROCESSID"" ";'; put 'put ",""SYSPROCESSMODE"" : ""&SYSPROCESSMODE"" ";'; put 'SYSPROCESSNAME=quote(urlencode(cats(SYSPROCESSNAME)));'; put 'put ",""SYSPROCESSNAME"" : " SYSPROCESSNAME;'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put '',"SYSWARNINGTEXT" : '' syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'length memsize $32;'; put 'memsize="%sysfunc(INPUTN(%sysfunc(getoption(memsize)), best.),sizekmg.)";'; put 'memsize=quote(cats(memsize));'; put 'put '',"MEMSIZE" : '' memsize;'; put 'put "}" @;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutEND<<'';'; put '%end;'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjsref lrecl=1 recfm=n;'; put 'file &fref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjsref clear;'; put '%end;'; put '%mend mm_webout;'; put '%macro webout(action,ds,dslabel=,fmt=,missing=NULL,showmeta=NO,maxobs=MAX);'; put '%mm_webout(&action,ds=&ds,dslabel=&dslabel,fmt=&fmt'; put ',missing=&missing'; put ',showmeta=&showmeta'; put ',maxobs=&maxobs'; put ') %mend;'; put '/* provide additional debug info */'; put '%global _program;'; put '%put &=syscc;'; put '%put user=%mf_getuser();'; put '%put pgm=&_program;'; put '%put timestamp=%sysfunc(datetime(),datetime19.);'; put '* Service Variables start;'; put '* Service Variables end;'; put '* SAS Macros start;'; put '%macro mf_getapploc(pgm);'; put '%if "&pgm"="" %then %do;'; put '%if %symexist(_program) %then %let pgm=&_program;'; put '%else %do;'; put '%put &sysmacroname: No value provided and no _program variable available;'; put '%return;'; put '%end;'; put '%end;'; put '%local root;'; put '/**'; put '* First check we are not in the tests/macros folder (which has no subfolders)'; put '* or specifically in the testsetup or testteardown services'; put '*/'; put '%if %index(&pgm,/tests/macros/)'; put 'or %index(&pgm,/tests/testsetup)'; put 'or %index(&pgm,/tests/testteardown)'; put '%then %do;'; put '%let root=%substr(&pgm,1,%index(&pgm,/tests)-1);'; put '&root'; put '%return;'; put '%end;'; put '/**'; put '* Next, move up two levels to avoid matches on subfolder or service name'; put '*/'; put '%let root=%substr(&pgm,1,%length(&pgm)-%length(%scan(&pgm,-1,/))-1);'; put '%let root=%substr(&root,1,%length(&root)-%length(%scan(&root,-1,/))-1);'; put '%if %index(&root,/tests/) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/tests/)-1);'; put '%end;'; put '%else %if %index(&root,/services) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/services)-1);'; put '%end;'; put '%else %if %index(&root,/jobs) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/jobs)-1);'; put '%end;'; put '%else %put &sysmacroname: Could not find an app location from &pgm;'; put '&root'; put '%mend mf_getapploc ;'; put '%macro mf_getuniquefileref(prefix=_,maxtries=1000,lrecl=32767);'; put '%local rc fname;'; put '%if &prefix=0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%end;'; put '%else %do;'; put '%local x len;'; put '%let len=%eval(8-%length(&prefix));'; put '%let x=0;'; put '%do x=0 %to &maxtries;'; put '%let fname=&prefix%substr(%sysfunc(ranuni(0)),3,&len);'; put '%if %sysfunc(fileref(&fname)) > 0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%return;'; put '%end;'; put '%end;'; put '%put unable to find available fileref after &maxtries attempts;'; put '%end;'; put '%mend mf_getuniquefileref;'; put '%macro mp_abort(mac=mp_abort.sas, type=, msg=, iftrue=%str(1=1)'; put ', errds=work.mp_abort_errds'; put ', mode=REGULAR'; put ')/*/STORE SOURCE*/;'; put '%global sysprocessmode sysprocessname sasjs_stpsrv_header_loc sasjsprocessmode;'; put '%local fref fid i;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%put NOTE: /// mp_abort macro executing //;'; put '%if %length(&mac)>0 %then %put NOTE- called by &mac;'; put '%put NOTE - &msg;'; put '%if %symexist(_SYSINCLUDEFILEDEVICE)'; put '/* abort cancel FILE does not restart outside the INCLUDE on Viya 3.5 */'; put 'and %superq(SYSPROCESSNAME) ne %str(Compute Server)'; put '%then %do;'; put '%if "*&_SYSINCLUDEFILEDEVICE*" ne "**" %then %do;'; put 'data &errds;'; put 'iftrue=''1=1'';'; put 'length mac $100 msg $5000;'; put 'mac=symget(''mac'');'; put 'msg=symget(''msg'');'; put 'run;'; put 'data _null_;'; put 'abort cancel FILE;'; put 'run;'; put '%return;'; put '%end;'; put '%end;'; put '/* Web App Context */'; put '%if %symexist(_PROGRAM)'; put 'or %superq(SYSPROCESSNAME) = %str(Compute Server)'; put 'or &mode=INCLUDE'; put '%then %do;'; put 'options obs=max replace mprint;'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5"'; put '%then %do;'; put 'options nosyntaxcheck;'; put '%end;'; put '%if &mode=INCLUDE %then %do;'; put '%if %sysfunc(exist(&errds))=1 %then %do;'; put 'data _null_;'; put 'set &errds;'; put 'call symputx(''iftrue'',iftrue,''l'');'; put 'call symputx(''mac'',mac,''l'');'; put 'call symputx(''msg'',msg,''l'');'; put 'putlog (_all_)(=);'; put 'run;'; put '%if (&iftrue)=0 %then %return;'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: No include errors found;'; put '%return;'; put '%end;'; put '%end;'; put '/* extract log errs / warns, if exist */'; put '%local logloc logline;'; put '%global logmsg; /* capture global messages */'; put '%if %symexist(SYSPRINTTOLOG) %then %let logloc=&SYSPRINTTOLOG;'; put '%else %let logloc=%qsysfunc(getoption(LOG));'; put 'proc printto log=log;run;'; put '%let logline=0;'; put '%if %length(&logloc)>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input; putlog _infile_;'; put 'i=1;'; put 'retain logonce 0;'; put 'if ('; put '_infile_=:"%str(WARN)ING" or _infile_=:"%str(ERR)OR"'; put ') and logonce=0 then'; put 'do;'; put 'call symputx(''logline'',_n_);'; put 'logonce+1;'; put 'end;'; put 'run;'; put '/* capture log including lines BEFORE the err */'; put '%if &logline>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input;'; put 'i=1;'; put 'stoploop=0;'; put 'if _n_ ge &logline-15 and stoploop=0 then do until (i>22);'; put 'call symputx(''logmsg'',catx(''\n'',symget(''logmsg''),_infile_));'; put 'input;'; put 'i+1;'; put 'stoploop=1;'; put 'end;'; put 'if stoploop=1 then stop;'; put 'run;'; put '%end;'; put '%end;'; put '%if %symexist(SYS_JES_JOB_URI) %then %do;'; put '/* setup webout for Viya */'; put 'options nobomfile;'; put '%if "X&SYS_JES_JOB_URI.X"="XX" %then %do;'; put 'filename _webout temp lrecl=999999 mod;'; put '%end;'; put '%else %do;'; put 'filename _webout filesrvc parenturi="&SYS_JES_JOB_URI"'; put 'name="_webout.json" lrecl=999999 mod;'; put '%end;'; put '%end;'; put '%else %if %sysfunc(filename(fref,&sasjs_stpsrv_header_loc))=0 %then %do;'; put 'options nobomfile;'; put '/* set up http header for SASjs Server */'; put '%let fid=%sysfunc(fopen(&fref,A));'; put '%if &fid=0 %then %do;'; put '%put %str(ERR)OR: %sysfunc(sysmsg());'; put '%return;'; put '%end;'; put '%let rc=%sysfunc(fput(&fid,%str(Content-Type: application/json)));'; put '%let rc=%sysfunc(fwrite(&fid));'; put '%let rc=%sysfunc(fclose(&fid));'; put '%let rc=%sysfunc(filename(&fref));'; put '%end;'; put '/* send response in SASjs JSON format */'; put 'data _null_;'; put 'file _webout mod lrecl=32000 encoding=''utf-8'';'; put 'length msg syswarningtext syserrortext $32767 mode $10 ;'; put 'sasdatetime=datetime();'; put 'msg=symget(''msg'');'; put '%if &logline>0 %then %do;'; put 'msg=cats(msg,''\n\nLog Extract:\n'',symget(''logmsg''));'; put '%end;'; put '/* escape the escapes */'; put 'msg=tranwrd(msg,''\'',''\\'');'; put '/* escape the quotes */'; put 'msg=tranwrd(msg,''"'',''\"'');'; put '/* ditch the CRLFs as chrome complains */'; put 'msg=compress(msg,,''kw'');'; put '/* quote without quoting the quotes (which are escaped instead) */'; put 'msg=cats(''"'',msg,''"'');'; put 'if symexist(''_debug'') then debug=quote(trim(symget(''_debug'')));'; put 'else debug=''""'';'; put 'if symget(''sasjsprocessmode'')=''Stored Program'' then mode=''SASJS'';'; put 'if mode ne ''SASJS'' then put ''>>weboutBEGIN<<'';'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'put '',"sasjsAbort" : [{'';'; put 'put '' "MSG":'' msg ;'; put 'put '' ,"MAC": "'' "&mac" ''"}]'';'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'put '',"_DEBUG":'' debug ;'; put 'if symexist(''_metauser'') then do;'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put 'end;'; put 'if symexist(''SYS_JES_JOB_URI'') then do;'; put 'SYS_JES_JOB_URI=quote(trim(symget(''SYS_JES_JOB_URI'')));'; put 'put '',"SYS_JES_JOB_URI": '' SYS_JES_JOB_URI;'; put 'end;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put ",""SYSWARNINGTEXT"" : " syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'put "}" ;'; put 'if mode ne ''SASJS'' then put ''>>weboutEND<<'';'; put 'run;'; put '%put _all_;'; put '%if "&sysprocessmode " = "SAS Stored Process Server " %then %do;'; put 'data _null_;'; put 'putlog ''stpsrvset program err and syscc'';'; put 'rc=stpsrvset(''program error'', 0);'; put 'call symputx("syscc",0,"g");'; put 'run;'; put '%if &sysscp=WIN'; put 'and 1=0 /* deprecating this logic until we figure out a consistent abort */'; put 'and "%substr(%str(&sysvlong ),1,8)"="9.04.01M"'; put 'and "%substr(%str(&sysvlong ),9,1)">"5" %then %do;'; put '/* skip approach (below) does not work in windows m6+ envs */'; put 'endsas;'; put '%end;'; put '%else %do;'; put '/**'; put '* endsas kills 9.4m3 deployments by orphaning multibridges.'; put '* Abort variants are ungraceful (non zero return code)'; put '* This approach lets SAS run silently until the end :-)'; put '* Caution - fails when called within a %include within a macro'; put '* Use mp_include() to handle this.'; put '*/'; put 'filename skip temp;'; put 'data _null_;'; put 'file skip;'; put 'put ''%macro skip();'';'; put 'comment ''%mend skip; -> fix lint '';'; put 'put ''%macro skippy();'';'; put 'comment ''%mend skippy; -> fix lint '';'; put 'run;'; put '%inc skip;'; put '%end;'; put '%end;'; put '%else %if "&sysprocessmode " = "SAS Compute Server " %then %do;'; put '/* endsas kills the session making it harder to fetch results */'; put 'data _null_;'; put 'syswarningtext=symget(''syswarningtext'');'; put 'syserrortext=symget(''syserrortext'');'; put 'abort_msg=symget(''msg'');'; put 'syscc=symget(''syscc'');'; put 'sysuserid=symget(''sysuserid'');'; put 'iftrue=symget(''iftrue'');'; put 'put (_all_)(/=);'; put 'call symputx(''syscc'',0);'; put 'abort cancel nolist;'; put 'run;'; put '%end;'; put '%else %do;'; put '%abort cancel;'; put '%end;'; put '%end;'; put '%else %do;'; put '%put _all_;'; put '%abort cancel;'; put '%end;'; put '%mend mp_abort;'; put '/** @endcond */'; put '%macro mm_getstpcode('; put 'tree=/User Folders/sasdemo/somestp'; put ',name='; put ',outloc=0'; put ',outref=0'; put ',mDebug=1'; put ',showlog=NO'; put ');'; put '%local mD;'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing &sysmacroname..sas;'; put '%&mD.put _local_;'; put '%if %length(&name)>0 %then %let name=/&name;'; put '/* first, check if STP exists */'; put '%local tsuri;'; put '%let tsuri=stopifempty ;'; put 'data _null_;'; put 'format type uri tsuri value $200.;'; put 'call missing (of _all_);'; put 'path="&tree&name(StoredProcess)";'; put '/* first, find the STP ID */'; put 'if metadata_pathobj("",path,"StoredProcess",type,uri)>0 then do;'; put '/* get sourcecode */'; put 'cnt=1;'; put 'do while (metadata_getnasn(uri,"Notes",cnt,tsuri)>0);'; put 'rc=metadata_getattr(tsuri,"Name",value);'; put '&mD.put tsuri= value=;'; put 'if value="SourceCode" then do;'; put '/* found it! */'; put 'rc=metadata_getattr(tsuri,"Id",value);'; put 'call symputx(''tsuri'',value,''l'');'; put 'stop;'; put 'end;'; put 'cnt+1;'; put 'end;'; put 'end;'; put 'else put (_all_)(=);'; put 'run;'; put '%mp_abort(iftrue= (&tsuri=stopifempty)'; put ',mac=mm_getstpcode'; put ',msg=%str(&tree&name.(StoredProcess) not found!)'; put ')'; put '/**'; put '* Now we can extract the textstore'; put '*/'; put 'filename __getdoc temp lrecl=10000000;'; put 'proc metadata'; put 'in="$METAREPOSITORY'; put ''; put 'SAS1"'; put 'out=__getdoc ;'; put 'run;'; put '/* find the beginning of the text */'; put '%local start;'; put 'data _null_;'; put 'infile __getdoc lrecl=10000;'; put 'input;'; put 'start=index(_infile_,''StoredText="'');'; put 'if start then do;'; put 'call symputx("start",start+11);'; put '*putlog ''"'' _infile_ ''"'';'; put 'end;'; put 'stop;'; put '%local outeng;'; put '%if "&outloc"="0" %then %let outeng=TEMP;'; put '%else %let outeng="&outloc";'; put '%local fref;'; put '%if &outref=0 %then %let fref=%mf_getuniquefileref();'; put '%else %let fref=&outref;'; put '/* read the content, byte by byte, resolving escaped chars */'; put 'filename &fref &outeng lrecl=100000;'; put 'data _null_;'; put 'length filein 8 fileid 8;'; put 'filein = fopen("__getdoc","I",1,"B");'; put 'fileid = fopen("&fref","O",1,"B");'; put 'rec = "20"x;'; put 'length entity $6;'; put 'do while(fread(filein)=0);'; put 'x+1;'; put 'if x>&start then do;'; put 'rc = fget(filein,rec,1);'; put 'if rec=''"'' then leave;'; put 'else if rec="&" then do;'; put 'entity=rec;'; put 'do until (rec=";");'; put 'if fread(filein) ne 0 then goto getout;'; put 'rc = fget(filein,rec,1);'; put 'entity=cats(entity,rec);'; put 'end;'; put 'select (entity);'; put 'when (''&'' ) rec=''&'' ;'; put 'when (''<'' ) rec=''<'' ;'; put 'when (''>'' ) rec=''>'' ;'; put 'when (''''') rec="''" ;'; put 'when (''"'') rec=''"'' ;'; put 'when ('' '') rec=''0A''x;'; put 'when ('' '') rec=''0D''x;'; put 'when (''$'' ) rec=''$'' ;'; put 'when ('' '') rec=''09''x;'; put 'otherwise putlog "%str(WARN)ING: missing value for " entity=;'; put 'end;'; put 'rc =fput(fileid, substr(rec,1,1));'; put 'rc =fwrite(fileid);'; put 'end;'; put 'else do;'; put 'rc =fput(fileid,rec);'; put 'rc =fwrite(fileid);'; put 'end;'; put 'end;'; put 'end;'; put 'getout:'; put 'rc=fclose(filein);'; put 'rc=fclose(fileid);'; put 'run;'; put '%if &showlog=YES %then %do;'; put 'data _null_;'; put 'infile &fref lrecl=32767 end=last;'; put 'input;'; put 'if _n_=1 then putlog ''>>stpcodeBEGIN<<'';'; put 'putlog _infile_;'; put 'if last then putlog ''>>stpcodeEND<<'';'; put 'run;'; put '%end;'; put 'filename __getdoc clear;'; put '%if &outref=0 %then %do;'; put 'filename &fref clear;'; put '%end;'; put '%mend mm_getstpcode;'; put '%macro dc_getsettings();'; put '%global _program;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(syscc=&syscc on entry (&syswarningtext &syserrortext))'; put ')'; put '%if %length(&_PROGRAM)>1 %then %let root=&_program;'; put '%else %do;'; put '%global _metauser;'; put '%let _metauser=&sysuserid;'; put '/* to mimic a "real" _program we need to give a dummy role and stp name */'; put '%let root=/dummyRole/dummyName;'; put '%end;'; put '/* the DC precode is stored in the Admin folder in the root of'; put 'the project. Lets find that root. */'; put '%put &=root;'; put '%let root=%mf_getapploc();'; put '%put &=root;'; put '/* Now we know the root location we can retrieve the params */'; put '%let temploc=%sysfunc(pathname(work))/settings.txt;'; put '%mm_getstpcode(tree=&root/services/public'; put ',name=Data_Controller_Settings'; put ',outloc=&temploc'; put ')'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Unable to run getstpcode)'; put ')'; put 'filename _getsets "&temploc" lrecl=2000;'; put '/*'; put 'Do not use mp_include here - this puts a copy in every service, which creates'; put 'compilation problems when calling services from mp_include'; put '*/'; put '%inc _getsets/source2;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Problem running &sysmacroname (&syswarningtext &syserrortext))'; put ')'; put '%mend dc_getsettings;'; put '%macro mf_fmtdttm('; put ')/*/STORE SOURCE*/;'; put '%if "&sysver"="9.2" or "&sysver"="9.3"'; put 'or ("&sysver"="9.4" and "%substr(&SYSVLONG,9,1)" le "3")'; put 'or "%substr(&sysver,1,1)"="4"'; put 'or "%substr(&sysver,1,1)"="5"'; put '%then %do;DATETIME19.3%end;'; put '%else %do;E8601DT26.6%end;'; put '%mend mf_fmtdttm;'; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '%macro mp_init(prefix=SASJS'; put ')/*/STORE SOURCE*/;'; put '%if %symexist(SASJS_PREFIX) %then %return; /* only run once */'; put '%global'; put 'SASJS_PREFIX /* the ONLY hard-coded global macro variable in SASjs */'; put '&prefix._FUNCTIONS /* used in mcf_init() to track core function compilation */'; put '&prefix._INIT_NUM /* initialisation time as numeric */'; put '&prefix._INIT_DTTM /* initialisation time in E8601DT26.6 format */'; put '&prefix.WORK /* avoid typing %sysfunc(pathname(work)) every time */'; put ';'; put '%let sasjs_prefix=&prefix;'; put 'data _null_;'; put 'dttm=datetime();'; put 'call symputx("&sasjs_prefix._init_num",dttm,''g'');'; put 'call symputx("&sasjs_prefix._init_dttm",put(dttm,E8601DT26.6),''g'');'; put 'call symputx("&sasjs_prefix.work",pathname(''WORK''),''g'');'; put 'run;'; put 'options'; put 'compress=CHAR /* default is none so ensure we have something! */'; put 'datastmtchk=ALLKEYWORDS /* protection from overwriting input datasets */'; put 'errorcheck=STRICT /* catch errs in libname/filename statements */'; put 'fmterr /* ensure err when a format cannot be found */'; put 'mergenoby=%str(ERR)OR /* throw err when a merge has no BY variables */'; put 'missing=. /* changing this can cause hard to detect errs */'; put 'noquotelenmax /* avoid warnings for long strings */'; put 'noreplace /* avoid overwriting permanent datasets */'; put 'ps=max /* reduce log size slightly */'; put 'ls=max /* reduce log even more and avoid word truncation */'; put 'validmemname=COMPATIBLE /* avoid special characters etc in table names */'; put 'validvarname=V7 /* avoid special characters etc in variable names */'; put 'varinitchk=%str(ERR)OR /* avoid data mistakes from variable name typos */'; put 'varlenchk=%str(ERR)OR /* fail hard if truncation (data loss) can result */'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5" %then %do;'; put 'noautocorrect /* disallow misspelled procedure names */'; put 'dsoptions=note2err /* undocumented - convert bad NOTEs to ERRs */'; put '%end;'; put ';'; put '%mend mp_init;'; put '%macro mpeinit(fetch=YES);'; put '%global mpeinit'; put 'mpeadmins /* group with unrestricted Meditor access */'; put 'mpelocapprovals /* location for landing and staging files */'; put 'mpelib /* location of configuration tables for DC */'; put 'dc_repo_users /* location of user / group metadata */'; put 'dc_licence_key /* extracted in dc_getsettings */'; put 'dc_activation_key /* extracted in dc_getsettings */'; put 'dc_locale /* extracted in dc_getsettings */'; put 'dc_dttmtfmt /* can be overridden in dc_getsettings */'; put '_debug'; put ';'; put '%if &mpeinit=1 %then %return;'; put '%else %let mpeinit=1;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(Problem on service startup (&syswarningtext &syserrortext))'; put ')'; put '%mp_init()'; put '%if &fetch=YES %then %do;'; put '%webout(FETCH)'; put '%end;'; put '%global _CLIENTNAME;'; put '%mp_abort(iftrue= (&_CLIENTNAME=SAS Enterprise Guide)'; put ',mac=&_program..sas'; put ',msg=%str(Data Controller is a web app and should not be executed from EG)'; put ')'; put 'options urlencoding=utf8 nobomfile lrecl=32767;'; put '%let perf=%sysfunc(datetime());'; put '%put perfdiff: 0;'; put '%let dc_locale=SYSTEM; /* default if not set */'; put '/**'; put '* E8601DT26.6 has widest database support - but not all SAS flavours can'; put '* handle it. Override in the settings STP if needed.'; put '*/'; put 'data _null_;'; put 'dc_dttmtfmt=''"%sysfunc(datetime(),''!!"%mf_fmtdttm()"!!'')"dt'';'; put 'call symputx(''dc_dttmtfmt'',dc_dttmtfmt);'; put 'put dc_dttmtfmt=;'; put 'run;'; put '%put &=dc_dttmtfmt;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc prior to dc_getsettings)'; put ')'; put '%dc_getsettings()'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc after dc_getsettings)'; put ')'; put 'data _null_;'; put 'set &DC_LIBREF..mpe_config(where=('; put 'var_scope="DC"'; put 'and &dc_dttmtfmt lt tx_to'; put 'and var_active=1'; put '));'; put 'call symputx(var_name,var_value,''G'');'; put 'putlog var_name "=" var_value;'; put 'run;'; put '%let mpelib=&dc_libref;'; put '%let mpeadmins=&dc_admin_group;'; put '%let mpelocapprovals=&dc_staging_area;'; put '%let dc_repo_users=&dc_repo_users;'; put '%if &dc_locale ne SYSTEM %then %do;'; put 'options locale=&dc_locale;'; put '%end;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(Problem during compilation or with STP precode (&syswarningtext))'; put ')'; put '%mend mpeinit;'; put '%macro mf_mval(var);'; put '%if %symexist(&var) %then %do;'; put '%superq(&var)'; put '%end;'; put '%mend mf_mval;'; put '%macro mf_trimstr(basestr,trimstr);'; put '%local baselen trimlen trimval;'; put '/* return if basestr is shorter than trimstr (or 0) */'; put '%let baselen=%length(%superq(basestr));'; put '%let trimlen=%length(%superq(trimstr));'; put '%if &baselen < &trimlen or &baselen=0 %then %return;'; put '/* obtain the characters from the end of basestr */'; put '%let trimval=%qsubstr(%superq(basestr)'; put ',%length(%superq(basestr))-&trimlen+1'; put ',&trimlen);'; put '/* compare and if matching, chop it off! */'; put '%if %superq(basestr)=%superq(trimstr) %then %do;'; put '%return;'; put '%end;'; put '%else %if %superq(trimval)=%superq(trimstr) %then %do;'; put '%qsubstr(%superq(basestr),1,%length(%superq(basestr))-&trimlen)'; put '%end;'; put '%else %do;'; put '&basestr'; put '%end;'; put '%mend mf_trimstr;'; put '%macro mf_getplatform(switch'; put ')/*/STORE SOURCE*/;'; put '%local a b c;'; put '%if &switch.NONE=NONE %then %do;'; put '%if %symexist(sasjsprocessmode) %then %do;'; put '%if &sasjsprocessmode=Stored Program %then %do;'; put 'SASJS'; put '%return;'; put '%end;'; put '%end;'; put '%if %symexist(sysprocessmode) %then %do;'; put '%if "&sysprocessmode"="SAS Object Server"'; put 'or "&sysprocessmode"= "SAS Compute Server" %then %do;'; put 'SASVIYA'; put '%end;'; put '%else %if "&sysprocessmode"="SAS Stored Process Server"'; put 'or "&sysprocessmode"="SAS Workspace Server"'; put '%then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if %symexist(_metaport) or %symexist(_metauser) %then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if &switch=SASSTUDIO %then %do;'; put '/* return the version of SAS Studio else 0 */'; put '%if %mf_mval(_CLIENTAPP)=%str(SAS Studio) %then %do;'; put '%let a=%mf_mval(_CLIENTVERSION);'; put '%let b=%scan(&a,1,.);'; put '%if %eval(&b >2) %then %do;'; put '&b'; put '%end;'; put '%else 0;'; put '%end;'; put '%else 0;'; put '%end;'; put '%else %if &switch=VIYARESTAPI %then %do;'; put '%mf_trimstr(%sysfunc(getoption(servicesbaseurl)),/)'; put '%end;'; put '%mend mf_getplatform;'; put '%macro mpeterm();'; put '%local oldloc;'; put 'data _null_;'; put 'if symexist(''SYSPRINTTOLOG'') then oldloc=symget(''SYSPRINTTOLOG'');'; put 'else oldloc=getoption(''LOG'');'; put 'if subpad(oldloc,1,1) not in (''"'',"''",'' '') then oldloc=quote(cats(oldloc));'; put 'call symputx(''oldloc'',oldloc,''l'');'; put 'run;'; put '%if %length(&oldloc)>0 %then %do;'; put 'proc printto log=log;'; put 'run;'; put 'data _null_;'; put 'infile &oldloc;'; put 'input; putlog _infile_;'; put 'run;'; put '%end;'; put '%if %sysfunc(exist(&dc_libref..mpe_requests)) and %mf_getplatform() ne SASVIYA'; put '%then %do;'; put 'data ;'; put 'if 0 then set &dc_libref..mpe_requests;'; put 'request_dttm=%sysfunc(datetime());'; put 'request_user="%mf_getuser()";'; put 'request_service="%scan(&_program,-2,/)/%scan(&_program,-1,/)";'; put 'request_params='''';'; put 'output;stop;'; put 'proc append base=&dc_libref..mpe_requests data=&syslast force nowarn;'; put 'run;'; put '%end;'; put '%mend mpeterm;'; put '%macro mpe_columnlevelsecurity(tgtlib,tgtds,inds'; put ',mode=VIEW'; put ',groupds=work.groups'; put ',clsds=work.clsview'; put ',outds=CLSVIEW'; put ',outmeta=work.cls_rules'; put ');'; put '%local col_list is_admin;'; put '/* filter for the appropriate rules */'; put 'proc sql;'; put 'create table &outmeta as'; put 'select CLS_VARIABLE_NM,'; put 'min(case when CLS_HIDE=1 then 1 else 0 end) as CLS_HIDE'; put 'from &clsds'; put 'where &dc_dttmtfmt. lt tx_to'; put 'and CLS_SCOPE in ("&mode",''ALL'')'; put 'and CLS_ACTIVE=1'; put '%if &mode=VIEW %then %do;'; put 'and CLS_HIDE ne 1'; put '%end;'; put 'and upcase(CLS_GROUP) in (select upcase(groupname) from &groupds)'; put 'and CLS_LIBREF="%upcase(&tgtlib)"'; put 'and CLS_TABLE="%upcase(&tgtds)"'; put 'group by CLS_VARIABLE_NM;'; put '%let is_admin=0;'; put 'proc sql;'; put 'select count(*) into: is_admin from &groupds where groupname="&MPEADMINS";'; put '%put &sysmacroname: &=is_admin;'; put '%if %mf_nobs(work.cls_rules) = 0 or &is_admin>0 %then %do;'; put '%put &sysmacroname: no CLS rules to apply;'; put '%put &=is_admin;'; put '/* copy using append for speed */'; put 'data &outds;'; put 'set &inds;'; put 'stop;'; put 'run;'; put 'proc append base=&outds data=&inds;'; put 'run;'; put '/* ensure CLS_RULES is empty in case of admin */'; put 'data &outmeta;'; put 'set &outmeta;'; put 'stop;'; put 'run;'; put '%return;'; put '%end;'; put '%else %if &mode=VIEW %then %do;'; put '/* just send back the relevant columns */'; put '%let col_list=0;'; put 'proc sql noprint;'; put 'select CLS_VARIABLE_NM into: col_list separated by '' '' from &outmeta'; put 'where CLS_HIDE=0;'; put '%if &col_list=0 %then %do;'; put '/*'; put 'We have columns that are set to CLS_HIDE=1 but we do not have any to'; put 'explicitly show. Therefore we assume all columns are to be shown except'; put 'those that are explicitly hidden.'; put '*/'; put 'proc sql noprint;'; put 'select CLS_VARIABLE_NM into: col_list separated by '' '' from &outmeta'; put 'where CLS_HIDE=1;'; put 'data &outds;'; put 'set &inds;'; put 'drop &col_list;'; put 'run;'; put '%end;'; put '%else %do;'; put 'data &outds;'; put 'set &inds;'; put 'keep &col_list;'; put 'run;'; put '%end;'; put '%end;'; put '%else %if &mode=EDIT %then %do;'; put '/*'; put 'In this case we pass all columns and the frontend will filter out the'; put 'ones that are not allowed to be edited.'; put '*/'; put 'data &outds;'; put 'set &inds;'; put 'stop;'; put 'run;'; put 'proc append base=&outds data=&inds;'; put 'run;'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: invalid mode - &mode!;'; put '%abort;'; put '%end;'; put '%mend mpe_columnlevelsecurity;'; put '%macro mp_dsmeta(libds,outds=work.dsmeta);'; put '%local ds1 ds2;'; put 'data;run; %let ds1=&syslast;'; put 'data;run; %let ds2=&syslast;'; put '/* setup the ODS capture */'; put 'ods output attributes=&ds1 enginehost=&ds2;'; put '/* export the metadata */'; put 'proc contents data=&libds;'; put 'run;'; put '/* load it into a single table */'; put 'data &outds (keep=ods_table name value);'; put 'length ods_table $10 name label2 label1 label $100'; put 'value cvalue cvalue1 cvalue2 $1000'; put 'nvalue nvalue1 nvalue2 8;'; put 'if _n_=1 then call missing (of _all_);'; put '* putlog (_all_)(=);'; put 'set &ds1 (in=atrs) &ds2 (in=eng);'; put 'if atrs then do;'; put 'ods_table=''ATTRIBUTES'';'; put 'name=coalescec(label1,label);'; put 'value=coalescec(cvalue1,cvalue,put(coalesce(nvalue1,nvalue),best.));'; put 'output;'; put 'if label2 ne '''' then do;'; put 'name=label2;'; put 'value=coalescec(cvalue2,put(nvalue2,best.));'; put 'output;'; put 'end;'; put 'end;'; put 'else if eng then do;'; put 'ods_table=''ENGINEHOST'';'; put 'name=coalescec(label1,label);'; put 'value=coalescec(cvalue1,cvalue,put(coalesce(nvalue1,nvalue),best.));'; put 'output;'; put 'end;'; put 'run;'; put 'proc sql;'; put 'drop table &ds1, &ds2;'; put '%mend mp_dsmeta;'; put '%macro mpe_dsmeta(libds, outds=dsmeta);'; put '%local ddsd ddld notes lenstmt;'; put '%let lenstmt=length ods_table $18 name $100 value $1000;'; put '%let libds=%upcase(&libds);'; put '%mp_dsmeta(&libds, outds=&outds)'; put 'data _null_;'; put 'set &mpelib..mpe_datadictionary;'; put 'where &dc_dttmtfmt < tx_to & dd_source=%upcase("&libds") & dd_type=''TABLE'';'; put 'call symputx(''ddsd'',dd_shortdesc,''l'');'; put 'call symputx(''ddld'',dd_longdesc,''l'');'; put 'run;'; put 'data &outds;'; put '&lenstmt;'; put 'if last then do;'; put 'ODS_TABLE=''MPE_DATADICTIONARY'';'; put 'NAME=''DD_SHORTDESC'';'; put 'VALUE="&ddsd";'; put 'output;'; put 'NAME=''DD_LONGDESC'';'; put 'VALUE="&ddld";'; put 'output;'; put 'end;'; put 'set &outds end=last;'; put 'output;'; put 'run;'; put 'data _data_;'; put 'set &mpelib..mpe_tables;'; put 'where libref="%scan(&libds,1,.)"'; put '& dsn="%scan(&libds,2,.)"'; put '& &dc_dttmtfmt0);'; put 'rc=metadata_getattr(groupuri, "Name", groupname);'; put 'rc=metadata_getattr(groupuri, "Desc", groupdesc);'; put 'rc=metadata_getattr(groupuri,"PublicType",group_or_role);'; put 'if Group_or_Role = ''UserGroup'' then output;'; put 'i+1;'; put 'end;'; put 'run;'; put '%end;'; put '%else %do;'; put 'data &outds (keep=groupuri groupname groupdesc);'; put 'length uri groupuri groupname groupdesc group_or_role $256;'; put 'call missing(of _all_);'; put 'rc=metadata_getnobj("omsobj:Person?@Name=''&user''",1,uri);'; put 'if rc<=0 then do;'; put 'putlog "%str(WARN)ING: rc=" rc "&user not found "'; put '", or there was an issue reading the repository.";'; put 'stop;'; put 'end;'; put 'a=1;'; put 'grpassn=metadata_getnasn(uri,"IdentityGroups",a,groupuri);'; put 'if grpassn in (-3,-4) then do;'; put 'putlog "%str(WARN)ING: No metadata groups found for &user";'; put 'output;'; put 'end;'; put 'else do while (grpassn > 0);'; put 'rc=metadata_getattr(groupuri, "Name", groupname);'; put 'rc=metadata_getattr(groupuri, "Desc", groupdesc);'; put 'a+1;'; put 'rc=metadata_getattr(groupuri,"PublicType",group_or_role);'; put 'if Group_or_Role = ''UserGroup'' then output;'; put 'grpassn=metadata_getnasn(uri,"IdentityGroups",a,groupuri);'; put 'end;'; put 'run;'; put '%end;'; put '%if &oldrepo ne &repo %then %do;'; put 'options metarepository=&oldrepo;'; put '%end;'; put '%mend mm_getGroups;'; put '%macro dc_getusergroups(user=,outds=mm_getgroups);'; put '%global dc_repo_users;'; put '%if &dc_repo_users= %then %let dc_repo_users=foundation;'; put '%mm_getgroups(user=&user,outds=&outds,repo=&dc_repo_users)'; put '%mend dc_getusergroups;'; put '%macro mpe_getgroups(user=,outds=);'; put '%if not %symexist(dc_repo_users) %then %let dc_repo_users=foundation;'; put '%dc_getusergroups(user=&user,outds=&outds)'; put 'data;'; put 'length groupname groupdesc $256;'; put 'set &dc_libref..mpe_groups;'; put 'where &dc_dttmtfmt. lt tx_to;'; put 'where also upcase(user_name)="%upcase(&user)";'; put 'groupname=group_name;'; put 'groupdesc=group_desc;'; put 'keep groupname groupdesc;'; put 'run;'; put 'data &outds;'; put 'set &syslast &outds(keep=groupname groupdesc);'; put 'run;'; put '%mend mpe_getgroups;'; put '%macro mf_getuniquename(prefix=MC);'; put '&prefix.%substr(%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32-%length(&prefix))'; put '%mend mf_getuniquename;'; put '%macro mf_getattrn('; put 'libds'; put ',attr'; put ')/*/STORE SOURCE*/;'; put '%local dsid rc;'; put '%let dsid=%sysfunc(open(&libds,is));'; put '%if &dsid = 0 %then %do;'; put '%put %str(WARN)ING: Cannot open %trim(&libds), system message below;'; put '%put %sysfunc(sysmsg());'; put '-1'; put '%end;'; put '%else %do;'; put '%sysfunc(attrn(&dsid,&attr))'; put '%let rc=%sysfunc(close(&dsid));'; put '%end;'; put '%mend mf_getattrn;'; put '%macro mf_nobs(libds'; put ')/*/STORE SOURCE*/;'; put '%mf_getattrn(&libds,NLOBS)'; put '%mend mf_nobs;'; put '%macro mp_filtergenerate(inds,outref=filter);'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(syscc=&syscc - on macro entry)'; put ')'; put 'filename &outref temp;'; put '%if %mf_nobs(&inds)=0 %then %do;'; put '/* ensure we have a default filter */'; put 'data _null_;'; put 'file &outref;'; put 'put ''1=1'';'; put 'run;'; put '%end;'; put '%else %do;'; put 'proc sort data=&inds;'; put 'by SUBGROUP_ID;'; put 'run;'; put 'data _null_;'; put 'file &outref lrecl=32800;'; put 'set &inds end=last;'; put 'by SUBGROUP_ID;'; put 'if _n_=1 then put ''(('';'; put 'else if first.SUBGROUP_ID then put +1 GROUP_LOGIC ''('';'; put 'else put +2 SUBGROUP_LOGIC;'; put 'put +4 VARIABLE_NM OPERATOR_NM RAW_VALUE;'; put 'if last.SUBGROUP_ID then put '')''@;'; put 'if last then put '')'';'; put 'run;'; put '%end;'; put '%mend mp_filtergenerate;'; put '%macro mpe_filtermaster(mode,libds,'; put 'dclib=,'; put 'filter_rk=-1,'; put 'outref=0,'; put 'outds=work.query'; put ');'; put '%put &sysmacroname entry vars:;'; put '%put _local_;'; put '%let mode=%upcase(&mode);'; put '%let libds=%upcase(&libds);'; put '%mp_abort(iftrue= ('; put '&mode ne EDIT and &mode ne VIEW and &mode ne DLOAD and &mode ne ULOAD'; put ')'; put ',mac=&sysmacroname'; put ',msg=%str(Invalid MODE: &mode)'; put ')'; put '%mp_abort(iftrue= (&outref = 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Please provide a fileref!)'; put ')'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(syscc=&syscc)'; put ')'; put 'filename &outref temp;'; put '/* ensure outputs exist */'; put 'data _null_;'; put 'file &outref;'; put 'put '' '';'; put 'run;'; put 'data &outds;'; put 'set &dclib..mpe_filtersource;'; put 'stop;'; put 'run;'; put '/**'; put '* Deal with FILTER_RK first'; put '*/'; put '%if &filter_rk gt 0 %then %do;'; put 'data _null_;'; put 'file &outref;'; put 'put ''( ''@@;'; put 'set &dclib..mpe_filteranytable(where=(filter_rk=&filter_rk));'; put 'call symputx(''filter_hash'',filter_hash,''l'');'; put 'run;'; put 'proc sort data=&dclib..mpe_filtersource(where=(filter_hash="&filter_hash"))'; put 'out=&outds(drop=filter_hash filter_line processed_dttm);'; put 'by filter_line;'; put 'run;'; put '%mp_filtergenerate(&outds,outref=&outref)'; put '%end;'; put '/* Now filter for current records if the MODE is EDIT or DLOAD */'; put '%local varfrom varto;'; put '%let varfrom=0;'; put 'proc sql;'; put 'select coalescec(var_txfrom,''0''), var_txto into: varfrom,:varto'; put 'from &dclib..MPE_TABLES'; put 'where &dc_dttmtfmt. lt tx_to'; put 'and libref="%scan(&libds,1,.)" and dsn="%scan(&libds,2,.)";'; put '%put &=varfrom;'; put '%put &=varto;'; put '/**'; put '* Check if the date variables were mentioned in the query'; put '* This is a trigger for serving a historical view instead of current'; put '* we skip this part when checking an ULOAD as there are no date vars'; put '*/'; put '%if &varfrom ne 0 and (&mode=EDIT or &mode=DLOAD) %then %do;'; put '%local validityvars;'; put 'proc sql;'; put 'select count(*) into: validityvars'; put 'from &outds'; put 'where variable_nm in ("&varfrom","&varto");'; put '%if &validityvars=0 %then %do;'; put 'data _null_;'; put 'file &outref mod;'; put 'length filter_text $32767;'; put 'varfrom=symget(''varfrom'');'; put 'varto=symget(''varto'');'; put 'filter_text=catx('' '','; put '''("%sysfunc(datetime(),'',"%mf_fmtdttm()",'')"dt <'',varto,'')'''; put ');'; put 'if &filter_rk > 0 then put ''AND '' filter_text;'; put 'else put filter_text;'; put 'run;'; put '%end;'; put '%end;'; put '/**'; put '* Now do Row Level Security based on the MPE_ROW_LEVEL_SECURITY table'; put '*/'; put '/* first determine users group membership */'; put '%mpe_getgroups(user=%mf_getuser(),outds=work.groups)'; put '%local admin_check;'; put 'proc sql;'; put 'select count(*) into: admin_check'; put 'from work.groups'; put 'where groupname="&mpeadmins";'; put '%put &sysmacroname: &=admin_check &=mpeadmins;'; put '%if &admin_check=0 %then %do;'; put '%local scopeval;'; put '%if &mode=DLOAD %then %let scopeval=VIEW;'; put '%if &mode=ULOAD %then %let scopeval=EDIT;'; put '%else %let scopeval=&mode;'; put '/* extract relevant rows */'; put '%local rlsds;'; put '%let rlsds=%mf_getuniquename();'; put 'proc sql;'; put 'create table work.&rlsds as'; put 'select rls_group,'; put 'rls_group_logic as group_logic,'; put 'rls_subgroup_logic as subgroup_logic,'; put 'rls_subgroup_id as subgroup_id,'; put 'rls_variable_nm as variable_nm,'; put 'rls_operator_nm as operator_nm,'; put 'rls_raw_value as raw_value'; put 'from &mpelib..mpe_row_level_security'; put 'where &dc_dttmtfmt. lt tx_to'; put 'and rls_scope in ("&scopeval",''ALL'')'; put 'and upcase(rls_group) in (select upcase(groupname) from work.groups)'; put 'and rls_libref="%scan(&libds,1,.)"'; put 'and rls_table="%scan(&libds,2,.)"'; put 'and rls_active=1'; put 'order by rls_group,rls_subgroup_id;'; put '%if &sqlobs>0 %then %do;'; put '/* check if we currently have filter or not */'; put 'data ;'; put 'infile &outref end=eof;'; put 'input;'; put 'if _n_=1 and eof and cats(_infile_)='''' then newfilter=1;'; put 'output;'; put 'stop;'; put 'run;'; put 'data _null_;'; put 'set &syslast;'; put 'file &outref mod;'; put 'if newfilter=1 then put ''('';'; put 'else put ''AND ('';'; put 'run;'; put '/* loop through and apply filters for each group membership */'; put '%local fref ds;'; put '%let fref=%mf_getuniquefileref();'; put '%let ds=%mf_getuniquename();'; put 'proc sql noprint;'; put 'select distinct rls_group into : group1 -'; put 'from work.&rlsds;'; put '%do i=1 %to &sqlobs;'; put 'data work.&ds;'; put 'set work.&rlsds;'; put 'where rls_group="&&group&i";'; put 'drop rls_group;'; put 'run;'; put '%mp_filtergenerate(&ds,outref=&fref)'; put 'data _null_;'; put 'infile &fref;'; put 'file &outref mod;'; put 'input;'; put 'if &i>1 and _n_=1 then put '' OR '';'; put 'put _infile_;'; put 'run;'; put '%end;'; put 'data _null_;'; put 'file &outref mod;'; put 'put '')'';'; put 'run;'; put '%end; /* &sqlobs>0 */'; put '%else %do;'; put '%put &sysmacroname: no matching groups;'; put 'data _null_;'; put 'set work.groups;'; put 'putlog (_all_)(=);'; put 'run;'; put '%end;'; put '%mp_abort(iftrue= (&syscc>0)'; put ',mac=&sysmacroname'; put ',msg=%str(Row Level Security Generation Error)'; put ')'; put '%end; /* &admin_check=0 */'; put '%put leaving &sysmacroname with the following query:;'; put '%local empty;'; put '%let empty=0;'; put 'data _null_;'; put 'infile &outref end=eof;'; put 'input;'; put 'putlog _infile_;'; put 'if _n_=1 and eof and cats(_infile_)='''' then do;'; put 'put ''1=1'';'; put 'call symputx(''empty'',1,''l'');'; put 'end;'; put 'run;'; put '%if &empty=1 %then %do;'; put 'data _null_;'; put 'file &outref;'; put 'put ''1=1'';'; put 'run;'; put '%end;'; put '%mend mpe_filtermaster;'; put '%macro mm_assignlib('; put 'libref'; put ',mAbort=HARD'; put ')/*/STORE SOURCE*/;'; put '%local mp_abort msg;'; put '%let mp_abort=0;'; put '%if %sysfunc(libref(&libref)) %then %do;'; put 'data _null_;'; put 'length liburi LibName msg $200;'; put 'call missing(of _all_);'; put 'nobj=metadata_getnobj("omsobj:SASLibrary?@Libref=''&libref''",1,liburi);'; put 'if nobj=1 then do;'; put 'rc=metadata_getattr(liburi,"Name",LibName);'; put '/* now try and assign it */'; put 'if libname("&libref",,''meta'',cats(''liburi="'',liburi,''";'')) ne 0 then do;'; put 'putlog "&libref could not be assigned";'; put 'putlog liburi=;'; put '/**'; put '* Fetch the system message for display in the abort modal. This is'; put '* not always helpful though. One example, previously received:'; put '* NOTE: Libref XX refers to the same library metadata as libref XX.'; put '*/'; put 'msg=sysmsg();'; put 'if msg=:''ERROR: Libref SAVE is not assigned.'' then do;'; put 'msg=catx(" ",'; put '"Could not assign %upcase(&libref).",'; put '"Please check metadata permissions! Libname:",libname,'; put '"Liburi:",liburi'; put ');'; put 'end;'; put 'else if msg="ERROR: User does not have appropriate authorization "!!'; put '"level for library SAVE."'; put 'then do;'; put 'msg=catx(" ",'; put '"ERROR: User does not have appropriate authorization level",'; put '"for library %upcase(&libref), libname:",libname,'; put '"Liburi:",liburi'; put ');'; put 'end;'; put 'call symputx(''msg'',msg,''l'');'; put 'if "&mabort"=''HARD'' then call symputx(''mp_abort'',1,''l'');'; put 'end;'; put 'else do;'; put 'put (_all_)(=);'; put 'call symputx(''libname'',libname,''L'');'; put 'call symputx(''liburi'',liburi,''L'');'; put 'end;'; put 'end;'; put 'else if nobj>1 then do;'; put 'if "&mabort"=''HARD'' then call symputx(''mp_abort'',1);'; put 'call symputx(''msg'',"More than one library with libref=&libref");'; put 'end;'; put 'else do;'; put 'if "&mabort"=''HARD'' then call symputx(''mp_abort'',1);'; put 'call symputx(''msg'',"Library &libref not found in metadata");'; put 'end;'; put 'run;'; put '%put NOTE: &msg;'; put '%end;'; put '%else %do;'; put '%put NOTE: Library &libref is already assigned;'; put '%end;'; put '%mp_abort(iftrue= (&mp_abort=1)'; put ',mac=mm_assignlib.sas'; put ',msg=%superq(msg)'; put ')'; put '%mend mm_assignlib;'; put '/** @cond */'; put '%macro mf_getengine(libref'; put ')/*/STORE SOURCE*/;'; put '%local dsid engnum rc engine;'; put '/* in case the parameter is a libref.tablename, pull off just the libref */'; put '%let libref = %upcase(%scan(&libref, 1, %str(.)));'; put '%let dsid=%sysfunc('; put 'open(sashelp.vlibnam(where=(libname="%upcase(&libref)")),i)'; put ');'; put '%if (&dsid ^= 0) %then %do;'; put '%let engnum=%sysfunc(varnum(&dsid,ENGINE));'; put '%let rc=%sysfunc(fetch(&dsid));'; put '%let engine=%sysfunc(getvarc(&dsid,&engnum));'; put '%put &libref. ENGINE is &engine.;'; put '%let rc= %sysfunc(close(&dsid));'; put '%end;'; put '%upcase(&engine)'; put '%mend mf_getengine;'; put '/** @endcond */'; put '%macro mm_assigndirectlib('; put 'libref'; put ',open_passthrough='; put ',sql_options='; put ',mDebug=0'; put ',mAbort=0'; put ')/*/STORE SOURCE*/;'; put '%local mD;'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing mm_assigndirectlib.sas;'; put '%&mD.put _local_;'; put '%if &mAbort=1 %then %let mAbort=;'; put '%else %let mAbort=%str(*);'; put '%&mD.put NOTE: Creating direct (non META) connection to &libref library;'; put '%local cur_engine;'; put '%let cur_engine=%mf_getengine(&libref);'; put '%if &cur_engine ne META and &cur_engine ne %then %do;'; put '%put NOTE: &libref already has a direct (&cur_engine) libname connection;'; put '%return;'; put '%end;'; put '%else %if %upcase(&libref)=WORK %then %do;'; put '%put NOTE: We already have a direct connection to WORK :-) ;'; put '%return;'; put '%end;'; put '/* need to determine the library ENGINE first */'; put '%local engine;'; put 'data _null_;'; put 'length lib_uri engine $256;'; put 'call missing (of _all_);'; put '/* get URI for the particular library */'; put 'rc1=metadata_getnobj("omsobj:SASLibrary?@Libref =''&libref''",1,lib_uri);'; put '/* get the Engine attribute of the previous object */'; put 'rc2=metadata_getattr(lib_uri,''Engine'',engine);'; put 'putlog "mm_assigndirectlib for &libref:" rc1= lib_uri= rc2= engine=;'; put 'call symputx("liburi",lib_uri,''l'');'; put 'call symputx("engine",engine,''l'');'; put 'run;'; put '/* now obtain engine specific connection details */'; put '%if &engine=BASE %then %do;'; put '%&mD.put NOTE: Retrieving BASE library path;'; put 'data _null_;'; put 'length up_uri $256 path cat_path $1024;'; put 'retain cat_path;'; put 'call missing (of _all_);'; put '/* get all the filepaths of the UsingPackages association */'; put 'i=1;'; put 'rc3=metadata_getnasn("&liburi",''UsingPackages'',i,up_uri);'; put 'do while (rc3>0);'; put '/* get the DirectoryName attribute of the previous object */'; put 'rc4=metadata_getattr(up_uri,''DirectoryName'',path);'; put 'if i=1 then path = ''("''!!trim(path)!!''" '';'; put 'else path ='' "''!!trim(path)!!''" '';'; put 'cat_path = trim(cat_path) !! " " !! trim(path) ;'; put 'i+1;'; put 'rc3=metadata_getnasn("&liburi",''UsingPackages'',i,up_uri);'; put 'end;'; put 'cat_path = trim(cat_path) !! ")";'; put '&mD.putlog "NOTE: Getting physical path for &libref library";'; put '&mD.putlog rc3= up_uri= rc4= cat_path= path=;'; put '&mD.putlog "NOTE: Libname cmd will be:";'; put '&mD.putlog "libname &libref" cat_path;'; put 'call symputx("filepath",cat_path,''l'');'; put 'run;'; put '%if %sysevalf(&sysver<9.4) %then %do;'; put 'libname &libref &filepath;'; put '%end;'; put '%else %do;'; put '/* apply the new filelocks option to cater for temporary locks */'; put 'libname &libref &filepath filelockwait=5;'; put '%end;'; put '%end;'; put '%else %if &engine=REMOTE %then %do;'; put 'data x;'; put 'length rcCon rcProp rc k 3 uriCon uriProp PropertyValue PropertyName'; put 'Delimiter $256 properties $2048;'; put 'retain properties;'; put 'rcCon = metadata_getnasn("&liburi", "LibraryConnection", 1, uriCon);'; put 'rcProp = metadata_getnasn(uriCon, "Properties", 1, uriProp);'; put 'k = 1;'; put 'rcProp = metadata_getnasn(uriCon, "Properties", k, uriProp);'; put 'do while (rcProp > 0);'; put 'rc = metadata_getattr(uriProp , "DefaultValue",PropertyValue);'; put 'rc = metadata_getattr(uriProp , "PropertyName",PropertyName);'; put 'rc = metadata_getattr(uriProp , "Delimiter",Delimiter);'; put 'properties = trim(properties) !! " " !! trim(PropertyName)'; put '!! trim(Delimiter) !! trim(PropertyValue);'; put 'output;'; put 'k+1;'; put 'rcProp = metadata_getnasn(uriCon, "Properties", k, uriProp);'; put 'end;'; put '%&mD.put NOTE: Getting properties for REMOTE SHARE &libref library;'; put '&mD.put _all_;'; put '%&mD.put NOTE: Libname cmd will be:;'; put '%&mD.put libname &libref &engine &properties slibref=&libref;'; put 'call symputx ("properties",trim(properties),''l'');'; put 'run;'; put 'libname &libref &engine &properties slibref=&libref;'; put '%end;'; put '%else %if &engine=OLEDB %then %do;'; put '%&mD.put NOTE: Retrieving OLEDB connection details;'; put 'data _null_;'; put 'length domain datasource provider properties schema'; put 'connx_uri domain_uri conprop_uri lib_uri schema_uri value $256.;'; put 'call missing (of _all_);'; put '/* get source connection ID */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,connx_uri);'; put '/* get connection domain */'; put 'rc1=metadata_getnasn(connx_uri,''Domain'',1,domain_uri);'; put 'rc2=metadata_getattr(domain_uri,''Name'',domain);'; put '&mD.putlog / ''NOTE: '' // ''NOTE- connection id: '' connx_uri ;'; put '&mD.putlog ''NOTE- domain: '' domain;'; put '/* get DSN and PROVIDER from connection properties */'; put 'i=0;'; put 'do until (rc<0);'; put 'i+1;'; put 'rc=metadata_getnasn(connx_uri,''Properties'',i,conprop_uri);'; put 'rc2=metadata_getattr(conprop_uri,''Name'',value);'; put 'if value=''Connection.OLE.Property.DATASOURCE.Name.xmlKey.txt'' then do;'; put 'rc3=metadata_getattr(conprop_uri,''DefaultValue'',datasource);'; put 'end;'; put 'else if value=''Connection.OLE.Property.PROVIDER.Name.xmlKey.txt'' then do;'; put 'rc4=metadata_getattr(conprop_uri,''DefaultValue'',provider);'; put 'end;'; put 'else if value=''Connection.OLE.Property.PROPERTIES.Name.xmlKey.txt'' then'; put 'do;'; put 'rc5=metadata_getattr(conprop_uri,''DefaultValue'',properties);'; put 'end;'; put 'end;'; put '&mD.putlog ''NOTE- dsn/provider/properties: '' /'; put 'datasource provider properties;'; put '&mD.putlog ''NOTE- schema: '' schema // ''NOTE-'';'; put '/* get SCHEMA */'; put 'rc6=metadata_getnasn("&liburi",''UsingPackages'',1,lib_uri);'; put 'rc7=metadata_getattr(lib_uri,''SchemaName'',schema);'; put 'call symputx(''SQL_domain'',domain,''l'');'; put 'call symputx(''SQL_dsn'',datasource,''l'');'; put 'call symputx(''SQL_provider'',provider,''l'');'; put 'call symputx(''SQL_properties'',properties,''l'');'; put 'call symputx(''SQL_schema'',schema,''l'');'; put 'run;'; put '%if %length(&open_passthrough)>0 %then %do;'; put 'proc sql &sql_options;'; put 'connect to OLEDB as &open_passthrough(INSERT_SQL=YES'; put '/* need additional properties to make this work */'; put 'properties=(''Integrated Security''=SSPI'; put '''Persist Security Info''=True'; put '%sysfunc(compress(%str(&SQL_properties),%str(())))'; put ')'; put 'DATASOURCE=&sql_dsn PROMPT=NO'; put 'PROVIDER=&sql_provider SCHEMA=&sql_schema CONNECTION = GLOBAL);'; put '%end;'; put '%else %do;'; put 'LIBNAME &libref OLEDB PROPERTIES=&sql_properties'; put 'DATASOURCE=&sql_dsn PROVIDER=&sql_provider SCHEMA=&sql_schema'; put '%if %length(&sql_domain)>0 %then %do;'; put 'authdomain="&sql_domain"'; put '%end;'; put 'connection=shared;'; put '%end;'; put '%end;'; put '%else %if &engine=ODBC %then %do;'; put '%&mD.put NOTE: Retrieving ODBC connection details;'; put 'data _null_;'; put 'length connx_uri conprop_uri value datasource up_uri schema domprop_uri authdomain $256.;'; put 'call missing (of _all_);'; put '/* get source connection ID */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,connx_uri);'; put '/* get connection properties */'; put 'i=0;'; put 'do until (rc2<0);'; put 'i+1;'; put 'rc2=metadata_getnasn(connx_uri,''Properties'',i,conprop_uri);'; put 'rc3=metadata_getattr(conprop_uri,''Name'',value);'; put 'if value=''Connection.ODBC.Property.DATASRC.Name.xmlKey.txt'' then do;'; put 'rc4=metadata_getattr(conprop_uri,''DefaultValue'',datasource);'; put 'rc2=-1;'; put 'end;'; put 'end;'; put '/* get auth domain */'; put 'autrc=metadata_getnasn(connx_uri,"Domain",1,domprop_uri);'; put 'arc=metadata_getattr(domprop_uri,"Name",authdomain);'; put 'if not missing(authdomain) then authdomain=cats(''AUTHDOMAIN='',authdomain);'; put 'call symputx(''authdomain'',authdomain,''l'');'; put '/* get SCHEMA */'; put 'rc6=metadata_getnasn("&liburi",''UsingPackages'',1,up_uri);'; put 'rc7=metadata_getattr(up_uri,''SchemaName'',schema);'; put '&mD.put rc= connx_uri= rc2= conprop_uri= rc3= value= rc4= datasource='; put 'rc6= up_uri= rc7= schema=;'; put 'call symputx(''SQL_schema'',schema,''l'');'; put 'call symputx(''SQL_dsn'',datasource,''l'');'; put 'run;'; put '%if %length(&open_passthrough)>0 %then %do;'; put 'proc sql &sql_options;'; put 'connect to ODBC as &open_passthrough'; put '(INSERT_SQL=YES DATASRC=&sql_dsn. CONNECTION=global);'; put '%end;'; put '%else %do;'; put 'libname &libref ODBC DATASRC=&sql_dsn SCHEMA=&sql_schema &authdomain;'; put '%end;'; put '%end;'; put '%else %if &engine=POSTGRES %then %do;'; put '%put NOTE: Obtaining POSTGRES library details;'; put 'data _null_;'; put 'length database ignore_read_only_columns direct_exe preserve_col_names'; put 'preserve_tab_names server schema authdomain user password'; put 'prop name value uri urisrc $256.;'; put 'call missing (of _all_);'; put '/* get database value */'; put 'prop=''Connection.DBMS.Property.DB.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,database,"");'; put 'if database^='''' then database=''database=''!!quote(trim(database));'; put 'call symputx(''database'',database,''l'');'; put '/* get IGNORE_READ_ONLY_COLUMNS value */'; put 'prop=''Library.DBMS.Property.DBIROC.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,ignore_read_only_columns,"");'; put 'if ignore_read_only_columns^='''' then ignore_read_only_columns='; put '''ignore_read_only_columns=''!!ignore_read_only_columns;'; put 'call symputx(''ignore_read_only_columns'',ignore_read_only_columns,''l'');'; put '/* get DIRECT_EXE value */'; put 'prop=''Library.DBMS.Property.DirectExe.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,direct_exe,"");'; put 'if direct_exe^='''' then direct_exe=''direct_exe=''!!direct_exe;'; put 'call symputx(''direct_exe'',direct_exe,''l'');'; put '/* get PRESERVE_COL_NAMES value */'; put 'prop=''Library.DBMS.Property.PreserveColNames.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,preserve_col_names,"");'; put 'if preserve_col_names^='''' then preserve_col_names='; put '''preserve_col_names=''!!preserve_col_names;'; put 'call symputx(''preserve_col_names'',preserve_col_names,''l'');'; put '/* get PRESERVE_TAB_NAMES value */'; put '/* be careful with PRESERVE_TAB_NAMES=YES - it will mean your table will'; put 'become case sensitive!! */'; put 'prop=''Library.DBMS.Property.PreserveTabNames.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,preserve_tab_names,"");'; put 'if preserve_tab_names^='''' then preserve_tab_names='; put '''preserve_tab_names=''!!preserve_tab_names;'; put 'call symputx(''preserve_tab_names'',preserve_tab_names,''l'');'; put '/* get SERVER value */'; put 'if metadata_getnasn("&liburi","LibraryConnection",1,uri)>0 then do;'; put 'prop=''Connection.DBMS.Property.SERVER.Name.xmlKey.txt'';'; put 'rc=metadata_getprop(uri,prop,server,"");'; put 'end;'; put 'if server^='''' then server=''server=''!!quote(cats(server));'; put 'call symputx(''server'',server,''l'');'; put '/* get SCHEMA value */'; put 'if metadata_getnasn("&liburi","UsingPackages",1,uri)>0 then do;'; put 'rc=metadata_getattr(uri,"SchemaName",schema);'; put 'end;'; put 'if schema^='''' then schema=''schema=''!!schema;'; put 'call symputx(''schema'',schema,''l'');'; put '/* get AUTHDOMAIN value */'; put '/* this is only useful if the user account contains that auth domain'; put 'if metadata_getnasn("&liburi","DefaultLogin",1,uri)>0 then do;'; put 'rc=metadata_getnasn(uri,"Domain",1,urisrc);'; put 'rc=metadata_getattr(urisrc,"Name",authdomain);'; put 'end;'; put 'if authdomain^='''' then authdomain=''authdomain=''!!quote(trim(authdomain));'; put '*/'; put 'call symputx(''authdomain'',authdomain,''l'');'; put '/* get user & pass */'; put 'if authdomain='''' & metadata_getnasn("&liburi","DefaultLogin",1,uri)>0 then'; put 'do;'; put 'rc=metadata_getattr(uri,"UserID",user);'; put 'rc=metadata_getattr(uri,"Password",password);'; put 'end;'; put 'if user^='''' then do;'; put 'user=''user=''!!quote(trim(user));'; put 'password=''password=''!!quote(trim(password));'; put 'end;'; put 'call symputx(''user'',user,''l'');'; put 'call symputx(''password'',password,''l'');'; put '&md.put _all_;'; put 'run;'; put '%if %length(&open_passthrough)>0 %then %do;'; put '%put %str(WARN)ING: Passthrough option for postgres not yet supported;'; put '%return;'; put '%end;'; put '%else %do;'; put '%if &mdebug=1 %then %do;'; put '%put NOTE: Executing the following:/;'; put '%put NOTE- libname &libref POSTGRES &database &ignore_read_only_columns;'; put '%put NOTE- &direct_exe &preserve_col_names &preserve_tab_names;'; put '%put NOTE- &server &schema &authdomain &user &password //;'; put '%end;'; put 'libname &libref POSTGRES &database &ignore_read_only_columns &direct_exe'; put '&preserve_col_names &preserve_tab_names &server &schema &authdomain'; put '&user &password;'; put '%end;'; put '%end;'; put '%else %if &engine=ORACLE %then %do;'; put '%put NOTE: Obtaining &engine library details;'; put 'data _null_;'; put 'length assocuri1 assocuri2 assocuri3 authdomain path schema $256;'; put 'call missing (of _all_);'; put '/* get auth domain */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,assocuri1);'; put 'rc=metadata_getnasn(assocuri1,''Domain'',1,assocuri2);'; put 'rc=metadata_getattr(assocuri2,"Name",authdomain);'; put 'call symputx(''authdomain'',authdomain,''l'');'; put '/* path */'; put 'rc=metadata_getprop(assocuri1,'; put '''Connection.Oracle.Property.PATH.Name.xmlKey.txt'',path);'; put 'call symputx(''path'',path,''l'');'; put '/* schema */'; put 'rc=metadata_getnasn("&liburi",''UsingPackages'',1,assocuri3);'; put 'rc=metadata_getattr(assocuri3,''SchemaName'',schema);'; put 'call symputx(''schema'',schema,''l'');'; put 'run;'; put '%put NOTE: Executing the following:/; %put NOTE-;'; put '%put NOTE- libname &libref ORACLE path=&path schema=&schema;'; put '%put NOTE- authdomain=&authdomain;'; put '%put NOTE-;'; put 'libname &libref ORACLE path=&path schema=&schema authdomain=&authdomain;'; put '%end;'; put '%else %if &engine=SQLSVR %then %do;'; put '%put NOTE: Obtaining &engine library details;'; put 'data _null;'; put 'length assocuri1 assocuri2 assocuri3 authdomain path schema userid'; put 'passwd $256;'; put 'call missing (of _all_);'; put 'rc=metadata_getnasn("&liburi",''DefaultLogin'',1,assocuri1);'; put 'rc=metadata_getattr(assocuri1,"UserID",userid);'; put 'rc=metadata_getattr(assocuri1,"Password",passwd);'; put 'call symputx(''user'',userid,''l'');'; put 'call symputx(''pass'',passwd,''l'');'; put '/* path */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,assocuri2);'; put 'rc=metadata_getprop(assocuri2,'; put '''Connection.SQL.Property.Datasrc.Name.xmlKey.txt'',path);'; put 'call symputx(''path'',path,''l'');'; put '/* schema */'; put 'rc=metadata_getnasn("&liburi",''UsingPackages'',1,assocuri3);'; put 'rc=metadata_getattr(assocuri3,''SchemaName'',schema);'; put 'call symputx(''schema'',schema,''l'');'; put 'run;'; put '%put NOTE: Executing the following:/; %put NOTE-;'; put '%put NOTE- libname &libref SQLSVR datasrc=&path schema=&schema ;'; put '%put NOTE- user="&user" pass="XXX";'; put '%put NOTE-;'; put 'libname &libref SQLSVR datasrc=&path schema=&schema user="&user" pass="&pass";'; put '%end;'; put '%else %if &engine=TERADATA %then %do;'; put '%put NOTE: Obtaining &engine library details;'; put 'data _null;'; put 'length assocuri1 assocuri2 assocuri3 authdomain path schema userid'; put 'passwd $256;'; put 'call missing (of _all_);'; put '/* get auth domain */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,assocuri1);'; put 'rc=metadata_getnasn(assocuri1,''Domain'',1,assocuri2);'; put 'rc=metadata_getattr(assocuri2,"Name",authdomain);'; put 'call symputx(''authdomain'',authdomain,''l'');'; put '/*'; put 'rc=metadata_getnasn("&liburi",''DefaultLogin'',1,assocuri1);'; put 'rc=metadata_getattr(assocuri1,"UserID",userid);'; put 'rc=metadata_getattr(assocuri1,"Password",passwd);'; put 'call symputx(''user'',userid,''l'');'; put 'call symputx(''pass'',passwd,''l'');'; put '*/'; put '/* path */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,assocuri2);'; put 'rc=metadata_getprop(assocuri2,'; put '''Connection.Teradata.Property.SERVER.Name.xmlKey.txt'',path);'; put 'call symputx(''path'',path,''l'');'; put '/* schema */'; put 'rc=metadata_getnasn("&liburi",''UsingPackages'',1,assocuri3);'; put 'rc=metadata_getattr(assocuri3,''SchemaName'',schema);'; put 'call symputx(''schema'',schema,''l'');'; put 'run;'; put '%put NOTE: Executing the following:/; %put NOTE-;'; put '%put NOTE- libname &libref TERADATA server="&path" schema=&schema ;'; put '%put NOTe- authdomain=&authdomain;'; put '%put NOTE-;'; put 'libname &libref TERADATA server="&path" schema=&schema authdomain=&authdomain;'; put '%end;'; put '%else %if &engine= %then %do;'; put '%put NOTE: Libref &libref is not registered in metadata;'; put '%&mAbort.mp_abort('; put 'msg=%str(ERR)OR: Libref &libref is not registered in metadata'; put ',mac=mm_assigndirectlib.sas);'; put '%return;'; put '%end;'; put '%else %do;'; put '%put %str(WARN)ING: Engine &engine is currently unsupported;'; put '%put %str(WARN)ING- Please contact your support team.;'; put '%return;'; put '%end;'; put '%mend mm_assigndirectlib;'; put '%macro mm_getrepos('; put 'outds=work.mm_getrepos'; put ')/*/STORE SOURCE*/;'; put '* use a temporary fileref to hold the response;'; put 'filename response temp;'; put '/* get list of libraries */'; put 'proc metadata in='; put '"1"'; put 'out=response;'; put 'run;'; put '/* write the response to the log for debugging */'; put '/*'; put 'data _null_;'; put 'infile response lrecl=1048576;'; put 'input;'; put 'put _infile_;'; put 'run;'; put '*/'; put '/* create an XML map to read the response */'; put 'filename sxlemap temp;'; put 'data _null_;'; put 'file sxlemap;'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository";'; put 'put "";'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Id";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Name";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Desc";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@DefaultNS";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@RepositoryType";'; put 'put "characterstring20";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@RepositoryFormat";'; put 'put "characterstring10";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@Access";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@CurrentAccess";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@PauseState";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Path";'; put 'put "";'; put 'put "characterstring256";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Engine";'; put 'put "";'; put 'put "characterstring8";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Options";'; put 'put "";'; put 'put "characterstring32";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@MetadataCreated";'; put 'put "characterstring24";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@MetadataUpdated";'; put 'put "characterstring24";'; put 'put '''';'; put 'put ''
'';'; put 'run;'; put 'libname _XML_ xml xmlfileref=response xmlmap=sxlemap;'; put 'proc sort data= _XML_.SASRepos out=&outds;'; put 'by name;'; put 'run;'; put '/* clear references */'; put 'filename sxlemap clear;'; put 'filename response clear;'; put 'libname _XML_ clear;'; put '%mend mm_getrepos;'; put '%macro dc_assignlib(type,libref,passthru=);'; put '%put &sysmacroname entry vars:;'; put '%put _local_;'; put '/* take current repository */'; put '%local repo repocnt x;'; put '%let repo=%sysfunc(getoption(metarepository));'; put '/* get list of repositories and filter */'; put '%mm_getrepos(outds=repos)'; put '%let repocnt=0;'; put 'data repos;'; put 'set repos;'; put 'where repositorytype in(''CUSTOM'',''FOUNDATION'')'; put '& upcase(name) ne "%upcase(&repo)";'; put 'keep id name ;'; put 'call symputx(cats(''repo'',_n_),name,''l'');'; put 'call symputx(''repocnt'',_n_,''l'');'; put 'run;'; put '/* find out which of these repositories has the libref we are searching for */'; put '%local lib_uri;'; put '%let lib_uri=NOTFOUND;'; put 'data _null_; /* check default repo first */'; put 'length lib_uri $256;'; put 'call missing (of _all_);'; put 'rc=metadata_getnobj("omsobj:SASLibrary?@Libref =''&libref''",1,lib_uri);'; put 'call symputx(''lib_uri'',coalescec(lib_uri,''NOTFOUND''),''l'');'; put 'run;'; put '%if &lib_uri=NOTFOUND %then %do;'; put '%do x=1 %to &repocnt;'; put 'options metarepository=&&repo&x;'; put 'data _null_;'; put 'length lib_uri $256;'; put 'call missing (of _all_);'; put 'rc=metadata_getnobj("omsobj:SASLibrary?@Libref =''&libref''",1,lib_uri);'; put 'call symputx(''lib_uri'',coalescec(lib_uri,''NOTFOUND''),''l'');'; put 'run;'; put '%if &lib_uri ne NOTFOUND %then %let x=%eval(&repocnt+1);'; put '%end;'; put '%end;'; put '%if &type=READ %then %do;'; put '%mm_assignlib(&libref)'; put '%end;'; put '%else %if &type=WRITE %then %do;'; put '%mm_assigndirectlib(&libref,open_passthrough=&passthru)'; put '%end;'; put 'options metarepository=&repo;'; put '%mend dc_assignlib;'; put '%macro mm_getlibs('; put 'outds=work.mm_getLibs'; put ')/*/STORE SOURCE*/;'; put '/*'; put 'flags:'; put 'OMI_SUCCINCT (2048) Do not return attributes with null values.'; put 'OMI_GET_METADATA (256) Executes a GetMetadata call for each object that'; put 'is returned by the GetMetadataObjects method.'; put 'OMI_ALL_SIMPLE (8) Gets all of the attributes of the requested object.'; put '*/'; put 'data _null_;'; put 'flags=2048+256+8;'; put 'call symputx(''flags'',flags,''l'');'; put 'run;'; put '* use a temporary fileref to hold the response;'; put 'filename response temp;'; put '/* get list of libraries */'; put 'proc metadata in='; put ''''; put '$METAREPOSITORY'; put 'SASLibrary'; put ''; put 'SAS'; put '&flags'; put ''; put ''''; put 'out=response;'; put 'run;'; put '/* write the response to the log for debugging */'; put 'data _null_;'; put 'infile response lrecl=32767;'; put 'input;'; put 'put _infile_;'; put 'run;'; put '/* create an XML map to read the response */'; put 'filename sxlemap temp;'; put 'data _null_;'; put 'file sxlemap;'; put 'put '''';'; put 'put '''';'; put 'put ''//Objects/SASLibrary'';'; put 'put ''>17'';'; put 'put ''//Objects/SASLibrary/@Id'';'; put 'put ''256>'';'; put 'put ''//Objects/SASLibrary/@Name'';'; put 'put ''8'';'; put 'put ''//Objects/SASLibrary/@Libref'';'; put 'put ''>12'';'; put 'put ''//Objects/SASLibrary/@Engine'';'; put 'put ''
'';'; put 'run;'; put 'libname _XML_ xml xmlfileref=response xmlmap=sxlemap;'; put '/* sort the response by library name */'; put 'proc sort data=_XML_.saslibrary out=&outds;'; put 'by libraryname;'; put 'run;'; put '/* clear references */'; put 'filename sxlemap clear;'; put 'filename response clear;'; put 'libname _XML_ clear;'; put '%mend mm_getlibs;'; put '%macro mm_gettables('; put 'uri='; put ',outds=work.mm_gettables'; put ',getauth=YES'; put ')/*/STORE SOURCE*/;'; put 'data &outds;'; put 'length uri serveruri conn_uri domainuri libname ServerContext AuthDomain'; put 'path_schema usingpkguri type tableuri $256 id $17'; put 'libdesc $200 libref engine $8 IsDBMSLibname IsPreassigned $1'; put 'tablename $50 /* metadata table names can be longer than $32 */'; put ';'; put 'keep libname libdesc libref engine ServerContext path_schema AuthDomain'; put 'tableuri tablename IsPreassigned IsDBMSLibname id;'; put 'call missing (of _all_);'; put 'uri=symget(''uri'');'; put 'rc= metadata_getattr(uri, "Name", libname);'; put 'if rc <0 then do;'; put 'put ''The library is not defined in this metadata repository.'';'; put 'stop;'; put 'end;'; put 'rc= metadata_getattr(uri, "Desc", libdesc);'; put 'rc= metadata_getattr(uri, "Libref", libref);'; put 'rc= metadata_getattr(uri, "Engine", engine);'; put 'rc= metadata_getattr(uri, "IsDBMSLibname", IsDBMSLibname);'; put 'rc= metadata_getattr(uri, "IsPreassigned", IsPreassigned);'; put 'rc= metadata_getattr(uri, "Id", Id);'; put '/*** Get associated ServerContext ***/'; put 'rc= metadata_getnasn(uri, "DeployedComponents", 1, serveruri);'; put 'if rc > 0 then rc2= metadata_getattr(serveruri, "Name", ServerContext);'; put 'else ServerContext='''';'; put '/*** If the library is a DBMS library, get the Authentication Domain'; put 'associated with the DBMS connection credentials ***/'; put 'if IsDBMSLibname="1" and "&getauth"=''YES'' then do;'; put 'rc= metadata_getnasn(uri, "LibraryConnection", 1, conn_uri);'; put 'if rc>0 then do;'; put 'rc2= metadata_getnasn(conn_uri, "Domain", 1, domainuri);'; put 'if rc2>0 then rc3= metadata_getattr(domainuri, "Name", AuthDomain);'; put 'end;'; put 'end;'; put '/*** Get the path/database schema for this library ***/'; put 'rc=metadata_getnasn(uri, "UsingPackages", 1, usingpkguri);'; put 'if rc>0 then do;'; put 'rc=metadata_resolve(usingpkguri,type,id);'; put 'if type=''Directory'' then'; put 'rc=metadata_getattr(usingpkguri, "DirectoryName", path_schema);'; put 'else if type=''DatabaseSchema'' then'; put 'rc=metadata_getattr(usingpkguri, "Name", path_schema);'; put 'else path_schema="unknown";'; put 'end;'; put '/*** Get the tables associated with this library ***/'; put '/*** If DBMS, tables are associated with DatabaseSchema ***/'; put 'if type=''DatabaseSchema'' then do;'; put 't=1;'; put 'ntab=metadata_getnasn(usingpkguri, "Tables", t, tableuri);'; put 'if ntab>0 then do t=1 to ntab;'; put 'tableuri='''';'; put 'tablename='''';'; put 'ntab=metadata_getnasn(usingpkguri, "Tables", t, tableuri);'; put 'tabrc= metadata_getattr(tableuri, "Name", tablename);'; put 'output;'; put 'end;'; put 'else put ''Library '' libname '' has no tables registered'';'; put 'end;'; put 'else if type in (''Directory'',''SASLibrary'') then do;'; put 't=1;'; put 'ntab=metadata_getnasn(uri, "Tables", t, tableuri);'; put 'if ntab>0 then do t=1 to ntab;'; put 'tableuri='''';'; put 'tablename='''';'; put 'ntab=metadata_getnasn(uri, "Tables", t, tableuri);'; put 'tabrc= metadata_getattr(tableuri, "Name", tablename);'; put 'output;'; put 'end;'; put 'else put ''Library '' libname '' has no tables registered'';'; put 'end;'; put 'run;'; put 'proc sort;'; put 'by tablename tableuri;'; put 'run;'; put '%mend mm_gettables;'; put '%macro mm_getcols('; put 'tableuri='; put ',outds=work.mm_getcols'; put ')/*/STORE SOURCE*/;'; put 'data &outds;'; put 'keep col: SAS:;'; put 'length assoc uri coluri colname coldesc SASColumnType SASFormat SASInformat'; put 'SASPrecision SASColumnLength $256;'; put 'call missing (of _all_);'; put 'uri=symget(''tableuri'');'; put 'n=1;'; put 'do while (metadata_getnasn(uri,''Columns'',n,coluri)>0);'; put 'rc3=metadata_getattr(coluri,"Name",colname);'; put 'rc3=metadata_getattr(coluri,"Desc",coldesc);'; put 'rc4=metadata_getattr(coluri,"SASColumnType",SASColumnType);'; put 'rc5=metadata_getattr(coluri,"SASFormat",SASFormat);'; put 'rc6=metadata_getattr(coluri,"SASInformat",SASInformat);'; put 'rc7=metadata_getattr(coluri,"SASPrecision",SASPrecision);'; put 'rc8=metadata_getattr(coluri,"SASColumnLength",SASColumnLength);'; put 'output;'; put 'call missing(colname,coldesc,SASColumnType,SASFormat,SASInformat'; put ',SASPrecision,SASColumnLength);'; put 'n+1;'; put 'end;'; put 'run;'; put 'proc sort;'; put 'by colname;'; put 'run;'; put '%mend mm_getcols;'; put '%macro mm_createdataset(libds=,tableuri=,outds=work.mm_createdataset,mDebug=0);'; put '%local dbg errorcheck tempds1 tempds2 tempds3;'; put '%if &mDebug=0 %then %let dbg=*;'; put '%let errorcheck=1;'; put '%if %index(&libds,.)>0 %then %do;'; put '/* get lib uri */'; put 'data;run;%let tempds1=&syslast;'; put '%mm_getlibs(outds=&tempds1)'; put 'data _null_;'; put 'set &tempds1;'; put 'if upcase(libraryref)="%upcase(%scan(&libds,1,.))";'; put 'call symputx(''liburi'',LibraryId,''l'');'; put 'run;'; put '/* get ds uri */'; put 'data;run;%let tempds2=&syslast;'; put '%mm_gettables(uri=&liburi,outds=&tempds2)'; put 'data _null_;'; put 'set &tempds2;'; put 'where upcase(tablename)="%upcase(%scan(&libds,2,.))";'; put '&dbg putlog tableuri=;'; put 'call symputx(''tableuri'',tableuri);'; put 'run;'; put '%end;'; put 'data;run;'; put '%let tempds3=&syslast;'; put '%mm_getcols(tableuri=&tableuri,outds=&tempds3)'; put '%if %mf_nobs(&tempds3)=0 %then %do;'; put '%put &libds (&tableuri) has no columns defined!!;'; put 'data &outds;'; put 'run;'; put '%return;'; put '%end;'; put 'data _null_;'; put 'set &tempds3 end=last;'; put 'if _n_=1 then call execute(''data &outds;'');'; put 'length attrib $32767;'; put 'if SAScolumntype=''C'' then type=''$'';'; put 'attrib=''attrib ''!!cats(colname)!!'' length=''!!cats(type,SASColumnLength,''.'');'; put 'if not missing(sasformat) then fmt='' format=''!!cats(sasformat);'; put 'if not missing(sasinformat) then infmt='' informat=''!!cats(sasinformat);'; put 'if not missing(coldesc) then desc='' label=''!!quote(cats(coldesc));'; put 'attrib=trim(attrib)!!fmt!!infmt!!desc!!'';'';'; put 'call execute(attrib);'; put 'if last then call execute(''call missing(of _all_);stop;run;'');'; put 'run;'; put '%mend mm_createdataset;'; put '%macro dc_createdataset(libds=mm_getlibs,outds=viewdata);'; put '%mm_createdataset(libds=&libds,outds=viewdata)'; put '%mend dc_createdataset;'; put '%macro mm_gettableid('; put 'libref='; put ',ds='; put ',outds=work.mm_gettableid'; put ',mDebug=0'; put ')/*/STORE SOURCE*/;'; put '%local mD;'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing &sysmacroname..sas;'; put '%&mD.put _local_;'; put 'data &outds;'; put 'length uri usingpkguri id type tableuri tablename tmpuri $256;'; put 'call missing(of _all_);'; put 'keep tableuri tablename;'; put 'n=1;'; put 'rc=0;'; put 'if metadata_getnobj("omsobj:SASLibrary?@Libref=''&libref''",n,uri)<1 then do;'; put 'put "Library &libref not found";'; put 'stop;'; put 'end;'; put '&mD.putlog "uri is " uri;'; put 'if metadata_getnasn(uri, "UsingPackages", 1, usingpkguri)>0 then do;'; put 'rc=metadata_resolve(usingpkguri,type,id);'; put '&mD.putlog "Type is " type;'; put 'end;'; put 'if type=''DatabaseSchema'' then tmpuri=usingpkguri;'; put 'else tmpuri=uri;'; put 't=1;'; put 'do while(metadata_getnasn(tmpuri, "Tables", t, tableuri)>0);'; put 't+1;'; put 'rc= metadata_getattr(tableuri, "Name", tablename);'; put '&mD.putlog "Table is " tablename;'; put 'if upcase(tablename)="%upcase(&ds)" then do;'; put 'output;'; put 'end;'; put 'end;'; put 'run;'; put '%mend mm_gettableid;'; put '%macro dc_gettableid(libref='; put ',ds='; put ',outds=);'; put '%mm_gettableid(libref=&libref,ds=&ds,outds=&outds)'; put '%if %mf_nobs(&outds)=0 %then %do;'; put 'data &outds;'; put 'tableuri='''';'; put 'tablename="&ds";'; put 'run;'; put '%end;'; put '%mend dc_gettableid;'; put '%macro mf_existds(libds'; put ')/*/STORE SOURCE*/;'; put '%if %sysfunc(exist(&libds)) ne 1 & %sysfunc(exist(&libds,VIEW)) ne 1 %then 0;'; put '%else 1;'; put '%mend mf_existds;'; put '%macro mf_getvarcount(libds,typefilter=A'; put ')/*/STORE SOURCE*/;'; put '%local dsid nvars rc outcnt x;'; put '%let dsid=%sysfunc(open(&libds));'; put '%let nvars=.;'; put '%let outcnt=0;'; put '%let typefilter=%upcase(&typefilter);'; put '%if &dsid %then %do;'; put '%let nvars=%sysfunc(attrn(&dsid,NVARS));'; put '%if &typefilter=A %then %let outcnt=&nvars;'; put '%else %if &nvars>0 %then %do x=1 %to &nvars;'; put '/* increment based on variable type */'; put '%if %sysfunc(vartype(&dsid,&x))=&typefilter %then %do;'; put '%let outcnt=%eval(&outcnt+1);'; put '%end;'; put '%end;'; put '%let rc=%sysfunc(close(&dsid));'; put '%end;'; put '%else %do;'; put '%put unable to open &libds (rc=&dsid);'; put '%let rc=%sysfunc(close(&dsid));'; put '%end;'; put '&outcnt'; put '%mend mf_getvarcount;'; put '%macro mf_abort(mac=mf_abort.sas, msg=, iftrue=%str(1=1)'; put ')/des=''ungraceful abort'' /*STORE SOURCE*/;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%put NOTE: /// mf_abort macro executing //;'; put '%if %length(&mac)>0 %then %put NOTE- called by &mac;'; put '%put NOTE - &msg;'; put '%abort;'; put '%mend mf_abort;'; put '/** @endcond */'; put '%macro mf_verifymacvars('; put 'verifyVars /* list of macro variable NAMES */'; put ',makeUpcase=NO /* set to YES to make all the variable VALUES uppercase */'; put ',mAbort=SOFT'; put ')/*/STORE SOURCE*/;'; put '%local verifyIterator verifyVar abortmsg;'; put '%do verifyIterator=1 %to %sysfunc(countw(&verifyVars,%str( )));'; put '%let verifyVar=%qscan(&verifyVars,&verifyIterator,%str( ));'; put '%if not %symexist(&verifyvar) %then %do;'; put '%let abortmsg= Variable &verifyVar is MISSING;'; put '%goto exit_err;'; put '%end;'; put '%if %length(%trim(&&&verifyVar))=0 %then %do;'; put '%let abortmsg= Variable &verifyVar is EMPTY;'; put '%goto exit_err;'; put '%end;'; put '%if &makeupcase=YES %then %do;'; put '%let &verifyVar=%upcase(&&&verifyvar);'; put '%end;'; put '%end;'; put '%goto exit_success;'; put '%exit_err:'; put '%put &abortmsg;'; put '%mf_abort(iftrue=(&mabort ne SOFT),'; put 'mac=mf_verifymacvars,'; put 'msg=%str(&abortmsg)'; put ')'; put '0'; put '%return;'; put '%exit_success:'; put '1'; put '%mend mf_verifymacvars;'; put '%macro mddl_sas_cntlout(libds=WORK.CNTLOUT);'; put 'proc sql;'; put 'create table &libds('; put 'TYPE char(1) label='; put '''Format Type: either N (num fmt), C (char fmt), I (num infmt) or J (char infmt)'''; put ',FMTNAME char(32) label=''Format name'''; put ',FMTROW num label='; put '''CALCULATED Position of record by FMTNAME (reqd for multilabel formats)'''; put ',START char(32767) label=''Starting value for format'''; put '/*'; put 'Keep lengths of START and END the same to avoid this err:'; put '"Start is greater than end: -<."'; put 'Similar usage note: https://support.sas.com/kb/69/330.html'; put '*/'; put ',END char(32767) label=''Ending value for format'''; put ',LABEL char(32767) label=''Format value label'''; put ',MIN num length=3 label=''Minimum length'''; put ',MAX num length=3 label=''Maximum length'''; put ',DEFAULT num length=3 label=''Default length'''; put ',LENGTH num length=3 label=''Format length'''; put ',FUZZ num label=''Fuzz value'''; put ',PREFIX char(2) label=''Prefix characters'''; put ',MULT num label=''Multiplier'''; put ',FILL char(1) label=''Fill character'''; put ',NOEDIT num length=3 label=''Is picture string noedit?'''; put ',SEXCL char(1) label=''Start exclusion'''; put ',EEXCL char(1) label=''End exclusion'''; put ',HLO char(13) label='; put '''More info: https://core.sasjs.io/mddl__sas__cntlout_8sas_source.html'''; put ',DECSEP char(1) label=''Decimal separator'''; put ',DIG3SEP char(1) label=''Three-digit separator'''; put ',DATATYPE char(8) label=''Date/time/datetime?'''; put ',LANGUAGE char(8) label=''Language for date strings'''; put ');'; put '%local lib;'; put '%let libds=%upcase(&libds);'; put '%if %index(&libds,.)=0 %then %let lib=WORK;'; put '%else %let lib=%scan(&libds,1,.);'; put 'proc datasets lib=&lib noprint;'; put 'modify %scan(&libds,-1,.);'; put 'index create'; put 'pk_cntlout=(type fmtname fmtrow)'; put '/nomiss unique;'; put 'quit;'; put '%mend mddl_sas_cntlout;'; put '%macro mp_aligndecimal(var,width=8);'; put '%local tmpvar;'; put '%let tmpvar=%mf_getuniquename(prefix=aligndp);'; put 'length &tmpvar $&width;'; put 'if index(&var,''.'') then do;'; put '&tmpvar=cats(scan(&var,1,''.''));'; put '&tmpvar=right(&tmpvar);'; put '&var=&tmpvar!!''.''!!cats(scan(&var,2,''.''));'; put 'end;'; put 'else do;'; put '&tmpvar=cats(&var);'; put '&tmpvar=right(&tmpvar);'; put '&var=&tmpvar;'; put 'end;'; put 'drop &tmpvar;'; put '%mend mp_aligndecimal;'; put '%macro mp_cntlout('; put 'iftrue=(1=1)'; put ',libcat='; put ',cntlout=work.fmtextract'; put ',fmtlist=0'; put ')/*/STORE SOURCE*/;'; put '%local ddlds cntlds i;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%let ddlds=%mf_getuniquename();'; put '%let cntlds=%mf_getuniquename();'; put '%mddl_sas_cntlout(libds=&ddlds)'; put '%if %index(&libcat,-)>0 and %scan(&libcat,2,-)=FC %then %do;'; put '%let libcat=%scan(&libcat,1,-);'; put '%end;'; put 'proc format lib=&libcat cntlout=&cntlds;'; put '%if "&fmtlist" ne "0" and "&fmtlist" ne "" %then %do;'; put 'select'; put '%do i=1 %to %sysfunc(countw(&fmtlist,%str( )));'; put '%scan(&fmtlist,&i,%str( ))'; put '%end;'; put ';'; put '%end;'; put 'run;'; put 'data &cntlout/nonote2err;'; put 'if 0 then set &ddlds;'; put 'set &cntlds;'; put 'by type fmtname notsorted;'; put '/* align the numeric values to avoid overlapping ranges */'; put 'if type in ("I","N") then do;'; put '%mp_aligndecimal(start,width=16)'; put '%mp_aligndecimal(end,width=16)'; put 'end;'; put '/* create row marker. Data cannot be sorted without it! */'; put 'if first.fmtname then fmtrow=1;'; put 'else fmtrow+1;'; put 'run;'; put 'proc sort;'; put 'by type fmtname fmtrow;'; put 'run;'; put 'proc sql;'; put 'drop table &ddlds,&cntlds;'; put '%mend mp_cntlout;'; put '/** @endcond */'; put '%macro mp_getcols(ds, outds=work.cols);'; put '%local dropds;'; put 'proc contents noprint data=&ds'; put 'out=_data_ (keep=name type length label varnum format:);'; put 'run;'; put '%let dropds=&syslast;'; put 'data &outds(keep=name type length varnum format label ddtype fmtname);'; put 'set &dropds(rename=(format=fmtname type=type2));'; put 'name=upcase(name);'; put 'if type2=2 then do;'; put 'length format $49.;'; put 'if fmtname='''' then format=cats(''$'',length,''.'');'; put 'else if formatl=0 then format=cats(fmtname,''.'');'; put 'else format=cats(fmtname,formatl,''.'');'; put 'type=''C'';'; put 'ddtype=''CHARACTER'';'; put 'end;'; put 'else do;'; put 'if fmtname='''' then format=cats(length,''.'');'; put 'else if formatl=0 then format=cats(fmtname,''.'');'; put 'else if formatd=0 then format=cats(fmtname,formatl,''.'');'; put 'else format=cats(fmtname,formatl,''.'',formatd);'; put 'type=''N'';'; put 'if format=:''DATETIME'' or format=:''E8601DT'' then ddtype=''DATETIME'';'; put 'else if format=:''DATE'' or format=:''DDMMYY'' or format=:''MMDDYY'''; put 'or format=:''YYMMDD'' or format=:''E8601DA'' or format=:''B8601DA'''; put 'or format=:''MONYY'''; put 'then ddtype=''DATE'';'; put 'else if format=:''TIME'' then ddtype=''TIME'';'; put 'else ddtype=''NUMERIC'';'; put 'end;'; put 'if label='''' then label=name;'; put 'run;'; put 'proc sql;'; put 'drop table &dropds;'; put '%mend mp_getcols;'; put '/** @cond */'; put '%macro mf_existfeature(feature'; put ')/*/STORE SOURCE*/;'; put '%let feature=%upcase(&feature);'; put '%local platform;'; put '%let platform=%mf_getplatform();'; put '%if &feature= %then %do;'; put '%put No feature was requested for detection;'; put '%end;'; put '%else %if &feature=COLCONSTRAINTS %then %do;'; put '%if "%substr(&sysver,1,1)"="4" or "%substr(&sysver,1,1)"="5" %then 0;'; put '%else 1;'; put '%end;'; put '%else %if &feature=PROCLUA %then %do;'; put '/* https://blogs.sas.com/content/sasdummy/2015/08/03/using-lua-within-your-sas-programs */'; put '%if &platform=SASVIYA %then 1;'; put '%else %if "&sysver"="9.2" or "&sysver"="9.3" %then 0;'; put '%else %if "&SYSVLONG" < "9.04.01M3" %then 0;'; put '%else 1;'; put '%end;'; put '%else %if &feature=DBMS_MEMTYPE %then %do;'; put '/* does dbms_memtype exist in dictionary.tables? */'; put '%if "%substr(&sysver,1,1)"="4" or "%substr(&sysver,1,1)"="5" %then 0;'; put '%else 1;'; put '%end;'; put '%else %if &feature=EXPORTXLS %then %do;'; put '/* is it possible to PROC EXPORT an excel file? */'; put '%if "%substr(&sysver,1,1)"="4" or "%substr(&sysver,1,1)"="5" %then 1;'; put '%else %if %sysfunc(sysprod(SAS/ACCESS Interface to PC Files)) = 1 %then 1;'; put '%else 0;'; put '%end;'; put '%else %do;'; put '-1'; put '%put &sysmacroname: &feature not found;'; put '%end;'; put '%mend mf_existfeature;'; put '/** @endcond */'; put '%macro mf_getschema(libref'; put ')/*/STORE SOURCE*/;'; put '%local dsid vnum rc schema;'; put '/* in case the parameter is a libref.tablename, pull off just the libref */'; put '%let libref = %upcase(%scan(&libref, 1, %str(.)));'; put '%let dsid=%sysfunc(open(sashelp.vlibnam(where=('; put 'libname="%upcase(&libref)" and sysname=''Schema/Owner'''; put ')),i));'; put '%if (&dsid ^= 0) %then %do;'; put '%let vnum=%sysfunc(varnum(&dsid,SYSVALUE));'; put '%let rc=%sysfunc(fetch(&dsid));'; put '%let schema=%sysfunc(getvarc(&dsid,&vnum));'; put '%put &libref. schema is &schema.;'; put '%let rc= %sysfunc(close(&dsid));'; put '%end;'; put '&schema'; put '%mend mf_getschema;'; put '/** @endcond */'; put '%macro mf_isblank(param'; put ')/*/STORE SOURCE*/;'; put '%sysevalf(%superq(param)=,boolean)'; put '%mend mf_isblank;'; put '%macro mp_dropmembers('; put 'list /* space separated list of datasets / views */'; put ',libref=WORK /* can only drop from a single library at a time */'; put ',iftrue=%str(1=1)'; put ')/*/STORE SOURCE*/;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%if %mf_isblank(&list) %then %do;'; put '%put NOTE: nothing to drop!;'; put '%return;'; put '%end;'; put 'proc datasets lib=&libref nolist;'; put 'delete &list;'; put 'delete &list /mtype=view;'; put 'run;'; put '%mend mp_dropmembers;'; put '%macro mp_getconstraints(lib=WORK'; put ',ds='; put ',outds=mp_getconstraints'; put ',mdebug=0'; put ')/*/STORE SOURCE*/;'; put '%let lib=%upcase(&lib);'; put '%let ds=%upcase(&ds);'; put '/**'; put '* Cater for environments where sashelp.vcncolu is not available'; put '*/'; put '%if %sysfunc(exist(sashelp.vcncolu,view))=0 %then %do;'; put 'proc sql;'; put 'create table &outds('; put 'libref char(8)'; put ',TABLE_NAME char(32)'; put ',constraint_type char(8) label=''Constraint Type'''; put ',constraint_name char(32) label=''Constraint Name'''; put ',column_name char(32) label=''Column'''; put ',constraint_order num'; put ');'; put '%return;'; put '%end;'; put '/**'; put '* Neither dictionary tables nor sashelp provides a constraint order column,'; put '* however they DO arrive in the correct order. So, create the col.'; put '**/'; put '%local vw;'; put '%let vw=%mf_getuniquename(prefix=mp_getconstraints_vw_);'; put 'data &vw /view=&vw;'; put 'set sashelp.vcncolu;'; put 'where table_catalog="&lib";'; put '/* use retain approach to reset the constraint order with each constraint */'; put 'length tmp $1000;'; put 'retain tmp;'; put 'drop tmp;'; put 'if tmp ne catx(''|'',table_catalog,table_name,constraint_name) then do;'; put 'constraint_order=1;'; put 'end;'; put 'else constraint_order+1;'; put 'tmp=catx(''|'',table_catalog, table_name,constraint_name);'; put 'run;'; put '/* must use SQL as proc datasets does not support length changes */'; put 'proc sql noprint;'; put 'create table &outds as'; put 'select upcase(a.TABLE_CATALOG) as libref'; put ',upcase(a.TABLE_NAME) as TABLE_NAME'; put ',a.constraint_type'; put ',a.constraint_name'; put ',b.column_name'; put ',b.constraint_order'; put 'from dictionary.TABLE_CONSTRAINTS a'; put 'left join &vw b'; put 'on upcase(a.TABLE_CATALOG)=upcase(b.TABLE_CATALOG)'; put 'and upcase(a.TABLE_NAME)=upcase(b.TABLE_NAME)'; put 'and a.constraint_name=b.constraint_name'; put '/**'; put '* We cannot apply this clause to the underlying dictionary table. See:'; put '* https://communities.sas.com/t5/SAS-Programming/Unexpected-Where-Clause-behaviour-in-dictionary-TABLE/m-p/771554#M244867'; put '* cannot use`where calculated libref="&lib"` either as it will STILL execute'; put '* all the underlying constraint queries, causing exception errors in some'; put '* cases: https://github.com/sasjs/core/issues/283'; put '*/'; put 'where a.TABLE_CATALOG="&lib"'; put '%if "&ds" ne "" %then %do;'; put 'and upcase(a.TABLE_NAME)="&ds"'; put 'and upcase(b.TABLE_NAME)="&ds"'; put '%end;'; put 'order by libref, table_name, constraint_name, constraint_order'; put ';'; put '/* tidy up */'; put '%mp_dropmembers('; put '&vw,'; put 'iftrue=(&mdebug=0)'; put ')'; put '%mend mp_getconstraints;'; put '%macro mp_getpk('; put 'lib,'; put 'ds=0,'; put 'outds=work.mp_getpk,'; put 'mdebug=0'; put ')/*/STORE SOURCE*/;'; put '%local engine schema ds1 ds2 ds3 dsn tabs1 tabs2 sum pk4sure pkdefault finalpks'; put 'pkfromindex;'; put '%let lib=%upcase(&lib);'; put '%let ds=%upcase(&ds);'; put '%let engine=%mf_getengine(&lib);'; put '%let schema=%mf_getschema(&lib);'; put '%let ds1=%mf_getuniquename(prefix=getpk_ds1);'; put '%let ds2=%mf_getuniquename(prefix=getpk_ds2);'; put '%let ds3=%mf_getuniquename(prefix=getpk_ds3);'; put '%let tabs1=%mf_getuniquename(prefix=getpk_tabs1);'; put '%let tabs2=%mf_getuniquename(prefix=getpk_tabs2);'; put '%let sum=%mf_getuniquename(prefix=getpk_sum);'; put '%let pk4sure=%mf_getuniquename(prefix=getpk_pk4sure);'; put '%let pkdefault=%mf_getuniquename(prefix=getpk_pkdefault);'; put '%let pkfromindex=%mf_getuniquename(prefix=getpk_pkfromindex);'; put '%let finalpks=%mf_getuniquename(prefix=getpk_finalpks);'; put '%local dbg;'; put '%if &mdebug=1 %then %do;'; put '%put &sysmacroname entry vars:;'; put '%put _local_;'; put '%end;'; put '%else %let dbg=*;'; put 'proc sql;'; put 'create table &ds1 as'; put 'select libname as libref'; put ',upcase(memname) as dsn'; put ',memtype'; put ',upcase(name) as name'; put ',type'; put ',length'; put ',varnum'; put ',label'; put ',format'; put ',idxusage'; put ',notnull'; put 'from dictionary.columns'; put 'where upcase(libname)="&lib"'; put '%if &ds ne 0 %then %do;'; put 'and upcase(memname)="&ds"'; put '%end;'; put ';'; put '%if &engine=SQLSVR %then %do;'; put 'proc sql;'; put 'connect using &lib;'; put 'create table work.&ds2 as'; put 'select * from connection to &lib('; put 'select'; put 's.name as SchemaName,'; put 't.name as memname,'; put 'tc.name as name,'; put 'ic.key_ordinal as KeyOrderNr'; put 'from'; put 'sys.schemas s'; put 'inner join sys.tables t on s.schema_id=t.schema_id'; put 'inner join sys.indexes i on t.object_id=i.object_id'; put 'inner join sys.index_columns ic on i.object_id=ic.object_id'; put 'and i.index_id=ic.index_id'; put 'inner join sys.columns tc on ic.object_id=tc.object_id'; put 'and ic.column_id=tc.column_id'; put 'where i.is_primary_key=1'; put 'and s.name=%str(%'')&schema%str(%'')'; put 'order by t.name, ic.key_ordinal ;'; put ');disconnect from &lib;'; put 'create table &ds3 as'; put 'select a.*'; put ',case when b.name is not null then 1 else 0 end as pk_ind'; put 'from work.&ds1 a'; put 'left join work.&ds2 b'; put 'on a.dsn=b.memname'; put 'and upcase(a.name)=upcase(b.name)'; put 'order by libref,dsn;'; put '%end;'; put '%else %do;'; put '%if &ds = 0 %then %let dsn=;'; put '/* get all constraints, in constraint order*/'; put '%mp_getconstraints(lib=&lib,ds=&dsn,outds=work.&ds2)'; put '/* extract cols that are clearly primary keys */'; put 'proc sql;'; put 'create table &pk4sure as'; put 'select libref'; put ',table_name'; put ',constraint_name'; put ',constraint_order'; put ',column_name as name'; put 'from work.&ds2'; put 'where constraint_type=''PRIMARY'''; put 'order by 1,2,3,4;'; put '/* extract unique constraints where every col is also NOT NULL */'; put 'proc sql;'; put 'create table &sum as'; put 'select a.libref'; put ',a.table_name'; put ',a.constraint_name'; put ',count(a.column_name) as unq_cnt'; put ',count(b.column_name) as nul_cnt'; put 'from work.&ds2(where=(constraint_type =''UNIQUE'')) a'; put 'left join work.&ds2(where=(constraint_type =''NOT NULL'')) b'; put 'on a.libref=b.libref'; put 'and a.table_name=b.table_name'; put 'and a.column_name=b.column_name'; put 'group by 1,2,3'; put 'having unq_cnt=nul_cnt;'; put '/* extract cols from the relevant unique constraints */'; put 'create table &pkdefault as'; put 'select a.libref'; put ',a.table_name'; put ',a.constraint_name'; put ',b.constraint_order'; put ',b.column_name as name'; put 'from &sum a'; put 'left join &ds2(where=(constraint_type =''UNIQUE'')) b'; put 'on a.libref=b.libref'; put 'and a.table_name=b.table_name'; put 'and a.constraint_name=b.constraint_name'; put 'order by 1,2,3,4;'; put '/* extract cols from the relevant unique INDEXES */'; put 'create table &pkfromindex as'; put 'select libname as libref'; put ',memname as table_name'; put ',indxname as constraint_name'; put ',indxpos as constraint_order'; put ',name'; put 'from dictionary.indexes'; put 'where nomiss=''yes'' and unique=''yes'' and upcase(libname)="&lib"'; put '%if &ds ne 0 %then %do;'; put 'and upcase(memname)="&ds"'; put '%end;'; put 'order by 1,2,3,4;'; put '/* create one table */'; put 'data &finalpks;'; put 'set &pkdefault &pk4sure &pkfromindex;'; put 'pk_ind=1;'; put '/* if there are multiple unique constraints, take the first */'; put 'by libref table_name constraint_name;'; put 'retain keepme;'; put 'if first.table_name then keepme=1;'; put 'if first.constraint_name and not first.table_name then keepme=0;'; put 'if keepme=1;'; put 'run;'; put '/* join back to starting table */'; put 'proc sql;'; put 'create table &ds3 as'; put 'select a.*'; put ',b.constraint_order'; put ',case when b.pk_ind=1 then 1 else 0 end as pk_ind'; put 'from work.&ds1 a'; put 'left join work.&finalpks b'; put 'on a.libref=b.libref'; put 'and a.dsn=b.table_name'; put 'and upcase(a.name)=upcase(b.name)'; put 'order by libref,dsn,constraint_order;'; put '%end;'; put '/* prepare tables */'; put 'proc sql;'; put 'create table work.&tabs1 as select'; put 'libname as libref'; put ',upcase(memname) as dsn'; put ',memtype'; put '%if %mf_existfeature(DBMS_MEMTYPE)=1 %then %do;'; put ',dbms_memtype'; put '%end;'; put '%else %do;'; put ',''n/a'' as dbms_memtype format=$32.'; put '%end;'; put ',typemem'; put ',memlabel'; put ',nvar'; put ',compress'; put 'from dictionary.tables'; put 'where upcase(libname)="&lib"'; put '%if &ds ne 0 %then %do;'; put 'and upcase(memname)="&ds"'; put '%end;'; put ';'; put 'data &tabs2;'; put 'set &ds3;'; put 'length pk_fields $512;'; put 'retain pk_fields;'; put 'by libref dsn constraint_order;'; put 'if first.dsn then pk_fields='''';'; put 'if pk_ind=1 then pk_fields=catx('' '',pk_fields,name);'; put 'if last.dsn then output;'; put 'run;'; put 'proc sql;'; put 'create table &outds as'; put 'select a.libref'; put ',a.dsn'; put ',a.memtype'; put ',a.dbms_memtype'; put ',a.typemem'; put ',a.memlabel'; put ',a.nvar'; put ',a.compress'; put ',b.pk_fields'; put 'from work.&tabs1 a'; put 'left join work.&tabs2 b'; put 'on a.libref=b.libref'; put 'and a.dsn=b.dsn;'; put '/* tidy up */'; put '%mp_dropmembers('; put '&ds1 &ds2 &ds3 &dsn &tabs1 &tabs2 &sum &pk4sure &pkdefault &finalpks,'; put 'iftrue=(&mdebug=0)'; put ')'; put '%mend mp_getpk;'; put '%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y'; put ',engine=DATASTEP'; put ',missing=NULL'; put ',showmeta=N'; put ',maxobs=MAX'; put ')/*/STORE SOURCE*/;'; put '%local tempds colinfo fmtds i numcols numobs stmt_obs lastobs optval'; put 'tmpds1 tmpds2 tmpds3 tmpds4;'; put '%let numcols=0;'; put '%if &maxobs ne MAX %then %let stmt_obs=%str(if _n_>&maxobs then stop;);'; put '%if &action=OPEN %then %do;'; put 'options nobomfile;'; put 'data _null_;file &jref encoding=''utf-8'' lrecl=200;'; put 'put ''{"PROCESSED_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''"'';'; put 'run;'; put '%end;'; put '%else %if (&action=ARR or &action=OBJ) %then %do;'; put '/* force variable names to always be uppercase in the JSON */'; put 'options validvarname=upcase;'; put '/* To avoid issues with _webout on EBI - such as encoding diffs and truncation'; put '(https://support.sas.com/kb/49/325.html) we use temporary files */'; put 'filename _sjs1 temp lrecl=200 ;'; put 'data _null_; file _sjs1 encoding=''utf-8'';'; put 'put ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":";'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjs1 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs1 clear;'; put '/* grab col defs */'; put 'proc contents noprint data=&ds'; put 'out=_data_(keep=name type length format formatl formatd varnum label);'; put 'run;'; put '%let colinfo=%scan(&syslast,2,.);'; put 'proc sort data=&colinfo;'; put 'by varnum;'; put 'run;'; put '/* move meta to mac vars */'; put 'data &colinfo;'; put 'if _n_=1 then call symputx(''numcols'',nobs,''l'');'; put 'set &colinfo end=last nobs=nobs;'; put 'name=upcase(name);'; put '/* fix formats */'; put 'if type=2 or type=6 then do;'; put 'typelong=''char'';'; put 'length fmt $49.;'; put 'if format='''' then fmt=cats(''$'',length,''.'');'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else fmt=cats(format,formatl,''.'');'; put 'end;'; put 'else do;'; put 'typelong=''num'';'; put 'if format='''' then fmt=''best.'';'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else if formatd=0 then fmt=cats(format,formatl,''.'');'; put 'else fmt=cats(format,formatl,''.'',formatd);'; put 'end;'; put '/* 32 char unique name */'; put 'newname=''sasjs''!!substr(cats(put(md5(name),$hex32.)),1,27);'; put 'call symputx(cats(''name'',_n_),name,''l'');'; put 'call symputx(cats(''newname'',_n_),newname,''l'');'; put 'call symputx(cats(''length'',_n_),length,''l'');'; put 'call symputx(cats(''fmt'',_n_),fmt,''l'');'; put 'call symputx(cats(''type'',_n_),type,''l'');'; put 'call symputx(cats(''typelong'',_n_),typelong,''l'');'; put 'call symputx(cats(''label'',_n_),coalescec(label,name),''l'');'; put '/* overwritten when fmt=Y and a custom format exists in catalog */'; put 'if typelong=''num'' then call symputx(cats(''fmtlen'',_n_),200,''l'');'; put 'else call symputx(cats(''fmtlen'',_n_),min(32767,ceil((length+10)*1.5)),''l'');'; put 'run;'; put '%let tempds=%substr(_%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql;'; put 'select count(*) into: lastobs from &ds;'; put '%if &maxobs ne MAX %then %let lastobs=%sysfunc(min(&lastobs,&maxobs));'; put '%if &engine=PROCJSON %then %do;'; put '%if &missing=STRING %then %do;'; put '%put &sysmacroname: Special Missings not supported in proc json.;'; put '%put &sysmacroname: Switching to DATASTEP engine;'; put '%goto datastep;'; put '%end;'; put 'data &tempds;'; put 'set &ds;'; put '&stmt_obs;'; put '%if &fmt=N %then format _numeric_ best32.;;'; put '/* PRETTY is necessary to avoid line truncation in large files */'; put 'filename _sjs2 temp lrecl=131068 encoding=''utf-8'';'; put 'proc json out=_sjs2 pretty'; put '%if &action=ARR %then nokeys ;'; put ';export &tempds / nosastags fmtnumeric;'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs2 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs2 clear;'; put '%end;'; put '%else %if &engine=DATASTEP %then %do;'; put '%datastep:'; put '%if %sysfunc(exist(&ds)) ne 1 & %sysfunc(exist(&ds,VIEW)) ne 1'; put '%then %do;'; put '%put &sysmacroname: &ds NOT FOUND!!!;'; put '%return;'; put '%end;'; put '%if &fmt=Y %then %do;'; put '/**'; put '* Extract format definitions'; put '* First, by getting library locations from dictionary.formats'; put '* Then, by exporting the width using proc format'; put '* Cannot use maxw from sashelp.vformat as not always populated'; put '* Cannot use fmtinfo() as not supported in all flavours'; put '*/'; put '%let tmpds1=%substr(fmtsum%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds2=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds3=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds4=%substr(col%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql noprint;'; put 'create table &tmpds1 as'; put 'select cats(libname,''.'',memname) as FMTCAT,'; put 'FMTNAME'; put 'from dictionary.formats'; put 'where fmttype=''F'' and libname is not null'; put 'and fmtname in (select format from &colinfo where format is not null)'; put 'order by 1;'; put 'create table &tmpds2('; put 'FMTNAME char(32),'; put 'LENGTH num'; put ');'; put '%local catlist cat fmtlist i;'; put 'select distinct fmtcat into: catlist separated by '' '' from &tmpds1;'; put '%do i=1 %to %sysfunc(countw(&catlist,%str( )));'; put '%let cat=%scan(&catlist,&i,%str( ));'; put 'proc sql;'; put 'select distinct fmtname into: fmtlist separated by '' '''; put 'from &tmpds1 where fmtcat="&cat";'; put 'proc format lib=&cat cntlout=&tmpds3(keep=fmtname length);'; put 'select &fmtlist;'; put 'run;'; put 'proc sql;'; put 'insert into &tmpds2 select distinct fmtname,length from &tmpds3;'; put '%end;'; put 'proc sql;'; put 'create table &tmpds4 as'; put 'select a.*, b.length as MAXW'; put 'from &colinfo a'; put 'left join &tmpds2 b'; put 'on cats(a.format)=cats(upcase(b.fmtname))'; put 'order by a.varnum;'; put 'data _null_;'; put 'set &tmpds4;'; put 'if not missing(maxw);'; put 'call symputx('; put 'cats(''fmtlen'',_n_),'; put '/* vars need extra padding due to JSON escaping of special chars */'; put 'min(32767,ceil((max(length,maxw)+10)*1.5))'; put ',''l'''; put ');'; put 'run;'; put '/* configure varlenchk - as we are explicitly shortening the variables */'; put '%let optval=%sysfunc(getoption(varlenchk));'; put 'options varlenchk=NOWARN;'; put 'data _data_(compress=char);'; put '/* shorten the new vars */'; put 'length'; put '%do i=1 %to &numcols;'; put '&&name&i $&&fmtlen&i'; put '%end;'; put ';'; put '/* rename on entry */'; put 'set &ds(rename=('; put '%do i=1 %to &numcols;'; put '&&name&i=&&newname&i'; put '%end;'; put '));'; put '&stmt_obs;'; put 'drop'; put '%do i=1 %to &numcols;'; put '&&newname&i'; put '%end;'; put ';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=num %then %do;'; put '&&name&i=cats(put(&&newname&i,&&fmt&i));'; put '%end;'; put '%else %do;'; put '&&name&i=put(&&newname&i,&&fmt&i);'; put '%end;'; put '%end;'; put 'if _error_ then do;'; put 'call symputx(''syscc'',1012);'; put 'stop;'; put 'end;'; put 'run;'; put '%let fmtds=&syslast;'; put 'options varlenchk=&optval;'; put '%end;'; put 'proc format; /* credit yabwon for special null removal */'; put 'value bart (default=40)'; put '%if &missing=NULL %then %do;'; put '._ - .z = null'; put '%end;'; put '%else %do;'; put '._ = [quote()]'; put '. = null'; put '.a - .z = [quote()]'; put '%end;'; put 'other = [best.];'; put 'data &tempds;'; put 'attrib _all_ label='''';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'length &&name&i $&&fmtlen&i...;'; put 'format &&name&i $&&fmtlen&i...;'; put '%end;'; put '%end;'; put '%if &fmt=Y %then %do;'; put 'set &fmtds;'; put '%end;'; put '%else %do;'; put 'set &ds;'; put '%end;'; put '&stmt_obs;'; put 'format _numeric_ bart.;'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'if findc(&&name&i,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put '&&name&i=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,&&name&i)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else &&name&i=quote(cats(&&name&i));'; put '%end;'; put '%end;'; put 'run;'; put 'filename _sjs3 temp lrecl=131068 ;'; put 'data _null_;'; put 'file _sjs3 encoding=''utf-8'';'; put 'if _n_=1 then put "[";'; put 'set &tempds;'; put 'if _n_>1 then put "," @; put'; put '%if &action=ARR %then "[" ; %else "{" ;'; put '%do i=1 %to &numcols;'; put '%if &i>1 %then "," ;'; put '%if &action=OBJ %then """&&name&i"":" ;'; put '"&&name&i"n /* name literal for reserved variable names */'; put '%end;'; put '%if &action=ARR %then "]" ; %else "}" ; ;'; put '/* close out the table */'; put 'data _null_;'; put 'file _sjs3 mod encoding=''utf-8'';'; put 'put '']'';'; put 'run;'; put 'data _null_;'; put 'infile _sjs3 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs3 clear;'; put '%end;'; put 'proc sql;'; put 'drop table &colinfo, &tempds;'; put '%if %substr(&showmeta,1,1)=Y %then %do;'; put 'filename _sjs4 temp lrecl=131068 encoding=''utf-8'';'; put 'data _null_;'; put 'file _sjs4;'; put 'length label $350;'; put 'put ", ""$%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":{""vars"":{";'; put 'do i=1 to &numcols;'; put 'name=quote(trim(symget(cats(''name'',i))));'; put 'format=quote(trim(symget(cats(''fmt'',i))));'; put 'label=quote(prxchange(''s/\\/\\\\/'',-1,trim(symget(cats(''label'',i)))));'; put 'length=quote(trim(symget(cats(''length'',i))));'; put 'type=quote(trim(symget(cats(''typelong'',i))));'; put 'if i>1 then put "," @@;'; put 'put name '':{"format":'' format '',"label":'' label'; put ''',"length":'' length '',"type":'' type ''}'';'; put 'end;'; put 'put ''}}'';'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs4 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs4 clear;'; put '%end;'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put 'data _null_; file &jref encoding=''utf-8'' mod ;'; put 'put "}";'; put 'run;'; put '%end;'; put '%mend mp_jsonout;'; put '%macro mf_getvarlist(libds'; put ',dlm=%str( )'; put ',quote=no'; put ',typefilter=A'; put ')/*/STORE SOURCE*/;'; put '/* declare local vars */'; put '%local outvar dsid nvars x rc dlm q var vtype;'; put '/* credit Rowland Hale - byte34 is double quote, 39 is single quote */'; put '%if %upcase("e)=DOUBLE %then %let q=%qsysfunc(byte(34));'; put '%else %if %upcase("e)=SINGLE %then %let q=%qsysfunc(byte(39));'; put '/* open dataset in macro */'; put '%let dsid=%sysfunc(open(&libds));'; put '%if &dsid %then %do;'; put '%let nvars=%sysfunc(attrn(&dsid,NVARS));'; put '%if &nvars>0 %then %do;'; put '/* add variables with supplied delimeter */'; put '%do x=1 %to &nvars;'; put '/* get variable type */'; put '%let vtype=%sysfunc(vartype(&dsid,&x));'; put '%if &vtype=&typefilter or &typefilter=A %then %do;'; put '%let var=&q.%sysfunc(varname(&dsid,&x))&q.;'; put '%if &var=&q&q %then %do;'; put '%put &sysmacroname: Empty column found in &libds!;'; put '%let var=&q. &q.;'; put '%end;'; put '%if %quote(&outvar)=%quote() %then %let outvar=&var;'; put '%else %let outvar=&outvar.&dlm.&var.;'; put '%end;'; put '%end;'; put '%end;'; put '%let rc=%sysfunc(close(&dsid));'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: Unable to open &libds (rc=&dsid);'; put '%put &sysmacroname: SYSMSG= %sysfunc(sysmsg());'; put '%let rc=%sysfunc(close(&dsid));'; put '%end;'; put '%do;%unquote(&outvar)%end;'; put '%mend mf_getvarlist;'; put '%macro mf_getvartype(libds /* two level name */'; put ', var /* variable name from which to return the type */'; put ')/*/STORE SOURCE*/;'; put '%local dsid vnum vtype rc;'; put '/* Open dataset */'; put '%let dsid = %sysfunc(open(&libds));'; put '%if &dsid. > 0 %then %do;'; put '/* Get variable number */'; put '%let vnum = %sysfunc(varnum(&dsid, &var));'; put '/* Get variable type (C/N) */'; put '%if(&vnum. > 0) %then %let vtype = %sysfunc(vartype(&dsid, &vnum.));'; put '%else %do;'; put '%put NOTE: Variable &var does not exist in &libds;'; put '%let vtype = %str( );'; put '%end;'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: dataset &libds not opened! (rc=&dsid);'; put '%put &sysmacroname: %sysfunc(sysmsg());'; put '%return;'; put '%end;'; put '/* Close dataset */'; put '%let rc = %sysfunc(close(&dsid));'; put '/* Return variable type */'; put '&vtype'; put '%mend mf_getvartype;'; put '%macro mf_mkdir(dir'; put ')/*/STORE SOURCE*/;'; put '%local lastchar child parent;'; put '%let lastchar = %substr(&dir, %length(&dir));'; put '%if (%bquote(&lastchar) eq %str(:)) %then %do;'; put '/* Cannot create drive mappings */'; put '%return;'; put '%end;'; put '%if (%bquote(&lastchar)=%str(/)) or (%bquote(&lastchar)=%str(\)) %then %do;'; put '/* last char is a slash */'; put '%if (%length(&dir) eq 1) %then %do;'; put '/* one single slash - root location is assumed to exist */'; put '%return;'; put '%end;'; put '%else %do;'; put '/* strip last slash */'; put '%let dir = %substr(&dir, 1, %length(&dir)-1);'; put '%end;'; put '%end;'; put '%if (%sysfunc(fileexist(%bquote(&dir))) = 0) %then %do;'; put '/* directory does not exist so prepare to create */'; put '/* first get the childmost directory */'; put '%let child = %scan(&dir, -1, %str(/\:));'; put '/*'; put 'If child name = path name then there are no parents to create. Else'; put 'they must be recursively scanned.'; put '*/'; put '%if (%length(&dir) gt %length(&child)) %then %do;'; put '%let parent = %substr(&dir, 1, %length(&dir)-%length(&child));'; put '%mf_mkdir(&parent)'; put '%end;'; put '/*'; put 'Now create the directory. Complain loudly of any errs.'; put '*/'; put '%let dname = %sysfunc(dcreate(&child, &parent));'; put '%if (%bquote(&dname) eq ) %then %do;'; put '%put %str(ERR)OR: could not create &parent + &child;'; put '%abort cancel;'; put '%end;'; put '%else %do;'; put '%put Directory created: &dir;'; put '%end;'; put '%end;'; put '/* exit quietly if directory did exist.*/'; put '%mend mf_mkdir;'; put '%macro mp_searchdata(lib='; put ',ds='; put ',string= /* the query will use a contains (?) operator */'; put ',numval= /* numeric must match exactly */'; put ',outloc=0'; put ',outlib=MPSEARCH'; put ',outobs=-1'; put ',filter_text=%str(1=1)'; put ')/*/STORE SOURCE*/;'; put '%local table_list table table_num table colnum col start_tm check_tm vars type'; put 'coltype;'; put '%put process began at %sysfunc(datetime(),datetime19.);'; put '%if &syscc ge 4 %then %do;'; put '%put %str(WAR)NING: SYSCC=&syscc on macro entry;'; put '%return;'; put '%end;'; put '%if &string = %then %let type=N;'; put '%else %let type=C;'; put '%if "&outloc"="0" %then %do;'; put '%let outloc=%sysfunc(pathname(work))/%mf_getuniquename();'; put '%end;'; put '%mf_mkdir(&outloc)'; put 'libname &outlib "&outloc";'; put '/* get the list of tables in the library */'; put 'proc sql noprint;'; put 'select distinct memname into: table_list separated by '' '''; put 'from dictionary.tables'; put 'where upcase(libname)="%upcase(&lib)"'; put '%if &ds ne %then %do;'; put 'and upcase(memname)=%upcase("&ds")'; put '%end;'; put ';'; put '/* check that we have something to check */'; put '%if %length(&table_list)=0 %then %put library &lib contains no tables!;'; put '/* loop through each table */'; put '%else %do table_num=1 %to %sysfunc(countw(&table_list,%str( )));'; put '%let table=%scan(&table_list,&table_num,%str( ));'; put '%let vars=%mf_getvarlist(&lib..&table);'; put '%if %length(&vars)=0 %then %do;'; put '%put NO COLUMNS IN &lib..&table! This will be skipped.;'; put '%end;'; put '%else %do;'; put '%let check_tm=%sysfunc(datetime());'; put '/* prep input */'; put 'data &outlib..&table;'; put 'set &lib..&table;'; put 'where %unquote(&filter_text) and ( 0'; put '/* loop through columns */'; put '%do colnum=1 %to %sysfunc(countw(&vars,%str( )));'; put '%let col=%scan(&vars,&colnum,%str( ));'; put '%let coltype=%mf_getvartype(&lib..&table,&col);'; put '%if &type=C and &coltype=C %then %do;'; put '/* if a char column, see if it contains the string */'; put 'or ("&col"n ? "&string")'; put '%end;'; put '%else %if &type=N and &coltype=N %then %do;'; put '/* if numeric match exactly */'; put 'or ("&col"n = &numval)'; put '%end;'; put '%end;'; put ');'; put '%if &outobs>-1 %then %do;'; put 'if _n_ > &outobs then stop;'; put '%end;'; put 'run;'; put '%put Search query for &table took'; put '%sysevalf(%sysfunc(datetime())-&check_tm) seconds;'; put '%if &syscc ne 0 %then %do;'; put '%put %str(ERR)ROR: SYSCC=&syscc when processing &lib..&table;'; put '%return;'; put '%end;'; put '%if %mf_nobs(&outlib..&table)=0 %then %do;'; put 'proc sql;'; put 'drop table &outlib..&table;'; put '%end;'; put '%end;'; put '%end;'; put '%put process finished at %sysfunc(datetime(),datetime19.);'; put '%mend mp_searchdata;'; put '%macro mp_validatecol(incol,rule,outcol);'; put '/* tempcol is given a unique name with every invocation */'; put '%local tempcol;'; put '%let tempcol=%mf_getuniquename();'; put '%if &rule=ISINT %then %do;'; put '&outcol=0;'; put 'if not missing(&incol) then do;'; put '&tempcol=input(&incol,?? best32.);'; put 'if not missing(&tempcol) then if mod(&tempcol,1)=0 then &outcol=1;'; put 'end;'; put 'drop &tempcol;'; put '%end;'; put '%else %if &rule=ISNUM %then %do;'; put '/*'; put 'credit SOREN LASSEN'; put 'https://sasmacro.blogspot.com/2009/06/welcome-isnum-macro.html'; put '*/'; put '&tempcol=input(&incol,?? best32.);'; put 'if missing(&tempcol) then &outcol=0;'; put 'else &outcol=1;'; put 'drop &tempcol;'; put '%end;'; put '%else %if &rule=LIBDS %then %do;'; put '/* match libref.dataset */'; put 'if _n_=1 then do;'; put 'retain &tempcol;'; put '&tempcol=prxparse(''/^[_a-z]\w{0,7}\.[_a-z]\w{0,31}$/i'');'; put 'if missing(&tempcol) then do;'; put 'putlog ''ERR'' +(-1) "OR: Invalid expression for LIBDS";'; put 'stop;'; put 'end;'; put 'drop &tempcol;'; put 'end;'; put 'if prxmatch(&tempcol, trim(&incol)) then &outcol=1;'; put 'else &outcol=0;'; put '%end;'; put '%else %if &rule=FORMAT %then %do;'; put '/* match valid format - regex could probably be improved */'; put 'if _n_=1 then do;'; put 'retain &tempcol;'; put '&tempcol=prxparse(''/^[_a-z\$]\w{0,31}\.[0-9]*$/i'');'; put 'if missing(&tempcol) then do;'; put 'putlog ''ERR'' +(-1) "OR: Invalid expression for FORMAT";'; put 'stop;'; put 'end;'; put 'drop &tempcol;'; put 'end;'; put 'if prxmatch(&tempcol, trim(&incol)) then &outcol=1;'; put 'else &outcol=0;'; put '%end;'; put '%mend mp_validatecol;'; put '* SAS Macros end;'; put '* SAS Includes start;'; put '* SAS Includes end;'; put '* Binary Files start;'; put '* Binary Files end;'; put '* ServiceInit start;'; put 'options noquotelenmax ps=max;'; put '/* create dummy macros to prevent stpbegin / stpend from corrupting sessions */'; put '%macro stpbegin();'; put '%put NOTE: the STPBEGIN macro should not be used for web apps!;'; put '%mend stpbegin;'; put '%macro stpend();'; put '%put NOTE: the STPEND macro should not be used for web apps!;'; put '%mend stpend;'; put '* ServiceInit end;'; put '* Service start;'; put '/**'; put '@file viewdata.sas'; put '@brief Provide the raw view of the data'; put '@details Pass a LIBDS and FILTER_RK to return a dataset for viewing.'; put 'VIEW datasets include all columns / rows (unlike EDIT, which are filtered'; put 'for current records and don''t include the SCD2 etc cols).'; put '

Service Inputs

'; put '
SASCONTROLTABLE
'; put '|LIBDS:$41.|FILTER_RK:$5.|SEARCHTYPE:$4|SEARCHVAL:$1000'; put '|---|---|---|---'; put '|DC258467.MPE_X_TEST|-1|CHAR|Some String|'; put '

Service Outputs

'; put '
cols
'; put '@li DDTYPE'; put '@li FORMAT'; put '@li LABEL'; put '@li LENGTH'; put '@li NAME'; put '@li TYPE'; put '@li VARNUM'; put '
sasparams
'; put '@li FILTER_TEXT'; put '@li NOBS'; put '@li PK_FIELDS - string seperated list of primary key fields, if they exist'; put '@li TABLENAME'; put '@li TABLEURI'; put '@li VARS'; put '
viewdata
'; put 'The raw data from the target table.'; put '

SAS Macros

'; put '@li dc_assignlib.sas'; put '@li dc_createdataset.sas'; put '@li dc_gettableid.sas'; put '@li mf_existds.sas'; put '@li mf_getvarcount.sas'; put '@li mf_nobs.sas'; put '@li mf_verifymacvars.sas'; put '@li mp_abort.sas'; put '@li mp_cntlout.sas'; put '@li mp_getcols.sas'; put '@li mp_getpk.sas'; put '@li mp_jsonout.sas'; put '@li mp_searchdata.sas'; put '@li mp_validatecol.sas'; put '@li mpe_columnlevelsecurity.sas'; put '@li mpe_dsmeta.sas'; put '@li mpe_filtermaster.sas'; put '@version 9.2'; put '@author 4GL Apps Ltd'; put '@copyright 4GL Apps Ltd. This code may only be used within Data Controller'; put 'and may not be re-distributed or re-sold without the express permission of'; put '4GL Apps Ltd.'; put '**/'; put '%mpeinit()'; put '/* configure macvars */'; put '%global LIBDS FILTER_RK SEARCHVAL SEARCHTYPE FMT_IND;'; put '%let maxrows=250;'; put '/* avoid code injection */'; put '%let FMT_IND=0;'; put '%let SEARCHTYPE=;'; put '%let SEARCHVAL=;'; put '%let FILTER_RK=;'; put '%let LIBDS=;'; put '/**'; put '* Validate inputs'; put '*/'; put 'data work.intest;'; put 'length libds $41 filter_rk 8. searchval $100 searchtype $4;'; put 'set work.SASCONTROLTABLE;'; put '/* validate filter_rk */'; put 'if filter_rk le 0 then filter_rk=-1;'; put '/* check if the request is for a format catalog */'; put 'if substr(cats(reverse(libds)),1,3)=:''CF-'' then do;'; put 'libds=scan(libds,1,''-'');'; put 'putlog "Format Catalog Captured";'; put 'call symputx(''fmt_ind'',1);'; put 'end;'; put 'putlog (_all_)(=);'; put '/* validate libds */'; put '%mp_validatecol(LIBDS,LIBDS,is_libds)'; put 'if searchtype in (''CHAR'',''NUM'') then do;'; put 'searchval=tranwrd(searchval,''%'','''');'; put 'searchval=tranwrd(searchval,''&'','''');'; put 'searchval=tranwrd(searchval,'';'','''');'; put 'searchval=tranwrd(searchval,''"'','''');'; put 'call symputx(''searchtype'',searchtype);'; put 'call symputx(''searchval'',searchval);'; put 'end;'; put 'else if searchtype not in ('''',''NONE'') then do;'; put 'putlog ''ERR'' ''OR: Invalid searchtype:'' searchtype;'; put 'stop;'; put 'end;'; put 'if is_libds=0 then do;'; put 'putlog ''ERR'' ''OR: Invalid libds:'' libds;'; put 'stop;'; put 'end;'; put 'else do;'; put 'call symputx(''filter_rk'',filter_rk);'; put 'call symputx(''libds'',libds);'; put 'end;'; put 'output;'; put 'stop;'; put 'run;'; put '%mp_abort(iftrue= (%mf_verifymacvars(libds filter_rk fmt_ind)=0)'; put ',mac=&_program..sas'; put ',msg=%str(Problem with macro inputs)'; put ')'; put '%mp_abort(iftrue= (%mf_nobs(work.intest)=0)'; put ',mac=&_program'; put ',msg=%str(Some err with service inputs)'; put ')'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(syscc=&syscc)'; put ')'; put '/**'; put '* assign the Library'; put '*/'; put '%dc_assignlib(READ,%scan(&LIBDS,1,.))'; put '/* abort if looking for a format and the catalog doesn''t exist */'; put '%mp_abort(iftrue= (&fmt_ind=1 and %sysfunc(exist(&libds,CATALOG))=0)'; put ',mac=&_program..sas'; put ',msg=%str(Catalog &libds does not exist!)'; put ')'; put '/**'; put 'check if dataset can actually be opened - as library may exist but it may not'; put 'be possible to assign, and even if it can, the physical table may not exist'; put '**/'; put 'data _null_;'; put 'if &fmt_ind=0 then do;'; put 'dsid=open("&libds");'; put 'rc=close(dsid);'; put 'end;'; put 'else dsid=42;'; put 'call symputx(''existds'',dsid,''l'');'; put 'putlog ''dataset exists check:'' dsid;'; put 'run;'; put '/**'; put '* get the data'; put '*/'; put '%global dsobs;'; put '%let dsobs=0;'; put '%macro x();'; put '%if &existds>0 %then %do;'; put '%if &fmt_ind=1 %then %do;'; put '/* export format and point the libds to the output table from here on */'; put '%mp_cntlout('; put 'libcat=&libds'; put ',fmtlist=0'; put ',cntlout=work.fmtextract'; put ')'; put '%let libds=WORK.FMTEXTRACT;'; put 'proc datasets lib=work noprint;'; put 'modify FMTEXTRACT;'; put 'index create'; put 'pk_cntlout=(type fmtname fmtrow)'; put '/nomiss unique;'; put 'quit;'; put '%end;'; put 'proc sql noprint;'; put 'select count(*) into: dsobs from &libds;'; put '%put preparing query;'; put '%mpe_filtermaster(VIEW,&libds,'; put 'dclib=&mpelib,'; put 'filter_rk=&filter_rk,'; put 'outref=filtref,'; put 'outds=work.query'; put ')'; put '%put printing generated filterquery:;'; put 'data _null_;'; put 'infile filtref;'; put 'input;'; put 'putlog _infile_;'; put 'run;'; put '%if &searchtype=NONE or "%trim(&searchtype) " = " " %then %do;'; put '/* get row count */'; put 'filename rows temp;'; put 'data _null_;'; put 'file rows;'; put 'infile filtref end=eof;'; put 'input;'; put 'if _n_=1 then do;'; put 'put ''proc sql;'';'; put 'put "select count(*) into: dsobs from &libds where";'; put 'end;'; put 'put _infile_;'; put 'if eof then put '';'';'; put 'run;'; put 'data _null_;'; put 'infile rows;'; put 'input;'; put 'putlog _infile_;'; put 'run;'; put '%inc rows;'; put '/* send actual data, filtered and row-capped */'; put 'data work.viewdata;'; put 'set &libds;'; put 'where %inc filtref;;'; put 'if _n_>&maxrows then stop;'; put 'run;'; put '%if %mf_nobs(work.viewdata)=0 %then %do;'; put 'data work.viewdata;'; put '/* send empty row if empty table to help with hot rendering */'; put 'output;'; put 'set work.viewdata;'; put 'run;'; put '%end;'; put '%end;'; put '%else %do;'; put 'data work.vwsearch/view=work.vwsearch;'; put 'set &libds;'; put 'where %inc filtref;;'; put 'run;'; put '%if %upcase(&searchtype)=CHAR %then %do;'; put '%mp_searchdata(lib=work'; put ',ds=vwsearch'; put ',string=%superq(searchval)'; put ',outobs=&maxrows'; put ')'; put '%end;'; put '%else %if %upcase(&searchtype)=NUM %then %do;'; put '%mp_searchdata(lib=work'; put ',ds=vwsearch'; put ',numval=%superq(searchval)'; put ',outobs=&maxrows'; put ')'; put '%end;'; put '%if %mf_existds(libds=MPSEARCH.vwsearch) %then %do;'; put '%let dsobs=%mf_nobs(MPSEARCH.vwsearch);'; put 'data viewdata;'; put 'set MPSEARCH.vwsearch;'; put 'if _n_<&maxrows;'; put 'run;'; put '%end;'; put '%else %do;'; put '%let dsobs=0;'; put 'data viewdata;'; put 'set &libds;'; put 'stop;'; put 'run;'; put '%end;'; put '%end;'; put '%end;'; put '%else %do;'; put '/* physical table is not accessible so create from metatadata definition */'; put '%dc_createdataset(libds=&libds,outds=viewdata)'; put 'data viewData;'; put 'output;'; put 'set viewdata;'; put 'run;'; put '/* make filtref / work.query / work.groups to avoid downstream issues */'; put 'filename filtref temp;'; put 'data work.query;'; put 'file filtref;'; put 'x=0;'; put 'put x;'; put 'run;'; put 'data work.groups;'; put 'length groupuri groupname $32 groupdesc $128 ;'; put 'call missing (of _all_);'; put 'output;'; put 'stop;'; put 'run;'; put '%end;'; put '%mend x; %x()'; put '/* apply column level security */'; put '%mpe_columnlevelsecurity(%scan(&libds,1,.),%scan(&libds,2,.),work.viewdata'; put ',mode=VIEW'; put ',clsds=&mpelib..mpe_column_level_security'; put ',groupds=work.groups /* was created in mpe_filtermaster */'; put ',outds=work.viewdata2'; put ',outmeta=work.cls_rules'; put ')'; put '/* get table uri (if sas 9) to enable linking direct to lineage */'; put '%dc_gettableid(libref=%scan(&libds,1,.)'; put ',ds=%scan(&libds,2,.)'; put ',outds=work.parambase'; put ')'; put 'data _null_;'; put 'infile filtref end=eof;'; put 'input;'; put 'length filter_text $32767;'; put 'retain filter_text;'; put 'filter_text=catx('' '',filter_text,_infile_);'; put 'if eof then do;'; put 'if cats(filter_text)=''1=1'' then filter_text='''';'; put 'call symputx(''filter_text'',filter_text);'; put 'end;'; put 'run;'; put '%mp_getpk(%scan(&libds,1,.), ds=%scan(&libds,2,.), outds=work.pk_fields)'; put '%let pk_fields=;'; put 'data _null_;'; put 'set work.pk_fields;'; put 'call symputx(''pk_fields'',pk_fields);'; put 'run;'; put 'data work.sasparams;'; put 'set work.parambase;'; put 'format FILTER_TEXT $32767.;'; put 'FILTER_TEXT=symget(''FILTER_TEXT'');'; put 'length PK_FIELDS $512;'; put 'PK_FIELDS=symget(''PK_FIELDS'');'; put 'nobs=&dsobs;'; put 'vars=%mf_getvarcount(viewdata);'; put 'maxrows=&maxrows;'; put 'run;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(syscc=&syscc)'; put ')'; put '%mp_getcols(&libds, outds=cols)'; put '%mpe_dsmeta(&libds, outds=dsmeta)'; put '%webout(OPEN)'; put '%webout(OBJ,cls_rules)'; put '%webout(OBJ,cols)'; put '%webout(OBJ,dsmeta)'; put '%webout(OBJ,query)'; put '%webout(OBJ,sasparams)'; put '%webout(OBJ,viewData2,fmt=Y,missing=STRING,showmeta=YES,dslabel=viewdata)'; put '%webout(CLOSE)'; put '%mpeterm()'; put '* Service end;'; run; %mm_createwebservice(path=&appLoc/&path, name=&service, code=sascode, server=&serverName, replace=yes) filename sascode clear; %let service=viewlibarray; filename sascode temp lrecl=32767; data _null_; file sascode; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '/**'; put '@file mp_jsonout.sas'; put '@brief Writes JSON in SASjs format to a fileref'; put '@details This macro can be used to OPEN a JSON stream and send one or more'; put 'tables as arrays of rows, where each row can be an object or a nested array.'; put 'There are two engines available - DATASTEP or PROCJSON.'; put 'PROC JSON is fast but will produce errs like the ones below if'; put 'special chars are encountered.'; put '> (ERR)OR: Some code points did not transcode.'; put '> An object or array close is not valid at this point in the JSON text.'; put '> Date value out of range'; put 'If this happens, try running with ENGINE=DATASTEP.'; put 'The DATASTEP engine is used to handle special SAS missing numerics, and'; put 'can also convert entire datasets to formatted values. Output JSON is always'; put 'in UTF-8.'; put 'Usage:'; put 'filename tmp temp;'; put 'data class; set sashelp.class;run;'; put '%mp_jsonout(OPEN,jref=tmp)'; put '%mp_jsonout(OBJ,class,jref=tmp)'; put '%mp_jsonout(OBJ,class,dslabel=class2,jref=tmp,showmeta=Y)'; put '%mp_jsonout(CLOSE,jref=tmp)'; put 'data _null_;'; put 'infile tmp;'; put 'input;putlog _infile_;'; put 'run;'; put 'If you are building web apps with SAS then you are strongly encouraged to use'; put 'the mX_createwebservice macros in combination with the'; put '[sasjs adapter](https://github.com/sasjs/adapter).'; put 'For more information see https://sasjs.io'; put '@param [in] action Valid values:'; put '@li OPEN - opens the JSON'; put '@li OBJ - sends a table with each row as an object'; put '@li ARR - sends a table with each row in an array'; put '@li CLOSE - closes the JSON'; put '@param [in] ds The dataset to send. Must be a work table.'; put '@param [out] jref= (_webout) The fileref to which to send the JSON'; put '@param [out] dslabel= The name to give the table in the exported JSON'; put '@param [in] fmt= (Y) Whether to keep (Y) or strip (N) formats from the table'; put '@param [in] engine= (DATASTEP) Which engine to use to send the JSON. Options:'; put '@li PROCJSON (default)'; put '@li DATASTEP (more reliable when data has non standard characters)'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to JSON'; put '

Related Files

'; put '@li mp_ds2fmtds.sas'; put '@version 9.2'; put '@author Allan Bowe'; put '@source https://github.com/sasjs/core'; put '**/'; put '%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y'; put ',engine=DATASTEP'; put ',missing=NULL'; put ',showmeta=N'; put ',maxobs=MAX'; put ')/*/STORE SOURCE*/;'; put '%local tempds colinfo fmtds i numcols numobs stmt_obs lastobs optval'; put 'tmpds1 tmpds2 tmpds3 tmpds4;'; put '%let numcols=0;'; put '%if &maxobs ne MAX %then %let stmt_obs=%str(if _n_>&maxobs then stop;);'; put '%if &action=OPEN %then %do;'; put 'options nobomfile;'; put 'data _null_;file &jref encoding=''utf-8'' lrecl=200;'; put 'put ''{"PROCESSED_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''"'';'; put 'run;'; put '%end;'; put '%else %if (&action=ARR or &action=OBJ) %then %do;'; put '/* force variable names to always be uppercase in the JSON */'; put 'options validvarname=upcase;'; put '/* To avoid issues with _webout on EBI - such as encoding diffs and truncation'; put '(https://support.sas.com/kb/49/325.html) we use temporary files */'; put 'filename _sjs1 temp lrecl=200 ;'; put 'data _null_; file _sjs1 encoding=''utf-8'';'; put 'put ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":";'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjs1 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs1 clear;'; put '/* grab col defs */'; put 'proc contents noprint data=&ds'; put 'out=_data_(keep=name type length format formatl formatd varnum label);'; put 'run;'; put '%let colinfo=%scan(&syslast,2,.);'; put 'proc sort data=&colinfo;'; put 'by varnum;'; put 'run;'; put '/* move meta to mac vars */'; put 'data &colinfo;'; put 'if _n_=1 then call symputx(''numcols'',nobs,''l'');'; put 'set &colinfo end=last nobs=nobs;'; put 'name=upcase(name);'; put '/* fix formats */'; put 'if type=2 or type=6 then do;'; put 'typelong=''char'';'; put 'length fmt $49.;'; put 'if format='''' then fmt=cats(''$'',length,''.'');'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else fmt=cats(format,formatl,''.'');'; put 'end;'; put 'else do;'; put 'typelong=''num'';'; put 'if format='''' then fmt=''best.'';'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else if formatd=0 then fmt=cats(format,formatl,''.'');'; put 'else fmt=cats(format,formatl,''.'',formatd);'; put 'end;'; put '/* 32 char unique name */'; put 'newname=''sasjs''!!substr(cats(put(md5(name),$hex32.)),1,27);'; put 'call symputx(cats(''name'',_n_),name,''l'');'; put 'call symputx(cats(''newname'',_n_),newname,''l'');'; put 'call symputx(cats(''length'',_n_),length,''l'');'; put 'call symputx(cats(''fmt'',_n_),fmt,''l'');'; put 'call symputx(cats(''type'',_n_),type,''l'');'; put 'call symputx(cats(''typelong'',_n_),typelong,''l'');'; put 'call symputx(cats(''label'',_n_),coalescec(label,name),''l'');'; put '/* overwritten when fmt=Y and a custom format exists in catalog */'; put 'if typelong=''num'' then call symputx(cats(''fmtlen'',_n_),200,''l'');'; put 'else call symputx(cats(''fmtlen'',_n_),min(32767,ceil((length+10)*1.5)),''l'');'; put 'run;'; put '%let tempds=%substr(_%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql;'; put 'select count(*) into: lastobs from &ds;'; put '%if &maxobs ne MAX %then %let lastobs=%sysfunc(min(&lastobs,&maxobs));'; put '%if &engine=PROCJSON %then %do;'; put '%if &missing=STRING %then %do;'; put '%put &sysmacroname: Special Missings not supported in proc json.;'; put '%put &sysmacroname: Switching to DATASTEP engine;'; put '%goto datastep;'; put '%end;'; put 'data &tempds;'; put 'set &ds;'; put '&stmt_obs;'; put '%if &fmt=N %then format _numeric_ best32.;;'; put '/* PRETTY is necessary to avoid line truncation in large files */'; put 'filename _sjs2 temp lrecl=131068 encoding=''utf-8'';'; put 'proc json out=_sjs2 pretty'; put '%if &action=ARR %then nokeys ;'; put ';export &tempds / nosastags fmtnumeric;'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs2 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs2 clear;'; put '%end;'; put '%else %if &engine=DATASTEP %then %do;'; put '%datastep:'; put '%if %sysfunc(exist(&ds)) ne 1 & %sysfunc(exist(&ds,VIEW)) ne 1'; put '%then %do;'; put '%put &sysmacroname: &ds NOT FOUND!!!;'; put '%return;'; put '%end;'; put '%if &fmt=Y %then %do;'; put '/**'; put '* Extract format definitions'; put '* First, by getting library locations from dictionary.formats'; put '* Then, by exporting the width using proc format'; put '* Cannot use maxw from sashelp.vformat as not always populated'; put '* Cannot use fmtinfo() as not supported in all flavours'; put '*/'; put '%let tmpds1=%substr(fmtsum%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds2=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds3=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds4=%substr(col%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql noprint;'; put 'create table &tmpds1 as'; put 'select cats(libname,''.'',memname) as FMTCAT,'; put 'FMTNAME'; put 'from dictionary.formats'; put 'where fmttype=''F'' and libname is not null'; put 'and fmtname in (select format from &colinfo where format is not null)'; put 'order by 1;'; put 'create table &tmpds2('; put 'FMTNAME char(32),'; put 'LENGTH num'; put ');'; put '%local catlist cat fmtlist i;'; put 'select distinct fmtcat into: catlist separated by '' '' from &tmpds1;'; put '%do i=1 %to %sysfunc(countw(&catlist,%str( )));'; put '%let cat=%scan(&catlist,&i,%str( ));'; put 'proc sql;'; put 'select distinct fmtname into: fmtlist separated by '' '''; put 'from &tmpds1 where fmtcat="&cat";'; put 'proc format lib=&cat cntlout=&tmpds3(keep=fmtname length);'; put 'select &fmtlist;'; put 'run;'; put 'proc sql;'; put 'insert into &tmpds2 select distinct fmtname,length from &tmpds3;'; put '%end;'; put 'proc sql;'; put 'create table &tmpds4 as'; put 'select a.*, b.length as MAXW'; put 'from &colinfo a'; put 'left join &tmpds2 b'; put 'on cats(a.format)=cats(upcase(b.fmtname))'; put 'order by a.varnum;'; put 'data _null_;'; put 'set &tmpds4;'; put 'if not missing(maxw);'; put 'call symputx('; put 'cats(''fmtlen'',_n_),'; put '/* vars need extra padding due to JSON escaping of special chars */'; put 'min(32767,ceil((max(length,maxw)+10)*1.5))'; put ',''l'''; put ');'; put 'run;'; put '/* configure varlenchk - as we are explicitly shortening the variables */'; put '%let optval=%sysfunc(getoption(varlenchk));'; put 'options varlenchk=NOWARN;'; put 'data _data_(compress=char);'; put '/* shorten the new vars */'; put 'length'; put '%do i=1 %to &numcols;'; put '&&name&i $&&fmtlen&i'; put '%end;'; put ';'; put '/* rename on entry */'; put 'set &ds(rename=('; put '%do i=1 %to &numcols;'; put '&&name&i=&&newname&i'; put '%end;'; put '));'; put '&stmt_obs;'; put 'drop'; put '%do i=1 %to &numcols;'; put '&&newname&i'; put '%end;'; put ';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=num %then %do;'; put '&&name&i=cats(put(&&newname&i,&&fmt&i));'; put '%end;'; put '%else %do;'; put '&&name&i=put(&&newname&i,&&fmt&i);'; put '%end;'; put '%end;'; put 'if _error_ then do;'; put 'call symputx(''syscc'',1012);'; put 'stop;'; put 'end;'; put 'run;'; put '%let fmtds=&syslast;'; put 'options varlenchk=&optval;'; put '%end;'; put 'proc format; /* credit yabwon for special null removal */'; put 'value bart (default=40)'; put '%if &missing=NULL %then %do;'; put '._ - .z = null'; put '%end;'; put '%else %do;'; put '._ = [quote()]'; put '. = null'; put '.a - .z = [quote()]'; put '%end;'; put 'other = [best.];'; put 'data &tempds;'; put 'attrib _all_ label='''';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'length &&name&i $&&fmtlen&i...;'; put 'format &&name&i $&&fmtlen&i...;'; put '%end;'; put '%end;'; put '%if &fmt=Y %then %do;'; put 'set &fmtds;'; put '%end;'; put '%else %do;'; put 'set &ds;'; put '%end;'; put '&stmt_obs;'; put 'format _numeric_ bart.;'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'if findc(&&name&i,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put '&&name&i=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,&&name&i)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else &&name&i=quote(cats(&&name&i));'; put '%end;'; put '%end;'; put 'run;'; put 'filename _sjs3 temp lrecl=131068 ;'; put 'data _null_;'; put 'file _sjs3 encoding=''utf-8'';'; put 'if _n_=1 then put "[";'; put 'set &tempds;'; put 'if _n_>1 then put "," @; put'; put '%if &action=ARR %then "[" ; %else "{" ;'; put '%do i=1 %to &numcols;'; put '%if &i>1 %then "," ;'; put '%if &action=OBJ %then """&&name&i"":" ;'; put '"&&name&i"n /* name literal for reserved variable names */'; put '%end;'; put '%if &action=ARR %then "]" ; %else "}" ; ;'; put '/* close out the table */'; put 'data _null_;'; put 'file _sjs3 mod encoding=''utf-8'';'; put 'put '']'';'; put 'run;'; put 'data _null_;'; put 'infile _sjs3 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs3 clear;'; put '%end;'; put 'proc sql;'; put 'drop table &colinfo, &tempds;'; put '%if %substr(&showmeta,1,1)=Y %then %do;'; put 'filename _sjs4 temp lrecl=131068 encoding=''utf-8'';'; put 'data _null_;'; put 'file _sjs4;'; put 'length label $350;'; put 'put ", ""$%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":{""vars"":{";'; put 'do i=1 to &numcols;'; put 'name=quote(trim(symget(cats(''name'',i))));'; put 'format=quote(trim(symget(cats(''fmt'',i))));'; put 'label=quote(prxchange(''s/\\/\\\\/'',-1,trim(symget(cats(''label'',i)))));'; put 'length=quote(trim(symget(cats(''length'',i))));'; put 'type=quote(trim(symget(cats(''typelong'',i))));'; put 'if i>1 then put "," @@;'; put 'put name '':{"format":'' format '',"label":'' label'; put ''',"length":'' length '',"type":'' type ''}'';'; put 'end;'; put 'put ''}}'';'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs4 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs4 clear;'; put '%end;'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put 'data _null_; file &jref encoding=''utf-8'' mod ;'; put 'put "}";'; put 'run;'; put '%end;'; put '%mend mp_jsonout;'; put '/**'; put '@file mm_webout.sas'; put '@brief Send data to/from SAS Stored Processes'; put '@details This macro should be added to the start of each Stored Process,'; put '**immediately** followed by a call to:'; put '%mm_webout(FETCH)'; put 'This will read all the input data and create same-named SAS datasets in the'; put 'WORK library. You can then insert your code, and send data back using the'; put 'following syntax:'; put 'data some datasets; * make some data ;'; put 'retain some columns;'; put 'run;'; put '%mm_webout(OPEN)'; put '%mm_webout(ARR,some) * Array format, fast, suitable for large tables ;'; put '%mm_webout(OBJ,datasets) * Object format, easier to work with ;'; put 'Finally, wrap everything up send some helpful system variables too'; put '%mm_webout(CLOSE)'; put '@param [in] action Either FETCH, OPEN, ARR, OBJ or CLOSE'; put '@param [in] ds The dataset to send back to the frontend'; put '@param [out] dslabel= Value to use instead of table name for sending to JSON'; put '@param [in] fmt= (N) Setting Y converts all vars to their formatted values'; put '@param [out] fref= (_webout) The fileref to which to write the JSON'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] workobs= (0) When set to a positive integer, will create a new'; put 'output object (WORK) which contains this number of observations from all'; put 'tables in the WORK library.'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to output JSON'; put '

SAS Macros

'; put '@li mp_jsonout.sas'; put '

Related Macros

'; put '@li ms_webout.sas'; put '@li mv_webout.sas'; put '@version 9.3'; put '@author Allan Bowe'; put '**/'; put '%macro mm_webout(action,ds,dslabel=,fref=_webout,fmt=N,missing=NULL'; put ',showmeta=N,maxobs=MAX,workobs=0'; put ');'; put '%global _webin_file_count _webin_fileref1 _webin_name1 _program _debug'; put 'sasjs_tables;'; put '%local i tempds jsonengine;'; put '/* see https://github.com/sasjs/core/issues/41 */'; put '%if "%upcase(&SYSENCODING)" ne "UTF-8" %then %let jsonengine=PROCJSON;'; put '%else %let jsonengine=DATASTEP;'; put '%if &action=FETCH %then %do;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'options mprint notes mprintnest;'; put '%end;'; put '%let _webin_file_count=%eval(&_webin_file_count+0);'; put '/* now read in the data */'; put '%do i=1 %to &_webin_file_count;'; put '%if &_webin_file_count=1 %then %do;'; put '%let _webin_fileref1=&_webin_fileref;'; put '%let _webin_name1=&_webin_name;'; put '%end;'; put 'data _null_;'; put 'infile &&_webin_fileref&i termstr=crlf;'; put 'input;'; put 'call symputx(''input_statement'',_infile_);'; put 'putlog "&&_webin_name&i input statement: " _infile_;'; put 'stop;'; put 'data &&_webin_name&i;'; put 'infile &&_webin_fileref&i firstobs=2 dsd termstr=crlf encoding=''utf-8'';'; put 'input &input_statement;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'if _n_<20 then putlog _infile_;'; put '%end;'; put 'run;'; put '%let sasjs_tables=&sasjs_tables &&_webin_name&i;'; put '%end;'; put '%end;'; put '%else %if &action=OPEN %then %do;'; put '/* fix encoding */'; put 'OPTIONS NOBOMFILE;'; put '/**'; put '* check xengine type to avoid the below err message:'; put '* > Function is only valid for filerefs using the CACHE access method.'; put '*/'; put 'data _null_;'; put 'set sashelp.vextfl(where=(fileref="_WEBOUT"));'; put 'if xengine=''STREAM'' then do;'; put 'rc=stpsrv_header(''Content-type'',"text/html; encoding=utf-8");'; put 'end;'; put 'run;'; put '/* setup json */'; put 'data _null_;file &fref encoding=''utf-8'';'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutBEGIN<<'';'; put '%end;'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'run;'; put '%end;'; put '%else %if &action=ARR or &action=OBJ %then %do;'; put '%mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt,jref=&fref'; put ',engine=&jsonengine,missing=&missing,showmeta=&showmeta,maxobs=&maxobs'; put ')'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put '/* To avoid issues with _webout on EBI we use a temporary file */'; put 'filename _sjsref temp lrecl=131068;'; put '%if %str(&workobs) > 0 %then %do;'; put '/* if debug mode, send back first XX records of each work table also */'; put 'data;run;%let tempds=%scan(&syslast,2,.);'; put 'ods output Members=&tempds;'; put 'proc datasets library=WORK memtype=data;'; put '%local wtcnt;%let wtcnt=0;'; put 'data _null_;'; put 'set &tempds;'; put 'if not (upcase(name) =:"DATA"); /* ignore temp datasets */'; put 'i+1;'; put 'call symputx(cats(''wt'',i),name,''l'');'; put 'call symputx(''wtcnt'',i,''l'');'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put ",""WORK"":{";'; put '%do i=1 %to &wtcnt;'; put '%let wt=&&wt&i;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'dsid=open("WORK.&wt",''is'');'; put 'nlobs=attrn(dsid,''NLOBS'');'; put 'nvars=attrn(dsid,''NVARS'');'; put 'rc=close(dsid);'; put 'if &i>1 then put '',''@;'; put 'put " ""&wt"" : {";'; put 'put ''"nlobs":'' nlobs;'; put 'put '',"nvars":'' nvars;'; put '%mp_jsonout(OBJ,&wt,jref=_sjsref,dslabel=first10rows,showmeta=Y'; put ',maxobs=&workobs'; put ')'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put '%end;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put 'run;'; put '%end;'; put '/* close off json */'; put 'data _null_;file _sjsref mod encoding=''utf-8'';'; put 'length SYSPROCESSNAME syserrortext syswarningtext autoexec $512;'; put 'put ",""_DEBUG"" : ""&_debug"" ";'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'autoexec=quote(urlencode(trim(getoption(''autoexec''))));'; put 'put '',"AUTOEXEC" : '' autoexec;'; put 'put ",""MF_GETUSER"" : ""%mf_getuser()"" ";'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'put ",""SYSENCODING"" : ""&sysencoding"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSPROCESSID"" : ""&SYSPROCESSID"" ";'; put 'put ",""SYSPROCESSMODE"" : ""&SYSPROCESSMODE"" ";'; put 'SYSPROCESSNAME=quote(urlencode(cats(SYSPROCESSNAME)));'; put 'put ",""SYSPROCESSNAME"" : " SYSPROCESSNAME;'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put '',"SYSWARNINGTEXT" : '' syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'length memsize $32;'; put 'memsize="%sysfunc(INPUTN(%sysfunc(getoption(memsize)), best.),sizekmg.)";'; put 'memsize=quote(cats(memsize));'; put 'put '',"MEMSIZE" : '' memsize;'; put 'put "}" @;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutEND<<'';'; put '%end;'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjsref lrecl=1 recfm=n;'; put 'file &fref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjsref clear;'; put '%end;'; put '%mend mm_webout;'; put '%macro webout(action,ds,dslabel=,fmt=,missing=NULL,showmeta=NO,maxobs=MAX);'; put '%mm_webout(&action,ds=&ds,dslabel=&dslabel,fmt=&fmt'; put ',missing=&missing'; put ',showmeta=&showmeta'; put ',maxobs=&maxobs'; put ') %mend;'; put '/* provide additional debug info */'; put '%global _program;'; put '%put &=syscc;'; put '%put user=%mf_getuser();'; put '%put pgm=&_program;'; put '%put timestamp=%sysfunc(datetime(),datetime19.);'; put '* Service Variables start;'; put '* Service Variables end;'; put '* SAS Macros start;'; put '%macro mf_getapploc(pgm);'; put '%if "&pgm"="" %then %do;'; put '%if %symexist(_program) %then %let pgm=&_program;'; put '%else %do;'; put '%put &sysmacroname: No value provided and no _program variable available;'; put '%return;'; put '%end;'; put '%end;'; put '%local root;'; put '/**'; put '* First check we are not in the tests/macros folder (which has no subfolders)'; put '* or specifically in the testsetup or testteardown services'; put '*/'; put '%if %index(&pgm,/tests/macros/)'; put 'or %index(&pgm,/tests/testsetup)'; put 'or %index(&pgm,/tests/testteardown)'; put '%then %do;'; put '%let root=%substr(&pgm,1,%index(&pgm,/tests)-1);'; put '&root'; put '%return;'; put '%end;'; put '/**'; put '* Next, move up two levels to avoid matches on subfolder or service name'; put '*/'; put '%let root=%substr(&pgm,1,%length(&pgm)-%length(%scan(&pgm,-1,/))-1);'; put '%let root=%substr(&root,1,%length(&root)-%length(%scan(&root,-1,/))-1);'; put '%if %index(&root,/tests/) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/tests/)-1);'; put '%end;'; put '%else %if %index(&root,/services) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/services)-1);'; put '%end;'; put '%else %if %index(&root,/jobs) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/jobs)-1);'; put '%end;'; put '%else %put &sysmacroname: Could not find an app location from &pgm;'; put '&root'; put '%mend mf_getapploc ;'; put '%macro mf_getuniquefileref(prefix=_,maxtries=1000,lrecl=32767);'; put '%local rc fname;'; put '%if &prefix=0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%end;'; put '%else %do;'; put '%local x len;'; put '%let len=%eval(8-%length(&prefix));'; put '%let x=0;'; put '%do x=0 %to &maxtries;'; put '%let fname=&prefix%substr(%sysfunc(ranuni(0)),3,&len);'; put '%if %sysfunc(fileref(&fname)) > 0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%return;'; put '%end;'; put '%end;'; put '%put unable to find available fileref after &maxtries attempts;'; put '%end;'; put '%mend mf_getuniquefileref;'; put '%macro mp_abort(mac=mp_abort.sas, type=, msg=, iftrue=%str(1=1)'; put ', errds=work.mp_abort_errds'; put ', mode=REGULAR'; put ')/*/STORE SOURCE*/;'; put '%global sysprocessmode sysprocessname sasjs_stpsrv_header_loc sasjsprocessmode;'; put '%local fref fid i;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%put NOTE: /// mp_abort macro executing //;'; put '%if %length(&mac)>0 %then %put NOTE- called by &mac;'; put '%put NOTE - &msg;'; put '%if %symexist(_SYSINCLUDEFILEDEVICE)'; put '/* abort cancel FILE does not restart outside the INCLUDE on Viya 3.5 */'; put 'and %superq(SYSPROCESSNAME) ne %str(Compute Server)'; put '%then %do;'; put '%if "*&_SYSINCLUDEFILEDEVICE*" ne "**" %then %do;'; put 'data &errds;'; put 'iftrue=''1=1'';'; put 'length mac $100 msg $5000;'; put 'mac=symget(''mac'');'; put 'msg=symget(''msg'');'; put 'run;'; put 'data _null_;'; put 'abort cancel FILE;'; put 'run;'; put '%return;'; put '%end;'; put '%end;'; put '/* Web App Context */'; put '%if %symexist(_PROGRAM)'; put 'or %superq(SYSPROCESSNAME) = %str(Compute Server)'; put 'or &mode=INCLUDE'; put '%then %do;'; put 'options obs=max replace mprint;'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5"'; put '%then %do;'; put 'options nosyntaxcheck;'; put '%end;'; put '%if &mode=INCLUDE %then %do;'; put '%if %sysfunc(exist(&errds))=1 %then %do;'; put 'data _null_;'; put 'set &errds;'; put 'call symputx(''iftrue'',iftrue,''l'');'; put 'call symputx(''mac'',mac,''l'');'; put 'call symputx(''msg'',msg,''l'');'; put 'putlog (_all_)(=);'; put 'run;'; put '%if (&iftrue)=0 %then %return;'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: No include errors found;'; put '%return;'; put '%end;'; put '%end;'; put '/* extract log errs / warns, if exist */'; put '%local logloc logline;'; put '%global logmsg; /* capture global messages */'; put '%if %symexist(SYSPRINTTOLOG) %then %let logloc=&SYSPRINTTOLOG;'; put '%else %let logloc=%qsysfunc(getoption(LOG));'; put 'proc printto log=log;run;'; put '%let logline=0;'; put '%if %length(&logloc)>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input; putlog _infile_;'; put 'i=1;'; put 'retain logonce 0;'; put 'if ('; put '_infile_=:"%str(WARN)ING" or _infile_=:"%str(ERR)OR"'; put ') and logonce=0 then'; put 'do;'; put 'call symputx(''logline'',_n_);'; put 'logonce+1;'; put 'end;'; put 'run;'; put '/* capture log including lines BEFORE the err */'; put '%if &logline>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input;'; put 'i=1;'; put 'stoploop=0;'; put 'if _n_ ge &logline-15 and stoploop=0 then do until (i>22);'; put 'call symputx(''logmsg'',catx(''\n'',symget(''logmsg''),_infile_));'; put 'input;'; put 'i+1;'; put 'stoploop=1;'; put 'end;'; put 'if stoploop=1 then stop;'; put 'run;'; put '%end;'; put '%end;'; put '%if %symexist(SYS_JES_JOB_URI) %then %do;'; put '/* setup webout for Viya */'; put 'options nobomfile;'; put '%if "X&SYS_JES_JOB_URI.X"="XX" %then %do;'; put 'filename _webout temp lrecl=999999 mod;'; put '%end;'; put '%else %do;'; put 'filename _webout filesrvc parenturi="&SYS_JES_JOB_URI"'; put 'name="_webout.json" lrecl=999999 mod;'; put '%end;'; put '%end;'; put '%else %if %sysfunc(filename(fref,&sasjs_stpsrv_header_loc))=0 %then %do;'; put 'options nobomfile;'; put '/* set up http header for SASjs Server */'; put '%let fid=%sysfunc(fopen(&fref,A));'; put '%if &fid=0 %then %do;'; put '%put %str(ERR)OR: %sysfunc(sysmsg());'; put '%return;'; put '%end;'; put '%let rc=%sysfunc(fput(&fid,%str(Content-Type: application/json)));'; put '%let rc=%sysfunc(fwrite(&fid));'; put '%let rc=%sysfunc(fclose(&fid));'; put '%let rc=%sysfunc(filename(&fref));'; put '%end;'; put '/* send response in SASjs JSON format */'; put 'data _null_;'; put 'file _webout mod lrecl=32000 encoding=''utf-8'';'; put 'length msg syswarningtext syserrortext $32767 mode $10 ;'; put 'sasdatetime=datetime();'; put 'msg=symget(''msg'');'; put '%if &logline>0 %then %do;'; put 'msg=cats(msg,''\n\nLog Extract:\n'',symget(''logmsg''));'; put '%end;'; put '/* escape the escapes */'; put 'msg=tranwrd(msg,''\'',''\\'');'; put '/* escape the quotes */'; put 'msg=tranwrd(msg,''"'',''\"'');'; put '/* ditch the CRLFs as chrome complains */'; put 'msg=compress(msg,,''kw'');'; put '/* quote without quoting the quotes (which are escaped instead) */'; put 'msg=cats(''"'',msg,''"'');'; put 'if symexist(''_debug'') then debug=quote(trim(symget(''_debug'')));'; put 'else debug=''""'';'; put 'if symget(''sasjsprocessmode'')=''Stored Program'' then mode=''SASJS'';'; put 'if mode ne ''SASJS'' then put ''>>weboutBEGIN<<'';'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'put '',"sasjsAbort" : [{'';'; put 'put '' "MSG":'' msg ;'; put 'put '' ,"MAC": "'' "&mac" ''"}]'';'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'put '',"_DEBUG":'' debug ;'; put 'if symexist(''_metauser'') then do;'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put 'end;'; put 'if symexist(''SYS_JES_JOB_URI'') then do;'; put 'SYS_JES_JOB_URI=quote(trim(symget(''SYS_JES_JOB_URI'')));'; put 'put '',"SYS_JES_JOB_URI": '' SYS_JES_JOB_URI;'; put 'end;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put ",""SYSWARNINGTEXT"" : " syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'put "}" ;'; put 'if mode ne ''SASJS'' then put ''>>weboutEND<<'';'; put 'run;'; put '%put _all_;'; put '%if "&sysprocessmode " = "SAS Stored Process Server " %then %do;'; put 'data _null_;'; put 'putlog ''stpsrvset program err and syscc'';'; put 'rc=stpsrvset(''program error'', 0);'; put 'call symputx("syscc",0,"g");'; put 'run;'; put '%if &sysscp=WIN'; put 'and 1=0 /* deprecating this logic until we figure out a consistent abort */'; put 'and "%substr(%str(&sysvlong ),1,8)"="9.04.01M"'; put 'and "%substr(%str(&sysvlong ),9,1)">"5" %then %do;'; put '/* skip approach (below) does not work in windows m6+ envs */'; put 'endsas;'; put '%end;'; put '%else %do;'; put '/**'; put '* endsas kills 9.4m3 deployments by orphaning multibridges.'; put '* Abort variants are ungraceful (non zero return code)'; put '* This approach lets SAS run silently until the end :-)'; put '* Caution - fails when called within a %include within a macro'; put '* Use mp_include() to handle this.'; put '*/'; put 'filename skip temp;'; put 'data _null_;'; put 'file skip;'; put 'put ''%macro skip();'';'; put 'comment ''%mend skip; -> fix lint '';'; put 'put ''%macro skippy();'';'; put 'comment ''%mend skippy; -> fix lint '';'; put 'run;'; put '%inc skip;'; put '%end;'; put '%end;'; put '%else %if "&sysprocessmode " = "SAS Compute Server " %then %do;'; put '/* endsas kills the session making it harder to fetch results */'; put 'data _null_;'; put 'syswarningtext=symget(''syswarningtext'');'; put 'syserrortext=symget(''syserrortext'');'; put 'abort_msg=symget(''msg'');'; put 'syscc=symget(''syscc'');'; put 'sysuserid=symget(''sysuserid'');'; put 'iftrue=symget(''iftrue'');'; put 'put (_all_)(/=);'; put 'call symputx(''syscc'',0);'; put 'abort cancel nolist;'; put 'run;'; put '%end;'; put '%else %do;'; put '%abort cancel;'; put '%end;'; put '%end;'; put '%else %do;'; put '%put _all_;'; put '%abort cancel;'; put '%end;'; put '%mend mp_abort;'; put '/** @endcond */'; put '%macro mm_getstpcode('; put 'tree=/User Folders/sasdemo/somestp'; put ',name='; put ',outloc=0'; put ',outref=0'; put ',mDebug=1'; put ',showlog=NO'; put ');'; put '%local mD;'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing &sysmacroname..sas;'; put '%&mD.put _local_;'; put '%if %length(&name)>0 %then %let name=/&name;'; put '/* first, check if STP exists */'; put '%local tsuri;'; put '%let tsuri=stopifempty ;'; put 'data _null_;'; put 'format type uri tsuri value $200.;'; put 'call missing (of _all_);'; put 'path="&tree&name(StoredProcess)";'; put '/* first, find the STP ID */'; put 'if metadata_pathobj("",path,"StoredProcess",type,uri)>0 then do;'; put '/* get sourcecode */'; put 'cnt=1;'; put 'do while (metadata_getnasn(uri,"Notes",cnt,tsuri)>0);'; put 'rc=metadata_getattr(tsuri,"Name",value);'; put '&mD.put tsuri= value=;'; put 'if value="SourceCode" then do;'; put '/* found it! */'; put 'rc=metadata_getattr(tsuri,"Id",value);'; put 'call symputx(''tsuri'',value,''l'');'; put 'stop;'; put 'end;'; put 'cnt+1;'; put 'end;'; put 'end;'; put 'else put (_all_)(=);'; put 'run;'; put '%mp_abort(iftrue= (&tsuri=stopifempty)'; put ',mac=mm_getstpcode'; put ',msg=%str(&tree&name.(StoredProcess) not found!)'; put ')'; put '/**'; put '* Now we can extract the textstore'; put '*/'; put 'filename __getdoc temp lrecl=10000000;'; put 'proc metadata'; put 'in="$METAREPOSITORY'; put ''; put 'SAS1"'; put 'out=__getdoc ;'; put 'run;'; put '/* find the beginning of the text */'; put '%local start;'; put 'data _null_;'; put 'infile __getdoc lrecl=10000;'; put 'input;'; put 'start=index(_infile_,''StoredText="'');'; put 'if start then do;'; put 'call symputx("start",start+11);'; put '*putlog ''"'' _infile_ ''"'';'; put 'end;'; put 'stop;'; put '%local outeng;'; put '%if "&outloc"="0" %then %let outeng=TEMP;'; put '%else %let outeng="&outloc";'; put '%local fref;'; put '%if &outref=0 %then %let fref=%mf_getuniquefileref();'; put '%else %let fref=&outref;'; put '/* read the content, byte by byte, resolving escaped chars */'; put 'filename &fref &outeng lrecl=100000;'; put 'data _null_;'; put 'length filein 8 fileid 8;'; put 'filein = fopen("__getdoc","I",1,"B");'; put 'fileid = fopen("&fref","O",1,"B");'; put 'rec = "20"x;'; put 'length entity $6;'; put 'do while(fread(filein)=0);'; put 'x+1;'; put 'if x>&start then do;'; put 'rc = fget(filein,rec,1);'; put 'if rec=''"'' then leave;'; put 'else if rec="&" then do;'; put 'entity=rec;'; put 'do until (rec=";");'; put 'if fread(filein) ne 0 then goto getout;'; put 'rc = fget(filein,rec,1);'; put 'entity=cats(entity,rec);'; put 'end;'; put 'select (entity);'; put 'when (''&'' ) rec=''&'' ;'; put 'when (''<'' ) rec=''<'' ;'; put 'when (''>'' ) rec=''>'' ;'; put 'when (''''') rec="''" ;'; put 'when (''"'') rec=''"'' ;'; put 'when ('' '') rec=''0A''x;'; put 'when ('' '') rec=''0D''x;'; put 'when (''$'' ) rec=''$'' ;'; put 'when ('' '') rec=''09''x;'; put 'otherwise putlog "%str(WARN)ING: missing value for " entity=;'; put 'end;'; put 'rc =fput(fileid, substr(rec,1,1));'; put 'rc =fwrite(fileid);'; put 'end;'; put 'else do;'; put 'rc =fput(fileid,rec);'; put 'rc =fwrite(fileid);'; put 'end;'; put 'end;'; put 'end;'; put 'getout:'; put 'rc=fclose(filein);'; put 'rc=fclose(fileid);'; put 'run;'; put '%if &showlog=YES %then %do;'; put 'data _null_;'; put 'infile &fref lrecl=32767 end=last;'; put 'input;'; put 'if _n_=1 then putlog ''>>stpcodeBEGIN<<'';'; put 'putlog _infile_;'; put 'if last then putlog ''>>stpcodeEND<<'';'; put 'run;'; put '%end;'; put 'filename __getdoc clear;'; put '%if &outref=0 %then %do;'; put 'filename &fref clear;'; put '%end;'; put '%mend mm_getstpcode;'; put '%macro dc_getsettings();'; put '%global _program;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(syscc=&syscc on entry (&syswarningtext &syserrortext))'; put ')'; put '%if %length(&_PROGRAM)>1 %then %let root=&_program;'; put '%else %do;'; put '%global _metauser;'; put '%let _metauser=&sysuserid;'; put '/* to mimic a "real" _program we need to give a dummy role and stp name */'; put '%let root=/dummyRole/dummyName;'; put '%end;'; put '/* the DC precode is stored in the Admin folder in the root of'; put 'the project. Lets find that root. */'; put '%put &=root;'; put '%let root=%mf_getapploc();'; put '%put &=root;'; put '/* Now we know the root location we can retrieve the params */'; put '%let temploc=%sysfunc(pathname(work))/settings.txt;'; put '%mm_getstpcode(tree=&root/services/public'; put ',name=Data_Controller_Settings'; put ',outloc=&temploc'; put ')'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Unable to run getstpcode)'; put ')'; put 'filename _getsets "&temploc" lrecl=2000;'; put '/*'; put 'Do not use mp_include here - this puts a copy in every service, which creates'; put 'compilation problems when calling services from mp_include'; put '*/'; put '%inc _getsets/source2;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Problem running &sysmacroname (&syswarningtext &syserrortext))'; put ')'; put '%mend dc_getsettings;'; put '%macro mf_fmtdttm('; put ')/*/STORE SOURCE*/;'; put '%if "&sysver"="9.2" or "&sysver"="9.3"'; put 'or ("&sysver"="9.4" and "%substr(&SYSVLONG,9,1)" le "3")'; put 'or "%substr(&sysver,1,1)"="4"'; put 'or "%substr(&sysver,1,1)"="5"'; put '%then %do;DATETIME19.3%end;'; put '%else %do;E8601DT26.6%end;'; put '%mend mf_fmtdttm;'; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '%macro mp_init(prefix=SASJS'; put ')/*/STORE SOURCE*/;'; put '%if %symexist(SASJS_PREFIX) %then %return; /* only run once */'; put '%global'; put 'SASJS_PREFIX /* the ONLY hard-coded global macro variable in SASjs */'; put '&prefix._FUNCTIONS /* used in mcf_init() to track core function compilation */'; put '&prefix._INIT_NUM /* initialisation time as numeric */'; put '&prefix._INIT_DTTM /* initialisation time in E8601DT26.6 format */'; put '&prefix.WORK /* avoid typing %sysfunc(pathname(work)) every time */'; put ';'; put '%let sasjs_prefix=&prefix;'; put 'data _null_;'; put 'dttm=datetime();'; put 'call symputx("&sasjs_prefix._init_num",dttm,''g'');'; put 'call symputx("&sasjs_prefix._init_dttm",put(dttm,E8601DT26.6),''g'');'; put 'call symputx("&sasjs_prefix.work",pathname(''WORK''),''g'');'; put 'run;'; put 'options'; put 'compress=CHAR /* default is none so ensure we have something! */'; put 'datastmtchk=ALLKEYWORDS /* protection from overwriting input datasets */'; put 'errorcheck=STRICT /* catch errs in libname/filename statements */'; put 'fmterr /* ensure err when a format cannot be found */'; put 'mergenoby=%str(ERR)OR /* throw err when a merge has no BY variables */'; put 'missing=. /* changing this can cause hard to detect errs */'; put 'noquotelenmax /* avoid warnings for long strings */'; put 'noreplace /* avoid overwriting permanent datasets */'; put 'ps=max /* reduce log size slightly */'; put 'ls=max /* reduce log even more and avoid word truncation */'; put 'validmemname=COMPATIBLE /* avoid special characters etc in table names */'; put 'validvarname=V7 /* avoid special characters etc in variable names */'; put 'varinitchk=%str(ERR)OR /* avoid data mistakes from variable name typos */'; put 'varlenchk=%str(ERR)OR /* fail hard if truncation (data loss) can result */'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5" %then %do;'; put 'noautocorrect /* disallow misspelled procedure names */'; put 'dsoptions=note2err /* undocumented - convert bad NOTEs to ERRs */'; put '%end;'; put ';'; put '%mend mp_init;'; put '%macro mpeinit(fetch=YES);'; put '%global mpeinit'; put 'mpeadmins /* group with unrestricted Meditor access */'; put 'mpelocapprovals /* location for landing and staging files */'; put 'mpelib /* location of configuration tables for DC */'; put 'dc_repo_users /* location of user / group metadata */'; put 'dc_licence_key /* extracted in dc_getsettings */'; put 'dc_activation_key /* extracted in dc_getsettings */'; put 'dc_locale /* extracted in dc_getsettings */'; put 'dc_dttmtfmt /* can be overridden in dc_getsettings */'; put '_debug'; put ';'; put '%if &mpeinit=1 %then %return;'; put '%else %let mpeinit=1;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(Problem on service startup (&syswarningtext &syserrortext))'; put ')'; put '%mp_init()'; put '%if &fetch=YES %then %do;'; put '%webout(FETCH)'; put '%end;'; put '%global _CLIENTNAME;'; put '%mp_abort(iftrue= (&_CLIENTNAME=SAS Enterprise Guide)'; put ',mac=&_program..sas'; put ',msg=%str(Data Controller is a web app and should not be executed from EG)'; put ')'; put 'options urlencoding=utf8 nobomfile lrecl=32767;'; put '%let perf=%sysfunc(datetime());'; put '%put perfdiff: 0;'; put '%let dc_locale=SYSTEM; /* default if not set */'; put '/**'; put '* E8601DT26.6 has widest database support - but not all SAS flavours can'; put '* handle it. Override in the settings STP if needed.'; put '*/'; put 'data _null_;'; put 'dc_dttmtfmt=''"%sysfunc(datetime(),''!!"%mf_fmtdttm()"!!'')"dt'';'; put 'call symputx(''dc_dttmtfmt'',dc_dttmtfmt);'; put 'put dc_dttmtfmt=;'; put 'run;'; put '%put &=dc_dttmtfmt;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc prior to dc_getsettings)'; put ')'; put '%dc_getsettings()'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc after dc_getsettings)'; put ')'; put 'data _null_;'; put 'set &DC_LIBREF..mpe_config(where=('; put 'var_scope="DC"'; put 'and &dc_dttmtfmt lt tx_to'; put 'and var_active=1'; put '));'; put 'call symputx(var_name,var_value,''G'');'; put 'putlog var_name "=" var_value;'; put 'run;'; put '%let mpelib=&dc_libref;'; put '%let mpeadmins=&dc_admin_group;'; put '%let mpelocapprovals=&dc_staging_area;'; put '%let dc_repo_users=&dc_repo_users;'; put '%if &dc_locale ne SYSTEM %then %do;'; put 'options locale=&dc_locale;'; put '%end;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(Problem during compilation or with STP precode (&syswarningtext))'; put ')'; put '%mend mpeinit;'; put '%macro mf_mval(var);'; put '%if %symexist(&var) %then %do;'; put '%superq(&var)'; put '%end;'; put '%mend mf_mval;'; put '%macro mf_trimstr(basestr,trimstr);'; put '%local baselen trimlen trimval;'; put '/* return if basestr is shorter than trimstr (or 0) */'; put '%let baselen=%length(%superq(basestr));'; put '%let trimlen=%length(%superq(trimstr));'; put '%if &baselen < &trimlen or &baselen=0 %then %return;'; put '/* obtain the characters from the end of basestr */'; put '%let trimval=%qsubstr(%superq(basestr)'; put ',%length(%superq(basestr))-&trimlen+1'; put ',&trimlen);'; put '/* compare and if matching, chop it off! */'; put '%if %superq(basestr)=%superq(trimstr) %then %do;'; put '%return;'; put '%end;'; put '%else %if %superq(trimval)=%superq(trimstr) %then %do;'; put '%qsubstr(%superq(basestr),1,%length(%superq(basestr))-&trimlen)'; put '%end;'; put '%else %do;'; put '&basestr'; put '%end;'; put '%mend mf_trimstr;'; put '%macro mf_getplatform(switch'; put ')/*/STORE SOURCE*/;'; put '%local a b c;'; put '%if &switch.NONE=NONE %then %do;'; put '%if %symexist(sasjsprocessmode) %then %do;'; put '%if &sasjsprocessmode=Stored Program %then %do;'; put 'SASJS'; put '%return;'; put '%end;'; put '%end;'; put '%if %symexist(sysprocessmode) %then %do;'; put '%if "&sysprocessmode"="SAS Object Server"'; put 'or "&sysprocessmode"= "SAS Compute Server" %then %do;'; put 'SASVIYA'; put '%end;'; put '%else %if "&sysprocessmode"="SAS Stored Process Server"'; put 'or "&sysprocessmode"="SAS Workspace Server"'; put '%then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if %symexist(_metaport) or %symexist(_metauser) %then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if &switch=SASSTUDIO %then %do;'; put '/* return the version of SAS Studio else 0 */'; put '%if %mf_mval(_CLIENTAPP)=%str(SAS Studio) %then %do;'; put '%let a=%mf_mval(_CLIENTVERSION);'; put '%let b=%scan(&a,1,.);'; put '%if %eval(&b >2) %then %do;'; put '&b'; put '%end;'; put '%else 0;'; put '%end;'; put '%else 0;'; put '%end;'; put '%else %if &switch=VIYARESTAPI %then %do;'; put '%mf_trimstr(%sysfunc(getoption(servicesbaseurl)),/)'; put '%end;'; put '%mend mf_getplatform;'; put '%macro mpeterm();'; put '%local oldloc;'; put 'data _null_;'; put 'if symexist(''SYSPRINTTOLOG'') then oldloc=symget(''SYSPRINTTOLOG'');'; put 'else oldloc=getoption(''LOG'');'; put 'if subpad(oldloc,1,1) not in (''"'',"''",'' '') then oldloc=quote(cats(oldloc));'; put 'call symputx(''oldloc'',oldloc,''l'');'; put 'run;'; put '%if %length(&oldloc)>0 %then %do;'; put 'proc printto log=log;'; put 'run;'; put 'data _null_;'; put 'infile &oldloc;'; put 'input; putlog _infile_;'; put 'run;'; put '%end;'; put '%if %sysfunc(exist(&dc_libref..mpe_requests)) and %mf_getplatform() ne SASVIYA'; put '%then %do;'; put 'data ;'; put 'if 0 then set &dc_libref..mpe_requests;'; put 'request_dttm=%sysfunc(datetime());'; put 'request_user="%mf_getuser()";'; put 'request_service="%scan(&_program,-2,/)/%scan(&_program,-1,/)";'; put 'request_params='''';'; put 'output;stop;'; put 'proc append base=&dc_libref..mpe_requests data=&syslast force nowarn;'; put 'run;'; put '%end;'; put '%mend mpeterm;'; put '%macro mm_getGroups('; put 'user='; put ',outds=work.mm_getGroups'; put ',repo=foundation'; put ',mDebug=0'; put ')/*/STORE SOURCE*/;'; put '%local mD oldrepo;'; put '%let oldrepo=%sysfunc(getoption(metarepository));'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing mm_getGroups.sas;'; put '%&mD.put _local_;'; put '/* on some sites, user / group info is in a different metadata repo to the'; put 'default */'; put '%if &oldrepo ne &repo %then %do;'; put 'options metarepository=&repo;'; put '%end;'; put '%if %length(&user)=0 %then %do;'; put 'data &outds (keep=groupuri groupname groupdesc);'; put 'length groupuri groupname groupdesc group_or_role $256;'; put 'call missing(of _all_);'; put 'i+1;'; put 'do while'; put '(metadata_getnobj("omsobj:IdentityGroup?@Id contains ''.''",i,groupuri)>0);'; put 'rc=metadata_getattr(groupuri, "Name", groupname);'; put 'rc=metadata_getattr(groupuri, "Desc", groupdesc);'; put 'rc=metadata_getattr(groupuri,"PublicType",group_or_role);'; put 'if Group_or_Role = ''UserGroup'' then output;'; put 'i+1;'; put 'end;'; put 'run;'; put '%end;'; put '%else %do;'; put 'data &outds (keep=groupuri groupname groupdesc);'; put 'length uri groupuri groupname groupdesc group_or_role $256;'; put 'call missing(of _all_);'; put 'rc=metadata_getnobj("omsobj:Person?@Name=''&user''",1,uri);'; put 'if rc<=0 then do;'; put 'putlog "%str(WARN)ING: rc=" rc "&user not found "'; put '", or there was an issue reading the repository.";'; put 'stop;'; put 'end;'; put 'a=1;'; put 'grpassn=metadata_getnasn(uri,"IdentityGroups",a,groupuri);'; put 'if grpassn in (-3,-4) then do;'; put 'putlog "%str(WARN)ING: No metadata groups found for &user";'; put 'output;'; put 'end;'; put 'else do while (grpassn > 0);'; put 'rc=metadata_getattr(groupuri, "Name", groupname);'; put 'rc=metadata_getattr(groupuri, "Desc", groupdesc);'; put 'a+1;'; put 'rc=metadata_getattr(groupuri,"PublicType",group_or_role);'; put 'if Group_or_Role = ''UserGroup'' then output;'; put 'grpassn=metadata_getnasn(uri,"IdentityGroups",a,groupuri);'; put 'end;'; put 'run;'; put '%end;'; put '%if &oldrepo ne &repo %then %do;'; put 'options metarepository=&oldrepo;'; put '%end;'; put '%mend mm_getGroups;'; put '%macro dc_getusergroups(user=,outds=mm_getgroups);'; put '%global dc_repo_users;'; put '%if &dc_repo_users= %then %let dc_repo_users=foundation;'; put '%mm_getgroups(user=&user,outds=&outds,repo=&dc_repo_users)'; put '%mend dc_getusergroups;'; put '%macro mpe_getgroups(user=,outds=);'; put '%if not %symexist(dc_repo_users) %then %let dc_repo_users=foundation;'; put '%dc_getusergroups(user=&user,outds=&outds)'; put 'data;'; put 'length groupname groupdesc $256;'; put 'set &dc_libref..mpe_groups;'; put 'where &dc_dttmtfmt. lt tx_to;'; put 'where also upcase(user_name)="%upcase(&user)";'; put 'groupname=group_name;'; put 'groupdesc=group_desc;'; put 'keep groupname groupdesc;'; put 'run;'; put 'data &outds;'; put 'set &syslast &outds(keep=groupname groupdesc);'; put 'run;'; put '%mend mpe_getgroups;'; put '%macro mm_getlibs('; put 'outds=work.mm_getLibs'; put ')/*/STORE SOURCE*/;'; put '/*'; put 'flags:'; put 'OMI_SUCCINCT (2048) Do not return attributes with null values.'; put 'OMI_GET_METADATA (256) Executes a GetMetadata call for each object that'; put 'is returned by the GetMetadataObjects method.'; put 'OMI_ALL_SIMPLE (8) Gets all of the attributes of the requested object.'; put '*/'; put 'data _null_;'; put 'flags=2048+256+8;'; put 'call symputx(''flags'',flags,''l'');'; put 'run;'; put '* use a temporary fileref to hold the response;'; put 'filename response temp;'; put '/* get list of libraries */'; put 'proc metadata in='; put ''''; put '$METAREPOSITORY'; put 'SASLibrary'; put ''; put 'SAS'; put '&flags'; put ''; put ''''; put 'out=response;'; put 'run;'; put '/* write the response to the log for debugging */'; put 'data _null_;'; put 'infile response lrecl=32767;'; put 'input;'; put 'put _infile_;'; put 'run;'; put '/* create an XML map to read the response */'; put 'filename sxlemap temp;'; put 'data _null_;'; put 'file sxlemap;'; put 'put '''';'; put 'put '''';'; put 'put ''//Objects/SASLibrary'';'; put 'put ''>17'';'; put 'put ''//Objects/SASLibrary/@Id'';'; put 'put ''256>'';'; put 'put ''//Objects/SASLibrary/@Name'';'; put 'put ''8'';'; put 'put ''//Objects/SASLibrary/@Libref'';'; put 'put ''>12'';'; put 'put ''//Objects/SASLibrary/@Engine'';'; put 'put ''
'';'; put 'run;'; put 'libname _XML_ xml xmlfileref=response xmlmap=sxlemap;'; put '/* sort the response by library name */'; put 'proc sort data=_XML_.saslibrary out=&outds;'; put 'by libraryname;'; put 'run;'; put '/* clear references */'; put 'filename sxlemap clear;'; put 'filename response clear;'; put 'libname _XML_ clear;'; put '%mend mm_getlibs;'; put '%macro mm_getrepos('; put 'outds=work.mm_getrepos'; put ')/*/STORE SOURCE*/;'; put '* use a temporary fileref to hold the response;'; put 'filename response temp;'; put '/* get list of libraries */'; put 'proc metadata in='; put '"1"'; put 'out=response;'; put 'run;'; put '/* write the response to the log for debugging */'; put '/*'; put 'data _null_;'; put 'infile response lrecl=1048576;'; put 'input;'; put 'put _infile_;'; put 'run;'; put '*/'; put '/* create an XML map to read the response */'; put 'filename sxlemap temp;'; put 'data _null_;'; put 'file sxlemap;'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository";'; put 'put "";'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Id";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Name";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Desc";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@DefaultNS";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@RepositoryType";'; put 'put "characterstring20";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@RepositoryFormat";'; put 'put "characterstring10";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@Access";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@CurrentAccess";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@PauseState";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Path";'; put 'put "";'; put 'put "characterstring256";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Engine";'; put 'put "";'; put 'put "characterstring8";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Options";'; put 'put "";'; put 'put "characterstring32";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@MetadataCreated";'; put 'put "characterstring24";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@MetadataUpdated";'; put 'put "characterstring24";'; put 'put '''';'; put 'put ''
'';'; put 'run;'; put 'libname _XML_ xml xmlfileref=response xmlmap=sxlemap;'; put 'proc sort data= _XML_.SASRepos out=&outds;'; put 'by name;'; put 'run;'; put '/* clear references */'; put 'filename sxlemap clear;'; put 'filename response clear;'; put 'libname _XML_ clear;'; put '%mend mm_getrepos;'; put '%macro dc_getlibs(outds=mm_getlibs);'; put '/* take current repository */'; put '%local repo repocnt x;'; put '%let repo=%sysfunc(getoption(metarepository));'; put '/* get list of repositories and filter */'; put '%mm_getrepos(outds=repos)'; put '%let repocnt=0;'; put 'data repos;'; put 'set repos;'; put 'where repositorytype in(''CUSTOM'',''FOUNDATION'')'; put '& upcase(name) ne "%upcase(&repo)";'; put 'keep id name ;'; put 'call symputx(''repo''!!cats(_n_),name,''l'');'; put 'call symputx(''repocnt'',_n_,''l'');'; put 'run;'; put '%put _local_;'; put '%mm_getlibs(outds=&outds)'; put '%do x=1 %to &repocnt;'; put 'options metarepository=&&repo&x;'; put '%mm_getlibs(outds=&outds.a)'; put 'proc append base=&outds data=&outds.a;'; put 'run;'; put '%end;'; put 'options metarepository=&repo;'; put '%mend dc_getlibs;'; put '%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y'; put ',engine=DATASTEP'; put ',missing=NULL'; put ',showmeta=N'; put ',maxobs=MAX'; put ')/*/STORE SOURCE*/;'; put '%local tempds colinfo fmtds i numcols numobs stmt_obs lastobs optval'; put 'tmpds1 tmpds2 tmpds3 tmpds4;'; put '%let numcols=0;'; put '%if &maxobs ne MAX %then %let stmt_obs=%str(if _n_>&maxobs then stop;);'; put '%if &action=OPEN %then %do;'; put 'options nobomfile;'; put 'data _null_;file &jref encoding=''utf-8'' lrecl=200;'; put 'put ''{"PROCESSED_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''"'';'; put 'run;'; put '%end;'; put '%else %if (&action=ARR or &action=OBJ) %then %do;'; put '/* force variable names to always be uppercase in the JSON */'; put 'options validvarname=upcase;'; put '/* To avoid issues with _webout on EBI - such as encoding diffs and truncation'; put '(https://support.sas.com/kb/49/325.html) we use temporary files */'; put 'filename _sjs1 temp lrecl=200 ;'; put 'data _null_; file _sjs1 encoding=''utf-8'';'; put 'put ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":";'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjs1 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs1 clear;'; put '/* grab col defs */'; put 'proc contents noprint data=&ds'; put 'out=_data_(keep=name type length format formatl formatd varnum label);'; put 'run;'; put '%let colinfo=%scan(&syslast,2,.);'; put 'proc sort data=&colinfo;'; put 'by varnum;'; put 'run;'; put '/* move meta to mac vars */'; put 'data &colinfo;'; put 'if _n_=1 then call symputx(''numcols'',nobs,''l'');'; put 'set &colinfo end=last nobs=nobs;'; put 'name=upcase(name);'; put '/* fix formats */'; put 'if type=2 or type=6 then do;'; put 'typelong=''char'';'; put 'length fmt $49.;'; put 'if format='''' then fmt=cats(''$'',length,''.'');'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else fmt=cats(format,formatl,''.'');'; put 'end;'; put 'else do;'; put 'typelong=''num'';'; put 'if format='''' then fmt=''best.'';'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else if formatd=0 then fmt=cats(format,formatl,''.'');'; put 'else fmt=cats(format,formatl,''.'',formatd);'; put 'end;'; put '/* 32 char unique name */'; put 'newname=''sasjs''!!substr(cats(put(md5(name),$hex32.)),1,27);'; put 'call symputx(cats(''name'',_n_),name,''l'');'; put 'call symputx(cats(''newname'',_n_),newname,''l'');'; put 'call symputx(cats(''length'',_n_),length,''l'');'; put 'call symputx(cats(''fmt'',_n_),fmt,''l'');'; put 'call symputx(cats(''type'',_n_),type,''l'');'; put 'call symputx(cats(''typelong'',_n_),typelong,''l'');'; put 'call symputx(cats(''label'',_n_),coalescec(label,name),''l'');'; put '/* overwritten when fmt=Y and a custom format exists in catalog */'; put 'if typelong=''num'' then call symputx(cats(''fmtlen'',_n_),200,''l'');'; put 'else call symputx(cats(''fmtlen'',_n_),min(32767,ceil((length+10)*1.5)),''l'');'; put 'run;'; put '%let tempds=%substr(_%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql;'; put 'select count(*) into: lastobs from &ds;'; put '%if &maxobs ne MAX %then %let lastobs=%sysfunc(min(&lastobs,&maxobs));'; put '%if &engine=PROCJSON %then %do;'; put '%if &missing=STRING %then %do;'; put '%put &sysmacroname: Special Missings not supported in proc json.;'; put '%put &sysmacroname: Switching to DATASTEP engine;'; put '%goto datastep;'; put '%end;'; put 'data &tempds;'; put 'set &ds;'; put '&stmt_obs;'; put '%if &fmt=N %then format _numeric_ best32.;;'; put '/* PRETTY is necessary to avoid line truncation in large files */'; put 'filename _sjs2 temp lrecl=131068 encoding=''utf-8'';'; put 'proc json out=_sjs2 pretty'; put '%if &action=ARR %then nokeys ;'; put ';export &tempds / nosastags fmtnumeric;'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs2 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs2 clear;'; put '%end;'; put '%else %if &engine=DATASTEP %then %do;'; put '%datastep:'; put '%if %sysfunc(exist(&ds)) ne 1 & %sysfunc(exist(&ds,VIEW)) ne 1'; put '%then %do;'; put '%put &sysmacroname: &ds NOT FOUND!!!;'; put '%return;'; put '%end;'; put '%if &fmt=Y %then %do;'; put '/**'; put '* Extract format definitions'; put '* First, by getting library locations from dictionary.formats'; put '* Then, by exporting the width using proc format'; put '* Cannot use maxw from sashelp.vformat as not always populated'; put '* Cannot use fmtinfo() as not supported in all flavours'; put '*/'; put '%let tmpds1=%substr(fmtsum%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds2=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds3=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds4=%substr(col%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql noprint;'; put 'create table &tmpds1 as'; put 'select cats(libname,''.'',memname) as FMTCAT,'; put 'FMTNAME'; put 'from dictionary.formats'; put 'where fmttype=''F'' and libname is not null'; put 'and fmtname in (select format from &colinfo where format is not null)'; put 'order by 1;'; put 'create table &tmpds2('; put 'FMTNAME char(32),'; put 'LENGTH num'; put ');'; put '%local catlist cat fmtlist i;'; put 'select distinct fmtcat into: catlist separated by '' '' from &tmpds1;'; put '%do i=1 %to %sysfunc(countw(&catlist,%str( )));'; put '%let cat=%scan(&catlist,&i,%str( ));'; put 'proc sql;'; put 'select distinct fmtname into: fmtlist separated by '' '''; put 'from &tmpds1 where fmtcat="&cat";'; put 'proc format lib=&cat cntlout=&tmpds3(keep=fmtname length);'; put 'select &fmtlist;'; put 'run;'; put 'proc sql;'; put 'insert into &tmpds2 select distinct fmtname,length from &tmpds3;'; put '%end;'; put 'proc sql;'; put 'create table &tmpds4 as'; put 'select a.*, b.length as MAXW'; put 'from &colinfo a'; put 'left join &tmpds2 b'; put 'on cats(a.format)=cats(upcase(b.fmtname))'; put 'order by a.varnum;'; put 'data _null_;'; put 'set &tmpds4;'; put 'if not missing(maxw);'; put 'call symputx('; put 'cats(''fmtlen'',_n_),'; put '/* vars need extra padding due to JSON escaping of special chars */'; put 'min(32767,ceil((max(length,maxw)+10)*1.5))'; put ',''l'''; put ');'; put 'run;'; put '/* configure varlenchk - as we are explicitly shortening the variables */'; put '%let optval=%sysfunc(getoption(varlenchk));'; put 'options varlenchk=NOWARN;'; put 'data _data_(compress=char);'; put '/* shorten the new vars */'; put 'length'; put '%do i=1 %to &numcols;'; put '&&name&i $&&fmtlen&i'; put '%end;'; put ';'; put '/* rename on entry */'; put 'set &ds(rename=('; put '%do i=1 %to &numcols;'; put '&&name&i=&&newname&i'; put '%end;'; put '));'; put '&stmt_obs;'; put 'drop'; put '%do i=1 %to &numcols;'; put '&&newname&i'; put '%end;'; put ';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=num %then %do;'; put '&&name&i=cats(put(&&newname&i,&&fmt&i));'; put '%end;'; put '%else %do;'; put '&&name&i=put(&&newname&i,&&fmt&i);'; put '%end;'; put '%end;'; put 'if _error_ then do;'; put 'call symputx(''syscc'',1012);'; put 'stop;'; put 'end;'; put 'run;'; put '%let fmtds=&syslast;'; put 'options varlenchk=&optval;'; put '%end;'; put 'proc format; /* credit yabwon for special null removal */'; put 'value bart (default=40)'; put '%if &missing=NULL %then %do;'; put '._ - .z = null'; put '%end;'; put '%else %do;'; put '._ = [quote()]'; put '. = null'; put '.a - .z = [quote()]'; put '%end;'; put 'other = [best.];'; put 'data &tempds;'; put 'attrib _all_ label='''';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'length &&name&i $&&fmtlen&i...;'; put 'format &&name&i $&&fmtlen&i...;'; put '%end;'; put '%end;'; put '%if &fmt=Y %then %do;'; put 'set &fmtds;'; put '%end;'; put '%else %do;'; put 'set &ds;'; put '%end;'; put '&stmt_obs;'; put 'format _numeric_ bart.;'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'if findc(&&name&i,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put '&&name&i=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,&&name&i)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else &&name&i=quote(cats(&&name&i));'; put '%end;'; put '%end;'; put 'run;'; put 'filename _sjs3 temp lrecl=131068 ;'; put 'data _null_;'; put 'file _sjs3 encoding=''utf-8'';'; put 'if _n_=1 then put "[";'; put 'set &tempds;'; put 'if _n_>1 then put "," @; put'; put '%if &action=ARR %then "[" ; %else "{" ;'; put '%do i=1 %to &numcols;'; put '%if &i>1 %then "," ;'; put '%if &action=OBJ %then """&&name&i"":" ;'; put '"&&name&i"n /* name literal for reserved variable names */'; put '%end;'; put '%if &action=ARR %then "]" ; %else "}" ; ;'; put '/* close out the table */'; put 'data _null_;'; put 'file _sjs3 mod encoding=''utf-8'';'; put 'put '']'';'; put 'run;'; put 'data _null_;'; put 'infile _sjs3 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs3 clear;'; put '%end;'; put 'proc sql;'; put 'drop table &colinfo, &tempds;'; put '%if %substr(&showmeta,1,1)=Y %then %do;'; put 'filename _sjs4 temp lrecl=131068 encoding=''utf-8'';'; put 'data _null_;'; put 'file _sjs4;'; put 'length label $350;'; put 'put ", ""$%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":{""vars"":{";'; put 'do i=1 to &numcols;'; put 'name=quote(trim(symget(cats(''name'',i))));'; put 'format=quote(trim(symget(cats(''fmt'',i))));'; put 'label=quote(prxchange(''s/\\/\\\\/'',-1,trim(symget(cats(''label'',i)))));'; put 'length=quote(trim(symget(cats(''length'',i))));'; put 'type=quote(trim(symget(cats(''typelong'',i))));'; put 'if i>1 then put "," @@;'; put 'put name '':{"format":'' format '',"label":'' label'; put ''',"length":'' length '',"type":'' type ''}'';'; put 'end;'; put 'put ''}}'';'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs4 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs4 clear;'; put '%end;'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put 'data _null_; file &jref encoding=''utf-8'' mod ;'; put 'put "}";'; put 'run;'; put '%end;'; put '%mend mp_jsonout;'; put '%macro mm_webout(action,ds,dslabel=,fref=_webout,fmt=N,missing=NULL'; put ',showmeta=N,maxobs=MAX,workobs=0'; put ');'; put '%global _webin_file_count _webin_fileref1 _webin_name1 _program _debug'; put 'sasjs_tables;'; put '%local i tempds jsonengine;'; put '/* see https://github.com/sasjs/core/issues/41 */'; put '%if "%upcase(&SYSENCODING)" ne "UTF-8" %then %let jsonengine=PROCJSON;'; put '%else %let jsonengine=DATASTEP;'; put '%if &action=FETCH %then %do;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'options mprint notes mprintnest;'; put '%end;'; put '%let _webin_file_count=%eval(&_webin_file_count+0);'; put '/* now read in the data */'; put '%do i=1 %to &_webin_file_count;'; put '%if &_webin_file_count=1 %then %do;'; put '%let _webin_fileref1=&_webin_fileref;'; put '%let _webin_name1=&_webin_name;'; put '%end;'; put 'data _null_;'; put 'infile &&_webin_fileref&i termstr=crlf;'; put 'input;'; put 'call symputx(''input_statement'',_infile_);'; put 'putlog "&&_webin_name&i input statement: " _infile_;'; put 'stop;'; put 'data &&_webin_name&i;'; put 'infile &&_webin_fileref&i firstobs=2 dsd termstr=crlf encoding=''utf-8'';'; put 'input &input_statement;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'if _n_<20 then putlog _infile_;'; put '%end;'; put 'run;'; put '%let sasjs_tables=&sasjs_tables &&_webin_name&i;'; put '%end;'; put '%end;'; put '%else %if &action=OPEN %then %do;'; put '/* fix encoding */'; put 'OPTIONS NOBOMFILE;'; put '/**'; put '* check xengine type to avoid the below err message:'; put '* > Function is only valid for filerefs using the CACHE access method.'; put '*/'; put 'data _null_;'; put 'set sashelp.vextfl(where=(fileref="_WEBOUT"));'; put 'if xengine=''STREAM'' then do;'; put 'rc=stpsrv_header(''Content-type'',"text/html; encoding=utf-8");'; put 'end;'; put 'run;'; put '/* setup json */'; put 'data _null_;file &fref encoding=''utf-8'';'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutBEGIN<<'';'; put '%end;'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'run;'; put '%end;'; put '%else %if &action=ARR or &action=OBJ %then %do;'; put '%mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt,jref=&fref'; put ',engine=&jsonengine,missing=&missing,showmeta=&showmeta,maxobs=&maxobs'; put ')'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put '/* To avoid issues with _webout on EBI we use a temporary file */'; put 'filename _sjsref temp lrecl=131068;'; put '%if %str(&workobs) > 0 %then %do;'; put '/* if debug mode, send back first XX records of each work table also */'; put 'data;run;%let tempds=%scan(&syslast,2,.);'; put 'ods output Members=&tempds;'; put 'proc datasets library=WORK memtype=data;'; put '%local wtcnt;%let wtcnt=0;'; put 'data _null_;'; put 'set &tempds;'; put 'if not (upcase(name) =:"DATA"); /* ignore temp datasets */'; put 'i+1;'; put 'call symputx(cats(''wt'',i),name,''l'');'; put 'call symputx(''wtcnt'',i,''l'');'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put ",""WORK"":{";'; put '%do i=1 %to &wtcnt;'; put '%let wt=&&wt&i;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'dsid=open("WORK.&wt",''is'');'; put 'nlobs=attrn(dsid,''NLOBS'');'; put 'nvars=attrn(dsid,''NVARS'');'; put 'rc=close(dsid);'; put 'if &i>1 then put '',''@;'; put 'put " ""&wt"" : {";'; put 'put ''"nlobs":'' nlobs;'; put 'put '',"nvars":'' nvars;'; put '%mp_jsonout(OBJ,&wt,jref=_sjsref,dslabel=first10rows,showmeta=Y'; put ',maxobs=&workobs'; put ')'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put '%end;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put 'run;'; put '%end;'; put '/* close off json */'; put 'data _null_;file _sjsref mod encoding=''utf-8'';'; put 'length SYSPROCESSNAME syserrortext syswarningtext autoexec $512;'; put 'put ",""_DEBUG"" : ""&_debug"" ";'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'autoexec=quote(urlencode(trim(getoption(''autoexec''))));'; put 'put '',"AUTOEXEC" : '' autoexec;'; put 'put ",""MF_GETUSER"" : ""%mf_getuser()"" ";'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'put ",""SYSENCODING"" : ""&sysencoding"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSPROCESSID"" : ""&SYSPROCESSID"" ";'; put 'put ",""SYSPROCESSMODE"" : ""&SYSPROCESSMODE"" ";'; put 'SYSPROCESSNAME=quote(urlencode(cats(SYSPROCESSNAME)));'; put 'put ",""SYSPROCESSNAME"" : " SYSPROCESSNAME;'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put '',"SYSWARNINGTEXT" : '' syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'length memsize $32;'; put 'memsize="%sysfunc(INPUTN(%sysfunc(getoption(memsize)), best.),sizekmg.)";'; put 'memsize=quote(cats(memsize));'; put 'put '',"MEMSIZE" : '' memsize;'; put 'put "}" @;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutEND<<'';'; put '%end;'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjsref lrecl=1 recfm=n;'; put 'file &fref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjsref clear;'; put '%end;'; put '%mend mm_webout;'; put '%macro mf_existds(libds'; put ')/*/STORE SOURCE*/;'; put '%if %sysfunc(exist(&libds)) ne 1 & %sysfunc(exist(&libds,VIEW)) ne 1 %then 0;'; put '%else 1;'; put '%mend mf_existds;'; put '* SAS Macros end;'; put '* SAS Includes start;'; put '* SAS Includes end;'; put '* Binary Files start;'; put '* Binary Files end;'; put '* ServiceInit start;'; put 'options noquotelenmax ps=max;'; put '/* create dummy macros to prevent stpbegin / stpend from corrupting sessions */'; put '%macro stpbegin();'; put '%put NOTE: the STPBEGIN macro should not be used for web apps!;'; put '%mend stpbegin;'; put '%macro stpend();'; put '%put NOTE: the STPEND macro should not be used for web apps!;'; put '%mend stpend;'; put '* ServiceInit end;'; put '* Service start;'; put '/**'; put '@file viewlibarray.sas'; put '@brief List the libraries for view access'; put '@details'; put '

SAS Macros

'; put '@li dc_getlibs.sas'; put '@li mp_abort.sas'; put '@li mf_getuser.sas'; put '@li mpe_getgroups.sas'; put '@li mm_webout.sas'; put '@li mf_existds.sas'; put '@version 9.2'; put '@author 4GL Apps Ltd'; put '@copyright 4GL Apps Ltd. This code may only be used within Data Controller'; put 'and may not be re-distributed or re-sold without the express permission of'; put '4GL Apps Ltd.'; put '**/'; put '%mpeinit()'; put '%let keepvars=libraryref libraryname;'; put 'data _null_;'; put 'length keepvars $32;'; put 'set %sysfunc(ifc(%mf_existds(iwant),iwant,_null_));'; put 'call symputx(''keepvars'',keepvars);'; put 'run;'; put '/**'; put '* get full list of libraries'; put '*/'; put '%dc_getlibs(outds=work.mm_getLibs)'; put '/* get security groups */'; put '%mpe_getgroups(user=%mf_getuser(),outds=groups)'; put '/* get security settings */'; put 'data sec;'; put 'set &mpelib..mpe_security;'; put 'where &dc_dttmtfmt. lt tx_to and ACCESS_LEVEL=''VIEW'';'; put 'run;'; put '/* check for any matching groups */'; put 'proc sql noprint;'; put 'create table matches as'; put 'select * from sec'; put 'where upcase(sas_group) in (select upcase(groupname) from groups);'; put 'select count(*) into: securitygroupscount from matches;'; put 'select count(*) into: ALL_CNT from matches where libref=''*ALL*'';'; put '%put securitygroupscount=&securitygroupscount;'; put '%put ALL_CNT=&ALL_CNT;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(syscc=&syscc)'; put ')'; put '%macro mpestp_viewlibs();'; put '%if not %symexist(DC_RESTRICT_VIEWER) %then %let DC_RESTRICT_VIEWER=NO;'; put '/* scenario 1 - user is in admin group, hence can view all libraries */'; put 'proc sql noprint;'; put 'select count(*) into: scenario1 from groups where groupname="&mpeadmins";'; put '%if &scenario1>0 %then %do;'; put '%put user in admin group (scenario1=&scenario1);'; put '%return;'; put '%end;'; put '/* scenario 2 - viewer unrestricted and no groups listed */'; put '%if &DC_RESTRICT_VIEWER=NO and &securitygroupscount=0 %then %do;'; put '%put DC_RESTRICT_VIEWER=&DC_RESTRICT_VIEWER;'; put '%put securitygroupscount=&securitygroupscount;'; put '%return;'; put '%end;'; put '/* scenario 3 - an *ALL* libref is listed */'; put '%if &all_cnt>0 %then %do;'; put '%put all_cnt=&all_cnt;'; put '%return;'; put '%end;'; put '/* scenario 4 - specific librefs listed */'; put '%if &securitygroupscount>0 %then %do;'; put '%put scenario 4;'; put '%put securitygroupscount=&securitygroupscount;'; put 'proc sql;'; put 'delete from mm_getLibs'; put 'where upcase(libraryref) not in (select upcase(libref) from matches);'; put '%return;'; put '%end;'; put '/* viewer restricted and no groups listed */'; put '%if &DC_RESTRICT_VIEWER=YES and &securitygroupscount=0 %then %do;'; put '%put DC_RESTRICT_VIEWER=&DC_RESTRICT_VIEWER;'; put '%put securitygroupscount=&securitygroupscount;'; put 'data mm_getlibs;'; put 'set mm_getlibs;'; put 'stop;'; put 'run;'; put '%return;'; put '%end;'; put '%mp_abort(iftrue= (1=1)'; put ',mac=&_program..sas'; put ',msg=%str(unhandled security logic error!)'; put ')'; put '%mend mpestp_viewlibs;'; put '%mpestp_viewlibs()'; put '%global dc_viewlib_check;'; put '/**'; put '* deal with invalid and duplicate library definitions'; put '*/'; put 'proc sort data=mm_getlibs;'; put 'by libraryref libraryname;'; put 'run;'; put 'data mm_getlibs;'; put 'set mm_getlibs;'; put 'by libraryref;'; put 'if symget(''dc_viewlib_check'')=''YES'' then do;'; put '/* note - invalid libraries can result in exception errors. If this happens,'; put 'configure the dc_viewlib_check variable to NO in Data Controller Settings'; put '*/'; put 'rc=libname(libraryref,,''meta'',cats(''library="'',libraryname,''";''));'; put 'drop rc;'; put 'if rc ne 0 then do;'; put 'putlog "NOTE: Library " libraryname " does not exist!!";'; put 'putlog (_all_) (=);'; put 'delete;'; put 'end;'; put 'end;'; put 'if not first.libraryref then delete;'; put 'run;'; put 'proc sort data=mm_getlibs (keep=&keepvars);'; put 'by libraryname;'; put 'run;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(syscc=&syscc)'; put ')'; put '%mm_webout(OPEN)'; put '%mm_webout(ARR, mm_getLibs)'; put '%mm_webout(CLOSE)'; put '* Service end;'; run; %mm_createwebservice(path=&appLoc/&path, name=&service, code=sascode, server=&serverName, replace=yes) filename sascode clear; %let service=viewlibs; filename sascode temp lrecl=32767; data _null_; file sascode; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '/**'; put '@file mp_jsonout.sas'; put '@brief Writes JSON in SASjs format to a fileref'; put '@details This macro can be used to OPEN a JSON stream and send one or more'; put 'tables as arrays of rows, where each row can be an object or a nested array.'; put 'There are two engines available - DATASTEP or PROCJSON.'; put 'PROC JSON is fast but will produce errs like the ones below if'; put 'special chars are encountered.'; put '> (ERR)OR: Some code points did not transcode.'; put '> An object or array close is not valid at this point in the JSON text.'; put '> Date value out of range'; put 'If this happens, try running with ENGINE=DATASTEP.'; put 'The DATASTEP engine is used to handle special SAS missing numerics, and'; put 'can also convert entire datasets to formatted values. Output JSON is always'; put 'in UTF-8.'; put 'Usage:'; put 'filename tmp temp;'; put 'data class; set sashelp.class;run;'; put '%mp_jsonout(OPEN,jref=tmp)'; put '%mp_jsonout(OBJ,class,jref=tmp)'; put '%mp_jsonout(OBJ,class,dslabel=class2,jref=tmp,showmeta=Y)'; put '%mp_jsonout(CLOSE,jref=tmp)'; put 'data _null_;'; put 'infile tmp;'; put 'input;putlog _infile_;'; put 'run;'; put 'If you are building web apps with SAS then you are strongly encouraged to use'; put 'the mX_createwebservice macros in combination with the'; put '[sasjs adapter](https://github.com/sasjs/adapter).'; put 'For more information see https://sasjs.io'; put '@param [in] action Valid values:'; put '@li OPEN - opens the JSON'; put '@li OBJ - sends a table with each row as an object'; put '@li ARR - sends a table with each row in an array'; put '@li CLOSE - closes the JSON'; put '@param [in] ds The dataset to send. Must be a work table.'; put '@param [out] jref= (_webout) The fileref to which to send the JSON'; put '@param [out] dslabel= The name to give the table in the exported JSON'; put '@param [in] fmt= (Y) Whether to keep (Y) or strip (N) formats from the table'; put '@param [in] engine= (DATASTEP) Which engine to use to send the JSON. Options:'; put '@li PROCJSON (default)'; put '@li DATASTEP (more reliable when data has non standard characters)'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to JSON'; put '

Related Files

'; put '@li mp_ds2fmtds.sas'; put '@version 9.2'; put '@author Allan Bowe'; put '@source https://github.com/sasjs/core'; put '**/'; put '%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y'; put ',engine=DATASTEP'; put ',missing=NULL'; put ',showmeta=N'; put ',maxobs=MAX'; put ')/*/STORE SOURCE*/;'; put '%local tempds colinfo fmtds i numcols numobs stmt_obs lastobs optval'; put 'tmpds1 tmpds2 tmpds3 tmpds4;'; put '%let numcols=0;'; put '%if &maxobs ne MAX %then %let stmt_obs=%str(if _n_>&maxobs then stop;);'; put '%if &action=OPEN %then %do;'; put 'options nobomfile;'; put 'data _null_;file &jref encoding=''utf-8'' lrecl=200;'; put 'put ''{"PROCESSED_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''"'';'; put 'run;'; put '%end;'; put '%else %if (&action=ARR or &action=OBJ) %then %do;'; put '/* force variable names to always be uppercase in the JSON */'; put 'options validvarname=upcase;'; put '/* To avoid issues with _webout on EBI - such as encoding diffs and truncation'; put '(https://support.sas.com/kb/49/325.html) we use temporary files */'; put 'filename _sjs1 temp lrecl=200 ;'; put 'data _null_; file _sjs1 encoding=''utf-8'';'; put 'put ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":";'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjs1 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs1 clear;'; put '/* grab col defs */'; put 'proc contents noprint data=&ds'; put 'out=_data_(keep=name type length format formatl formatd varnum label);'; put 'run;'; put '%let colinfo=%scan(&syslast,2,.);'; put 'proc sort data=&colinfo;'; put 'by varnum;'; put 'run;'; put '/* move meta to mac vars */'; put 'data &colinfo;'; put 'if _n_=1 then call symputx(''numcols'',nobs,''l'');'; put 'set &colinfo end=last nobs=nobs;'; put 'name=upcase(name);'; put '/* fix formats */'; put 'if type=2 or type=6 then do;'; put 'typelong=''char'';'; put 'length fmt $49.;'; put 'if format='''' then fmt=cats(''$'',length,''.'');'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else fmt=cats(format,formatl,''.'');'; put 'end;'; put 'else do;'; put 'typelong=''num'';'; put 'if format='''' then fmt=''best.'';'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else if formatd=0 then fmt=cats(format,formatl,''.'');'; put 'else fmt=cats(format,formatl,''.'',formatd);'; put 'end;'; put '/* 32 char unique name */'; put 'newname=''sasjs''!!substr(cats(put(md5(name),$hex32.)),1,27);'; put 'call symputx(cats(''name'',_n_),name,''l'');'; put 'call symputx(cats(''newname'',_n_),newname,''l'');'; put 'call symputx(cats(''length'',_n_),length,''l'');'; put 'call symputx(cats(''fmt'',_n_),fmt,''l'');'; put 'call symputx(cats(''type'',_n_),type,''l'');'; put 'call symputx(cats(''typelong'',_n_),typelong,''l'');'; put 'call symputx(cats(''label'',_n_),coalescec(label,name),''l'');'; put '/* overwritten when fmt=Y and a custom format exists in catalog */'; put 'if typelong=''num'' then call symputx(cats(''fmtlen'',_n_),200,''l'');'; put 'else call symputx(cats(''fmtlen'',_n_),min(32767,ceil((length+10)*1.5)),''l'');'; put 'run;'; put '%let tempds=%substr(_%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql;'; put 'select count(*) into: lastobs from &ds;'; put '%if &maxobs ne MAX %then %let lastobs=%sysfunc(min(&lastobs,&maxobs));'; put '%if &engine=PROCJSON %then %do;'; put '%if &missing=STRING %then %do;'; put '%put &sysmacroname: Special Missings not supported in proc json.;'; put '%put &sysmacroname: Switching to DATASTEP engine;'; put '%goto datastep;'; put '%end;'; put 'data &tempds;'; put 'set &ds;'; put '&stmt_obs;'; put '%if &fmt=N %then format _numeric_ best32.;;'; put '/* PRETTY is necessary to avoid line truncation in large files */'; put 'filename _sjs2 temp lrecl=131068 encoding=''utf-8'';'; put 'proc json out=_sjs2 pretty'; put '%if &action=ARR %then nokeys ;'; put ';export &tempds / nosastags fmtnumeric;'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs2 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs2 clear;'; put '%end;'; put '%else %if &engine=DATASTEP %then %do;'; put '%datastep:'; put '%if %sysfunc(exist(&ds)) ne 1 & %sysfunc(exist(&ds,VIEW)) ne 1'; put '%then %do;'; put '%put &sysmacroname: &ds NOT FOUND!!!;'; put '%return;'; put '%end;'; put '%if &fmt=Y %then %do;'; put '/**'; put '* Extract format definitions'; put '* First, by getting library locations from dictionary.formats'; put '* Then, by exporting the width using proc format'; put '* Cannot use maxw from sashelp.vformat as not always populated'; put '* Cannot use fmtinfo() as not supported in all flavours'; put '*/'; put '%let tmpds1=%substr(fmtsum%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds2=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds3=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds4=%substr(col%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql noprint;'; put 'create table &tmpds1 as'; put 'select cats(libname,''.'',memname) as FMTCAT,'; put 'FMTNAME'; put 'from dictionary.formats'; put 'where fmttype=''F'' and libname is not null'; put 'and fmtname in (select format from &colinfo where format is not null)'; put 'order by 1;'; put 'create table &tmpds2('; put 'FMTNAME char(32),'; put 'LENGTH num'; put ');'; put '%local catlist cat fmtlist i;'; put 'select distinct fmtcat into: catlist separated by '' '' from &tmpds1;'; put '%do i=1 %to %sysfunc(countw(&catlist,%str( )));'; put '%let cat=%scan(&catlist,&i,%str( ));'; put 'proc sql;'; put 'select distinct fmtname into: fmtlist separated by '' '''; put 'from &tmpds1 where fmtcat="&cat";'; put 'proc format lib=&cat cntlout=&tmpds3(keep=fmtname length);'; put 'select &fmtlist;'; put 'run;'; put 'proc sql;'; put 'insert into &tmpds2 select distinct fmtname,length from &tmpds3;'; put '%end;'; put 'proc sql;'; put 'create table &tmpds4 as'; put 'select a.*, b.length as MAXW'; put 'from &colinfo a'; put 'left join &tmpds2 b'; put 'on cats(a.format)=cats(upcase(b.fmtname))'; put 'order by a.varnum;'; put 'data _null_;'; put 'set &tmpds4;'; put 'if not missing(maxw);'; put 'call symputx('; put 'cats(''fmtlen'',_n_),'; put '/* vars need extra padding due to JSON escaping of special chars */'; put 'min(32767,ceil((max(length,maxw)+10)*1.5))'; put ',''l'''; put ');'; put 'run;'; put '/* configure varlenchk - as we are explicitly shortening the variables */'; put '%let optval=%sysfunc(getoption(varlenchk));'; put 'options varlenchk=NOWARN;'; put 'data _data_(compress=char);'; put '/* shorten the new vars */'; put 'length'; put '%do i=1 %to &numcols;'; put '&&name&i $&&fmtlen&i'; put '%end;'; put ';'; put '/* rename on entry */'; put 'set &ds(rename=('; put '%do i=1 %to &numcols;'; put '&&name&i=&&newname&i'; put '%end;'; put '));'; put '&stmt_obs;'; put 'drop'; put '%do i=1 %to &numcols;'; put '&&newname&i'; put '%end;'; put ';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=num %then %do;'; put '&&name&i=cats(put(&&newname&i,&&fmt&i));'; put '%end;'; put '%else %do;'; put '&&name&i=put(&&newname&i,&&fmt&i);'; put '%end;'; put '%end;'; put 'if _error_ then do;'; put 'call symputx(''syscc'',1012);'; put 'stop;'; put 'end;'; put 'run;'; put '%let fmtds=&syslast;'; put 'options varlenchk=&optval;'; put '%end;'; put 'proc format; /* credit yabwon for special null removal */'; put 'value bart (default=40)'; put '%if &missing=NULL %then %do;'; put '._ - .z = null'; put '%end;'; put '%else %do;'; put '._ = [quote()]'; put '. = null'; put '.a - .z = [quote()]'; put '%end;'; put 'other = [best.];'; put 'data &tempds;'; put 'attrib _all_ label='''';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'length &&name&i $&&fmtlen&i...;'; put 'format &&name&i $&&fmtlen&i...;'; put '%end;'; put '%end;'; put '%if &fmt=Y %then %do;'; put 'set &fmtds;'; put '%end;'; put '%else %do;'; put 'set &ds;'; put '%end;'; put '&stmt_obs;'; put 'format _numeric_ bart.;'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'if findc(&&name&i,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put '&&name&i=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,&&name&i)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else &&name&i=quote(cats(&&name&i));'; put '%end;'; put '%end;'; put 'run;'; put 'filename _sjs3 temp lrecl=131068 ;'; put 'data _null_;'; put 'file _sjs3 encoding=''utf-8'';'; put 'if _n_=1 then put "[";'; put 'set &tempds;'; put 'if _n_>1 then put "," @; put'; put '%if &action=ARR %then "[" ; %else "{" ;'; put '%do i=1 %to &numcols;'; put '%if &i>1 %then "," ;'; put '%if &action=OBJ %then """&&name&i"":" ;'; put '"&&name&i"n /* name literal for reserved variable names */'; put '%end;'; put '%if &action=ARR %then "]" ; %else "}" ; ;'; put '/* close out the table */'; put 'data _null_;'; put 'file _sjs3 mod encoding=''utf-8'';'; put 'put '']'';'; put 'run;'; put 'data _null_;'; put 'infile _sjs3 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs3 clear;'; put '%end;'; put 'proc sql;'; put 'drop table &colinfo, &tempds;'; put '%if %substr(&showmeta,1,1)=Y %then %do;'; put 'filename _sjs4 temp lrecl=131068 encoding=''utf-8'';'; put 'data _null_;'; put 'file _sjs4;'; put 'length label $350;'; put 'put ", ""$%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":{""vars"":{";'; put 'do i=1 to &numcols;'; put 'name=quote(trim(symget(cats(''name'',i))));'; put 'format=quote(trim(symget(cats(''fmt'',i))));'; put 'label=quote(prxchange(''s/\\/\\\\/'',-1,trim(symget(cats(''label'',i)))));'; put 'length=quote(trim(symget(cats(''length'',i))));'; put 'type=quote(trim(symget(cats(''typelong'',i))));'; put 'if i>1 then put "," @@;'; put 'put name '':{"format":'' format '',"label":'' label'; put ''',"length":'' length '',"type":'' type ''}'';'; put 'end;'; put 'put ''}}'';'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs4 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs4 clear;'; put '%end;'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put 'data _null_; file &jref encoding=''utf-8'' mod ;'; put 'put "}";'; put 'run;'; put '%end;'; put '%mend mp_jsonout;'; put '/**'; put '@file mm_webout.sas'; put '@brief Send data to/from SAS Stored Processes'; put '@details This macro should be added to the start of each Stored Process,'; put '**immediately** followed by a call to:'; put '%mm_webout(FETCH)'; put 'This will read all the input data and create same-named SAS datasets in the'; put 'WORK library. You can then insert your code, and send data back using the'; put 'following syntax:'; put 'data some datasets; * make some data ;'; put 'retain some columns;'; put 'run;'; put '%mm_webout(OPEN)'; put '%mm_webout(ARR,some) * Array format, fast, suitable for large tables ;'; put '%mm_webout(OBJ,datasets) * Object format, easier to work with ;'; put 'Finally, wrap everything up send some helpful system variables too'; put '%mm_webout(CLOSE)'; put '@param [in] action Either FETCH, OPEN, ARR, OBJ or CLOSE'; put '@param [in] ds The dataset to send back to the frontend'; put '@param [out] dslabel= Value to use instead of table name for sending to JSON'; put '@param [in] fmt= (N) Setting Y converts all vars to their formatted values'; put '@param [out] fref= (_webout) The fileref to which to write the JSON'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] workobs= (0) When set to a positive integer, will create a new'; put 'output object (WORK) which contains this number of observations from all'; put 'tables in the WORK library.'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to output JSON'; put '

SAS Macros

'; put '@li mp_jsonout.sas'; put '

Related Macros

'; put '@li ms_webout.sas'; put '@li mv_webout.sas'; put '@version 9.3'; put '@author Allan Bowe'; put '**/'; put '%macro mm_webout(action,ds,dslabel=,fref=_webout,fmt=N,missing=NULL'; put ',showmeta=N,maxobs=MAX,workobs=0'; put ');'; put '%global _webin_file_count _webin_fileref1 _webin_name1 _program _debug'; put 'sasjs_tables;'; put '%local i tempds jsonengine;'; put '/* see https://github.com/sasjs/core/issues/41 */'; put '%if "%upcase(&SYSENCODING)" ne "UTF-8" %then %let jsonengine=PROCJSON;'; put '%else %let jsonengine=DATASTEP;'; put '%if &action=FETCH %then %do;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'options mprint notes mprintnest;'; put '%end;'; put '%let _webin_file_count=%eval(&_webin_file_count+0);'; put '/* now read in the data */'; put '%do i=1 %to &_webin_file_count;'; put '%if &_webin_file_count=1 %then %do;'; put '%let _webin_fileref1=&_webin_fileref;'; put '%let _webin_name1=&_webin_name;'; put '%end;'; put 'data _null_;'; put 'infile &&_webin_fileref&i termstr=crlf;'; put 'input;'; put 'call symputx(''input_statement'',_infile_);'; put 'putlog "&&_webin_name&i input statement: " _infile_;'; put 'stop;'; put 'data &&_webin_name&i;'; put 'infile &&_webin_fileref&i firstobs=2 dsd termstr=crlf encoding=''utf-8'';'; put 'input &input_statement;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'if _n_<20 then putlog _infile_;'; put '%end;'; put 'run;'; put '%let sasjs_tables=&sasjs_tables &&_webin_name&i;'; put '%end;'; put '%end;'; put '%else %if &action=OPEN %then %do;'; put '/* fix encoding */'; put 'OPTIONS NOBOMFILE;'; put '/**'; put '* check xengine type to avoid the below err message:'; put '* > Function is only valid for filerefs using the CACHE access method.'; put '*/'; put 'data _null_;'; put 'set sashelp.vextfl(where=(fileref="_WEBOUT"));'; put 'if xengine=''STREAM'' then do;'; put 'rc=stpsrv_header(''Content-type'',"text/html; encoding=utf-8");'; put 'end;'; put 'run;'; put '/* setup json */'; put 'data _null_;file &fref encoding=''utf-8'';'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutBEGIN<<'';'; put '%end;'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'run;'; put '%end;'; put '%else %if &action=ARR or &action=OBJ %then %do;'; put '%mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt,jref=&fref'; put ',engine=&jsonengine,missing=&missing,showmeta=&showmeta,maxobs=&maxobs'; put ')'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put '/* To avoid issues with _webout on EBI we use a temporary file */'; put 'filename _sjsref temp lrecl=131068;'; put '%if %str(&workobs) > 0 %then %do;'; put '/* if debug mode, send back first XX records of each work table also */'; put 'data;run;%let tempds=%scan(&syslast,2,.);'; put 'ods output Members=&tempds;'; put 'proc datasets library=WORK memtype=data;'; put '%local wtcnt;%let wtcnt=0;'; put 'data _null_;'; put 'set &tempds;'; put 'if not (upcase(name) =:"DATA"); /* ignore temp datasets */'; put 'i+1;'; put 'call symputx(cats(''wt'',i),name,''l'');'; put 'call symputx(''wtcnt'',i,''l'');'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put ",""WORK"":{";'; put '%do i=1 %to &wtcnt;'; put '%let wt=&&wt&i;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'dsid=open("WORK.&wt",''is'');'; put 'nlobs=attrn(dsid,''NLOBS'');'; put 'nvars=attrn(dsid,''NVARS'');'; put 'rc=close(dsid);'; put 'if &i>1 then put '',''@;'; put 'put " ""&wt"" : {";'; put 'put ''"nlobs":'' nlobs;'; put 'put '',"nvars":'' nvars;'; put '%mp_jsonout(OBJ,&wt,jref=_sjsref,dslabel=first10rows,showmeta=Y'; put ',maxobs=&workobs'; put ')'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put '%end;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put 'run;'; put '%end;'; put '/* close off json */'; put 'data _null_;file _sjsref mod encoding=''utf-8'';'; put 'length SYSPROCESSNAME syserrortext syswarningtext autoexec $512;'; put 'put ",""_DEBUG"" : ""&_debug"" ";'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'autoexec=quote(urlencode(trim(getoption(''autoexec''))));'; put 'put '',"AUTOEXEC" : '' autoexec;'; put 'put ",""MF_GETUSER"" : ""%mf_getuser()"" ";'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'put ",""SYSENCODING"" : ""&sysencoding"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSPROCESSID"" : ""&SYSPROCESSID"" ";'; put 'put ",""SYSPROCESSMODE"" : ""&SYSPROCESSMODE"" ";'; put 'SYSPROCESSNAME=quote(urlencode(cats(SYSPROCESSNAME)));'; put 'put ",""SYSPROCESSNAME"" : " SYSPROCESSNAME;'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put '',"SYSWARNINGTEXT" : '' syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'length memsize $32;'; put 'memsize="%sysfunc(INPUTN(%sysfunc(getoption(memsize)), best.),sizekmg.)";'; put 'memsize=quote(cats(memsize));'; put 'put '',"MEMSIZE" : '' memsize;'; put 'put "}" @;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutEND<<'';'; put '%end;'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjsref lrecl=1 recfm=n;'; put 'file &fref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjsref clear;'; put '%end;'; put '%mend mm_webout;'; put '%macro webout(action,ds,dslabel=,fmt=,missing=NULL,showmeta=NO,maxobs=MAX);'; put '%mm_webout(&action,ds=&ds,dslabel=&dslabel,fmt=&fmt'; put ',missing=&missing'; put ',showmeta=&showmeta'; put ',maxobs=&maxobs'; put ') %mend;'; put '/* provide additional debug info */'; put '%global _program;'; put '%put &=syscc;'; put '%put user=%mf_getuser();'; put '%put pgm=&_program;'; put '%put timestamp=%sysfunc(datetime(),datetime19.);'; put '* Service Variables start;'; put '* Service Variables end;'; put '* SAS Macros start;'; put '%macro mf_getapploc(pgm);'; put '%if "&pgm"="" %then %do;'; put '%if %symexist(_program) %then %let pgm=&_program;'; put '%else %do;'; put '%put &sysmacroname: No value provided and no _program variable available;'; put '%return;'; put '%end;'; put '%end;'; put '%local root;'; put '/**'; put '* First check we are not in the tests/macros folder (which has no subfolders)'; put '* or specifically in the testsetup or testteardown services'; put '*/'; put '%if %index(&pgm,/tests/macros/)'; put 'or %index(&pgm,/tests/testsetup)'; put 'or %index(&pgm,/tests/testteardown)'; put '%then %do;'; put '%let root=%substr(&pgm,1,%index(&pgm,/tests)-1);'; put '&root'; put '%return;'; put '%end;'; put '/**'; put '* Next, move up two levels to avoid matches on subfolder or service name'; put '*/'; put '%let root=%substr(&pgm,1,%length(&pgm)-%length(%scan(&pgm,-1,/))-1);'; put '%let root=%substr(&root,1,%length(&root)-%length(%scan(&root,-1,/))-1);'; put '%if %index(&root,/tests/) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/tests/)-1);'; put '%end;'; put '%else %if %index(&root,/services) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/services)-1);'; put '%end;'; put '%else %if %index(&root,/jobs) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/jobs)-1);'; put '%end;'; put '%else %put &sysmacroname: Could not find an app location from &pgm;'; put '&root'; put '%mend mf_getapploc ;'; put '%macro mf_getuniquefileref(prefix=_,maxtries=1000,lrecl=32767);'; put '%local rc fname;'; put '%if &prefix=0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%end;'; put '%else %do;'; put '%local x len;'; put '%let len=%eval(8-%length(&prefix));'; put '%let x=0;'; put '%do x=0 %to &maxtries;'; put '%let fname=&prefix%substr(%sysfunc(ranuni(0)),3,&len);'; put '%if %sysfunc(fileref(&fname)) > 0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%return;'; put '%end;'; put '%end;'; put '%put unable to find available fileref after &maxtries attempts;'; put '%end;'; put '%mend mf_getuniquefileref;'; put '%macro mp_abort(mac=mp_abort.sas, type=, msg=, iftrue=%str(1=1)'; put ', errds=work.mp_abort_errds'; put ', mode=REGULAR'; put ')/*/STORE SOURCE*/;'; put '%global sysprocessmode sysprocessname sasjs_stpsrv_header_loc sasjsprocessmode;'; put '%local fref fid i;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%put NOTE: /// mp_abort macro executing //;'; put '%if %length(&mac)>0 %then %put NOTE- called by &mac;'; put '%put NOTE - &msg;'; put '%if %symexist(_SYSINCLUDEFILEDEVICE)'; put '/* abort cancel FILE does not restart outside the INCLUDE on Viya 3.5 */'; put 'and %superq(SYSPROCESSNAME) ne %str(Compute Server)'; put '%then %do;'; put '%if "*&_SYSINCLUDEFILEDEVICE*" ne "**" %then %do;'; put 'data &errds;'; put 'iftrue=''1=1'';'; put 'length mac $100 msg $5000;'; put 'mac=symget(''mac'');'; put 'msg=symget(''msg'');'; put 'run;'; put 'data _null_;'; put 'abort cancel FILE;'; put 'run;'; put '%return;'; put '%end;'; put '%end;'; put '/* Web App Context */'; put '%if %symexist(_PROGRAM)'; put 'or %superq(SYSPROCESSNAME) = %str(Compute Server)'; put 'or &mode=INCLUDE'; put '%then %do;'; put 'options obs=max replace mprint;'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5"'; put '%then %do;'; put 'options nosyntaxcheck;'; put '%end;'; put '%if &mode=INCLUDE %then %do;'; put '%if %sysfunc(exist(&errds))=1 %then %do;'; put 'data _null_;'; put 'set &errds;'; put 'call symputx(''iftrue'',iftrue,''l'');'; put 'call symputx(''mac'',mac,''l'');'; put 'call symputx(''msg'',msg,''l'');'; put 'putlog (_all_)(=);'; put 'run;'; put '%if (&iftrue)=0 %then %return;'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: No include errors found;'; put '%return;'; put '%end;'; put '%end;'; put '/* extract log errs / warns, if exist */'; put '%local logloc logline;'; put '%global logmsg; /* capture global messages */'; put '%if %symexist(SYSPRINTTOLOG) %then %let logloc=&SYSPRINTTOLOG;'; put '%else %let logloc=%qsysfunc(getoption(LOG));'; put 'proc printto log=log;run;'; put '%let logline=0;'; put '%if %length(&logloc)>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input; putlog _infile_;'; put 'i=1;'; put 'retain logonce 0;'; put 'if ('; put '_infile_=:"%str(WARN)ING" or _infile_=:"%str(ERR)OR"'; put ') and logonce=0 then'; put 'do;'; put 'call symputx(''logline'',_n_);'; put 'logonce+1;'; put 'end;'; put 'run;'; put '/* capture log including lines BEFORE the err */'; put '%if &logline>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input;'; put 'i=1;'; put 'stoploop=0;'; put 'if _n_ ge &logline-15 and stoploop=0 then do until (i>22);'; put 'call symputx(''logmsg'',catx(''\n'',symget(''logmsg''),_infile_));'; put 'input;'; put 'i+1;'; put 'stoploop=1;'; put 'end;'; put 'if stoploop=1 then stop;'; put 'run;'; put '%end;'; put '%end;'; put '%if %symexist(SYS_JES_JOB_URI) %then %do;'; put '/* setup webout for Viya */'; put 'options nobomfile;'; put '%if "X&SYS_JES_JOB_URI.X"="XX" %then %do;'; put 'filename _webout temp lrecl=999999 mod;'; put '%end;'; put '%else %do;'; put 'filename _webout filesrvc parenturi="&SYS_JES_JOB_URI"'; put 'name="_webout.json" lrecl=999999 mod;'; put '%end;'; put '%end;'; put '%else %if %sysfunc(filename(fref,&sasjs_stpsrv_header_loc))=0 %then %do;'; put 'options nobomfile;'; put '/* set up http header for SASjs Server */'; put '%let fid=%sysfunc(fopen(&fref,A));'; put '%if &fid=0 %then %do;'; put '%put %str(ERR)OR: %sysfunc(sysmsg());'; put '%return;'; put '%end;'; put '%let rc=%sysfunc(fput(&fid,%str(Content-Type: application/json)));'; put '%let rc=%sysfunc(fwrite(&fid));'; put '%let rc=%sysfunc(fclose(&fid));'; put '%let rc=%sysfunc(filename(&fref));'; put '%end;'; put '/* send response in SASjs JSON format */'; put 'data _null_;'; put 'file _webout mod lrecl=32000 encoding=''utf-8'';'; put 'length msg syswarningtext syserrortext $32767 mode $10 ;'; put 'sasdatetime=datetime();'; put 'msg=symget(''msg'');'; put '%if &logline>0 %then %do;'; put 'msg=cats(msg,''\n\nLog Extract:\n'',symget(''logmsg''));'; put '%end;'; put '/* escape the escapes */'; put 'msg=tranwrd(msg,''\'',''\\'');'; put '/* escape the quotes */'; put 'msg=tranwrd(msg,''"'',''\"'');'; put '/* ditch the CRLFs as chrome complains */'; put 'msg=compress(msg,,''kw'');'; put '/* quote without quoting the quotes (which are escaped instead) */'; put 'msg=cats(''"'',msg,''"'');'; put 'if symexist(''_debug'') then debug=quote(trim(symget(''_debug'')));'; put 'else debug=''""'';'; put 'if symget(''sasjsprocessmode'')=''Stored Program'' then mode=''SASJS'';'; put 'if mode ne ''SASJS'' then put ''>>weboutBEGIN<<'';'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'put '',"sasjsAbort" : [{'';'; put 'put '' "MSG":'' msg ;'; put 'put '' ,"MAC": "'' "&mac" ''"}]'';'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'put '',"_DEBUG":'' debug ;'; put 'if symexist(''_metauser'') then do;'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put 'end;'; put 'if symexist(''SYS_JES_JOB_URI'') then do;'; put 'SYS_JES_JOB_URI=quote(trim(symget(''SYS_JES_JOB_URI'')));'; put 'put '',"SYS_JES_JOB_URI": '' SYS_JES_JOB_URI;'; put 'end;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put ",""SYSWARNINGTEXT"" : " syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'put "}" ;'; put 'if mode ne ''SASJS'' then put ''>>weboutEND<<'';'; put 'run;'; put '%put _all_;'; put '%if "&sysprocessmode " = "SAS Stored Process Server " %then %do;'; put 'data _null_;'; put 'putlog ''stpsrvset program err and syscc'';'; put 'rc=stpsrvset(''program error'', 0);'; put 'call symputx("syscc",0,"g");'; put 'run;'; put '%if &sysscp=WIN'; put 'and 1=0 /* deprecating this logic until we figure out a consistent abort */'; put 'and "%substr(%str(&sysvlong ),1,8)"="9.04.01M"'; put 'and "%substr(%str(&sysvlong ),9,1)">"5" %then %do;'; put '/* skip approach (below) does not work in windows m6+ envs */'; put 'endsas;'; put '%end;'; put '%else %do;'; put '/**'; put '* endsas kills 9.4m3 deployments by orphaning multibridges.'; put '* Abort variants are ungraceful (non zero return code)'; put '* This approach lets SAS run silently until the end :-)'; put '* Caution - fails when called within a %include within a macro'; put '* Use mp_include() to handle this.'; put '*/'; put 'filename skip temp;'; put 'data _null_;'; put 'file skip;'; put 'put ''%macro skip();'';'; put 'comment ''%mend skip; -> fix lint '';'; put 'put ''%macro skippy();'';'; put 'comment ''%mend skippy; -> fix lint '';'; put 'run;'; put '%inc skip;'; put '%end;'; put '%end;'; put '%else %if "&sysprocessmode " = "SAS Compute Server " %then %do;'; put '/* endsas kills the session making it harder to fetch results */'; put 'data _null_;'; put 'syswarningtext=symget(''syswarningtext'');'; put 'syserrortext=symget(''syserrortext'');'; put 'abort_msg=symget(''msg'');'; put 'syscc=symget(''syscc'');'; put 'sysuserid=symget(''sysuserid'');'; put 'iftrue=symget(''iftrue'');'; put 'put (_all_)(/=);'; put 'call symputx(''syscc'',0);'; put 'abort cancel nolist;'; put 'run;'; put '%end;'; put '%else %do;'; put '%abort cancel;'; put '%end;'; put '%end;'; put '%else %do;'; put '%put _all_;'; put '%abort cancel;'; put '%end;'; put '%mend mp_abort;'; put '/** @endcond */'; put '%macro mm_getstpcode('; put 'tree=/User Folders/sasdemo/somestp'; put ',name='; put ',outloc=0'; put ',outref=0'; put ',mDebug=1'; put ',showlog=NO'; put ');'; put '%local mD;'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing &sysmacroname..sas;'; put '%&mD.put _local_;'; put '%if %length(&name)>0 %then %let name=/&name;'; put '/* first, check if STP exists */'; put '%local tsuri;'; put '%let tsuri=stopifempty ;'; put 'data _null_;'; put 'format type uri tsuri value $200.;'; put 'call missing (of _all_);'; put 'path="&tree&name(StoredProcess)";'; put '/* first, find the STP ID */'; put 'if metadata_pathobj("",path,"StoredProcess",type,uri)>0 then do;'; put '/* get sourcecode */'; put 'cnt=1;'; put 'do while (metadata_getnasn(uri,"Notes",cnt,tsuri)>0);'; put 'rc=metadata_getattr(tsuri,"Name",value);'; put '&mD.put tsuri= value=;'; put 'if value="SourceCode" then do;'; put '/* found it! */'; put 'rc=metadata_getattr(tsuri,"Id",value);'; put 'call symputx(''tsuri'',value,''l'');'; put 'stop;'; put 'end;'; put 'cnt+1;'; put 'end;'; put 'end;'; put 'else put (_all_)(=);'; put 'run;'; put '%mp_abort(iftrue= (&tsuri=stopifempty)'; put ',mac=mm_getstpcode'; put ',msg=%str(&tree&name.(StoredProcess) not found!)'; put ')'; put '/**'; put '* Now we can extract the textstore'; put '*/'; put 'filename __getdoc temp lrecl=10000000;'; put 'proc metadata'; put 'in="$METAREPOSITORY'; put ''; put 'SAS1"'; put 'out=__getdoc ;'; put 'run;'; put '/* find the beginning of the text */'; put '%local start;'; put 'data _null_;'; put 'infile __getdoc lrecl=10000;'; put 'input;'; put 'start=index(_infile_,''StoredText="'');'; put 'if start then do;'; put 'call symputx("start",start+11);'; put '*putlog ''"'' _infile_ ''"'';'; put 'end;'; put 'stop;'; put '%local outeng;'; put '%if "&outloc"="0" %then %let outeng=TEMP;'; put '%else %let outeng="&outloc";'; put '%local fref;'; put '%if &outref=0 %then %let fref=%mf_getuniquefileref();'; put '%else %let fref=&outref;'; put '/* read the content, byte by byte, resolving escaped chars */'; put 'filename &fref &outeng lrecl=100000;'; put 'data _null_;'; put 'length filein 8 fileid 8;'; put 'filein = fopen("__getdoc","I",1,"B");'; put 'fileid = fopen("&fref","O",1,"B");'; put 'rec = "20"x;'; put 'length entity $6;'; put 'do while(fread(filein)=0);'; put 'x+1;'; put 'if x>&start then do;'; put 'rc = fget(filein,rec,1);'; put 'if rec=''"'' then leave;'; put 'else if rec="&" then do;'; put 'entity=rec;'; put 'do until (rec=";");'; put 'if fread(filein) ne 0 then goto getout;'; put 'rc = fget(filein,rec,1);'; put 'entity=cats(entity,rec);'; put 'end;'; put 'select (entity);'; put 'when (''&'' ) rec=''&'' ;'; put 'when (''<'' ) rec=''<'' ;'; put 'when (''>'' ) rec=''>'' ;'; put 'when (''''') rec="''" ;'; put 'when (''"'') rec=''"'' ;'; put 'when ('' '') rec=''0A''x;'; put 'when ('' '') rec=''0D''x;'; put 'when (''$'' ) rec=''$'' ;'; put 'when ('' '') rec=''09''x;'; put 'otherwise putlog "%str(WARN)ING: missing value for " entity=;'; put 'end;'; put 'rc =fput(fileid, substr(rec,1,1));'; put 'rc =fwrite(fileid);'; put 'end;'; put 'else do;'; put 'rc =fput(fileid,rec);'; put 'rc =fwrite(fileid);'; put 'end;'; put 'end;'; put 'end;'; put 'getout:'; put 'rc=fclose(filein);'; put 'rc=fclose(fileid);'; put 'run;'; put '%if &showlog=YES %then %do;'; put 'data _null_;'; put 'infile &fref lrecl=32767 end=last;'; put 'input;'; put 'if _n_=1 then putlog ''>>stpcodeBEGIN<<'';'; put 'putlog _infile_;'; put 'if last then putlog ''>>stpcodeEND<<'';'; put 'run;'; put '%end;'; put 'filename __getdoc clear;'; put '%if &outref=0 %then %do;'; put 'filename &fref clear;'; put '%end;'; put '%mend mm_getstpcode;'; put '%macro dc_getsettings();'; put '%global _program;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(syscc=&syscc on entry (&syswarningtext &syserrortext))'; put ')'; put '%if %length(&_PROGRAM)>1 %then %let root=&_program;'; put '%else %do;'; put '%global _metauser;'; put '%let _metauser=&sysuserid;'; put '/* to mimic a "real" _program we need to give a dummy role and stp name */'; put '%let root=/dummyRole/dummyName;'; put '%end;'; put '/* the DC precode is stored in the Admin folder in the root of'; put 'the project. Lets find that root. */'; put '%put &=root;'; put '%let root=%mf_getapploc();'; put '%put &=root;'; put '/* Now we know the root location we can retrieve the params */'; put '%let temploc=%sysfunc(pathname(work))/settings.txt;'; put '%mm_getstpcode(tree=&root/services/public'; put ',name=Data_Controller_Settings'; put ',outloc=&temploc'; put ')'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Unable to run getstpcode)'; put ')'; put 'filename _getsets "&temploc" lrecl=2000;'; put '/*'; put 'Do not use mp_include here - this puts a copy in every service, which creates'; put 'compilation problems when calling services from mp_include'; put '*/'; put '%inc _getsets/source2;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Problem running &sysmacroname (&syswarningtext &syserrortext))'; put ')'; put '%mend dc_getsettings;'; put '%macro mf_fmtdttm('; put ')/*/STORE SOURCE*/;'; put '%if "&sysver"="9.2" or "&sysver"="9.3"'; put 'or ("&sysver"="9.4" and "%substr(&SYSVLONG,9,1)" le "3")'; put 'or "%substr(&sysver,1,1)"="4"'; put 'or "%substr(&sysver,1,1)"="5"'; put '%then %do;DATETIME19.3%end;'; put '%else %do;E8601DT26.6%end;'; put '%mend mf_fmtdttm;'; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '%macro mp_init(prefix=SASJS'; put ')/*/STORE SOURCE*/;'; put '%if %symexist(SASJS_PREFIX) %then %return; /* only run once */'; put '%global'; put 'SASJS_PREFIX /* the ONLY hard-coded global macro variable in SASjs */'; put '&prefix._FUNCTIONS /* used in mcf_init() to track core function compilation */'; put '&prefix._INIT_NUM /* initialisation time as numeric */'; put '&prefix._INIT_DTTM /* initialisation time in E8601DT26.6 format */'; put '&prefix.WORK /* avoid typing %sysfunc(pathname(work)) every time */'; put ';'; put '%let sasjs_prefix=&prefix;'; put 'data _null_;'; put 'dttm=datetime();'; put 'call symputx("&sasjs_prefix._init_num",dttm,''g'');'; put 'call symputx("&sasjs_prefix._init_dttm",put(dttm,E8601DT26.6),''g'');'; put 'call symputx("&sasjs_prefix.work",pathname(''WORK''),''g'');'; put 'run;'; put 'options'; put 'compress=CHAR /* default is none so ensure we have something! */'; put 'datastmtchk=ALLKEYWORDS /* protection from overwriting input datasets */'; put 'errorcheck=STRICT /* catch errs in libname/filename statements */'; put 'fmterr /* ensure err when a format cannot be found */'; put 'mergenoby=%str(ERR)OR /* throw err when a merge has no BY variables */'; put 'missing=. /* changing this can cause hard to detect errs */'; put 'noquotelenmax /* avoid warnings for long strings */'; put 'noreplace /* avoid overwriting permanent datasets */'; put 'ps=max /* reduce log size slightly */'; put 'ls=max /* reduce log even more and avoid word truncation */'; put 'validmemname=COMPATIBLE /* avoid special characters etc in table names */'; put 'validvarname=V7 /* avoid special characters etc in variable names */'; put 'varinitchk=%str(ERR)OR /* avoid data mistakes from variable name typos */'; put 'varlenchk=%str(ERR)OR /* fail hard if truncation (data loss) can result */'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5" %then %do;'; put 'noautocorrect /* disallow misspelled procedure names */'; put 'dsoptions=note2err /* undocumented - convert bad NOTEs to ERRs */'; put '%end;'; put ';'; put '%mend mp_init;'; put '%macro mpeinit(fetch=YES);'; put '%global mpeinit'; put 'mpeadmins /* group with unrestricted Meditor access */'; put 'mpelocapprovals /* location for landing and staging files */'; put 'mpelib /* location of configuration tables for DC */'; put 'dc_repo_users /* location of user / group metadata */'; put 'dc_licence_key /* extracted in dc_getsettings */'; put 'dc_activation_key /* extracted in dc_getsettings */'; put 'dc_locale /* extracted in dc_getsettings */'; put 'dc_dttmtfmt /* can be overridden in dc_getsettings */'; put '_debug'; put ';'; put '%if &mpeinit=1 %then %return;'; put '%else %let mpeinit=1;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(Problem on service startup (&syswarningtext &syserrortext))'; put ')'; put '%mp_init()'; put '%if &fetch=YES %then %do;'; put '%webout(FETCH)'; put '%end;'; put '%global _CLIENTNAME;'; put '%mp_abort(iftrue= (&_CLIENTNAME=SAS Enterprise Guide)'; put ',mac=&_program..sas'; put ',msg=%str(Data Controller is a web app and should not be executed from EG)'; put ')'; put 'options urlencoding=utf8 nobomfile lrecl=32767;'; put '%let perf=%sysfunc(datetime());'; put '%put perfdiff: 0;'; put '%let dc_locale=SYSTEM; /* default if not set */'; put '/**'; put '* E8601DT26.6 has widest database support - but not all SAS flavours can'; put '* handle it. Override in the settings STP if needed.'; put '*/'; put 'data _null_;'; put 'dc_dttmtfmt=''"%sysfunc(datetime(),''!!"%mf_fmtdttm()"!!'')"dt'';'; put 'call symputx(''dc_dttmtfmt'',dc_dttmtfmt);'; put 'put dc_dttmtfmt=;'; put 'run;'; put '%put &=dc_dttmtfmt;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc prior to dc_getsettings)'; put ')'; put '%dc_getsettings()'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc after dc_getsettings)'; put ')'; put 'data _null_;'; put 'set &DC_LIBREF..mpe_config(where=('; put 'var_scope="DC"'; put 'and &dc_dttmtfmt lt tx_to'; put 'and var_active=1'; put '));'; put 'call symputx(var_name,var_value,''G'');'; put 'putlog var_name "=" var_value;'; put 'run;'; put '%let mpelib=&dc_libref;'; put '%let mpeadmins=&dc_admin_group;'; put '%let mpelocapprovals=&dc_staging_area;'; put '%let dc_repo_users=&dc_repo_users;'; put '%if &dc_locale ne SYSTEM %then %do;'; put 'options locale=&dc_locale;'; put '%end;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(Problem during compilation or with STP precode (&syswarningtext))'; put ')'; put '%mend mpeinit;'; put '%macro mf_mval(var);'; put '%if %symexist(&var) %then %do;'; put '%superq(&var)'; put '%end;'; put '%mend mf_mval;'; put '%macro mf_trimstr(basestr,trimstr);'; put '%local baselen trimlen trimval;'; put '/* return if basestr is shorter than trimstr (or 0) */'; put '%let baselen=%length(%superq(basestr));'; put '%let trimlen=%length(%superq(trimstr));'; put '%if &baselen < &trimlen or &baselen=0 %then %return;'; put '/* obtain the characters from the end of basestr */'; put '%let trimval=%qsubstr(%superq(basestr)'; put ',%length(%superq(basestr))-&trimlen+1'; put ',&trimlen);'; put '/* compare and if matching, chop it off! */'; put '%if %superq(basestr)=%superq(trimstr) %then %do;'; put '%return;'; put '%end;'; put '%else %if %superq(trimval)=%superq(trimstr) %then %do;'; put '%qsubstr(%superq(basestr),1,%length(%superq(basestr))-&trimlen)'; put '%end;'; put '%else %do;'; put '&basestr'; put '%end;'; put '%mend mf_trimstr;'; put '%macro mf_getplatform(switch'; put ')/*/STORE SOURCE*/;'; put '%local a b c;'; put '%if &switch.NONE=NONE %then %do;'; put '%if %symexist(sasjsprocessmode) %then %do;'; put '%if &sasjsprocessmode=Stored Program %then %do;'; put 'SASJS'; put '%return;'; put '%end;'; put '%end;'; put '%if %symexist(sysprocessmode) %then %do;'; put '%if "&sysprocessmode"="SAS Object Server"'; put 'or "&sysprocessmode"= "SAS Compute Server" %then %do;'; put 'SASVIYA'; put '%end;'; put '%else %if "&sysprocessmode"="SAS Stored Process Server"'; put 'or "&sysprocessmode"="SAS Workspace Server"'; put '%then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if %symexist(_metaport) or %symexist(_metauser) %then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if &switch=SASSTUDIO %then %do;'; put '/* return the version of SAS Studio else 0 */'; put '%if %mf_mval(_CLIENTAPP)=%str(SAS Studio) %then %do;'; put '%let a=%mf_mval(_CLIENTVERSION);'; put '%let b=%scan(&a,1,.);'; put '%if %eval(&b >2) %then %do;'; put '&b'; put '%end;'; put '%else 0;'; put '%end;'; put '%else 0;'; put '%end;'; put '%else %if &switch=VIYARESTAPI %then %do;'; put '%mf_trimstr(%sysfunc(getoption(servicesbaseurl)),/)'; put '%end;'; put '%mend mf_getplatform;'; put '%macro mpeterm();'; put '%local oldloc;'; put 'data _null_;'; put 'if symexist(''SYSPRINTTOLOG'') then oldloc=symget(''SYSPRINTTOLOG'');'; put 'else oldloc=getoption(''LOG'');'; put 'if subpad(oldloc,1,1) not in (''"'',"''",'' '') then oldloc=quote(cats(oldloc));'; put 'call symputx(''oldloc'',oldloc,''l'');'; put 'run;'; put '%if %length(&oldloc)>0 %then %do;'; put 'proc printto log=log;'; put 'run;'; put 'data _null_;'; put 'infile &oldloc;'; put 'input; putlog _infile_;'; put 'run;'; put '%end;'; put '%if %sysfunc(exist(&dc_libref..mpe_requests)) and %mf_getplatform() ne SASVIYA'; put '%then %do;'; put 'data ;'; put 'if 0 then set &dc_libref..mpe_requests;'; put 'request_dttm=%sysfunc(datetime());'; put 'request_user="%mf_getuser()";'; put 'request_service="%scan(&_program,-2,/)/%scan(&_program,-1,/)";'; put 'request_params='''';'; put 'output;stop;'; put 'proc append base=&dc_libref..mpe_requests data=&syslast force nowarn;'; put 'run;'; put '%end;'; put '%mend mpeterm;'; put '%macro mm_getGroups('; put 'user='; put ',outds=work.mm_getGroups'; put ',repo=foundation'; put ',mDebug=0'; put ')/*/STORE SOURCE*/;'; put '%local mD oldrepo;'; put '%let oldrepo=%sysfunc(getoption(metarepository));'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing mm_getGroups.sas;'; put '%&mD.put _local_;'; put '/* on some sites, user / group info is in a different metadata repo to the'; put 'default */'; put '%if &oldrepo ne &repo %then %do;'; put 'options metarepository=&repo;'; put '%end;'; put '%if %length(&user)=0 %then %do;'; put 'data &outds (keep=groupuri groupname groupdesc);'; put 'length groupuri groupname groupdesc group_or_role $256;'; put 'call missing(of _all_);'; put 'i+1;'; put 'do while'; put '(metadata_getnobj("omsobj:IdentityGroup?@Id contains ''.''",i,groupuri)>0);'; put 'rc=metadata_getattr(groupuri, "Name", groupname);'; put 'rc=metadata_getattr(groupuri, "Desc", groupdesc);'; put 'rc=metadata_getattr(groupuri,"PublicType",group_or_role);'; put 'if Group_or_Role = ''UserGroup'' then output;'; put 'i+1;'; put 'end;'; put 'run;'; put '%end;'; put '%else %do;'; put 'data &outds (keep=groupuri groupname groupdesc);'; put 'length uri groupuri groupname groupdesc group_or_role $256;'; put 'call missing(of _all_);'; put 'rc=metadata_getnobj("omsobj:Person?@Name=''&user''",1,uri);'; put 'if rc<=0 then do;'; put 'putlog "%str(WARN)ING: rc=" rc "&user not found "'; put '", or there was an issue reading the repository.";'; put 'stop;'; put 'end;'; put 'a=1;'; put 'grpassn=metadata_getnasn(uri,"IdentityGroups",a,groupuri);'; put 'if grpassn in (-3,-4) then do;'; put 'putlog "%str(WARN)ING: No metadata groups found for &user";'; put 'output;'; put 'end;'; put 'else do while (grpassn > 0);'; put 'rc=metadata_getattr(groupuri, "Name", groupname);'; put 'rc=metadata_getattr(groupuri, "Desc", groupdesc);'; put 'a+1;'; put 'rc=metadata_getattr(groupuri,"PublicType",group_or_role);'; put 'if Group_or_Role = ''UserGroup'' then output;'; put 'grpassn=metadata_getnasn(uri,"IdentityGroups",a,groupuri);'; put 'end;'; put 'run;'; put '%end;'; put '%if &oldrepo ne &repo %then %do;'; put 'options metarepository=&oldrepo;'; put '%end;'; put '%mend mm_getGroups;'; put '%macro dc_getusergroups(user=,outds=mm_getgroups);'; put '%global dc_repo_users;'; put '%if &dc_repo_users= %then %let dc_repo_users=foundation;'; put '%mm_getgroups(user=&user,outds=&outds,repo=&dc_repo_users)'; put '%mend dc_getusergroups;'; put '%macro mpe_getgroups(user=,outds=);'; put '%if not %symexist(dc_repo_users) %then %let dc_repo_users=foundation;'; put '%dc_getusergroups(user=&user,outds=&outds)'; put 'data;'; put 'length groupname groupdesc $256;'; put 'set &dc_libref..mpe_groups;'; put 'where &dc_dttmtfmt. lt tx_to;'; put 'where also upcase(user_name)="%upcase(&user)";'; put 'groupname=group_name;'; put 'groupdesc=group_desc;'; put 'keep groupname groupdesc;'; put 'run;'; put 'data &outds;'; put 'set &syslast &outds(keep=groupname groupdesc);'; put 'run;'; put '%mend mpe_getgroups;'; put '%macro mm_getlibs('; put 'outds=work.mm_getLibs'; put ')/*/STORE SOURCE*/;'; put '/*'; put 'flags:'; put 'OMI_SUCCINCT (2048) Do not return attributes with null values.'; put 'OMI_GET_METADATA (256) Executes a GetMetadata call for each object that'; put 'is returned by the GetMetadataObjects method.'; put 'OMI_ALL_SIMPLE (8) Gets all of the attributes of the requested object.'; put '*/'; put 'data _null_;'; put 'flags=2048+256+8;'; put 'call symputx(''flags'',flags,''l'');'; put 'run;'; put '* use a temporary fileref to hold the response;'; put 'filename response temp;'; put '/* get list of libraries */'; put 'proc metadata in='; put ''''; put '$METAREPOSITORY'; put 'SASLibrary'; put ''; put 'SAS'; put '&flags'; put ''; put ''''; put 'out=response;'; put 'run;'; put '/* write the response to the log for debugging */'; put 'data _null_;'; put 'infile response lrecl=32767;'; put 'input;'; put 'put _infile_;'; put 'run;'; put '/* create an XML map to read the response */'; put 'filename sxlemap temp;'; put 'data _null_;'; put 'file sxlemap;'; put 'put '''';'; put 'put '''';'; put 'put ''//Objects/SASLibrary'';'; put 'put ''>17'';'; put 'put ''//Objects/SASLibrary/@Id'';'; put 'put ''256>'';'; put 'put ''//Objects/SASLibrary/@Name'';'; put 'put ''8'';'; put 'put ''//Objects/SASLibrary/@Libref'';'; put 'put ''>12'';'; put 'put ''//Objects/SASLibrary/@Engine'';'; put 'put ''
'';'; put 'run;'; put 'libname _XML_ xml xmlfileref=response xmlmap=sxlemap;'; put '/* sort the response by library name */'; put 'proc sort data=_XML_.saslibrary out=&outds;'; put 'by libraryname;'; put 'run;'; put '/* clear references */'; put 'filename sxlemap clear;'; put 'filename response clear;'; put 'libname _XML_ clear;'; put '%mend mm_getlibs;'; put '%macro mm_getrepos('; put 'outds=work.mm_getrepos'; put ')/*/STORE SOURCE*/;'; put '* use a temporary fileref to hold the response;'; put 'filename response temp;'; put '/* get list of libraries */'; put 'proc metadata in='; put '"1"'; put 'out=response;'; put 'run;'; put '/* write the response to the log for debugging */'; put '/*'; put 'data _null_;'; put 'infile response lrecl=1048576;'; put 'input;'; put 'put _infile_;'; put 'run;'; put '*/'; put '/* create an XML map to read the response */'; put 'filename sxlemap temp;'; put 'data _null_;'; put 'file sxlemap;'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository";'; put 'put "";'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Id";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Name";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Desc";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@DefaultNS";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@RepositoryType";'; put 'put "characterstring20";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@RepositoryFormat";'; put 'put "characterstring10";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@Access";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@CurrentAccess";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@PauseState";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Path";'; put 'put "";'; put 'put "characterstring256";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Engine";'; put 'put "";'; put 'put "characterstring8";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Options";'; put 'put "";'; put 'put "characterstring32";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@MetadataCreated";'; put 'put "characterstring24";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@MetadataUpdated";'; put 'put "characterstring24";'; put 'put '''';'; put 'put ''
'';'; put 'run;'; put 'libname _XML_ xml xmlfileref=response xmlmap=sxlemap;'; put 'proc sort data= _XML_.SASRepos out=&outds;'; put 'by name;'; put 'run;'; put '/* clear references */'; put 'filename sxlemap clear;'; put 'filename response clear;'; put 'libname _XML_ clear;'; put '%mend mm_getrepos;'; put '%macro dc_getlibs(outds=mm_getlibs);'; put '/* take current repository */'; put '%local repo repocnt x;'; put '%let repo=%sysfunc(getoption(metarepository));'; put '/* get list of repositories and filter */'; put '%mm_getrepos(outds=repos)'; put '%let repocnt=0;'; put 'data repos;'; put 'set repos;'; put 'where repositorytype in(''CUSTOM'',''FOUNDATION'')'; put '& upcase(name) ne "%upcase(&repo)";'; put 'keep id name ;'; put 'call symputx(''repo''!!cats(_n_),name,''l'');'; put 'call symputx(''repocnt'',_n_,''l'');'; put 'run;'; put '%put _local_;'; put '%mm_getlibs(outds=&outds)'; put '%do x=1 %to &repocnt;'; put 'options metarepository=&&repo&x;'; put '%mm_getlibs(outds=&outds.a)'; put 'proc append base=&outds data=&outds.a;'; put 'run;'; put '%end;'; put 'options metarepository=&repo;'; put '%mend dc_getlibs;'; put '* SAS Macros end;'; put '* SAS Includes start;'; put '* SAS Includes end;'; put '* Binary Files start;'; put '* Binary Files end;'; put '* ServiceInit start;'; put 'options noquotelenmax ps=max;'; put '/* create dummy macros to prevent stpbegin / stpend from corrupting sessions */'; put '%macro stpbegin();'; put '%put NOTE: the STPBEGIN macro should not be used for web apps!;'; put '%mend stpbegin;'; put '%macro stpend();'; put '%put NOTE: the STPEND macro should not be used for web apps!;'; put '%mend stpend;'; put '* ServiceInit end;'; put '* Service start;'; put '/**'; put '@file viewlibs.sas'; put '@brief List the libraries for view access'; put '@details'; put '

SAS Macros

'; put '@li dc_getlibs.sas'; put '@li mp_abort.sas'; put '@li mf_getuser.sas'; put '@li mpe_getgroups.sas'; put '@li mpeinit.sas'; put '@version 9.3'; put '@author 4GL Apps Ltd'; put '@copyright 4GL Apps Ltd. This code may only be used within Data Controller'; put 'and may not be re-distributed or re-sold without the express permission of'; put '4GL Apps Ltd.'; put '**/'; put '%mpeinit()'; put '/**'; put '* get full list of libraries'; put '*/'; put '%dc_getlibs(outds=work.mm_getLibs)'; put '/* get security groups */'; put '%mpe_getgroups(user=%mf_getuser(),outds=groups)'; put '/* get security settings */'; put 'data sec;'; put 'set &mpelib..mpe_security;'; put 'where &dc_dttmtfmt.lt tx_to and ACCESS_LEVEL=''VIEW'';'; put 'run;'; put '/* check for any matching groups */'; put 'proc sql noprint;'; put 'create table matches as'; put 'select * from sec'; put 'where upcase(sas_group) in (select upcase(groupname) from groups);'; put 'select count(*) into: securitygroupscount from matches;'; put 'select count(*) into: ALL_CNT from matches where libref=''*ALL*'';'; put '%put securitygroupscount=&securitygroupscount;'; put '%put ALL_CNT=&ALL_CNT;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(syscc=&syscc)'; put ')'; put '%macro mpestp_viewlibs();'; put '%if not %symexist(DC_RESTRICT_VIEWER) %then %let DC_RESTRICT_VIEWER=NO;'; put '/* scenario 1 - user is in admin group, hence can view all libraries */'; put 'proc sql noprint;'; put 'select count(*) into: scenario1 from groups where groupname="&mpeadmins";'; put '%if &scenario1>0 %then %do;'; put '%put user in admin group (scenario1=&scenario1);'; put '%return;'; put '%end;'; put '/* scenario 2 - viewer unrestricted and no groups listed */'; put '%if &DC_RESTRICT_VIEWER=NO and &securitygroupscount=0 %then %do;'; put '%put DC_RESTRICT_VIEWER=&DC_RESTRICT_VIEWER;'; put '%put securitygroupscount=&securitygroupscount;'; put '%return;'; put '%end;'; put '/* scenario 3 - an *ALL* libref is listed */'; put '%if &all_cnt>0 %then %do;'; put '%put all_cnt=&all_cnt;'; put '%return;'; put '%end;'; put '/* scenario 4 - specific librefs listed */'; put '%if &securitygroupscount>0 %then %do;'; put '%put scenario 4;'; put '%put securitygroupscount=&securitygroupscount;'; put 'proc sql;'; put 'delete from mm_getLibs'; put 'where upcase(libraryref) not in (select upcase(libref) from matches);'; put '%return;'; put '%end;'; put '/* viewer restricted and no groups listed */'; put '%if &DC_RESTRICT_VIEWER=YES and &securitygroupscount=0 %then %do;'; put '%put DC_RESTRICT_VIEWER=&DC_RESTRICT_VIEWER;'; put '%put securitygroupscount=&securitygroupscount;'; put 'data mm_getlibs;'; put 'set mm_getlibs;'; put 'stop;'; put 'run;'; put '%return;'; put '%end;'; put '%mp_abort(iftrue= (1=1)'; put ',mac=&_program..sas'; put ',msg=%str(unhandled security logic err!)'; put ')'; put '%mend mpestp_viewlibs;'; put '%mpestp_viewlibs()'; put '%global dc_viewlib_check;'; put '/**'; put '* deal with invalid and duplicate library definitions'; put '*/'; put 'proc sort data=mm_getlibs;'; put 'by libraryref libraryname;'; put 'run;'; put 'data mm_getlibs;'; put 'set mm_getlibs;'; put 'by libraryref;'; put 'if symget(''dc_viewlib_check'')=''YES'' then do;'; put '/* note - invalid libraries can result in exception errors. If this happens,'; put 'configure the dc_viewlib_check variable to NO in Data Controller Settings'; put '*/'; put 'rc=libname(libraryref,,''meta'',cats(''library="'',libraryname,''";''));'; put 'drop rc;'; put 'if rc ne 0 then do;'; put 'putlog "NOTE: Library " libraryname " does not exist!!";'; put 'putlog (_all_) (=);'; put 'delete;'; put 'end;'; put 'end;'; put 'if not first.libraryref then delete;'; put 'run;'; put 'proc sort data=mm_getlibs out=saslibs;'; put 'by libraryname;'; put 'run;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(syscc=&syscc)'; put ')'; put '%webout(OPEN)'; put '%webout(OBJ,saslibs)'; put '%webout(CLOSE)'; put '%mpeterm()'; put '* Service end;'; run; %mm_createwebservice(path=&appLoc/&path, name=&service, code=sascode, server=&serverName, replace=yes) filename sascode clear; %let service=viewtables; filename sascode temp lrecl=32767; data _null_; file sascode; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '/**'; put '@file mp_jsonout.sas'; put '@brief Writes JSON in SASjs format to a fileref'; put '@details This macro can be used to OPEN a JSON stream and send one or more'; put 'tables as arrays of rows, where each row can be an object or a nested array.'; put 'There are two engines available - DATASTEP or PROCJSON.'; put 'PROC JSON is fast but will produce errs like the ones below if'; put 'special chars are encountered.'; put '> (ERR)OR: Some code points did not transcode.'; put '> An object or array close is not valid at this point in the JSON text.'; put '> Date value out of range'; put 'If this happens, try running with ENGINE=DATASTEP.'; put 'The DATASTEP engine is used to handle special SAS missing numerics, and'; put 'can also convert entire datasets to formatted values. Output JSON is always'; put 'in UTF-8.'; put 'Usage:'; put 'filename tmp temp;'; put 'data class; set sashelp.class;run;'; put '%mp_jsonout(OPEN,jref=tmp)'; put '%mp_jsonout(OBJ,class,jref=tmp)'; put '%mp_jsonout(OBJ,class,dslabel=class2,jref=tmp,showmeta=Y)'; put '%mp_jsonout(CLOSE,jref=tmp)'; put 'data _null_;'; put 'infile tmp;'; put 'input;putlog _infile_;'; put 'run;'; put 'If you are building web apps with SAS then you are strongly encouraged to use'; put 'the mX_createwebservice macros in combination with the'; put '[sasjs adapter](https://github.com/sasjs/adapter).'; put 'For more information see https://sasjs.io'; put '@param [in] action Valid values:'; put '@li OPEN - opens the JSON'; put '@li OBJ - sends a table with each row as an object'; put '@li ARR - sends a table with each row in an array'; put '@li CLOSE - closes the JSON'; put '@param [in] ds The dataset to send. Must be a work table.'; put '@param [out] jref= (_webout) The fileref to which to send the JSON'; put '@param [out] dslabel= The name to give the table in the exported JSON'; put '@param [in] fmt= (Y) Whether to keep (Y) or strip (N) formats from the table'; put '@param [in] engine= (DATASTEP) Which engine to use to send the JSON. Options:'; put '@li PROCJSON (default)'; put '@li DATASTEP (more reliable when data has non standard characters)'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to JSON'; put '

Related Files

'; put '@li mp_ds2fmtds.sas'; put '@version 9.2'; put '@author Allan Bowe'; put '@source https://github.com/sasjs/core'; put '**/'; put '%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y'; put ',engine=DATASTEP'; put ',missing=NULL'; put ',showmeta=N'; put ',maxobs=MAX'; put ')/*/STORE SOURCE*/;'; put '%local tempds colinfo fmtds i numcols numobs stmt_obs lastobs optval'; put 'tmpds1 tmpds2 tmpds3 tmpds4;'; put '%let numcols=0;'; put '%if &maxobs ne MAX %then %let stmt_obs=%str(if _n_>&maxobs then stop;);'; put '%if &action=OPEN %then %do;'; put 'options nobomfile;'; put 'data _null_;file &jref encoding=''utf-8'' lrecl=200;'; put 'put ''{"PROCESSED_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''"'';'; put 'run;'; put '%end;'; put '%else %if (&action=ARR or &action=OBJ) %then %do;'; put '/* force variable names to always be uppercase in the JSON */'; put 'options validvarname=upcase;'; put '/* To avoid issues with _webout on EBI - such as encoding diffs and truncation'; put '(https://support.sas.com/kb/49/325.html) we use temporary files */'; put 'filename _sjs1 temp lrecl=200 ;'; put 'data _null_; file _sjs1 encoding=''utf-8'';'; put 'put ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":";'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjs1 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs1 clear;'; put '/* grab col defs */'; put 'proc contents noprint data=&ds'; put 'out=_data_(keep=name type length format formatl formatd varnum label);'; put 'run;'; put '%let colinfo=%scan(&syslast,2,.);'; put 'proc sort data=&colinfo;'; put 'by varnum;'; put 'run;'; put '/* move meta to mac vars */'; put 'data &colinfo;'; put 'if _n_=1 then call symputx(''numcols'',nobs,''l'');'; put 'set &colinfo end=last nobs=nobs;'; put 'name=upcase(name);'; put '/* fix formats */'; put 'if type=2 or type=6 then do;'; put 'typelong=''char'';'; put 'length fmt $49.;'; put 'if format='''' then fmt=cats(''$'',length,''.'');'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else fmt=cats(format,formatl,''.'');'; put 'end;'; put 'else do;'; put 'typelong=''num'';'; put 'if format='''' then fmt=''best.'';'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else if formatd=0 then fmt=cats(format,formatl,''.'');'; put 'else fmt=cats(format,formatl,''.'',formatd);'; put 'end;'; put '/* 32 char unique name */'; put 'newname=''sasjs''!!substr(cats(put(md5(name),$hex32.)),1,27);'; put 'call symputx(cats(''name'',_n_),name,''l'');'; put 'call symputx(cats(''newname'',_n_),newname,''l'');'; put 'call symputx(cats(''length'',_n_),length,''l'');'; put 'call symputx(cats(''fmt'',_n_),fmt,''l'');'; put 'call symputx(cats(''type'',_n_),type,''l'');'; put 'call symputx(cats(''typelong'',_n_),typelong,''l'');'; put 'call symputx(cats(''label'',_n_),coalescec(label,name),''l'');'; put '/* overwritten when fmt=Y and a custom format exists in catalog */'; put 'if typelong=''num'' then call symputx(cats(''fmtlen'',_n_),200,''l'');'; put 'else call symputx(cats(''fmtlen'',_n_),min(32767,ceil((length+10)*1.5)),''l'');'; put 'run;'; put '%let tempds=%substr(_%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql;'; put 'select count(*) into: lastobs from &ds;'; put '%if &maxobs ne MAX %then %let lastobs=%sysfunc(min(&lastobs,&maxobs));'; put '%if &engine=PROCJSON %then %do;'; put '%if &missing=STRING %then %do;'; put '%put &sysmacroname: Special Missings not supported in proc json.;'; put '%put &sysmacroname: Switching to DATASTEP engine;'; put '%goto datastep;'; put '%end;'; put 'data &tempds;'; put 'set &ds;'; put '&stmt_obs;'; put '%if &fmt=N %then format _numeric_ best32.;;'; put '/* PRETTY is necessary to avoid line truncation in large files */'; put 'filename _sjs2 temp lrecl=131068 encoding=''utf-8'';'; put 'proc json out=_sjs2 pretty'; put '%if &action=ARR %then nokeys ;'; put ';export &tempds / nosastags fmtnumeric;'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs2 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs2 clear;'; put '%end;'; put '%else %if &engine=DATASTEP %then %do;'; put '%datastep:'; put '%if %sysfunc(exist(&ds)) ne 1 & %sysfunc(exist(&ds,VIEW)) ne 1'; put '%then %do;'; put '%put &sysmacroname: &ds NOT FOUND!!!;'; put '%return;'; put '%end;'; put '%if &fmt=Y %then %do;'; put '/**'; put '* Extract format definitions'; put '* First, by getting library locations from dictionary.formats'; put '* Then, by exporting the width using proc format'; put '* Cannot use maxw from sashelp.vformat as not always populated'; put '* Cannot use fmtinfo() as not supported in all flavours'; put '*/'; put '%let tmpds1=%substr(fmtsum%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds2=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds3=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds4=%substr(col%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql noprint;'; put 'create table &tmpds1 as'; put 'select cats(libname,''.'',memname) as FMTCAT,'; put 'FMTNAME'; put 'from dictionary.formats'; put 'where fmttype=''F'' and libname is not null'; put 'and fmtname in (select format from &colinfo where format is not null)'; put 'order by 1;'; put 'create table &tmpds2('; put 'FMTNAME char(32),'; put 'LENGTH num'; put ');'; put '%local catlist cat fmtlist i;'; put 'select distinct fmtcat into: catlist separated by '' '' from &tmpds1;'; put '%do i=1 %to %sysfunc(countw(&catlist,%str( )));'; put '%let cat=%scan(&catlist,&i,%str( ));'; put 'proc sql;'; put 'select distinct fmtname into: fmtlist separated by '' '''; put 'from &tmpds1 where fmtcat="&cat";'; put 'proc format lib=&cat cntlout=&tmpds3(keep=fmtname length);'; put 'select &fmtlist;'; put 'run;'; put 'proc sql;'; put 'insert into &tmpds2 select distinct fmtname,length from &tmpds3;'; put '%end;'; put 'proc sql;'; put 'create table &tmpds4 as'; put 'select a.*, b.length as MAXW'; put 'from &colinfo a'; put 'left join &tmpds2 b'; put 'on cats(a.format)=cats(upcase(b.fmtname))'; put 'order by a.varnum;'; put 'data _null_;'; put 'set &tmpds4;'; put 'if not missing(maxw);'; put 'call symputx('; put 'cats(''fmtlen'',_n_),'; put '/* vars need extra padding due to JSON escaping of special chars */'; put 'min(32767,ceil((max(length,maxw)+10)*1.5))'; put ',''l'''; put ');'; put 'run;'; put '/* configure varlenchk - as we are explicitly shortening the variables */'; put '%let optval=%sysfunc(getoption(varlenchk));'; put 'options varlenchk=NOWARN;'; put 'data _data_(compress=char);'; put '/* shorten the new vars */'; put 'length'; put '%do i=1 %to &numcols;'; put '&&name&i $&&fmtlen&i'; put '%end;'; put ';'; put '/* rename on entry */'; put 'set &ds(rename=('; put '%do i=1 %to &numcols;'; put '&&name&i=&&newname&i'; put '%end;'; put '));'; put '&stmt_obs;'; put 'drop'; put '%do i=1 %to &numcols;'; put '&&newname&i'; put '%end;'; put ';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=num %then %do;'; put '&&name&i=cats(put(&&newname&i,&&fmt&i));'; put '%end;'; put '%else %do;'; put '&&name&i=put(&&newname&i,&&fmt&i);'; put '%end;'; put '%end;'; put 'if _error_ then do;'; put 'call symputx(''syscc'',1012);'; put 'stop;'; put 'end;'; put 'run;'; put '%let fmtds=&syslast;'; put 'options varlenchk=&optval;'; put '%end;'; put 'proc format; /* credit yabwon for special null removal */'; put 'value bart (default=40)'; put '%if &missing=NULL %then %do;'; put '._ - .z = null'; put '%end;'; put '%else %do;'; put '._ = [quote()]'; put '. = null'; put '.a - .z = [quote()]'; put '%end;'; put 'other = [best.];'; put 'data &tempds;'; put 'attrib _all_ label='''';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'length &&name&i $&&fmtlen&i...;'; put 'format &&name&i $&&fmtlen&i...;'; put '%end;'; put '%end;'; put '%if &fmt=Y %then %do;'; put 'set &fmtds;'; put '%end;'; put '%else %do;'; put 'set &ds;'; put '%end;'; put '&stmt_obs;'; put 'format _numeric_ bart.;'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'if findc(&&name&i,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put '&&name&i=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,&&name&i)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else &&name&i=quote(cats(&&name&i));'; put '%end;'; put '%end;'; put 'run;'; put 'filename _sjs3 temp lrecl=131068 ;'; put 'data _null_;'; put 'file _sjs3 encoding=''utf-8'';'; put 'if _n_=1 then put "[";'; put 'set &tempds;'; put 'if _n_>1 then put "," @; put'; put '%if &action=ARR %then "[" ; %else "{" ;'; put '%do i=1 %to &numcols;'; put '%if &i>1 %then "," ;'; put '%if &action=OBJ %then """&&name&i"":" ;'; put '"&&name&i"n /* name literal for reserved variable names */'; put '%end;'; put '%if &action=ARR %then "]" ; %else "}" ; ;'; put '/* close out the table */'; put 'data _null_;'; put 'file _sjs3 mod encoding=''utf-8'';'; put 'put '']'';'; put 'run;'; put 'data _null_;'; put 'infile _sjs3 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs3 clear;'; put '%end;'; put 'proc sql;'; put 'drop table &colinfo, &tempds;'; put '%if %substr(&showmeta,1,1)=Y %then %do;'; put 'filename _sjs4 temp lrecl=131068 encoding=''utf-8'';'; put 'data _null_;'; put 'file _sjs4;'; put 'length label $350;'; put 'put ", ""$%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":{""vars"":{";'; put 'do i=1 to &numcols;'; put 'name=quote(trim(symget(cats(''name'',i))));'; put 'format=quote(trim(symget(cats(''fmt'',i))));'; put 'label=quote(prxchange(''s/\\/\\\\/'',-1,trim(symget(cats(''label'',i)))));'; put 'length=quote(trim(symget(cats(''length'',i))));'; put 'type=quote(trim(symget(cats(''typelong'',i))));'; put 'if i>1 then put "," @@;'; put 'put name '':{"format":'' format '',"label":'' label'; put ''',"length":'' length '',"type":'' type ''}'';'; put 'end;'; put 'put ''}}'';'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs4 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs4 clear;'; put '%end;'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put 'data _null_; file &jref encoding=''utf-8'' mod ;'; put 'put "}";'; put 'run;'; put '%end;'; put '%mend mp_jsonout;'; put '/**'; put '@file mm_webout.sas'; put '@brief Send data to/from SAS Stored Processes'; put '@details This macro should be added to the start of each Stored Process,'; put '**immediately** followed by a call to:'; put '%mm_webout(FETCH)'; put 'This will read all the input data and create same-named SAS datasets in the'; put 'WORK library. You can then insert your code, and send data back using the'; put 'following syntax:'; put 'data some datasets; * make some data ;'; put 'retain some columns;'; put 'run;'; put '%mm_webout(OPEN)'; put '%mm_webout(ARR,some) * Array format, fast, suitable for large tables ;'; put '%mm_webout(OBJ,datasets) * Object format, easier to work with ;'; put 'Finally, wrap everything up send some helpful system variables too'; put '%mm_webout(CLOSE)'; put '@param [in] action Either FETCH, OPEN, ARR, OBJ or CLOSE'; put '@param [in] ds The dataset to send back to the frontend'; put '@param [out] dslabel= Value to use instead of table name for sending to JSON'; put '@param [in] fmt= (N) Setting Y converts all vars to their formatted values'; put '@param [out] fref= (_webout) The fileref to which to write the JSON'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] workobs= (0) When set to a positive integer, will create a new'; put 'output object (WORK) which contains this number of observations from all'; put 'tables in the WORK library.'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to output JSON'; put '

SAS Macros

'; put '@li mp_jsonout.sas'; put '

Related Macros

'; put '@li ms_webout.sas'; put '@li mv_webout.sas'; put '@version 9.3'; put '@author Allan Bowe'; put '**/'; put '%macro mm_webout(action,ds,dslabel=,fref=_webout,fmt=N,missing=NULL'; put ',showmeta=N,maxobs=MAX,workobs=0'; put ');'; put '%global _webin_file_count _webin_fileref1 _webin_name1 _program _debug'; put 'sasjs_tables;'; put '%local i tempds jsonengine;'; put '/* see https://github.com/sasjs/core/issues/41 */'; put '%if "%upcase(&SYSENCODING)" ne "UTF-8" %then %let jsonengine=PROCJSON;'; put '%else %let jsonengine=DATASTEP;'; put '%if &action=FETCH %then %do;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'options mprint notes mprintnest;'; put '%end;'; put '%let _webin_file_count=%eval(&_webin_file_count+0);'; put '/* now read in the data */'; put '%do i=1 %to &_webin_file_count;'; put '%if &_webin_file_count=1 %then %do;'; put '%let _webin_fileref1=&_webin_fileref;'; put '%let _webin_name1=&_webin_name;'; put '%end;'; put 'data _null_;'; put 'infile &&_webin_fileref&i termstr=crlf;'; put 'input;'; put 'call symputx(''input_statement'',_infile_);'; put 'putlog "&&_webin_name&i input statement: " _infile_;'; put 'stop;'; put 'data &&_webin_name&i;'; put 'infile &&_webin_fileref&i firstobs=2 dsd termstr=crlf encoding=''utf-8'';'; put 'input &input_statement;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'if _n_<20 then putlog _infile_;'; put '%end;'; put 'run;'; put '%let sasjs_tables=&sasjs_tables &&_webin_name&i;'; put '%end;'; put '%end;'; put '%else %if &action=OPEN %then %do;'; put '/* fix encoding */'; put 'OPTIONS NOBOMFILE;'; put '/**'; put '* check xengine type to avoid the below err message:'; put '* > Function is only valid for filerefs using the CACHE access method.'; put '*/'; put 'data _null_;'; put 'set sashelp.vextfl(where=(fileref="_WEBOUT"));'; put 'if xengine=''STREAM'' then do;'; put 'rc=stpsrv_header(''Content-type'',"text/html; encoding=utf-8");'; put 'end;'; put 'run;'; put '/* setup json */'; put 'data _null_;file &fref encoding=''utf-8'';'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutBEGIN<<'';'; put '%end;'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'run;'; put '%end;'; put '%else %if &action=ARR or &action=OBJ %then %do;'; put '%mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt,jref=&fref'; put ',engine=&jsonengine,missing=&missing,showmeta=&showmeta,maxobs=&maxobs'; put ')'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put '/* To avoid issues with _webout on EBI we use a temporary file */'; put 'filename _sjsref temp lrecl=131068;'; put '%if %str(&workobs) > 0 %then %do;'; put '/* if debug mode, send back first XX records of each work table also */'; put 'data;run;%let tempds=%scan(&syslast,2,.);'; put 'ods output Members=&tempds;'; put 'proc datasets library=WORK memtype=data;'; put '%local wtcnt;%let wtcnt=0;'; put 'data _null_;'; put 'set &tempds;'; put 'if not (upcase(name) =:"DATA"); /* ignore temp datasets */'; put 'i+1;'; put 'call symputx(cats(''wt'',i),name,''l'');'; put 'call symputx(''wtcnt'',i,''l'');'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put ",""WORK"":{";'; put '%do i=1 %to &wtcnt;'; put '%let wt=&&wt&i;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'dsid=open("WORK.&wt",''is'');'; put 'nlobs=attrn(dsid,''NLOBS'');'; put 'nvars=attrn(dsid,''NVARS'');'; put 'rc=close(dsid);'; put 'if &i>1 then put '',''@;'; put 'put " ""&wt"" : {";'; put 'put ''"nlobs":'' nlobs;'; put 'put '',"nvars":'' nvars;'; put '%mp_jsonout(OBJ,&wt,jref=_sjsref,dslabel=first10rows,showmeta=Y'; put ',maxobs=&workobs'; put ')'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put '%end;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put 'run;'; put '%end;'; put '/* close off json */'; put 'data _null_;file _sjsref mod encoding=''utf-8'';'; put 'length SYSPROCESSNAME syserrortext syswarningtext autoexec $512;'; put 'put ",""_DEBUG"" : ""&_debug"" ";'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'autoexec=quote(urlencode(trim(getoption(''autoexec''))));'; put 'put '',"AUTOEXEC" : '' autoexec;'; put 'put ",""MF_GETUSER"" : ""%mf_getuser()"" ";'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'put ",""SYSENCODING"" : ""&sysencoding"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSPROCESSID"" : ""&SYSPROCESSID"" ";'; put 'put ",""SYSPROCESSMODE"" : ""&SYSPROCESSMODE"" ";'; put 'SYSPROCESSNAME=quote(urlencode(cats(SYSPROCESSNAME)));'; put 'put ",""SYSPROCESSNAME"" : " SYSPROCESSNAME;'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put '',"SYSWARNINGTEXT" : '' syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'length memsize $32;'; put 'memsize="%sysfunc(INPUTN(%sysfunc(getoption(memsize)), best.),sizekmg.)";'; put 'memsize=quote(cats(memsize));'; put 'put '',"MEMSIZE" : '' memsize;'; put 'put "}" @;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutEND<<'';'; put '%end;'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjsref lrecl=1 recfm=n;'; put 'file &fref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjsref clear;'; put '%end;'; put '%mend mm_webout;'; put '%macro webout(action,ds,dslabel=,fmt=,missing=NULL,showmeta=NO,maxobs=MAX);'; put '%mm_webout(&action,ds=&ds,dslabel=&dslabel,fmt=&fmt'; put ',missing=&missing'; put ',showmeta=&showmeta'; put ',maxobs=&maxobs'; put ') %mend;'; put '/* provide additional debug info */'; put '%global _program;'; put '%put &=syscc;'; put '%put user=%mf_getuser();'; put '%put pgm=&_program;'; put '%put timestamp=%sysfunc(datetime(),datetime19.);'; put '* Service Variables start;'; put '* Service Variables end;'; put '* SAS Macros start;'; put '%macro mf_getapploc(pgm);'; put '%if "&pgm"="" %then %do;'; put '%if %symexist(_program) %then %let pgm=&_program;'; put '%else %do;'; put '%put &sysmacroname: No value provided and no _program variable available;'; put '%return;'; put '%end;'; put '%end;'; put '%local root;'; put '/**'; put '* First check we are not in the tests/macros folder (which has no subfolders)'; put '* or specifically in the testsetup or testteardown services'; put '*/'; put '%if %index(&pgm,/tests/macros/)'; put 'or %index(&pgm,/tests/testsetup)'; put 'or %index(&pgm,/tests/testteardown)'; put '%then %do;'; put '%let root=%substr(&pgm,1,%index(&pgm,/tests)-1);'; put '&root'; put '%return;'; put '%end;'; put '/**'; put '* Next, move up two levels to avoid matches on subfolder or service name'; put '*/'; put '%let root=%substr(&pgm,1,%length(&pgm)-%length(%scan(&pgm,-1,/))-1);'; put '%let root=%substr(&root,1,%length(&root)-%length(%scan(&root,-1,/))-1);'; put '%if %index(&root,/tests/) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/tests/)-1);'; put '%end;'; put '%else %if %index(&root,/services) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/services)-1);'; put '%end;'; put '%else %if %index(&root,/jobs) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/jobs)-1);'; put '%end;'; put '%else %put &sysmacroname: Could not find an app location from &pgm;'; put '&root'; put '%mend mf_getapploc ;'; put '%macro mf_getuniquefileref(prefix=_,maxtries=1000,lrecl=32767);'; put '%local rc fname;'; put '%if &prefix=0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%end;'; put '%else %do;'; put '%local x len;'; put '%let len=%eval(8-%length(&prefix));'; put '%let x=0;'; put '%do x=0 %to &maxtries;'; put '%let fname=&prefix%substr(%sysfunc(ranuni(0)),3,&len);'; put '%if %sysfunc(fileref(&fname)) > 0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%return;'; put '%end;'; put '%end;'; put '%put unable to find available fileref after &maxtries attempts;'; put '%end;'; put '%mend mf_getuniquefileref;'; put '%macro mp_abort(mac=mp_abort.sas, type=, msg=, iftrue=%str(1=1)'; put ', errds=work.mp_abort_errds'; put ', mode=REGULAR'; put ')/*/STORE SOURCE*/;'; put '%global sysprocessmode sysprocessname sasjs_stpsrv_header_loc sasjsprocessmode;'; put '%local fref fid i;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%put NOTE: /// mp_abort macro executing //;'; put '%if %length(&mac)>0 %then %put NOTE- called by &mac;'; put '%put NOTE - &msg;'; put '%if %symexist(_SYSINCLUDEFILEDEVICE)'; put '/* abort cancel FILE does not restart outside the INCLUDE on Viya 3.5 */'; put 'and %superq(SYSPROCESSNAME) ne %str(Compute Server)'; put '%then %do;'; put '%if "*&_SYSINCLUDEFILEDEVICE*" ne "**" %then %do;'; put 'data &errds;'; put 'iftrue=''1=1'';'; put 'length mac $100 msg $5000;'; put 'mac=symget(''mac'');'; put 'msg=symget(''msg'');'; put 'run;'; put 'data _null_;'; put 'abort cancel FILE;'; put 'run;'; put '%return;'; put '%end;'; put '%end;'; put '/* Web App Context */'; put '%if %symexist(_PROGRAM)'; put 'or %superq(SYSPROCESSNAME) = %str(Compute Server)'; put 'or &mode=INCLUDE'; put '%then %do;'; put 'options obs=max replace mprint;'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5"'; put '%then %do;'; put 'options nosyntaxcheck;'; put '%end;'; put '%if &mode=INCLUDE %then %do;'; put '%if %sysfunc(exist(&errds))=1 %then %do;'; put 'data _null_;'; put 'set &errds;'; put 'call symputx(''iftrue'',iftrue,''l'');'; put 'call symputx(''mac'',mac,''l'');'; put 'call symputx(''msg'',msg,''l'');'; put 'putlog (_all_)(=);'; put 'run;'; put '%if (&iftrue)=0 %then %return;'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: No include errors found;'; put '%return;'; put '%end;'; put '%end;'; put '/* extract log errs / warns, if exist */'; put '%local logloc logline;'; put '%global logmsg; /* capture global messages */'; put '%if %symexist(SYSPRINTTOLOG) %then %let logloc=&SYSPRINTTOLOG;'; put '%else %let logloc=%qsysfunc(getoption(LOG));'; put 'proc printto log=log;run;'; put '%let logline=0;'; put '%if %length(&logloc)>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input; putlog _infile_;'; put 'i=1;'; put 'retain logonce 0;'; put 'if ('; put '_infile_=:"%str(WARN)ING" or _infile_=:"%str(ERR)OR"'; put ') and logonce=0 then'; put 'do;'; put 'call symputx(''logline'',_n_);'; put 'logonce+1;'; put 'end;'; put 'run;'; put '/* capture log including lines BEFORE the err */'; put '%if &logline>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input;'; put 'i=1;'; put 'stoploop=0;'; put 'if _n_ ge &logline-15 and stoploop=0 then do until (i>22);'; put 'call symputx(''logmsg'',catx(''\n'',symget(''logmsg''),_infile_));'; put 'input;'; put 'i+1;'; put 'stoploop=1;'; put 'end;'; put 'if stoploop=1 then stop;'; put 'run;'; put '%end;'; put '%end;'; put '%if %symexist(SYS_JES_JOB_URI) %then %do;'; put '/* setup webout for Viya */'; put 'options nobomfile;'; put '%if "X&SYS_JES_JOB_URI.X"="XX" %then %do;'; put 'filename _webout temp lrecl=999999 mod;'; put '%end;'; put '%else %do;'; put 'filename _webout filesrvc parenturi="&SYS_JES_JOB_URI"'; put 'name="_webout.json" lrecl=999999 mod;'; put '%end;'; put '%end;'; put '%else %if %sysfunc(filename(fref,&sasjs_stpsrv_header_loc))=0 %then %do;'; put 'options nobomfile;'; put '/* set up http header for SASjs Server */'; put '%let fid=%sysfunc(fopen(&fref,A));'; put '%if &fid=0 %then %do;'; put '%put %str(ERR)OR: %sysfunc(sysmsg());'; put '%return;'; put '%end;'; put '%let rc=%sysfunc(fput(&fid,%str(Content-Type: application/json)));'; put '%let rc=%sysfunc(fwrite(&fid));'; put '%let rc=%sysfunc(fclose(&fid));'; put '%let rc=%sysfunc(filename(&fref));'; put '%end;'; put '/* send response in SASjs JSON format */'; put 'data _null_;'; put 'file _webout mod lrecl=32000 encoding=''utf-8'';'; put 'length msg syswarningtext syserrortext $32767 mode $10 ;'; put 'sasdatetime=datetime();'; put 'msg=symget(''msg'');'; put '%if &logline>0 %then %do;'; put 'msg=cats(msg,''\n\nLog Extract:\n'',symget(''logmsg''));'; put '%end;'; put '/* escape the escapes */'; put 'msg=tranwrd(msg,''\'',''\\'');'; put '/* escape the quotes */'; put 'msg=tranwrd(msg,''"'',''\"'');'; put '/* ditch the CRLFs as chrome complains */'; put 'msg=compress(msg,,''kw'');'; put '/* quote without quoting the quotes (which are escaped instead) */'; put 'msg=cats(''"'',msg,''"'');'; put 'if symexist(''_debug'') then debug=quote(trim(symget(''_debug'')));'; put 'else debug=''""'';'; put 'if symget(''sasjsprocessmode'')=''Stored Program'' then mode=''SASJS'';'; put 'if mode ne ''SASJS'' then put ''>>weboutBEGIN<<'';'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'put '',"sasjsAbort" : [{'';'; put 'put '' "MSG":'' msg ;'; put 'put '' ,"MAC": "'' "&mac" ''"}]'';'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'put '',"_DEBUG":'' debug ;'; put 'if symexist(''_metauser'') then do;'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put 'end;'; put 'if symexist(''SYS_JES_JOB_URI'') then do;'; put 'SYS_JES_JOB_URI=quote(trim(symget(''SYS_JES_JOB_URI'')));'; put 'put '',"SYS_JES_JOB_URI": '' SYS_JES_JOB_URI;'; put 'end;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put ",""SYSWARNINGTEXT"" : " syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'put "}" ;'; put 'if mode ne ''SASJS'' then put ''>>weboutEND<<'';'; put 'run;'; put '%put _all_;'; put '%if "&sysprocessmode " = "SAS Stored Process Server " %then %do;'; put 'data _null_;'; put 'putlog ''stpsrvset program err and syscc'';'; put 'rc=stpsrvset(''program error'', 0);'; put 'call symputx("syscc",0,"g");'; put 'run;'; put '%if &sysscp=WIN'; put 'and 1=0 /* deprecating this logic until we figure out a consistent abort */'; put 'and "%substr(%str(&sysvlong ),1,8)"="9.04.01M"'; put 'and "%substr(%str(&sysvlong ),9,1)">"5" %then %do;'; put '/* skip approach (below) does not work in windows m6+ envs */'; put 'endsas;'; put '%end;'; put '%else %do;'; put '/**'; put '* endsas kills 9.4m3 deployments by orphaning multibridges.'; put '* Abort variants are ungraceful (non zero return code)'; put '* This approach lets SAS run silently until the end :-)'; put '* Caution - fails when called within a %include within a macro'; put '* Use mp_include() to handle this.'; put '*/'; put 'filename skip temp;'; put 'data _null_;'; put 'file skip;'; put 'put ''%macro skip();'';'; put 'comment ''%mend skip; -> fix lint '';'; put 'put ''%macro skippy();'';'; put 'comment ''%mend skippy; -> fix lint '';'; put 'run;'; put '%inc skip;'; put '%end;'; put '%end;'; put '%else %if "&sysprocessmode " = "SAS Compute Server " %then %do;'; put '/* endsas kills the session making it harder to fetch results */'; put 'data _null_;'; put 'syswarningtext=symget(''syswarningtext'');'; put 'syserrortext=symget(''syserrortext'');'; put 'abort_msg=symget(''msg'');'; put 'syscc=symget(''syscc'');'; put 'sysuserid=symget(''sysuserid'');'; put 'iftrue=symget(''iftrue'');'; put 'put (_all_)(/=);'; put 'call symputx(''syscc'',0);'; put 'abort cancel nolist;'; put 'run;'; put '%end;'; put '%else %do;'; put '%abort cancel;'; put '%end;'; put '%end;'; put '%else %do;'; put '%put _all_;'; put '%abort cancel;'; put '%end;'; put '%mend mp_abort;'; put '/** @endcond */'; put '%macro mm_getstpcode('; put 'tree=/User Folders/sasdemo/somestp'; put ',name='; put ',outloc=0'; put ',outref=0'; put ',mDebug=1'; put ',showlog=NO'; put ');'; put '%local mD;'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing &sysmacroname..sas;'; put '%&mD.put _local_;'; put '%if %length(&name)>0 %then %let name=/&name;'; put '/* first, check if STP exists */'; put '%local tsuri;'; put '%let tsuri=stopifempty ;'; put 'data _null_;'; put 'format type uri tsuri value $200.;'; put 'call missing (of _all_);'; put 'path="&tree&name(StoredProcess)";'; put '/* first, find the STP ID */'; put 'if metadata_pathobj("",path,"StoredProcess",type,uri)>0 then do;'; put '/* get sourcecode */'; put 'cnt=1;'; put 'do while (metadata_getnasn(uri,"Notes",cnt,tsuri)>0);'; put 'rc=metadata_getattr(tsuri,"Name",value);'; put '&mD.put tsuri= value=;'; put 'if value="SourceCode" then do;'; put '/* found it! */'; put 'rc=metadata_getattr(tsuri,"Id",value);'; put 'call symputx(''tsuri'',value,''l'');'; put 'stop;'; put 'end;'; put 'cnt+1;'; put 'end;'; put 'end;'; put 'else put (_all_)(=);'; put 'run;'; put '%mp_abort(iftrue= (&tsuri=stopifempty)'; put ',mac=mm_getstpcode'; put ',msg=%str(&tree&name.(StoredProcess) not found!)'; put ')'; put '/**'; put '* Now we can extract the textstore'; put '*/'; put 'filename __getdoc temp lrecl=10000000;'; put 'proc metadata'; put 'in="$METAREPOSITORY'; put ''; put 'SAS1"'; put 'out=__getdoc ;'; put 'run;'; put '/* find the beginning of the text */'; put '%local start;'; put 'data _null_;'; put 'infile __getdoc lrecl=10000;'; put 'input;'; put 'start=index(_infile_,''StoredText="'');'; put 'if start then do;'; put 'call symputx("start",start+11);'; put '*putlog ''"'' _infile_ ''"'';'; put 'end;'; put 'stop;'; put '%local outeng;'; put '%if "&outloc"="0" %then %let outeng=TEMP;'; put '%else %let outeng="&outloc";'; put '%local fref;'; put '%if &outref=0 %then %let fref=%mf_getuniquefileref();'; put '%else %let fref=&outref;'; put '/* read the content, byte by byte, resolving escaped chars */'; put 'filename &fref &outeng lrecl=100000;'; put 'data _null_;'; put 'length filein 8 fileid 8;'; put 'filein = fopen("__getdoc","I",1,"B");'; put 'fileid = fopen("&fref","O",1,"B");'; put 'rec = "20"x;'; put 'length entity $6;'; put 'do while(fread(filein)=0);'; put 'x+1;'; put 'if x>&start then do;'; put 'rc = fget(filein,rec,1);'; put 'if rec=''"'' then leave;'; put 'else if rec="&" then do;'; put 'entity=rec;'; put 'do until (rec=";");'; put 'if fread(filein) ne 0 then goto getout;'; put 'rc = fget(filein,rec,1);'; put 'entity=cats(entity,rec);'; put 'end;'; put 'select (entity);'; put 'when (''&'' ) rec=''&'' ;'; put 'when (''<'' ) rec=''<'' ;'; put 'when (''>'' ) rec=''>'' ;'; put 'when (''''') rec="''" ;'; put 'when (''"'') rec=''"'' ;'; put 'when ('' '') rec=''0A''x;'; put 'when ('' '') rec=''0D''x;'; put 'when (''$'' ) rec=''$'' ;'; put 'when ('' '') rec=''09''x;'; put 'otherwise putlog "%str(WARN)ING: missing value for " entity=;'; put 'end;'; put 'rc =fput(fileid, substr(rec,1,1));'; put 'rc =fwrite(fileid);'; put 'end;'; put 'else do;'; put 'rc =fput(fileid,rec);'; put 'rc =fwrite(fileid);'; put 'end;'; put 'end;'; put 'end;'; put 'getout:'; put 'rc=fclose(filein);'; put 'rc=fclose(fileid);'; put 'run;'; put '%if &showlog=YES %then %do;'; put 'data _null_;'; put 'infile &fref lrecl=32767 end=last;'; put 'input;'; put 'if _n_=1 then putlog ''>>stpcodeBEGIN<<'';'; put 'putlog _infile_;'; put 'if last then putlog ''>>stpcodeEND<<'';'; put 'run;'; put '%end;'; put 'filename __getdoc clear;'; put '%if &outref=0 %then %do;'; put 'filename &fref clear;'; put '%end;'; put '%mend mm_getstpcode;'; put '%macro dc_getsettings();'; put '%global _program;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(syscc=&syscc on entry (&syswarningtext &syserrortext))'; put ')'; put '%if %length(&_PROGRAM)>1 %then %let root=&_program;'; put '%else %do;'; put '%global _metauser;'; put '%let _metauser=&sysuserid;'; put '/* to mimic a "real" _program we need to give a dummy role and stp name */'; put '%let root=/dummyRole/dummyName;'; put '%end;'; put '/* the DC precode is stored in the Admin folder in the root of'; put 'the project. Lets find that root. */'; put '%put &=root;'; put '%let root=%mf_getapploc();'; put '%put &=root;'; put '/* Now we know the root location we can retrieve the params */'; put '%let temploc=%sysfunc(pathname(work))/settings.txt;'; put '%mm_getstpcode(tree=&root/services/public'; put ',name=Data_Controller_Settings'; put ',outloc=&temploc'; put ')'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Unable to run getstpcode)'; put ')'; put 'filename _getsets "&temploc" lrecl=2000;'; put '/*'; put 'Do not use mp_include here - this puts a copy in every service, which creates'; put 'compilation problems when calling services from mp_include'; put '*/'; put '%inc _getsets/source2;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Problem running &sysmacroname (&syswarningtext &syserrortext))'; put ')'; put '%mend dc_getsettings;'; put '%macro mf_fmtdttm('; put ')/*/STORE SOURCE*/;'; put '%if "&sysver"="9.2" or "&sysver"="9.3"'; put 'or ("&sysver"="9.4" and "%substr(&SYSVLONG,9,1)" le "3")'; put 'or "%substr(&sysver,1,1)"="4"'; put 'or "%substr(&sysver,1,1)"="5"'; put '%then %do;DATETIME19.3%end;'; put '%else %do;E8601DT26.6%end;'; put '%mend mf_fmtdttm;'; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '%macro mp_init(prefix=SASJS'; put ')/*/STORE SOURCE*/;'; put '%if %symexist(SASJS_PREFIX) %then %return; /* only run once */'; put '%global'; put 'SASJS_PREFIX /* the ONLY hard-coded global macro variable in SASjs */'; put '&prefix._FUNCTIONS /* used in mcf_init() to track core function compilation */'; put '&prefix._INIT_NUM /* initialisation time as numeric */'; put '&prefix._INIT_DTTM /* initialisation time in E8601DT26.6 format */'; put '&prefix.WORK /* avoid typing %sysfunc(pathname(work)) every time */'; put ';'; put '%let sasjs_prefix=&prefix;'; put 'data _null_;'; put 'dttm=datetime();'; put 'call symputx("&sasjs_prefix._init_num",dttm,''g'');'; put 'call symputx("&sasjs_prefix._init_dttm",put(dttm,E8601DT26.6),''g'');'; put 'call symputx("&sasjs_prefix.work",pathname(''WORK''),''g'');'; put 'run;'; put 'options'; put 'compress=CHAR /* default is none so ensure we have something! */'; put 'datastmtchk=ALLKEYWORDS /* protection from overwriting input datasets */'; put 'errorcheck=STRICT /* catch errs in libname/filename statements */'; put 'fmterr /* ensure err when a format cannot be found */'; put 'mergenoby=%str(ERR)OR /* throw err when a merge has no BY variables */'; put 'missing=. /* changing this can cause hard to detect errs */'; put 'noquotelenmax /* avoid warnings for long strings */'; put 'noreplace /* avoid overwriting permanent datasets */'; put 'ps=max /* reduce log size slightly */'; put 'ls=max /* reduce log even more and avoid word truncation */'; put 'validmemname=COMPATIBLE /* avoid special characters etc in table names */'; put 'validvarname=V7 /* avoid special characters etc in variable names */'; put 'varinitchk=%str(ERR)OR /* avoid data mistakes from variable name typos */'; put 'varlenchk=%str(ERR)OR /* fail hard if truncation (data loss) can result */'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5" %then %do;'; put 'noautocorrect /* disallow misspelled procedure names */'; put 'dsoptions=note2err /* undocumented - convert bad NOTEs to ERRs */'; put '%end;'; put ';'; put '%mend mp_init;'; put '%macro mpeinit(fetch=YES);'; put '%global mpeinit'; put 'mpeadmins /* group with unrestricted Meditor access */'; put 'mpelocapprovals /* location for landing and staging files */'; put 'mpelib /* location of configuration tables for DC */'; put 'dc_repo_users /* location of user / group metadata */'; put 'dc_licence_key /* extracted in dc_getsettings */'; put 'dc_activation_key /* extracted in dc_getsettings */'; put 'dc_locale /* extracted in dc_getsettings */'; put 'dc_dttmtfmt /* can be overridden in dc_getsettings */'; put '_debug'; put ';'; put '%if &mpeinit=1 %then %return;'; put '%else %let mpeinit=1;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(Problem on service startup (&syswarningtext &syserrortext))'; put ')'; put '%mp_init()'; put '%if &fetch=YES %then %do;'; put '%webout(FETCH)'; put '%end;'; put '%global _CLIENTNAME;'; put '%mp_abort(iftrue= (&_CLIENTNAME=SAS Enterprise Guide)'; put ',mac=&_program..sas'; put ',msg=%str(Data Controller is a web app and should not be executed from EG)'; put ')'; put 'options urlencoding=utf8 nobomfile lrecl=32767;'; put '%let perf=%sysfunc(datetime());'; put '%put perfdiff: 0;'; put '%let dc_locale=SYSTEM; /* default if not set */'; put '/**'; put '* E8601DT26.6 has widest database support - but not all SAS flavours can'; put '* handle it. Override in the settings STP if needed.'; put '*/'; put 'data _null_;'; put 'dc_dttmtfmt=''"%sysfunc(datetime(),''!!"%mf_fmtdttm()"!!'')"dt'';'; put 'call symputx(''dc_dttmtfmt'',dc_dttmtfmt);'; put 'put dc_dttmtfmt=;'; put 'run;'; put '%put &=dc_dttmtfmt;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc prior to dc_getsettings)'; put ')'; put '%dc_getsettings()'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc after dc_getsettings)'; put ')'; put 'data _null_;'; put 'set &DC_LIBREF..mpe_config(where=('; put 'var_scope="DC"'; put 'and &dc_dttmtfmt lt tx_to'; put 'and var_active=1'; put '));'; put 'call symputx(var_name,var_value,''G'');'; put 'putlog var_name "=" var_value;'; put 'run;'; put '%let mpelib=&dc_libref;'; put '%let mpeadmins=&dc_admin_group;'; put '%let mpelocapprovals=&dc_staging_area;'; put '%let dc_repo_users=&dc_repo_users;'; put '%if &dc_locale ne SYSTEM %then %do;'; put 'options locale=&dc_locale;'; put '%end;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(Problem during compilation or with STP precode (&syswarningtext))'; put ')'; put '%mend mpeinit;'; put '%macro mf_mval(var);'; put '%if %symexist(&var) %then %do;'; put '%superq(&var)'; put '%end;'; put '%mend mf_mval;'; put '%macro mf_trimstr(basestr,trimstr);'; put '%local baselen trimlen trimval;'; put '/* return if basestr is shorter than trimstr (or 0) */'; put '%let baselen=%length(%superq(basestr));'; put '%let trimlen=%length(%superq(trimstr));'; put '%if &baselen < &trimlen or &baselen=0 %then %return;'; put '/* obtain the characters from the end of basestr */'; put '%let trimval=%qsubstr(%superq(basestr)'; put ',%length(%superq(basestr))-&trimlen+1'; put ',&trimlen);'; put '/* compare and if matching, chop it off! */'; put '%if %superq(basestr)=%superq(trimstr) %then %do;'; put '%return;'; put '%end;'; put '%else %if %superq(trimval)=%superq(trimstr) %then %do;'; put '%qsubstr(%superq(basestr),1,%length(%superq(basestr))-&trimlen)'; put '%end;'; put '%else %do;'; put '&basestr'; put '%end;'; put '%mend mf_trimstr;'; put '%macro mf_getplatform(switch'; put ')/*/STORE SOURCE*/;'; put '%local a b c;'; put '%if &switch.NONE=NONE %then %do;'; put '%if %symexist(sasjsprocessmode) %then %do;'; put '%if &sasjsprocessmode=Stored Program %then %do;'; put 'SASJS'; put '%return;'; put '%end;'; put '%end;'; put '%if %symexist(sysprocessmode) %then %do;'; put '%if "&sysprocessmode"="SAS Object Server"'; put 'or "&sysprocessmode"= "SAS Compute Server" %then %do;'; put 'SASVIYA'; put '%end;'; put '%else %if "&sysprocessmode"="SAS Stored Process Server"'; put 'or "&sysprocessmode"="SAS Workspace Server"'; put '%then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if %symexist(_metaport) or %symexist(_metauser) %then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if &switch=SASSTUDIO %then %do;'; put '/* return the version of SAS Studio else 0 */'; put '%if %mf_mval(_CLIENTAPP)=%str(SAS Studio) %then %do;'; put '%let a=%mf_mval(_CLIENTVERSION);'; put '%let b=%scan(&a,1,.);'; put '%if %eval(&b >2) %then %do;'; put '&b'; put '%end;'; put '%else 0;'; put '%end;'; put '%else 0;'; put '%end;'; put '%else %if &switch=VIYARESTAPI %then %do;'; put '%mf_trimstr(%sysfunc(getoption(servicesbaseurl)),/)'; put '%end;'; put '%mend mf_getplatform;'; put '%macro mpeterm();'; put '%local oldloc;'; put 'data _null_;'; put 'if symexist(''SYSPRINTTOLOG'') then oldloc=symget(''SYSPRINTTOLOG'');'; put 'else oldloc=getoption(''LOG'');'; put 'if subpad(oldloc,1,1) not in (''"'',"''",'' '') then oldloc=quote(cats(oldloc));'; put 'call symputx(''oldloc'',oldloc,''l'');'; put 'run;'; put '%if %length(&oldloc)>0 %then %do;'; put 'proc printto log=log;'; put 'run;'; put 'data _null_;'; put 'infile &oldloc;'; put 'input; putlog _infile_;'; put 'run;'; put '%end;'; put '%if %sysfunc(exist(&dc_libref..mpe_requests)) and %mf_getplatform() ne SASVIYA'; put '%then %do;'; put 'data ;'; put 'if 0 then set &dc_libref..mpe_requests;'; put 'request_dttm=%sysfunc(datetime());'; put 'request_user="%mf_getuser()";'; put 'request_service="%scan(&_program,-2,/)/%scan(&_program,-1,/)";'; put 'request_params='''';'; put 'output;stop;'; put 'proc append base=&dc_libref..mpe_requests data=&syslast force nowarn;'; put 'run;'; put '%end;'; put '%mend mpeterm;'; put '%macro mm_getGroups('; put 'user='; put ',outds=work.mm_getGroups'; put ',repo=foundation'; put ',mDebug=0'; put ')/*/STORE SOURCE*/;'; put '%local mD oldrepo;'; put '%let oldrepo=%sysfunc(getoption(metarepository));'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing mm_getGroups.sas;'; put '%&mD.put _local_;'; put '/* on some sites, user / group info is in a different metadata repo to the'; put 'default */'; put '%if &oldrepo ne &repo %then %do;'; put 'options metarepository=&repo;'; put '%end;'; put '%if %length(&user)=0 %then %do;'; put 'data &outds (keep=groupuri groupname groupdesc);'; put 'length groupuri groupname groupdesc group_or_role $256;'; put 'call missing(of _all_);'; put 'i+1;'; put 'do while'; put '(metadata_getnobj("omsobj:IdentityGroup?@Id contains ''.''",i,groupuri)>0);'; put 'rc=metadata_getattr(groupuri, "Name", groupname);'; put 'rc=metadata_getattr(groupuri, "Desc", groupdesc);'; put 'rc=metadata_getattr(groupuri,"PublicType",group_or_role);'; put 'if Group_or_Role = ''UserGroup'' then output;'; put 'i+1;'; put 'end;'; put 'run;'; put '%end;'; put '%else %do;'; put 'data &outds (keep=groupuri groupname groupdesc);'; put 'length uri groupuri groupname groupdesc group_or_role $256;'; put 'call missing(of _all_);'; put 'rc=metadata_getnobj("omsobj:Person?@Name=''&user''",1,uri);'; put 'if rc<=0 then do;'; put 'putlog "%str(WARN)ING: rc=" rc "&user not found "'; put '", or there was an issue reading the repository.";'; put 'stop;'; put 'end;'; put 'a=1;'; put 'grpassn=metadata_getnasn(uri,"IdentityGroups",a,groupuri);'; put 'if grpassn in (-3,-4) then do;'; put 'putlog "%str(WARN)ING: No metadata groups found for &user";'; put 'output;'; put 'end;'; put 'else do while (grpassn > 0);'; put 'rc=metadata_getattr(groupuri, "Name", groupname);'; put 'rc=metadata_getattr(groupuri, "Desc", groupdesc);'; put 'a+1;'; put 'rc=metadata_getattr(groupuri,"PublicType",group_or_role);'; put 'if Group_or_Role = ''UserGroup'' then output;'; put 'grpassn=metadata_getnasn(uri,"IdentityGroups",a,groupuri);'; put 'end;'; put 'run;'; put '%end;'; put '%if &oldrepo ne &repo %then %do;'; put 'options metarepository=&oldrepo;'; put '%end;'; put '%mend mm_getGroups;'; put '%macro dc_getusergroups(user=,outds=mm_getgroups);'; put '%global dc_repo_users;'; put '%if &dc_repo_users= %then %let dc_repo_users=foundation;'; put '%mm_getgroups(user=&user,outds=&outds,repo=&dc_repo_users)'; put '%mend dc_getusergroups;'; put '%macro mpe_getgroups(user=,outds=);'; put '%if not %symexist(dc_repo_users) %then %let dc_repo_users=foundation;'; put '%dc_getusergroups(user=&user,outds=&outds)'; put 'data;'; put 'length groupname groupdesc $256;'; put 'set &dc_libref..mpe_groups;'; put 'where &dc_dttmtfmt. lt tx_to;'; put 'where also upcase(user_name)="%upcase(&user)";'; put 'groupname=group_name;'; put 'groupdesc=group_desc;'; put 'keep groupname groupdesc;'; put 'run;'; put 'data &outds;'; put 'set &syslast &outds(keep=groupname groupdesc);'; put 'run;'; put '%mend mpe_getgroups;'; put '%macro mpe_getvars(injs,outds);'; put '/* load parameters */'; put 'data _null_;'; put '__dummychar='''';__dummynum=0;'; put 'set &outds;'; put 'array __charvals _character_;'; put 'do over __charvals;'; put 'call symputx(vname(__charvals),__charvals,''g'');'; put 'end;'; put 'array __numvals _numeric_;'; put 'do over __numvals;'; put 'call symputx(vname(__numvals),__numvals,''g'');'; put 'end;'; put 'run;'; put '%mend mpe_getvars;'; put '%macro mm_assignlib('; put 'libref'; put ',mAbort=HARD'; put ')/*/STORE SOURCE*/;'; put '%local mp_abort msg;'; put '%let mp_abort=0;'; put '%if %sysfunc(libref(&libref)) %then %do;'; put 'data _null_;'; put 'length liburi LibName msg $200;'; put 'call missing(of _all_);'; put 'nobj=metadata_getnobj("omsobj:SASLibrary?@Libref=''&libref''",1,liburi);'; put 'if nobj=1 then do;'; put 'rc=metadata_getattr(liburi,"Name",LibName);'; put '/* now try and assign it */'; put 'if libname("&libref",,''meta'',cats(''liburi="'',liburi,''";'')) ne 0 then do;'; put 'putlog "&libref could not be assigned";'; put 'putlog liburi=;'; put '/**'; put '* Fetch the system message for display in the abort modal. This is'; put '* not always helpful though. One example, previously received:'; put '* NOTE: Libref XX refers to the same library metadata as libref XX.'; put '*/'; put 'msg=sysmsg();'; put 'if msg=:''ERROR: Libref SAVE is not assigned.'' then do;'; put 'msg=catx(" ",'; put '"Could not assign %upcase(&libref).",'; put '"Please check metadata permissions! Libname:",libname,'; put '"Liburi:",liburi'; put ');'; put 'end;'; put 'else if msg="ERROR: User does not have appropriate authorization "!!'; put '"level for library SAVE."'; put 'then do;'; put 'msg=catx(" ",'; put '"ERROR: User does not have appropriate authorization level",'; put '"for library %upcase(&libref), libname:",libname,'; put '"Liburi:",liburi'; put ');'; put 'end;'; put 'call symputx(''msg'',msg,''l'');'; put 'if "&mabort"=''HARD'' then call symputx(''mp_abort'',1,''l'');'; put 'end;'; put 'else do;'; put 'put (_all_)(=);'; put 'call symputx(''libname'',libname,''L'');'; put 'call symputx(''liburi'',liburi,''L'');'; put 'end;'; put 'end;'; put 'else if nobj>1 then do;'; put 'if "&mabort"=''HARD'' then call symputx(''mp_abort'',1);'; put 'call symputx(''msg'',"More than one library with libref=&libref");'; put 'end;'; put 'else do;'; put 'if "&mabort"=''HARD'' then call symputx(''mp_abort'',1);'; put 'call symputx(''msg'',"Library &libref not found in metadata");'; put 'end;'; put 'run;'; put '%put NOTE: &msg;'; put '%end;'; put '%else %do;'; put '%put NOTE: Library &libref is already assigned;'; put '%end;'; put '%mp_abort(iftrue= (&mp_abort=1)'; put ',mac=mm_assignlib.sas'; put ',msg=%superq(msg)'; put ')'; put '%mend mm_assignlib;'; put '/** @cond */'; put '%macro mf_getengine(libref'; put ')/*/STORE SOURCE*/;'; put '%local dsid engnum rc engine;'; put '/* in case the parameter is a libref.tablename, pull off just the libref */'; put '%let libref = %upcase(%scan(&libref, 1, %str(.)));'; put '%let dsid=%sysfunc('; put 'open(sashelp.vlibnam(where=(libname="%upcase(&libref)")),i)'; put ');'; put '%if (&dsid ^= 0) %then %do;'; put '%let engnum=%sysfunc(varnum(&dsid,ENGINE));'; put '%let rc=%sysfunc(fetch(&dsid));'; put '%let engine=%sysfunc(getvarc(&dsid,&engnum));'; put '%put &libref. ENGINE is &engine.;'; put '%let rc= %sysfunc(close(&dsid));'; put '%end;'; put '%upcase(&engine)'; put '%mend mf_getengine;'; put '/** @endcond */'; put '%macro mm_assigndirectlib('; put 'libref'; put ',open_passthrough='; put ',sql_options='; put ',mDebug=0'; put ',mAbort=0'; put ')/*/STORE SOURCE*/;'; put '%local mD;'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing mm_assigndirectlib.sas;'; put '%&mD.put _local_;'; put '%if &mAbort=1 %then %let mAbort=;'; put '%else %let mAbort=%str(*);'; put '%&mD.put NOTE: Creating direct (non META) connection to &libref library;'; put '%local cur_engine;'; put '%let cur_engine=%mf_getengine(&libref);'; put '%if &cur_engine ne META and &cur_engine ne %then %do;'; put '%put NOTE: &libref already has a direct (&cur_engine) libname connection;'; put '%return;'; put '%end;'; put '%else %if %upcase(&libref)=WORK %then %do;'; put '%put NOTE: We already have a direct connection to WORK :-) ;'; put '%return;'; put '%end;'; put '/* need to determine the library ENGINE first */'; put '%local engine;'; put 'data _null_;'; put 'length lib_uri engine $256;'; put 'call missing (of _all_);'; put '/* get URI for the particular library */'; put 'rc1=metadata_getnobj("omsobj:SASLibrary?@Libref =''&libref''",1,lib_uri);'; put '/* get the Engine attribute of the previous object */'; put 'rc2=metadata_getattr(lib_uri,''Engine'',engine);'; put 'putlog "mm_assigndirectlib for &libref:" rc1= lib_uri= rc2= engine=;'; put 'call symputx("liburi",lib_uri,''l'');'; put 'call symputx("engine",engine,''l'');'; put 'run;'; put '/* now obtain engine specific connection details */'; put '%if &engine=BASE %then %do;'; put '%&mD.put NOTE: Retrieving BASE library path;'; put 'data _null_;'; put 'length up_uri $256 path cat_path $1024;'; put 'retain cat_path;'; put 'call missing (of _all_);'; put '/* get all the filepaths of the UsingPackages association */'; put 'i=1;'; put 'rc3=metadata_getnasn("&liburi",''UsingPackages'',i,up_uri);'; put 'do while (rc3>0);'; put '/* get the DirectoryName attribute of the previous object */'; put 'rc4=metadata_getattr(up_uri,''DirectoryName'',path);'; put 'if i=1 then path = ''("''!!trim(path)!!''" '';'; put 'else path ='' "''!!trim(path)!!''" '';'; put 'cat_path = trim(cat_path) !! " " !! trim(path) ;'; put 'i+1;'; put 'rc3=metadata_getnasn("&liburi",''UsingPackages'',i,up_uri);'; put 'end;'; put 'cat_path = trim(cat_path) !! ")";'; put '&mD.putlog "NOTE: Getting physical path for &libref library";'; put '&mD.putlog rc3= up_uri= rc4= cat_path= path=;'; put '&mD.putlog "NOTE: Libname cmd will be:";'; put '&mD.putlog "libname &libref" cat_path;'; put 'call symputx("filepath",cat_path,''l'');'; put 'run;'; put '%if %sysevalf(&sysver<9.4) %then %do;'; put 'libname &libref &filepath;'; put '%end;'; put '%else %do;'; put '/* apply the new filelocks option to cater for temporary locks */'; put 'libname &libref &filepath filelockwait=5;'; put '%end;'; put '%end;'; put '%else %if &engine=REMOTE %then %do;'; put 'data x;'; put 'length rcCon rcProp rc k 3 uriCon uriProp PropertyValue PropertyName'; put 'Delimiter $256 properties $2048;'; put 'retain properties;'; put 'rcCon = metadata_getnasn("&liburi", "LibraryConnection", 1, uriCon);'; put 'rcProp = metadata_getnasn(uriCon, "Properties", 1, uriProp);'; put 'k = 1;'; put 'rcProp = metadata_getnasn(uriCon, "Properties", k, uriProp);'; put 'do while (rcProp > 0);'; put 'rc = metadata_getattr(uriProp , "DefaultValue",PropertyValue);'; put 'rc = metadata_getattr(uriProp , "PropertyName",PropertyName);'; put 'rc = metadata_getattr(uriProp , "Delimiter",Delimiter);'; put 'properties = trim(properties) !! " " !! trim(PropertyName)'; put '!! trim(Delimiter) !! trim(PropertyValue);'; put 'output;'; put 'k+1;'; put 'rcProp = metadata_getnasn(uriCon, "Properties", k, uriProp);'; put 'end;'; put '%&mD.put NOTE: Getting properties for REMOTE SHARE &libref library;'; put '&mD.put _all_;'; put '%&mD.put NOTE: Libname cmd will be:;'; put '%&mD.put libname &libref &engine &properties slibref=&libref;'; put 'call symputx ("properties",trim(properties),''l'');'; put 'run;'; put 'libname &libref &engine &properties slibref=&libref;'; put '%end;'; put '%else %if &engine=OLEDB %then %do;'; put '%&mD.put NOTE: Retrieving OLEDB connection details;'; put 'data _null_;'; put 'length domain datasource provider properties schema'; put 'connx_uri domain_uri conprop_uri lib_uri schema_uri value $256.;'; put 'call missing (of _all_);'; put '/* get source connection ID */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,connx_uri);'; put '/* get connection domain */'; put 'rc1=metadata_getnasn(connx_uri,''Domain'',1,domain_uri);'; put 'rc2=metadata_getattr(domain_uri,''Name'',domain);'; put '&mD.putlog / ''NOTE: '' // ''NOTE- connection id: '' connx_uri ;'; put '&mD.putlog ''NOTE- domain: '' domain;'; put '/* get DSN and PROVIDER from connection properties */'; put 'i=0;'; put 'do until (rc<0);'; put 'i+1;'; put 'rc=metadata_getnasn(connx_uri,''Properties'',i,conprop_uri);'; put 'rc2=metadata_getattr(conprop_uri,''Name'',value);'; put 'if value=''Connection.OLE.Property.DATASOURCE.Name.xmlKey.txt'' then do;'; put 'rc3=metadata_getattr(conprop_uri,''DefaultValue'',datasource);'; put 'end;'; put 'else if value=''Connection.OLE.Property.PROVIDER.Name.xmlKey.txt'' then do;'; put 'rc4=metadata_getattr(conprop_uri,''DefaultValue'',provider);'; put 'end;'; put 'else if value=''Connection.OLE.Property.PROPERTIES.Name.xmlKey.txt'' then'; put 'do;'; put 'rc5=metadata_getattr(conprop_uri,''DefaultValue'',properties);'; put 'end;'; put 'end;'; put '&mD.putlog ''NOTE- dsn/provider/properties: '' /'; put 'datasource provider properties;'; put '&mD.putlog ''NOTE- schema: '' schema // ''NOTE-'';'; put '/* get SCHEMA */'; put 'rc6=metadata_getnasn("&liburi",''UsingPackages'',1,lib_uri);'; put 'rc7=metadata_getattr(lib_uri,''SchemaName'',schema);'; put 'call symputx(''SQL_domain'',domain,''l'');'; put 'call symputx(''SQL_dsn'',datasource,''l'');'; put 'call symputx(''SQL_provider'',provider,''l'');'; put 'call symputx(''SQL_properties'',properties,''l'');'; put 'call symputx(''SQL_schema'',schema,''l'');'; put 'run;'; put '%if %length(&open_passthrough)>0 %then %do;'; put 'proc sql &sql_options;'; put 'connect to OLEDB as &open_passthrough(INSERT_SQL=YES'; put '/* need additional properties to make this work */'; put 'properties=(''Integrated Security''=SSPI'; put '''Persist Security Info''=True'; put '%sysfunc(compress(%str(&SQL_properties),%str(())))'; put ')'; put 'DATASOURCE=&sql_dsn PROMPT=NO'; put 'PROVIDER=&sql_provider SCHEMA=&sql_schema CONNECTION = GLOBAL);'; put '%end;'; put '%else %do;'; put 'LIBNAME &libref OLEDB PROPERTIES=&sql_properties'; put 'DATASOURCE=&sql_dsn PROVIDER=&sql_provider SCHEMA=&sql_schema'; put '%if %length(&sql_domain)>0 %then %do;'; put 'authdomain="&sql_domain"'; put '%end;'; put 'connection=shared;'; put '%end;'; put '%end;'; put '%else %if &engine=ODBC %then %do;'; put '%&mD.put NOTE: Retrieving ODBC connection details;'; put 'data _null_;'; put 'length connx_uri conprop_uri value datasource up_uri schema domprop_uri authdomain $256.;'; put 'call missing (of _all_);'; put '/* get source connection ID */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,connx_uri);'; put '/* get connection properties */'; put 'i=0;'; put 'do until (rc2<0);'; put 'i+1;'; put 'rc2=metadata_getnasn(connx_uri,''Properties'',i,conprop_uri);'; put 'rc3=metadata_getattr(conprop_uri,''Name'',value);'; put 'if value=''Connection.ODBC.Property.DATASRC.Name.xmlKey.txt'' then do;'; put 'rc4=metadata_getattr(conprop_uri,''DefaultValue'',datasource);'; put 'rc2=-1;'; put 'end;'; put 'end;'; put '/* get auth domain */'; put 'autrc=metadata_getnasn(connx_uri,"Domain",1,domprop_uri);'; put 'arc=metadata_getattr(domprop_uri,"Name",authdomain);'; put 'if not missing(authdomain) then authdomain=cats(''AUTHDOMAIN='',authdomain);'; put 'call symputx(''authdomain'',authdomain,''l'');'; put '/* get SCHEMA */'; put 'rc6=metadata_getnasn("&liburi",''UsingPackages'',1,up_uri);'; put 'rc7=metadata_getattr(up_uri,''SchemaName'',schema);'; put '&mD.put rc= connx_uri= rc2= conprop_uri= rc3= value= rc4= datasource='; put 'rc6= up_uri= rc7= schema=;'; put 'call symputx(''SQL_schema'',schema,''l'');'; put 'call symputx(''SQL_dsn'',datasource,''l'');'; put 'run;'; put '%if %length(&open_passthrough)>0 %then %do;'; put 'proc sql &sql_options;'; put 'connect to ODBC as &open_passthrough'; put '(INSERT_SQL=YES DATASRC=&sql_dsn. CONNECTION=global);'; put '%end;'; put '%else %do;'; put 'libname &libref ODBC DATASRC=&sql_dsn SCHEMA=&sql_schema &authdomain;'; put '%end;'; put '%end;'; put '%else %if &engine=POSTGRES %then %do;'; put '%put NOTE: Obtaining POSTGRES library details;'; put 'data _null_;'; put 'length database ignore_read_only_columns direct_exe preserve_col_names'; put 'preserve_tab_names server schema authdomain user password'; put 'prop name value uri urisrc $256.;'; put 'call missing (of _all_);'; put '/* get database value */'; put 'prop=''Connection.DBMS.Property.DB.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,database,"");'; put 'if database^='''' then database=''database=''!!quote(trim(database));'; put 'call symputx(''database'',database,''l'');'; put '/* get IGNORE_READ_ONLY_COLUMNS value */'; put 'prop=''Library.DBMS.Property.DBIROC.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,ignore_read_only_columns,"");'; put 'if ignore_read_only_columns^='''' then ignore_read_only_columns='; put '''ignore_read_only_columns=''!!ignore_read_only_columns;'; put 'call symputx(''ignore_read_only_columns'',ignore_read_only_columns,''l'');'; put '/* get DIRECT_EXE value */'; put 'prop=''Library.DBMS.Property.DirectExe.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,direct_exe,"");'; put 'if direct_exe^='''' then direct_exe=''direct_exe=''!!direct_exe;'; put 'call symputx(''direct_exe'',direct_exe,''l'');'; put '/* get PRESERVE_COL_NAMES value */'; put 'prop=''Library.DBMS.Property.PreserveColNames.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,preserve_col_names,"");'; put 'if preserve_col_names^='''' then preserve_col_names='; put '''preserve_col_names=''!!preserve_col_names;'; put 'call symputx(''preserve_col_names'',preserve_col_names,''l'');'; put '/* get PRESERVE_TAB_NAMES value */'; put '/* be careful with PRESERVE_TAB_NAMES=YES - it will mean your table will'; put 'become case sensitive!! */'; put 'prop=''Library.DBMS.Property.PreserveTabNames.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,preserve_tab_names,"");'; put 'if preserve_tab_names^='''' then preserve_tab_names='; put '''preserve_tab_names=''!!preserve_tab_names;'; put 'call symputx(''preserve_tab_names'',preserve_tab_names,''l'');'; put '/* get SERVER value */'; put 'if metadata_getnasn("&liburi","LibraryConnection",1,uri)>0 then do;'; put 'prop=''Connection.DBMS.Property.SERVER.Name.xmlKey.txt'';'; put 'rc=metadata_getprop(uri,prop,server,"");'; put 'end;'; put 'if server^='''' then server=''server=''!!quote(cats(server));'; put 'call symputx(''server'',server,''l'');'; put '/* get SCHEMA value */'; put 'if metadata_getnasn("&liburi","UsingPackages",1,uri)>0 then do;'; put 'rc=metadata_getattr(uri,"SchemaName",schema);'; put 'end;'; put 'if schema^='''' then schema=''schema=''!!schema;'; put 'call symputx(''schema'',schema,''l'');'; put '/* get AUTHDOMAIN value */'; put '/* this is only useful if the user account contains that auth domain'; put 'if metadata_getnasn("&liburi","DefaultLogin",1,uri)>0 then do;'; put 'rc=metadata_getnasn(uri,"Domain",1,urisrc);'; put 'rc=metadata_getattr(urisrc,"Name",authdomain);'; put 'end;'; put 'if authdomain^='''' then authdomain=''authdomain=''!!quote(trim(authdomain));'; put '*/'; put 'call symputx(''authdomain'',authdomain,''l'');'; put '/* get user & pass */'; put 'if authdomain='''' & metadata_getnasn("&liburi","DefaultLogin",1,uri)>0 then'; put 'do;'; put 'rc=metadata_getattr(uri,"UserID",user);'; put 'rc=metadata_getattr(uri,"Password",password);'; put 'end;'; put 'if user^='''' then do;'; put 'user=''user=''!!quote(trim(user));'; put 'password=''password=''!!quote(trim(password));'; put 'end;'; put 'call symputx(''user'',user,''l'');'; put 'call symputx(''password'',password,''l'');'; put '&md.put _all_;'; put 'run;'; put '%if %length(&open_passthrough)>0 %then %do;'; put '%put %str(WARN)ING: Passthrough option for postgres not yet supported;'; put '%return;'; put '%end;'; put '%else %do;'; put '%if &mdebug=1 %then %do;'; put '%put NOTE: Executing the following:/;'; put '%put NOTE- libname &libref POSTGRES &database &ignore_read_only_columns;'; put '%put NOTE- &direct_exe &preserve_col_names &preserve_tab_names;'; put '%put NOTE- &server &schema &authdomain &user &password //;'; put '%end;'; put 'libname &libref POSTGRES &database &ignore_read_only_columns &direct_exe'; put '&preserve_col_names &preserve_tab_names &server &schema &authdomain'; put '&user &password;'; put '%end;'; put '%end;'; put '%else %if &engine=ORACLE %then %do;'; put '%put NOTE: Obtaining &engine library details;'; put 'data _null_;'; put 'length assocuri1 assocuri2 assocuri3 authdomain path schema $256;'; put 'call missing (of _all_);'; put '/* get auth domain */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,assocuri1);'; put 'rc=metadata_getnasn(assocuri1,''Domain'',1,assocuri2);'; put 'rc=metadata_getattr(assocuri2,"Name",authdomain);'; put 'call symputx(''authdomain'',authdomain,''l'');'; put '/* path */'; put 'rc=metadata_getprop(assocuri1,'; put '''Connection.Oracle.Property.PATH.Name.xmlKey.txt'',path);'; put 'call symputx(''path'',path,''l'');'; put '/* schema */'; put 'rc=metadata_getnasn("&liburi",''UsingPackages'',1,assocuri3);'; put 'rc=metadata_getattr(assocuri3,''SchemaName'',schema);'; put 'call symputx(''schema'',schema,''l'');'; put 'run;'; put '%put NOTE: Executing the following:/; %put NOTE-;'; put '%put NOTE- libname &libref ORACLE path=&path schema=&schema;'; put '%put NOTE- authdomain=&authdomain;'; put '%put NOTE-;'; put 'libname &libref ORACLE path=&path schema=&schema authdomain=&authdomain;'; put '%end;'; put '%else %if &engine=SQLSVR %then %do;'; put '%put NOTE: Obtaining &engine library details;'; put 'data _null;'; put 'length assocuri1 assocuri2 assocuri3 authdomain path schema userid'; put 'passwd $256;'; put 'call missing (of _all_);'; put 'rc=metadata_getnasn("&liburi",''DefaultLogin'',1,assocuri1);'; put 'rc=metadata_getattr(assocuri1,"UserID",userid);'; put 'rc=metadata_getattr(assocuri1,"Password",passwd);'; put 'call symputx(''user'',userid,''l'');'; put 'call symputx(''pass'',passwd,''l'');'; put '/* path */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,assocuri2);'; put 'rc=metadata_getprop(assocuri2,'; put '''Connection.SQL.Property.Datasrc.Name.xmlKey.txt'',path);'; put 'call symputx(''path'',path,''l'');'; put '/* schema */'; put 'rc=metadata_getnasn("&liburi",''UsingPackages'',1,assocuri3);'; put 'rc=metadata_getattr(assocuri3,''SchemaName'',schema);'; put 'call symputx(''schema'',schema,''l'');'; put 'run;'; put '%put NOTE: Executing the following:/; %put NOTE-;'; put '%put NOTE- libname &libref SQLSVR datasrc=&path schema=&schema ;'; put '%put NOTE- user="&user" pass="XXX";'; put '%put NOTE-;'; put 'libname &libref SQLSVR datasrc=&path schema=&schema user="&user" pass="&pass";'; put '%end;'; put '%else %if &engine=TERADATA %then %do;'; put '%put NOTE: Obtaining &engine library details;'; put 'data _null;'; put 'length assocuri1 assocuri2 assocuri3 authdomain path schema userid'; put 'passwd $256;'; put 'call missing (of _all_);'; put '/* get auth domain */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,assocuri1);'; put 'rc=metadata_getnasn(assocuri1,''Domain'',1,assocuri2);'; put 'rc=metadata_getattr(assocuri2,"Name",authdomain);'; put 'call symputx(''authdomain'',authdomain,''l'');'; put '/*'; put 'rc=metadata_getnasn("&liburi",''DefaultLogin'',1,assocuri1);'; put 'rc=metadata_getattr(assocuri1,"UserID",userid);'; put 'rc=metadata_getattr(assocuri1,"Password",passwd);'; put 'call symputx(''user'',userid,''l'');'; put 'call symputx(''pass'',passwd,''l'');'; put '*/'; put '/* path */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,assocuri2);'; put 'rc=metadata_getprop(assocuri2,'; put '''Connection.Teradata.Property.SERVER.Name.xmlKey.txt'',path);'; put 'call symputx(''path'',path,''l'');'; put '/* schema */'; put 'rc=metadata_getnasn("&liburi",''UsingPackages'',1,assocuri3);'; put 'rc=metadata_getattr(assocuri3,''SchemaName'',schema);'; put 'call symputx(''schema'',schema,''l'');'; put 'run;'; put '%put NOTE: Executing the following:/; %put NOTE-;'; put '%put NOTE- libname &libref TERADATA server="&path" schema=&schema ;'; put '%put NOTe- authdomain=&authdomain;'; put '%put NOTE-;'; put 'libname &libref TERADATA server="&path" schema=&schema authdomain=&authdomain;'; put '%end;'; put '%else %if &engine= %then %do;'; put '%put NOTE: Libref &libref is not registered in metadata;'; put '%&mAbort.mp_abort('; put 'msg=%str(ERR)OR: Libref &libref is not registered in metadata'; put ',mac=mm_assigndirectlib.sas);'; put '%return;'; put '%end;'; put '%else %do;'; put '%put %str(WARN)ING: Engine &engine is currently unsupported;'; put '%put %str(WARN)ING- Please contact your support team.;'; put '%return;'; put '%end;'; put '%mend mm_assigndirectlib;'; put '%macro mm_getrepos('; put 'outds=work.mm_getrepos'; put ')/*/STORE SOURCE*/;'; put '* use a temporary fileref to hold the response;'; put 'filename response temp;'; put '/* get list of libraries */'; put 'proc metadata in='; put '"1"'; put 'out=response;'; put 'run;'; put '/* write the response to the log for debugging */'; put '/*'; put 'data _null_;'; put 'infile response lrecl=1048576;'; put 'input;'; put 'put _infile_;'; put 'run;'; put '*/'; put '/* create an XML map to read the response */'; put 'filename sxlemap temp;'; put 'data _null_;'; put 'file sxlemap;'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository";'; put 'put "";'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Id";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Name";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Desc";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@DefaultNS";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@RepositoryType";'; put 'put "characterstring20";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@RepositoryFormat";'; put 'put "characterstring10";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@Access";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@CurrentAccess";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@PauseState";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Path";'; put 'put "";'; put 'put "characterstring256";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Engine";'; put 'put "";'; put 'put "characterstring8";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Options";'; put 'put "";'; put 'put "characterstring32";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@MetadataCreated";'; put 'put "characterstring24";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@MetadataUpdated";'; put 'put "characterstring24";'; put 'put '''';'; put 'put ''
'';'; put 'run;'; put 'libname _XML_ xml xmlfileref=response xmlmap=sxlemap;'; put 'proc sort data= _XML_.SASRepos out=&outds;'; put 'by name;'; put 'run;'; put '/* clear references */'; put 'filename sxlemap clear;'; put 'filename response clear;'; put 'libname _XML_ clear;'; put '%mend mm_getrepos;'; put '%macro dc_assignlib(type,libref,passthru=);'; put '%put &sysmacroname entry vars:;'; put '%put _local_;'; put '/* take current repository */'; put '%local repo repocnt x;'; put '%let repo=%sysfunc(getoption(metarepository));'; put '/* get list of repositories and filter */'; put '%mm_getrepos(outds=repos)'; put '%let repocnt=0;'; put 'data repos;'; put 'set repos;'; put 'where repositorytype in(''CUSTOM'',''FOUNDATION'')'; put '& upcase(name) ne "%upcase(&repo)";'; put 'keep id name ;'; put 'call symputx(cats(''repo'',_n_),name,''l'');'; put 'call symputx(''repocnt'',_n_,''l'');'; put 'run;'; put '/* find out which of these repositories has the libref we are searching for */'; put '%local lib_uri;'; put '%let lib_uri=NOTFOUND;'; put 'data _null_; /* check default repo first */'; put 'length lib_uri $256;'; put 'call missing (of _all_);'; put 'rc=metadata_getnobj("omsobj:SASLibrary?@Libref =''&libref''",1,lib_uri);'; put 'call symputx(''lib_uri'',coalescec(lib_uri,''NOTFOUND''),''l'');'; put 'run;'; put '%if &lib_uri=NOTFOUND %then %do;'; put '%do x=1 %to &repocnt;'; put 'options metarepository=&&repo&x;'; put 'data _null_;'; put 'length lib_uri $256;'; put 'call missing (of _all_);'; put 'rc=metadata_getnobj("omsobj:SASLibrary?@Libref =''&libref''",1,lib_uri);'; put 'call symputx(''lib_uri'',coalescec(lib_uri,''NOTFOUND''),''l'');'; put 'run;'; put '%if &lib_uri ne NOTFOUND %then %let x=%eval(&repocnt+1);'; put '%end;'; put '%end;'; put '%if &type=READ %then %do;'; put '%mm_assignlib(&libref)'; put '%end;'; put '%else %if &type=WRITE %then %do;'; put '%mm_assigndirectlib(&libref,open_passthrough=&passthru)'; put '%end;'; put 'options metarepository=&repo;'; put '%mend dc_assignlib;'; put '* SAS Macros end;'; put '* SAS Includes start;'; put '* SAS Includes end;'; put '* Binary Files start;'; put '* Binary Files end;'; put '* ServiceInit start;'; put 'options noquotelenmax ps=max;'; put '/* create dummy macros to prevent stpbegin / stpend from corrupting sessions */'; put '%macro stpbegin();'; put '%put NOTE: the STPBEGIN macro should not be used for web apps!;'; put '%mend stpbegin;'; put '%macro stpend();'; put '%put NOTE: the STPEND macro should not be used for web apps!;'; put '%mend stpend;'; put '* ServiceInit end;'; put '* Service start;'; put '/**'; put '@file viewtables.sas'; put '@brief List the tables and format catalogs the user can view'; put '@details Provide a library and get list of tables and catalogs. Also return'; put 'the libinfo details.'; put '

Service Inputs

'; put '
SASControlTable
'; put 'Just one input - MPLIB (the libref to get tables and info for)'; put '|MPLIB:$char8.|'; put '|---|'; put '|SOMELIB|'; put '

Service Outputs

'; put '
work.mptables
'; put '|MEMNAME:$char32.|'; put '|---|'; put '|DS1|'; put '|DS2|'; put '|DS3|'; put 'etc'; put '
work.libinfo
'; put 'If attributes are empty, they don''t need to be shown on screen.'; put '|engine $|libname $|paths $|perms $|owners $|schemas $ |libid $|libsize $|table_cnt |'; put '|---|---|---|---|---|---|---|---|---|'; put '|V9|SOMELIB|"some/path"|rwxrwxr-x|sassrv|` `|` `|636MB|33|'; put '

SAS Macros

'; put '@li dc_assignlib.sas'; put '@li mf_getuser.sas'; put '@li mpe_getgroups.sas'; put '@li mpe_getvars.sas'; put '@li mpeinit.sas'; put '@version 9.2'; put '@author 4GL Apps Ltd'; put '@copyright 4GL Apps Ltd. This code may only be used within Data Controller'; put 'and may not be re-distributed or re-sold without the express permission of'; put '4GL Apps Ltd.'; put '**/'; put '%mpeinit()'; put '%global MPLIB;'; put '/* load parameters */'; put '%mpe_getvars(SASControlTable, SASControlTable)'; put '/**'; put '* assign the Library'; put '*/'; put '%put &=MPLIB;'; put '%dc_assignlib(READ,&MPLIB)'; put '%mp_abort(iftrue= (&syscc ne 0 )'; put ',mac=&_program..sas'; put ',msg=%str(Unable to assign &mplib library)'; put ')'; put '/**'; put '* get the tables'; put '*/'; put 'data members; /* empty table */'; put 'name='''';'; put 'memtype='''';'; put 'run;'; put 'ods output Members=Members;'; put 'proc datasets library=&mplib ;'; put 'quit;'; put '/* cannot avoid the proc datasets warn!ng for an empty lib */'; put '/* nolist means no output and nowarn has no effect */'; put '%put &=syscc;'; put 'data _null_;'; put 'if "&syscc" ne "0" then do;'; put 'putlog "Library &mplib is empty, setting syscc to zero";'; put 'call symputx(''syscc'',0);'; put 'end;'; put 'run;'; put '%put &=syscc;'; put 'proc sql;'; put 'create table work.mptables as'; put 'select distinct case when memtype=''CATALOG'' then cats(name,''-FC'')'; put 'else name end as memname'; put 'from members;'; put '/* get security groups */'; put '%mpe_getgroups(user=%mf_getuser(),outds=groups)'; put '/* get security settings */'; put 'data sec;'; put 'set &mpelib..mpe_security;'; put 'where &dc_dttmtfmt. lt tx_to and ACCESS_LEVEL=''VIEW'';'; put 'where also libref in (''*ALL*'',"%upcase(&mplib)");'; put 'run;'; put '/* check for any matching groups */'; put 'proc sql noprint;'; put 'create table matches as'; put 'select * from sec'; put 'where upcase(sas_group) in (select upcase(groupname) from groups);'; put 'select count(*) into: securitygroupscount from matches;'; put 'select count(*) into: ALL_CNT from matches'; put 'where libref=''*ALL*'''; put 'or (libref="&mplib" and dsn=''*ALL*'');'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(syscc=&syscc)'; put ')'; put '%macro mpestp_viewtables();'; put '%if not %symexist(DC_RESTRICT_VIEWER) %then %let DC_RESTRICT_VIEWER=NO;'; put '/* scenario 1 - user is in admin group, hence can view all libraries */'; put 'proc sql noprint;'; put 'select count(*) into: scenario1 from groups where groupname="&mpeadmins";'; put '%if &scenario1>0 %then %return;'; put '/* scenario 2 - viewer unrestricted and no groups listed */'; put '%if &DC_RESTRICT_VIEWER=NO and &securitygroupscount=0 %then %return;'; put '/* scenario 3 - an *ALL* libref or DSN is listed */'; put '%if &all_cnt>0 %then %return;'; put '/* scenario 4 - specific tables listed */'; put '%if &securitygroupscount>0 %then %do;'; put 'proc sql;'; put 'delete from mptables'; put 'where upcase(memname) not in (select upcase(dsn) from sec);'; put '%return;'; put '%end;'; put '/* viewer restricted and no groups listed */'; put '%if &DC_RESTRICT_VIEWER=YES and &securitygroupscount=0 %then %do;'; put 'data mptables;'; put 'set mptables;'; put 'stop;'; put 'run;'; put '%return;'; put '%end;'; put '%mp_abort(iftrue= (1=1)'; put ',mac=&_program..sas'; put ',msg=%str(unhandled security logic error!)'; put ')'; put '%mend mpestp_viewtables;'; put '%mpestp_viewtables()'; put '/* get libinfo */'; put 'proc sql;'; put 'create table work.libinfo as'; put 'select a.engine,'; put 'a.libname,'; put 'a.paths,'; put 'a.perms,'; put 'a.owners,'; put 'a.schemas,'; put 'a.libid,'; put 'coalesce(b.libsize,0) as libsize,'; put 'coalesce(b.table_cnt,0) as table_cnt'; put 'from &mpelib..mpe_datacatalog_libs(where=(&dc_dttmtfmt. lt tx_to)) a'; put 'left join &mpelib..mpe_datastatus_libs(where=(&dc_dttmtfmt. lt tx_to)) b'; put 'on a.libref=b.libref'; put 'where a.libref="&MPLIB";'; put '%webout(OPEN)'; put '%webout(OBJ,mptables)'; put '%webout(OBJ,libinfo)'; put '%webout(CLOSE)'; put '%mpeterm()'; put '* Service end;'; run; %mm_createwebservice(path=&appLoc/&path, name=&service, code=sascode, server=&serverName, replace=yes) filename sascode clear; %let path=services/usernav; %let service=usergroupsbymember; filename sascode temp lrecl=32767; data _null_; file sascode; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '/**'; put '@file mp_jsonout.sas'; put '@brief Writes JSON in SASjs format to a fileref'; put '@details This macro can be used to OPEN a JSON stream and send one or more'; put 'tables as arrays of rows, where each row can be an object or a nested array.'; put 'There are two engines available - DATASTEP or PROCJSON.'; put 'PROC JSON is fast but will produce errs like the ones below if'; put 'special chars are encountered.'; put '> (ERR)OR: Some code points did not transcode.'; put '> An object or array close is not valid at this point in the JSON text.'; put '> Date value out of range'; put 'If this happens, try running with ENGINE=DATASTEP.'; put 'The DATASTEP engine is used to handle special SAS missing numerics, and'; put 'can also convert entire datasets to formatted values. Output JSON is always'; put 'in UTF-8.'; put 'Usage:'; put 'filename tmp temp;'; put 'data class; set sashelp.class;run;'; put '%mp_jsonout(OPEN,jref=tmp)'; put '%mp_jsonout(OBJ,class,jref=tmp)'; put '%mp_jsonout(OBJ,class,dslabel=class2,jref=tmp,showmeta=Y)'; put '%mp_jsonout(CLOSE,jref=tmp)'; put 'data _null_;'; put 'infile tmp;'; put 'input;putlog _infile_;'; put 'run;'; put 'If you are building web apps with SAS then you are strongly encouraged to use'; put 'the mX_createwebservice macros in combination with the'; put '[sasjs adapter](https://github.com/sasjs/adapter).'; put 'For more information see https://sasjs.io'; put '@param [in] action Valid values:'; put '@li OPEN - opens the JSON'; put '@li OBJ - sends a table with each row as an object'; put '@li ARR - sends a table with each row in an array'; put '@li CLOSE - closes the JSON'; put '@param [in] ds The dataset to send. Must be a work table.'; put '@param [out] jref= (_webout) The fileref to which to send the JSON'; put '@param [out] dslabel= The name to give the table in the exported JSON'; put '@param [in] fmt= (Y) Whether to keep (Y) or strip (N) formats from the table'; put '@param [in] engine= (DATASTEP) Which engine to use to send the JSON. Options:'; put '@li PROCJSON (default)'; put '@li DATASTEP (more reliable when data has non standard characters)'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to JSON'; put '

Related Files

'; put '@li mp_ds2fmtds.sas'; put '@version 9.2'; put '@author Allan Bowe'; put '@source https://github.com/sasjs/core'; put '**/'; put '%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y'; put ',engine=DATASTEP'; put ',missing=NULL'; put ',showmeta=N'; put ',maxobs=MAX'; put ')/*/STORE SOURCE*/;'; put '%local tempds colinfo fmtds i numcols numobs stmt_obs lastobs optval'; put 'tmpds1 tmpds2 tmpds3 tmpds4;'; put '%let numcols=0;'; put '%if &maxobs ne MAX %then %let stmt_obs=%str(if _n_>&maxobs then stop;);'; put '%if &action=OPEN %then %do;'; put 'options nobomfile;'; put 'data _null_;file &jref encoding=''utf-8'' lrecl=200;'; put 'put ''{"PROCESSED_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''"'';'; put 'run;'; put '%end;'; put '%else %if (&action=ARR or &action=OBJ) %then %do;'; put '/* force variable names to always be uppercase in the JSON */'; put 'options validvarname=upcase;'; put '/* To avoid issues with _webout on EBI - such as encoding diffs and truncation'; put '(https://support.sas.com/kb/49/325.html) we use temporary files */'; put 'filename _sjs1 temp lrecl=200 ;'; put 'data _null_; file _sjs1 encoding=''utf-8'';'; put 'put ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":";'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjs1 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs1 clear;'; put '/* grab col defs */'; put 'proc contents noprint data=&ds'; put 'out=_data_(keep=name type length format formatl formatd varnum label);'; put 'run;'; put '%let colinfo=%scan(&syslast,2,.);'; put 'proc sort data=&colinfo;'; put 'by varnum;'; put 'run;'; put '/* move meta to mac vars */'; put 'data &colinfo;'; put 'if _n_=1 then call symputx(''numcols'',nobs,''l'');'; put 'set &colinfo end=last nobs=nobs;'; put 'name=upcase(name);'; put '/* fix formats */'; put 'if type=2 or type=6 then do;'; put 'typelong=''char'';'; put 'length fmt $49.;'; put 'if format='''' then fmt=cats(''$'',length,''.'');'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else fmt=cats(format,formatl,''.'');'; put 'end;'; put 'else do;'; put 'typelong=''num'';'; put 'if format='''' then fmt=''best.'';'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else if formatd=0 then fmt=cats(format,formatl,''.'');'; put 'else fmt=cats(format,formatl,''.'',formatd);'; put 'end;'; put '/* 32 char unique name */'; put 'newname=''sasjs''!!substr(cats(put(md5(name),$hex32.)),1,27);'; put 'call symputx(cats(''name'',_n_),name,''l'');'; put 'call symputx(cats(''newname'',_n_),newname,''l'');'; put 'call symputx(cats(''length'',_n_),length,''l'');'; put 'call symputx(cats(''fmt'',_n_),fmt,''l'');'; put 'call symputx(cats(''type'',_n_),type,''l'');'; put 'call symputx(cats(''typelong'',_n_),typelong,''l'');'; put 'call symputx(cats(''label'',_n_),coalescec(label,name),''l'');'; put '/* overwritten when fmt=Y and a custom format exists in catalog */'; put 'if typelong=''num'' then call symputx(cats(''fmtlen'',_n_),200,''l'');'; put 'else call symputx(cats(''fmtlen'',_n_),min(32767,ceil((length+10)*1.5)),''l'');'; put 'run;'; put '%let tempds=%substr(_%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql;'; put 'select count(*) into: lastobs from &ds;'; put '%if &maxobs ne MAX %then %let lastobs=%sysfunc(min(&lastobs,&maxobs));'; put '%if &engine=PROCJSON %then %do;'; put '%if &missing=STRING %then %do;'; put '%put &sysmacroname: Special Missings not supported in proc json.;'; put '%put &sysmacroname: Switching to DATASTEP engine;'; put '%goto datastep;'; put '%end;'; put 'data &tempds;'; put 'set &ds;'; put '&stmt_obs;'; put '%if &fmt=N %then format _numeric_ best32.;;'; put '/* PRETTY is necessary to avoid line truncation in large files */'; put 'filename _sjs2 temp lrecl=131068 encoding=''utf-8'';'; put 'proc json out=_sjs2 pretty'; put '%if &action=ARR %then nokeys ;'; put ';export &tempds / nosastags fmtnumeric;'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs2 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs2 clear;'; put '%end;'; put '%else %if &engine=DATASTEP %then %do;'; put '%datastep:'; put '%if %sysfunc(exist(&ds)) ne 1 & %sysfunc(exist(&ds,VIEW)) ne 1'; put '%then %do;'; put '%put &sysmacroname: &ds NOT FOUND!!!;'; put '%return;'; put '%end;'; put '%if &fmt=Y %then %do;'; put '/**'; put '* Extract format definitions'; put '* First, by getting library locations from dictionary.formats'; put '* Then, by exporting the width using proc format'; put '* Cannot use maxw from sashelp.vformat as not always populated'; put '* Cannot use fmtinfo() as not supported in all flavours'; put '*/'; put '%let tmpds1=%substr(fmtsum%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds2=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds3=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds4=%substr(col%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql noprint;'; put 'create table &tmpds1 as'; put 'select cats(libname,''.'',memname) as FMTCAT,'; put 'FMTNAME'; put 'from dictionary.formats'; put 'where fmttype=''F'' and libname is not null'; put 'and fmtname in (select format from &colinfo where format is not null)'; put 'order by 1;'; put 'create table &tmpds2('; put 'FMTNAME char(32),'; put 'LENGTH num'; put ');'; put '%local catlist cat fmtlist i;'; put 'select distinct fmtcat into: catlist separated by '' '' from &tmpds1;'; put '%do i=1 %to %sysfunc(countw(&catlist,%str( )));'; put '%let cat=%scan(&catlist,&i,%str( ));'; put 'proc sql;'; put 'select distinct fmtname into: fmtlist separated by '' '''; put 'from &tmpds1 where fmtcat="&cat";'; put 'proc format lib=&cat cntlout=&tmpds3(keep=fmtname length);'; put 'select &fmtlist;'; put 'run;'; put 'proc sql;'; put 'insert into &tmpds2 select distinct fmtname,length from &tmpds3;'; put '%end;'; put 'proc sql;'; put 'create table &tmpds4 as'; put 'select a.*, b.length as MAXW'; put 'from &colinfo a'; put 'left join &tmpds2 b'; put 'on cats(a.format)=cats(upcase(b.fmtname))'; put 'order by a.varnum;'; put 'data _null_;'; put 'set &tmpds4;'; put 'if not missing(maxw);'; put 'call symputx('; put 'cats(''fmtlen'',_n_),'; put '/* vars need extra padding due to JSON escaping of special chars */'; put 'min(32767,ceil((max(length,maxw)+10)*1.5))'; put ',''l'''; put ');'; put 'run;'; put '/* configure varlenchk - as we are explicitly shortening the variables */'; put '%let optval=%sysfunc(getoption(varlenchk));'; put 'options varlenchk=NOWARN;'; put 'data _data_(compress=char);'; put '/* shorten the new vars */'; put 'length'; put '%do i=1 %to &numcols;'; put '&&name&i $&&fmtlen&i'; put '%end;'; put ';'; put '/* rename on entry */'; put 'set &ds(rename=('; put '%do i=1 %to &numcols;'; put '&&name&i=&&newname&i'; put '%end;'; put '));'; put '&stmt_obs;'; put 'drop'; put '%do i=1 %to &numcols;'; put '&&newname&i'; put '%end;'; put ';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=num %then %do;'; put '&&name&i=cats(put(&&newname&i,&&fmt&i));'; put '%end;'; put '%else %do;'; put '&&name&i=put(&&newname&i,&&fmt&i);'; put '%end;'; put '%end;'; put 'if _error_ then do;'; put 'call symputx(''syscc'',1012);'; put 'stop;'; put 'end;'; put 'run;'; put '%let fmtds=&syslast;'; put 'options varlenchk=&optval;'; put '%end;'; put 'proc format; /* credit yabwon for special null removal */'; put 'value bart (default=40)'; put '%if &missing=NULL %then %do;'; put '._ - .z = null'; put '%end;'; put '%else %do;'; put '._ = [quote()]'; put '. = null'; put '.a - .z = [quote()]'; put '%end;'; put 'other = [best.];'; put 'data &tempds;'; put 'attrib _all_ label='''';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'length &&name&i $&&fmtlen&i...;'; put 'format &&name&i $&&fmtlen&i...;'; put '%end;'; put '%end;'; put '%if &fmt=Y %then %do;'; put 'set &fmtds;'; put '%end;'; put '%else %do;'; put 'set &ds;'; put '%end;'; put '&stmt_obs;'; put 'format _numeric_ bart.;'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'if findc(&&name&i,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put '&&name&i=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,&&name&i)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else &&name&i=quote(cats(&&name&i));'; put '%end;'; put '%end;'; put 'run;'; put 'filename _sjs3 temp lrecl=131068 ;'; put 'data _null_;'; put 'file _sjs3 encoding=''utf-8'';'; put 'if _n_=1 then put "[";'; put 'set &tempds;'; put 'if _n_>1 then put "," @; put'; put '%if &action=ARR %then "[" ; %else "{" ;'; put '%do i=1 %to &numcols;'; put '%if &i>1 %then "," ;'; put '%if &action=OBJ %then """&&name&i"":" ;'; put '"&&name&i"n /* name literal for reserved variable names */'; put '%end;'; put '%if &action=ARR %then "]" ; %else "}" ; ;'; put '/* close out the table */'; put 'data _null_;'; put 'file _sjs3 mod encoding=''utf-8'';'; put 'put '']'';'; put 'run;'; put 'data _null_;'; put 'infile _sjs3 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs3 clear;'; put '%end;'; put 'proc sql;'; put 'drop table &colinfo, &tempds;'; put '%if %substr(&showmeta,1,1)=Y %then %do;'; put 'filename _sjs4 temp lrecl=131068 encoding=''utf-8'';'; put 'data _null_;'; put 'file _sjs4;'; put 'length label $350;'; put 'put ", ""$%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":{""vars"":{";'; put 'do i=1 to &numcols;'; put 'name=quote(trim(symget(cats(''name'',i))));'; put 'format=quote(trim(symget(cats(''fmt'',i))));'; put 'label=quote(prxchange(''s/\\/\\\\/'',-1,trim(symget(cats(''label'',i)))));'; put 'length=quote(trim(symget(cats(''length'',i))));'; put 'type=quote(trim(symget(cats(''typelong'',i))));'; put 'if i>1 then put "," @@;'; put 'put name '':{"format":'' format '',"label":'' label'; put ''',"length":'' length '',"type":'' type ''}'';'; put 'end;'; put 'put ''}}'';'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs4 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs4 clear;'; put '%end;'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put 'data _null_; file &jref encoding=''utf-8'' mod ;'; put 'put "}";'; put 'run;'; put '%end;'; put '%mend mp_jsonout;'; put '/**'; put '@file mm_webout.sas'; put '@brief Send data to/from SAS Stored Processes'; put '@details This macro should be added to the start of each Stored Process,'; put '**immediately** followed by a call to:'; put '%mm_webout(FETCH)'; put 'This will read all the input data and create same-named SAS datasets in the'; put 'WORK library. You can then insert your code, and send data back using the'; put 'following syntax:'; put 'data some datasets; * make some data ;'; put 'retain some columns;'; put 'run;'; put '%mm_webout(OPEN)'; put '%mm_webout(ARR,some) * Array format, fast, suitable for large tables ;'; put '%mm_webout(OBJ,datasets) * Object format, easier to work with ;'; put 'Finally, wrap everything up send some helpful system variables too'; put '%mm_webout(CLOSE)'; put '@param [in] action Either FETCH, OPEN, ARR, OBJ or CLOSE'; put '@param [in] ds The dataset to send back to the frontend'; put '@param [out] dslabel= Value to use instead of table name for sending to JSON'; put '@param [in] fmt= (N) Setting Y converts all vars to their formatted values'; put '@param [out] fref= (_webout) The fileref to which to write the JSON'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] workobs= (0) When set to a positive integer, will create a new'; put 'output object (WORK) which contains this number of observations from all'; put 'tables in the WORK library.'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to output JSON'; put '

SAS Macros

'; put '@li mp_jsonout.sas'; put '

Related Macros

'; put '@li ms_webout.sas'; put '@li mv_webout.sas'; put '@version 9.3'; put '@author Allan Bowe'; put '**/'; put '%macro mm_webout(action,ds,dslabel=,fref=_webout,fmt=N,missing=NULL'; put ',showmeta=N,maxobs=MAX,workobs=0'; put ');'; put '%global _webin_file_count _webin_fileref1 _webin_name1 _program _debug'; put 'sasjs_tables;'; put '%local i tempds jsonengine;'; put '/* see https://github.com/sasjs/core/issues/41 */'; put '%if "%upcase(&SYSENCODING)" ne "UTF-8" %then %let jsonengine=PROCJSON;'; put '%else %let jsonengine=DATASTEP;'; put '%if &action=FETCH %then %do;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'options mprint notes mprintnest;'; put '%end;'; put '%let _webin_file_count=%eval(&_webin_file_count+0);'; put '/* now read in the data */'; put '%do i=1 %to &_webin_file_count;'; put '%if &_webin_file_count=1 %then %do;'; put '%let _webin_fileref1=&_webin_fileref;'; put '%let _webin_name1=&_webin_name;'; put '%end;'; put 'data _null_;'; put 'infile &&_webin_fileref&i termstr=crlf;'; put 'input;'; put 'call symputx(''input_statement'',_infile_);'; put 'putlog "&&_webin_name&i input statement: " _infile_;'; put 'stop;'; put 'data &&_webin_name&i;'; put 'infile &&_webin_fileref&i firstobs=2 dsd termstr=crlf encoding=''utf-8'';'; put 'input &input_statement;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'if _n_<20 then putlog _infile_;'; put '%end;'; put 'run;'; put '%let sasjs_tables=&sasjs_tables &&_webin_name&i;'; put '%end;'; put '%end;'; put '%else %if &action=OPEN %then %do;'; put '/* fix encoding */'; put 'OPTIONS NOBOMFILE;'; put '/**'; put '* check xengine type to avoid the below err message:'; put '* > Function is only valid for filerefs using the CACHE access method.'; put '*/'; put 'data _null_;'; put 'set sashelp.vextfl(where=(fileref="_WEBOUT"));'; put 'if xengine=''STREAM'' then do;'; put 'rc=stpsrv_header(''Content-type'',"text/html; encoding=utf-8");'; put 'end;'; put 'run;'; put '/* setup json */'; put 'data _null_;file &fref encoding=''utf-8'';'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutBEGIN<<'';'; put '%end;'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'run;'; put '%end;'; put '%else %if &action=ARR or &action=OBJ %then %do;'; put '%mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt,jref=&fref'; put ',engine=&jsonengine,missing=&missing,showmeta=&showmeta,maxobs=&maxobs'; put ')'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put '/* To avoid issues with _webout on EBI we use a temporary file */'; put 'filename _sjsref temp lrecl=131068;'; put '%if %str(&workobs) > 0 %then %do;'; put '/* if debug mode, send back first XX records of each work table also */'; put 'data;run;%let tempds=%scan(&syslast,2,.);'; put 'ods output Members=&tempds;'; put 'proc datasets library=WORK memtype=data;'; put '%local wtcnt;%let wtcnt=0;'; put 'data _null_;'; put 'set &tempds;'; put 'if not (upcase(name) =:"DATA"); /* ignore temp datasets */'; put 'i+1;'; put 'call symputx(cats(''wt'',i),name,''l'');'; put 'call symputx(''wtcnt'',i,''l'');'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put ",""WORK"":{";'; put '%do i=1 %to &wtcnt;'; put '%let wt=&&wt&i;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'dsid=open("WORK.&wt",''is'');'; put 'nlobs=attrn(dsid,''NLOBS'');'; put 'nvars=attrn(dsid,''NVARS'');'; put 'rc=close(dsid);'; put 'if &i>1 then put '',''@;'; put 'put " ""&wt"" : {";'; put 'put ''"nlobs":'' nlobs;'; put 'put '',"nvars":'' nvars;'; put '%mp_jsonout(OBJ,&wt,jref=_sjsref,dslabel=first10rows,showmeta=Y'; put ',maxobs=&workobs'; put ')'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put '%end;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put 'run;'; put '%end;'; put '/* close off json */'; put 'data _null_;file _sjsref mod encoding=''utf-8'';'; put 'length SYSPROCESSNAME syserrortext syswarningtext autoexec $512;'; put 'put ",""_DEBUG"" : ""&_debug"" ";'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'autoexec=quote(urlencode(trim(getoption(''autoexec''))));'; put 'put '',"AUTOEXEC" : '' autoexec;'; put 'put ",""MF_GETUSER"" : ""%mf_getuser()"" ";'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'put ",""SYSENCODING"" : ""&sysencoding"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSPROCESSID"" : ""&SYSPROCESSID"" ";'; put 'put ",""SYSPROCESSMODE"" : ""&SYSPROCESSMODE"" ";'; put 'SYSPROCESSNAME=quote(urlencode(cats(SYSPROCESSNAME)));'; put 'put ",""SYSPROCESSNAME"" : " SYSPROCESSNAME;'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put '',"SYSWARNINGTEXT" : '' syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'length memsize $32;'; put 'memsize="%sysfunc(INPUTN(%sysfunc(getoption(memsize)), best.),sizekmg.)";'; put 'memsize=quote(cats(memsize));'; put 'put '',"MEMSIZE" : '' memsize;'; put 'put "}" @;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutEND<<'';'; put '%end;'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjsref lrecl=1 recfm=n;'; put 'file &fref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjsref clear;'; put '%end;'; put '%mend mm_webout;'; put '%macro webout(action,ds,dslabel=,fmt=,missing=NULL,showmeta=NO,maxobs=MAX);'; put '%mm_webout(&action,ds=&ds,dslabel=&dslabel,fmt=&fmt'; put ',missing=&missing'; put ',showmeta=&showmeta'; put ',maxobs=&maxobs'; put ') %mend;'; put '/* provide additional debug info */'; put '%global _program;'; put '%put &=syscc;'; put '%put user=%mf_getuser();'; put '%put pgm=&_program;'; put '%put timestamp=%sysfunc(datetime(),datetime19.);'; put '* Service Variables start;'; put '* Service Variables end;'; put '* SAS Macros start;'; put '%macro mf_getapploc(pgm);'; put '%if "&pgm"="" %then %do;'; put '%if %symexist(_program) %then %let pgm=&_program;'; put '%else %do;'; put '%put &sysmacroname: No value provided and no _program variable available;'; put '%return;'; put '%end;'; put '%end;'; put '%local root;'; put '/**'; put '* First check we are not in the tests/macros folder (which has no subfolders)'; put '* or specifically in the testsetup or testteardown services'; put '*/'; put '%if %index(&pgm,/tests/macros/)'; put 'or %index(&pgm,/tests/testsetup)'; put 'or %index(&pgm,/tests/testteardown)'; put '%then %do;'; put '%let root=%substr(&pgm,1,%index(&pgm,/tests)-1);'; put '&root'; put '%return;'; put '%end;'; put '/**'; put '* Next, move up two levels to avoid matches on subfolder or service name'; put '*/'; put '%let root=%substr(&pgm,1,%length(&pgm)-%length(%scan(&pgm,-1,/))-1);'; put '%let root=%substr(&root,1,%length(&root)-%length(%scan(&root,-1,/))-1);'; put '%if %index(&root,/tests/) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/tests/)-1);'; put '%end;'; put '%else %if %index(&root,/services) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/services)-1);'; put '%end;'; put '%else %if %index(&root,/jobs) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/jobs)-1);'; put '%end;'; put '%else %put &sysmacroname: Could not find an app location from &pgm;'; put '&root'; put '%mend mf_getapploc ;'; put '%macro mf_getuniquefileref(prefix=_,maxtries=1000,lrecl=32767);'; put '%local rc fname;'; put '%if &prefix=0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%end;'; put '%else %do;'; put '%local x len;'; put '%let len=%eval(8-%length(&prefix));'; put '%let x=0;'; put '%do x=0 %to &maxtries;'; put '%let fname=&prefix%substr(%sysfunc(ranuni(0)),3,&len);'; put '%if %sysfunc(fileref(&fname)) > 0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%return;'; put '%end;'; put '%end;'; put '%put unable to find available fileref after &maxtries attempts;'; put '%end;'; put '%mend mf_getuniquefileref;'; put '%macro mp_abort(mac=mp_abort.sas, type=, msg=, iftrue=%str(1=1)'; put ', errds=work.mp_abort_errds'; put ', mode=REGULAR'; put ')/*/STORE SOURCE*/;'; put '%global sysprocessmode sysprocessname sasjs_stpsrv_header_loc sasjsprocessmode;'; put '%local fref fid i;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%put NOTE: /// mp_abort macro executing //;'; put '%if %length(&mac)>0 %then %put NOTE- called by &mac;'; put '%put NOTE - &msg;'; put '%if %symexist(_SYSINCLUDEFILEDEVICE)'; put '/* abort cancel FILE does not restart outside the INCLUDE on Viya 3.5 */'; put 'and %superq(SYSPROCESSNAME) ne %str(Compute Server)'; put '%then %do;'; put '%if "*&_SYSINCLUDEFILEDEVICE*" ne "**" %then %do;'; put 'data &errds;'; put 'iftrue=''1=1'';'; put 'length mac $100 msg $5000;'; put 'mac=symget(''mac'');'; put 'msg=symget(''msg'');'; put 'run;'; put 'data _null_;'; put 'abort cancel FILE;'; put 'run;'; put '%return;'; put '%end;'; put '%end;'; put '/* Web App Context */'; put '%if %symexist(_PROGRAM)'; put 'or %superq(SYSPROCESSNAME) = %str(Compute Server)'; put 'or &mode=INCLUDE'; put '%then %do;'; put 'options obs=max replace mprint;'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5"'; put '%then %do;'; put 'options nosyntaxcheck;'; put '%end;'; put '%if &mode=INCLUDE %then %do;'; put '%if %sysfunc(exist(&errds))=1 %then %do;'; put 'data _null_;'; put 'set &errds;'; put 'call symputx(''iftrue'',iftrue,''l'');'; put 'call symputx(''mac'',mac,''l'');'; put 'call symputx(''msg'',msg,''l'');'; put 'putlog (_all_)(=);'; put 'run;'; put '%if (&iftrue)=0 %then %return;'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: No include errors found;'; put '%return;'; put '%end;'; put '%end;'; put '/* extract log errs / warns, if exist */'; put '%local logloc logline;'; put '%global logmsg; /* capture global messages */'; put '%if %symexist(SYSPRINTTOLOG) %then %let logloc=&SYSPRINTTOLOG;'; put '%else %let logloc=%qsysfunc(getoption(LOG));'; put 'proc printto log=log;run;'; put '%let logline=0;'; put '%if %length(&logloc)>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input; putlog _infile_;'; put 'i=1;'; put 'retain logonce 0;'; put 'if ('; put '_infile_=:"%str(WARN)ING" or _infile_=:"%str(ERR)OR"'; put ') and logonce=0 then'; put 'do;'; put 'call symputx(''logline'',_n_);'; put 'logonce+1;'; put 'end;'; put 'run;'; put '/* capture log including lines BEFORE the err */'; put '%if &logline>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input;'; put 'i=1;'; put 'stoploop=0;'; put 'if _n_ ge &logline-15 and stoploop=0 then do until (i>22);'; put 'call symputx(''logmsg'',catx(''\n'',symget(''logmsg''),_infile_));'; put 'input;'; put 'i+1;'; put 'stoploop=1;'; put 'end;'; put 'if stoploop=1 then stop;'; put 'run;'; put '%end;'; put '%end;'; put '%if %symexist(SYS_JES_JOB_URI) %then %do;'; put '/* setup webout for Viya */'; put 'options nobomfile;'; put '%if "X&SYS_JES_JOB_URI.X"="XX" %then %do;'; put 'filename _webout temp lrecl=999999 mod;'; put '%end;'; put '%else %do;'; put 'filename _webout filesrvc parenturi="&SYS_JES_JOB_URI"'; put 'name="_webout.json" lrecl=999999 mod;'; put '%end;'; put '%end;'; put '%else %if %sysfunc(filename(fref,&sasjs_stpsrv_header_loc))=0 %then %do;'; put 'options nobomfile;'; put '/* set up http header for SASjs Server */'; put '%let fid=%sysfunc(fopen(&fref,A));'; put '%if &fid=0 %then %do;'; put '%put %str(ERR)OR: %sysfunc(sysmsg());'; put '%return;'; put '%end;'; put '%let rc=%sysfunc(fput(&fid,%str(Content-Type: application/json)));'; put '%let rc=%sysfunc(fwrite(&fid));'; put '%let rc=%sysfunc(fclose(&fid));'; put '%let rc=%sysfunc(filename(&fref));'; put '%end;'; put '/* send response in SASjs JSON format */'; put 'data _null_;'; put 'file _webout mod lrecl=32000 encoding=''utf-8'';'; put 'length msg syswarningtext syserrortext $32767 mode $10 ;'; put 'sasdatetime=datetime();'; put 'msg=symget(''msg'');'; put '%if &logline>0 %then %do;'; put 'msg=cats(msg,''\n\nLog Extract:\n'',symget(''logmsg''));'; put '%end;'; put '/* escape the escapes */'; put 'msg=tranwrd(msg,''\'',''\\'');'; put '/* escape the quotes */'; put 'msg=tranwrd(msg,''"'',''\"'');'; put '/* ditch the CRLFs as chrome complains */'; put 'msg=compress(msg,,''kw'');'; put '/* quote without quoting the quotes (which are escaped instead) */'; put 'msg=cats(''"'',msg,''"'');'; put 'if symexist(''_debug'') then debug=quote(trim(symget(''_debug'')));'; put 'else debug=''""'';'; put 'if symget(''sasjsprocessmode'')=''Stored Program'' then mode=''SASJS'';'; put 'if mode ne ''SASJS'' then put ''>>weboutBEGIN<<'';'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'put '',"sasjsAbort" : [{'';'; put 'put '' "MSG":'' msg ;'; put 'put '' ,"MAC": "'' "&mac" ''"}]'';'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'put '',"_DEBUG":'' debug ;'; put 'if symexist(''_metauser'') then do;'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put 'end;'; put 'if symexist(''SYS_JES_JOB_URI'') then do;'; put 'SYS_JES_JOB_URI=quote(trim(symget(''SYS_JES_JOB_URI'')));'; put 'put '',"SYS_JES_JOB_URI": '' SYS_JES_JOB_URI;'; put 'end;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put ",""SYSWARNINGTEXT"" : " syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'put "}" ;'; put 'if mode ne ''SASJS'' then put ''>>weboutEND<<'';'; put 'run;'; put '%put _all_;'; put '%if "&sysprocessmode " = "SAS Stored Process Server " %then %do;'; put 'data _null_;'; put 'putlog ''stpsrvset program err and syscc'';'; put 'rc=stpsrvset(''program error'', 0);'; put 'call symputx("syscc",0,"g");'; put 'run;'; put '%if &sysscp=WIN'; put 'and 1=0 /* deprecating this logic until we figure out a consistent abort */'; put 'and "%substr(%str(&sysvlong ),1,8)"="9.04.01M"'; put 'and "%substr(%str(&sysvlong ),9,1)">"5" %then %do;'; put '/* skip approach (below) does not work in windows m6+ envs */'; put 'endsas;'; put '%end;'; put '%else %do;'; put '/**'; put '* endsas kills 9.4m3 deployments by orphaning multibridges.'; put '* Abort variants are ungraceful (non zero return code)'; put '* This approach lets SAS run silently until the end :-)'; put '* Caution - fails when called within a %include within a macro'; put '* Use mp_include() to handle this.'; put '*/'; put 'filename skip temp;'; put 'data _null_;'; put 'file skip;'; put 'put ''%macro skip();'';'; put 'comment ''%mend skip; -> fix lint '';'; put 'put ''%macro skippy();'';'; put 'comment ''%mend skippy; -> fix lint '';'; put 'run;'; put '%inc skip;'; put '%end;'; put '%end;'; put '%else %if "&sysprocessmode " = "SAS Compute Server " %then %do;'; put '/* endsas kills the session making it harder to fetch results */'; put 'data _null_;'; put 'syswarningtext=symget(''syswarningtext'');'; put 'syserrortext=symget(''syserrortext'');'; put 'abort_msg=symget(''msg'');'; put 'syscc=symget(''syscc'');'; put 'sysuserid=symget(''sysuserid'');'; put 'iftrue=symget(''iftrue'');'; put 'put (_all_)(/=);'; put 'call symputx(''syscc'',0);'; put 'abort cancel nolist;'; put 'run;'; put '%end;'; put '%else %do;'; put '%abort cancel;'; put '%end;'; put '%end;'; put '%else %do;'; put '%put _all_;'; put '%abort cancel;'; put '%end;'; put '%mend mp_abort;'; put '/** @endcond */'; put '%macro mm_getstpcode('; put 'tree=/User Folders/sasdemo/somestp'; put ',name='; put ',outloc=0'; put ',outref=0'; put ',mDebug=1'; put ',showlog=NO'; put ');'; put '%local mD;'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing &sysmacroname..sas;'; put '%&mD.put _local_;'; put '%if %length(&name)>0 %then %let name=/&name;'; put '/* first, check if STP exists */'; put '%local tsuri;'; put '%let tsuri=stopifempty ;'; put 'data _null_;'; put 'format type uri tsuri value $200.;'; put 'call missing (of _all_);'; put 'path="&tree&name(StoredProcess)";'; put '/* first, find the STP ID */'; put 'if metadata_pathobj("",path,"StoredProcess",type,uri)>0 then do;'; put '/* get sourcecode */'; put 'cnt=1;'; put 'do while (metadata_getnasn(uri,"Notes",cnt,tsuri)>0);'; put 'rc=metadata_getattr(tsuri,"Name",value);'; put '&mD.put tsuri= value=;'; put 'if value="SourceCode" then do;'; put '/* found it! */'; put 'rc=metadata_getattr(tsuri,"Id",value);'; put 'call symputx(''tsuri'',value,''l'');'; put 'stop;'; put 'end;'; put 'cnt+1;'; put 'end;'; put 'end;'; put 'else put (_all_)(=);'; put 'run;'; put '%mp_abort(iftrue= (&tsuri=stopifempty)'; put ',mac=mm_getstpcode'; put ',msg=%str(&tree&name.(StoredProcess) not found!)'; put ')'; put '/**'; put '* Now we can extract the textstore'; put '*/'; put 'filename __getdoc temp lrecl=10000000;'; put 'proc metadata'; put 'in="$METAREPOSITORY'; put ''; put 'SAS1"'; put 'out=__getdoc ;'; put 'run;'; put '/* find the beginning of the text */'; put '%local start;'; put 'data _null_;'; put 'infile __getdoc lrecl=10000;'; put 'input;'; put 'start=index(_infile_,''StoredText="'');'; put 'if start then do;'; put 'call symputx("start",start+11);'; put '*putlog ''"'' _infile_ ''"'';'; put 'end;'; put 'stop;'; put '%local outeng;'; put '%if "&outloc"="0" %then %let outeng=TEMP;'; put '%else %let outeng="&outloc";'; put '%local fref;'; put '%if &outref=0 %then %let fref=%mf_getuniquefileref();'; put '%else %let fref=&outref;'; put '/* read the content, byte by byte, resolving escaped chars */'; put 'filename &fref &outeng lrecl=100000;'; put 'data _null_;'; put 'length filein 8 fileid 8;'; put 'filein = fopen("__getdoc","I",1,"B");'; put 'fileid = fopen("&fref","O",1,"B");'; put 'rec = "20"x;'; put 'length entity $6;'; put 'do while(fread(filein)=0);'; put 'x+1;'; put 'if x>&start then do;'; put 'rc = fget(filein,rec,1);'; put 'if rec=''"'' then leave;'; put 'else if rec="&" then do;'; put 'entity=rec;'; put 'do until (rec=";");'; put 'if fread(filein) ne 0 then goto getout;'; put 'rc = fget(filein,rec,1);'; put 'entity=cats(entity,rec);'; put 'end;'; put 'select (entity);'; put 'when (''&'' ) rec=''&'' ;'; put 'when (''<'' ) rec=''<'' ;'; put 'when (''>'' ) rec=''>'' ;'; put 'when (''''') rec="''" ;'; put 'when (''"'') rec=''"'' ;'; put 'when ('' '') rec=''0A''x;'; put 'when ('' '') rec=''0D''x;'; put 'when (''$'' ) rec=''$'' ;'; put 'when ('' '') rec=''09''x;'; put 'otherwise putlog "%str(WARN)ING: missing value for " entity=;'; put 'end;'; put 'rc =fput(fileid, substr(rec,1,1));'; put 'rc =fwrite(fileid);'; put 'end;'; put 'else do;'; put 'rc =fput(fileid,rec);'; put 'rc =fwrite(fileid);'; put 'end;'; put 'end;'; put 'end;'; put 'getout:'; put 'rc=fclose(filein);'; put 'rc=fclose(fileid);'; put 'run;'; put '%if &showlog=YES %then %do;'; put 'data _null_;'; put 'infile &fref lrecl=32767 end=last;'; put 'input;'; put 'if _n_=1 then putlog ''>>stpcodeBEGIN<<'';'; put 'putlog _infile_;'; put 'if last then putlog ''>>stpcodeEND<<'';'; put 'run;'; put '%end;'; put 'filename __getdoc clear;'; put '%if &outref=0 %then %do;'; put 'filename &fref clear;'; put '%end;'; put '%mend mm_getstpcode;'; put '%macro dc_getsettings();'; put '%global _program;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(syscc=&syscc on entry (&syswarningtext &syserrortext))'; put ')'; put '%if %length(&_PROGRAM)>1 %then %let root=&_program;'; put '%else %do;'; put '%global _metauser;'; put '%let _metauser=&sysuserid;'; put '/* to mimic a "real" _program we need to give a dummy role and stp name */'; put '%let root=/dummyRole/dummyName;'; put '%end;'; put '/* the DC precode is stored in the Admin folder in the root of'; put 'the project. Lets find that root. */'; put '%put &=root;'; put '%let root=%mf_getapploc();'; put '%put &=root;'; put '/* Now we know the root location we can retrieve the params */'; put '%let temploc=%sysfunc(pathname(work))/settings.txt;'; put '%mm_getstpcode(tree=&root/services/public'; put ',name=Data_Controller_Settings'; put ',outloc=&temploc'; put ')'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Unable to run getstpcode)'; put ')'; put 'filename _getsets "&temploc" lrecl=2000;'; put '/*'; put 'Do not use mp_include here - this puts a copy in every service, which creates'; put 'compilation problems when calling services from mp_include'; put '*/'; put '%inc _getsets/source2;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Problem running &sysmacroname (&syswarningtext &syserrortext))'; put ')'; put '%mend dc_getsettings;'; put '%macro mf_fmtdttm('; put ')/*/STORE SOURCE*/;'; put '%if "&sysver"="9.2" or "&sysver"="9.3"'; put 'or ("&sysver"="9.4" and "%substr(&SYSVLONG,9,1)" le "3")'; put 'or "%substr(&sysver,1,1)"="4"'; put 'or "%substr(&sysver,1,1)"="5"'; put '%then %do;DATETIME19.3%end;'; put '%else %do;E8601DT26.6%end;'; put '%mend mf_fmtdttm;'; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '%macro mp_init(prefix=SASJS'; put ')/*/STORE SOURCE*/;'; put '%if %symexist(SASJS_PREFIX) %then %return; /* only run once */'; put '%global'; put 'SASJS_PREFIX /* the ONLY hard-coded global macro variable in SASjs */'; put '&prefix._FUNCTIONS /* used in mcf_init() to track core function compilation */'; put '&prefix._INIT_NUM /* initialisation time as numeric */'; put '&prefix._INIT_DTTM /* initialisation time in E8601DT26.6 format */'; put '&prefix.WORK /* avoid typing %sysfunc(pathname(work)) every time */'; put ';'; put '%let sasjs_prefix=&prefix;'; put 'data _null_;'; put 'dttm=datetime();'; put 'call symputx("&sasjs_prefix._init_num",dttm,''g'');'; put 'call symputx("&sasjs_prefix._init_dttm",put(dttm,E8601DT26.6),''g'');'; put 'call symputx("&sasjs_prefix.work",pathname(''WORK''),''g'');'; put 'run;'; put 'options'; put 'compress=CHAR /* default is none so ensure we have something! */'; put 'datastmtchk=ALLKEYWORDS /* protection from overwriting input datasets */'; put 'errorcheck=STRICT /* catch errs in libname/filename statements */'; put 'fmterr /* ensure err when a format cannot be found */'; put 'mergenoby=%str(ERR)OR /* throw err when a merge has no BY variables */'; put 'missing=. /* changing this can cause hard to detect errs */'; put 'noquotelenmax /* avoid warnings for long strings */'; put 'noreplace /* avoid overwriting permanent datasets */'; put 'ps=max /* reduce log size slightly */'; put 'ls=max /* reduce log even more and avoid word truncation */'; put 'validmemname=COMPATIBLE /* avoid special characters etc in table names */'; put 'validvarname=V7 /* avoid special characters etc in variable names */'; put 'varinitchk=%str(ERR)OR /* avoid data mistakes from variable name typos */'; put 'varlenchk=%str(ERR)OR /* fail hard if truncation (data loss) can result */'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5" %then %do;'; put 'noautocorrect /* disallow misspelled procedure names */'; put 'dsoptions=note2err /* undocumented - convert bad NOTEs to ERRs */'; put '%end;'; put ';'; put '%mend mp_init;'; put '%macro mpeinit(fetch=YES);'; put '%global mpeinit'; put 'mpeadmins /* group with unrestricted Meditor access */'; put 'mpelocapprovals /* location for landing and staging files */'; put 'mpelib /* location of configuration tables for DC */'; put 'dc_repo_users /* location of user / group metadata */'; put 'dc_licence_key /* extracted in dc_getsettings */'; put 'dc_activation_key /* extracted in dc_getsettings */'; put 'dc_locale /* extracted in dc_getsettings */'; put 'dc_dttmtfmt /* can be overridden in dc_getsettings */'; put '_debug'; put ';'; put '%if &mpeinit=1 %then %return;'; put '%else %let mpeinit=1;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(Problem on service startup (&syswarningtext &syserrortext))'; put ')'; put '%mp_init()'; put '%if &fetch=YES %then %do;'; put '%webout(FETCH)'; put '%end;'; put '%global _CLIENTNAME;'; put '%mp_abort(iftrue= (&_CLIENTNAME=SAS Enterprise Guide)'; put ',mac=&_program..sas'; put ',msg=%str(Data Controller is a web app and should not be executed from EG)'; put ')'; put 'options urlencoding=utf8 nobomfile lrecl=32767;'; put '%let perf=%sysfunc(datetime());'; put '%put perfdiff: 0;'; put '%let dc_locale=SYSTEM; /* default if not set */'; put '/**'; put '* E8601DT26.6 has widest database support - but not all SAS flavours can'; put '* handle it. Override in the settings STP if needed.'; put '*/'; put 'data _null_;'; put 'dc_dttmtfmt=''"%sysfunc(datetime(),''!!"%mf_fmtdttm()"!!'')"dt'';'; put 'call symputx(''dc_dttmtfmt'',dc_dttmtfmt);'; put 'put dc_dttmtfmt=;'; put 'run;'; put '%put &=dc_dttmtfmt;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc prior to dc_getsettings)'; put ')'; put '%dc_getsettings()'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc after dc_getsettings)'; put ')'; put 'data _null_;'; put 'set &DC_LIBREF..mpe_config(where=('; put 'var_scope="DC"'; put 'and &dc_dttmtfmt lt tx_to'; put 'and var_active=1'; put '));'; put 'call symputx(var_name,var_value,''G'');'; put 'putlog var_name "=" var_value;'; put 'run;'; put '%let mpelib=&dc_libref;'; put '%let mpeadmins=&dc_admin_group;'; put '%let mpelocapprovals=&dc_staging_area;'; put '%let dc_repo_users=&dc_repo_users;'; put '%if &dc_locale ne SYSTEM %then %do;'; put 'options locale=&dc_locale;'; put '%end;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(Problem during compilation or with STP precode (&syswarningtext))'; put ')'; put '%mend mpeinit;'; put '%macro mf_mval(var);'; put '%if %symexist(&var) %then %do;'; put '%superq(&var)'; put '%end;'; put '%mend mf_mval;'; put '%macro mf_trimstr(basestr,trimstr);'; put '%local baselen trimlen trimval;'; put '/* return if basestr is shorter than trimstr (or 0) */'; put '%let baselen=%length(%superq(basestr));'; put '%let trimlen=%length(%superq(trimstr));'; put '%if &baselen < &trimlen or &baselen=0 %then %return;'; put '/* obtain the characters from the end of basestr */'; put '%let trimval=%qsubstr(%superq(basestr)'; put ',%length(%superq(basestr))-&trimlen+1'; put ',&trimlen);'; put '/* compare and if matching, chop it off! */'; put '%if %superq(basestr)=%superq(trimstr) %then %do;'; put '%return;'; put '%end;'; put '%else %if %superq(trimval)=%superq(trimstr) %then %do;'; put '%qsubstr(%superq(basestr),1,%length(%superq(basestr))-&trimlen)'; put '%end;'; put '%else %do;'; put '&basestr'; put '%end;'; put '%mend mf_trimstr;'; put '%macro mf_getplatform(switch'; put ')/*/STORE SOURCE*/;'; put '%local a b c;'; put '%if &switch.NONE=NONE %then %do;'; put '%if %symexist(sasjsprocessmode) %then %do;'; put '%if &sasjsprocessmode=Stored Program %then %do;'; put 'SASJS'; put '%return;'; put '%end;'; put '%end;'; put '%if %symexist(sysprocessmode) %then %do;'; put '%if "&sysprocessmode"="SAS Object Server"'; put 'or "&sysprocessmode"= "SAS Compute Server" %then %do;'; put 'SASVIYA'; put '%end;'; put '%else %if "&sysprocessmode"="SAS Stored Process Server"'; put 'or "&sysprocessmode"="SAS Workspace Server"'; put '%then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if %symexist(_metaport) or %symexist(_metauser) %then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if &switch=SASSTUDIO %then %do;'; put '/* return the version of SAS Studio else 0 */'; put '%if %mf_mval(_CLIENTAPP)=%str(SAS Studio) %then %do;'; put '%let a=%mf_mval(_CLIENTVERSION);'; put '%let b=%scan(&a,1,.);'; put '%if %eval(&b >2) %then %do;'; put '&b'; put '%end;'; put '%else 0;'; put '%end;'; put '%else 0;'; put '%end;'; put '%else %if &switch=VIYARESTAPI %then %do;'; put '%mf_trimstr(%sysfunc(getoption(servicesbaseurl)),/)'; put '%end;'; put '%mend mf_getplatform;'; put '%macro mpeterm();'; put '%local oldloc;'; put 'data _null_;'; put 'if symexist(''SYSPRINTTOLOG'') then oldloc=symget(''SYSPRINTTOLOG'');'; put 'else oldloc=getoption(''LOG'');'; put 'if subpad(oldloc,1,1) not in (''"'',"''",'' '') then oldloc=quote(cats(oldloc));'; put 'call symputx(''oldloc'',oldloc,''l'');'; put 'run;'; put '%if %length(&oldloc)>0 %then %do;'; put 'proc printto log=log;'; put 'run;'; put 'data _null_;'; put 'infile &oldloc;'; put 'input; putlog _infile_;'; put 'run;'; put '%end;'; put '%if %sysfunc(exist(&dc_libref..mpe_requests)) and %mf_getplatform() ne SASVIYA'; put '%then %do;'; put 'data ;'; put 'if 0 then set &dc_libref..mpe_requests;'; put 'request_dttm=%sysfunc(datetime());'; put 'request_user="%mf_getuser()";'; put 'request_service="%scan(&_program,-2,/)/%scan(&_program,-1,/)";'; put 'request_params='''';'; put 'output;stop;'; put 'proc append base=&dc_libref..mpe_requests data=&syslast force nowarn;'; put 'run;'; put '%end;'; put '%mend mpeterm;'; put '%macro mm_getusers('; put 'outds=work.mm_getusers,'; put 'user=0'; put ')/*/STORE SOURCE*/;'; put 'filename response temp;'; put '%if %superq(user)=0 %then %do;'; put 'proc metadata in= '''; put '$METAREPOSITORY'; put 'Person'; put 'SAS'; put '0'; put ''; put ''; put ''; put ''; put ''; put ''''; put 'out=response;'; put 'run;'; put '%end;'; put '%else %do;'; put 'filename inref temp;'; put 'data _null_;'; put 'file inref;'; put 'put "";'; put 'put "$METAREPOSITORY";'; put 'put "Person";'; put 'put "SAS";'; put 'put "";'; put 'put "128";'; put 'put "";'; put 'put "";'; put 'put '''';'; put 'put "";'; put 'length string $10000;'; put 'string=cats('''');'; put 'put string;'; put 'put "";'; put 'put "";'; put 'run;'; put 'proc metadata in=inref out=response;'; put 'run;'; put '%end;'; put 'filename sxlemap temp;'; put 'data _null_;'; put 'file sxlemap;'; put 'put '''';'; put 'put "/GetMetadataObjects/Objects/Person";'; put 'put "";'; put 'put '''';'; put 'put "/GetMetadataObjects/Objects/Person/@Id";'; put 'put "characterstring32";'; put 'put '''';'; put 'put "/GetMetadataObjects/Objects/Person/@Name";'; put 'put "characterstring256";'; put 'put ''
'';'; put 'run;'; put 'libname _XML_ xml xmlfileref=response xmlmap=sxlemap;'; put 'proc sort data= _XML_.SASObjects out=&outds;'; put 'by name;'; put 'run;'; put 'filename sxlemap clear;'; put 'filename response clear;'; put 'libname _XML_ clear;'; put '%mend mm_getusers;'; put '* SAS Macros end;'; put '* SAS Includes start;'; put '* SAS Includes end;'; put '* Binary Files start;'; put '* Binary Files end;'; put '* ServiceInit start;'; put 'options noquotelenmax ps=max;'; put '/* create dummy macros to prevent stpbegin / stpend from corrupting sessions */'; put '%macro stpbegin();'; put '%put NOTE: the STPBEGIN macro should not be used for web apps!;'; put '%mend stpbegin;'; put '%macro stpend();'; put '%put NOTE: the STPEND macro should not be used for web apps!;'; put '%mend stpend;'; put '* ServiceInit end;'; put '* Service start;'; put '/**'; put '@file usergroupsbymember.sas'; put '@brief List the groups a member is in'; put '@details Runs without \%mpeinit() - this enables the dropdown to be populated'; put 'during configuration, when the settings service does not yet exist.'; put '

SAS Macros

'; put '@li mp_abort.sas'; put '@li mm_getusers.sas'; put '@version 9.3'; put '@author 4GL Apps Ltd'; put '@copyright 4GL Apps Ltd. This code may only be used within Data Controller'; put 'and may not be re-distributed or re-sold without the express permission of'; put '4GL Apps Ltd.'; put '**/'; put '%macro x();'; put '%if %sysfunc(exist(work.iwant)) ne 1 %then %do;'; put '/* macro called by configurator - grab the URI of calling user */'; put '%mm_getusers(user=&_metaperson, outds=work.iwant)'; put '%end;'; put '%mend x;'; put '%x()'; put 'data groups'; put 'roles(rename=(groupuri=roleuri groupname=rolename groupdesc=roledesc)) ;'; put 'length uri groupuri groupname groupdesc publictype str $256;'; put 'call missing(of _all_);'; put 'set iwant;'; put 'a=1;'; put 'grpassn=metadata_getnasn(uri,"IdentityGroups",a,groupuri);'; put 'if grpassn in (-3,-4) then do;'; put 'putlog "WARNING: No groups found for ";'; put 'end;'; put 'else do while (grpassn > 0);'; put 'rc=metadata_getattr(groupuri, "Name", groupname);'; put 'rc=metadata_getattr(groupuri, "Desc", groupdesc);'; put 'rc=metadata_getattr(groupuri, "PublicType", PublicType);'; put 'a+1;'; put 'if PublicType = ''Role'' then output roles;'; put 'else output groups;'; put 'grpassn=metadata_getnasn(uri,"IdentityGroups",a,groupuri);'; put 'end;'; put 'keep groupuri groupname groupdesc;'; put 'if _n_=1 then delete; /* no content so don''t send empty row */'; put 'run;'; put 'data emails;'; put 'keep email type;'; put 'length emailuri email type uri str $256;'; put 'call missing(of _all_);'; put 'set iwant;'; put '/* credit'; put 'https://seleritysas.com/data-step-view-of-email-addresses-in-sas-metadata'; put '*/'; put 'emailrc=1;email_count=1;'; put 'do while(emailrc>0);'; put '/* Get Email from Person */'; put 'emailrc=metadata_getnasn(uri,"EmailAddresses",email_count,emailuri);'; put 'arc=1;'; put 'if (emailrc>0) then do;'; put 'arc=metadata_getattr(emailuri,"Address",email);'; put 'arc=metadata_getattr(emailuri,"EmailType",type);'; put 'end;'; put 'if (arc=0) then output emails;'; put 'email_count=email_count+1;'; put 'end;'; put 'run;'; put 'data logins;'; put 'length domain userid loginuri domainuri uri $256;'; put 'keep domain userid;'; put 'call missing(of _all_);'; put 'set iwant;'; put 'login_count=1;'; put 'do while(metadata_getnasn(uri,"Logins",login_count,loginuri)>0);'; put 'rc=metadata_getattr(loginuri,"UserID",userid);'; put 'rc=metadata_getnasn(loginuri,"Domain",1,domainuri);'; put 'rc=metadata_getattr(domainuri,"Name",domain);'; put 'output;'; put 'login_count+1;'; put 'end;'; put 'run;'; put 'data info;'; put 'length uri name displayname metadatacreated metadataupdated $256;'; put 'keep name displayname metadatacreated metadataupdated;'; put 'call missing(of _all_);'; put 'set iwant;'; put 'rc=metadata_getattr(uri,"Name",name);'; put 'rc=metadata_getattr(uri,"DisplayName",displayname);'; put 'rc=metadata_getattr(uri,"MetadataCreated",MetadataCreated);'; put 'rc=metadata_getattr(uri,"MetadataUpdated",MetadataUpdated);'; put 'run;'; put '%webout(OPEN)'; put '%webout(OBJ,emails)'; put '%webout(OBJ,groups)'; put '%webout(OBJ,roles)'; put '%webout(OBJ,logins)'; put '%webout(OBJ,info)'; put '%webout(CLOSE)'; put '* Service end;'; run; %mm_createwebservice(path=&appLoc/&path, name=&service, code=sascode, server=&serverName, replace=yes) filename sascode clear; %let service=usermembers; filename sascode temp lrecl=32767; data _null_; file sascode; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '/**'; put '@file mp_jsonout.sas'; put '@brief Writes JSON in SASjs format to a fileref'; put '@details This macro can be used to OPEN a JSON stream and send one or more'; put 'tables as arrays of rows, where each row can be an object or a nested array.'; put 'There are two engines available - DATASTEP or PROCJSON.'; put 'PROC JSON is fast but will produce errs like the ones below if'; put 'special chars are encountered.'; put '> (ERR)OR: Some code points did not transcode.'; put '> An object or array close is not valid at this point in the JSON text.'; put '> Date value out of range'; put 'If this happens, try running with ENGINE=DATASTEP.'; put 'The DATASTEP engine is used to handle special SAS missing numerics, and'; put 'can also convert entire datasets to formatted values. Output JSON is always'; put 'in UTF-8.'; put 'Usage:'; put 'filename tmp temp;'; put 'data class; set sashelp.class;run;'; put '%mp_jsonout(OPEN,jref=tmp)'; put '%mp_jsonout(OBJ,class,jref=tmp)'; put '%mp_jsonout(OBJ,class,dslabel=class2,jref=tmp,showmeta=Y)'; put '%mp_jsonout(CLOSE,jref=tmp)'; put 'data _null_;'; put 'infile tmp;'; put 'input;putlog _infile_;'; put 'run;'; put 'If you are building web apps with SAS then you are strongly encouraged to use'; put 'the mX_createwebservice macros in combination with the'; put '[sasjs adapter](https://github.com/sasjs/adapter).'; put 'For more information see https://sasjs.io'; put '@param [in] action Valid values:'; put '@li OPEN - opens the JSON'; put '@li OBJ - sends a table with each row as an object'; put '@li ARR - sends a table with each row in an array'; put '@li CLOSE - closes the JSON'; put '@param [in] ds The dataset to send. Must be a work table.'; put '@param [out] jref= (_webout) The fileref to which to send the JSON'; put '@param [out] dslabel= The name to give the table in the exported JSON'; put '@param [in] fmt= (Y) Whether to keep (Y) or strip (N) formats from the table'; put '@param [in] engine= (DATASTEP) Which engine to use to send the JSON. Options:'; put '@li PROCJSON (default)'; put '@li DATASTEP (more reliable when data has non standard characters)'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to JSON'; put '

Related Files

'; put '@li mp_ds2fmtds.sas'; put '@version 9.2'; put '@author Allan Bowe'; put '@source https://github.com/sasjs/core'; put '**/'; put '%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y'; put ',engine=DATASTEP'; put ',missing=NULL'; put ',showmeta=N'; put ',maxobs=MAX'; put ')/*/STORE SOURCE*/;'; put '%local tempds colinfo fmtds i numcols numobs stmt_obs lastobs optval'; put 'tmpds1 tmpds2 tmpds3 tmpds4;'; put '%let numcols=0;'; put '%if &maxobs ne MAX %then %let stmt_obs=%str(if _n_>&maxobs then stop;);'; put '%if &action=OPEN %then %do;'; put 'options nobomfile;'; put 'data _null_;file &jref encoding=''utf-8'' lrecl=200;'; put 'put ''{"PROCESSED_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''"'';'; put 'run;'; put '%end;'; put '%else %if (&action=ARR or &action=OBJ) %then %do;'; put '/* force variable names to always be uppercase in the JSON */'; put 'options validvarname=upcase;'; put '/* To avoid issues with _webout on EBI - such as encoding diffs and truncation'; put '(https://support.sas.com/kb/49/325.html) we use temporary files */'; put 'filename _sjs1 temp lrecl=200 ;'; put 'data _null_; file _sjs1 encoding=''utf-8'';'; put 'put ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":";'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjs1 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs1 clear;'; put '/* grab col defs */'; put 'proc contents noprint data=&ds'; put 'out=_data_(keep=name type length format formatl formatd varnum label);'; put 'run;'; put '%let colinfo=%scan(&syslast,2,.);'; put 'proc sort data=&colinfo;'; put 'by varnum;'; put 'run;'; put '/* move meta to mac vars */'; put 'data &colinfo;'; put 'if _n_=1 then call symputx(''numcols'',nobs,''l'');'; put 'set &colinfo end=last nobs=nobs;'; put 'name=upcase(name);'; put '/* fix formats */'; put 'if type=2 or type=6 then do;'; put 'typelong=''char'';'; put 'length fmt $49.;'; put 'if format='''' then fmt=cats(''$'',length,''.'');'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else fmt=cats(format,formatl,''.'');'; put 'end;'; put 'else do;'; put 'typelong=''num'';'; put 'if format='''' then fmt=''best.'';'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else if formatd=0 then fmt=cats(format,formatl,''.'');'; put 'else fmt=cats(format,formatl,''.'',formatd);'; put 'end;'; put '/* 32 char unique name */'; put 'newname=''sasjs''!!substr(cats(put(md5(name),$hex32.)),1,27);'; put 'call symputx(cats(''name'',_n_),name,''l'');'; put 'call symputx(cats(''newname'',_n_),newname,''l'');'; put 'call symputx(cats(''length'',_n_),length,''l'');'; put 'call symputx(cats(''fmt'',_n_),fmt,''l'');'; put 'call symputx(cats(''type'',_n_),type,''l'');'; put 'call symputx(cats(''typelong'',_n_),typelong,''l'');'; put 'call symputx(cats(''label'',_n_),coalescec(label,name),''l'');'; put '/* overwritten when fmt=Y and a custom format exists in catalog */'; put 'if typelong=''num'' then call symputx(cats(''fmtlen'',_n_),200,''l'');'; put 'else call symputx(cats(''fmtlen'',_n_),min(32767,ceil((length+10)*1.5)),''l'');'; put 'run;'; put '%let tempds=%substr(_%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql;'; put 'select count(*) into: lastobs from &ds;'; put '%if &maxobs ne MAX %then %let lastobs=%sysfunc(min(&lastobs,&maxobs));'; put '%if &engine=PROCJSON %then %do;'; put '%if &missing=STRING %then %do;'; put '%put &sysmacroname: Special Missings not supported in proc json.;'; put '%put &sysmacroname: Switching to DATASTEP engine;'; put '%goto datastep;'; put '%end;'; put 'data &tempds;'; put 'set &ds;'; put '&stmt_obs;'; put '%if &fmt=N %then format _numeric_ best32.;;'; put '/* PRETTY is necessary to avoid line truncation in large files */'; put 'filename _sjs2 temp lrecl=131068 encoding=''utf-8'';'; put 'proc json out=_sjs2 pretty'; put '%if &action=ARR %then nokeys ;'; put ';export &tempds / nosastags fmtnumeric;'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs2 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs2 clear;'; put '%end;'; put '%else %if &engine=DATASTEP %then %do;'; put '%datastep:'; put '%if %sysfunc(exist(&ds)) ne 1 & %sysfunc(exist(&ds,VIEW)) ne 1'; put '%then %do;'; put '%put &sysmacroname: &ds NOT FOUND!!!;'; put '%return;'; put '%end;'; put '%if &fmt=Y %then %do;'; put '/**'; put '* Extract format definitions'; put '* First, by getting library locations from dictionary.formats'; put '* Then, by exporting the width using proc format'; put '* Cannot use maxw from sashelp.vformat as not always populated'; put '* Cannot use fmtinfo() as not supported in all flavours'; put '*/'; put '%let tmpds1=%substr(fmtsum%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds2=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds3=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds4=%substr(col%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql noprint;'; put 'create table &tmpds1 as'; put 'select cats(libname,''.'',memname) as FMTCAT,'; put 'FMTNAME'; put 'from dictionary.formats'; put 'where fmttype=''F'' and libname is not null'; put 'and fmtname in (select format from &colinfo where format is not null)'; put 'order by 1;'; put 'create table &tmpds2('; put 'FMTNAME char(32),'; put 'LENGTH num'; put ');'; put '%local catlist cat fmtlist i;'; put 'select distinct fmtcat into: catlist separated by '' '' from &tmpds1;'; put '%do i=1 %to %sysfunc(countw(&catlist,%str( )));'; put '%let cat=%scan(&catlist,&i,%str( ));'; put 'proc sql;'; put 'select distinct fmtname into: fmtlist separated by '' '''; put 'from &tmpds1 where fmtcat="&cat";'; put 'proc format lib=&cat cntlout=&tmpds3(keep=fmtname length);'; put 'select &fmtlist;'; put 'run;'; put 'proc sql;'; put 'insert into &tmpds2 select distinct fmtname,length from &tmpds3;'; put '%end;'; put 'proc sql;'; put 'create table &tmpds4 as'; put 'select a.*, b.length as MAXW'; put 'from &colinfo a'; put 'left join &tmpds2 b'; put 'on cats(a.format)=cats(upcase(b.fmtname))'; put 'order by a.varnum;'; put 'data _null_;'; put 'set &tmpds4;'; put 'if not missing(maxw);'; put 'call symputx('; put 'cats(''fmtlen'',_n_),'; put '/* vars need extra padding due to JSON escaping of special chars */'; put 'min(32767,ceil((max(length,maxw)+10)*1.5))'; put ',''l'''; put ');'; put 'run;'; put '/* configure varlenchk - as we are explicitly shortening the variables */'; put '%let optval=%sysfunc(getoption(varlenchk));'; put 'options varlenchk=NOWARN;'; put 'data _data_(compress=char);'; put '/* shorten the new vars */'; put 'length'; put '%do i=1 %to &numcols;'; put '&&name&i $&&fmtlen&i'; put '%end;'; put ';'; put '/* rename on entry */'; put 'set &ds(rename=('; put '%do i=1 %to &numcols;'; put '&&name&i=&&newname&i'; put '%end;'; put '));'; put '&stmt_obs;'; put 'drop'; put '%do i=1 %to &numcols;'; put '&&newname&i'; put '%end;'; put ';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=num %then %do;'; put '&&name&i=cats(put(&&newname&i,&&fmt&i));'; put '%end;'; put '%else %do;'; put '&&name&i=put(&&newname&i,&&fmt&i);'; put '%end;'; put '%end;'; put 'if _error_ then do;'; put 'call symputx(''syscc'',1012);'; put 'stop;'; put 'end;'; put 'run;'; put '%let fmtds=&syslast;'; put 'options varlenchk=&optval;'; put '%end;'; put 'proc format; /* credit yabwon for special null removal */'; put 'value bart (default=40)'; put '%if &missing=NULL %then %do;'; put '._ - .z = null'; put '%end;'; put '%else %do;'; put '._ = [quote()]'; put '. = null'; put '.a - .z = [quote()]'; put '%end;'; put 'other = [best.];'; put 'data &tempds;'; put 'attrib _all_ label='''';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'length &&name&i $&&fmtlen&i...;'; put 'format &&name&i $&&fmtlen&i...;'; put '%end;'; put '%end;'; put '%if &fmt=Y %then %do;'; put 'set &fmtds;'; put '%end;'; put '%else %do;'; put 'set &ds;'; put '%end;'; put '&stmt_obs;'; put 'format _numeric_ bart.;'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'if findc(&&name&i,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put '&&name&i=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,&&name&i)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else &&name&i=quote(cats(&&name&i));'; put '%end;'; put '%end;'; put 'run;'; put 'filename _sjs3 temp lrecl=131068 ;'; put 'data _null_;'; put 'file _sjs3 encoding=''utf-8'';'; put 'if _n_=1 then put "[";'; put 'set &tempds;'; put 'if _n_>1 then put "," @; put'; put '%if &action=ARR %then "[" ; %else "{" ;'; put '%do i=1 %to &numcols;'; put '%if &i>1 %then "," ;'; put '%if &action=OBJ %then """&&name&i"":" ;'; put '"&&name&i"n /* name literal for reserved variable names */'; put '%end;'; put '%if &action=ARR %then "]" ; %else "}" ; ;'; put '/* close out the table */'; put 'data _null_;'; put 'file _sjs3 mod encoding=''utf-8'';'; put 'put '']'';'; put 'run;'; put 'data _null_;'; put 'infile _sjs3 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs3 clear;'; put '%end;'; put 'proc sql;'; put 'drop table &colinfo, &tempds;'; put '%if %substr(&showmeta,1,1)=Y %then %do;'; put 'filename _sjs4 temp lrecl=131068 encoding=''utf-8'';'; put 'data _null_;'; put 'file _sjs4;'; put 'length label $350;'; put 'put ", ""$%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":{""vars"":{";'; put 'do i=1 to &numcols;'; put 'name=quote(trim(symget(cats(''name'',i))));'; put 'format=quote(trim(symget(cats(''fmt'',i))));'; put 'label=quote(prxchange(''s/\\/\\\\/'',-1,trim(symget(cats(''label'',i)))));'; put 'length=quote(trim(symget(cats(''length'',i))));'; put 'type=quote(trim(symget(cats(''typelong'',i))));'; put 'if i>1 then put "," @@;'; put 'put name '':{"format":'' format '',"label":'' label'; put ''',"length":'' length '',"type":'' type ''}'';'; put 'end;'; put 'put ''}}'';'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs4 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs4 clear;'; put '%end;'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put 'data _null_; file &jref encoding=''utf-8'' mod ;'; put 'put "}";'; put 'run;'; put '%end;'; put '%mend mp_jsonout;'; put '/**'; put '@file mm_webout.sas'; put '@brief Send data to/from SAS Stored Processes'; put '@details This macro should be added to the start of each Stored Process,'; put '**immediately** followed by a call to:'; put '%mm_webout(FETCH)'; put 'This will read all the input data and create same-named SAS datasets in the'; put 'WORK library. You can then insert your code, and send data back using the'; put 'following syntax:'; put 'data some datasets; * make some data ;'; put 'retain some columns;'; put 'run;'; put '%mm_webout(OPEN)'; put '%mm_webout(ARR,some) * Array format, fast, suitable for large tables ;'; put '%mm_webout(OBJ,datasets) * Object format, easier to work with ;'; put 'Finally, wrap everything up send some helpful system variables too'; put '%mm_webout(CLOSE)'; put '@param [in] action Either FETCH, OPEN, ARR, OBJ or CLOSE'; put '@param [in] ds The dataset to send back to the frontend'; put '@param [out] dslabel= Value to use instead of table name for sending to JSON'; put '@param [in] fmt= (N) Setting Y converts all vars to their formatted values'; put '@param [out] fref= (_webout) The fileref to which to write the JSON'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] workobs= (0) When set to a positive integer, will create a new'; put 'output object (WORK) which contains this number of observations from all'; put 'tables in the WORK library.'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to output JSON'; put '

SAS Macros

'; put '@li mp_jsonout.sas'; put '

Related Macros

'; put '@li ms_webout.sas'; put '@li mv_webout.sas'; put '@version 9.3'; put '@author Allan Bowe'; put '**/'; put '%macro mm_webout(action,ds,dslabel=,fref=_webout,fmt=N,missing=NULL'; put ',showmeta=N,maxobs=MAX,workobs=0'; put ');'; put '%global _webin_file_count _webin_fileref1 _webin_name1 _program _debug'; put 'sasjs_tables;'; put '%local i tempds jsonengine;'; put '/* see https://github.com/sasjs/core/issues/41 */'; put '%if "%upcase(&SYSENCODING)" ne "UTF-8" %then %let jsonengine=PROCJSON;'; put '%else %let jsonengine=DATASTEP;'; put '%if &action=FETCH %then %do;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'options mprint notes mprintnest;'; put '%end;'; put '%let _webin_file_count=%eval(&_webin_file_count+0);'; put '/* now read in the data */'; put '%do i=1 %to &_webin_file_count;'; put '%if &_webin_file_count=1 %then %do;'; put '%let _webin_fileref1=&_webin_fileref;'; put '%let _webin_name1=&_webin_name;'; put '%end;'; put 'data _null_;'; put 'infile &&_webin_fileref&i termstr=crlf;'; put 'input;'; put 'call symputx(''input_statement'',_infile_);'; put 'putlog "&&_webin_name&i input statement: " _infile_;'; put 'stop;'; put 'data &&_webin_name&i;'; put 'infile &&_webin_fileref&i firstobs=2 dsd termstr=crlf encoding=''utf-8'';'; put 'input &input_statement;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'if _n_<20 then putlog _infile_;'; put '%end;'; put 'run;'; put '%let sasjs_tables=&sasjs_tables &&_webin_name&i;'; put '%end;'; put '%end;'; put '%else %if &action=OPEN %then %do;'; put '/* fix encoding */'; put 'OPTIONS NOBOMFILE;'; put '/**'; put '* check xengine type to avoid the below err message:'; put '* > Function is only valid for filerefs using the CACHE access method.'; put '*/'; put 'data _null_;'; put 'set sashelp.vextfl(where=(fileref="_WEBOUT"));'; put 'if xengine=''STREAM'' then do;'; put 'rc=stpsrv_header(''Content-type'',"text/html; encoding=utf-8");'; put 'end;'; put 'run;'; put '/* setup json */'; put 'data _null_;file &fref encoding=''utf-8'';'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutBEGIN<<'';'; put '%end;'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'run;'; put '%end;'; put '%else %if &action=ARR or &action=OBJ %then %do;'; put '%mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt,jref=&fref'; put ',engine=&jsonengine,missing=&missing,showmeta=&showmeta,maxobs=&maxobs'; put ')'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put '/* To avoid issues with _webout on EBI we use a temporary file */'; put 'filename _sjsref temp lrecl=131068;'; put '%if %str(&workobs) > 0 %then %do;'; put '/* if debug mode, send back first XX records of each work table also */'; put 'data;run;%let tempds=%scan(&syslast,2,.);'; put 'ods output Members=&tempds;'; put 'proc datasets library=WORK memtype=data;'; put '%local wtcnt;%let wtcnt=0;'; put 'data _null_;'; put 'set &tempds;'; put 'if not (upcase(name) =:"DATA"); /* ignore temp datasets */'; put 'i+1;'; put 'call symputx(cats(''wt'',i),name,''l'');'; put 'call symputx(''wtcnt'',i,''l'');'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put ",""WORK"":{";'; put '%do i=1 %to &wtcnt;'; put '%let wt=&&wt&i;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'dsid=open("WORK.&wt",''is'');'; put 'nlobs=attrn(dsid,''NLOBS'');'; put 'nvars=attrn(dsid,''NVARS'');'; put 'rc=close(dsid);'; put 'if &i>1 then put '',''@;'; put 'put " ""&wt"" : {";'; put 'put ''"nlobs":'' nlobs;'; put 'put '',"nvars":'' nvars;'; put '%mp_jsonout(OBJ,&wt,jref=_sjsref,dslabel=first10rows,showmeta=Y'; put ',maxobs=&workobs'; put ')'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put '%end;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put 'run;'; put '%end;'; put '/* close off json */'; put 'data _null_;file _sjsref mod encoding=''utf-8'';'; put 'length SYSPROCESSNAME syserrortext syswarningtext autoexec $512;'; put 'put ",""_DEBUG"" : ""&_debug"" ";'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'autoexec=quote(urlencode(trim(getoption(''autoexec''))));'; put 'put '',"AUTOEXEC" : '' autoexec;'; put 'put ",""MF_GETUSER"" : ""%mf_getuser()"" ";'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'put ",""SYSENCODING"" : ""&sysencoding"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSPROCESSID"" : ""&SYSPROCESSID"" ";'; put 'put ",""SYSPROCESSMODE"" : ""&SYSPROCESSMODE"" ";'; put 'SYSPROCESSNAME=quote(urlencode(cats(SYSPROCESSNAME)));'; put 'put ",""SYSPROCESSNAME"" : " SYSPROCESSNAME;'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put '',"SYSWARNINGTEXT" : '' syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'length memsize $32;'; put 'memsize="%sysfunc(INPUTN(%sysfunc(getoption(memsize)), best.),sizekmg.)";'; put 'memsize=quote(cats(memsize));'; put 'put '',"MEMSIZE" : '' memsize;'; put 'put "}" @;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutEND<<'';'; put '%end;'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjsref lrecl=1 recfm=n;'; put 'file &fref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjsref clear;'; put '%end;'; put '%mend mm_webout;'; put '%macro webout(action,ds,dslabel=,fmt=,missing=NULL,showmeta=NO,maxobs=MAX);'; put '%mm_webout(&action,ds=&ds,dslabel=&dslabel,fmt=&fmt'; put ',missing=&missing'; put ',showmeta=&showmeta'; put ',maxobs=&maxobs'; put ') %mend;'; put '/* provide additional debug info */'; put '%global _program;'; put '%put &=syscc;'; put '%put user=%mf_getuser();'; put '%put pgm=&_program;'; put '%put timestamp=%sysfunc(datetime(),datetime19.);'; put '* Service Variables start;'; put '* Service Variables end;'; put '* SAS Macros start;'; put '%macro mf_getapploc(pgm);'; put '%if "&pgm"="" %then %do;'; put '%if %symexist(_program) %then %let pgm=&_program;'; put '%else %do;'; put '%put &sysmacroname: No value provided and no _program variable available;'; put '%return;'; put '%end;'; put '%end;'; put '%local root;'; put '/**'; put '* First check we are not in the tests/macros folder (which has no subfolders)'; put '* or specifically in the testsetup or testteardown services'; put '*/'; put '%if %index(&pgm,/tests/macros/)'; put 'or %index(&pgm,/tests/testsetup)'; put 'or %index(&pgm,/tests/testteardown)'; put '%then %do;'; put '%let root=%substr(&pgm,1,%index(&pgm,/tests)-1);'; put '&root'; put '%return;'; put '%end;'; put '/**'; put '* Next, move up two levels to avoid matches on subfolder or service name'; put '*/'; put '%let root=%substr(&pgm,1,%length(&pgm)-%length(%scan(&pgm,-1,/))-1);'; put '%let root=%substr(&root,1,%length(&root)-%length(%scan(&root,-1,/))-1);'; put '%if %index(&root,/tests/) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/tests/)-1);'; put '%end;'; put '%else %if %index(&root,/services) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/services)-1);'; put '%end;'; put '%else %if %index(&root,/jobs) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/jobs)-1);'; put '%end;'; put '%else %put &sysmacroname: Could not find an app location from &pgm;'; put '&root'; put '%mend mf_getapploc ;'; put '%macro mf_getuniquefileref(prefix=_,maxtries=1000,lrecl=32767);'; put '%local rc fname;'; put '%if &prefix=0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%end;'; put '%else %do;'; put '%local x len;'; put '%let len=%eval(8-%length(&prefix));'; put '%let x=0;'; put '%do x=0 %to &maxtries;'; put '%let fname=&prefix%substr(%sysfunc(ranuni(0)),3,&len);'; put '%if %sysfunc(fileref(&fname)) > 0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%return;'; put '%end;'; put '%end;'; put '%put unable to find available fileref after &maxtries attempts;'; put '%end;'; put '%mend mf_getuniquefileref;'; put '%macro mp_abort(mac=mp_abort.sas, type=, msg=, iftrue=%str(1=1)'; put ', errds=work.mp_abort_errds'; put ', mode=REGULAR'; put ')/*/STORE SOURCE*/;'; put '%global sysprocessmode sysprocessname sasjs_stpsrv_header_loc sasjsprocessmode;'; put '%local fref fid i;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%put NOTE: /// mp_abort macro executing //;'; put '%if %length(&mac)>0 %then %put NOTE- called by &mac;'; put '%put NOTE - &msg;'; put '%if %symexist(_SYSINCLUDEFILEDEVICE)'; put '/* abort cancel FILE does not restart outside the INCLUDE on Viya 3.5 */'; put 'and %superq(SYSPROCESSNAME) ne %str(Compute Server)'; put '%then %do;'; put '%if "*&_SYSINCLUDEFILEDEVICE*" ne "**" %then %do;'; put 'data &errds;'; put 'iftrue=''1=1'';'; put 'length mac $100 msg $5000;'; put 'mac=symget(''mac'');'; put 'msg=symget(''msg'');'; put 'run;'; put 'data _null_;'; put 'abort cancel FILE;'; put 'run;'; put '%return;'; put '%end;'; put '%end;'; put '/* Web App Context */'; put '%if %symexist(_PROGRAM)'; put 'or %superq(SYSPROCESSNAME) = %str(Compute Server)'; put 'or &mode=INCLUDE'; put '%then %do;'; put 'options obs=max replace mprint;'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5"'; put '%then %do;'; put 'options nosyntaxcheck;'; put '%end;'; put '%if &mode=INCLUDE %then %do;'; put '%if %sysfunc(exist(&errds))=1 %then %do;'; put 'data _null_;'; put 'set &errds;'; put 'call symputx(''iftrue'',iftrue,''l'');'; put 'call symputx(''mac'',mac,''l'');'; put 'call symputx(''msg'',msg,''l'');'; put 'putlog (_all_)(=);'; put 'run;'; put '%if (&iftrue)=0 %then %return;'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: No include errors found;'; put '%return;'; put '%end;'; put '%end;'; put '/* extract log errs / warns, if exist */'; put '%local logloc logline;'; put '%global logmsg; /* capture global messages */'; put '%if %symexist(SYSPRINTTOLOG) %then %let logloc=&SYSPRINTTOLOG;'; put '%else %let logloc=%qsysfunc(getoption(LOG));'; put 'proc printto log=log;run;'; put '%let logline=0;'; put '%if %length(&logloc)>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input; putlog _infile_;'; put 'i=1;'; put 'retain logonce 0;'; put 'if ('; put '_infile_=:"%str(WARN)ING" or _infile_=:"%str(ERR)OR"'; put ') and logonce=0 then'; put 'do;'; put 'call symputx(''logline'',_n_);'; put 'logonce+1;'; put 'end;'; put 'run;'; put '/* capture log including lines BEFORE the err */'; put '%if &logline>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input;'; put 'i=1;'; put 'stoploop=0;'; put 'if _n_ ge &logline-15 and stoploop=0 then do until (i>22);'; put 'call symputx(''logmsg'',catx(''\n'',symget(''logmsg''),_infile_));'; put 'input;'; put 'i+1;'; put 'stoploop=1;'; put 'end;'; put 'if stoploop=1 then stop;'; put 'run;'; put '%end;'; put '%end;'; put '%if %symexist(SYS_JES_JOB_URI) %then %do;'; put '/* setup webout for Viya */'; put 'options nobomfile;'; put '%if "X&SYS_JES_JOB_URI.X"="XX" %then %do;'; put 'filename _webout temp lrecl=999999 mod;'; put '%end;'; put '%else %do;'; put 'filename _webout filesrvc parenturi="&SYS_JES_JOB_URI"'; put 'name="_webout.json" lrecl=999999 mod;'; put '%end;'; put '%end;'; put '%else %if %sysfunc(filename(fref,&sasjs_stpsrv_header_loc))=0 %then %do;'; put 'options nobomfile;'; put '/* set up http header for SASjs Server */'; put '%let fid=%sysfunc(fopen(&fref,A));'; put '%if &fid=0 %then %do;'; put '%put %str(ERR)OR: %sysfunc(sysmsg());'; put '%return;'; put '%end;'; put '%let rc=%sysfunc(fput(&fid,%str(Content-Type: application/json)));'; put '%let rc=%sysfunc(fwrite(&fid));'; put '%let rc=%sysfunc(fclose(&fid));'; put '%let rc=%sysfunc(filename(&fref));'; put '%end;'; put '/* send response in SASjs JSON format */'; put 'data _null_;'; put 'file _webout mod lrecl=32000 encoding=''utf-8'';'; put 'length msg syswarningtext syserrortext $32767 mode $10 ;'; put 'sasdatetime=datetime();'; put 'msg=symget(''msg'');'; put '%if &logline>0 %then %do;'; put 'msg=cats(msg,''\n\nLog Extract:\n'',symget(''logmsg''));'; put '%end;'; put '/* escape the escapes */'; put 'msg=tranwrd(msg,''\'',''\\'');'; put '/* escape the quotes */'; put 'msg=tranwrd(msg,''"'',''\"'');'; put '/* ditch the CRLFs as chrome complains */'; put 'msg=compress(msg,,''kw'');'; put '/* quote without quoting the quotes (which are escaped instead) */'; put 'msg=cats(''"'',msg,''"'');'; put 'if symexist(''_debug'') then debug=quote(trim(symget(''_debug'')));'; put 'else debug=''""'';'; put 'if symget(''sasjsprocessmode'')=''Stored Program'' then mode=''SASJS'';'; put 'if mode ne ''SASJS'' then put ''>>weboutBEGIN<<'';'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'put '',"sasjsAbort" : [{'';'; put 'put '' "MSG":'' msg ;'; put 'put '' ,"MAC": "'' "&mac" ''"}]'';'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'put '',"_DEBUG":'' debug ;'; put 'if symexist(''_metauser'') then do;'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put 'end;'; put 'if symexist(''SYS_JES_JOB_URI'') then do;'; put 'SYS_JES_JOB_URI=quote(trim(symget(''SYS_JES_JOB_URI'')));'; put 'put '',"SYS_JES_JOB_URI": '' SYS_JES_JOB_URI;'; put 'end;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put ",""SYSWARNINGTEXT"" : " syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'put "}" ;'; put 'if mode ne ''SASJS'' then put ''>>weboutEND<<'';'; put 'run;'; put '%put _all_;'; put '%if "&sysprocessmode " = "SAS Stored Process Server " %then %do;'; put 'data _null_;'; put 'putlog ''stpsrvset program err and syscc'';'; put 'rc=stpsrvset(''program error'', 0);'; put 'call symputx("syscc",0,"g");'; put 'run;'; put '%if &sysscp=WIN'; put 'and 1=0 /* deprecating this logic until we figure out a consistent abort */'; put 'and "%substr(%str(&sysvlong ),1,8)"="9.04.01M"'; put 'and "%substr(%str(&sysvlong ),9,1)">"5" %then %do;'; put '/* skip approach (below) does not work in windows m6+ envs */'; put 'endsas;'; put '%end;'; put '%else %do;'; put '/**'; put '* endsas kills 9.4m3 deployments by orphaning multibridges.'; put '* Abort variants are ungraceful (non zero return code)'; put '* This approach lets SAS run silently until the end :-)'; put '* Caution - fails when called within a %include within a macro'; put '* Use mp_include() to handle this.'; put '*/'; put 'filename skip temp;'; put 'data _null_;'; put 'file skip;'; put 'put ''%macro skip();'';'; put 'comment ''%mend skip; -> fix lint '';'; put 'put ''%macro skippy();'';'; put 'comment ''%mend skippy; -> fix lint '';'; put 'run;'; put '%inc skip;'; put '%end;'; put '%end;'; put '%else %if "&sysprocessmode " = "SAS Compute Server " %then %do;'; put '/* endsas kills the session making it harder to fetch results */'; put 'data _null_;'; put 'syswarningtext=symget(''syswarningtext'');'; put 'syserrortext=symget(''syserrortext'');'; put 'abort_msg=symget(''msg'');'; put 'syscc=symget(''syscc'');'; put 'sysuserid=symget(''sysuserid'');'; put 'iftrue=symget(''iftrue'');'; put 'put (_all_)(/=);'; put 'call symputx(''syscc'',0);'; put 'abort cancel nolist;'; put 'run;'; put '%end;'; put '%else %do;'; put '%abort cancel;'; put '%end;'; put '%end;'; put '%else %do;'; put '%put _all_;'; put '%abort cancel;'; put '%end;'; put '%mend mp_abort;'; put '/** @endcond */'; put '%macro mm_getstpcode('; put 'tree=/User Folders/sasdemo/somestp'; put ',name='; put ',outloc=0'; put ',outref=0'; put ',mDebug=1'; put ',showlog=NO'; put ');'; put '%local mD;'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing &sysmacroname..sas;'; put '%&mD.put _local_;'; put '%if %length(&name)>0 %then %let name=/&name;'; put '/* first, check if STP exists */'; put '%local tsuri;'; put '%let tsuri=stopifempty ;'; put 'data _null_;'; put 'format type uri tsuri value $200.;'; put 'call missing (of _all_);'; put 'path="&tree&name(StoredProcess)";'; put '/* first, find the STP ID */'; put 'if metadata_pathobj("",path,"StoredProcess",type,uri)>0 then do;'; put '/* get sourcecode */'; put 'cnt=1;'; put 'do while (metadata_getnasn(uri,"Notes",cnt,tsuri)>0);'; put 'rc=metadata_getattr(tsuri,"Name",value);'; put '&mD.put tsuri= value=;'; put 'if value="SourceCode" then do;'; put '/* found it! */'; put 'rc=metadata_getattr(tsuri,"Id",value);'; put 'call symputx(''tsuri'',value,''l'');'; put 'stop;'; put 'end;'; put 'cnt+1;'; put 'end;'; put 'end;'; put 'else put (_all_)(=);'; put 'run;'; put '%mp_abort(iftrue= (&tsuri=stopifempty)'; put ',mac=mm_getstpcode'; put ',msg=%str(&tree&name.(StoredProcess) not found!)'; put ')'; put '/**'; put '* Now we can extract the textstore'; put '*/'; put 'filename __getdoc temp lrecl=10000000;'; put 'proc metadata'; put 'in="$METAREPOSITORY'; put ''; put 'SAS1"'; put 'out=__getdoc ;'; put 'run;'; put '/* find the beginning of the text */'; put '%local start;'; put 'data _null_;'; put 'infile __getdoc lrecl=10000;'; put 'input;'; put 'start=index(_infile_,''StoredText="'');'; put 'if start then do;'; put 'call symputx("start",start+11);'; put '*putlog ''"'' _infile_ ''"'';'; put 'end;'; put 'stop;'; put '%local outeng;'; put '%if "&outloc"="0" %then %let outeng=TEMP;'; put '%else %let outeng="&outloc";'; put '%local fref;'; put '%if &outref=0 %then %let fref=%mf_getuniquefileref();'; put '%else %let fref=&outref;'; put '/* read the content, byte by byte, resolving escaped chars */'; put 'filename &fref &outeng lrecl=100000;'; put 'data _null_;'; put 'length filein 8 fileid 8;'; put 'filein = fopen("__getdoc","I",1,"B");'; put 'fileid = fopen("&fref","O",1,"B");'; put 'rec = "20"x;'; put 'length entity $6;'; put 'do while(fread(filein)=0);'; put 'x+1;'; put 'if x>&start then do;'; put 'rc = fget(filein,rec,1);'; put 'if rec=''"'' then leave;'; put 'else if rec="&" then do;'; put 'entity=rec;'; put 'do until (rec=";");'; put 'if fread(filein) ne 0 then goto getout;'; put 'rc = fget(filein,rec,1);'; put 'entity=cats(entity,rec);'; put 'end;'; put 'select (entity);'; put 'when (''&'' ) rec=''&'' ;'; put 'when (''<'' ) rec=''<'' ;'; put 'when (''>'' ) rec=''>'' ;'; put 'when (''''') rec="''" ;'; put 'when (''"'') rec=''"'' ;'; put 'when ('' '') rec=''0A''x;'; put 'when ('' '') rec=''0D''x;'; put 'when (''$'' ) rec=''$'' ;'; put 'when ('' '') rec=''09''x;'; put 'otherwise putlog "%str(WARN)ING: missing value for " entity=;'; put 'end;'; put 'rc =fput(fileid, substr(rec,1,1));'; put 'rc =fwrite(fileid);'; put 'end;'; put 'else do;'; put 'rc =fput(fileid,rec);'; put 'rc =fwrite(fileid);'; put 'end;'; put 'end;'; put 'end;'; put 'getout:'; put 'rc=fclose(filein);'; put 'rc=fclose(fileid);'; put 'run;'; put '%if &showlog=YES %then %do;'; put 'data _null_;'; put 'infile &fref lrecl=32767 end=last;'; put 'input;'; put 'if _n_=1 then putlog ''>>stpcodeBEGIN<<'';'; put 'putlog _infile_;'; put 'if last then putlog ''>>stpcodeEND<<'';'; put 'run;'; put '%end;'; put 'filename __getdoc clear;'; put '%if &outref=0 %then %do;'; put 'filename &fref clear;'; put '%end;'; put '%mend mm_getstpcode;'; put '%macro dc_getsettings();'; put '%global _program;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(syscc=&syscc on entry (&syswarningtext &syserrortext))'; put ')'; put '%if %length(&_PROGRAM)>1 %then %let root=&_program;'; put '%else %do;'; put '%global _metauser;'; put '%let _metauser=&sysuserid;'; put '/* to mimic a "real" _program we need to give a dummy role and stp name */'; put '%let root=/dummyRole/dummyName;'; put '%end;'; put '/* the DC precode is stored in the Admin folder in the root of'; put 'the project. Lets find that root. */'; put '%put &=root;'; put '%let root=%mf_getapploc();'; put '%put &=root;'; put '/* Now we know the root location we can retrieve the params */'; put '%let temploc=%sysfunc(pathname(work))/settings.txt;'; put '%mm_getstpcode(tree=&root/services/public'; put ',name=Data_Controller_Settings'; put ',outloc=&temploc'; put ')'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Unable to run getstpcode)'; put ')'; put 'filename _getsets "&temploc" lrecl=2000;'; put '/*'; put 'Do not use mp_include here - this puts a copy in every service, which creates'; put 'compilation problems when calling services from mp_include'; put '*/'; put '%inc _getsets/source2;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Problem running &sysmacroname (&syswarningtext &syserrortext))'; put ')'; put '%mend dc_getsettings;'; put '%macro mf_fmtdttm('; put ')/*/STORE SOURCE*/;'; put '%if "&sysver"="9.2" or "&sysver"="9.3"'; put 'or ("&sysver"="9.4" and "%substr(&SYSVLONG,9,1)" le "3")'; put 'or "%substr(&sysver,1,1)"="4"'; put 'or "%substr(&sysver,1,1)"="5"'; put '%then %do;DATETIME19.3%end;'; put '%else %do;E8601DT26.6%end;'; put '%mend mf_fmtdttm;'; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '%macro mp_init(prefix=SASJS'; put ')/*/STORE SOURCE*/;'; put '%if %symexist(SASJS_PREFIX) %then %return; /* only run once */'; put '%global'; put 'SASJS_PREFIX /* the ONLY hard-coded global macro variable in SASjs */'; put '&prefix._FUNCTIONS /* used in mcf_init() to track core function compilation */'; put '&prefix._INIT_NUM /* initialisation time as numeric */'; put '&prefix._INIT_DTTM /* initialisation time in E8601DT26.6 format */'; put '&prefix.WORK /* avoid typing %sysfunc(pathname(work)) every time */'; put ';'; put '%let sasjs_prefix=&prefix;'; put 'data _null_;'; put 'dttm=datetime();'; put 'call symputx("&sasjs_prefix._init_num",dttm,''g'');'; put 'call symputx("&sasjs_prefix._init_dttm",put(dttm,E8601DT26.6),''g'');'; put 'call symputx("&sasjs_prefix.work",pathname(''WORK''),''g'');'; put 'run;'; put 'options'; put 'compress=CHAR /* default is none so ensure we have something! */'; put 'datastmtchk=ALLKEYWORDS /* protection from overwriting input datasets */'; put 'errorcheck=STRICT /* catch errs in libname/filename statements */'; put 'fmterr /* ensure err when a format cannot be found */'; put 'mergenoby=%str(ERR)OR /* throw err when a merge has no BY variables */'; put 'missing=. /* changing this can cause hard to detect errs */'; put 'noquotelenmax /* avoid warnings for long strings */'; put 'noreplace /* avoid overwriting permanent datasets */'; put 'ps=max /* reduce log size slightly */'; put 'ls=max /* reduce log even more and avoid word truncation */'; put 'validmemname=COMPATIBLE /* avoid special characters etc in table names */'; put 'validvarname=V7 /* avoid special characters etc in variable names */'; put 'varinitchk=%str(ERR)OR /* avoid data mistakes from variable name typos */'; put 'varlenchk=%str(ERR)OR /* fail hard if truncation (data loss) can result */'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5" %then %do;'; put 'noautocorrect /* disallow misspelled procedure names */'; put 'dsoptions=note2err /* undocumented - convert bad NOTEs to ERRs */'; put '%end;'; put ';'; put '%mend mp_init;'; put '%macro mpeinit(fetch=YES);'; put '%global mpeinit'; put 'mpeadmins /* group with unrestricted Meditor access */'; put 'mpelocapprovals /* location for landing and staging files */'; put 'mpelib /* location of configuration tables for DC */'; put 'dc_repo_users /* location of user / group metadata */'; put 'dc_licence_key /* extracted in dc_getsettings */'; put 'dc_activation_key /* extracted in dc_getsettings */'; put 'dc_locale /* extracted in dc_getsettings */'; put 'dc_dttmtfmt /* can be overridden in dc_getsettings */'; put '_debug'; put ';'; put '%if &mpeinit=1 %then %return;'; put '%else %let mpeinit=1;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(Problem on service startup (&syswarningtext &syserrortext))'; put ')'; put '%mp_init()'; put '%if &fetch=YES %then %do;'; put '%webout(FETCH)'; put '%end;'; put '%global _CLIENTNAME;'; put '%mp_abort(iftrue= (&_CLIENTNAME=SAS Enterprise Guide)'; put ',mac=&_program..sas'; put ',msg=%str(Data Controller is a web app and should not be executed from EG)'; put ')'; put 'options urlencoding=utf8 nobomfile lrecl=32767;'; put '%let perf=%sysfunc(datetime());'; put '%put perfdiff: 0;'; put '%let dc_locale=SYSTEM; /* default if not set */'; put '/**'; put '* E8601DT26.6 has widest database support - but not all SAS flavours can'; put '* handle it. Override in the settings STP if needed.'; put '*/'; put 'data _null_;'; put 'dc_dttmtfmt=''"%sysfunc(datetime(),''!!"%mf_fmtdttm()"!!'')"dt'';'; put 'call symputx(''dc_dttmtfmt'',dc_dttmtfmt);'; put 'put dc_dttmtfmt=;'; put 'run;'; put '%put &=dc_dttmtfmt;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc prior to dc_getsettings)'; put ')'; put '%dc_getsettings()'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc after dc_getsettings)'; put ')'; put 'data _null_;'; put 'set &DC_LIBREF..mpe_config(where=('; put 'var_scope="DC"'; put 'and &dc_dttmtfmt lt tx_to'; put 'and var_active=1'; put '));'; put 'call symputx(var_name,var_value,''G'');'; put 'putlog var_name "=" var_value;'; put 'run;'; put '%let mpelib=&dc_libref;'; put '%let mpeadmins=&dc_admin_group;'; put '%let mpelocapprovals=&dc_staging_area;'; put '%let dc_repo_users=&dc_repo_users;'; put '%if &dc_locale ne SYSTEM %then %do;'; put 'options locale=&dc_locale;'; put '%end;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(Problem during compilation or with STP precode (&syswarningtext))'; put ')'; put '%mend mpeinit;'; put '%macro mf_mval(var);'; put '%if %symexist(&var) %then %do;'; put '%superq(&var)'; put '%end;'; put '%mend mf_mval;'; put '%macro mf_trimstr(basestr,trimstr);'; put '%local baselen trimlen trimval;'; put '/* return if basestr is shorter than trimstr (or 0) */'; put '%let baselen=%length(%superq(basestr));'; put '%let trimlen=%length(%superq(trimstr));'; put '%if &baselen < &trimlen or &baselen=0 %then %return;'; put '/* obtain the characters from the end of basestr */'; put '%let trimval=%qsubstr(%superq(basestr)'; put ',%length(%superq(basestr))-&trimlen+1'; put ',&trimlen);'; put '/* compare and if matching, chop it off! */'; put '%if %superq(basestr)=%superq(trimstr) %then %do;'; put '%return;'; put '%end;'; put '%else %if %superq(trimval)=%superq(trimstr) %then %do;'; put '%qsubstr(%superq(basestr),1,%length(%superq(basestr))-&trimlen)'; put '%end;'; put '%else %do;'; put '&basestr'; put '%end;'; put '%mend mf_trimstr;'; put '%macro mf_getplatform(switch'; put ')/*/STORE SOURCE*/;'; put '%local a b c;'; put '%if &switch.NONE=NONE %then %do;'; put '%if %symexist(sasjsprocessmode) %then %do;'; put '%if &sasjsprocessmode=Stored Program %then %do;'; put 'SASJS'; put '%return;'; put '%end;'; put '%end;'; put '%if %symexist(sysprocessmode) %then %do;'; put '%if "&sysprocessmode"="SAS Object Server"'; put 'or "&sysprocessmode"= "SAS Compute Server" %then %do;'; put 'SASVIYA'; put '%end;'; put '%else %if "&sysprocessmode"="SAS Stored Process Server"'; put 'or "&sysprocessmode"="SAS Workspace Server"'; put '%then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if %symexist(_metaport) or %symexist(_metauser) %then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if &switch=SASSTUDIO %then %do;'; put '/* return the version of SAS Studio else 0 */'; put '%if %mf_mval(_CLIENTAPP)=%str(SAS Studio) %then %do;'; put '%let a=%mf_mval(_CLIENTVERSION);'; put '%let b=%scan(&a,1,.);'; put '%if %eval(&b >2) %then %do;'; put '&b'; put '%end;'; put '%else 0;'; put '%end;'; put '%else 0;'; put '%end;'; put '%else %if &switch=VIYARESTAPI %then %do;'; put '%mf_trimstr(%sysfunc(getoption(servicesbaseurl)),/)'; put '%end;'; put '%mend mf_getplatform;'; put '%macro mpeterm();'; put '%local oldloc;'; put 'data _null_;'; put 'if symexist(''SYSPRINTTOLOG'') then oldloc=symget(''SYSPRINTTOLOG'');'; put 'else oldloc=getoption(''LOG'');'; put 'if subpad(oldloc,1,1) not in (''"'',"''",'' '') then oldloc=quote(cats(oldloc));'; put 'call symputx(''oldloc'',oldloc,''l'');'; put 'run;'; put '%if %length(&oldloc)>0 %then %do;'; put 'proc printto log=log;'; put 'run;'; put 'data _null_;'; put 'infile &oldloc;'; put 'input; putlog _infile_;'; put 'run;'; put '%end;'; put '%if %sysfunc(exist(&dc_libref..mpe_requests)) and %mf_getplatform() ne SASVIYA'; put '%then %do;'; put 'data ;'; put 'if 0 then set &dc_libref..mpe_requests;'; put 'request_dttm=%sysfunc(datetime());'; put 'request_user="%mf_getuser()";'; put 'request_service="%scan(&_program,-2,/)/%scan(&_program,-1,/)";'; put 'request_params='''';'; put 'output;stop;'; put 'proc append base=&dc_libref..mpe_requests data=&syslast force nowarn;'; put 'run;'; put '%end;'; put '%mend mpeterm;'; put '%macro mm_getusers('; put 'outds=work.mm_getusers,'; put 'user=0'; put ')/*/STORE SOURCE*/;'; put 'filename response temp;'; put '%if %superq(user)=0 %then %do;'; put 'proc metadata in= '''; put '$METAREPOSITORY'; put 'Person'; put 'SAS'; put '0'; put ''; put ''; put ''; put ''; put ''; put ''''; put 'out=response;'; put 'run;'; put '%end;'; put '%else %do;'; put 'filename inref temp;'; put 'data _null_;'; put 'file inref;'; put 'put "";'; put 'put "$METAREPOSITORY";'; put 'put "Person";'; put 'put "SAS";'; put 'put "";'; put 'put "128";'; put 'put "";'; put 'put "";'; put 'put '''';'; put 'put "";'; put 'length string $10000;'; put 'string=cats('''');'; put 'put string;'; put 'put "";'; put 'put "";'; put 'run;'; put 'proc metadata in=inref out=response;'; put 'run;'; put '%end;'; put 'filename sxlemap temp;'; put 'data _null_;'; put 'file sxlemap;'; put 'put '''';'; put 'put "/GetMetadataObjects/Objects/Person";'; put 'put "";'; put 'put '''';'; put 'put "/GetMetadataObjects/Objects/Person/@Id";'; put 'put "characterstring32";'; put 'put '''';'; put 'put "/GetMetadataObjects/Objects/Person/@Name";'; put 'put "characterstring256";'; put 'put ''
'';'; put 'run;'; put 'libname _XML_ xml xmlfileref=response xmlmap=sxlemap;'; put 'proc sort data= _XML_.SASObjects out=&outds;'; put 'by name;'; put 'run;'; put 'filename sxlemap clear;'; put 'filename response clear;'; put 'libname _XML_ clear;'; put '%mend mm_getusers;'; put '%macro mm_getrepos('; put 'outds=work.mm_getrepos'; put ')/*/STORE SOURCE*/;'; put '* use a temporary fileref to hold the response;'; put 'filename response temp;'; put '/* get list of libraries */'; put 'proc metadata in='; put '"1"'; put 'out=response;'; put 'run;'; put '/* write the response to the log for debugging */'; put '/*'; put 'data _null_;'; put 'infile response lrecl=1048576;'; put 'input;'; put 'put _infile_;'; put 'run;'; put '*/'; put '/* create an XML map to read the response */'; put 'filename sxlemap temp;'; put 'data _null_;'; put 'file sxlemap;'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository";'; put 'put "";'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Id";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Name";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Desc";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@DefaultNS";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@RepositoryType";'; put 'put "characterstring20";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@RepositoryFormat";'; put 'put "characterstring10";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@Access";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@CurrentAccess";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@PauseState";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Path";'; put 'put "";'; put 'put "characterstring256";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Engine";'; put 'put "";'; put 'put "characterstring8";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Options";'; put 'put "";'; put 'put "characterstring32";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@MetadataCreated";'; put 'put "characterstring24";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@MetadataUpdated";'; put 'put "characterstring24";'; put 'put '''';'; put 'put ''
'';'; put 'run;'; put 'libname _XML_ xml xmlfileref=response xmlmap=sxlemap;'; put 'proc sort data= _XML_.SASRepos out=&outds;'; put 'by name;'; put 'run;'; put '/* clear references */'; put 'filename sxlemap clear;'; put 'filename response clear;'; put 'libname _XML_ clear;'; put '%mend mm_getrepos;'; put '%macro dc_getusers(outds=mm_getlibs);'; put '/* take current repository */'; put '%local repo repocnt x;'; put '%let repo=%sysfunc(getoption(metarepository));'; put '/* get list of repositories and filter */'; put '%mm_getrepos(outds=repos)'; put '%let repocnt=0;'; put 'data repos;'; put 'set repos;'; put 'where repositorytype in(''CUSTOM'',''FOUNDATION'')'; put '& upcase(name) ne "%upcase(&repo)";'; put 'keep id name ;'; put 'call symputx(cats(''repo'',_n_),name,''l'');'; put 'call symputx(''repocnt'',_n_,''l'');'; put 'run;'; put '%put _local_;'; put '%mm_getusers(outds=&outds)'; put '%do x=1 %to &repocnt;'; put 'options metarepository=&&repo&x;'; put '%mm_getusers(outds=&outds.a)'; put 'proc append base=&outds data=&outds.a;'; put 'run;'; put '%end;'; put 'options metarepository=&repo;'; put '%mend dc_getusers;'; put '* SAS Macros end;'; put '* SAS Includes start;'; put '* SAS Includes end;'; put '* Binary Files start;'; put '* Binary Files end;'; put '* ServiceInit start;'; put 'options noquotelenmax ps=max;'; put '/* create dummy macros to prevent stpbegin / stpend from corrupting sessions */'; put '%macro stpbegin();'; put '%put NOTE: the STPBEGIN macro should not be used for web apps!;'; put '%mend stpbegin;'; put '%macro stpend();'; put '%put NOTE: the STPEND macro should not be used for web apps!;'; put '%mend stpend;'; put '* ServiceInit end;'; put '* Service start;'; put '/**'; put '@file usermembers.sas'; put '@brief List all SAS users'; put '@details Gets a list of all SAS users'; put '

SAS Macros

'; put '@li dc_getusers.sas'; put '@li mpeinit.sas'; put '@version 9.3'; put '@author 4GL Apps Ltd'; put '@copyright 4GL Apps Ltd. This code may only be used within Data Controller'; put 'and may not be re-distributed or re-sold without the express permission of'; put '4GL Apps Ltd.'; put '**/'; put '%mpeinit()'; put '%dc_getusers(outds=users)'; put '%webout(OPEN)'; put '%webout(OBJ,users)'; put '%webout(CLOSE)'; put '* Service end;'; run; %mm_createwebservice(path=&appLoc/&path, name=&service, code=sascode, server=&serverName, replace=yes) filename sascode clear; %let service=usermembersbygroup; filename sascode temp lrecl=32767; data _null_; file sascode; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '/**'; put '@file mp_jsonout.sas'; put '@brief Writes JSON in SASjs format to a fileref'; put '@details This macro can be used to OPEN a JSON stream and send one or more'; put 'tables as arrays of rows, where each row can be an object or a nested array.'; put 'There are two engines available - DATASTEP or PROCJSON.'; put 'PROC JSON is fast but will produce errs like the ones below if'; put 'special chars are encountered.'; put '> (ERR)OR: Some code points did not transcode.'; put '> An object or array close is not valid at this point in the JSON text.'; put '> Date value out of range'; put 'If this happens, try running with ENGINE=DATASTEP.'; put 'The DATASTEP engine is used to handle special SAS missing numerics, and'; put 'can also convert entire datasets to formatted values. Output JSON is always'; put 'in UTF-8.'; put 'Usage:'; put 'filename tmp temp;'; put 'data class; set sashelp.class;run;'; put '%mp_jsonout(OPEN,jref=tmp)'; put '%mp_jsonout(OBJ,class,jref=tmp)'; put '%mp_jsonout(OBJ,class,dslabel=class2,jref=tmp,showmeta=Y)'; put '%mp_jsonout(CLOSE,jref=tmp)'; put 'data _null_;'; put 'infile tmp;'; put 'input;putlog _infile_;'; put 'run;'; put 'If you are building web apps with SAS then you are strongly encouraged to use'; put 'the mX_createwebservice macros in combination with the'; put '[sasjs adapter](https://github.com/sasjs/adapter).'; put 'For more information see https://sasjs.io'; put '@param [in] action Valid values:'; put '@li OPEN - opens the JSON'; put '@li OBJ - sends a table with each row as an object'; put '@li ARR - sends a table with each row in an array'; put '@li CLOSE - closes the JSON'; put '@param [in] ds The dataset to send. Must be a work table.'; put '@param [out] jref= (_webout) The fileref to which to send the JSON'; put '@param [out] dslabel= The name to give the table in the exported JSON'; put '@param [in] fmt= (Y) Whether to keep (Y) or strip (N) formats from the table'; put '@param [in] engine= (DATASTEP) Which engine to use to send the JSON. Options:'; put '@li PROCJSON (default)'; put '@li DATASTEP (more reliable when data has non standard characters)'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to JSON'; put '

Related Files

'; put '@li mp_ds2fmtds.sas'; put '@version 9.2'; put '@author Allan Bowe'; put '@source https://github.com/sasjs/core'; put '**/'; put '%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y'; put ',engine=DATASTEP'; put ',missing=NULL'; put ',showmeta=N'; put ',maxobs=MAX'; put ')/*/STORE SOURCE*/;'; put '%local tempds colinfo fmtds i numcols numobs stmt_obs lastobs optval'; put 'tmpds1 tmpds2 tmpds3 tmpds4;'; put '%let numcols=0;'; put '%if &maxobs ne MAX %then %let stmt_obs=%str(if _n_>&maxobs then stop;);'; put '%if &action=OPEN %then %do;'; put 'options nobomfile;'; put 'data _null_;file &jref encoding=''utf-8'' lrecl=200;'; put 'put ''{"PROCESSED_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''"'';'; put 'run;'; put '%end;'; put '%else %if (&action=ARR or &action=OBJ) %then %do;'; put '/* force variable names to always be uppercase in the JSON */'; put 'options validvarname=upcase;'; put '/* To avoid issues with _webout on EBI - such as encoding diffs and truncation'; put '(https://support.sas.com/kb/49/325.html) we use temporary files */'; put 'filename _sjs1 temp lrecl=200 ;'; put 'data _null_; file _sjs1 encoding=''utf-8'';'; put 'put ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":";'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjs1 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs1 clear;'; put '/* grab col defs */'; put 'proc contents noprint data=&ds'; put 'out=_data_(keep=name type length format formatl formatd varnum label);'; put 'run;'; put '%let colinfo=%scan(&syslast,2,.);'; put 'proc sort data=&colinfo;'; put 'by varnum;'; put 'run;'; put '/* move meta to mac vars */'; put 'data &colinfo;'; put 'if _n_=1 then call symputx(''numcols'',nobs,''l'');'; put 'set &colinfo end=last nobs=nobs;'; put 'name=upcase(name);'; put '/* fix formats */'; put 'if type=2 or type=6 then do;'; put 'typelong=''char'';'; put 'length fmt $49.;'; put 'if format='''' then fmt=cats(''$'',length,''.'');'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else fmt=cats(format,formatl,''.'');'; put 'end;'; put 'else do;'; put 'typelong=''num'';'; put 'if format='''' then fmt=''best.'';'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else if formatd=0 then fmt=cats(format,formatl,''.'');'; put 'else fmt=cats(format,formatl,''.'',formatd);'; put 'end;'; put '/* 32 char unique name */'; put 'newname=''sasjs''!!substr(cats(put(md5(name),$hex32.)),1,27);'; put 'call symputx(cats(''name'',_n_),name,''l'');'; put 'call symputx(cats(''newname'',_n_),newname,''l'');'; put 'call symputx(cats(''length'',_n_),length,''l'');'; put 'call symputx(cats(''fmt'',_n_),fmt,''l'');'; put 'call symputx(cats(''type'',_n_),type,''l'');'; put 'call symputx(cats(''typelong'',_n_),typelong,''l'');'; put 'call symputx(cats(''label'',_n_),coalescec(label,name),''l'');'; put '/* overwritten when fmt=Y and a custom format exists in catalog */'; put 'if typelong=''num'' then call symputx(cats(''fmtlen'',_n_),200,''l'');'; put 'else call symputx(cats(''fmtlen'',_n_),min(32767,ceil((length+10)*1.5)),''l'');'; put 'run;'; put '%let tempds=%substr(_%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql;'; put 'select count(*) into: lastobs from &ds;'; put '%if &maxobs ne MAX %then %let lastobs=%sysfunc(min(&lastobs,&maxobs));'; put '%if &engine=PROCJSON %then %do;'; put '%if &missing=STRING %then %do;'; put '%put &sysmacroname: Special Missings not supported in proc json.;'; put '%put &sysmacroname: Switching to DATASTEP engine;'; put '%goto datastep;'; put '%end;'; put 'data &tempds;'; put 'set &ds;'; put '&stmt_obs;'; put '%if &fmt=N %then format _numeric_ best32.;;'; put '/* PRETTY is necessary to avoid line truncation in large files */'; put 'filename _sjs2 temp lrecl=131068 encoding=''utf-8'';'; put 'proc json out=_sjs2 pretty'; put '%if &action=ARR %then nokeys ;'; put ';export &tempds / nosastags fmtnumeric;'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs2 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs2 clear;'; put '%end;'; put '%else %if &engine=DATASTEP %then %do;'; put '%datastep:'; put '%if %sysfunc(exist(&ds)) ne 1 & %sysfunc(exist(&ds,VIEW)) ne 1'; put '%then %do;'; put '%put &sysmacroname: &ds NOT FOUND!!!;'; put '%return;'; put '%end;'; put '%if &fmt=Y %then %do;'; put '/**'; put '* Extract format definitions'; put '* First, by getting library locations from dictionary.formats'; put '* Then, by exporting the width using proc format'; put '* Cannot use maxw from sashelp.vformat as not always populated'; put '* Cannot use fmtinfo() as not supported in all flavours'; put '*/'; put '%let tmpds1=%substr(fmtsum%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds2=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds3=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds4=%substr(col%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql noprint;'; put 'create table &tmpds1 as'; put 'select cats(libname,''.'',memname) as FMTCAT,'; put 'FMTNAME'; put 'from dictionary.formats'; put 'where fmttype=''F'' and libname is not null'; put 'and fmtname in (select format from &colinfo where format is not null)'; put 'order by 1;'; put 'create table &tmpds2('; put 'FMTNAME char(32),'; put 'LENGTH num'; put ');'; put '%local catlist cat fmtlist i;'; put 'select distinct fmtcat into: catlist separated by '' '' from &tmpds1;'; put '%do i=1 %to %sysfunc(countw(&catlist,%str( )));'; put '%let cat=%scan(&catlist,&i,%str( ));'; put 'proc sql;'; put 'select distinct fmtname into: fmtlist separated by '' '''; put 'from &tmpds1 where fmtcat="&cat";'; put 'proc format lib=&cat cntlout=&tmpds3(keep=fmtname length);'; put 'select &fmtlist;'; put 'run;'; put 'proc sql;'; put 'insert into &tmpds2 select distinct fmtname,length from &tmpds3;'; put '%end;'; put 'proc sql;'; put 'create table &tmpds4 as'; put 'select a.*, b.length as MAXW'; put 'from &colinfo a'; put 'left join &tmpds2 b'; put 'on cats(a.format)=cats(upcase(b.fmtname))'; put 'order by a.varnum;'; put 'data _null_;'; put 'set &tmpds4;'; put 'if not missing(maxw);'; put 'call symputx('; put 'cats(''fmtlen'',_n_),'; put '/* vars need extra padding due to JSON escaping of special chars */'; put 'min(32767,ceil((max(length,maxw)+10)*1.5))'; put ',''l'''; put ');'; put 'run;'; put '/* configure varlenchk - as we are explicitly shortening the variables */'; put '%let optval=%sysfunc(getoption(varlenchk));'; put 'options varlenchk=NOWARN;'; put 'data _data_(compress=char);'; put '/* shorten the new vars */'; put 'length'; put '%do i=1 %to &numcols;'; put '&&name&i $&&fmtlen&i'; put '%end;'; put ';'; put '/* rename on entry */'; put 'set &ds(rename=('; put '%do i=1 %to &numcols;'; put '&&name&i=&&newname&i'; put '%end;'; put '));'; put '&stmt_obs;'; put 'drop'; put '%do i=1 %to &numcols;'; put '&&newname&i'; put '%end;'; put ';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=num %then %do;'; put '&&name&i=cats(put(&&newname&i,&&fmt&i));'; put '%end;'; put '%else %do;'; put '&&name&i=put(&&newname&i,&&fmt&i);'; put '%end;'; put '%end;'; put 'if _error_ then do;'; put 'call symputx(''syscc'',1012);'; put 'stop;'; put 'end;'; put 'run;'; put '%let fmtds=&syslast;'; put 'options varlenchk=&optval;'; put '%end;'; put 'proc format; /* credit yabwon for special null removal */'; put 'value bart (default=40)'; put '%if &missing=NULL %then %do;'; put '._ - .z = null'; put '%end;'; put '%else %do;'; put '._ = [quote()]'; put '. = null'; put '.a - .z = [quote()]'; put '%end;'; put 'other = [best.];'; put 'data &tempds;'; put 'attrib _all_ label='''';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'length &&name&i $&&fmtlen&i...;'; put 'format &&name&i $&&fmtlen&i...;'; put '%end;'; put '%end;'; put '%if &fmt=Y %then %do;'; put 'set &fmtds;'; put '%end;'; put '%else %do;'; put 'set &ds;'; put '%end;'; put '&stmt_obs;'; put 'format _numeric_ bart.;'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'if findc(&&name&i,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put '&&name&i=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,&&name&i)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else &&name&i=quote(cats(&&name&i));'; put '%end;'; put '%end;'; put 'run;'; put 'filename _sjs3 temp lrecl=131068 ;'; put 'data _null_;'; put 'file _sjs3 encoding=''utf-8'';'; put 'if _n_=1 then put "[";'; put 'set &tempds;'; put 'if _n_>1 then put "," @; put'; put '%if &action=ARR %then "[" ; %else "{" ;'; put '%do i=1 %to &numcols;'; put '%if &i>1 %then "," ;'; put '%if &action=OBJ %then """&&name&i"":" ;'; put '"&&name&i"n /* name literal for reserved variable names */'; put '%end;'; put '%if &action=ARR %then "]" ; %else "}" ; ;'; put '/* close out the table */'; put 'data _null_;'; put 'file _sjs3 mod encoding=''utf-8'';'; put 'put '']'';'; put 'run;'; put 'data _null_;'; put 'infile _sjs3 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs3 clear;'; put '%end;'; put 'proc sql;'; put 'drop table &colinfo, &tempds;'; put '%if %substr(&showmeta,1,1)=Y %then %do;'; put 'filename _sjs4 temp lrecl=131068 encoding=''utf-8'';'; put 'data _null_;'; put 'file _sjs4;'; put 'length label $350;'; put 'put ", ""$%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":{""vars"":{";'; put 'do i=1 to &numcols;'; put 'name=quote(trim(symget(cats(''name'',i))));'; put 'format=quote(trim(symget(cats(''fmt'',i))));'; put 'label=quote(prxchange(''s/\\/\\\\/'',-1,trim(symget(cats(''label'',i)))));'; put 'length=quote(trim(symget(cats(''length'',i))));'; put 'type=quote(trim(symget(cats(''typelong'',i))));'; put 'if i>1 then put "," @@;'; put 'put name '':{"format":'' format '',"label":'' label'; put ''',"length":'' length '',"type":'' type ''}'';'; put 'end;'; put 'put ''}}'';'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs4 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs4 clear;'; put '%end;'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put 'data _null_; file &jref encoding=''utf-8'' mod ;'; put 'put "}";'; put 'run;'; put '%end;'; put '%mend mp_jsonout;'; put '/**'; put '@file mm_webout.sas'; put '@brief Send data to/from SAS Stored Processes'; put '@details This macro should be added to the start of each Stored Process,'; put '**immediately** followed by a call to:'; put '%mm_webout(FETCH)'; put 'This will read all the input data and create same-named SAS datasets in the'; put 'WORK library. You can then insert your code, and send data back using the'; put 'following syntax:'; put 'data some datasets; * make some data ;'; put 'retain some columns;'; put 'run;'; put '%mm_webout(OPEN)'; put '%mm_webout(ARR,some) * Array format, fast, suitable for large tables ;'; put '%mm_webout(OBJ,datasets) * Object format, easier to work with ;'; put 'Finally, wrap everything up send some helpful system variables too'; put '%mm_webout(CLOSE)'; put '@param [in] action Either FETCH, OPEN, ARR, OBJ or CLOSE'; put '@param [in] ds The dataset to send back to the frontend'; put '@param [out] dslabel= Value to use instead of table name for sending to JSON'; put '@param [in] fmt= (N) Setting Y converts all vars to their formatted values'; put '@param [out] fref= (_webout) The fileref to which to write the JSON'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] workobs= (0) When set to a positive integer, will create a new'; put 'output object (WORK) which contains this number of observations from all'; put 'tables in the WORK library.'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to output JSON'; put '

SAS Macros

'; put '@li mp_jsonout.sas'; put '

Related Macros

'; put '@li ms_webout.sas'; put '@li mv_webout.sas'; put '@version 9.3'; put '@author Allan Bowe'; put '**/'; put '%macro mm_webout(action,ds,dslabel=,fref=_webout,fmt=N,missing=NULL'; put ',showmeta=N,maxobs=MAX,workobs=0'; put ');'; put '%global _webin_file_count _webin_fileref1 _webin_name1 _program _debug'; put 'sasjs_tables;'; put '%local i tempds jsonengine;'; put '/* see https://github.com/sasjs/core/issues/41 */'; put '%if "%upcase(&SYSENCODING)" ne "UTF-8" %then %let jsonengine=PROCJSON;'; put '%else %let jsonengine=DATASTEP;'; put '%if &action=FETCH %then %do;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'options mprint notes mprintnest;'; put '%end;'; put '%let _webin_file_count=%eval(&_webin_file_count+0);'; put '/* now read in the data */'; put '%do i=1 %to &_webin_file_count;'; put '%if &_webin_file_count=1 %then %do;'; put '%let _webin_fileref1=&_webin_fileref;'; put '%let _webin_name1=&_webin_name;'; put '%end;'; put 'data _null_;'; put 'infile &&_webin_fileref&i termstr=crlf;'; put 'input;'; put 'call symputx(''input_statement'',_infile_);'; put 'putlog "&&_webin_name&i input statement: " _infile_;'; put 'stop;'; put 'data &&_webin_name&i;'; put 'infile &&_webin_fileref&i firstobs=2 dsd termstr=crlf encoding=''utf-8'';'; put 'input &input_statement;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'if _n_<20 then putlog _infile_;'; put '%end;'; put 'run;'; put '%let sasjs_tables=&sasjs_tables &&_webin_name&i;'; put '%end;'; put '%end;'; put '%else %if &action=OPEN %then %do;'; put '/* fix encoding */'; put 'OPTIONS NOBOMFILE;'; put '/**'; put '* check xengine type to avoid the below err message:'; put '* > Function is only valid for filerefs using the CACHE access method.'; put '*/'; put 'data _null_;'; put 'set sashelp.vextfl(where=(fileref="_WEBOUT"));'; put 'if xengine=''STREAM'' then do;'; put 'rc=stpsrv_header(''Content-type'',"text/html; encoding=utf-8");'; put 'end;'; put 'run;'; put '/* setup json */'; put 'data _null_;file &fref encoding=''utf-8'';'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutBEGIN<<'';'; put '%end;'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'run;'; put '%end;'; put '%else %if &action=ARR or &action=OBJ %then %do;'; put '%mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt,jref=&fref'; put ',engine=&jsonengine,missing=&missing,showmeta=&showmeta,maxobs=&maxobs'; put ')'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put '/* To avoid issues with _webout on EBI we use a temporary file */'; put 'filename _sjsref temp lrecl=131068;'; put '%if %str(&workobs) > 0 %then %do;'; put '/* if debug mode, send back first XX records of each work table also */'; put 'data;run;%let tempds=%scan(&syslast,2,.);'; put 'ods output Members=&tempds;'; put 'proc datasets library=WORK memtype=data;'; put '%local wtcnt;%let wtcnt=0;'; put 'data _null_;'; put 'set &tempds;'; put 'if not (upcase(name) =:"DATA"); /* ignore temp datasets */'; put 'i+1;'; put 'call symputx(cats(''wt'',i),name,''l'');'; put 'call symputx(''wtcnt'',i,''l'');'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put ",""WORK"":{";'; put '%do i=1 %to &wtcnt;'; put '%let wt=&&wt&i;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'dsid=open("WORK.&wt",''is'');'; put 'nlobs=attrn(dsid,''NLOBS'');'; put 'nvars=attrn(dsid,''NVARS'');'; put 'rc=close(dsid);'; put 'if &i>1 then put '',''@;'; put 'put " ""&wt"" : {";'; put 'put ''"nlobs":'' nlobs;'; put 'put '',"nvars":'' nvars;'; put '%mp_jsonout(OBJ,&wt,jref=_sjsref,dslabel=first10rows,showmeta=Y'; put ',maxobs=&workobs'; put ')'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put '%end;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put 'run;'; put '%end;'; put '/* close off json */'; put 'data _null_;file _sjsref mod encoding=''utf-8'';'; put 'length SYSPROCESSNAME syserrortext syswarningtext autoexec $512;'; put 'put ",""_DEBUG"" : ""&_debug"" ";'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'autoexec=quote(urlencode(trim(getoption(''autoexec''))));'; put 'put '',"AUTOEXEC" : '' autoexec;'; put 'put ",""MF_GETUSER"" : ""%mf_getuser()"" ";'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'put ",""SYSENCODING"" : ""&sysencoding"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSPROCESSID"" : ""&SYSPROCESSID"" ";'; put 'put ",""SYSPROCESSMODE"" : ""&SYSPROCESSMODE"" ";'; put 'SYSPROCESSNAME=quote(urlencode(cats(SYSPROCESSNAME)));'; put 'put ",""SYSPROCESSNAME"" : " SYSPROCESSNAME;'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put '',"SYSWARNINGTEXT" : '' syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'length memsize $32;'; put 'memsize="%sysfunc(INPUTN(%sysfunc(getoption(memsize)), best.),sizekmg.)";'; put 'memsize=quote(cats(memsize));'; put 'put '',"MEMSIZE" : '' memsize;'; put 'put "}" @;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutEND<<'';'; put '%end;'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjsref lrecl=1 recfm=n;'; put 'file &fref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjsref clear;'; put '%end;'; put '%mend mm_webout;'; put '%macro webout(action,ds,dslabel=,fmt=,missing=NULL,showmeta=NO,maxobs=MAX);'; put '%mm_webout(&action,ds=&ds,dslabel=&dslabel,fmt=&fmt'; put ',missing=&missing'; put ',showmeta=&showmeta'; put ',maxobs=&maxobs'; put ') %mend;'; put '/* provide additional debug info */'; put '%global _program;'; put '%put &=syscc;'; put '%put user=%mf_getuser();'; put '%put pgm=&_program;'; put '%put timestamp=%sysfunc(datetime(),datetime19.);'; put '* Service Variables start;'; put '* Service Variables end;'; put '* SAS Macros start;'; put '%macro mf_getapploc(pgm);'; put '%if "&pgm"="" %then %do;'; put '%if %symexist(_program) %then %let pgm=&_program;'; put '%else %do;'; put '%put &sysmacroname: No value provided and no _program variable available;'; put '%return;'; put '%end;'; put '%end;'; put '%local root;'; put '/**'; put '* First check we are not in the tests/macros folder (which has no subfolders)'; put '* or specifically in the testsetup or testteardown services'; put '*/'; put '%if %index(&pgm,/tests/macros/)'; put 'or %index(&pgm,/tests/testsetup)'; put 'or %index(&pgm,/tests/testteardown)'; put '%then %do;'; put '%let root=%substr(&pgm,1,%index(&pgm,/tests)-1);'; put '&root'; put '%return;'; put '%end;'; put '/**'; put '* Next, move up two levels to avoid matches on subfolder or service name'; put '*/'; put '%let root=%substr(&pgm,1,%length(&pgm)-%length(%scan(&pgm,-1,/))-1);'; put '%let root=%substr(&root,1,%length(&root)-%length(%scan(&root,-1,/))-1);'; put '%if %index(&root,/tests/) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/tests/)-1);'; put '%end;'; put '%else %if %index(&root,/services) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/services)-1);'; put '%end;'; put '%else %if %index(&root,/jobs) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/jobs)-1);'; put '%end;'; put '%else %put &sysmacroname: Could not find an app location from &pgm;'; put '&root'; put '%mend mf_getapploc ;'; put '%macro mf_getuniquefileref(prefix=_,maxtries=1000,lrecl=32767);'; put '%local rc fname;'; put '%if &prefix=0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%end;'; put '%else %do;'; put '%local x len;'; put '%let len=%eval(8-%length(&prefix));'; put '%let x=0;'; put '%do x=0 %to &maxtries;'; put '%let fname=&prefix%substr(%sysfunc(ranuni(0)),3,&len);'; put '%if %sysfunc(fileref(&fname)) > 0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%return;'; put '%end;'; put '%end;'; put '%put unable to find available fileref after &maxtries attempts;'; put '%end;'; put '%mend mf_getuniquefileref;'; put '%macro mp_abort(mac=mp_abort.sas, type=, msg=, iftrue=%str(1=1)'; put ', errds=work.mp_abort_errds'; put ', mode=REGULAR'; put ')/*/STORE SOURCE*/;'; put '%global sysprocessmode sysprocessname sasjs_stpsrv_header_loc sasjsprocessmode;'; put '%local fref fid i;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%put NOTE: /// mp_abort macro executing //;'; put '%if %length(&mac)>0 %then %put NOTE- called by &mac;'; put '%put NOTE - &msg;'; put '%if %symexist(_SYSINCLUDEFILEDEVICE)'; put '/* abort cancel FILE does not restart outside the INCLUDE on Viya 3.5 */'; put 'and %superq(SYSPROCESSNAME) ne %str(Compute Server)'; put '%then %do;'; put '%if "*&_SYSINCLUDEFILEDEVICE*" ne "**" %then %do;'; put 'data &errds;'; put 'iftrue=''1=1'';'; put 'length mac $100 msg $5000;'; put 'mac=symget(''mac'');'; put 'msg=symget(''msg'');'; put 'run;'; put 'data _null_;'; put 'abort cancel FILE;'; put 'run;'; put '%return;'; put '%end;'; put '%end;'; put '/* Web App Context */'; put '%if %symexist(_PROGRAM)'; put 'or %superq(SYSPROCESSNAME) = %str(Compute Server)'; put 'or &mode=INCLUDE'; put '%then %do;'; put 'options obs=max replace mprint;'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5"'; put '%then %do;'; put 'options nosyntaxcheck;'; put '%end;'; put '%if &mode=INCLUDE %then %do;'; put '%if %sysfunc(exist(&errds))=1 %then %do;'; put 'data _null_;'; put 'set &errds;'; put 'call symputx(''iftrue'',iftrue,''l'');'; put 'call symputx(''mac'',mac,''l'');'; put 'call symputx(''msg'',msg,''l'');'; put 'putlog (_all_)(=);'; put 'run;'; put '%if (&iftrue)=0 %then %return;'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: No include errors found;'; put '%return;'; put '%end;'; put '%end;'; put '/* extract log errs / warns, if exist */'; put '%local logloc logline;'; put '%global logmsg; /* capture global messages */'; put '%if %symexist(SYSPRINTTOLOG) %then %let logloc=&SYSPRINTTOLOG;'; put '%else %let logloc=%qsysfunc(getoption(LOG));'; put 'proc printto log=log;run;'; put '%let logline=0;'; put '%if %length(&logloc)>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input; putlog _infile_;'; put 'i=1;'; put 'retain logonce 0;'; put 'if ('; put '_infile_=:"%str(WARN)ING" or _infile_=:"%str(ERR)OR"'; put ') and logonce=0 then'; put 'do;'; put 'call symputx(''logline'',_n_);'; put 'logonce+1;'; put 'end;'; put 'run;'; put '/* capture log including lines BEFORE the err */'; put '%if &logline>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input;'; put 'i=1;'; put 'stoploop=0;'; put 'if _n_ ge &logline-15 and stoploop=0 then do until (i>22);'; put 'call symputx(''logmsg'',catx(''\n'',symget(''logmsg''),_infile_));'; put 'input;'; put 'i+1;'; put 'stoploop=1;'; put 'end;'; put 'if stoploop=1 then stop;'; put 'run;'; put '%end;'; put '%end;'; put '%if %symexist(SYS_JES_JOB_URI) %then %do;'; put '/* setup webout for Viya */'; put 'options nobomfile;'; put '%if "X&SYS_JES_JOB_URI.X"="XX" %then %do;'; put 'filename _webout temp lrecl=999999 mod;'; put '%end;'; put '%else %do;'; put 'filename _webout filesrvc parenturi="&SYS_JES_JOB_URI"'; put 'name="_webout.json" lrecl=999999 mod;'; put '%end;'; put '%end;'; put '%else %if %sysfunc(filename(fref,&sasjs_stpsrv_header_loc))=0 %then %do;'; put 'options nobomfile;'; put '/* set up http header for SASjs Server */'; put '%let fid=%sysfunc(fopen(&fref,A));'; put '%if &fid=0 %then %do;'; put '%put %str(ERR)OR: %sysfunc(sysmsg());'; put '%return;'; put '%end;'; put '%let rc=%sysfunc(fput(&fid,%str(Content-Type: application/json)));'; put '%let rc=%sysfunc(fwrite(&fid));'; put '%let rc=%sysfunc(fclose(&fid));'; put '%let rc=%sysfunc(filename(&fref));'; put '%end;'; put '/* send response in SASjs JSON format */'; put 'data _null_;'; put 'file _webout mod lrecl=32000 encoding=''utf-8'';'; put 'length msg syswarningtext syserrortext $32767 mode $10 ;'; put 'sasdatetime=datetime();'; put 'msg=symget(''msg'');'; put '%if &logline>0 %then %do;'; put 'msg=cats(msg,''\n\nLog Extract:\n'',symget(''logmsg''));'; put '%end;'; put '/* escape the escapes */'; put 'msg=tranwrd(msg,''\'',''\\'');'; put '/* escape the quotes */'; put 'msg=tranwrd(msg,''"'',''\"'');'; put '/* ditch the CRLFs as chrome complains */'; put 'msg=compress(msg,,''kw'');'; put '/* quote without quoting the quotes (which are escaped instead) */'; put 'msg=cats(''"'',msg,''"'');'; put 'if symexist(''_debug'') then debug=quote(trim(symget(''_debug'')));'; put 'else debug=''""'';'; put 'if symget(''sasjsprocessmode'')=''Stored Program'' then mode=''SASJS'';'; put 'if mode ne ''SASJS'' then put ''>>weboutBEGIN<<'';'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'put '',"sasjsAbort" : [{'';'; put 'put '' "MSG":'' msg ;'; put 'put '' ,"MAC": "'' "&mac" ''"}]'';'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'put '',"_DEBUG":'' debug ;'; put 'if symexist(''_metauser'') then do;'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put 'end;'; put 'if symexist(''SYS_JES_JOB_URI'') then do;'; put 'SYS_JES_JOB_URI=quote(trim(symget(''SYS_JES_JOB_URI'')));'; put 'put '',"SYS_JES_JOB_URI": '' SYS_JES_JOB_URI;'; put 'end;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put ",""SYSWARNINGTEXT"" : " syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'put "}" ;'; put 'if mode ne ''SASJS'' then put ''>>weboutEND<<'';'; put 'run;'; put '%put _all_;'; put '%if "&sysprocessmode " = "SAS Stored Process Server " %then %do;'; put 'data _null_;'; put 'putlog ''stpsrvset program err and syscc'';'; put 'rc=stpsrvset(''program error'', 0);'; put 'call symputx("syscc",0,"g");'; put 'run;'; put '%if &sysscp=WIN'; put 'and 1=0 /* deprecating this logic until we figure out a consistent abort */'; put 'and "%substr(%str(&sysvlong ),1,8)"="9.04.01M"'; put 'and "%substr(%str(&sysvlong ),9,1)">"5" %then %do;'; put '/* skip approach (below) does not work in windows m6+ envs */'; put 'endsas;'; put '%end;'; put '%else %do;'; put '/**'; put '* endsas kills 9.4m3 deployments by orphaning multibridges.'; put '* Abort variants are ungraceful (non zero return code)'; put '* This approach lets SAS run silently until the end :-)'; put '* Caution - fails when called within a %include within a macro'; put '* Use mp_include() to handle this.'; put '*/'; put 'filename skip temp;'; put 'data _null_;'; put 'file skip;'; put 'put ''%macro skip();'';'; put 'comment ''%mend skip; -> fix lint '';'; put 'put ''%macro skippy();'';'; put 'comment ''%mend skippy; -> fix lint '';'; put 'run;'; put '%inc skip;'; put '%end;'; put '%end;'; put '%else %if "&sysprocessmode " = "SAS Compute Server " %then %do;'; put '/* endsas kills the session making it harder to fetch results */'; put 'data _null_;'; put 'syswarningtext=symget(''syswarningtext'');'; put 'syserrortext=symget(''syserrortext'');'; put 'abort_msg=symget(''msg'');'; put 'syscc=symget(''syscc'');'; put 'sysuserid=symget(''sysuserid'');'; put 'iftrue=symget(''iftrue'');'; put 'put (_all_)(/=);'; put 'call symputx(''syscc'',0);'; put 'abort cancel nolist;'; put 'run;'; put '%end;'; put '%else %do;'; put '%abort cancel;'; put '%end;'; put '%end;'; put '%else %do;'; put '%put _all_;'; put '%abort cancel;'; put '%end;'; put '%mend mp_abort;'; put '/** @endcond */'; put '%macro mm_getstpcode('; put 'tree=/User Folders/sasdemo/somestp'; put ',name='; put ',outloc=0'; put ',outref=0'; put ',mDebug=1'; put ',showlog=NO'; put ');'; put '%local mD;'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing &sysmacroname..sas;'; put '%&mD.put _local_;'; put '%if %length(&name)>0 %then %let name=/&name;'; put '/* first, check if STP exists */'; put '%local tsuri;'; put '%let tsuri=stopifempty ;'; put 'data _null_;'; put 'format type uri tsuri value $200.;'; put 'call missing (of _all_);'; put 'path="&tree&name(StoredProcess)";'; put '/* first, find the STP ID */'; put 'if metadata_pathobj("",path,"StoredProcess",type,uri)>0 then do;'; put '/* get sourcecode */'; put 'cnt=1;'; put 'do while (metadata_getnasn(uri,"Notes",cnt,tsuri)>0);'; put 'rc=metadata_getattr(tsuri,"Name",value);'; put '&mD.put tsuri= value=;'; put 'if value="SourceCode" then do;'; put '/* found it! */'; put 'rc=metadata_getattr(tsuri,"Id",value);'; put 'call symputx(''tsuri'',value,''l'');'; put 'stop;'; put 'end;'; put 'cnt+1;'; put 'end;'; put 'end;'; put 'else put (_all_)(=);'; put 'run;'; put '%mp_abort(iftrue= (&tsuri=stopifempty)'; put ',mac=mm_getstpcode'; put ',msg=%str(&tree&name.(StoredProcess) not found!)'; put ')'; put '/**'; put '* Now we can extract the textstore'; put '*/'; put 'filename __getdoc temp lrecl=10000000;'; put 'proc metadata'; put 'in="$METAREPOSITORY'; put ''; put 'SAS1"'; put 'out=__getdoc ;'; put 'run;'; put '/* find the beginning of the text */'; put '%local start;'; put 'data _null_;'; put 'infile __getdoc lrecl=10000;'; put 'input;'; put 'start=index(_infile_,''StoredText="'');'; put 'if start then do;'; put 'call symputx("start",start+11);'; put '*putlog ''"'' _infile_ ''"'';'; put 'end;'; put 'stop;'; put '%local outeng;'; put '%if "&outloc"="0" %then %let outeng=TEMP;'; put '%else %let outeng="&outloc";'; put '%local fref;'; put '%if &outref=0 %then %let fref=%mf_getuniquefileref();'; put '%else %let fref=&outref;'; put '/* read the content, byte by byte, resolving escaped chars */'; put 'filename &fref &outeng lrecl=100000;'; put 'data _null_;'; put 'length filein 8 fileid 8;'; put 'filein = fopen("__getdoc","I",1,"B");'; put 'fileid = fopen("&fref","O",1,"B");'; put 'rec = "20"x;'; put 'length entity $6;'; put 'do while(fread(filein)=0);'; put 'x+1;'; put 'if x>&start then do;'; put 'rc = fget(filein,rec,1);'; put 'if rec=''"'' then leave;'; put 'else if rec="&" then do;'; put 'entity=rec;'; put 'do until (rec=";");'; put 'if fread(filein) ne 0 then goto getout;'; put 'rc = fget(filein,rec,1);'; put 'entity=cats(entity,rec);'; put 'end;'; put 'select (entity);'; put 'when (''&'' ) rec=''&'' ;'; put 'when (''<'' ) rec=''<'' ;'; put 'when (''>'' ) rec=''>'' ;'; put 'when (''''') rec="''" ;'; put 'when (''"'') rec=''"'' ;'; put 'when ('' '') rec=''0A''x;'; put 'when ('' '') rec=''0D''x;'; put 'when (''$'' ) rec=''$'' ;'; put 'when ('' '') rec=''09''x;'; put 'otherwise putlog "%str(WARN)ING: missing value for " entity=;'; put 'end;'; put 'rc =fput(fileid, substr(rec,1,1));'; put 'rc =fwrite(fileid);'; put 'end;'; put 'else do;'; put 'rc =fput(fileid,rec);'; put 'rc =fwrite(fileid);'; put 'end;'; put 'end;'; put 'end;'; put 'getout:'; put 'rc=fclose(filein);'; put 'rc=fclose(fileid);'; put 'run;'; put '%if &showlog=YES %then %do;'; put 'data _null_;'; put 'infile &fref lrecl=32767 end=last;'; put 'input;'; put 'if _n_=1 then putlog ''>>stpcodeBEGIN<<'';'; put 'putlog _infile_;'; put 'if last then putlog ''>>stpcodeEND<<'';'; put 'run;'; put '%end;'; put 'filename __getdoc clear;'; put '%if &outref=0 %then %do;'; put 'filename &fref clear;'; put '%end;'; put '%mend mm_getstpcode;'; put '%macro dc_getsettings();'; put '%global _program;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(syscc=&syscc on entry (&syswarningtext &syserrortext))'; put ')'; put '%if %length(&_PROGRAM)>1 %then %let root=&_program;'; put '%else %do;'; put '%global _metauser;'; put '%let _metauser=&sysuserid;'; put '/* to mimic a "real" _program we need to give a dummy role and stp name */'; put '%let root=/dummyRole/dummyName;'; put '%end;'; put '/* the DC precode is stored in the Admin folder in the root of'; put 'the project. Lets find that root. */'; put '%put &=root;'; put '%let root=%mf_getapploc();'; put '%put &=root;'; put '/* Now we know the root location we can retrieve the params */'; put '%let temploc=%sysfunc(pathname(work))/settings.txt;'; put '%mm_getstpcode(tree=&root/services/public'; put ',name=Data_Controller_Settings'; put ',outloc=&temploc'; put ')'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Unable to run getstpcode)'; put ')'; put 'filename _getsets "&temploc" lrecl=2000;'; put '/*'; put 'Do not use mp_include here - this puts a copy in every service, which creates'; put 'compilation problems when calling services from mp_include'; put '*/'; put '%inc _getsets/source2;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Problem running &sysmacroname (&syswarningtext &syserrortext))'; put ')'; put '%mend dc_getsettings;'; put '%macro mf_fmtdttm('; put ')/*/STORE SOURCE*/;'; put '%if "&sysver"="9.2" or "&sysver"="9.3"'; put 'or ("&sysver"="9.4" and "%substr(&SYSVLONG,9,1)" le "3")'; put 'or "%substr(&sysver,1,1)"="4"'; put 'or "%substr(&sysver,1,1)"="5"'; put '%then %do;DATETIME19.3%end;'; put '%else %do;E8601DT26.6%end;'; put '%mend mf_fmtdttm;'; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '%macro mp_init(prefix=SASJS'; put ')/*/STORE SOURCE*/;'; put '%if %symexist(SASJS_PREFIX) %then %return; /* only run once */'; put '%global'; put 'SASJS_PREFIX /* the ONLY hard-coded global macro variable in SASjs */'; put '&prefix._FUNCTIONS /* used in mcf_init() to track core function compilation */'; put '&prefix._INIT_NUM /* initialisation time as numeric */'; put '&prefix._INIT_DTTM /* initialisation time in E8601DT26.6 format */'; put '&prefix.WORK /* avoid typing %sysfunc(pathname(work)) every time */'; put ';'; put '%let sasjs_prefix=&prefix;'; put 'data _null_;'; put 'dttm=datetime();'; put 'call symputx("&sasjs_prefix._init_num",dttm,''g'');'; put 'call symputx("&sasjs_prefix._init_dttm",put(dttm,E8601DT26.6),''g'');'; put 'call symputx("&sasjs_prefix.work",pathname(''WORK''),''g'');'; put 'run;'; put 'options'; put 'compress=CHAR /* default is none so ensure we have something! */'; put 'datastmtchk=ALLKEYWORDS /* protection from overwriting input datasets */'; put 'errorcheck=STRICT /* catch errs in libname/filename statements */'; put 'fmterr /* ensure err when a format cannot be found */'; put 'mergenoby=%str(ERR)OR /* throw err when a merge has no BY variables */'; put 'missing=. /* changing this can cause hard to detect errs */'; put 'noquotelenmax /* avoid warnings for long strings */'; put 'noreplace /* avoid overwriting permanent datasets */'; put 'ps=max /* reduce log size slightly */'; put 'ls=max /* reduce log even more and avoid word truncation */'; put 'validmemname=COMPATIBLE /* avoid special characters etc in table names */'; put 'validvarname=V7 /* avoid special characters etc in variable names */'; put 'varinitchk=%str(ERR)OR /* avoid data mistakes from variable name typos */'; put 'varlenchk=%str(ERR)OR /* fail hard if truncation (data loss) can result */'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5" %then %do;'; put 'noautocorrect /* disallow misspelled procedure names */'; put 'dsoptions=note2err /* undocumented - convert bad NOTEs to ERRs */'; put '%end;'; put ';'; put '%mend mp_init;'; put '%macro mpeinit(fetch=YES);'; put '%global mpeinit'; put 'mpeadmins /* group with unrestricted Meditor access */'; put 'mpelocapprovals /* location for landing and staging files */'; put 'mpelib /* location of configuration tables for DC */'; put 'dc_repo_users /* location of user / group metadata */'; put 'dc_licence_key /* extracted in dc_getsettings */'; put 'dc_activation_key /* extracted in dc_getsettings */'; put 'dc_locale /* extracted in dc_getsettings */'; put 'dc_dttmtfmt /* can be overridden in dc_getsettings */'; put '_debug'; put ';'; put '%if &mpeinit=1 %then %return;'; put '%else %let mpeinit=1;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(Problem on service startup (&syswarningtext &syserrortext))'; put ')'; put '%mp_init()'; put '%if &fetch=YES %then %do;'; put '%webout(FETCH)'; put '%end;'; put '%global _CLIENTNAME;'; put '%mp_abort(iftrue= (&_CLIENTNAME=SAS Enterprise Guide)'; put ',mac=&_program..sas'; put ',msg=%str(Data Controller is a web app and should not be executed from EG)'; put ')'; put 'options urlencoding=utf8 nobomfile lrecl=32767;'; put '%let perf=%sysfunc(datetime());'; put '%put perfdiff: 0;'; put '%let dc_locale=SYSTEM; /* default if not set */'; put '/**'; put '* E8601DT26.6 has widest database support - but not all SAS flavours can'; put '* handle it. Override in the settings STP if needed.'; put '*/'; put 'data _null_;'; put 'dc_dttmtfmt=''"%sysfunc(datetime(),''!!"%mf_fmtdttm()"!!'')"dt'';'; put 'call symputx(''dc_dttmtfmt'',dc_dttmtfmt);'; put 'put dc_dttmtfmt=;'; put 'run;'; put '%put &=dc_dttmtfmt;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc prior to dc_getsettings)'; put ')'; put '%dc_getsettings()'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc after dc_getsettings)'; put ')'; put 'data _null_;'; put 'set &DC_LIBREF..mpe_config(where=('; put 'var_scope="DC"'; put 'and &dc_dttmtfmt lt tx_to'; put 'and var_active=1'; put '));'; put 'call symputx(var_name,var_value,''G'');'; put 'putlog var_name "=" var_value;'; put 'run;'; put '%let mpelib=&dc_libref;'; put '%let mpeadmins=&dc_admin_group;'; put '%let mpelocapprovals=&dc_staging_area;'; put '%let dc_repo_users=&dc_repo_users;'; put '%if &dc_locale ne SYSTEM %then %do;'; put 'options locale=&dc_locale;'; put '%end;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(Problem during compilation or with STP precode (&syswarningtext))'; put ')'; put '%mend mpeinit;'; put '%macro mf_mval(var);'; put '%if %symexist(&var) %then %do;'; put '%superq(&var)'; put '%end;'; put '%mend mf_mval;'; put '%macro mf_trimstr(basestr,trimstr);'; put '%local baselen trimlen trimval;'; put '/* return if basestr is shorter than trimstr (or 0) */'; put '%let baselen=%length(%superq(basestr));'; put '%let trimlen=%length(%superq(trimstr));'; put '%if &baselen < &trimlen or &baselen=0 %then %return;'; put '/* obtain the characters from the end of basestr */'; put '%let trimval=%qsubstr(%superq(basestr)'; put ',%length(%superq(basestr))-&trimlen+1'; put ',&trimlen);'; put '/* compare and if matching, chop it off! */'; put '%if %superq(basestr)=%superq(trimstr) %then %do;'; put '%return;'; put '%end;'; put '%else %if %superq(trimval)=%superq(trimstr) %then %do;'; put '%qsubstr(%superq(basestr),1,%length(%superq(basestr))-&trimlen)'; put '%end;'; put '%else %do;'; put '&basestr'; put '%end;'; put '%mend mf_trimstr;'; put '%macro mf_getplatform(switch'; put ')/*/STORE SOURCE*/;'; put '%local a b c;'; put '%if &switch.NONE=NONE %then %do;'; put '%if %symexist(sasjsprocessmode) %then %do;'; put '%if &sasjsprocessmode=Stored Program %then %do;'; put 'SASJS'; put '%return;'; put '%end;'; put '%end;'; put '%if %symexist(sysprocessmode) %then %do;'; put '%if "&sysprocessmode"="SAS Object Server"'; put 'or "&sysprocessmode"= "SAS Compute Server" %then %do;'; put 'SASVIYA'; put '%end;'; put '%else %if "&sysprocessmode"="SAS Stored Process Server"'; put 'or "&sysprocessmode"="SAS Workspace Server"'; put '%then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if %symexist(_metaport) or %symexist(_metauser) %then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if &switch=SASSTUDIO %then %do;'; put '/* return the version of SAS Studio else 0 */'; put '%if %mf_mval(_CLIENTAPP)=%str(SAS Studio) %then %do;'; put '%let a=%mf_mval(_CLIENTVERSION);'; put '%let b=%scan(&a,1,.);'; put '%if %eval(&b >2) %then %do;'; put '&b'; put '%end;'; put '%else 0;'; put '%end;'; put '%else 0;'; put '%end;'; put '%else %if &switch=VIYARESTAPI %then %do;'; put '%mf_trimstr(%sysfunc(getoption(servicesbaseurl)),/)'; put '%end;'; put '%mend mf_getplatform;'; put '%macro mpeterm();'; put '%local oldloc;'; put 'data _null_;'; put 'if symexist(''SYSPRINTTOLOG'') then oldloc=symget(''SYSPRINTTOLOG'');'; put 'else oldloc=getoption(''LOG'');'; put 'if subpad(oldloc,1,1) not in (''"'',"''",'' '') then oldloc=quote(cats(oldloc));'; put 'call symputx(''oldloc'',oldloc,''l'');'; put 'run;'; put '%if %length(&oldloc)>0 %then %do;'; put 'proc printto log=log;'; put 'run;'; put 'data _null_;'; put 'infile &oldloc;'; put 'input; putlog _infile_;'; put 'run;'; put '%end;'; put '%if %sysfunc(exist(&dc_libref..mpe_requests)) and %mf_getplatform() ne SASVIYA'; put '%then %do;'; put 'data ;'; put 'if 0 then set &dc_libref..mpe_requests;'; put 'request_dttm=%sysfunc(datetime());'; put 'request_user="%mf_getuser()";'; put 'request_service="%scan(&_program,-2,/)/%scan(&_program,-1,/)";'; put 'request_params='''';'; put 'output;stop;'; put 'proc append base=&dc_libref..mpe_requests data=&syslast force nowarn;'; put 'run;'; put '%end;'; put '%mend mpeterm;'; put '* SAS Macros end;'; put '* SAS Includes start;'; put '* SAS Includes end;'; put '* Binary Files start;'; put '* Binary Files end;'; put '* ServiceInit start;'; put 'options noquotelenmax ps=max;'; put '/* create dummy macros to prevent stpbegin / stpend from corrupting sessions */'; put '%macro stpbegin();'; put '%put NOTE: the STPBEGIN macro should not be used for web apps!;'; put '%mend stpbegin;'; put '%macro stpend();'; put '%put NOTE: the STPEND macro should not be used for web apps!;'; put '%mend stpend;'; put '* ServiceInit end;'; put '* Service start;'; put '/**'; put '@file usermembersbygroup.sas'; put '@brief List the members of a group'; put '

SAS Macros

'; put '@li mp_abort.sas'; put '@li mpeinit.sas'; put '@version 9.3'; put '@author 4GL Apps Ltd'; put '@copyright 4GL Apps Ltd. This code may only be used within Data Controller'; put 'and may not be re-distributed or re-sold without the express permission of'; put '4GL Apps Ltd.'; put '**/'; put '%mpeinit()'; put 'data sasMembers ;'; put 'attrib uriGrp uriMem GroupId GroupName Group_or_Role MemberName MemberType'; put 'MemberUpdated membercreated emailuri length=$64'; put 'GroupDesc email length=$256'; put 'rcGrp rcMem rc i j length=3;'; put 'call missing (of _all_);'; put 'drop uriGrp rcGrp rcMem rc i j;'; put 'set iwant;'; put 'i=1;'; put '* Grab the URI for the first Group ;'; put 'rcGrp=metadata_getnobj(groupid,i,uriGrp);'; put '* If Group found, enter do loop ;'; put 'if rcGrp>0 then do;'; put 'call missing (rcMem,uriMem,GroupId,GroupName,Group_or_Role'; put ',MemberName,MemberType);'; put '* get group info ;'; put 'rc = metadata_getattr(uriGrp,"Id",GroupId);'; put 'rc = metadata_getattr(uriGrp,"Name",GroupName);'; put 'rc = metadata_getattr(uriGrp,"PublicType",Group_or_Role);'; put 'rc = metadata_getattr(uriGrp,"Desc",GroupDesc);'; put 'j=1;'; put 'do while (metadata_getnasn(uriGrp,"MemberIdentities",j,uriMem) > 0);'; put 'call missing (MemberName,MemberType);'; put 'rc = metadata_getattr(uriMem,"Name",MemberName);'; put 'rc = metadata_getattr(uriMem,"PublicType",MemberType);'; put 'rc=metadata_getattr(uriMem, "MetadataCreated", MemberCreated);'; put 'rc=metadata_getattr(uriMem, "MetadataUpdated", MemberUpdated);'; put 'emailrc=metadata_getnasn(uriMem,"EmailAddresses",1,emailuri);'; put 'if (emailrc>0) then rc=metadata_getattr(emailuri,"Address",email);'; put 'output;'; put 'j+1;'; put 'call missing(email,emailuri);'; put 'end;'; put 'end;'; put 'if _n_=1 then delete;'; put 'run;'; put '%webout(OPEN)'; put '%webout(OBJ,sasMembers)'; put '%webout(CLOSE)'; put '* Service end;'; run; %mm_createwebservice(path=&appLoc/&path, name=&service, code=sascode, server=&serverName, replace=yes) filename sascode clear; %let service=usermembersbyrole; filename sascode temp lrecl=32767; data _null_; file sascode; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '/**'; put '@file mp_jsonout.sas'; put '@brief Writes JSON in SASjs format to a fileref'; put '@details This macro can be used to OPEN a JSON stream and send one or more'; put 'tables as arrays of rows, where each row can be an object or a nested array.'; put 'There are two engines available - DATASTEP or PROCJSON.'; put 'PROC JSON is fast but will produce errs like the ones below if'; put 'special chars are encountered.'; put '> (ERR)OR: Some code points did not transcode.'; put '> An object or array close is not valid at this point in the JSON text.'; put '> Date value out of range'; put 'If this happens, try running with ENGINE=DATASTEP.'; put 'The DATASTEP engine is used to handle special SAS missing numerics, and'; put 'can also convert entire datasets to formatted values. Output JSON is always'; put 'in UTF-8.'; put 'Usage:'; put 'filename tmp temp;'; put 'data class; set sashelp.class;run;'; put '%mp_jsonout(OPEN,jref=tmp)'; put '%mp_jsonout(OBJ,class,jref=tmp)'; put '%mp_jsonout(OBJ,class,dslabel=class2,jref=tmp,showmeta=Y)'; put '%mp_jsonout(CLOSE,jref=tmp)'; put 'data _null_;'; put 'infile tmp;'; put 'input;putlog _infile_;'; put 'run;'; put 'If you are building web apps with SAS then you are strongly encouraged to use'; put 'the mX_createwebservice macros in combination with the'; put '[sasjs adapter](https://github.com/sasjs/adapter).'; put 'For more information see https://sasjs.io'; put '@param [in] action Valid values:'; put '@li OPEN - opens the JSON'; put '@li OBJ - sends a table with each row as an object'; put '@li ARR - sends a table with each row in an array'; put '@li CLOSE - closes the JSON'; put '@param [in] ds The dataset to send. Must be a work table.'; put '@param [out] jref= (_webout) The fileref to which to send the JSON'; put '@param [out] dslabel= The name to give the table in the exported JSON'; put '@param [in] fmt= (Y) Whether to keep (Y) or strip (N) formats from the table'; put '@param [in] engine= (DATASTEP) Which engine to use to send the JSON. Options:'; put '@li PROCJSON (default)'; put '@li DATASTEP (more reliable when data has non standard characters)'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to JSON'; put '

Related Files

'; put '@li mp_ds2fmtds.sas'; put '@version 9.2'; put '@author Allan Bowe'; put '@source https://github.com/sasjs/core'; put '**/'; put '%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y'; put ',engine=DATASTEP'; put ',missing=NULL'; put ',showmeta=N'; put ',maxobs=MAX'; put ')/*/STORE SOURCE*/;'; put '%local tempds colinfo fmtds i numcols numobs stmt_obs lastobs optval'; put 'tmpds1 tmpds2 tmpds3 tmpds4;'; put '%let numcols=0;'; put '%if &maxobs ne MAX %then %let stmt_obs=%str(if _n_>&maxobs then stop;);'; put '%if &action=OPEN %then %do;'; put 'options nobomfile;'; put 'data _null_;file &jref encoding=''utf-8'' lrecl=200;'; put 'put ''{"PROCESSED_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''"'';'; put 'run;'; put '%end;'; put '%else %if (&action=ARR or &action=OBJ) %then %do;'; put '/* force variable names to always be uppercase in the JSON */'; put 'options validvarname=upcase;'; put '/* To avoid issues with _webout on EBI - such as encoding diffs and truncation'; put '(https://support.sas.com/kb/49/325.html) we use temporary files */'; put 'filename _sjs1 temp lrecl=200 ;'; put 'data _null_; file _sjs1 encoding=''utf-8'';'; put 'put ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":";'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjs1 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs1 clear;'; put '/* grab col defs */'; put 'proc contents noprint data=&ds'; put 'out=_data_(keep=name type length format formatl formatd varnum label);'; put 'run;'; put '%let colinfo=%scan(&syslast,2,.);'; put 'proc sort data=&colinfo;'; put 'by varnum;'; put 'run;'; put '/* move meta to mac vars */'; put 'data &colinfo;'; put 'if _n_=1 then call symputx(''numcols'',nobs,''l'');'; put 'set &colinfo end=last nobs=nobs;'; put 'name=upcase(name);'; put '/* fix formats */'; put 'if type=2 or type=6 then do;'; put 'typelong=''char'';'; put 'length fmt $49.;'; put 'if format='''' then fmt=cats(''$'',length,''.'');'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else fmt=cats(format,formatl,''.'');'; put 'end;'; put 'else do;'; put 'typelong=''num'';'; put 'if format='''' then fmt=''best.'';'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else if formatd=0 then fmt=cats(format,formatl,''.'');'; put 'else fmt=cats(format,formatl,''.'',formatd);'; put 'end;'; put '/* 32 char unique name */'; put 'newname=''sasjs''!!substr(cats(put(md5(name),$hex32.)),1,27);'; put 'call symputx(cats(''name'',_n_),name,''l'');'; put 'call symputx(cats(''newname'',_n_),newname,''l'');'; put 'call symputx(cats(''length'',_n_),length,''l'');'; put 'call symputx(cats(''fmt'',_n_),fmt,''l'');'; put 'call symputx(cats(''type'',_n_),type,''l'');'; put 'call symputx(cats(''typelong'',_n_),typelong,''l'');'; put 'call symputx(cats(''label'',_n_),coalescec(label,name),''l'');'; put '/* overwritten when fmt=Y and a custom format exists in catalog */'; put 'if typelong=''num'' then call symputx(cats(''fmtlen'',_n_),200,''l'');'; put 'else call symputx(cats(''fmtlen'',_n_),min(32767,ceil((length+10)*1.5)),''l'');'; put 'run;'; put '%let tempds=%substr(_%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql;'; put 'select count(*) into: lastobs from &ds;'; put '%if &maxobs ne MAX %then %let lastobs=%sysfunc(min(&lastobs,&maxobs));'; put '%if &engine=PROCJSON %then %do;'; put '%if &missing=STRING %then %do;'; put '%put &sysmacroname: Special Missings not supported in proc json.;'; put '%put &sysmacroname: Switching to DATASTEP engine;'; put '%goto datastep;'; put '%end;'; put 'data &tempds;'; put 'set &ds;'; put '&stmt_obs;'; put '%if &fmt=N %then format _numeric_ best32.;;'; put '/* PRETTY is necessary to avoid line truncation in large files */'; put 'filename _sjs2 temp lrecl=131068 encoding=''utf-8'';'; put 'proc json out=_sjs2 pretty'; put '%if &action=ARR %then nokeys ;'; put ';export &tempds / nosastags fmtnumeric;'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs2 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs2 clear;'; put '%end;'; put '%else %if &engine=DATASTEP %then %do;'; put '%datastep:'; put '%if %sysfunc(exist(&ds)) ne 1 & %sysfunc(exist(&ds,VIEW)) ne 1'; put '%then %do;'; put '%put &sysmacroname: &ds NOT FOUND!!!;'; put '%return;'; put '%end;'; put '%if &fmt=Y %then %do;'; put '/**'; put '* Extract format definitions'; put '* First, by getting library locations from dictionary.formats'; put '* Then, by exporting the width using proc format'; put '* Cannot use maxw from sashelp.vformat as not always populated'; put '* Cannot use fmtinfo() as not supported in all flavours'; put '*/'; put '%let tmpds1=%substr(fmtsum%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds2=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds3=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds4=%substr(col%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql noprint;'; put 'create table &tmpds1 as'; put 'select cats(libname,''.'',memname) as FMTCAT,'; put 'FMTNAME'; put 'from dictionary.formats'; put 'where fmttype=''F'' and libname is not null'; put 'and fmtname in (select format from &colinfo where format is not null)'; put 'order by 1;'; put 'create table &tmpds2('; put 'FMTNAME char(32),'; put 'LENGTH num'; put ');'; put '%local catlist cat fmtlist i;'; put 'select distinct fmtcat into: catlist separated by '' '' from &tmpds1;'; put '%do i=1 %to %sysfunc(countw(&catlist,%str( )));'; put '%let cat=%scan(&catlist,&i,%str( ));'; put 'proc sql;'; put 'select distinct fmtname into: fmtlist separated by '' '''; put 'from &tmpds1 where fmtcat="&cat";'; put 'proc format lib=&cat cntlout=&tmpds3(keep=fmtname length);'; put 'select &fmtlist;'; put 'run;'; put 'proc sql;'; put 'insert into &tmpds2 select distinct fmtname,length from &tmpds3;'; put '%end;'; put 'proc sql;'; put 'create table &tmpds4 as'; put 'select a.*, b.length as MAXW'; put 'from &colinfo a'; put 'left join &tmpds2 b'; put 'on cats(a.format)=cats(upcase(b.fmtname))'; put 'order by a.varnum;'; put 'data _null_;'; put 'set &tmpds4;'; put 'if not missing(maxw);'; put 'call symputx('; put 'cats(''fmtlen'',_n_),'; put '/* vars need extra padding due to JSON escaping of special chars */'; put 'min(32767,ceil((max(length,maxw)+10)*1.5))'; put ',''l'''; put ');'; put 'run;'; put '/* configure varlenchk - as we are explicitly shortening the variables */'; put '%let optval=%sysfunc(getoption(varlenchk));'; put 'options varlenchk=NOWARN;'; put 'data _data_(compress=char);'; put '/* shorten the new vars */'; put 'length'; put '%do i=1 %to &numcols;'; put '&&name&i $&&fmtlen&i'; put '%end;'; put ';'; put '/* rename on entry */'; put 'set &ds(rename=('; put '%do i=1 %to &numcols;'; put '&&name&i=&&newname&i'; put '%end;'; put '));'; put '&stmt_obs;'; put 'drop'; put '%do i=1 %to &numcols;'; put '&&newname&i'; put '%end;'; put ';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=num %then %do;'; put '&&name&i=cats(put(&&newname&i,&&fmt&i));'; put '%end;'; put '%else %do;'; put '&&name&i=put(&&newname&i,&&fmt&i);'; put '%end;'; put '%end;'; put 'if _error_ then do;'; put 'call symputx(''syscc'',1012);'; put 'stop;'; put 'end;'; put 'run;'; put '%let fmtds=&syslast;'; put 'options varlenchk=&optval;'; put '%end;'; put 'proc format; /* credit yabwon for special null removal */'; put 'value bart (default=40)'; put '%if &missing=NULL %then %do;'; put '._ - .z = null'; put '%end;'; put '%else %do;'; put '._ = [quote()]'; put '. = null'; put '.a - .z = [quote()]'; put '%end;'; put 'other = [best.];'; put 'data &tempds;'; put 'attrib _all_ label='''';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'length &&name&i $&&fmtlen&i...;'; put 'format &&name&i $&&fmtlen&i...;'; put '%end;'; put '%end;'; put '%if &fmt=Y %then %do;'; put 'set &fmtds;'; put '%end;'; put '%else %do;'; put 'set &ds;'; put '%end;'; put '&stmt_obs;'; put 'format _numeric_ bart.;'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'if findc(&&name&i,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put '&&name&i=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,&&name&i)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else &&name&i=quote(cats(&&name&i));'; put '%end;'; put '%end;'; put 'run;'; put 'filename _sjs3 temp lrecl=131068 ;'; put 'data _null_;'; put 'file _sjs3 encoding=''utf-8'';'; put 'if _n_=1 then put "[";'; put 'set &tempds;'; put 'if _n_>1 then put "," @; put'; put '%if &action=ARR %then "[" ; %else "{" ;'; put '%do i=1 %to &numcols;'; put '%if &i>1 %then "," ;'; put '%if &action=OBJ %then """&&name&i"":" ;'; put '"&&name&i"n /* name literal for reserved variable names */'; put '%end;'; put '%if &action=ARR %then "]" ; %else "}" ; ;'; put '/* close out the table */'; put 'data _null_;'; put 'file _sjs3 mod encoding=''utf-8'';'; put 'put '']'';'; put 'run;'; put 'data _null_;'; put 'infile _sjs3 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs3 clear;'; put '%end;'; put 'proc sql;'; put 'drop table &colinfo, &tempds;'; put '%if %substr(&showmeta,1,1)=Y %then %do;'; put 'filename _sjs4 temp lrecl=131068 encoding=''utf-8'';'; put 'data _null_;'; put 'file _sjs4;'; put 'length label $350;'; put 'put ", ""$%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":{""vars"":{";'; put 'do i=1 to &numcols;'; put 'name=quote(trim(symget(cats(''name'',i))));'; put 'format=quote(trim(symget(cats(''fmt'',i))));'; put 'label=quote(prxchange(''s/\\/\\\\/'',-1,trim(symget(cats(''label'',i)))));'; put 'length=quote(trim(symget(cats(''length'',i))));'; put 'type=quote(trim(symget(cats(''typelong'',i))));'; put 'if i>1 then put "," @@;'; put 'put name '':{"format":'' format '',"label":'' label'; put ''',"length":'' length '',"type":'' type ''}'';'; put 'end;'; put 'put ''}}'';'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs4 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs4 clear;'; put '%end;'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put 'data _null_; file &jref encoding=''utf-8'' mod ;'; put 'put "}";'; put 'run;'; put '%end;'; put '%mend mp_jsonout;'; put '/**'; put '@file mm_webout.sas'; put '@brief Send data to/from SAS Stored Processes'; put '@details This macro should be added to the start of each Stored Process,'; put '**immediately** followed by a call to:'; put '%mm_webout(FETCH)'; put 'This will read all the input data and create same-named SAS datasets in the'; put 'WORK library. You can then insert your code, and send data back using the'; put 'following syntax:'; put 'data some datasets; * make some data ;'; put 'retain some columns;'; put 'run;'; put '%mm_webout(OPEN)'; put '%mm_webout(ARR,some) * Array format, fast, suitable for large tables ;'; put '%mm_webout(OBJ,datasets) * Object format, easier to work with ;'; put 'Finally, wrap everything up send some helpful system variables too'; put '%mm_webout(CLOSE)'; put '@param [in] action Either FETCH, OPEN, ARR, OBJ or CLOSE'; put '@param [in] ds The dataset to send back to the frontend'; put '@param [out] dslabel= Value to use instead of table name for sending to JSON'; put '@param [in] fmt= (N) Setting Y converts all vars to their formatted values'; put '@param [out] fref= (_webout) The fileref to which to write the JSON'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] workobs= (0) When set to a positive integer, will create a new'; put 'output object (WORK) which contains this number of observations from all'; put 'tables in the WORK library.'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to output JSON'; put '

SAS Macros

'; put '@li mp_jsonout.sas'; put '

Related Macros

'; put '@li ms_webout.sas'; put '@li mv_webout.sas'; put '@version 9.3'; put '@author Allan Bowe'; put '**/'; put '%macro mm_webout(action,ds,dslabel=,fref=_webout,fmt=N,missing=NULL'; put ',showmeta=N,maxobs=MAX,workobs=0'; put ');'; put '%global _webin_file_count _webin_fileref1 _webin_name1 _program _debug'; put 'sasjs_tables;'; put '%local i tempds jsonengine;'; put '/* see https://github.com/sasjs/core/issues/41 */'; put '%if "%upcase(&SYSENCODING)" ne "UTF-8" %then %let jsonengine=PROCJSON;'; put '%else %let jsonengine=DATASTEP;'; put '%if &action=FETCH %then %do;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'options mprint notes mprintnest;'; put '%end;'; put '%let _webin_file_count=%eval(&_webin_file_count+0);'; put '/* now read in the data */'; put '%do i=1 %to &_webin_file_count;'; put '%if &_webin_file_count=1 %then %do;'; put '%let _webin_fileref1=&_webin_fileref;'; put '%let _webin_name1=&_webin_name;'; put '%end;'; put 'data _null_;'; put 'infile &&_webin_fileref&i termstr=crlf;'; put 'input;'; put 'call symputx(''input_statement'',_infile_);'; put 'putlog "&&_webin_name&i input statement: " _infile_;'; put 'stop;'; put 'data &&_webin_name&i;'; put 'infile &&_webin_fileref&i firstobs=2 dsd termstr=crlf encoding=''utf-8'';'; put 'input &input_statement;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'if _n_<20 then putlog _infile_;'; put '%end;'; put 'run;'; put '%let sasjs_tables=&sasjs_tables &&_webin_name&i;'; put '%end;'; put '%end;'; put '%else %if &action=OPEN %then %do;'; put '/* fix encoding */'; put 'OPTIONS NOBOMFILE;'; put '/**'; put '* check xengine type to avoid the below err message:'; put '* > Function is only valid for filerefs using the CACHE access method.'; put '*/'; put 'data _null_;'; put 'set sashelp.vextfl(where=(fileref="_WEBOUT"));'; put 'if xengine=''STREAM'' then do;'; put 'rc=stpsrv_header(''Content-type'',"text/html; encoding=utf-8");'; put 'end;'; put 'run;'; put '/* setup json */'; put 'data _null_;file &fref encoding=''utf-8'';'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutBEGIN<<'';'; put '%end;'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'run;'; put '%end;'; put '%else %if &action=ARR or &action=OBJ %then %do;'; put '%mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt,jref=&fref'; put ',engine=&jsonengine,missing=&missing,showmeta=&showmeta,maxobs=&maxobs'; put ')'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put '/* To avoid issues with _webout on EBI we use a temporary file */'; put 'filename _sjsref temp lrecl=131068;'; put '%if %str(&workobs) > 0 %then %do;'; put '/* if debug mode, send back first XX records of each work table also */'; put 'data;run;%let tempds=%scan(&syslast,2,.);'; put 'ods output Members=&tempds;'; put 'proc datasets library=WORK memtype=data;'; put '%local wtcnt;%let wtcnt=0;'; put 'data _null_;'; put 'set &tempds;'; put 'if not (upcase(name) =:"DATA"); /* ignore temp datasets */'; put 'i+1;'; put 'call symputx(cats(''wt'',i),name,''l'');'; put 'call symputx(''wtcnt'',i,''l'');'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put ",""WORK"":{";'; put '%do i=1 %to &wtcnt;'; put '%let wt=&&wt&i;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'dsid=open("WORK.&wt",''is'');'; put 'nlobs=attrn(dsid,''NLOBS'');'; put 'nvars=attrn(dsid,''NVARS'');'; put 'rc=close(dsid);'; put 'if &i>1 then put '',''@;'; put 'put " ""&wt"" : {";'; put 'put ''"nlobs":'' nlobs;'; put 'put '',"nvars":'' nvars;'; put '%mp_jsonout(OBJ,&wt,jref=_sjsref,dslabel=first10rows,showmeta=Y'; put ',maxobs=&workobs'; put ')'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put '%end;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put 'run;'; put '%end;'; put '/* close off json */'; put 'data _null_;file _sjsref mod encoding=''utf-8'';'; put 'length SYSPROCESSNAME syserrortext syswarningtext autoexec $512;'; put 'put ",""_DEBUG"" : ""&_debug"" ";'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'autoexec=quote(urlencode(trim(getoption(''autoexec''))));'; put 'put '',"AUTOEXEC" : '' autoexec;'; put 'put ",""MF_GETUSER"" : ""%mf_getuser()"" ";'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'put ",""SYSENCODING"" : ""&sysencoding"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSPROCESSID"" : ""&SYSPROCESSID"" ";'; put 'put ",""SYSPROCESSMODE"" : ""&SYSPROCESSMODE"" ";'; put 'SYSPROCESSNAME=quote(urlencode(cats(SYSPROCESSNAME)));'; put 'put ",""SYSPROCESSNAME"" : " SYSPROCESSNAME;'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put '',"SYSWARNINGTEXT" : '' syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'length memsize $32;'; put 'memsize="%sysfunc(INPUTN(%sysfunc(getoption(memsize)), best.),sizekmg.)";'; put 'memsize=quote(cats(memsize));'; put 'put '',"MEMSIZE" : '' memsize;'; put 'put "}" @;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutEND<<'';'; put '%end;'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjsref lrecl=1 recfm=n;'; put 'file &fref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjsref clear;'; put '%end;'; put '%mend mm_webout;'; put '%macro webout(action,ds,dslabel=,fmt=,missing=NULL,showmeta=NO,maxobs=MAX);'; put '%mm_webout(&action,ds=&ds,dslabel=&dslabel,fmt=&fmt'; put ',missing=&missing'; put ',showmeta=&showmeta'; put ',maxobs=&maxobs'; put ') %mend;'; put '/* provide additional debug info */'; put '%global _program;'; put '%put &=syscc;'; put '%put user=%mf_getuser();'; put '%put pgm=&_program;'; put '%put timestamp=%sysfunc(datetime(),datetime19.);'; put '* Service Variables start;'; put '* Service Variables end;'; put '* SAS Macros start;'; put '%macro mf_getapploc(pgm);'; put '%if "&pgm"="" %then %do;'; put '%if %symexist(_program) %then %let pgm=&_program;'; put '%else %do;'; put '%put &sysmacroname: No value provided and no _program variable available;'; put '%return;'; put '%end;'; put '%end;'; put '%local root;'; put '/**'; put '* First check we are not in the tests/macros folder (which has no subfolders)'; put '* or specifically in the testsetup or testteardown services'; put '*/'; put '%if %index(&pgm,/tests/macros/)'; put 'or %index(&pgm,/tests/testsetup)'; put 'or %index(&pgm,/tests/testteardown)'; put '%then %do;'; put '%let root=%substr(&pgm,1,%index(&pgm,/tests)-1);'; put '&root'; put '%return;'; put '%end;'; put '/**'; put '* Next, move up two levels to avoid matches on subfolder or service name'; put '*/'; put '%let root=%substr(&pgm,1,%length(&pgm)-%length(%scan(&pgm,-1,/))-1);'; put '%let root=%substr(&root,1,%length(&root)-%length(%scan(&root,-1,/))-1);'; put '%if %index(&root,/tests/) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/tests/)-1);'; put '%end;'; put '%else %if %index(&root,/services) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/services)-1);'; put '%end;'; put '%else %if %index(&root,/jobs) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/jobs)-1);'; put '%end;'; put '%else %put &sysmacroname: Could not find an app location from &pgm;'; put '&root'; put '%mend mf_getapploc ;'; put '%macro mf_getuniquefileref(prefix=_,maxtries=1000,lrecl=32767);'; put '%local rc fname;'; put '%if &prefix=0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%end;'; put '%else %do;'; put '%local x len;'; put '%let len=%eval(8-%length(&prefix));'; put '%let x=0;'; put '%do x=0 %to &maxtries;'; put '%let fname=&prefix%substr(%sysfunc(ranuni(0)),3,&len);'; put '%if %sysfunc(fileref(&fname)) > 0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%return;'; put '%end;'; put '%end;'; put '%put unable to find available fileref after &maxtries attempts;'; put '%end;'; put '%mend mf_getuniquefileref;'; put '%macro mp_abort(mac=mp_abort.sas, type=, msg=, iftrue=%str(1=1)'; put ', errds=work.mp_abort_errds'; put ', mode=REGULAR'; put ')/*/STORE SOURCE*/;'; put '%global sysprocessmode sysprocessname sasjs_stpsrv_header_loc sasjsprocessmode;'; put '%local fref fid i;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%put NOTE: /// mp_abort macro executing //;'; put '%if %length(&mac)>0 %then %put NOTE- called by &mac;'; put '%put NOTE - &msg;'; put '%if %symexist(_SYSINCLUDEFILEDEVICE)'; put '/* abort cancel FILE does not restart outside the INCLUDE on Viya 3.5 */'; put 'and %superq(SYSPROCESSNAME) ne %str(Compute Server)'; put '%then %do;'; put '%if "*&_SYSINCLUDEFILEDEVICE*" ne "**" %then %do;'; put 'data &errds;'; put 'iftrue=''1=1'';'; put 'length mac $100 msg $5000;'; put 'mac=symget(''mac'');'; put 'msg=symget(''msg'');'; put 'run;'; put 'data _null_;'; put 'abort cancel FILE;'; put 'run;'; put '%return;'; put '%end;'; put '%end;'; put '/* Web App Context */'; put '%if %symexist(_PROGRAM)'; put 'or %superq(SYSPROCESSNAME) = %str(Compute Server)'; put 'or &mode=INCLUDE'; put '%then %do;'; put 'options obs=max replace mprint;'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5"'; put '%then %do;'; put 'options nosyntaxcheck;'; put '%end;'; put '%if &mode=INCLUDE %then %do;'; put '%if %sysfunc(exist(&errds))=1 %then %do;'; put 'data _null_;'; put 'set &errds;'; put 'call symputx(''iftrue'',iftrue,''l'');'; put 'call symputx(''mac'',mac,''l'');'; put 'call symputx(''msg'',msg,''l'');'; put 'putlog (_all_)(=);'; put 'run;'; put '%if (&iftrue)=0 %then %return;'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: No include errors found;'; put '%return;'; put '%end;'; put '%end;'; put '/* extract log errs / warns, if exist */'; put '%local logloc logline;'; put '%global logmsg; /* capture global messages */'; put '%if %symexist(SYSPRINTTOLOG) %then %let logloc=&SYSPRINTTOLOG;'; put '%else %let logloc=%qsysfunc(getoption(LOG));'; put 'proc printto log=log;run;'; put '%let logline=0;'; put '%if %length(&logloc)>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input; putlog _infile_;'; put 'i=1;'; put 'retain logonce 0;'; put 'if ('; put '_infile_=:"%str(WARN)ING" or _infile_=:"%str(ERR)OR"'; put ') and logonce=0 then'; put 'do;'; put 'call symputx(''logline'',_n_);'; put 'logonce+1;'; put 'end;'; put 'run;'; put '/* capture log including lines BEFORE the err */'; put '%if &logline>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input;'; put 'i=1;'; put 'stoploop=0;'; put 'if _n_ ge &logline-15 and stoploop=0 then do until (i>22);'; put 'call symputx(''logmsg'',catx(''\n'',symget(''logmsg''),_infile_));'; put 'input;'; put 'i+1;'; put 'stoploop=1;'; put 'end;'; put 'if stoploop=1 then stop;'; put 'run;'; put '%end;'; put '%end;'; put '%if %symexist(SYS_JES_JOB_URI) %then %do;'; put '/* setup webout for Viya */'; put 'options nobomfile;'; put '%if "X&SYS_JES_JOB_URI.X"="XX" %then %do;'; put 'filename _webout temp lrecl=999999 mod;'; put '%end;'; put '%else %do;'; put 'filename _webout filesrvc parenturi="&SYS_JES_JOB_URI"'; put 'name="_webout.json" lrecl=999999 mod;'; put '%end;'; put '%end;'; put '%else %if %sysfunc(filename(fref,&sasjs_stpsrv_header_loc))=0 %then %do;'; put 'options nobomfile;'; put '/* set up http header for SASjs Server */'; put '%let fid=%sysfunc(fopen(&fref,A));'; put '%if &fid=0 %then %do;'; put '%put %str(ERR)OR: %sysfunc(sysmsg());'; put '%return;'; put '%end;'; put '%let rc=%sysfunc(fput(&fid,%str(Content-Type: application/json)));'; put '%let rc=%sysfunc(fwrite(&fid));'; put '%let rc=%sysfunc(fclose(&fid));'; put '%let rc=%sysfunc(filename(&fref));'; put '%end;'; put '/* send response in SASjs JSON format */'; put 'data _null_;'; put 'file _webout mod lrecl=32000 encoding=''utf-8'';'; put 'length msg syswarningtext syserrortext $32767 mode $10 ;'; put 'sasdatetime=datetime();'; put 'msg=symget(''msg'');'; put '%if &logline>0 %then %do;'; put 'msg=cats(msg,''\n\nLog Extract:\n'',symget(''logmsg''));'; put '%end;'; put '/* escape the escapes */'; put 'msg=tranwrd(msg,''\'',''\\'');'; put '/* escape the quotes */'; put 'msg=tranwrd(msg,''"'',''\"'');'; put '/* ditch the CRLFs as chrome complains */'; put 'msg=compress(msg,,''kw'');'; put '/* quote without quoting the quotes (which are escaped instead) */'; put 'msg=cats(''"'',msg,''"'');'; put 'if symexist(''_debug'') then debug=quote(trim(symget(''_debug'')));'; put 'else debug=''""'';'; put 'if symget(''sasjsprocessmode'')=''Stored Program'' then mode=''SASJS'';'; put 'if mode ne ''SASJS'' then put ''>>weboutBEGIN<<'';'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'put '',"sasjsAbort" : [{'';'; put 'put '' "MSG":'' msg ;'; put 'put '' ,"MAC": "'' "&mac" ''"}]'';'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'put '',"_DEBUG":'' debug ;'; put 'if symexist(''_metauser'') then do;'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put 'end;'; put 'if symexist(''SYS_JES_JOB_URI'') then do;'; put 'SYS_JES_JOB_URI=quote(trim(symget(''SYS_JES_JOB_URI'')));'; put 'put '',"SYS_JES_JOB_URI": '' SYS_JES_JOB_URI;'; put 'end;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put ",""SYSWARNINGTEXT"" : " syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'put "}" ;'; put 'if mode ne ''SASJS'' then put ''>>weboutEND<<'';'; put 'run;'; put '%put _all_;'; put '%if "&sysprocessmode " = "SAS Stored Process Server " %then %do;'; put 'data _null_;'; put 'putlog ''stpsrvset program err and syscc'';'; put 'rc=stpsrvset(''program error'', 0);'; put 'call symputx("syscc",0,"g");'; put 'run;'; put '%if &sysscp=WIN'; put 'and 1=0 /* deprecating this logic until we figure out a consistent abort */'; put 'and "%substr(%str(&sysvlong ),1,8)"="9.04.01M"'; put 'and "%substr(%str(&sysvlong ),9,1)">"5" %then %do;'; put '/* skip approach (below) does not work in windows m6+ envs */'; put 'endsas;'; put '%end;'; put '%else %do;'; put '/**'; put '* endsas kills 9.4m3 deployments by orphaning multibridges.'; put '* Abort variants are ungraceful (non zero return code)'; put '* This approach lets SAS run silently until the end :-)'; put '* Caution - fails when called within a %include within a macro'; put '* Use mp_include() to handle this.'; put '*/'; put 'filename skip temp;'; put 'data _null_;'; put 'file skip;'; put 'put ''%macro skip();'';'; put 'comment ''%mend skip; -> fix lint '';'; put 'put ''%macro skippy();'';'; put 'comment ''%mend skippy; -> fix lint '';'; put 'run;'; put '%inc skip;'; put '%end;'; put '%end;'; put '%else %if "&sysprocessmode " = "SAS Compute Server " %then %do;'; put '/* endsas kills the session making it harder to fetch results */'; put 'data _null_;'; put 'syswarningtext=symget(''syswarningtext'');'; put 'syserrortext=symget(''syserrortext'');'; put 'abort_msg=symget(''msg'');'; put 'syscc=symget(''syscc'');'; put 'sysuserid=symget(''sysuserid'');'; put 'iftrue=symget(''iftrue'');'; put 'put (_all_)(/=);'; put 'call symputx(''syscc'',0);'; put 'abort cancel nolist;'; put 'run;'; put '%end;'; put '%else %do;'; put '%abort cancel;'; put '%end;'; put '%end;'; put '%else %do;'; put '%put _all_;'; put '%abort cancel;'; put '%end;'; put '%mend mp_abort;'; put '/** @endcond */'; put '%macro mm_getstpcode('; put 'tree=/User Folders/sasdemo/somestp'; put ',name='; put ',outloc=0'; put ',outref=0'; put ',mDebug=1'; put ',showlog=NO'; put ');'; put '%local mD;'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing &sysmacroname..sas;'; put '%&mD.put _local_;'; put '%if %length(&name)>0 %then %let name=/&name;'; put '/* first, check if STP exists */'; put '%local tsuri;'; put '%let tsuri=stopifempty ;'; put 'data _null_;'; put 'format type uri tsuri value $200.;'; put 'call missing (of _all_);'; put 'path="&tree&name(StoredProcess)";'; put '/* first, find the STP ID */'; put 'if metadata_pathobj("",path,"StoredProcess",type,uri)>0 then do;'; put '/* get sourcecode */'; put 'cnt=1;'; put 'do while (metadata_getnasn(uri,"Notes",cnt,tsuri)>0);'; put 'rc=metadata_getattr(tsuri,"Name",value);'; put '&mD.put tsuri= value=;'; put 'if value="SourceCode" then do;'; put '/* found it! */'; put 'rc=metadata_getattr(tsuri,"Id",value);'; put 'call symputx(''tsuri'',value,''l'');'; put 'stop;'; put 'end;'; put 'cnt+1;'; put 'end;'; put 'end;'; put 'else put (_all_)(=);'; put 'run;'; put '%mp_abort(iftrue= (&tsuri=stopifempty)'; put ',mac=mm_getstpcode'; put ',msg=%str(&tree&name.(StoredProcess) not found!)'; put ')'; put '/**'; put '* Now we can extract the textstore'; put '*/'; put 'filename __getdoc temp lrecl=10000000;'; put 'proc metadata'; put 'in="$METAREPOSITORY'; put ''; put 'SAS1"'; put 'out=__getdoc ;'; put 'run;'; put '/* find the beginning of the text */'; put '%local start;'; put 'data _null_;'; put 'infile __getdoc lrecl=10000;'; put 'input;'; put 'start=index(_infile_,''StoredText="'');'; put 'if start then do;'; put 'call symputx("start",start+11);'; put '*putlog ''"'' _infile_ ''"'';'; put 'end;'; put 'stop;'; put '%local outeng;'; put '%if "&outloc"="0" %then %let outeng=TEMP;'; put '%else %let outeng="&outloc";'; put '%local fref;'; put '%if &outref=0 %then %let fref=%mf_getuniquefileref();'; put '%else %let fref=&outref;'; put '/* read the content, byte by byte, resolving escaped chars */'; put 'filename &fref &outeng lrecl=100000;'; put 'data _null_;'; put 'length filein 8 fileid 8;'; put 'filein = fopen("__getdoc","I",1,"B");'; put 'fileid = fopen("&fref","O",1,"B");'; put 'rec = "20"x;'; put 'length entity $6;'; put 'do while(fread(filein)=0);'; put 'x+1;'; put 'if x>&start then do;'; put 'rc = fget(filein,rec,1);'; put 'if rec=''"'' then leave;'; put 'else if rec="&" then do;'; put 'entity=rec;'; put 'do until (rec=";");'; put 'if fread(filein) ne 0 then goto getout;'; put 'rc = fget(filein,rec,1);'; put 'entity=cats(entity,rec);'; put 'end;'; put 'select (entity);'; put 'when (''&'' ) rec=''&'' ;'; put 'when (''<'' ) rec=''<'' ;'; put 'when (''>'' ) rec=''>'' ;'; put 'when (''''') rec="''" ;'; put 'when (''"'') rec=''"'' ;'; put 'when ('' '') rec=''0A''x;'; put 'when ('' '') rec=''0D''x;'; put 'when (''$'' ) rec=''$'' ;'; put 'when ('' '') rec=''09''x;'; put 'otherwise putlog "%str(WARN)ING: missing value for " entity=;'; put 'end;'; put 'rc =fput(fileid, substr(rec,1,1));'; put 'rc =fwrite(fileid);'; put 'end;'; put 'else do;'; put 'rc =fput(fileid,rec);'; put 'rc =fwrite(fileid);'; put 'end;'; put 'end;'; put 'end;'; put 'getout:'; put 'rc=fclose(filein);'; put 'rc=fclose(fileid);'; put 'run;'; put '%if &showlog=YES %then %do;'; put 'data _null_;'; put 'infile &fref lrecl=32767 end=last;'; put 'input;'; put 'if _n_=1 then putlog ''>>stpcodeBEGIN<<'';'; put 'putlog _infile_;'; put 'if last then putlog ''>>stpcodeEND<<'';'; put 'run;'; put '%end;'; put 'filename __getdoc clear;'; put '%if &outref=0 %then %do;'; put 'filename &fref clear;'; put '%end;'; put '%mend mm_getstpcode;'; put '%macro dc_getsettings();'; put '%global _program;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(syscc=&syscc on entry (&syswarningtext &syserrortext))'; put ')'; put '%if %length(&_PROGRAM)>1 %then %let root=&_program;'; put '%else %do;'; put '%global _metauser;'; put '%let _metauser=&sysuserid;'; put '/* to mimic a "real" _program we need to give a dummy role and stp name */'; put '%let root=/dummyRole/dummyName;'; put '%end;'; put '/* the DC precode is stored in the Admin folder in the root of'; put 'the project. Lets find that root. */'; put '%put &=root;'; put '%let root=%mf_getapploc();'; put '%put &=root;'; put '/* Now we know the root location we can retrieve the params */'; put '%let temploc=%sysfunc(pathname(work))/settings.txt;'; put '%mm_getstpcode(tree=&root/services/public'; put ',name=Data_Controller_Settings'; put ',outloc=&temploc'; put ')'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Unable to run getstpcode)'; put ')'; put 'filename _getsets "&temploc" lrecl=2000;'; put '/*'; put 'Do not use mp_include here - this puts a copy in every service, which creates'; put 'compilation problems when calling services from mp_include'; put '*/'; put '%inc _getsets/source2;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Problem running &sysmacroname (&syswarningtext &syserrortext))'; put ')'; put '%mend dc_getsettings;'; put '%macro mf_fmtdttm('; put ')/*/STORE SOURCE*/;'; put '%if "&sysver"="9.2" or "&sysver"="9.3"'; put 'or ("&sysver"="9.4" and "%substr(&SYSVLONG,9,1)" le "3")'; put 'or "%substr(&sysver,1,1)"="4"'; put 'or "%substr(&sysver,1,1)"="5"'; put '%then %do;DATETIME19.3%end;'; put '%else %do;E8601DT26.6%end;'; put '%mend mf_fmtdttm;'; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '%macro mp_init(prefix=SASJS'; put ')/*/STORE SOURCE*/;'; put '%if %symexist(SASJS_PREFIX) %then %return; /* only run once */'; put '%global'; put 'SASJS_PREFIX /* the ONLY hard-coded global macro variable in SASjs */'; put '&prefix._FUNCTIONS /* used in mcf_init() to track core function compilation */'; put '&prefix._INIT_NUM /* initialisation time as numeric */'; put '&prefix._INIT_DTTM /* initialisation time in E8601DT26.6 format */'; put '&prefix.WORK /* avoid typing %sysfunc(pathname(work)) every time */'; put ';'; put '%let sasjs_prefix=&prefix;'; put 'data _null_;'; put 'dttm=datetime();'; put 'call symputx("&sasjs_prefix._init_num",dttm,''g'');'; put 'call symputx("&sasjs_prefix._init_dttm",put(dttm,E8601DT26.6),''g'');'; put 'call symputx("&sasjs_prefix.work",pathname(''WORK''),''g'');'; put 'run;'; put 'options'; put 'compress=CHAR /* default is none so ensure we have something! */'; put 'datastmtchk=ALLKEYWORDS /* protection from overwriting input datasets */'; put 'errorcheck=STRICT /* catch errs in libname/filename statements */'; put 'fmterr /* ensure err when a format cannot be found */'; put 'mergenoby=%str(ERR)OR /* throw err when a merge has no BY variables */'; put 'missing=. /* changing this can cause hard to detect errs */'; put 'noquotelenmax /* avoid warnings for long strings */'; put 'noreplace /* avoid overwriting permanent datasets */'; put 'ps=max /* reduce log size slightly */'; put 'ls=max /* reduce log even more and avoid word truncation */'; put 'validmemname=COMPATIBLE /* avoid special characters etc in table names */'; put 'validvarname=V7 /* avoid special characters etc in variable names */'; put 'varinitchk=%str(ERR)OR /* avoid data mistakes from variable name typos */'; put 'varlenchk=%str(ERR)OR /* fail hard if truncation (data loss) can result */'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5" %then %do;'; put 'noautocorrect /* disallow misspelled procedure names */'; put 'dsoptions=note2err /* undocumented - convert bad NOTEs to ERRs */'; put '%end;'; put ';'; put '%mend mp_init;'; put '%macro mpeinit(fetch=YES);'; put '%global mpeinit'; put 'mpeadmins /* group with unrestricted Meditor access */'; put 'mpelocapprovals /* location for landing and staging files */'; put 'mpelib /* location of configuration tables for DC */'; put 'dc_repo_users /* location of user / group metadata */'; put 'dc_licence_key /* extracted in dc_getsettings */'; put 'dc_activation_key /* extracted in dc_getsettings */'; put 'dc_locale /* extracted in dc_getsettings */'; put 'dc_dttmtfmt /* can be overridden in dc_getsettings */'; put '_debug'; put ';'; put '%if &mpeinit=1 %then %return;'; put '%else %let mpeinit=1;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(Problem on service startup (&syswarningtext &syserrortext))'; put ')'; put '%mp_init()'; put '%if &fetch=YES %then %do;'; put '%webout(FETCH)'; put '%end;'; put '%global _CLIENTNAME;'; put '%mp_abort(iftrue= (&_CLIENTNAME=SAS Enterprise Guide)'; put ',mac=&_program..sas'; put ',msg=%str(Data Controller is a web app and should not be executed from EG)'; put ')'; put 'options urlencoding=utf8 nobomfile lrecl=32767;'; put '%let perf=%sysfunc(datetime());'; put '%put perfdiff: 0;'; put '%let dc_locale=SYSTEM; /* default if not set */'; put '/**'; put '* E8601DT26.6 has widest database support - but not all SAS flavours can'; put '* handle it. Override in the settings STP if needed.'; put '*/'; put 'data _null_;'; put 'dc_dttmtfmt=''"%sysfunc(datetime(),''!!"%mf_fmtdttm()"!!'')"dt'';'; put 'call symputx(''dc_dttmtfmt'',dc_dttmtfmt);'; put 'put dc_dttmtfmt=;'; put 'run;'; put '%put &=dc_dttmtfmt;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc prior to dc_getsettings)'; put ')'; put '%dc_getsettings()'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc after dc_getsettings)'; put ')'; put 'data _null_;'; put 'set &DC_LIBREF..mpe_config(where=('; put 'var_scope="DC"'; put 'and &dc_dttmtfmt lt tx_to'; put 'and var_active=1'; put '));'; put 'call symputx(var_name,var_value,''G'');'; put 'putlog var_name "=" var_value;'; put 'run;'; put '%let mpelib=&dc_libref;'; put '%let mpeadmins=&dc_admin_group;'; put '%let mpelocapprovals=&dc_staging_area;'; put '%let dc_repo_users=&dc_repo_users;'; put '%if &dc_locale ne SYSTEM %then %do;'; put 'options locale=&dc_locale;'; put '%end;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(Problem during compilation or with STP precode (&syswarningtext))'; put ')'; put '%mend mpeinit;'; put '%macro mf_mval(var);'; put '%if %symexist(&var) %then %do;'; put '%superq(&var)'; put '%end;'; put '%mend mf_mval;'; put '%macro mf_trimstr(basestr,trimstr);'; put '%local baselen trimlen trimval;'; put '/* return if basestr is shorter than trimstr (or 0) */'; put '%let baselen=%length(%superq(basestr));'; put '%let trimlen=%length(%superq(trimstr));'; put '%if &baselen < &trimlen or &baselen=0 %then %return;'; put '/* obtain the characters from the end of basestr */'; put '%let trimval=%qsubstr(%superq(basestr)'; put ',%length(%superq(basestr))-&trimlen+1'; put ',&trimlen);'; put '/* compare and if matching, chop it off! */'; put '%if %superq(basestr)=%superq(trimstr) %then %do;'; put '%return;'; put '%end;'; put '%else %if %superq(trimval)=%superq(trimstr) %then %do;'; put '%qsubstr(%superq(basestr),1,%length(%superq(basestr))-&trimlen)'; put '%end;'; put '%else %do;'; put '&basestr'; put '%end;'; put '%mend mf_trimstr;'; put '%macro mf_getplatform(switch'; put ')/*/STORE SOURCE*/;'; put '%local a b c;'; put '%if &switch.NONE=NONE %then %do;'; put '%if %symexist(sasjsprocessmode) %then %do;'; put '%if &sasjsprocessmode=Stored Program %then %do;'; put 'SASJS'; put '%return;'; put '%end;'; put '%end;'; put '%if %symexist(sysprocessmode) %then %do;'; put '%if "&sysprocessmode"="SAS Object Server"'; put 'or "&sysprocessmode"= "SAS Compute Server" %then %do;'; put 'SASVIYA'; put '%end;'; put '%else %if "&sysprocessmode"="SAS Stored Process Server"'; put 'or "&sysprocessmode"="SAS Workspace Server"'; put '%then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if %symexist(_metaport) or %symexist(_metauser) %then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if &switch=SASSTUDIO %then %do;'; put '/* return the version of SAS Studio else 0 */'; put '%if %mf_mval(_CLIENTAPP)=%str(SAS Studio) %then %do;'; put '%let a=%mf_mval(_CLIENTVERSION);'; put '%let b=%scan(&a,1,.);'; put '%if %eval(&b >2) %then %do;'; put '&b'; put '%end;'; put '%else 0;'; put '%end;'; put '%else 0;'; put '%end;'; put '%else %if &switch=VIYARESTAPI %then %do;'; put '%mf_trimstr(%sysfunc(getoption(servicesbaseurl)),/)'; put '%end;'; put '%mend mf_getplatform;'; put '%macro mpeterm();'; put '%local oldloc;'; put 'data _null_;'; put 'if symexist(''SYSPRINTTOLOG'') then oldloc=symget(''SYSPRINTTOLOG'');'; put 'else oldloc=getoption(''LOG'');'; put 'if subpad(oldloc,1,1) not in (''"'',"''",'' '') then oldloc=quote(cats(oldloc));'; put 'call symputx(''oldloc'',oldloc,''l'');'; put 'run;'; put '%if %length(&oldloc)>0 %then %do;'; put 'proc printto log=log;'; put 'run;'; put 'data _null_;'; put 'infile &oldloc;'; put 'input; putlog _infile_;'; put 'run;'; put '%end;'; put '%if %sysfunc(exist(&dc_libref..mpe_requests)) and %mf_getplatform() ne SASVIYA'; put '%then %do;'; put 'data ;'; put 'if 0 then set &dc_libref..mpe_requests;'; put 'request_dttm=%sysfunc(datetime());'; put 'request_user="%mf_getuser()";'; put 'request_service="%scan(&_program,-2,/)/%scan(&_program,-1,/)";'; put 'request_params='''';'; put 'output;stop;'; put 'proc append base=&dc_libref..mpe_requests data=&syslast force nowarn;'; put 'run;'; put '%end;'; put '%mend mpeterm;'; put '* SAS Macros end;'; put '* SAS Includes start;'; put '* SAS Includes end;'; put '* Binary Files start;'; put '* Binary Files end;'; put '* ServiceInit start;'; put 'options noquotelenmax ps=max;'; put '/* create dummy macros to prevent stpbegin / stpend from corrupting sessions */'; put '%macro stpbegin();'; put '%put NOTE: the STPBEGIN macro should not be used for web apps!;'; put '%mend stpbegin;'; put '%macro stpend();'; put '%put NOTE: the STPEND macro should not be used for web apps!;'; put '%mend stpend;'; put '* ServiceInit end;'; put '* Service start;'; put '/**'; put '@file usermembersbyrole.sas'; put '@brief List the members of a role'; put '

SAS Macros

'; put '@li mp_abort.sas'; put '@li mpeinit.sas'; put '@author 4GL Apps Ltd'; put '@copyright 4GL Apps Ltd. This code may only be used within Data Controller'; put 'and may not be re-distributed or re-sold without the express permission of'; put '4GL Apps Ltd.'; put '**/'; put '%mpeinit()'; put 'data sasMembers sasgroups;'; put 'attrib uriGrp uriMem roleId roleName Group_or_Role MemberName MemberType'; put 'MemberUpdated membercreated emailuri length=$64'; put 'roleDesc email length=$256'; put 'rcGrp rcMem rc i j length=3;'; put 'call missing (of _all_);'; put 'set iwant;'; put 'i=1;'; put '* Grab the URI for the first Group ;'; put 'rcGrp=metadata_getnobj(roleid,i,uriGrp);'; put '* If Group found, enter do loop ;'; put 'if rcGrp>0 then do;'; put 'call missing (rcMem,uriMem,roleId,roleName,Group_or_Role'; put ',MemberName,MemberType,roleDesc);'; put '* get group info ;'; put 'rc = metadata_getattr(uriGrp,"Id",roleId);'; put 'rc = metadata_getattr(uriGrp,"Name",roleName);'; put 'rc = metadata_getattr(uriGrp,"PublicType",Group_or_Role);'; put 'rc = metadata_getattr(uriGrp,"Desc",roleDesc);'; put 'j=1;'; put 'if Group_or_Role=''Role'' then do while'; put '(metadata_getnasn(uriGrp,"MemberIdentities",j,uriMem) > 0);'; put 'call missing (MemberName,MemberType);'; put 'call missing(email,emailuri);'; put 'rc = metadata_getattr(uriMem,"Name",MemberName);'; put 'rc = metadata_getattr(uriMem,"PublicType",MemberType);'; put 'rc=metadata_getattr(uriMem, "MetadataCreated", MemberCreated);'; put 'rc=metadata_getattr(uriMem, "MetadataUpdated", MemberUpdated);'; put 'emailrc=metadata_getnasn(uriMem,"EmailAddresses",1,emailuri);'; put 'if (emailrc>0) then rc=metadata_getattr(emailuri,"Address",email);'; put 'if membertype=''UserGroup'' then output sasgroups;'; put 'else output sasmembers;'; put 'j+1;'; put 'end;'; put 'end;'; put 'if _n_=1 then delete; /* no roles so don''t send empty row */'; put 'keep uriMem membertype membername MemberCreated MemberUpdated email;'; put 'run;'; put '%webout(OPEN)'; put '%webout(OBJ,sasGroups)'; put '%webout(OBJ,sasMembers)'; put '%webout(CLOSE)'; put '* Service end;'; run; %mm_createwebservice(path=&appLoc/&path, name=&service, code=sascode, server=&serverName, replace=yes) filename sascode clear; %let service=userroles; filename sascode temp lrecl=32767; data _null_; file sascode; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '/**'; put '@file mp_jsonout.sas'; put '@brief Writes JSON in SASjs format to a fileref'; put '@details This macro can be used to OPEN a JSON stream and send one or more'; put 'tables as arrays of rows, where each row can be an object or a nested array.'; put 'There are two engines available - DATASTEP or PROCJSON.'; put 'PROC JSON is fast but will produce errs like the ones below if'; put 'special chars are encountered.'; put '> (ERR)OR: Some code points did not transcode.'; put '> An object or array close is not valid at this point in the JSON text.'; put '> Date value out of range'; put 'If this happens, try running with ENGINE=DATASTEP.'; put 'The DATASTEP engine is used to handle special SAS missing numerics, and'; put 'can also convert entire datasets to formatted values. Output JSON is always'; put 'in UTF-8.'; put 'Usage:'; put 'filename tmp temp;'; put 'data class; set sashelp.class;run;'; put '%mp_jsonout(OPEN,jref=tmp)'; put '%mp_jsonout(OBJ,class,jref=tmp)'; put '%mp_jsonout(OBJ,class,dslabel=class2,jref=tmp,showmeta=Y)'; put '%mp_jsonout(CLOSE,jref=tmp)'; put 'data _null_;'; put 'infile tmp;'; put 'input;putlog _infile_;'; put 'run;'; put 'If you are building web apps with SAS then you are strongly encouraged to use'; put 'the mX_createwebservice macros in combination with the'; put '[sasjs adapter](https://github.com/sasjs/adapter).'; put 'For more information see https://sasjs.io'; put '@param [in] action Valid values:'; put '@li OPEN - opens the JSON'; put '@li OBJ - sends a table with each row as an object'; put '@li ARR - sends a table with each row in an array'; put '@li CLOSE - closes the JSON'; put '@param [in] ds The dataset to send. Must be a work table.'; put '@param [out] jref= (_webout) The fileref to which to send the JSON'; put '@param [out] dslabel= The name to give the table in the exported JSON'; put '@param [in] fmt= (Y) Whether to keep (Y) or strip (N) formats from the table'; put '@param [in] engine= (DATASTEP) Which engine to use to send the JSON. Options:'; put '@li PROCJSON (default)'; put '@li DATASTEP (more reliable when data has non standard characters)'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to JSON'; put '

Related Files

'; put '@li mp_ds2fmtds.sas'; put '@version 9.2'; put '@author Allan Bowe'; put '@source https://github.com/sasjs/core'; put '**/'; put '%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y'; put ',engine=DATASTEP'; put ',missing=NULL'; put ',showmeta=N'; put ',maxobs=MAX'; put ')/*/STORE SOURCE*/;'; put '%local tempds colinfo fmtds i numcols numobs stmt_obs lastobs optval'; put 'tmpds1 tmpds2 tmpds3 tmpds4;'; put '%let numcols=0;'; put '%if &maxobs ne MAX %then %let stmt_obs=%str(if _n_>&maxobs then stop;);'; put '%if &action=OPEN %then %do;'; put 'options nobomfile;'; put 'data _null_;file &jref encoding=''utf-8'' lrecl=200;'; put 'put ''{"PROCESSED_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''"'';'; put 'run;'; put '%end;'; put '%else %if (&action=ARR or &action=OBJ) %then %do;'; put '/* force variable names to always be uppercase in the JSON */'; put 'options validvarname=upcase;'; put '/* To avoid issues with _webout on EBI - such as encoding diffs and truncation'; put '(https://support.sas.com/kb/49/325.html) we use temporary files */'; put 'filename _sjs1 temp lrecl=200 ;'; put 'data _null_; file _sjs1 encoding=''utf-8'';'; put 'put ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":";'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjs1 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs1 clear;'; put '/* grab col defs */'; put 'proc contents noprint data=&ds'; put 'out=_data_(keep=name type length format formatl formatd varnum label);'; put 'run;'; put '%let colinfo=%scan(&syslast,2,.);'; put 'proc sort data=&colinfo;'; put 'by varnum;'; put 'run;'; put '/* move meta to mac vars */'; put 'data &colinfo;'; put 'if _n_=1 then call symputx(''numcols'',nobs,''l'');'; put 'set &colinfo end=last nobs=nobs;'; put 'name=upcase(name);'; put '/* fix formats */'; put 'if type=2 or type=6 then do;'; put 'typelong=''char'';'; put 'length fmt $49.;'; put 'if format='''' then fmt=cats(''$'',length,''.'');'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else fmt=cats(format,formatl,''.'');'; put 'end;'; put 'else do;'; put 'typelong=''num'';'; put 'if format='''' then fmt=''best.'';'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else if formatd=0 then fmt=cats(format,formatl,''.'');'; put 'else fmt=cats(format,formatl,''.'',formatd);'; put 'end;'; put '/* 32 char unique name */'; put 'newname=''sasjs''!!substr(cats(put(md5(name),$hex32.)),1,27);'; put 'call symputx(cats(''name'',_n_),name,''l'');'; put 'call symputx(cats(''newname'',_n_),newname,''l'');'; put 'call symputx(cats(''length'',_n_),length,''l'');'; put 'call symputx(cats(''fmt'',_n_),fmt,''l'');'; put 'call symputx(cats(''type'',_n_),type,''l'');'; put 'call symputx(cats(''typelong'',_n_),typelong,''l'');'; put 'call symputx(cats(''label'',_n_),coalescec(label,name),''l'');'; put '/* overwritten when fmt=Y and a custom format exists in catalog */'; put 'if typelong=''num'' then call symputx(cats(''fmtlen'',_n_),200,''l'');'; put 'else call symputx(cats(''fmtlen'',_n_),min(32767,ceil((length+10)*1.5)),''l'');'; put 'run;'; put '%let tempds=%substr(_%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql;'; put 'select count(*) into: lastobs from &ds;'; put '%if &maxobs ne MAX %then %let lastobs=%sysfunc(min(&lastobs,&maxobs));'; put '%if &engine=PROCJSON %then %do;'; put '%if &missing=STRING %then %do;'; put '%put &sysmacroname: Special Missings not supported in proc json.;'; put '%put &sysmacroname: Switching to DATASTEP engine;'; put '%goto datastep;'; put '%end;'; put 'data &tempds;'; put 'set &ds;'; put '&stmt_obs;'; put '%if &fmt=N %then format _numeric_ best32.;;'; put '/* PRETTY is necessary to avoid line truncation in large files */'; put 'filename _sjs2 temp lrecl=131068 encoding=''utf-8'';'; put 'proc json out=_sjs2 pretty'; put '%if &action=ARR %then nokeys ;'; put ';export &tempds / nosastags fmtnumeric;'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs2 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs2 clear;'; put '%end;'; put '%else %if &engine=DATASTEP %then %do;'; put '%datastep:'; put '%if %sysfunc(exist(&ds)) ne 1 & %sysfunc(exist(&ds,VIEW)) ne 1'; put '%then %do;'; put '%put &sysmacroname: &ds NOT FOUND!!!;'; put '%return;'; put '%end;'; put '%if &fmt=Y %then %do;'; put '/**'; put '* Extract format definitions'; put '* First, by getting library locations from dictionary.formats'; put '* Then, by exporting the width using proc format'; put '* Cannot use maxw from sashelp.vformat as not always populated'; put '* Cannot use fmtinfo() as not supported in all flavours'; put '*/'; put '%let tmpds1=%substr(fmtsum%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds2=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds3=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds4=%substr(col%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql noprint;'; put 'create table &tmpds1 as'; put 'select cats(libname,''.'',memname) as FMTCAT,'; put 'FMTNAME'; put 'from dictionary.formats'; put 'where fmttype=''F'' and libname is not null'; put 'and fmtname in (select format from &colinfo where format is not null)'; put 'order by 1;'; put 'create table &tmpds2('; put 'FMTNAME char(32),'; put 'LENGTH num'; put ');'; put '%local catlist cat fmtlist i;'; put 'select distinct fmtcat into: catlist separated by '' '' from &tmpds1;'; put '%do i=1 %to %sysfunc(countw(&catlist,%str( )));'; put '%let cat=%scan(&catlist,&i,%str( ));'; put 'proc sql;'; put 'select distinct fmtname into: fmtlist separated by '' '''; put 'from &tmpds1 where fmtcat="&cat";'; put 'proc format lib=&cat cntlout=&tmpds3(keep=fmtname length);'; put 'select &fmtlist;'; put 'run;'; put 'proc sql;'; put 'insert into &tmpds2 select distinct fmtname,length from &tmpds3;'; put '%end;'; put 'proc sql;'; put 'create table &tmpds4 as'; put 'select a.*, b.length as MAXW'; put 'from &colinfo a'; put 'left join &tmpds2 b'; put 'on cats(a.format)=cats(upcase(b.fmtname))'; put 'order by a.varnum;'; put 'data _null_;'; put 'set &tmpds4;'; put 'if not missing(maxw);'; put 'call symputx('; put 'cats(''fmtlen'',_n_),'; put '/* vars need extra padding due to JSON escaping of special chars */'; put 'min(32767,ceil((max(length,maxw)+10)*1.5))'; put ',''l'''; put ');'; put 'run;'; put '/* configure varlenchk - as we are explicitly shortening the variables */'; put '%let optval=%sysfunc(getoption(varlenchk));'; put 'options varlenchk=NOWARN;'; put 'data _data_(compress=char);'; put '/* shorten the new vars */'; put 'length'; put '%do i=1 %to &numcols;'; put '&&name&i $&&fmtlen&i'; put '%end;'; put ';'; put '/* rename on entry */'; put 'set &ds(rename=('; put '%do i=1 %to &numcols;'; put '&&name&i=&&newname&i'; put '%end;'; put '));'; put '&stmt_obs;'; put 'drop'; put '%do i=1 %to &numcols;'; put '&&newname&i'; put '%end;'; put ';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=num %then %do;'; put '&&name&i=cats(put(&&newname&i,&&fmt&i));'; put '%end;'; put '%else %do;'; put '&&name&i=put(&&newname&i,&&fmt&i);'; put '%end;'; put '%end;'; put 'if _error_ then do;'; put 'call symputx(''syscc'',1012);'; put 'stop;'; put 'end;'; put 'run;'; put '%let fmtds=&syslast;'; put 'options varlenchk=&optval;'; put '%end;'; put 'proc format; /* credit yabwon for special null removal */'; put 'value bart (default=40)'; put '%if &missing=NULL %then %do;'; put '._ - .z = null'; put '%end;'; put '%else %do;'; put '._ = [quote()]'; put '. = null'; put '.a - .z = [quote()]'; put '%end;'; put 'other = [best.];'; put 'data &tempds;'; put 'attrib _all_ label='''';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'length &&name&i $&&fmtlen&i...;'; put 'format &&name&i $&&fmtlen&i...;'; put '%end;'; put '%end;'; put '%if &fmt=Y %then %do;'; put 'set &fmtds;'; put '%end;'; put '%else %do;'; put 'set &ds;'; put '%end;'; put '&stmt_obs;'; put 'format _numeric_ bart.;'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'if findc(&&name&i,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put '&&name&i=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,&&name&i)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else &&name&i=quote(cats(&&name&i));'; put '%end;'; put '%end;'; put 'run;'; put 'filename _sjs3 temp lrecl=131068 ;'; put 'data _null_;'; put 'file _sjs3 encoding=''utf-8'';'; put 'if _n_=1 then put "[";'; put 'set &tempds;'; put 'if _n_>1 then put "," @; put'; put '%if &action=ARR %then "[" ; %else "{" ;'; put '%do i=1 %to &numcols;'; put '%if &i>1 %then "," ;'; put '%if &action=OBJ %then """&&name&i"":" ;'; put '"&&name&i"n /* name literal for reserved variable names */'; put '%end;'; put '%if &action=ARR %then "]" ; %else "}" ; ;'; put '/* close out the table */'; put 'data _null_;'; put 'file _sjs3 mod encoding=''utf-8'';'; put 'put '']'';'; put 'run;'; put 'data _null_;'; put 'infile _sjs3 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs3 clear;'; put '%end;'; put 'proc sql;'; put 'drop table &colinfo, &tempds;'; put '%if %substr(&showmeta,1,1)=Y %then %do;'; put 'filename _sjs4 temp lrecl=131068 encoding=''utf-8'';'; put 'data _null_;'; put 'file _sjs4;'; put 'length label $350;'; put 'put ", ""$%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":{""vars"":{";'; put 'do i=1 to &numcols;'; put 'name=quote(trim(symget(cats(''name'',i))));'; put 'format=quote(trim(symget(cats(''fmt'',i))));'; put 'label=quote(prxchange(''s/\\/\\\\/'',-1,trim(symget(cats(''label'',i)))));'; put 'length=quote(trim(symget(cats(''length'',i))));'; put 'type=quote(trim(symget(cats(''typelong'',i))));'; put 'if i>1 then put "," @@;'; put 'put name '':{"format":'' format '',"label":'' label'; put ''',"length":'' length '',"type":'' type ''}'';'; put 'end;'; put 'put ''}}'';'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs4 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs4 clear;'; put '%end;'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put 'data _null_; file &jref encoding=''utf-8'' mod ;'; put 'put "}";'; put 'run;'; put '%end;'; put '%mend mp_jsonout;'; put '/**'; put '@file mm_webout.sas'; put '@brief Send data to/from SAS Stored Processes'; put '@details This macro should be added to the start of each Stored Process,'; put '**immediately** followed by a call to:'; put '%mm_webout(FETCH)'; put 'This will read all the input data and create same-named SAS datasets in the'; put 'WORK library. You can then insert your code, and send data back using the'; put 'following syntax:'; put 'data some datasets; * make some data ;'; put 'retain some columns;'; put 'run;'; put '%mm_webout(OPEN)'; put '%mm_webout(ARR,some) * Array format, fast, suitable for large tables ;'; put '%mm_webout(OBJ,datasets) * Object format, easier to work with ;'; put 'Finally, wrap everything up send some helpful system variables too'; put '%mm_webout(CLOSE)'; put '@param [in] action Either FETCH, OPEN, ARR, OBJ or CLOSE'; put '@param [in] ds The dataset to send back to the frontend'; put '@param [out] dslabel= Value to use instead of table name for sending to JSON'; put '@param [in] fmt= (N) Setting Y converts all vars to their formatted values'; put '@param [out] fref= (_webout) The fileref to which to write the JSON'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] workobs= (0) When set to a positive integer, will create a new'; put 'output object (WORK) which contains this number of observations from all'; put 'tables in the WORK library.'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to output JSON'; put '

SAS Macros

'; put '@li mp_jsonout.sas'; put '

Related Macros

'; put '@li ms_webout.sas'; put '@li mv_webout.sas'; put '@version 9.3'; put '@author Allan Bowe'; put '**/'; put '%macro mm_webout(action,ds,dslabel=,fref=_webout,fmt=N,missing=NULL'; put ',showmeta=N,maxobs=MAX,workobs=0'; put ');'; put '%global _webin_file_count _webin_fileref1 _webin_name1 _program _debug'; put 'sasjs_tables;'; put '%local i tempds jsonengine;'; put '/* see https://github.com/sasjs/core/issues/41 */'; put '%if "%upcase(&SYSENCODING)" ne "UTF-8" %then %let jsonengine=PROCJSON;'; put '%else %let jsonengine=DATASTEP;'; put '%if &action=FETCH %then %do;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'options mprint notes mprintnest;'; put '%end;'; put '%let _webin_file_count=%eval(&_webin_file_count+0);'; put '/* now read in the data */'; put '%do i=1 %to &_webin_file_count;'; put '%if &_webin_file_count=1 %then %do;'; put '%let _webin_fileref1=&_webin_fileref;'; put '%let _webin_name1=&_webin_name;'; put '%end;'; put 'data _null_;'; put 'infile &&_webin_fileref&i termstr=crlf;'; put 'input;'; put 'call symputx(''input_statement'',_infile_);'; put 'putlog "&&_webin_name&i input statement: " _infile_;'; put 'stop;'; put 'data &&_webin_name&i;'; put 'infile &&_webin_fileref&i firstobs=2 dsd termstr=crlf encoding=''utf-8'';'; put 'input &input_statement;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'if _n_<20 then putlog _infile_;'; put '%end;'; put 'run;'; put '%let sasjs_tables=&sasjs_tables &&_webin_name&i;'; put '%end;'; put '%end;'; put '%else %if &action=OPEN %then %do;'; put '/* fix encoding */'; put 'OPTIONS NOBOMFILE;'; put '/**'; put '* check xengine type to avoid the below err message:'; put '* > Function is only valid for filerefs using the CACHE access method.'; put '*/'; put 'data _null_;'; put 'set sashelp.vextfl(where=(fileref="_WEBOUT"));'; put 'if xengine=''STREAM'' then do;'; put 'rc=stpsrv_header(''Content-type'',"text/html; encoding=utf-8");'; put 'end;'; put 'run;'; put '/* setup json */'; put 'data _null_;file &fref encoding=''utf-8'';'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutBEGIN<<'';'; put '%end;'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'run;'; put '%end;'; put '%else %if &action=ARR or &action=OBJ %then %do;'; put '%mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt,jref=&fref'; put ',engine=&jsonengine,missing=&missing,showmeta=&showmeta,maxobs=&maxobs'; put ')'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put '/* To avoid issues with _webout on EBI we use a temporary file */'; put 'filename _sjsref temp lrecl=131068;'; put '%if %str(&workobs) > 0 %then %do;'; put '/* if debug mode, send back first XX records of each work table also */'; put 'data;run;%let tempds=%scan(&syslast,2,.);'; put 'ods output Members=&tempds;'; put 'proc datasets library=WORK memtype=data;'; put '%local wtcnt;%let wtcnt=0;'; put 'data _null_;'; put 'set &tempds;'; put 'if not (upcase(name) =:"DATA"); /* ignore temp datasets */'; put 'i+1;'; put 'call symputx(cats(''wt'',i),name,''l'');'; put 'call symputx(''wtcnt'',i,''l'');'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put ",""WORK"":{";'; put '%do i=1 %to &wtcnt;'; put '%let wt=&&wt&i;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'dsid=open("WORK.&wt",''is'');'; put 'nlobs=attrn(dsid,''NLOBS'');'; put 'nvars=attrn(dsid,''NVARS'');'; put 'rc=close(dsid);'; put 'if &i>1 then put '',''@;'; put 'put " ""&wt"" : {";'; put 'put ''"nlobs":'' nlobs;'; put 'put '',"nvars":'' nvars;'; put '%mp_jsonout(OBJ,&wt,jref=_sjsref,dslabel=first10rows,showmeta=Y'; put ',maxobs=&workobs'; put ')'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put '%end;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put 'run;'; put '%end;'; put '/* close off json */'; put 'data _null_;file _sjsref mod encoding=''utf-8'';'; put 'length SYSPROCESSNAME syserrortext syswarningtext autoexec $512;'; put 'put ",""_DEBUG"" : ""&_debug"" ";'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'autoexec=quote(urlencode(trim(getoption(''autoexec''))));'; put 'put '',"AUTOEXEC" : '' autoexec;'; put 'put ",""MF_GETUSER"" : ""%mf_getuser()"" ";'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'put ",""SYSENCODING"" : ""&sysencoding"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSPROCESSID"" : ""&SYSPROCESSID"" ";'; put 'put ",""SYSPROCESSMODE"" : ""&SYSPROCESSMODE"" ";'; put 'SYSPROCESSNAME=quote(urlencode(cats(SYSPROCESSNAME)));'; put 'put ",""SYSPROCESSNAME"" : " SYSPROCESSNAME;'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put '',"SYSWARNINGTEXT" : '' syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'length memsize $32;'; put 'memsize="%sysfunc(INPUTN(%sysfunc(getoption(memsize)), best.),sizekmg.)";'; put 'memsize=quote(cats(memsize));'; put 'put '',"MEMSIZE" : '' memsize;'; put 'put "}" @;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutEND<<'';'; put '%end;'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjsref lrecl=1 recfm=n;'; put 'file &fref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjsref clear;'; put '%end;'; put '%mend mm_webout;'; put '%macro webout(action,ds,dslabel=,fmt=,missing=NULL,showmeta=NO,maxobs=MAX);'; put '%mm_webout(&action,ds=&ds,dslabel=&dslabel,fmt=&fmt'; put ',missing=&missing'; put ',showmeta=&showmeta'; put ',maxobs=&maxobs'; put ') %mend;'; put '/* provide additional debug info */'; put '%global _program;'; put '%put &=syscc;'; put '%put user=%mf_getuser();'; put '%put pgm=&_program;'; put '%put timestamp=%sysfunc(datetime(),datetime19.);'; put '* Service Variables start;'; put '* Service Variables end;'; put '* SAS Macros start;'; put '%macro mf_getapploc(pgm);'; put '%if "&pgm"="" %then %do;'; put '%if %symexist(_program) %then %let pgm=&_program;'; put '%else %do;'; put '%put &sysmacroname: No value provided and no _program variable available;'; put '%return;'; put '%end;'; put '%end;'; put '%local root;'; put '/**'; put '* First check we are not in the tests/macros folder (which has no subfolders)'; put '* or specifically in the testsetup or testteardown services'; put '*/'; put '%if %index(&pgm,/tests/macros/)'; put 'or %index(&pgm,/tests/testsetup)'; put 'or %index(&pgm,/tests/testteardown)'; put '%then %do;'; put '%let root=%substr(&pgm,1,%index(&pgm,/tests)-1);'; put '&root'; put '%return;'; put '%end;'; put '/**'; put '* Next, move up two levels to avoid matches on subfolder or service name'; put '*/'; put '%let root=%substr(&pgm,1,%length(&pgm)-%length(%scan(&pgm,-1,/))-1);'; put '%let root=%substr(&root,1,%length(&root)-%length(%scan(&root,-1,/))-1);'; put '%if %index(&root,/tests/) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/tests/)-1);'; put '%end;'; put '%else %if %index(&root,/services) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/services)-1);'; put '%end;'; put '%else %if %index(&root,/jobs) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/jobs)-1);'; put '%end;'; put '%else %put &sysmacroname: Could not find an app location from &pgm;'; put '&root'; put '%mend mf_getapploc ;'; put '%macro mf_getuniquefileref(prefix=_,maxtries=1000,lrecl=32767);'; put '%local rc fname;'; put '%if &prefix=0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%end;'; put '%else %do;'; put '%local x len;'; put '%let len=%eval(8-%length(&prefix));'; put '%let x=0;'; put '%do x=0 %to &maxtries;'; put '%let fname=&prefix%substr(%sysfunc(ranuni(0)),3,&len);'; put '%if %sysfunc(fileref(&fname)) > 0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%return;'; put '%end;'; put '%end;'; put '%put unable to find available fileref after &maxtries attempts;'; put '%end;'; put '%mend mf_getuniquefileref;'; put '%macro mp_abort(mac=mp_abort.sas, type=, msg=, iftrue=%str(1=1)'; put ', errds=work.mp_abort_errds'; put ', mode=REGULAR'; put ')/*/STORE SOURCE*/;'; put '%global sysprocessmode sysprocessname sasjs_stpsrv_header_loc sasjsprocessmode;'; put '%local fref fid i;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%put NOTE: /// mp_abort macro executing //;'; put '%if %length(&mac)>0 %then %put NOTE- called by &mac;'; put '%put NOTE - &msg;'; put '%if %symexist(_SYSINCLUDEFILEDEVICE)'; put '/* abort cancel FILE does not restart outside the INCLUDE on Viya 3.5 */'; put 'and %superq(SYSPROCESSNAME) ne %str(Compute Server)'; put '%then %do;'; put '%if "*&_SYSINCLUDEFILEDEVICE*" ne "**" %then %do;'; put 'data &errds;'; put 'iftrue=''1=1'';'; put 'length mac $100 msg $5000;'; put 'mac=symget(''mac'');'; put 'msg=symget(''msg'');'; put 'run;'; put 'data _null_;'; put 'abort cancel FILE;'; put 'run;'; put '%return;'; put '%end;'; put '%end;'; put '/* Web App Context */'; put '%if %symexist(_PROGRAM)'; put 'or %superq(SYSPROCESSNAME) = %str(Compute Server)'; put 'or &mode=INCLUDE'; put '%then %do;'; put 'options obs=max replace mprint;'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5"'; put '%then %do;'; put 'options nosyntaxcheck;'; put '%end;'; put '%if &mode=INCLUDE %then %do;'; put '%if %sysfunc(exist(&errds))=1 %then %do;'; put 'data _null_;'; put 'set &errds;'; put 'call symputx(''iftrue'',iftrue,''l'');'; put 'call symputx(''mac'',mac,''l'');'; put 'call symputx(''msg'',msg,''l'');'; put 'putlog (_all_)(=);'; put 'run;'; put '%if (&iftrue)=0 %then %return;'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: No include errors found;'; put '%return;'; put '%end;'; put '%end;'; put '/* extract log errs / warns, if exist */'; put '%local logloc logline;'; put '%global logmsg; /* capture global messages */'; put '%if %symexist(SYSPRINTTOLOG) %then %let logloc=&SYSPRINTTOLOG;'; put '%else %let logloc=%qsysfunc(getoption(LOG));'; put 'proc printto log=log;run;'; put '%let logline=0;'; put '%if %length(&logloc)>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input; putlog _infile_;'; put 'i=1;'; put 'retain logonce 0;'; put 'if ('; put '_infile_=:"%str(WARN)ING" or _infile_=:"%str(ERR)OR"'; put ') and logonce=0 then'; put 'do;'; put 'call symputx(''logline'',_n_);'; put 'logonce+1;'; put 'end;'; put 'run;'; put '/* capture log including lines BEFORE the err */'; put '%if &logline>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input;'; put 'i=1;'; put 'stoploop=0;'; put 'if _n_ ge &logline-15 and stoploop=0 then do until (i>22);'; put 'call symputx(''logmsg'',catx(''\n'',symget(''logmsg''),_infile_));'; put 'input;'; put 'i+1;'; put 'stoploop=1;'; put 'end;'; put 'if stoploop=1 then stop;'; put 'run;'; put '%end;'; put '%end;'; put '%if %symexist(SYS_JES_JOB_URI) %then %do;'; put '/* setup webout for Viya */'; put 'options nobomfile;'; put '%if "X&SYS_JES_JOB_URI.X"="XX" %then %do;'; put 'filename _webout temp lrecl=999999 mod;'; put '%end;'; put '%else %do;'; put 'filename _webout filesrvc parenturi="&SYS_JES_JOB_URI"'; put 'name="_webout.json" lrecl=999999 mod;'; put '%end;'; put '%end;'; put '%else %if %sysfunc(filename(fref,&sasjs_stpsrv_header_loc))=0 %then %do;'; put 'options nobomfile;'; put '/* set up http header for SASjs Server */'; put '%let fid=%sysfunc(fopen(&fref,A));'; put '%if &fid=0 %then %do;'; put '%put %str(ERR)OR: %sysfunc(sysmsg());'; put '%return;'; put '%end;'; put '%let rc=%sysfunc(fput(&fid,%str(Content-Type: application/json)));'; put '%let rc=%sysfunc(fwrite(&fid));'; put '%let rc=%sysfunc(fclose(&fid));'; put '%let rc=%sysfunc(filename(&fref));'; put '%end;'; put '/* send response in SASjs JSON format */'; put 'data _null_;'; put 'file _webout mod lrecl=32000 encoding=''utf-8'';'; put 'length msg syswarningtext syserrortext $32767 mode $10 ;'; put 'sasdatetime=datetime();'; put 'msg=symget(''msg'');'; put '%if &logline>0 %then %do;'; put 'msg=cats(msg,''\n\nLog Extract:\n'',symget(''logmsg''));'; put '%end;'; put '/* escape the escapes */'; put 'msg=tranwrd(msg,''\'',''\\'');'; put '/* escape the quotes */'; put 'msg=tranwrd(msg,''"'',''\"'');'; put '/* ditch the CRLFs as chrome complains */'; put 'msg=compress(msg,,''kw'');'; put '/* quote without quoting the quotes (which are escaped instead) */'; put 'msg=cats(''"'',msg,''"'');'; put 'if symexist(''_debug'') then debug=quote(trim(symget(''_debug'')));'; put 'else debug=''""'';'; put 'if symget(''sasjsprocessmode'')=''Stored Program'' then mode=''SASJS'';'; put 'if mode ne ''SASJS'' then put ''>>weboutBEGIN<<'';'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'put '',"sasjsAbort" : [{'';'; put 'put '' "MSG":'' msg ;'; put 'put '' ,"MAC": "'' "&mac" ''"}]'';'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'put '',"_DEBUG":'' debug ;'; put 'if symexist(''_metauser'') then do;'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put 'end;'; put 'if symexist(''SYS_JES_JOB_URI'') then do;'; put 'SYS_JES_JOB_URI=quote(trim(symget(''SYS_JES_JOB_URI'')));'; put 'put '',"SYS_JES_JOB_URI": '' SYS_JES_JOB_URI;'; put 'end;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put ",""SYSWARNINGTEXT"" : " syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'put "}" ;'; put 'if mode ne ''SASJS'' then put ''>>weboutEND<<'';'; put 'run;'; put '%put _all_;'; put '%if "&sysprocessmode " = "SAS Stored Process Server " %then %do;'; put 'data _null_;'; put 'putlog ''stpsrvset program err and syscc'';'; put 'rc=stpsrvset(''program error'', 0);'; put 'call symputx("syscc",0,"g");'; put 'run;'; put '%if &sysscp=WIN'; put 'and 1=0 /* deprecating this logic until we figure out a consistent abort */'; put 'and "%substr(%str(&sysvlong ),1,8)"="9.04.01M"'; put 'and "%substr(%str(&sysvlong ),9,1)">"5" %then %do;'; put '/* skip approach (below) does not work in windows m6+ envs */'; put 'endsas;'; put '%end;'; put '%else %do;'; put '/**'; put '* endsas kills 9.4m3 deployments by orphaning multibridges.'; put '* Abort variants are ungraceful (non zero return code)'; put '* This approach lets SAS run silently until the end :-)'; put '* Caution - fails when called within a %include within a macro'; put '* Use mp_include() to handle this.'; put '*/'; put 'filename skip temp;'; put 'data _null_;'; put 'file skip;'; put 'put ''%macro skip();'';'; put 'comment ''%mend skip; -> fix lint '';'; put 'put ''%macro skippy();'';'; put 'comment ''%mend skippy; -> fix lint '';'; put 'run;'; put '%inc skip;'; put '%end;'; put '%end;'; put '%else %if "&sysprocessmode " = "SAS Compute Server " %then %do;'; put '/* endsas kills the session making it harder to fetch results */'; put 'data _null_;'; put 'syswarningtext=symget(''syswarningtext'');'; put 'syserrortext=symget(''syserrortext'');'; put 'abort_msg=symget(''msg'');'; put 'syscc=symget(''syscc'');'; put 'sysuserid=symget(''sysuserid'');'; put 'iftrue=symget(''iftrue'');'; put 'put (_all_)(/=);'; put 'call symputx(''syscc'',0);'; put 'abort cancel nolist;'; put 'run;'; put '%end;'; put '%else %do;'; put '%abort cancel;'; put '%end;'; put '%end;'; put '%else %do;'; put '%put _all_;'; put '%abort cancel;'; put '%end;'; put '%mend mp_abort;'; put '/** @endcond */'; put '%macro mm_getstpcode('; put 'tree=/User Folders/sasdemo/somestp'; put ',name='; put ',outloc=0'; put ',outref=0'; put ',mDebug=1'; put ',showlog=NO'; put ');'; put '%local mD;'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing &sysmacroname..sas;'; put '%&mD.put _local_;'; put '%if %length(&name)>0 %then %let name=/&name;'; put '/* first, check if STP exists */'; put '%local tsuri;'; put '%let tsuri=stopifempty ;'; put 'data _null_;'; put 'format type uri tsuri value $200.;'; put 'call missing (of _all_);'; put 'path="&tree&name(StoredProcess)";'; put '/* first, find the STP ID */'; put 'if metadata_pathobj("",path,"StoredProcess",type,uri)>0 then do;'; put '/* get sourcecode */'; put 'cnt=1;'; put 'do while (metadata_getnasn(uri,"Notes",cnt,tsuri)>0);'; put 'rc=metadata_getattr(tsuri,"Name",value);'; put '&mD.put tsuri= value=;'; put 'if value="SourceCode" then do;'; put '/* found it! */'; put 'rc=metadata_getattr(tsuri,"Id",value);'; put 'call symputx(''tsuri'',value,''l'');'; put 'stop;'; put 'end;'; put 'cnt+1;'; put 'end;'; put 'end;'; put 'else put (_all_)(=);'; put 'run;'; put '%mp_abort(iftrue= (&tsuri=stopifempty)'; put ',mac=mm_getstpcode'; put ',msg=%str(&tree&name.(StoredProcess) not found!)'; put ')'; put '/**'; put '* Now we can extract the textstore'; put '*/'; put 'filename __getdoc temp lrecl=10000000;'; put 'proc metadata'; put 'in="$METAREPOSITORY'; put ''; put 'SAS1"'; put 'out=__getdoc ;'; put 'run;'; put '/* find the beginning of the text */'; put '%local start;'; put 'data _null_;'; put 'infile __getdoc lrecl=10000;'; put 'input;'; put 'start=index(_infile_,''StoredText="'');'; put 'if start then do;'; put 'call symputx("start",start+11);'; put '*putlog ''"'' _infile_ ''"'';'; put 'end;'; put 'stop;'; put '%local outeng;'; put '%if "&outloc"="0" %then %let outeng=TEMP;'; put '%else %let outeng="&outloc";'; put '%local fref;'; put '%if &outref=0 %then %let fref=%mf_getuniquefileref();'; put '%else %let fref=&outref;'; put '/* read the content, byte by byte, resolving escaped chars */'; put 'filename &fref &outeng lrecl=100000;'; put 'data _null_;'; put 'length filein 8 fileid 8;'; put 'filein = fopen("__getdoc","I",1,"B");'; put 'fileid = fopen("&fref","O",1,"B");'; put 'rec = "20"x;'; put 'length entity $6;'; put 'do while(fread(filein)=0);'; put 'x+1;'; put 'if x>&start then do;'; put 'rc = fget(filein,rec,1);'; put 'if rec=''"'' then leave;'; put 'else if rec="&" then do;'; put 'entity=rec;'; put 'do until (rec=";");'; put 'if fread(filein) ne 0 then goto getout;'; put 'rc = fget(filein,rec,1);'; put 'entity=cats(entity,rec);'; put 'end;'; put 'select (entity);'; put 'when (''&'' ) rec=''&'' ;'; put 'when (''<'' ) rec=''<'' ;'; put 'when (''>'' ) rec=''>'' ;'; put 'when (''''') rec="''" ;'; put 'when (''"'') rec=''"'' ;'; put 'when ('' '') rec=''0A''x;'; put 'when ('' '') rec=''0D''x;'; put 'when (''$'' ) rec=''$'' ;'; put 'when ('' '') rec=''09''x;'; put 'otherwise putlog "%str(WARN)ING: missing value for " entity=;'; put 'end;'; put 'rc =fput(fileid, substr(rec,1,1));'; put 'rc =fwrite(fileid);'; put 'end;'; put 'else do;'; put 'rc =fput(fileid,rec);'; put 'rc =fwrite(fileid);'; put 'end;'; put 'end;'; put 'end;'; put 'getout:'; put 'rc=fclose(filein);'; put 'rc=fclose(fileid);'; put 'run;'; put '%if &showlog=YES %then %do;'; put 'data _null_;'; put 'infile &fref lrecl=32767 end=last;'; put 'input;'; put 'if _n_=1 then putlog ''>>stpcodeBEGIN<<'';'; put 'putlog _infile_;'; put 'if last then putlog ''>>stpcodeEND<<'';'; put 'run;'; put '%end;'; put 'filename __getdoc clear;'; put '%if &outref=0 %then %do;'; put 'filename &fref clear;'; put '%end;'; put '%mend mm_getstpcode;'; put '%macro dc_getsettings();'; put '%global _program;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(syscc=&syscc on entry (&syswarningtext &syserrortext))'; put ')'; put '%if %length(&_PROGRAM)>1 %then %let root=&_program;'; put '%else %do;'; put '%global _metauser;'; put '%let _metauser=&sysuserid;'; put '/* to mimic a "real" _program we need to give a dummy role and stp name */'; put '%let root=/dummyRole/dummyName;'; put '%end;'; put '/* the DC precode is stored in the Admin folder in the root of'; put 'the project. Lets find that root. */'; put '%put &=root;'; put '%let root=%mf_getapploc();'; put '%put &=root;'; put '/* Now we know the root location we can retrieve the params */'; put '%let temploc=%sysfunc(pathname(work))/settings.txt;'; put '%mm_getstpcode(tree=&root/services/public'; put ',name=Data_Controller_Settings'; put ',outloc=&temploc'; put ')'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Unable to run getstpcode)'; put ')'; put 'filename _getsets "&temploc" lrecl=2000;'; put '/*'; put 'Do not use mp_include here - this puts a copy in every service, which creates'; put 'compilation problems when calling services from mp_include'; put '*/'; put '%inc _getsets/source2;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Problem running &sysmacroname (&syswarningtext &syserrortext))'; put ')'; put '%mend dc_getsettings;'; put '%macro mf_fmtdttm('; put ')/*/STORE SOURCE*/;'; put '%if "&sysver"="9.2" or "&sysver"="9.3"'; put 'or ("&sysver"="9.4" and "%substr(&SYSVLONG,9,1)" le "3")'; put 'or "%substr(&sysver,1,1)"="4"'; put 'or "%substr(&sysver,1,1)"="5"'; put '%then %do;DATETIME19.3%end;'; put '%else %do;E8601DT26.6%end;'; put '%mend mf_fmtdttm;'; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '%macro mp_init(prefix=SASJS'; put ')/*/STORE SOURCE*/;'; put '%if %symexist(SASJS_PREFIX) %then %return; /* only run once */'; put '%global'; put 'SASJS_PREFIX /* the ONLY hard-coded global macro variable in SASjs */'; put '&prefix._FUNCTIONS /* used in mcf_init() to track core function compilation */'; put '&prefix._INIT_NUM /* initialisation time as numeric */'; put '&prefix._INIT_DTTM /* initialisation time in E8601DT26.6 format */'; put '&prefix.WORK /* avoid typing %sysfunc(pathname(work)) every time */'; put ';'; put '%let sasjs_prefix=&prefix;'; put 'data _null_;'; put 'dttm=datetime();'; put 'call symputx("&sasjs_prefix._init_num",dttm,''g'');'; put 'call symputx("&sasjs_prefix._init_dttm",put(dttm,E8601DT26.6),''g'');'; put 'call symputx("&sasjs_prefix.work",pathname(''WORK''),''g'');'; put 'run;'; put 'options'; put 'compress=CHAR /* default is none so ensure we have something! */'; put 'datastmtchk=ALLKEYWORDS /* protection from overwriting input datasets */'; put 'errorcheck=STRICT /* catch errs in libname/filename statements */'; put 'fmterr /* ensure err when a format cannot be found */'; put 'mergenoby=%str(ERR)OR /* throw err when a merge has no BY variables */'; put 'missing=. /* changing this can cause hard to detect errs */'; put 'noquotelenmax /* avoid warnings for long strings */'; put 'noreplace /* avoid overwriting permanent datasets */'; put 'ps=max /* reduce log size slightly */'; put 'ls=max /* reduce log even more and avoid word truncation */'; put 'validmemname=COMPATIBLE /* avoid special characters etc in table names */'; put 'validvarname=V7 /* avoid special characters etc in variable names */'; put 'varinitchk=%str(ERR)OR /* avoid data mistakes from variable name typos */'; put 'varlenchk=%str(ERR)OR /* fail hard if truncation (data loss) can result */'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5" %then %do;'; put 'noautocorrect /* disallow misspelled procedure names */'; put 'dsoptions=note2err /* undocumented - convert bad NOTEs to ERRs */'; put '%end;'; put ';'; put '%mend mp_init;'; put '%macro mpeinit(fetch=YES);'; put '%global mpeinit'; put 'mpeadmins /* group with unrestricted Meditor access */'; put 'mpelocapprovals /* location for landing and staging files */'; put 'mpelib /* location of configuration tables for DC */'; put 'dc_repo_users /* location of user / group metadata */'; put 'dc_licence_key /* extracted in dc_getsettings */'; put 'dc_activation_key /* extracted in dc_getsettings */'; put 'dc_locale /* extracted in dc_getsettings */'; put 'dc_dttmtfmt /* can be overridden in dc_getsettings */'; put '_debug'; put ';'; put '%if &mpeinit=1 %then %return;'; put '%else %let mpeinit=1;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(Problem on service startup (&syswarningtext &syserrortext))'; put ')'; put '%mp_init()'; put '%if &fetch=YES %then %do;'; put '%webout(FETCH)'; put '%end;'; put '%global _CLIENTNAME;'; put '%mp_abort(iftrue= (&_CLIENTNAME=SAS Enterprise Guide)'; put ',mac=&_program..sas'; put ',msg=%str(Data Controller is a web app and should not be executed from EG)'; put ')'; put 'options urlencoding=utf8 nobomfile lrecl=32767;'; put '%let perf=%sysfunc(datetime());'; put '%put perfdiff: 0;'; put '%let dc_locale=SYSTEM; /* default if not set */'; put '/**'; put '* E8601DT26.6 has widest database support - but not all SAS flavours can'; put '* handle it. Override in the settings STP if needed.'; put '*/'; put 'data _null_;'; put 'dc_dttmtfmt=''"%sysfunc(datetime(),''!!"%mf_fmtdttm()"!!'')"dt'';'; put 'call symputx(''dc_dttmtfmt'',dc_dttmtfmt);'; put 'put dc_dttmtfmt=;'; put 'run;'; put '%put &=dc_dttmtfmt;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc prior to dc_getsettings)'; put ')'; put '%dc_getsettings()'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc after dc_getsettings)'; put ')'; put 'data _null_;'; put 'set &DC_LIBREF..mpe_config(where=('; put 'var_scope="DC"'; put 'and &dc_dttmtfmt lt tx_to'; put 'and var_active=1'; put '));'; put 'call symputx(var_name,var_value,''G'');'; put 'putlog var_name "=" var_value;'; put 'run;'; put '%let mpelib=&dc_libref;'; put '%let mpeadmins=&dc_admin_group;'; put '%let mpelocapprovals=&dc_staging_area;'; put '%let dc_repo_users=&dc_repo_users;'; put '%if &dc_locale ne SYSTEM %then %do;'; put 'options locale=&dc_locale;'; put '%end;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(Problem during compilation or with STP precode (&syswarningtext))'; put ')'; put '%mend mpeinit;'; put '%macro mf_mval(var);'; put '%if %symexist(&var) %then %do;'; put '%superq(&var)'; put '%end;'; put '%mend mf_mval;'; put '%macro mf_trimstr(basestr,trimstr);'; put '%local baselen trimlen trimval;'; put '/* return if basestr is shorter than trimstr (or 0) */'; put '%let baselen=%length(%superq(basestr));'; put '%let trimlen=%length(%superq(trimstr));'; put '%if &baselen < &trimlen or &baselen=0 %then %return;'; put '/* obtain the characters from the end of basestr */'; put '%let trimval=%qsubstr(%superq(basestr)'; put ',%length(%superq(basestr))-&trimlen+1'; put ',&trimlen);'; put '/* compare and if matching, chop it off! */'; put '%if %superq(basestr)=%superq(trimstr) %then %do;'; put '%return;'; put '%end;'; put '%else %if %superq(trimval)=%superq(trimstr) %then %do;'; put '%qsubstr(%superq(basestr),1,%length(%superq(basestr))-&trimlen)'; put '%end;'; put '%else %do;'; put '&basestr'; put '%end;'; put '%mend mf_trimstr;'; put '%macro mf_getplatform(switch'; put ')/*/STORE SOURCE*/;'; put '%local a b c;'; put '%if &switch.NONE=NONE %then %do;'; put '%if %symexist(sasjsprocessmode) %then %do;'; put '%if &sasjsprocessmode=Stored Program %then %do;'; put 'SASJS'; put '%return;'; put '%end;'; put '%end;'; put '%if %symexist(sysprocessmode) %then %do;'; put '%if "&sysprocessmode"="SAS Object Server"'; put 'or "&sysprocessmode"= "SAS Compute Server" %then %do;'; put 'SASVIYA'; put '%end;'; put '%else %if "&sysprocessmode"="SAS Stored Process Server"'; put 'or "&sysprocessmode"="SAS Workspace Server"'; put '%then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if %symexist(_metaport) or %symexist(_metauser) %then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if &switch=SASSTUDIO %then %do;'; put '/* return the version of SAS Studio else 0 */'; put '%if %mf_mval(_CLIENTAPP)=%str(SAS Studio) %then %do;'; put '%let a=%mf_mval(_CLIENTVERSION);'; put '%let b=%scan(&a,1,.);'; put '%if %eval(&b >2) %then %do;'; put '&b'; put '%end;'; put '%else 0;'; put '%end;'; put '%else 0;'; put '%end;'; put '%else %if &switch=VIYARESTAPI %then %do;'; put '%mf_trimstr(%sysfunc(getoption(servicesbaseurl)),/)'; put '%end;'; put '%mend mf_getplatform;'; put '%macro mpeterm();'; put '%local oldloc;'; put 'data _null_;'; put 'if symexist(''SYSPRINTTOLOG'') then oldloc=symget(''SYSPRINTTOLOG'');'; put 'else oldloc=getoption(''LOG'');'; put 'if subpad(oldloc,1,1) not in (''"'',"''",'' '') then oldloc=quote(cats(oldloc));'; put 'call symputx(''oldloc'',oldloc,''l'');'; put 'run;'; put '%if %length(&oldloc)>0 %then %do;'; put 'proc printto log=log;'; put 'run;'; put 'data _null_;'; put 'infile &oldloc;'; put 'input; putlog _infile_;'; put 'run;'; put '%end;'; put '%if %sysfunc(exist(&dc_libref..mpe_requests)) and %mf_getplatform() ne SASVIYA'; put '%then %do;'; put 'data ;'; put 'if 0 then set &dc_libref..mpe_requests;'; put 'request_dttm=%sysfunc(datetime());'; put 'request_user="%mf_getuser()";'; put 'request_service="%scan(&_program,-2,/)/%scan(&_program,-1,/)";'; put 'request_params='''';'; put 'output;stop;'; put 'proc append base=&dc_libref..mpe_requests data=&syslast force nowarn;'; put 'run;'; put '%end;'; put '%mend mpeterm;'; put '%macro mm_getroles('; put 'outds=work.mm_getroles'; put ')/*/STORE SOURCE*/;'; put 'filename response temp;'; put 'options noquotelenmax;'; put 'proc metadata in= ''$METAREPOSITORY'; put 'IdentityGroupSAS388'; put ''; put ''; put ''; put ''; put ''''; put 'out=response;'; put 'run;'; put 'filename sxlemap temp;'; put 'data _null_;'; put 'file sxlemap;'; put 'put '''';'; put 'put "/GetMetadataObjects/Objects/IdentityGroup";'; put 'put "";'; put 'put '''';'; put 'put "/GetMetadataObjects/Objects/IdentityGroup/@Id";'; put 'put "";'; put 'put "characterstring32";'; put 'put '''';'; put 'put "/GetMetadataObjects/Objects/IdentityGroup/@Name";'; put 'put "";'; put 'put "characterstring256";'; put 'put '''';'; put 'put "/GetMetadataObjects/Objects/IdentityGroup/@Desc";'; put 'put "";'; put 'put "characterstring500";'; put 'put ''
'';'; put 'run;'; put 'libname _XML_ xml xmlfileref=response xmlmap=sxlemap;'; put 'proc sort data= _XML_.roles out=&outds;'; put 'by rolename;'; put 'run;'; put 'filename sxlemap clear;'; put 'filename response clear;'; put 'libname _XML_ clear;'; put '%mend mm_getroles;'; put '%macro mm_getrepos('; put 'outds=work.mm_getrepos'; put ')/*/STORE SOURCE*/;'; put '* use a temporary fileref to hold the response;'; put 'filename response temp;'; put '/* get list of libraries */'; put 'proc metadata in='; put '"1"'; put 'out=response;'; put 'run;'; put '/* write the response to the log for debugging */'; put '/*'; put 'data _null_;'; put 'infile response lrecl=1048576;'; put 'input;'; put 'put _infile_;'; put 'run;'; put '*/'; put '/* create an XML map to read the response */'; put 'filename sxlemap temp;'; put 'data _null_;'; put 'file sxlemap;'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository";'; put 'put "";'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Id";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Name";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Desc";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@DefaultNS";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@RepositoryType";'; put 'put "characterstring20";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@RepositoryFormat";'; put 'put "characterstring10";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@Access";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@CurrentAccess";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@PauseState";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Path";'; put 'put "";'; put 'put "characterstring256";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Engine";'; put 'put "";'; put 'put "characterstring8";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Options";'; put 'put "";'; put 'put "characterstring32";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@MetadataCreated";'; put 'put "characterstring24";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@MetadataUpdated";'; put 'put "characterstring24";'; put 'put '''';'; put 'put ''
'';'; put 'run;'; put 'libname _XML_ xml xmlfileref=response xmlmap=sxlemap;'; put 'proc sort data= _XML_.SASRepos out=&outds;'; put 'by name;'; put 'run;'; put '/* clear references */'; put 'filename sxlemap clear;'; put 'filename response clear;'; put 'libname _XML_ clear;'; put '%mend mm_getrepos;'; put '%macro dc_getroles(outds=mm_getroles);'; put '/* take current repository */'; put '%local repo repocnt x;'; put '%let repo=%sysfunc(getoption(metarepository));'; put '/* get list of repositories and filter */'; put '%mm_getrepos(outds=repos)'; put '%let repocnt=0;'; put 'data repos;'; put 'set repos;'; put 'where repositorytype in(''CUSTOM'',''FOUNDATION'')'; put '& upcase(name) ne "%upcase(&repo)";'; put 'keep id name ;'; put 'call symputx(cats(''repo'',_n_),name,''l'');'; put 'call symputx(''repocnt'',_n_,''l'');'; put 'run;'; put '%put _local_;'; put '%mm_getroles(outds=&outds)'; put '%do x=1 %to &repocnt;'; put 'options metarepository=&&repo&x;'; put '%mm_getroles(outds=&outds.a)'; put 'proc append base=&outds data=&outds.a;'; put 'run;'; put '%end;'; put 'options metarepository=&repo;'; put '%mend dc_getroles;'; put '* SAS Macros end;'; put '* SAS Includes start;'; put '* SAS Includes end;'; put '* Binary Files start;'; put '* Binary Files end;'; put '* ServiceInit start;'; put 'options noquotelenmax ps=max;'; put '/* create dummy macros to prevent stpbegin / stpend from corrupting sessions */'; put '%macro stpbegin();'; put '%put NOTE: the STPBEGIN macro should not be used for web apps!;'; put '%mend stpbegin;'; put '%macro stpend();'; put '%put NOTE: the STPEND macro should not be used for web apps!;'; put '%mend stpend;'; put '* ServiceInit end;'; put '* Service start;'; put '/**'; put '@file userroles.sas'; put '@brief List all SAS Roles'; put '@details Gets a list of all SAS Roles'; put '

SAS Macros

'; put '@li dc_getroles.sas'; put '@li mpeinit.sas'; put '@author 4GL Apps Ltd'; put '@copyright 4GL Apps Ltd. This code may only be used within Data Controller'; put 'and may not be re-distributed or re-sold without the express permission of'; put '4GL Apps Ltd.'; put '**/'; put '%mpeinit()'; put '%dc_getroles(outds=roles)'; put '%webout(OPEN)'; put '%webout(OBJ,roles)'; put '%webout(CLOSE)'; put '* Service end;'; run; %mm_createwebservice(path=&appLoc/&path, name=&service, code=sascode, server=&serverName, replace=yes) filename sascode clear; %let path=services/validations; %let service=columns_in_libds; filename sascode temp lrecl=32767; data _null_; file sascode; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '/**'; put '@file mp_jsonout.sas'; put '@brief Writes JSON in SASjs format to a fileref'; put '@details This macro can be used to OPEN a JSON stream and send one or more'; put 'tables as arrays of rows, where each row can be an object or a nested array.'; put 'There are two engines available - DATASTEP or PROCJSON.'; put 'PROC JSON is fast but will produce errs like the ones below if'; put 'special chars are encountered.'; put '> (ERR)OR: Some code points did not transcode.'; put '> An object or array close is not valid at this point in the JSON text.'; put '> Date value out of range'; put 'If this happens, try running with ENGINE=DATASTEP.'; put 'The DATASTEP engine is used to handle special SAS missing numerics, and'; put 'can also convert entire datasets to formatted values. Output JSON is always'; put 'in UTF-8.'; put 'Usage:'; put 'filename tmp temp;'; put 'data class; set sashelp.class;run;'; put '%mp_jsonout(OPEN,jref=tmp)'; put '%mp_jsonout(OBJ,class,jref=tmp)'; put '%mp_jsonout(OBJ,class,dslabel=class2,jref=tmp,showmeta=Y)'; put '%mp_jsonout(CLOSE,jref=tmp)'; put 'data _null_;'; put 'infile tmp;'; put 'input;putlog _infile_;'; put 'run;'; put 'If you are building web apps with SAS then you are strongly encouraged to use'; put 'the mX_createwebservice macros in combination with the'; put '[sasjs adapter](https://github.com/sasjs/adapter).'; put 'For more information see https://sasjs.io'; put '@param [in] action Valid values:'; put '@li OPEN - opens the JSON'; put '@li OBJ - sends a table with each row as an object'; put '@li ARR - sends a table with each row in an array'; put '@li CLOSE - closes the JSON'; put '@param [in] ds The dataset to send. Must be a work table.'; put '@param [out] jref= (_webout) The fileref to which to send the JSON'; put '@param [out] dslabel= The name to give the table in the exported JSON'; put '@param [in] fmt= (Y) Whether to keep (Y) or strip (N) formats from the table'; put '@param [in] engine= (DATASTEP) Which engine to use to send the JSON. Options:'; put '@li PROCJSON (default)'; put '@li DATASTEP (more reliable when data has non standard characters)'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to JSON'; put '

Related Files

'; put '@li mp_ds2fmtds.sas'; put '@version 9.2'; put '@author Allan Bowe'; put '@source https://github.com/sasjs/core'; put '**/'; put '%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y'; put ',engine=DATASTEP'; put ',missing=NULL'; put ',showmeta=N'; put ',maxobs=MAX'; put ')/*/STORE SOURCE*/;'; put '%local tempds colinfo fmtds i numcols numobs stmt_obs lastobs optval'; put 'tmpds1 tmpds2 tmpds3 tmpds4;'; put '%let numcols=0;'; put '%if &maxobs ne MAX %then %let stmt_obs=%str(if _n_>&maxobs then stop;);'; put '%if &action=OPEN %then %do;'; put 'options nobomfile;'; put 'data _null_;file &jref encoding=''utf-8'' lrecl=200;'; put 'put ''{"PROCESSED_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''"'';'; put 'run;'; put '%end;'; put '%else %if (&action=ARR or &action=OBJ) %then %do;'; put '/* force variable names to always be uppercase in the JSON */'; put 'options validvarname=upcase;'; put '/* To avoid issues with _webout on EBI - such as encoding diffs and truncation'; put '(https://support.sas.com/kb/49/325.html) we use temporary files */'; put 'filename _sjs1 temp lrecl=200 ;'; put 'data _null_; file _sjs1 encoding=''utf-8'';'; put 'put ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":";'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjs1 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs1 clear;'; put '/* grab col defs */'; put 'proc contents noprint data=&ds'; put 'out=_data_(keep=name type length format formatl formatd varnum label);'; put 'run;'; put '%let colinfo=%scan(&syslast,2,.);'; put 'proc sort data=&colinfo;'; put 'by varnum;'; put 'run;'; put '/* move meta to mac vars */'; put 'data &colinfo;'; put 'if _n_=1 then call symputx(''numcols'',nobs,''l'');'; put 'set &colinfo end=last nobs=nobs;'; put 'name=upcase(name);'; put '/* fix formats */'; put 'if type=2 or type=6 then do;'; put 'typelong=''char'';'; put 'length fmt $49.;'; put 'if format='''' then fmt=cats(''$'',length,''.'');'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else fmt=cats(format,formatl,''.'');'; put 'end;'; put 'else do;'; put 'typelong=''num'';'; put 'if format='''' then fmt=''best.'';'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else if formatd=0 then fmt=cats(format,formatl,''.'');'; put 'else fmt=cats(format,formatl,''.'',formatd);'; put 'end;'; put '/* 32 char unique name */'; put 'newname=''sasjs''!!substr(cats(put(md5(name),$hex32.)),1,27);'; put 'call symputx(cats(''name'',_n_),name,''l'');'; put 'call symputx(cats(''newname'',_n_),newname,''l'');'; put 'call symputx(cats(''length'',_n_),length,''l'');'; put 'call symputx(cats(''fmt'',_n_),fmt,''l'');'; put 'call symputx(cats(''type'',_n_),type,''l'');'; put 'call symputx(cats(''typelong'',_n_),typelong,''l'');'; put 'call symputx(cats(''label'',_n_),coalescec(label,name),''l'');'; put '/* overwritten when fmt=Y and a custom format exists in catalog */'; put 'if typelong=''num'' then call symputx(cats(''fmtlen'',_n_),200,''l'');'; put 'else call symputx(cats(''fmtlen'',_n_),min(32767,ceil((length+10)*1.5)),''l'');'; put 'run;'; put '%let tempds=%substr(_%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql;'; put 'select count(*) into: lastobs from &ds;'; put '%if &maxobs ne MAX %then %let lastobs=%sysfunc(min(&lastobs,&maxobs));'; put '%if &engine=PROCJSON %then %do;'; put '%if &missing=STRING %then %do;'; put '%put &sysmacroname: Special Missings not supported in proc json.;'; put '%put &sysmacroname: Switching to DATASTEP engine;'; put '%goto datastep;'; put '%end;'; put 'data &tempds;'; put 'set &ds;'; put '&stmt_obs;'; put '%if &fmt=N %then format _numeric_ best32.;;'; put '/* PRETTY is necessary to avoid line truncation in large files */'; put 'filename _sjs2 temp lrecl=131068 encoding=''utf-8'';'; put 'proc json out=_sjs2 pretty'; put '%if &action=ARR %then nokeys ;'; put ';export &tempds / nosastags fmtnumeric;'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs2 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs2 clear;'; put '%end;'; put '%else %if &engine=DATASTEP %then %do;'; put '%datastep:'; put '%if %sysfunc(exist(&ds)) ne 1 & %sysfunc(exist(&ds,VIEW)) ne 1'; put '%then %do;'; put '%put &sysmacroname: &ds NOT FOUND!!!;'; put '%return;'; put '%end;'; put '%if &fmt=Y %then %do;'; put '/**'; put '* Extract format definitions'; put '* First, by getting library locations from dictionary.formats'; put '* Then, by exporting the width using proc format'; put '* Cannot use maxw from sashelp.vformat as not always populated'; put '* Cannot use fmtinfo() as not supported in all flavours'; put '*/'; put '%let tmpds1=%substr(fmtsum%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds2=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds3=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds4=%substr(col%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql noprint;'; put 'create table &tmpds1 as'; put 'select cats(libname,''.'',memname) as FMTCAT,'; put 'FMTNAME'; put 'from dictionary.formats'; put 'where fmttype=''F'' and libname is not null'; put 'and fmtname in (select format from &colinfo where format is not null)'; put 'order by 1;'; put 'create table &tmpds2('; put 'FMTNAME char(32),'; put 'LENGTH num'; put ');'; put '%local catlist cat fmtlist i;'; put 'select distinct fmtcat into: catlist separated by '' '' from &tmpds1;'; put '%do i=1 %to %sysfunc(countw(&catlist,%str( )));'; put '%let cat=%scan(&catlist,&i,%str( ));'; put 'proc sql;'; put 'select distinct fmtname into: fmtlist separated by '' '''; put 'from &tmpds1 where fmtcat="&cat";'; put 'proc format lib=&cat cntlout=&tmpds3(keep=fmtname length);'; put 'select &fmtlist;'; put 'run;'; put 'proc sql;'; put 'insert into &tmpds2 select distinct fmtname,length from &tmpds3;'; put '%end;'; put 'proc sql;'; put 'create table &tmpds4 as'; put 'select a.*, b.length as MAXW'; put 'from &colinfo a'; put 'left join &tmpds2 b'; put 'on cats(a.format)=cats(upcase(b.fmtname))'; put 'order by a.varnum;'; put 'data _null_;'; put 'set &tmpds4;'; put 'if not missing(maxw);'; put 'call symputx('; put 'cats(''fmtlen'',_n_),'; put '/* vars need extra padding due to JSON escaping of special chars */'; put 'min(32767,ceil((max(length,maxw)+10)*1.5))'; put ',''l'''; put ');'; put 'run;'; put '/* configure varlenchk - as we are explicitly shortening the variables */'; put '%let optval=%sysfunc(getoption(varlenchk));'; put 'options varlenchk=NOWARN;'; put 'data _data_(compress=char);'; put '/* shorten the new vars */'; put 'length'; put '%do i=1 %to &numcols;'; put '&&name&i $&&fmtlen&i'; put '%end;'; put ';'; put '/* rename on entry */'; put 'set &ds(rename=('; put '%do i=1 %to &numcols;'; put '&&name&i=&&newname&i'; put '%end;'; put '));'; put '&stmt_obs;'; put 'drop'; put '%do i=1 %to &numcols;'; put '&&newname&i'; put '%end;'; put ';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=num %then %do;'; put '&&name&i=cats(put(&&newname&i,&&fmt&i));'; put '%end;'; put '%else %do;'; put '&&name&i=put(&&newname&i,&&fmt&i);'; put '%end;'; put '%end;'; put 'if _error_ then do;'; put 'call symputx(''syscc'',1012);'; put 'stop;'; put 'end;'; put 'run;'; put '%let fmtds=&syslast;'; put 'options varlenchk=&optval;'; put '%end;'; put 'proc format; /* credit yabwon for special null removal */'; put 'value bart (default=40)'; put '%if &missing=NULL %then %do;'; put '._ - .z = null'; put '%end;'; put '%else %do;'; put '._ = [quote()]'; put '. = null'; put '.a - .z = [quote()]'; put '%end;'; put 'other = [best.];'; put 'data &tempds;'; put 'attrib _all_ label='''';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'length &&name&i $&&fmtlen&i...;'; put 'format &&name&i $&&fmtlen&i...;'; put '%end;'; put '%end;'; put '%if &fmt=Y %then %do;'; put 'set &fmtds;'; put '%end;'; put '%else %do;'; put 'set &ds;'; put '%end;'; put '&stmt_obs;'; put 'format _numeric_ bart.;'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'if findc(&&name&i,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put '&&name&i=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,&&name&i)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else &&name&i=quote(cats(&&name&i));'; put '%end;'; put '%end;'; put 'run;'; put 'filename _sjs3 temp lrecl=131068 ;'; put 'data _null_;'; put 'file _sjs3 encoding=''utf-8'';'; put 'if _n_=1 then put "[";'; put 'set &tempds;'; put 'if _n_>1 then put "," @; put'; put '%if &action=ARR %then "[" ; %else "{" ;'; put '%do i=1 %to &numcols;'; put '%if &i>1 %then "," ;'; put '%if &action=OBJ %then """&&name&i"":" ;'; put '"&&name&i"n /* name literal for reserved variable names */'; put '%end;'; put '%if &action=ARR %then "]" ; %else "}" ; ;'; put '/* close out the table */'; put 'data _null_;'; put 'file _sjs3 mod encoding=''utf-8'';'; put 'put '']'';'; put 'run;'; put 'data _null_;'; put 'infile _sjs3 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs3 clear;'; put '%end;'; put 'proc sql;'; put 'drop table &colinfo, &tempds;'; put '%if %substr(&showmeta,1,1)=Y %then %do;'; put 'filename _sjs4 temp lrecl=131068 encoding=''utf-8'';'; put 'data _null_;'; put 'file _sjs4;'; put 'length label $350;'; put 'put ", ""$%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":{""vars"":{";'; put 'do i=1 to &numcols;'; put 'name=quote(trim(symget(cats(''name'',i))));'; put 'format=quote(trim(symget(cats(''fmt'',i))));'; put 'label=quote(prxchange(''s/\\/\\\\/'',-1,trim(symget(cats(''label'',i)))));'; put 'length=quote(trim(symget(cats(''length'',i))));'; put 'type=quote(trim(symget(cats(''typelong'',i))));'; put 'if i>1 then put "," @@;'; put 'put name '':{"format":'' format '',"label":'' label'; put ''',"length":'' length '',"type":'' type ''}'';'; put 'end;'; put 'put ''}}'';'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs4 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs4 clear;'; put '%end;'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put 'data _null_; file &jref encoding=''utf-8'' mod ;'; put 'put "}";'; put 'run;'; put '%end;'; put '%mend mp_jsonout;'; put '/**'; put '@file mm_webout.sas'; put '@brief Send data to/from SAS Stored Processes'; put '@details This macro should be added to the start of each Stored Process,'; put '**immediately** followed by a call to:'; put '%mm_webout(FETCH)'; put 'This will read all the input data and create same-named SAS datasets in the'; put 'WORK library. You can then insert your code, and send data back using the'; put 'following syntax:'; put 'data some datasets; * make some data ;'; put 'retain some columns;'; put 'run;'; put '%mm_webout(OPEN)'; put '%mm_webout(ARR,some) * Array format, fast, suitable for large tables ;'; put '%mm_webout(OBJ,datasets) * Object format, easier to work with ;'; put 'Finally, wrap everything up send some helpful system variables too'; put '%mm_webout(CLOSE)'; put '@param [in] action Either FETCH, OPEN, ARR, OBJ or CLOSE'; put '@param [in] ds The dataset to send back to the frontend'; put '@param [out] dslabel= Value to use instead of table name for sending to JSON'; put '@param [in] fmt= (N) Setting Y converts all vars to their formatted values'; put '@param [out] fref= (_webout) The fileref to which to write the JSON'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] workobs= (0) When set to a positive integer, will create a new'; put 'output object (WORK) which contains this number of observations from all'; put 'tables in the WORK library.'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to output JSON'; put '

SAS Macros

'; put '@li mp_jsonout.sas'; put '

Related Macros

'; put '@li ms_webout.sas'; put '@li mv_webout.sas'; put '@version 9.3'; put '@author Allan Bowe'; put '**/'; put '%macro mm_webout(action,ds,dslabel=,fref=_webout,fmt=N,missing=NULL'; put ',showmeta=N,maxobs=MAX,workobs=0'; put ');'; put '%global _webin_file_count _webin_fileref1 _webin_name1 _program _debug'; put 'sasjs_tables;'; put '%local i tempds jsonengine;'; put '/* see https://github.com/sasjs/core/issues/41 */'; put '%if "%upcase(&SYSENCODING)" ne "UTF-8" %then %let jsonengine=PROCJSON;'; put '%else %let jsonengine=DATASTEP;'; put '%if &action=FETCH %then %do;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'options mprint notes mprintnest;'; put '%end;'; put '%let _webin_file_count=%eval(&_webin_file_count+0);'; put '/* now read in the data */'; put '%do i=1 %to &_webin_file_count;'; put '%if &_webin_file_count=1 %then %do;'; put '%let _webin_fileref1=&_webin_fileref;'; put '%let _webin_name1=&_webin_name;'; put '%end;'; put 'data _null_;'; put 'infile &&_webin_fileref&i termstr=crlf;'; put 'input;'; put 'call symputx(''input_statement'',_infile_);'; put 'putlog "&&_webin_name&i input statement: " _infile_;'; put 'stop;'; put 'data &&_webin_name&i;'; put 'infile &&_webin_fileref&i firstobs=2 dsd termstr=crlf encoding=''utf-8'';'; put 'input &input_statement;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'if _n_<20 then putlog _infile_;'; put '%end;'; put 'run;'; put '%let sasjs_tables=&sasjs_tables &&_webin_name&i;'; put '%end;'; put '%end;'; put '%else %if &action=OPEN %then %do;'; put '/* fix encoding */'; put 'OPTIONS NOBOMFILE;'; put '/**'; put '* check xengine type to avoid the below err message:'; put '* > Function is only valid for filerefs using the CACHE access method.'; put '*/'; put 'data _null_;'; put 'set sashelp.vextfl(where=(fileref="_WEBOUT"));'; put 'if xengine=''STREAM'' then do;'; put 'rc=stpsrv_header(''Content-type'',"text/html; encoding=utf-8");'; put 'end;'; put 'run;'; put '/* setup json */'; put 'data _null_;file &fref encoding=''utf-8'';'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutBEGIN<<'';'; put '%end;'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'run;'; put '%end;'; put '%else %if &action=ARR or &action=OBJ %then %do;'; put '%mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt,jref=&fref'; put ',engine=&jsonengine,missing=&missing,showmeta=&showmeta,maxobs=&maxobs'; put ')'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put '/* To avoid issues with _webout on EBI we use a temporary file */'; put 'filename _sjsref temp lrecl=131068;'; put '%if %str(&workobs) > 0 %then %do;'; put '/* if debug mode, send back first XX records of each work table also */'; put 'data;run;%let tempds=%scan(&syslast,2,.);'; put 'ods output Members=&tempds;'; put 'proc datasets library=WORK memtype=data;'; put '%local wtcnt;%let wtcnt=0;'; put 'data _null_;'; put 'set &tempds;'; put 'if not (upcase(name) =:"DATA"); /* ignore temp datasets */'; put 'i+1;'; put 'call symputx(cats(''wt'',i),name,''l'');'; put 'call symputx(''wtcnt'',i,''l'');'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put ",""WORK"":{";'; put '%do i=1 %to &wtcnt;'; put '%let wt=&&wt&i;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'dsid=open("WORK.&wt",''is'');'; put 'nlobs=attrn(dsid,''NLOBS'');'; put 'nvars=attrn(dsid,''NVARS'');'; put 'rc=close(dsid);'; put 'if &i>1 then put '',''@;'; put 'put " ""&wt"" : {";'; put 'put ''"nlobs":'' nlobs;'; put 'put '',"nvars":'' nvars;'; put '%mp_jsonout(OBJ,&wt,jref=_sjsref,dslabel=first10rows,showmeta=Y'; put ',maxobs=&workobs'; put ')'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put '%end;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put 'run;'; put '%end;'; put '/* close off json */'; put 'data _null_;file _sjsref mod encoding=''utf-8'';'; put 'length SYSPROCESSNAME syserrortext syswarningtext autoexec $512;'; put 'put ",""_DEBUG"" : ""&_debug"" ";'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'autoexec=quote(urlencode(trim(getoption(''autoexec''))));'; put 'put '',"AUTOEXEC" : '' autoexec;'; put 'put ",""MF_GETUSER"" : ""%mf_getuser()"" ";'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'put ",""SYSENCODING"" : ""&sysencoding"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSPROCESSID"" : ""&SYSPROCESSID"" ";'; put 'put ",""SYSPROCESSMODE"" : ""&SYSPROCESSMODE"" ";'; put 'SYSPROCESSNAME=quote(urlencode(cats(SYSPROCESSNAME)));'; put 'put ",""SYSPROCESSNAME"" : " SYSPROCESSNAME;'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put '',"SYSWARNINGTEXT" : '' syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'length memsize $32;'; put 'memsize="%sysfunc(INPUTN(%sysfunc(getoption(memsize)), best.),sizekmg.)";'; put 'memsize=quote(cats(memsize));'; put 'put '',"MEMSIZE" : '' memsize;'; put 'put "}" @;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutEND<<'';'; put '%end;'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjsref lrecl=1 recfm=n;'; put 'file &fref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjsref clear;'; put '%end;'; put '%mend mm_webout;'; put '%macro webout(action,ds,dslabel=,fmt=,missing=NULL,showmeta=NO,maxobs=MAX);'; put '%mm_webout(&action,ds=&ds,dslabel=&dslabel,fmt=&fmt'; put ',missing=&missing'; put ',showmeta=&showmeta'; put ',maxobs=&maxobs'; put ') %mend;'; put '/* provide additional debug info */'; put '%global _program;'; put '%put &=syscc;'; put '%put user=%mf_getuser();'; put '%put pgm=&_program;'; put '%put timestamp=%sysfunc(datetime(),datetime19.);'; put '* Service Variables start;'; put '* Service Variables end;'; put '* SAS Macros start;'; put '%macro mf_getapploc(pgm);'; put '%if "&pgm"="" %then %do;'; put '%if %symexist(_program) %then %let pgm=&_program;'; put '%else %do;'; put '%put &sysmacroname: No value provided and no _program variable available;'; put '%return;'; put '%end;'; put '%end;'; put '%local root;'; put '/**'; put '* First check we are not in the tests/macros folder (which has no subfolders)'; put '* or specifically in the testsetup or testteardown services'; put '*/'; put '%if %index(&pgm,/tests/macros/)'; put 'or %index(&pgm,/tests/testsetup)'; put 'or %index(&pgm,/tests/testteardown)'; put '%then %do;'; put '%let root=%substr(&pgm,1,%index(&pgm,/tests)-1);'; put '&root'; put '%return;'; put '%end;'; put '/**'; put '* Next, move up two levels to avoid matches on subfolder or service name'; put '*/'; put '%let root=%substr(&pgm,1,%length(&pgm)-%length(%scan(&pgm,-1,/))-1);'; put '%let root=%substr(&root,1,%length(&root)-%length(%scan(&root,-1,/))-1);'; put '%if %index(&root,/tests/) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/tests/)-1);'; put '%end;'; put '%else %if %index(&root,/services) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/services)-1);'; put '%end;'; put '%else %if %index(&root,/jobs) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/jobs)-1);'; put '%end;'; put '%else %put &sysmacroname: Could not find an app location from &pgm;'; put '&root'; put '%mend mf_getapploc ;'; put '%macro mf_getuniquefileref(prefix=_,maxtries=1000,lrecl=32767);'; put '%local rc fname;'; put '%if &prefix=0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%end;'; put '%else %do;'; put '%local x len;'; put '%let len=%eval(8-%length(&prefix));'; put '%let x=0;'; put '%do x=0 %to &maxtries;'; put '%let fname=&prefix%substr(%sysfunc(ranuni(0)),3,&len);'; put '%if %sysfunc(fileref(&fname)) > 0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%return;'; put '%end;'; put '%end;'; put '%put unable to find available fileref after &maxtries attempts;'; put '%end;'; put '%mend mf_getuniquefileref;'; put '%macro mp_abort(mac=mp_abort.sas, type=, msg=, iftrue=%str(1=1)'; put ', errds=work.mp_abort_errds'; put ', mode=REGULAR'; put ')/*/STORE SOURCE*/;'; put '%global sysprocessmode sysprocessname sasjs_stpsrv_header_loc sasjsprocessmode;'; put '%local fref fid i;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%put NOTE: /// mp_abort macro executing //;'; put '%if %length(&mac)>0 %then %put NOTE- called by &mac;'; put '%put NOTE - &msg;'; put '%if %symexist(_SYSINCLUDEFILEDEVICE)'; put '/* abort cancel FILE does not restart outside the INCLUDE on Viya 3.5 */'; put 'and %superq(SYSPROCESSNAME) ne %str(Compute Server)'; put '%then %do;'; put '%if "*&_SYSINCLUDEFILEDEVICE*" ne "**" %then %do;'; put 'data &errds;'; put 'iftrue=''1=1'';'; put 'length mac $100 msg $5000;'; put 'mac=symget(''mac'');'; put 'msg=symget(''msg'');'; put 'run;'; put 'data _null_;'; put 'abort cancel FILE;'; put 'run;'; put '%return;'; put '%end;'; put '%end;'; put '/* Web App Context */'; put '%if %symexist(_PROGRAM)'; put 'or %superq(SYSPROCESSNAME) = %str(Compute Server)'; put 'or &mode=INCLUDE'; put '%then %do;'; put 'options obs=max replace mprint;'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5"'; put '%then %do;'; put 'options nosyntaxcheck;'; put '%end;'; put '%if &mode=INCLUDE %then %do;'; put '%if %sysfunc(exist(&errds))=1 %then %do;'; put 'data _null_;'; put 'set &errds;'; put 'call symputx(''iftrue'',iftrue,''l'');'; put 'call symputx(''mac'',mac,''l'');'; put 'call symputx(''msg'',msg,''l'');'; put 'putlog (_all_)(=);'; put 'run;'; put '%if (&iftrue)=0 %then %return;'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: No include errors found;'; put '%return;'; put '%end;'; put '%end;'; put '/* extract log errs / warns, if exist */'; put '%local logloc logline;'; put '%global logmsg; /* capture global messages */'; put '%if %symexist(SYSPRINTTOLOG) %then %let logloc=&SYSPRINTTOLOG;'; put '%else %let logloc=%qsysfunc(getoption(LOG));'; put 'proc printto log=log;run;'; put '%let logline=0;'; put '%if %length(&logloc)>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input; putlog _infile_;'; put 'i=1;'; put 'retain logonce 0;'; put 'if ('; put '_infile_=:"%str(WARN)ING" or _infile_=:"%str(ERR)OR"'; put ') and logonce=0 then'; put 'do;'; put 'call symputx(''logline'',_n_);'; put 'logonce+1;'; put 'end;'; put 'run;'; put '/* capture log including lines BEFORE the err */'; put '%if &logline>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input;'; put 'i=1;'; put 'stoploop=0;'; put 'if _n_ ge &logline-15 and stoploop=0 then do until (i>22);'; put 'call symputx(''logmsg'',catx(''\n'',symget(''logmsg''),_infile_));'; put 'input;'; put 'i+1;'; put 'stoploop=1;'; put 'end;'; put 'if stoploop=1 then stop;'; put 'run;'; put '%end;'; put '%end;'; put '%if %symexist(SYS_JES_JOB_URI) %then %do;'; put '/* setup webout for Viya */'; put 'options nobomfile;'; put '%if "X&SYS_JES_JOB_URI.X"="XX" %then %do;'; put 'filename _webout temp lrecl=999999 mod;'; put '%end;'; put '%else %do;'; put 'filename _webout filesrvc parenturi="&SYS_JES_JOB_URI"'; put 'name="_webout.json" lrecl=999999 mod;'; put '%end;'; put '%end;'; put '%else %if %sysfunc(filename(fref,&sasjs_stpsrv_header_loc))=0 %then %do;'; put 'options nobomfile;'; put '/* set up http header for SASjs Server */'; put '%let fid=%sysfunc(fopen(&fref,A));'; put '%if &fid=0 %then %do;'; put '%put %str(ERR)OR: %sysfunc(sysmsg());'; put '%return;'; put '%end;'; put '%let rc=%sysfunc(fput(&fid,%str(Content-Type: application/json)));'; put '%let rc=%sysfunc(fwrite(&fid));'; put '%let rc=%sysfunc(fclose(&fid));'; put '%let rc=%sysfunc(filename(&fref));'; put '%end;'; put '/* send response in SASjs JSON format */'; put 'data _null_;'; put 'file _webout mod lrecl=32000 encoding=''utf-8'';'; put 'length msg syswarningtext syserrortext $32767 mode $10 ;'; put 'sasdatetime=datetime();'; put 'msg=symget(''msg'');'; put '%if &logline>0 %then %do;'; put 'msg=cats(msg,''\n\nLog Extract:\n'',symget(''logmsg''));'; put '%end;'; put '/* escape the escapes */'; put 'msg=tranwrd(msg,''\'',''\\'');'; put '/* escape the quotes */'; put 'msg=tranwrd(msg,''"'',''\"'');'; put '/* ditch the CRLFs as chrome complains */'; put 'msg=compress(msg,,''kw'');'; put '/* quote without quoting the quotes (which are escaped instead) */'; put 'msg=cats(''"'',msg,''"'');'; put 'if symexist(''_debug'') then debug=quote(trim(symget(''_debug'')));'; put 'else debug=''""'';'; put 'if symget(''sasjsprocessmode'')=''Stored Program'' then mode=''SASJS'';'; put 'if mode ne ''SASJS'' then put ''>>weboutBEGIN<<'';'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'put '',"sasjsAbort" : [{'';'; put 'put '' "MSG":'' msg ;'; put 'put '' ,"MAC": "'' "&mac" ''"}]'';'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'put '',"_DEBUG":'' debug ;'; put 'if symexist(''_metauser'') then do;'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put 'end;'; put 'if symexist(''SYS_JES_JOB_URI'') then do;'; put 'SYS_JES_JOB_URI=quote(trim(symget(''SYS_JES_JOB_URI'')));'; put 'put '',"SYS_JES_JOB_URI": '' SYS_JES_JOB_URI;'; put 'end;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put ",""SYSWARNINGTEXT"" : " syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'put "}" ;'; put 'if mode ne ''SASJS'' then put ''>>weboutEND<<'';'; put 'run;'; put '%put _all_;'; put '%if "&sysprocessmode " = "SAS Stored Process Server " %then %do;'; put 'data _null_;'; put 'putlog ''stpsrvset program err and syscc'';'; put 'rc=stpsrvset(''program error'', 0);'; put 'call symputx("syscc",0,"g");'; put 'run;'; put '%if &sysscp=WIN'; put 'and 1=0 /* deprecating this logic until we figure out a consistent abort */'; put 'and "%substr(%str(&sysvlong ),1,8)"="9.04.01M"'; put 'and "%substr(%str(&sysvlong ),9,1)">"5" %then %do;'; put '/* skip approach (below) does not work in windows m6+ envs */'; put 'endsas;'; put '%end;'; put '%else %do;'; put '/**'; put '* endsas kills 9.4m3 deployments by orphaning multibridges.'; put '* Abort variants are ungraceful (non zero return code)'; put '* This approach lets SAS run silently until the end :-)'; put '* Caution - fails when called within a %include within a macro'; put '* Use mp_include() to handle this.'; put '*/'; put 'filename skip temp;'; put 'data _null_;'; put 'file skip;'; put 'put ''%macro skip();'';'; put 'comment ''%mend skip; -> fix lint '';'; put 'put ''%macro skippy();'';'; put 'comment ''%mend skippy; -> fix lint '';'; put 'run;'; put '%inc skip;'; put '%end;'; put '%end;'; put '%else %if "&sysprocessmode " = "SAS Compute Server " %then %do;'; put '/* endsas kills the session making it harder to fetch results */'; put 'data _null_;'; put 'syswarningtext=symget(''syswarningtext'');'; put 'syserrortext=symget(''syserrortext'');'; put 'abort_msg=symget(''msg'');'; put 'syscc=symget(''syscc'');'; put 'sysuserid=symget(''sysuserid'');'; put 'iftrue=symget(''iftrue'');'; put 'put (_all_)(/=);'; put 'call symputx(''syscc'',0);'; put 'abort cancel nolist;'; put 'run;'; put '%end;'; put '%else %do;'; put '%abort cancel;'; put '%end;'; put '%end;'; put '%else %do;'; put '%put _all_;'; put '%abort cancel;'; put '%end;'; put '%mend mp_abort;'; put '/** @endcond */'; put '%macro mm_getstpcode('; put 'tree=/User Folders/sasdemo/somestp'; put ',name='; put ',outloc=0'; put ',outref=0'; put ',mDebug=1'; put ',showlog=NO'; put ');'; put '%local mD;'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing &sysmacroname..sas;'; put '%&mD.put _local_;'; put '%if %length(&name)>0 %then %let name=/&name;'; put '/* first, check if STP exists */'; put '%local tsuri;'; put '%let tsuri=stopifempty ;'; put 'data _null_;'; put 'format type uri tsuri value $200.;'; put 'call missing (of _all_);'; put 'path="&tree&name(StoredProcess)";'; put '/* first, find the STP ID */'; put 'if metadata_pathobj("",path,"StoredProcess",type,uri)>0 then do;'; put '/* get sourcecode */'; put 'cnt=1;'; put 'do while (metadata_getnasn(uri,"Notes",cnt,tsuri)>0);'; put 'rc=metadata_getattr(tsuri,"Name",value);'; put '&mD.put tsuri= value=;'; put 'if value="SourceCode" then do;'; put '/* found it! */'; put 'rc=metadata_getattr(tsuri,"Id",value);'; put 'call symputx(''tsuri'',value,''l'');'; put 'stop;'; put 'end;'; put 'cnt+1;'; put 'end;'; put 'end;'; put 'else put (_all_)(=);'; put 'run;'; put '%mp_abort(iftrue= (&tsuri=stopifempty)'; put ',mac=mm_getstpcode'; put ',msg=%str(&tree&name.(StoredProcess) not found!)'; put ')'; put '/**'; put '* Now we can extract the textstore'; put '*/'; put 'filename __getdoc temp lrecl=10000000;'; put 'proc metadata'; put 'in="$METAREPOSITORY'; put ''; put 'SAS1"'; put 'out=__getdoc ;'; put 'run;'; put '/* find the beginning of the text */'; put '%local start;'; put 'data _null_;'; put 'infile __getdoc lrecl=10000;'; put 'input;'; put 'start=index(_infile_,''StoredText="'');'; put 'if start then do;'; put 'call symputx("start",start+11);'; put '*putlog ''"'' _infile_ ''"'';'; put 'end;'; put 'stop;'; put '%local outeng;'; put '%if "&outloc"="0" %then %let outeng=TEMP;'; put '%else %let outeng="&outloc";'; put '%local fref;'; put '%if &outref=0 %then %let fref=%mf_getuniquefileref();'; put '%else %let fref=&outref;'; put '/* read the content, byte by byte, resolving escaped chars */'; put 'filename &fref &outeng lrecl=100000;'; put 'data _null_;'; put 'length filein 8 fileid 8;'; put 'filein = fopen("__getdoc","I",1,"B");'; put 'fileid = fopen("&fref","O",1,"B");'; put 'rec = "20"x;'; put 'length entity $6;'; put 'do while(fread(filein)=0);'; put 'x+1;'; put 'if x>&start then do;'; put 'rc = fget(filein,rec,1);'; put 'if rec=''"'' then leave;'; put 'else if rec="&" then do;'; put 'entity=rec;'; put 'do until (rec=";");'; put 'if fread(filein) ne 0 then goto getout;'; put 'rc = fget(filein,rec,1);'; put 'entity=cats(entity,rec);'; put 'end;'; put 'select (entity);'; put 'when (''&'' ) rec=''&'' ;'; put 'when (''<'' ) rec=''<'' ;'; put 'when (''>'' ) rec=''>'' ;'; put 'when (''''') rec="''" ;'; put 'when (''"'') rec=''"'' ;'; put 'when ('' '') rec=''0A''x;'; put 'when ('' '') rec=''0D''x;'; put 'when (''$'' ) rec=''$'' ;'; put 'when ('' '') rec=''09''x;'; put 'otherwise putlog "%str(WARN)ING: missing value for " entity=;'; put 'end;'; put 'rc =fput(fileid, substr(rec,1,1));'; put 'rc =fwrite(fileid);'; put 'end;'; put 'else do;'; put 'rc =fput(fileid,rec);'; put 'rc =fwrite(fileid);'; put 'end;'; put 'end;'; put 'end;'; put 'getout:'; put 'rc=fclose(filein);'; put 'rc=fclose(fileid);'; put 'run;'; put '%if &showlog=YES %then %do;'; put 'data _null_;'; put 'infile &fref lrecl=32767 end=last;'; put 'input;'; put 'if _n_=1 then putlog ''>>stpcodeBEGIN<<'';'; put 'putlog _infile_;'; put 'if last then putlog ''>>stpcodeEND<<'';'; put 'run;'; put '%end;'; put 'filename __getdoc clear;'; put '%if &outref=0 %then %do;'; put 'filename &fref clear;'; put '%end;'; put '%mend mm_getstpcode;'; put '%macro dc_getsettings();'; put '%global _program;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(syscc=&syscc on entry (&syswarningtext &syserrortext))'; put ')'; put '%if %length(&_PROGRAM)>1 %then %let root=&_program;'; put '%else %do;'; put '%global _metauser;'; put '%let _metauser=&sysuserid;'; put '/* to mimic a "real" _program we need to give a dummy role and stp name */'; put '%let root=/dummyRole/dummyName;'; put '%end;'; put '/* the DC precode is stored in the Admin folder in the root of'; put 'the project. Lets find that root. */'; put '%put &=root;'; put '%let root=%mf_getapploc();'; put '%put &=root;'; put '/* Now we know the root location we can retrieve the params */'; put '%let temploc=%sysfunc(pathname(work))/settings.txt;'; put '%mm_getstpcode(tree=&root/services/public'; put ',name=Data_Controller_Settings'; put ',outloc=&temploc'; put ')'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Unable to run getstpcode)'; put ')'; put 'filename _getsets "&temploc" lrecl=2000;'; put '/*'; put 'Do not use mp_include here - this puts a copy in every service, which creates'; put 'compilation problems when calling services from mp_include'; put '*/'; put '%inc _getsets/source2;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Problem running &sysmacroname (&syswarningtext &syserrortext))'; put ')'; put '%mend dc_getsettings;'; put '%macro mf_fmtdttm('; put ')/*/STORE SOURCE*/;'; put '%if "&sysver"="9.2" or "&sysver"="9.3"'; put 'or ("&sysver"="9.4" and "%substr(&SYSVLONG,9,1)" le "3")'; put 'or "%substr(&sysver,1,1)"="4"'; put 'or "%substr(&sysver,1,1)"="5"'; put '%then %do;DATETIME19.3%end;'; put '%else %do;E8601DT26.6%end;'; put '%mend mf_fmtdttm;'; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '%macro mp_init(prefix=SASJS'; put ')/*/STORE SOURCE*/;'; put '%if %symexist(SASJS_PREFIX) %then %return; /* only run once */'; put '%global'; put 'SASJS_PREFIX /* the ONLY hard-coded global macro variable in SASjs */'; put '&prefix._FUNCTIONS /* used in mcf_init() to track core function compilation */'; put '&prefix._INIT_NUM /* initialisation time as numeric */'; put '&prefix._INIT_DTTM /* initialisation time in E8601DT26.6 format */'; put '&prefix.WORK /* avoid typing %sysfunc(pathname(work)) every time */'; put ';'; put '%let sasjs_prefix=&prefix;'; put 'data _null_;'; put 'dttm=datetime();'; put 'call symputx("&sasjs_prefix._init_num",dttm,''g'');'; put 'call symputx("&sasjs_prefix._init_dttm",put(dttm,E8601DT26.6),''g'');'; put 'call symputx("&sasjs_prefix.work",pathname(''WORK''),''g'');'; put 'run;'; put 'options'; put 'compress=CHAR /* default is none so ensure we have something! */'; put 'datastmtchk=ALLKEYWORDS /* protection from overwriting input datasets */'; put 'errorcheck=STRICT /* catch errs in libname/filename statements */'; put 'fmterr /* ensure err when a format cannot be found */'; put 'mergenoby=%str(ERR)OR /* throw err when a merge has no BY variables */'; put 'missing=. /* changing this can cause hard to detect errs */'; put 'noquotelenmax /* avoid warnings for long strings */'; put 'noreplace /* avoid overwriting permanent datasets */'; put 'ps=max /* reduce log size slightly */'; put 'ls=max /* reduce log even more and avoid word truncation */'; put 'validmemname=COMPATIBLE /* avoid special characters etc in table names */'; put 'validvarname=V7 /* avoid special characters etc in variable names */'; put 'varinitchk=%str(ERR)OR /* avoid data mistakes from variable name typos */'; put 'varlenchk=%str(ERR)OR /* fail hard if truncation (data loss) can result */'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5" %then %do;'; put 'noautocorrect /* disallow misspelled procedure names */'; put 'dsoptions=note2err /* undocumented - convert bad NOTEs to ERRs */'; put '%end;'; put ';'; put '%mend mp_init;'; put '%macro mpeinit(fetch=YES);'; put '%global mpeinit'; put 'mpeadmins /* group with unrestricted Meditor access */'; put 'mpelocapprovals /* location for landing and staging files */'; put 'mpelib /* location of configuration tables for DC */'; put 'dc_repo_users /* location of user / group metadata */'; put 'dc_licence_key /* extracted in dc_getsettings */'; put 'dc_activation_key /* extracted in dc_getsettings */'; put 'dc_locale /* extracted in dc_getsettings */'; put 'dc_dttmtfmt /* can be overridden in dc_getsettings */'; put '_debug'; put ';'; put '%if &mpeinit=1 %then %return;'; put '%else %let mpeinit=1;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(Problem on service startup (&syswarningtext &syserrortext))'; put ')'; put '%mp_init()'; put '%if &fetch=YES %then %do;'; put '%webout(FETCH)'; put '%end;'; put '%global _CLIENTNAME;'; put '%mp_abort(iftrue= (&_CLIENTNAME=SAS Enterprise Guide)'; put ',mac=&_program..sas'; put ',msg=%str(Data Controller is a web app and should not be executed from EG)'; put ')'; put 'options urlencoding=utf8 nobomfile lrecl=32767;'; put '%let perf=%sysfunc(datetime());'; put '%put perfdiff: 0;'; put '%let dc_locale=SYSTEM; /* default if not set */'; put '/**'; put '* E8601DT26.6 has widest database support - but not all SAS flavours can'; put '* handle it. Override in the settings STP if needed.'; put '*/'; put 'data _null_;'; put 'dc_dttmtfmt=''"%sysfunc(datetime(),''!!"%mf_fmtdttm()"!!'')"dt'';'; put 'call symputx(''dc_dttmtfmt'',dc_dttmtfmt);'; put 'put dc_dttmtfmt=;'; put 'run;'; put '%put &=dc_dttmtfmt;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc prior to dc_getsettings)'; put ')'; put '%dc_getsettings()'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc after dc_getsettings)'; put ')'; put 'data _null_;'; put 'set &DC_LIBREF..mpe_config(where=('; put 'var_scope="DC"'; put 'and &dc_dttmtfmt lt tx_to'; put 'and var_active=1'; put '));'; put 'call symputx(var_name,var_value,''G'');'; put 'putlog var_name "=" var_value;'; put 'run;'; put '%let mpelib=&dc_libref;'; put '%let mpeadmins=&dc_admin_group;'; put '%let mpelocapprovals=&dc_staging_area;'; put '%let dc_repo_users=&dc_repo_users;'; put '%if &dc_locale ne SYSTEM %then %do;'; put 'options locale=&dc_locale;'; put '%end;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(Problem during compilation or with STP precode (&syswarningtext))'; put ')'; put '%mend mpeinit;'; put '%macro mf_mval(var);'; put '%if %symexist(&var) %then %do;'; put '%superq(&var)'; put '%end;'; put '%mend mf_mval;'; put '%macro mf_trimstr(basestr,trimstr);'; put '%local baselen trimlen trimval;'; put '/* return if basestr is shorter than trimstr (or 0) */'; put '%let baselen=%length(%superq(basestr));'; put '%let trimlen=%length(%superq(trimstr));'; put '%if &baselen < &trimlen or &baselen=0 %then %return;'; put '/* obtain the characters from the end of basestr */'; put '%let trimval=%qsubstr(%superq(basestr)'; put ',%length(%superq(basestr))-&trimlen+1'; put ',&trimlen);'; put '/* compare and if matching, chop it off! */'; put '%if %superq(basestr)=%superq(trimstr) %then %do;'; put '%return;'; put '%end;'; put '%else %if %superq(trimval)=%superq(trimstr) %then %do;'; put '%qsubstr(%superq(basestr),1,%length(%superq(basestr))-&trimlen)'; put '%end;'; put '%else %do;'; put '&basestr'; put '%end;'; put '%mend mf_trimstr;'; put '%macro mf_getplatform(switch'; put ')/*/STORE SOURCE*/;'; put '%local a b c;'; put '%if &switch.NONE=NONE %then %do;'; put '%if %symexist(sasjsprocessmode) %then %do;'; put '%if &sasjsprocessmode=Stored Program %then %do;'; put 'SASJS'; put '%return;'; put '%end;'; put '%end;'; put '%if %symexist(sysprocessmode) %then %do;'; put '%if "&sysprocessmode"="SAS Object Server"'; put 'or "&sysprocessmode"= "SAS Compute Server" %then %do;'; put 'SASVIYA'; put '%end;'; put '%else %if "&sysprocessmode"="SAS Stored Process Server"'; put 'or "&sysprocessmode"="SAS Workspace Server"'; put '%then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if %symexist(_metaport) or %symexist(_metauser) %then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if &switch=SASSTUDIO %then %do;'; put '/* return the version of SAS Studio else 0 */'; put '%if %mf_mval(_CLIENTAPP)=%str(SAS Studio) %then %do;'; put '%let a=%mf_mval(_CLIENTVERSION);'; put '%let b=%scan(&a,1,.);'; put '%if %eval(&b >2) %then %do;'; put '&b'; put '%end;'; put '%else 0;'; put '%end;'; put '%else 0;'; put '%end;'; put '%else %if &switch=VIYARESTAPI %then %do;'; put '%mf_trimstr(%sysfunc(getoption(servicesbaseurl)),/)'; put '%end;'; put '%mend mf_getplatform;'; put '%macro mpeterm();'; put '%local oldloc;'; put 'data _null_;'; put 'if symexist(''SYSPRINTTOLOG'') then oldloc=symget(''SYSPRINTTOLOG'');'; put 'else oldloc=getoption(''LOG'');'; put 'if subpad(oldloc,1,1) not in (''"'',"''",'' '') then oldloc=quote(cats(oldloc));'; put 'call symputx(''oldloc'',oldloc,''l'');'; put 'run;'; put '%if %length(&oldloc)>0 %then %do;'; put 'proc printto log=log;'; put 'run;'; put 'data _null_;'; put 'infile &oldloc;'; put 'input; putlog _infile_;'; put 'run;'; put '%end;'; put '%if %sysfunc(exist(&dc_libref..mpe_requests)) and %mf_getplatform() ne SASVIYA'; put '%then %do;'; put 'data ;'; put 'if 0 then set &dc_libref..mpe_requests;'; put 'request_dttm=%sysfunc(datetime());'; put 'request_user="%mf_getuser()";'; put 'request_service="%scan(&_program,-2,/)/%scan(&_program,-1,/)";'; put 'request_params='''';'; put 'output;stop;'; put 'proc append base=&dc_libref..mpe_requests data=&syslast force nowarn;'; put 'run;'; put '%end;'; put '%mend mpeterm;'; put '%macro mm_assignlib('; put 'libref'; put ',mAbort=HARD'; put ')/*/STORE SOURCE*/;'; put '%local mp_abort msg;'; put '%let mp_abort=0;'; put '%if %sysfunc(libref(&libref)) %then %do;'; put 'data _null_;'; put 'length liburi LibName msg $200;'; put 'call missing(of _all_);'; put 'nobj=metadata_getnobj("omsobj:SASLibrary?@Libref=''&libref''",1,liburi);'; put 'if nobj=1 then do;'; put 'rc=metadata_getattr(liburi,"Name",LibName);'; put '/* now try and assign it */'; put 'if libname("&libref",,''meta'',cats(''liburi="'',liburi,''";'')) ne 0 then do;'; put 'putlog "&libref could not be assigned";'; put 'putlog liburi=;'; put '/**'; put '* Fetch the system message for display in the abort modal. This is'; put '* not always helpful though. One example, previously received:'; put '* NOTE: Libref XX refers to the same library metadata as libref XX.'; put '*/'; put 'msg=sysmsg();'; put 'if msg=:''ERROR: Libref SAVE is not assigned.'' then do;'; put 'msg=catx(" ",'; put '"Could not assign %upcase(&libref).",'; put '"Please check metadata permissions! Libname:",libname,'; put '"Liburi:",liburi'; put ');'; put 'end;'; put 'else if msg="ERROR: User does not have appropriate authorization "!!'; put '"level for library SAVE."'; put 'then do;'; put 'msg=catx(" ",'; put '"ERROR: User does not have appropriate authorization level",'; put '"for library %upcase(&libref), libname:",libname,'; put '"Liburi:",liburi'; put ');'; put 'end;'; put 'call symputx(''msg'',msg,''l'');'; put 'if "&mabort"=''HARD'' then call symputx(''mp_abort'',1,''l'');'; put 'end;'; put 'else do;'; put 'put (_all_)(=);'; put 'call symputx(''libname'',libname,''L'');'; put 'call symputx(''liburi'',liburi,''L'');'; put 'end;'; put 'end;'; put 'else if nobj>1 then do;'; put 'if "&mabort"=''HARD'' then call symputx(''mp_abort'',1);'; put 'call symputx(''msg'',"More than one library with libref=&libref");'; put 'end;'; put 'else do;'; put 'if "&mabort"=''HARD'' then call symputx(''mp_abort'',1);'; put 'call symputx(''msg'',"Library &libref not found in metadata");'; put 'end;'; put 'run;'; put '%put NOTE: &msg;'; put '%end;'; put '%else %do;'; put '%put NOTE: Library &libref is already assigned;'; put '%end;'; put '%mp_abort(iftrue= (&mp_abort=1)'; put ',mac=mm_assignlib.sas'; put ',msg=%superq(msg)'; put ')'; put '%mend mm_assignlib;'; put '/** @cond */'; put '%macro mf_getengine(libref'; put ')/*/STORE SOURCE*/;'; put '%local dsid engnum rc engine;'; put '/* in case the parameter is a libref.tablename, pull off just the libref */'; put '%let libref = %upcase(%scan(&libref, 1, %str(.)));'; put '%let dsid=%sysfunc('; put 'open(sashelp.vlibnam(where=(libname="%upcase(&libref)")),i)'; put ');'; put '%if (&dsid ^= 0) %then %do;'; put '%let engnum=%sysfunc(varnum(&dsid,ENGINE));'; put '%let rc=%sysfunc(fetch(&dsid));'; put '%let engine=%sysfunc(getvarc(&dsid,&engnum));'; put '%put &libref. ENGINE is &engine.;'; put '%let rc= %sysfunc(close(&dsid));'; put '%end;'; put '%upcase(&engine)'; put '%mend mf_getengine;'; put '/** @endcond */'; put '%macro mm_assigndirectlib('; put 'libref'; put ',open_passthrough='; put ',sql_options='; put ',mDebug=0'; put ',mAbort=0'; put ')/*/STORE SOURCE*/;'; put '%local mD;'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing mm_assigndirectlib.sas;'; put '%&mD.put _local_;'; put '%if &mAbort=1 %then %let mAbort=;'; put '%else %let mAbort=%str(*);'; put '%&mD.put NOTE: Creating direct (non META) connection to &libref library;'; put '%local cur_engine;'; put '%let cur_engine=%mf_getengine(&libref);'; put '%if &cur_engine ne META and &cur_engine ne %then %do;'; put '%put NOTE: &libref already has a direct (&cur_engine) libname connection;'; put '%return;'; put '%end;'; put '%else %if %upcase(&libref)=WORK %then %do;'; put '%put NOTE: We already have a direct connection to WORK :-) ;'; put '%return;'; put '%end;'; put '/* need to determine the library ENGINE first */'; put '%local engine;'; put 'data _null_;'; put 'length lib_uri engine $256;'; put 'call missing (of _all_);'; put '/* get URI for the particular library */'; put 'rc1=metadata_getnobj("omsobj:SASLibrary?@Libref =''&libref''",1,lib_uri);'; put '/* get the Engine attribute of the previous object */'; put 'rc2=metadata_getattr(lib_uri,''Engine'',engine);'; put 'putlog "mm_assigndirectlib for &libref:" rc1= lib_uri= rc2= engine=;'; put 'call symputx("liburi",lib_uri,''l'');'; put 'call symputx("engine",engine,''l'');'; put 'run;'; put '/* now obtain engine specific connection details */'; put '%if &engine=BASE %then %do;'; put '%&mD.put NOTE: Retrieving BASE library path;'; put 'data _null_;'; put 'length up_uri $256 path cat_path $1024;'; put 'retain cat_path;'; put 'call missing (of _all_);'; put '/* get all the filepaths of the UsingPackages association */'; put 'i=1;'; put 'rc3=metadata_getnasn("&liburi",''UsingPackages'',i,up_uri);'; put 'do while (rc3>0);'; put '/* get the DirectoryName attribute of the previous object */'; put 'rc4=metadata_getattr(up_uri,''DirectoryName'',path);'; put 'if i=1 then path = ''("''!!trim(path)!!''" '';'; put 'else path ='' "''!!trim(path)!!''" '';'; put 'cat_path = trim(cat_path) !! " " !! trim(path) ;'; put 'i+1;'; put 'rc3=metadata_getnasn("&liburi",''UsingPackages'',i,up_uri);'; put 'end;'; put 'cat_path = trim(cat_path) !! ")";'; put '&mD.putlog "NOTE: Getting physical path for &libref library";'; put '&mD.putlog rc3= up_uri= rc4= cat_path= path=;'; put '&mD.putlog "NOTE: Libname cmd will be:";'; put '&mD.putlog "libname &libref" cat_path;'; put 'call symputx("filepath",cat_path,''l'');'; put 'run;'; put '%if %sysevalf(&sysver<9.4) %then %do;'; put 'libname &libref &filepath;'; put '%end;'; put '%else %do;'; put '/* apply the new filelocks option to cater for temporary locks */'; put 'libname &libref &filepath filelockwait=5;'; put '%end;'; put '%end;'; put '%else %if &engine=REMOTE %then %do;'; put 'data x;'; put 'length rcCon rcProp rc k 3 uriCon uriProp PropertyValue PropertyName'; put 'Delimiter $256 properties $2048;'; put 'retain properties;'; put 'rcCon = metadata_getnasn("&liburi", "LibraryConnection", 1, uriCon);'; put 'rcProp = metadata_getnasn(uriCon, "Properties", 1, uriProp);'; put 'k = 1;'; put 'rcProp = metadata_getnasn(uriCon, "Properties", k, uriProp);'; put 'do while (rcProp > 0);'; put 'rc = metadata_getattr(uriProp , "DefaultValue",PropertyValue);'; put 'rc = metadata_getattr(uriProp , "PropertyName",PropertyName);'; put 'rc = metadata_getattr(uriProp , "Delimiter",Delimiter);'; put 'properties = trim(properties) !! " " !! trim(PropertyName)'; put '!! trim(Delimiter) !! trim(PropertyValue);'; put 'output;'; put 'k+1;'; put 'rcProp = metadata_getnasn(uriCon, "Properties", k, uriProp);'; put 'end;'; put '%&mD.put NOTE: Getting properties for REMOTE SHARE &libref library;'; put '&mD.put _all_;'; put '%&mD.put NOTE: Libname cmd will be:;'; put '%&mD.put libname &libref &engine &properties slibref=&libref;'; put 'call symputx ("properties",trim(properties),''l'');'; put 'run;'; put 'libname &libref &engine &properties slibref=&libref;'; put '%end;'; put '%else %if &engine=OLEDB %then %do;'; put '%&mD.put NOTE: Retrieving OLEDB connection details;'; put 'data _null_;'; put 'length domain datasource provider properties schema'; put 'connx_uri domain_uri conprop_uri lib_uri schema_uri value $256.;'; put 'call missing (of _all_);'; put '/* get source connection ID */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,connx_uri);'; put '/* get connection domain */'; put 'rc1=metadata_getnasn(connx_uri,''Domain'',1,domain_uri);'; put 'rc2=metadata_getattr(domain_uri,''Name'',domain);'; put '&mD.putlog / ''NOTE: '' // ''NOTE- connection id: '' connx_uri ;'; put '&mD.putlog ''NOTE- domain: '' domain;'; put '/* get DSN and PROVIDER from connection properties */'; put 'i=0;'; put 'do until (rc<0);'; put 'i+1;'; put 'rc=metadata_getnasn(connx_uri,''Properties'',i,conprop_uri);'; put 'rc2=metadata_getattr(conprop_uri,''Name'',value);'; put 'if value=''Connection.OLE.Property.DATASOURCE.Name.xmlKey.txt'' then do;'; put 'rc3=metadata_getattr(conprop_uri,''DefaultValue'',datasource);'; put 'end;'; put 'else if value=''Connection.OLE.Property.PROVIDER.Name.xmlKey.txt'' then do;'; put 'rc4=metadata_getattr(conprop_uri,''DefaultValue'',provider);'; put 'end;'; put 'else if value=''Connection.OLE.Property.PROPERTIES.Name.xmlKey.txt'' then'; put 'do;'; put 'rc5=metadata_getattr(conprop_uri,''DefaultValue'',properties);'; put 'end;'; put 'end;'; put '&mD.putlog ''NOTE- dsn/provider/properties: '' /'; put 'datasource provider properties;'; put '&mD.putlog ''NOTE- schema: '' schema // ''NOTE-'';'; put '/* get SCHEMA */'; put 'rc6=metadata_getnasn("&liburi",''UsingPackages'',1,lib_uri);'; put 'rc7=metadata_getattr(lib_uri,''SchemaName'',schema);'; put 'call symputx(''SQL_domain'',domain,''l'');'; put 'call symputx(''SQL_dsn'',datasource,''l'');'; put 'call symputx(''SQL_provider'',provider,''l'');'; put 'call symputx(''SQL_properties'',properties,''l'');'; put 'call symputx(''SQL_schema'',schema,''l'');'; put 'run;'; put '%if %length(&open_passthrough)>0 %then %do;'; put 'proc sql &sql_options;'; put 'connect to OLEDB as &open_passthrough(INSERT_SQL=YES'; put '/* need additional properties to make this work */'; put 'properties=(''Integrated Security''=SSPI'; put '''Persist Security Info''=True'; put '%sysfunc(compress(%str(&SQL_properties),%str(())))'; put ')'; put 'DATASOURCE=&sql_dsn PROMPT=NO'; put 'PROVIDER=&sql_provider SCHEMA=&sql_schema CONNECTION = GLOBAL);'; put '%end;'; put '%else %do;'; put 'LIBNAME &libref OLEDB PROPERTIES=&sql_properties'; put 'DATASOURCE=&sql_dsn PROVIDER=&sql_provider SCHEMA=&sql_schema'; put '%if %length(&sql_domain)>0 %then %do;'; put 'authdomain="&sql_domain"'; put '%end;'; put 'connection=shared;'; put '%end;'; put '%end;'; put '%else %if &engine=ODBC %then %do;'; put '%&mD.put NOTE: Retrieving ODBC connection details;'; put 'data _null_;'; put 'length connx_uri conprop_uri value datasource up_uri schema domprop_uri authdomain $256.;'; put 'call missing (of _all_);'; put '/* get source connection ID */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,connx_uri);'; put '/* get connection properties */'; put 'i=0;'; put 'do until (rc2<0);'; put 'i+1;'; put 'rc2=metadata_getnasn(connx_uri,''Properties'',i,conprop_uri);'; put 'rc3=metadata_getattr(conprop_uri,''Name'',value);'; put 'if value=''Connection.ODBC.Property.DATASRC.Name.xmlKey.txt'' then do;'; put 'rc4=metadata_getattr(conprop_uri,''DefaultValue'',datasource);'; put 'rc2=-1;'; put 'end;'; put 'end;'; put '/* get auth domain */'; put 'autrc=metadata_getnasn(connx_uri,"Domain",1,domprop_uri);'; put 'arc=metadata_getattr(domprop_uri,"Name",authdomain);'; put 'if not missing(authdomain) then authdomain=cats(''AUTHDOMAIN='',authdomain);'; put 'call symputx(''authdomain'',authdomain,''l'');'; put '/* get SCHEMA */'; put 'rc6=metadata_getnasn("&liburi",''UsingPackages'',1,up_uri);'; put 'rc7=metadata_getattr(up_uri,''SchemaName'',schema);'; put '&mD.put rc= connx_uri= rc2= conprop_uri= rc3= value= rc4= datasource='; put 'rc6= up_uri= rc7= schema=;'; put 'call symputx(''SQL_schema'',schema,''l'');'; put 'call symputx(''SQL_dsn'',datasource,''l'');'; put 'run;'; put '%if %length(&open_passthrough)>0 %then %do;'; put 'proc sql &sql_options;'; put 'connect to ODBC as &open_passthrough'; put '(INSERT_SQL=YES DATASRC=&sql_dsn. CONNECTION=global);'; put '%end;'; put '%else %do;'; put 'libname &libref ODBC DATASRC=&sql_dsn SCHEMA=&sql_schema &authdomain;'; put '%end;'; put '%end;'; put '%else %if &engine=POSTGRES %then %do;'; put '%put NOTE: Obtaining POSTGRES library details;'; put 'data _null_;'; put 'length database ignore_read_only_columns direct_exe preserve_col_names'; put 'preserve_tab_names server schema authdomain user password'; put 'prop name value uri urisrc $256.;'; put 'call missing (of _all_);'; put '/* get database value */'; put 'prop=''Connection.DBMS.Property.DB.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,database,"");'; put 'if database^='''' then database=''database=''!!quote(trim(database));'; put 'call symputx(''database'',database,''l'');'; put '/* get IGNORE_READ_ONLY_COLUMNS value */'; put 'prop=''Library.DBMS.Property.DBIROC.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,ignore_read_only_columns,"");'; put 'if ignore_read_only_columns^='''' then ignore_read_only_columns='; put '''ignore_read_only_columns=''!!ignore_read_only_columns;'; put 'call symputx(''ignore_read_only_columns'',ignore_read_only_columns,''l'');'; put '/* get DIRECT_EXE value */'; put 'prop=''Library.DBMS.Property.DirectExe.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,direct_exe,"");'; put 'if direct_exe^='''' then direct_exe=''direct_exe=''!!direct_exe;'; put 'call symputx(''direct_exe'',direct_exe,''l'');'; put '/* get PRESERVE_COL_NAMES value */'; put 'prop=''Library.DBMS.Property.PreserveColNames.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,preserve_col_names,"");'; put 'if preserve_col_names^='''' then preserve_col_names='; put '''preserve_col_names=''!!preserve_col_names;'; put 'call symputx(''preserve_col_names'',preserve_col_names,''l'');'; put '/* get PRESERVE_TAB_NAMES value */'; put '/* be careful with PRESERVE_TAB_NAMES=YES - it will mean your table will'; put 'become case sensitive!! */'; put 'prop=''Library.DBMS.Property.PreserveTabNames.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,preserve_tab_names,"");'; put 'if preserve_tab_names^='''' then preserve_tab_names='; put '''preserve_tab_names=''!!preserve_tab_names;'; put 'call symputx(''preserve_tab_names'',preserve_tab_names,''l'');'; put '/* get SERVER value */'; put 'if metadata_getnasn("&liburi","LibraryConnection",1,uri)>0 then do;'; put 'prop=''Connection.DBMS.Property.SERVER.Name.xmlKey.txt'';'; put 'rc=metadata_getprop(uri,prop,server,"");'; put 'end;'; put 'if server^='''' then server=''server=''!!quote(cats(server));'; put 'call symputx(''server'',server,''l'');'; put '/* get SCHEMA value */'; put 'if metadata_getnasn("&liburi","UsingPackages",1,uri)>0 then do;'; put 'rc=metadata_getattr(uri,"SchemaName",schema);'; put 'end;'; put 'if schema^='''' then schema=''schema=''!!schema;'; put 'call symputx(''schema'',schema,''l'');'; put '/* get AUTHDOMAIN value */'; put '/* this is only useful if the user account contains that auth domain'; put 'if metadata_getnasn("&liburi","DefaultLogin",1,uri)>0 then do;'; put 'rc=metadata_getnasn(uri,"Domain",1,urisrc);'; put 'rc=metadata_getattr(urisrc,"Name",authdomain);'; put 'end;'; put 'if authdomain^='''' then authdomain=''authdomain=''!!quote(trim(authdomain));'; put '*/'; put 'call symputx(''authdomain'',authdomain,''l'');'; put '/* get user & pass */'; put 'if authdomain='''' & metadata_getnasn("&liburi","DefaultLogin",1,uri)>0 then'; put 'do;'; put 'rc=metadata_getattr(uri,"UserID",user);'; put 'rc=metadata_getattr(uri,"Password",password);'; put 'end;'; put 'if user^='''' then do;'; put 'user=''user=''!!quote(trim(user));'; put 'password=''password=''!!quote(trim(password));'; put 'end;'; put 'call symputx(''user'',user,''l'');'; put 'call symputx(''password'',password,''l'');'; put '&md.put _all_;'; put 'run;'; put '%if %length(&open_passthrough)>0 %then %do;'; put '%put %str(WARN)ING: Passthrough option for postgres not yet supported;'; put '%return;'; put '%end;'; put '%else %do;'; put '%if &mdebug=1 %then %do;'; put '%put NOTE: Executing the following:/;'; put '%put NOTE- libname &libref POSTGRES &database &ignore_read_only_columns;'; put '%put NOTE- &direct_exe &preserve_col_names &preserve_tab_names;'; put '%put NOTE- &server &schema &authdomain &user &password //;'; put '%end;'; put 'libname &libref POSTGRES &database &ignore_read_only_columns &direct_exe'; put '&preserve_col_names &preserve_tab_names &server &schema &authdomain'; put '&user &password;'; put '%end;'; put '%end;'; put '%else %if &engine=ORACLE %then %do;'; put '%put NOTE: Obtaining &engine library details;'; put 'data _null_;'; put 'length assocuri1 assocuri2 assocuri3 authdomain path schema $256;'; put 'call missing (of _all_);'; put '/* get auth domain */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,assocuri1);'; put 'rc=metadata_getnasn(assocuri1,''Domain'',1,assocuri2);'; put 'rc=metadata_getattr(assocuri2,"Name",authdomain);'; put 'call symputx(''authdomain'',authdomain,''l'');'; put '/* path */'; put 'rc=metadata_getprop(assocuri1,'; put '''Connection.Oracle.Property.PATH.Name.xmlKey.txt'',path);'; put 'call symputx(''path'',path,''l'');'; put '/* schema */'; put 'rc=metadata_getnasn("&liburi",''UsingPackages'',1,assocuri3);'; put 'rc=metadata_getattr(assocuri3,''SchemaName'',schema);'; put 'call symputx(''schema'',schema,''l'');'; put 'run;'; put '%put NOTE: Executing the following:/; %put NOTE-;'; put '%put NOTE- libname &libref ORACLE path=&path schema=&schema;'; put '%put NOTE- authdomain=&authdomain;'; put '%put NOTE-;'; put 'libname &libref ORACLE path=&path schema=&schema authdomain=&authdomain;'; put '%end;'; put '%else %if &engine=SQLSVR %then %do;'; put '%put NOTE: Obtaining &engine library details;'; put 'data _null;'; put 'length assocuri1 assocuri2 assocuri3 authdomain path schema userid'; put 'passwd $256;'; put 'call missing (of _all_);'; put 'rc=metadata_getnasn("&liburi",''DefaultLogin'',1,assocuri1);'; put 'rc=metadata_getattr(assocuri1,"UserID",userid);'; put 'rc=metadata_getattr(assocuri1,"Password",passwd);'; put 'call symputx(''user'',userid,''l'');'; put 'call symputx(''pass'',passwd,''l'');'; put '/* path */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,assocuri2);'; put 'rc=metadata_getprop(assocuri2,'; put '''Connection.SQL.Property.Datasrc.Name.xmlKey.txt'',path);'; put 'call symputx(''path'',path,''l'');'; put '/* schema */'; put 'rc=metadata_getnasn("&liburi",''UsingPackages'',1,assocuri3);'; put 'rc=metadata_getattr(assocuri3,''SchemaName'',schema);'; put 'call symputx(''schema'',schema,''l'');'; put 'run;'; put '%put NOTE: Executing the following:/; %put NOTE-;'; put '%put NOTE- libname &libref SQLSVR datasrc=&path schema=&schema ;'; put '%put NOTE- user="&user" pass="XXX";'; put '%put NOTE-;'; put 'libname &libref SQLSVR datasrc=&path schema=&schema user="&user" pass="&pass";'; put '%end;'; put '%else %if &engine=TERADATA %then %do;'; put '%put NOTE: Obtaining &engine library details;'; put 'data _null;'; put 'length assocuri1 assocuri2 assocuri3 authdomain path schema userid'; put 'passwd $256;'; put 'call missing (of _all_);'; put '/* get auth domain */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,assocuri1);'; put 'rc=metadata_getnasn(assocuri1,''Domain'',1,assocuri2);'; put 'rc=metadata_getattr(assocuri2,"Name",authdomain);'; put 'call symputx(''authdomain'',authdomain,''l'');'; put '/*'; put 'rc=metadata_getnasn("&liburi",''DefaultLogin'',1,assocuri1);'; put 'rc=metadata_getattr(assocuri1,"UserID",userid);'; put 'rc=metadata_getattr(assocuri1,"Password",passwd);'; put 'call symputx(''user'',userid,''l'');'; put 'call symputx(''pass'',passwd,''l'');'; put '*/'; put '/* path */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,assocuri2);'; put 'rc=metadata_getprop(assocuri2,'; put '''Connection.Teradata.Property.SERVER.Name.xmlKey.txt'',path);'; put 'call symputx(''path'',path,''l'');'; put '/* schema */'; put 'rc=metadata_getnasn("&liburi",''UsingPackages'',1,assocuri3);'; put 'rc=metadata_getattr(assocuri3,''SchemaName'',schema);'; put 'call symputx(''schema'',schema,''l'');'; put 'run;'; put '%put NOTE: Executing the following:/; %put NOTE-;'; put '%put NOTE- libname &libref TERADATA server="&path" schema=&schema ;'; put '%put NOTe- authdomain=&authdomain;'; put '%put NOTE-;'; put 'libname &libref TERADATA server="&path" schema=&schema authdomain=&authdomain;'; put '%end;'; put '%else %if &engine= %then %do;'; put '%put NOTE: Libref &libref is not registered in metadata;'; put '%&mAbort.mp_abort('; put 'msg=%str(ERR)OR: Libref &libref is not registered in metadata'; put ',mac=mm_assigndirectlib.sas);'; put '%return;'; put '%end;'; put '%else %do;'; put '%put %str(WARN)ING: Engine &engine is currently unsupported;'; put '%put %str(WARN)ING- Please contact your support team.;'; put '%return;'; put '%end;'; put '%mend mm_assigndirectlib;'; put '%macro mm_getrepos('; put 'outds=work.mm_getrepos'; put ')/*/STORE SOURCE*/;'; put '* use a temporary fileref to hold the response;'; put 'filename response temp;'; put '/* get list of libraries */'; put 'proc metadata in='; put '"1"'; put 'out=response;'; put 'run;'; put '/* write the response to the log for debugging */'; put '/*'; put 'data _null_;'; put 'infile response lrecl=1048576;'; put 'input;'; put 'put _infile_;'; put 'run;'; put '*/'; put '/* create an XML map to read the response */'; put 'filename sxlemap temp;'; put 'data _null_;'; put 'file sxlemap;'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository";'; put 'put "";'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Id";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Name";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Desc";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@DefaultNS";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@RepositoryType";'; put 'put "characterstring20";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@RepositoryFormat";'; put 'put "characterstring10";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@Access";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@CurrentAccess";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@PauseState";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Path";'; put 'put "";'; put 'put "characterstring256";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Engine";'; put 'put "";'; put 'put "characterstring8";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Options";'; put 'put "";'; put 'put "characterstring32";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@MetadataCreated";'; put 'put "characterstring24";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@MetadataUpdated";'; put 'put "characterstring24";'; put 'put '''';'; put 'put ''
'';'; put 'run;'; put 'libname _XML_ xml xmlfileref=response xmlmap=sxlemap;'; put 'proc sort data= _XML_.SASRepos out=&outds;'; put 'by name;'; put 'run;'; put '/* clear references */'; put 'filename sxlemap clear;'; put 'filename response clear;'; put 'libname _XML_ clear;'; put '%mend mm_getrepos;'; put '%macro dc_assignlib(type,libref,passthru=);'; put '%put &sysmacroname entry vars:;'; put '%put _local_;'; put '/* take current repository */'; put '%local repo repocnt x;'; put '%let repo=%sysfunc(getoption(metarepository));'; put '/* get list of repositories and filter */'; put '%mm_getrepos(outds=repos)'; put '%let repocnt=0;'; put 'data repos;'; put 'set repos;'; put 'where repositorytype in(''CUSTOM'',''FOUNDATION'')'; put '& upcase(name) ne "%upcase(&repo)";'; put 'keep id name ;'; put 'call symputx(cats(''repo'',_n_),name,''l'');'; put 'call symputx(''repocnt'',_n_,''l'');'; put 'run;'; put '/* find out which of these repositories has the libref we are searching for */'; put '%local lib_uri;'; put '%let lib_uri=NOTFOUND;'; put 'data _null_; /* check default repo first */'; put 'length lib_uri $256;'; put 'call missing (of _all_);'; put 'rc=metadata_getnobj("omsobj:SASLibrary?@Libref =''&libref''",1,lib_uri);'; put 'call symputx(''lib_uri'',coalescec(lib_uri,''NOTFOUND''),''l'');'; put 'run;'; put '%if &lib_uri=NOTFOUND %then %do;'; put '%do x=1 %to &repocnt;'; put 'options metarepository=&&repo&x;'; put 'data _null_;'; put 'length lib_uri $256;'; put 'call missing (of _all_);'; put 'rc=metadata_getnobj("omsobj:SASLibrary?@Libref =''&libref''",1,lib_uri);'; put 'call symputx(''lib_uri'',coalescec(lib_uri,''NOTFOUND''),''l'');'; put 'run;'; put '%if &lib_uri ne NOTFOUND %then %let x=%eval(&repocnt+1);'; put '%end;'; put '%end;'; put '%if &type=READ %then %do;'; put '%mm_assignlib(&libref)'; put '%end;'; put '%else %if &type=WRITE %then %do;'; put '%mm_assigndirectlib(&libref,open_passthrough=&passthru)'; put '%end;'; put 'options metarepository=&repo;'; put '%mend dc_assignlib;'; put '%macro mf_getuniquename(prefix=MC);'; put '&prefix.%substr(%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32-%length(&prefix))'; put '%mend mf_getuniquename;'; put '%macro mp_validatecol(incol,rule,outcol);'; put '/* tempcol is given a unique name with every invocation */'; put '%local tempcol;'; put '%let tempcol=%mf_getuniquename();'; put '%if &rule=ISINT %then %do;'; put '&outcol=0;'; put 'if not missing(&incol) then do;'; put '&tempcol=input(&incol,?? best32.);'; put 'if not missing(&tempcol) then if mod(&tempcol,1)=0 then &outcol=1;'; put 'end;'; put 'drop &tempcol;'; put '%end;'; put '%else %if &rule=ISNUM %then %do;'; put '/*'; put 'credit SOREN LASSEN'; put 'https://sasmacro.blogspot.com/2009/06/welcome-isnum-macro.html'; put '*/'; put '&tempcol=input(&incol,?? best32.);'; put 'if missing(&tempcol) then &outcol=0;'; put 'else &outcol=1;'; put 'drop &tempcol;'; put '%end;'; put '%else %if &rule=LIBDS %then %do;'; put '/* match libref.dataset */'; put 'if _n_=1 then do;'; put 'retain &tempcol;'; put '&tempcol=prxparse(''/^[_a-z]\w{0,7}\.[_a-z]\w{0,31}$/i'');'; put 'if missing(&tempcol) then do;'; put 'putlog ''ERR'' +(-1) "OR: Invalid expression for LIBDS";'; put 'stop;'; put 'end;'; put 'drop &tempcol;'; put 'end;'; put 'if prxmatch(&tempcol, trim(&incol)) then &outcol=1;'; put 'else &outcol=0;'; put '%end;'; put '%else %if &rule=FORMAT %then %do;'; put '/* match valid format - regex could probably be improved */'; put 'if _n_=1 then do;'; put 'retain &tempcol;'; put '&tempcol=prxparse(''/^[_a-z\$]\w{0,31}\.[0-9]*$/i'');'; put 'if missing(&tempcol) then do;'; put 'putlog ''ERR'' +(-1) "OR: Invalid expression for FORMAT";'; put 'stop;'; put 'end;'; put 'drop &tempcol;'; put 'end;'; put 'if prxmatch(&tempcol, trim(&incol)) then &outcol=1;'; put 'else &outcol=0;'; put '%end;'; put '%mend mp_validatecol;'; put '* SAS Macros end;'; put '* SAS Includes start;'; put '* SAS Includes end;'; put '* Binary Files start;'; put '* Binary Files end;'; put '* ServiceInit start;'; put 'options noquotelenmax ps=max;'; put '/* create dummy macros to prevent stpbegin / stpend from corrupting sessions */'; put '%macro stpbegin();'; put '%put NOTE: the STPBEGIN macro should not be used for web apps!;'; put '%mend stpbegin;'; put '%macro stpend();'; put '%put NOTE: the STPEND macro should not be used for web apps!;'; put '%mend stpend;'; put '* ServiceInit end;'; put '* Service start;'; put '/**'; put '@file'; put '@brief Generic validator for table columns'; put '@details The input table is simply one row from the target table in table'; put 'called "work.source_row".'; put 'Available macro variables:'; put '@li MPELIB - The DC control library'; put '@li LIBDS - The library.dataset being filtered'; put '@li VARIABLE_NM - The column being filtered'; put '

Service Inputs

'; put '
work.sourcerow
'; put 'Has source table structure.'; put '

Service Outputs

'; put 'The values provided below are generic samples - we encourage you to replace'; put 'these with realistic values in your own deployments.'; put '
DYNAMIC_VALUES
'; put 'The RAW_VALUE column may be charactor or numeric. If DISPLAY_INDEX is not'; put 'provided, it is added automatically.'; put '|DISPLAY_INDEX:best.|DISPLAY_VALUE:$|RAW_VALUE|'; put '|---|---|---|'; put '|1|$77.43|77.43|'; put '|2|$88.43|88.43|'; put '
DYNAMIC_EXTENDED_VALUES
'; put 'This table is optional. If provided, it will map the DISPLAY_INDEX from the'; put 'DYNAMIC_VALUES table to additional column/value pairs, that will be used to'; put 'populate dropdowns for _other_ cells in the _same_ row.'; put 'Should be used sparingly! The use of large tables here can slow down the'; put 'browser.'; put '|DISPLAY_INDEX:best.|EXTRA_COL_NAME:$32.|DISPLAY_VALUE:$|DISPLAY_TYPE:$1.|RAW_VALUE_NUM|RAW_VALUE_CHAR:$5000|'; put '|---|---|---|'; put '|1|DISCOUNT_RT|"50%"|N|0.5||'; put '|1|DISCOUNT_RT|"40%"|N|0.4||'; put '|1|DISCOUNT_RT|"30%"|N|0.3||'; put '|1|CURRENCY_SYMBOL|"GBP"|C||"GBP"|'; put '|1|CURRENCY_SYMBOL|"RSD"|C||"RSD"|'; put '|2|DISCOUNT_RT|"50%"|N|0.5||'; put '|2|DISCOUNT_RT|"40%"|N|0.4||'; put '|2|CURRENCY_SYMBOL|"EUR"|C||"EUR"|'; put '|2|CURRENCY_SYMBOL|"HKD"|C||"HKD"|'; put '

SAS Macros

'; put '@li dc_assignlib.sas'; put '@li mf_getuniquename.sas'; put '@li mp_abort.sas'; put '@li mp_validatecol.sas'; put '**/'; put '/* send back the raw and formatted values */'; put '%let tgtlibds=0;'; put '%let varlibds=%mf_getuniquename();'; put '%let vartgtlibds=%mf_getuniquename();'; put '%let var_is_libds=%mf_getuniquename();'; put 'data _null_;'; put 'length xl_libref base_lib select_lib rls_libref cls_libref libref $8'; put 'xl_table base_ds select_ds rls_table cls_table dsn $32;'; put 'if _n_=1 then call missing(of _all_);'; put 'set work.source_row;'; put '&varlibds=symget(''libds'');'; put 'if &varlibds="&mpelib..MPE_EXCEL_CONFIG"'; put 'then &vartgtlibds=cats(xl_libref,''.'',xl_table);'; put 'else if &varlibds="&mpelib..MPE_VALIDATIONS"'; put 'then &vartgtlibds=cats(BASE_LIB,''.'',BASE_DS);'; put 'else if &varlibds="&mpelib..MPE_SELECTBOX"'; put 'then &vartgtlibds=cats(select_lib,''.'',select_ds);'; put 'else if &varlibds="&mpelib..MPE_ROW_LEVEL_SECURITY"'; put 'then &vartgtlibds=cats(RLS_LIBREF,''.'',RLS_TABLE);'; put 'else if &varlibds="&mpelib..MPE_COLUMN_LEVEL_SECURITY"'; put 'then &vartgtlibds=cats(CLS_LIBREF,''.'',CLS_TABLE);'; put 'else if &varlibds="&mpelib..MPE_TABLES"'; put 'then &vartgtlibds=cats(LIBREF,''.'',DSN);'; put '/* validate libds */'; put '%mp_validatecol(&vartgtlibds,LIBDS,&var_is_libds)'; put 'if &var_is_libds=1 then call symputx(''tgtlibds'',&vartgtlibds);'; put 'putlog (_all_)(=);'; put 'run;'; put '%mp_abort(iftrue= ("&tgtlibds" ="0" )'; put ',mac=&_program..sas'; put ',msg=%str(Unable to extract libds vars from &libds inputs for &variable_nm)'; put ')'; put '%dc_assignlib(READ,%scan(&tgtlibds,1,.))'; put 'proc contents noprint data=&tgtlibds'; put 'out=work.DYNAMIC_VALUES (keep=name rename=(name=display_value) );'; put 'run;'; put 'data work.DYNAMIC_VALUES;'; put 'set work.DYNAMIC_VALUES;'; put 'raw_value=upcase(display_value);'; put 'format raw_value;'; put 'run;'; put '* Service end;'; run; %mm_createwebservice(path=&appLoc/&path, name=&service, code=sascode, server=&serverName, replace=yes) filename sascode clear; %let service=libraries_all; filename sascode temp lrecl=32767; data _null_; file sascode; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '/**'; put '@file mp_jsonout.sas'; put '@brief Writes JSON in SASjs format to a fileref'; put '@details This macro can be used to OPEN a JSON stream and send one or more'; put 'tables as arrays of rows, where each row can be an object or a nested array.'; put 'There are two engines available - DATASTEP or PROCJSON.'; put 'PROC JSON is fast but will produce errs like the ones below if'; put 'special chars are encountered.'; put '> (ERR)OR: Some code points did not transcode.'; put '> An object or array close is not valid at this point in the JSON text.'; put '> Date value out of range'; put 'If this happens, try running with ENGINE=DATASTEP.'; put 'The DATASTEP engine is used to handle special SAS missing numerics, and'; put 'can also convert entire datasets to formatted values. Output JSON is always'; put 'in UTF-8.'; put 'Usage:'; put 'filename tmp temp;'; put 'data class; set sashelp.class;run;'; put '%mp_jsonout(OPEN,jref=tmp)'; put '%mp_jsonout(OBJ,class,jref=tmp)'; put '%mp_jsonout(OBJ,class,dslabel=class2,jref=tmp,showmeta=Y)'; put '%mp_jsonout(CLOSE,jref=tmp)'; put 'data _null_;'; put 'infile tmp;'; put 'input;putlog _infile_;'; put 'run;'; put 'If you are building web apps with SAS then you are strongly encouraged to use'; put 'the mX_createwebservice macros in combination with the'; put '[sasjs adapter](https://github.com/sasjs/adapter).'; put 'For more information see https://sasjs.io'; put '@param [in] action Valid values:'; put '@li OPEN - opens the JSON'; put '@li OBJ - sends a table with each row as an object'; put '@li ARR - sends a table with each row in an array'; put '@li CLOSE - closes the JSON'; put '@param [in] ds The dataset to send. Must be a work table.'; put '@param [out] jref= (_webout) The fileref to which to send the JSON'; put '@param [out] dslabel= The name to give the table in the exported JSON'; put '@param [in] fmt= (Y) Whether to keep (Y) or strip (N) formats from the table'; put '@param [in] engine= (DATASTEP) Which engine to use to send the JSON. Options:'; put '@li PROCJSON (default)'; put '@li DATASTEP (more reliable when data has non standard characters)'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to JSON'; put '

Related Files

'; put '@li mp_ds2fmtds.sas'; put '@version 9.2'; put '@author Allan Bowe'; put '@source https://github.com/sasjs/core'; put '**/'; put '%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y'; put ',engine=DATASTEP'; put ',missing=NULL'; put ',showmeta=N'; put ',maxobs=MAX'; put ')/*/STORE SOURCE*/;'; put '%local tempds colinfo fmtds i numcols numobs stmt_obs lastobs optval'; put 'tmpds1 tmpds2 tmpds3 tmpds4;'; put '%let numcols=0;'; put '%if &maxobs ne MAX %then %let stmt_obs=%str(if _n_>&maxobs then stop;);'; put '%if &action=OPEN %then %do;'; put 'options nobomfile;'; put 'data _null_;file &jref encoding=''utf-8'' lrecl=200;'; put 'put ''{"PROCESSED_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''"'';'; put 'run;'; put '%end;'; put '%else %if (&action=ARR or &action=OBJ) %then %do;'; put '/* force variable names to always be uppercase in the JSON */'; put 'options validvarname=upcase;'; put '/* To avoid issues with _webout on EBI - such as encoding diffs and truncation'; put '(https://support.sas.com/kb/49/325.html) we use temporary files */'; put 'filename _sjs1 temp lrecl=200 ;'; put 'data _null_; file _sjs1 encoding=''utf-8'';'; put 'put ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":";'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjs1 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs1 clear;'; put '/* grab col defs */'; put 'proc contents noprint data=&ds'; put 'out=_data_(keep=name type length format formatl formatd varnum label);'; put 'run;'; put '%let colinfo=%scan(&syslast,2,.);'; put 'proc sort data=&colinfo;'; put 'by varnum;'; put 'run;'; put '/* move meta to mac vars */'; put 'data &colinfo;'; put 'if _n_=1 then call symputx(''numcols'',nobs,''l'');'; put 'set &colinfo end=last nobs=nobs;'; put 'name=upcase(name);'; put '/* fix formats */'; put 'if type=2 or type=6 then do;'; put 'typelong=''char'';'; put 'length fmt $49.;'; put 'if format='''' then fmt=cats(''$'',length,''.'');'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else fmt=cats(format,formatl,''.'');'; put 'end;'; put 'else do;'; put 'typelong=''num'';'; put 'if format='''' then fmt=''best.'';'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else if formatd=0 then fmt=cats(format,formatl,''.'');'; put 'else fmt=cats(format,formatl,''.'',formatd);'; put 'end;'; put '/* 32 char unique name */'; put 'newname=''sasjs''!!substr(cats(put(md5(name),$hex32.)),1,27);'; put 'call symputx(cats(''name'',_n_),name,''l'');'; put 'call symputx(cats(''newname'',_n_),newname,''l'');'; put 'call symputx(cats(''length'',_n_),length,''l'');'; put 'call symputx(cats(''fmt'',_n_),fmt,''l'');'; put 'call symputx(cats(''type'',_n_),type,''l'');'; put 'call symputx(cats(''typelong'',_n_),typelong,''l'');'; put 'call symputx(cats(''label'',_n_),coalescec(label,name),''l'');'; put '/* overwritten when fmt=Y and a custom format exists in catalog */'; put 'if typelong=''num'' then call symputx(cats(''fmtlen'',_n_),200,''l'');'; put 'else call symputx(cats(''fmtlen'',_n_),min(32767,ceil((length+10)*1.5)),''l'');'; put 'run;'; put '%let tempds=%substr(_%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql;'; put 'select count(*) into: lastobs from &ds;'; put '%if &maxobs ne MAX %then %let lastobs=%sysfunc(min(&lastobs,&maxobs));'; put '%if &engine=PROCJSON %then %do;'; put '%if &missing=STRING %then %do;'; put '%put &sysmacroname: Special Missings not supported in proc json.;'; put '%put &sysmacroname: Switching to DATASTEP engine;'; put '%goto datastep;'; put '%end;'; put 'data &tempds;'; put 'set &ds;'; put '&stmt_obs;'; put '%if &fmt=N %then format _numeric_ best32.;;'; put '/* PRETTY is necessary to avoid line truncation in large files */'; put 'filename _sjs2 temp lrecl=131068 encoding=''utf-8'';'; put 'proc json out=_sjs2 pretty'; put '%if &action=ARR %then nokeys ;'; put ';export &tempds / nosastags fmtnumeric;'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs2 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs2 clear;'; put '%end;'; put '%else %if &engine=DATASTEP %then %do;'; put '%datastep:'; put '%if %sysfunc(exist(&ds)) ne 1 & %sysfunc(exist(&ds,VIEW)) ne 1'; put '%then %do;'; put '%put &sysmacroname: &ds NOT FOUND!!!;'; put '%return;'; put '%end;'; put '%if &fmt=Y %then %do;'; put '/**'; put '* Extract format definitions'; put '* First, by getting library locations from dictionary.formats'; put '* Then, by exporting the width using proc format'; put '* Cannot use maxw from sashelp.vformat as not always populated'; put '* Cannot use fmtinfo() as not supported in all flavours'; put '*/'; put '%let tmpds1=%substr(fmtsum%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds2=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds3=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds4=%substr(col%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql noprint;'; put 'create table &tmpds1 as'; put 'select cats(libname,''.'',memname) as FMTCAT,'; put 'FMTNAME'; put 'from dictionary.formats'; put 'where fmttype=''F'' and libname is not null'; put 'and fmtname in (select format from &colinfo where format is not null)'; put 'order by 1;'; put 'create table &tmpds2('; put 'FMTNAME char(32),'; put 'LENGTH num'; put ');'; put '%local catlist cat fmtlist i;'; put 'select distinct fmtcat into: catlist separated by '' '' from &tmpds1;'; put '%do i=1 %to %sysfunc(countw(&catlist,%str( )));'; put '%let cat=%scan(&catlist,&i,%str( ));'; put 'proc sql;'; put 'select distinct fmtname into: fmtlist separated by '' '''; put 'from &tmpds1 where fmtcat="&cat";'; put 'proc format lib=&cat cntlout=&tmpds3(keep=fmtname length);'; put 'select &fmtlist;'; put 'run;'; put 'proc sql;'; put 'insert into &tmpds2 select distinct fmtname,length from &tmpds3;'; put '%end;'; put 'proc sql;'; put 'create table &tmpds4 as'; put 'select a.*, b.length as MAXW'; put 'from &colinfo a'; put 'left join &tmpds2 b'; put 'on cats(a.format)=cats(upcase(b.fmtname))'; put 'order by a.varnum;'; put 'data _null_;'; put 'set &tmpds4;'; put 'if not missing(maxw);'; put 'call symputx('; put 'cats(''fmtlen'',_n_),'; put '/* vars need extra padding due to JSON escaping of special chars */'; put 'min(32767,ceil((max(length,maxw)+10)*1.5))'; put ',''l'''; put ');'; put 'run;'; put '/* configure varlenchk - as we are explicitly shortening the variables */'; put '%let optval=%sysfunc(getoption(varlenchk));'; put 'options varlenchk=NOWARN;'; put 'data _data_(compress=char);'; put '/* shorten the new vars */'; put 'length'; put '%do i=1 %to &numcols;'; put '&&name&i $&&fmtlen&i'; put '%end;'; put ';'; put '/* rename on entry */'; put 'set &ds(rename=('; put '%do i=1 %to &numcols;'; put '&&name&i=&&newname&i'; put '%end;'; put '));'; put '&stmt_obs;'; put 'drop'; put '%do i=1 %to &numcols;'; put '&&newname&i'; put '%end;'; put ';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=num %then %do;'; put '&&name&i=cats(put(&&newname&i,&&fmt&i));'; put '%end;'; put '%else %do;'; put '&&name&i=put(&&newname&i,&&fmt&i);'; put '%end;'; put '%end;'; put 'if _error_ then do;'; put 'call symputx(''syscc'',1012);'; put 'stop;'; put 'end;'; put 'run;'; put '%let fmtds=&syslast;'; put 'options varlenchk=&optval;'; put '%end;'; put 'proc format; /* credit yabwon for special null removal */'; put 'value bart (default=40)'; put '%if &missing=NULL %then %do;'; put '._ - .z = null'; put '%end;'; put '%else %do;'; put '._ = [quote()]'; put '. = null'; put '.a - .z = [quote()]'; put '%end;'; put 'other = [best.];'; put 'data &tempds;'; put 'attrib _all_ label='''';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'length &&name&i $&&fmtlen&i...;'; put 'format &&name&i $&&fmtlen&i...;'; put '%end;'; put '%end;'; put '%if &fmt=Y %then %do;'; put 'set &fmtds;'; put '%end;'; put '%else %do;'; put 'set &ds;'; put '%end;'; put '&stmt_obs;'; put 'format _numeric_ bart.;'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'if findc(&&name&i,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put '&&name&i=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,&&name&i)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else &&name&i=quote(cats(&&name&i));'; put '%end;'; put '%end;'; put 'run;'; put 'filename _sjs3 temp lrecl=131068 ;'; put 'data _null_;'; put 'file _sjs3 encoding=''utf-8'';'; put 'if _n_=1 then put "[";'; put 'set &tempds;'; put 'if _n_>1 then put "," @; put'; put '%if &action=ARR %then "[" ; %else "{" ;'; put '%do i=1 %to &numcols;'; put '%if &i>1 %then "," ;'; put '%if &action=OBJ %then """&&name&i"":" ;'; put '"&&name&i"n /* name literal for reserved variable names */'; put '%end;'; put '%if &action=ARR %then "]" ; %else "}" ; ;'; put '/* close out the table */'; put 'data _null_;'; put 'file _sjs3 mod encoding=''utf-8'';'; put 'put '']'';'; put 'run;'; put 'data _null_;'; put 'infile _sjs3 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs3 clear;'; put '%end;'; put 'proc sql;'; put 'drop table &colinfo, &tempds;'; put '%if %substr(&showmeta,1,1)=Y %then %do;'; put 'filename _sjs4 temp lrecl=131068 encoding=''utf-8'';'; put 'data _null_;'; put 'file _sjs4;'; put 'length label $350;'; put 'put ", ""$%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":{""vars"":{";'; put 'do i=1 to &numcols;'; put 'name=quote(trim(symget(cats(''name'',i))));'; put 'format=quote(trim(symget(cats(''fmt'',i))));'; put 'label=quote(prxchange(''s/\\/\\\\/'',-1,trim(symget(cats(''label'',i)))));'; put 'length=quote(trim(symget(cats(''length'',i))));'; put 'type=quote(trim(symget(cats(''typelong'',i))));'; put 'if i>1 then put "," @@;'; put 'put name '':{"format":'' format '',"label":'' label'; put ''',"length":'' length '',"type":'' type ''}'';'; put 'end;'; put 'put ''}}'';'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs4 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs4 clear;'; put '%end;'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put 'data _null_; file &jref encoding=''utf-8'' mod ;'; put 'put "}";'; put 'run;'; put '%end;'; put '%mend mp_jsonout;'; put '/**'; put '@file mm_webout.sas'; put '@brief Send data to/from SAS Stored Processes'; put '@details This macro should be added to the start of each Stored Process,'; put '**immediately** followed by a call to:'; put '%mm_webout(FETCH)'; put 'This will read all the input data and create same-named SAS datasets in the'; put 'WORK library. You can then insert your code, and send data back using the'; put 'following syntax:'; put 'data some datasets; * make some data ;'; put 'retain some columns;'; put 'run;'; put '%mm_webout(OPEN)'; put '%mm_webout(ARR,some) * Array format, fast, suitable for large tables ;'; put '%mm_webout(OBJ,datasets) * Object format, easier to work with ;'; put 'Finally, wrap everything up send some helpful system variables too'; put '%mm_webout(CLOSE)'; put '@param [in] action Either FETCH, OPEN, ARR, OBJ or CLOSE'; put '@param [in] ds The dataset to send back to the frontend'; put '@param [out] dslabel= Value to use instead of table name for sending to JSON'; put '@param [in] fmt= (N) Setting Y converts all vars to their formatted values'; put '@param [out] fref= (_webout) The fileref to which to write the JSON'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] workobs= (0) When set to a positive integer, will create a new'; put 'output object (WORK) which contains this number of observations from all'; put 'tables in the WORK library.'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to output JSON'; put '

SAS Macros

'; put '@li mp_jsonout.sas'; put '

Related Macros

'; put '@li ms_webout.sas'; put '@li mv_webout.sas'; put '@version 9.3'; put '@author Allan Bowe'; put '**/'; put '%macro mm_webout(action,ds,dslabel=,fref=_webout,fmt=N,missing=NULL'; put ',showmeta=N,maxobs=MAX,workobs=0'; put ');'; put '%global _webin_file_count _webin_fileref1 _webin_name1 _program _debug'; put 'sasjs_tables;'; put '%local i tempds jsonengine;'; put '/* see https://github.com/sasjs/core/issues/41 */'; put '%if "%upcase(&SYSENCODING)" ne "UTF-8" %then %let jsonengine=PROCJSON;'; put '%else %let jsonengine=DATASTEP;'; put '%if &action=FETCH %then %do;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'options mprint notes mprintnest;'; put '%end;'; put '%let _webin_file_count=%eval(&_webin_file_count+0);'; put '/* now read in the data */'; put '%do i=1 %to &_webin_file_count;'; put '%if &_webin_file_count=1 %then %do;'; put '%let _webin_fileref1=&_webin_fileref;'; put '%let _webin_name1=&_webin_name;'; put '%end;'; put 'data _null_;'; put 'infile &&_webin_fileref&i termstr=crlf;'; put 'input;'; put 'call symputx(''input_statement'',_infile_);'; put 'putlog "&&_webin_name&i input statement: " _infile_;'; put 'stop;'; put 'data &&_webin_name&i;'; put 'infile &&_webin_fileref&i firstobs=2 dsd termstr=crlf encoding=''utf-8'';'; put 'input &input_statement;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'if _n_<20 then putlog _infile_;'; put '%end;'; put 'run;'; put '%let sasjs_tables=&sasjs_tables &&_webin_name&i;'; put '%end;'; put '%end;'; put '%else %if &action=OPEN %then %do;'; put '/* fix encoding */'; put 'OPTIONS NOBOMFILE;'; put '/**'; put '* check xengine type to avoid the below err message:'; put '* > Function is only valid for filerefs using the CACHE access method.'; put '*/'; put 'data _null_;'; put 'set sashelp.vextfl(where=(fileref="_WEBOUT"));'; put 'if xengine=''STREAM'' then do;'; put 'rc=stpsrv_header(''Content-type'',"text/html; encoding=utf-8");'; put 'end;'; put 'run;'; put '/* setup json */'; put 'data _null_;file &fref encoding=''utf-8'';'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutBEGIN<<'';'; put '%end;'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'run;'; put '%end;'; put '%else %if &action=ARR or &action=OBJ %then %do;'; put '%mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt,jref=&fref'; put ',engine=&jsonengine,missing=&missing,showmeta=&showmeta,maxobs=&maxobs'; put ')'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put '/* To avoid issues with _webout on EBI we use a temporary file */'; put 'filename _sjsref temp lrecl=131068;'; put '%if %str(&workobs) > 0 %then %do;'; put '/* if debug mode, send back first XX records of each work table also */'; put 'data;run;%let tempds=%scan(&syslast,2,.);'; put 'ods output Members=&tempds;'; put 'proc datasets library=WORK memtype=data;'; put '%local wtcnt;%let wtcnt=0;'; put 'data _null_;'; put 'set &tempds;'; put 'if not (upcase(name) =:"DATA"); /* ignore temp datasets */'; put 'i+1;'; put 'call symputx(cats(''wt'',i),name,''l'');'; put 'call symputx(''wtcnt'',i,''l'');'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put ",""WORK"":{";'; put '%do i=1 %to &wtcnt;'; put '%let wt=&&wt&i;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'dsid=open("WORK.&wt",''is'');'; put 'nlobs=attrn(dsid,''NLOBS'');'; put 'nvars=attrn(dsid,''NVARS'');'; put 'rc=close(dsid);'; put 'if &i>1 then put '',''@;'; put 'put " ""&wt"" : {";'; put 'put ''"nlobs":'' nlobs;'; put 'put '',"nvars":'' nvars;'; put '%mp_jsonout(OBJ,&wt,jref=_sjsref,dslabel=first10rows,showmeta=Y'; put ',maxobs=&workobs'; put ')'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put '%end;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put 'run;'; put '%end;'; put '/* close off json */'; put 'data _null_;file _sjsref mod encoding=''utf-8'';'; put 'length SYSPROCESSNAME syserrortext syswarningtext autoexec $512;'; put 'put ",""_DEBUG"" : ""&_debug"" ";'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'autoexec=quote(urlencode(trim(getoption(''autoexec''))));'; put 'put '',"AUTOEXEC" : '' autoexec;'; put 'put ",""MF_GETUSER"" : ""%mf_getuser()"" ";'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'put ",""SYSENCODING"" : ""&sysencoding"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSPROCESSID"" : ""&SYSPROCESSID"" ";'; put 'put ",""SYSPROCESSMODE"" : ""&SYSPROCESSMODE"" ";'; put 'SYSPROCESSNAME=quote(urlencode(cats(SYSPROCESSNAME)));'; put 'put ",""SYSPROCESSNAME"" : " SYSPROCESSNAME;'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put '',"SYSWARNINGTEXT" : '' syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'length memsize $32;'; put 'memsize="%sysfunc(INPUTN(%sysfunc(getoption(memsize)), best.),sizekmg.)";'; put 'memsize=quote(cats(memsize));'; put 'put '',"MEMSIZE" : '' memsize;'; put 'put "}" @;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutEND<<'';'; put '%end;'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjsref lrecl=1 recfm=n;'; put 'file &fref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjsref clear;'; put '%end;'; put '%mend mm_webout;'; put '%macro webout(action,ds,dslabel=,fmt=,missing=NULL,showmeta=NO,maxobs=MAX);'; put '%mm_webout(&action,ds=&ds,dslabel=&dslabel,fmt=&fmt'; put ',missing=&missing'; put ',showmeta=&showmeta'; put ',maxobs=&maxobs'; put ') %mend;'; put '/* provide additional debug info */'; put '%global _program;'; put '%put &=syscc;'; put '%put user=%mf_getuser();'; put '%put pgm=&_program;'; put '%put timestamp=%sysfunc(datetime(),datetime19.);'; put '* Service Variables start;'; put '* Service Variables end;'; put '* SAS Macros start;'; put '%macro mf_getapploc(pgm);'; put '%if "&pgm"="" %then %do;'; put '%if %symexist(_program) %then %let pgm=&_program;'; put '%else %do;'; put '%put &sysmacroname: No value provided and no _program variable available;'; put '%return;'; put '%end;'; put '%end;'; put '%local root;'; put '/**'; put '* First check we are not in the tests/macros folder (which has no subfolders)'; put '* or specifically in the testsetup or testteardown services'; put '*/'; put '%if %index(&pgm,/tests/macros/)'; put 'or %index(&pgm,/tests/testsetup)'; put 'or %index(&pgm,/tests/testteardown)'; put '%then %do;'; put '%let root=%substr(&pgm,1,%index(&pgm,/tests)-1);'; put '&root'; put '%return;'; put '%end;'; put '/**'; put '* Next, move up two levels to avoid matches on subfolder or service name'; put '*/'; put '%let root=%substr(&pgm,1,%length(&pgm)-%length(%scan(&pgm,-1,/))-1);'; put '%let root=%substr(&root,1,%length(&root)-%length(%scan(&root,-1,/))-1);'; put '%if %index(&root,/tests/) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/tests/)-1);'; put '%end;'; put '%else %if %index(&root,/services) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/services)-1);'; put '%end;'; put '%else %if %index(&root,/jobs) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/jobs)-1);'; put '%end;'; put '%else %put &sysmacroname: Could not find an app location from &pgm;'; put '&root'; put '%mend mf_getapploc ;'; put '%macro mf_getuniquefileref(prefix=_,maxtries=1000,lrecl=32767);'; put '%local rc fname;'; put '%if &prefix=0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%end;'; put '%else %do;'; put '%local x len;'; put '%let len=%eval(8-%length(&prefix));'; put '%let x=0;'; put '%do x=0 %to &maxtries;'; put '%let fname=&prefix%substr(%sysfunc(ranuni(0)),3,&len);'; put '%if %sysfunc(fileref(&fname)) > 0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%return;'; put '%end;'; put '%end;'; put '%put unable to find available fileref after &maxtries attempts;'; put '%end;'; put '%mend mf_getuniquefileref;'; put '%macro mp_abort(mac=mp_abort.sas, type=, msg=, iftrue=%str(1=1)'; put ', errds=work.mp_abort_errds'; put ', mode=REGULAR'; put ')/*/STORE SOURCE*/;'; put '%global sysprocessmode sysprocessname sasjs_stpsrv_header_loc sasjsprocessmode;'; put '%local fref fid i;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%put NOTE: /// mp_abort macro executing //;'; put '%if %length(&mac)>0 %then %put NOTE- called by &mac;'; put '%put NOTE - &msg;'; put '%if %symexist(_SYSINCLUDEFILEDEVICE)'; put '/* abort cancel FILE does not restart outside the INCLUDE on Viya 3.5 */'; put 'and %superq(SYSPROCESSNAME) ne %str(Compute Server)'; put '%then %do;'; put '%if "*&_SYSINCLUDEFILEDEVICE*" ne "**" %then %do;'; put 'data &errds;'; put 'iftrue=''1=1'';'; put 'length mac $100 msg $5000;'; put 'mac=symget(''mac'');'; put 'msg=symget(''msg'');'; put 'run;'; put 'data _null_;'; put 'abort cancel FILE;'; put 'run;'; put '%return;'; put '%end;'; put '%end;'; put '/* Web App Context */'; put '%if %symexist(_PROGRAM)'; put 'or %superq(SYSPROCESSNAME) = %str(Compute Server)'; put 'or &mode=INCLUDE'; put '%then %do;'; put 'options obs=max replace mprint;'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5"'; put '%then %do;'; put 'options nosyntaxcheck;'; put '%end;'; put '%if &mode=INCLUDE %then %do;'; put '%if %sysfunc(exist(&errds))=1 %then %do;'; put 'data _null_;'; put 'set &errds;'; put 'call symputx(''iftrue'',iftrue,''l'');'; put 'call symputx(''mac'',mac,''l'');'; put 'call symputx(''msg'',msg,''l'');'; put 'putlog (_all_)(=);'; put 'run;'; put '%if (&iftrue)=0 %then %return;'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: No include errors found;'; put '%return;'; put '%end;'; put '%end;'; put '/* extract log errs / warns, if exist */'; put '%local logloc logline;'; put '%global logmsg; /* capture global messages */'; put '%if %symexist(SYSPRINTTOLOG) %then %let logloc=&SYSPRINTTOLOG;'; put '%else %let logloc=%qsysfunc(getoption(LOG));'; put 'proc printto log=log;run;'; put '%let logline=0;'; put '%if %length(&logloc)>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input; putlog _infile_;'; put 'i=1;'; put 'retain logonce 0;'; put 'if ('; put '_infile_=:"%str(WARN)ING" or _infile_=:"%str(ERR)OR"'; put ') and logonce=0 then'; put 'do;'; put 'call symputx(''logline'',_n_);'; put 'logonce+1;'; put 'end;'; put 'run;'; put '/* capture log including lines BEFORE the err */'; put '%if &logline>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input;'; put 'i=1;'; put 'stoploop=0;'; put 'if _n_ ge &logline-15 and stoploop=0 then do until (i>22);'; put 'call symputx(''logmsg'',catx(''\n'',symget(''logmsg''),_infile_));'; put 'input;'; put 'i+1;'; put 'stoploop=1;'; put 'end;'; put 'if stoploop=1 then stop;'; put 'run;'; put '%end;'; put '%end;'; put '%if %symexist(SYS_JES_JOB_URI) %then %do;'; put '/* setup webout for Viya */'; put 'options nobomfile;'; put '%if "X&SYS_JES_JOB_URI.X"="XX" %then %do;'; put 'filename _webout temp lrecl=999999 mod;'; put '%end;'; put '%else %do;'; put 'filename _webout filesrvc parenturi="&SYS_JES_JOB_URI"'; put 'name="_webout.json" lrecl=999999 mod;'; put '%end;'; put '%end;'; put '%else %if %sysfunc(filename(fref,&sasjs_stpsrv_header_loc))=0 %then %do;'; put 'options nobomfile;'; put '/* set up http header for SASjs Server */'; put '%let fid=%sysfunc(fopen(&fref,A));'; put '%if &fid=0 %then %do;'; put '%put %str(ERR)OR: %sysfunc(sysmsg());'; put '%return;'; put '%end;'; put '%let rc=%sysfunc(fput(&fid,%str(Content-Type: application/json)));'; put '%let rc=%sysfunc(fwrite(&fid));'; put '%let rc=%sysfunc(fclose(&fid));'; put '%let rc=%sysfunc(filename(&fref));'; put '%end;'; put '/* send response in SASjs JSON format */'; put 'data _null_;'; put 'file _webout mod lrecl=32000 encoding=''utf-8'';'; put 'length msg syswarningtext syserrortext $32767 mode $10 ;'; put 'sasdatetime=datetime();'; put 'msg=symget(''msg'');'; put '%if &logline>0 %then %do;'; put 'msg=cats(msg,''\n\nLog Extract:\n'',symget(''logmsg''));'; put '%end;'; put '/* escape the escapes */'; put 'msg=tranwrd(msg,''\'',''\\'');'; put '/* escape the quotes */'; put 'msg=tranwrd(msg,''"'',''\"'');'; put '/* ditch the CRLFs as chrome complains */'; put 'msg=compress(msg,,''kw'');'; put '/* quote without quoting the quotes (which are escaped instead) */'; put 'msg=cats(''"'',msg,''"'');'; put 'if symexist(''_debug'') then debug=quote(trim(symget(''_debug'')));'; put 'else debug=''""'';'; put 'if symget(''sasjsprocessmode'')=''Stored Program'' then mode=''SASJS'';'; put 'if mode ne ''SASJS'' then put ''>>weboutBEGIN<<'';'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'put '',"sasjsAbort" : [{'';'; put 'put '' "MSG":'' msg ;'; put 'put '' ,"MAC": "'' "&mac" ''"}]'';'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'put '',"_DEBUG":'' debug ;'; put 'if symexist(''_metauser'') then do;'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put 'end;'; put 'if symexist(''SYS_JES_JOB_URI'') then do;'; put 'SYS_JES_JOB_URI=quote(trim(symget(''SYS_JES_JOB_URI'')));'; put 'put '',"SYS_JES_JOB_URI": '' SYS_JES_JOB_URI;'; put 'end;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put ",""SYSWARNINGTEXT"" : " syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'put "}" ;'; put 'if mode ne ''SASJS'' then put ''>>weboutEND<<'';'; put 'run;'; put '%put _all_;'; put '%if "&sysprocessmode " = "SAS Stored Process Server " %then %do;'; put 'data _null_;'; put 'putlog ''stpsrvset program err and syscc'';'; put 'rc=stpsrvset(''program error'', 0);'; put 'call symputx("syscc",0,"g");'; put 'run;'; put '%if &sysscp=WIN'; put 'and 1=0 /* deprecating this logic until we figure out a consistent abort */'; put 'and "%substr(%str(&sysvlong ),1,8)"="9.04.01M"'; put 'and "%substr(%str(&sysvlong ),9,1)">"5" %then %do;'; put '/* skip approach (below) does not work in windows m6+ envs */'; put 'endsas;'; put '%end;'; put '%else %do;'; put '/**'; put '* endsas kills 9.4m3 deployments by orphaning multibridges.'; put '* Abort variants are ungraceful (non zero return code)'; put '* This approach lets SAS run silently until the end :-)'; put '* Caution - fails when called within a %include within a macro'; put '* Use mp_include() to handle this.'; put '*/'; put 'filename skip temp;'; put 'data _null_;'; put 'file skip;'; put 'put ''%macro skip();'';'; put 'comment ''%mend skip; -> fix lint '';'; put 'put ''%macro skippy();'';'; put 'comment ''%mend skippy; -> fix lint '';'; put 'run;'; put '%inc skip;'; put '%end;'; put '%end;'; put '%else %if "&sysprocessmode " = "SAS Compute Server " %then %do;'; put '/* endsas kills the session making it harder to fetch results */'; put 'data _null_;'; put 'syswarningtext=symget(''syswarningtext'');'; put 'syserrortext=symget(''syserrortext'');'; put 'abort_msg=symget(''msg'');'; put 'syscc=symget(''syscc'');'; put 'sysuserid=symget(''sysuserid'');'; put 'iftrue=symget(''iftrue'');'; put 'put (_all_)(/=);'; put 'call symputx(''syscc'',0);'; put 'abort cancel nolist;'; put 'run;'; put '%end;'; put '%else %do;'; put '%abort cancel;'; put '%end;'; put '%end;'; put '%else %do;'; put '%put _all_;'; put '%abort cancel;'; put '%end;'; put '%mend mp_abort;'; put '/** @endcond */'; put '%macro mm_getstpcode('; put 'tree=/User Folders/sasdemo/somestp'; put ',name='; put ',outloc=0'; put ',outref=0'; put ',mDebug=1'; put ',showlog=NO'; put ');'; put '%local mD;'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing &sysmacroname..sas;'; put '%&mD.put _local_;'; put '%if %length(&name)>0 %then %let name=/&name;'; put '/* first, check if STP exists */'; put '%local tsuri;'; put '%let tsuri=stopifempty ;'; put 'data _null_;'; put 'format type uri tsuri value $200.;'; put 'call missing (of _all_);'; put 'path="&tree&name(StoredProcess)";'; put '/* first, find the STP ID */'; put 'if metadata_pathobj("",path,"StoredProcess",type,uri)>0 then do;'; put '/* get sourcecode */'; put 'cnt=1;'; put 'do while (metadata_getnasn(uri,"Notes",cnt,tsuri)>0);'; put 'rc=metadata_getattr(tsuri,"Name",value);'; put '&mD.put tsuri= value=;'; put 'if value="SourceCode" then do;'; put '/* found it! */'; put 'rc=metadata_getattr(tsuri,"Id",value);'; put 'call symputx(''tsuri'',value,''l'');'; put 'stop;'; put 'end;'; put 'cnt+1;'; put 'end;'; put 'end;'; put 'else put (_all_)(=);'; put 'run;'; put '%mp_abort(iftrue= (&tsuri=stopifempty)'; put ',mac=mm_getstpcode'; put ',msg=%str(&tree&name.(StoredProcess) not found!)'; put ')'; put '/**'; put '* Now we can extract the textstore'; put '*/'; put 'filename __getdoc temp lrecl=10000000;'; put 'proc metadata'; put 'in="$METAREPOSITORY'; put ''; put 'SAS1"'; put 'out=__getdoc ;'; put 'run;'; put '/* find the beginning of the text */'; put '%local start;'; put 'data _null_;'; put 'infile __getdoc lrecl=10000;'; put 'input;'; put 'start=index(_infile_,''StoredText="'');'; put 'if start then do;'; put 'call symputx("start",start+11);'; put '*putlog ''"'' _infile_ ''"'';'; put 'end;'; put 'stop;'; put '%local outeng;'; put '%if "&outloc"="0" %then %let outeng=TEMP;'; put '%else %let outeng="&outloc";'; put '%local fref;'; put '%if &outref=0 %then %let fref=%mf_getuniquefileref();'; put '%else %let fref=&outref;'; put '/* read the content, byte by byte, resolving escaped chars */'; put 'filename &fref &outeng lrecl=100000;'; put 'data _null_;'; put 'length filein 8 fileid 8;'; put 'filein = fopen("__getdoc","I",1,"B");'; put 'fileid = fopen("&fref","O",1,"B");'; put 'rec = "20"x;'; put 'length entity $6;'; put 'do while(fread(filein)=0);'; put 'x+1;'; put 'if x>&start then do;'; put 'rc = fget(filein,rec,1);'; put 'if rec=''"'' then leave;'; put 'else if rec="&" then do;'; put 'entity=rec;'; put 'do until (rec=";");'; put 'if fread(filein) ne 0 then goto getout;'; put 'rc = fget(filein,rec,1);'; put 'entity=cats(entity,rec);'; put 'end;'; put 'select (entity);'; put 'when (''&'' ) rec=''&'' ;'; put 'when (''<'' ) rec=''<'' ;'; put 'when (''>'' ) rec=''>'' ;'; put 'when (''''') rec="''" ;'; put 'when (''"'') rec=''"'' ;'; put 'when ('' '') rec=''0A''x;'; put 'when ('' '') rec=''0D''x;'; put 'when (''$'' ) rec=''$'' ;'; put 'when ('' '') rec=''09''x;'; put 'otherwise putlog "%str(WARN)ING: missing value for " entity=;'; put 'end;'; put 'rc =fput(fileid, substr(rec,1,1));'; put 'rc =fwrite(fileid);'; put 'end;'; put 'else do;'; put 'rc =fput(fileid,rec);'; put 'rc =fwrite(fileid);'; put 'end;'; put 'end;'; put 'end;'; put 'getout:'; put 'rc=fclose(filein);'; put 'rc=fclose(fileid);'; put 'run;'; put '%if &showlog=YES %then %do;'; put 'data _null_;'; put 'infile &fref lrecl=32767 end=last;'; put 'input;'; put 'if _n_=1 then putlog ''>>stpcodeBEGIN<<'';'; put 'putlog _infile_;'; put 'if last then putlog ''>>stpcodeEND<<'';'; put 'run;'; put '%end;'; put 'filename __getdoc clear;'; put '%if &outref=0 %then %do;'; put 'filename &fref clear;'; put '%end;'; put '%mend mm_getstpcode;'; put '%macro dc_getsettings();'; put '%global _program;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(syscc=&syscc on entry (&syswarningtext &syserrortext))'; put ')'; put '%if %length(&_PROGRAM)>1 %then %let root=&_program;'; put '%else %do;'; put '%global _metauser;'; put '%let _metauser=&sysuserid;'; put '/* to mimic a "real" _program we need to give a dummy role and stp name */'; put '%let root=/dummyRole/dummyName;'; put '%end;'; put '/* the DC precode is stored in the Admin folder in the root of'; put 'the project. Lets find that root. */'; put '%put &=root;'; put '%let root=%mf_getapploc();'; put '%put &=root;'; put '/* Now we know the root location we can retrieve the params */'; put '%let temploc=%sysfunc(pathname(work))/settings.txt;'; put '%mm_getstpcode(tree=&root/services/public'; put ',name=Data_Controller_Settings'; put ',outloc=&temploc'; put ')'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Unable to run getstpcode)'; put ')'; put 'filename _getsets "&temploc" lrecl=2000;'; put '/*'; put 'Do not use mp_include here - this puts a copy in every service, which creates'; put 'compilation problems when calling services from mp_include'; put '*/'; put '%inc _getsets/source2;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Problem running &sysmacroname (&syswarningtext &syserrortext))'; put ')'; put '%mend dc_getsettings;'; put '%macro mf_fmtdttm('; put ')/*/STORE SOURCE*/;'; put '%if "&sysver"="9.2" or "&sysver"="9.3"'; put 'or ("&sysver"="9.4" and "%substr(&SYSVLONG,9,1)" le "3")'; put 'or "%substr(&sysver,1,1)"="4"'; put 'or "%substr(&sysver,1,1)"="5"'; put '%then %do;DATETIME19.3%end;'; put '%else %do;E8601DT26.6%end;'; put '%mend mf_fmtdttm;'; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '%macro mp_init(prefix=SASJS'; put ')/*/STORE SOURCE*/;'; put '%if %symexist(SASJS_PREFIX) %then %return; /* only run once */'; put '%global'; put 'SASJS_PREFIX /* the ONLY hard-coded global macro variable in SASjs */'; put '&prefix._FUNCTIONS /* used in mcf_init() to track core function compilation */'; put '&prefix._INIT_NUM /* initialisation time as numeric */'; put '&prefix._INIT_DTTM /* initialisation time in E8601DT26.6 format */'; put '&prefix.WORK /* avoid typing %sysfunc(pathname(work)) every time */'; put ';'; put '%let sasjs_prefix=&prefix;'; put 'data _null_;'; put 'dttm=datetime();'; put 'call symputx("&sasjs_prefix._init_num",dttm,''g'');'; put 'call symputx("&sasjs_prefix._init_dttm",put(dttm,E8601DT26.6),''g'');'; put 'call symputx("&sasjs_prefix.work",pathname(''WORK''),''g'');'; put 'run;'; put 'options'; put 'compress=CHAR /* default is none so ensure we have something! */'; put 'datastmtchk=ALLKEYWORDS /* protection from overwriting input datasets */'; put 'errorcheck=STRICT /* catch errs in libname/filename statements */'; put 'fmterr /* ensure err when a format cannot be found */'; put 'mergenoby=%str(ERR)OR /* throw err when a merge has no BY variables */'; put 'missing=. /* changing this can cause hard to detect errs */'; put 'noquotelenmax /* avoid warnings for long strings */'; put 'noreplace /* avoid overwriting permanent datasets */'; put 'ps=max /* reduce log size slightly */'; put 'ls=max /* reduce log even more and avoid word truncation */'; put 'validmemname=COMPATIBLE /* avoid special characters etc in table names */'; put 'validvarname=V7 /* avoid special characters etc in variable names */'; put 'varinitchk=%str(ERR)OR /* avoid data mistakes from variable name typos */'; put 'varlenchk=%str(ERR)OR /* fail hard if truncation (data loss) can result */'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5" %then %do;'; put 'noautocorrect /* disallow misspelled procedure names */'; put 'dsoptions=note2err /* undocumented - convert bad NOTEs to ERRs */'; put '%end;'; put ';'; put '%mend mp_init;'; put '%macro mpeinit(fetch=YES);'; put '%global mpeinit'; put 'mpeadmins /* group with unrestricted Meditor access */'; put 'mpelocapprovals /* location for landing and staging files */'; put 'mpelib /* location of configuration tables for DC */'; put 'dc_repo_users /* location of user / group metadata */'; put 'dc_licence_key /* extracted in dc_getsettings */'; put 'dc_activation_key /* extracted in dc_getsettings */'; put 'dc_locale /* extracted in dc_getsettings */'; put 'dc_dttmtfmt /* can be overridden in dc_getsettings */'; put '_debug'; put ';'; put '%if &mpeinit=1 %then %return;'; put '%else %let mpeinit=1;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(Problem on service startup (&syswarningtext &syserrortext))'; put ')'; put '%mp_init()'; put '%if &fetch=YES %then %do;'; put '%webout(FETCH)'; put '%end;'; put '%global _CLIENTNAME;'; put '%mp_abort(iftrue= (&_CLIENTNAME=SAS Enterprise Guide)'; put ',mac=&_program..sas'; put ',msg=%str(Data Controller is a web app and should not be executed from EG)'; put ')'; put 'options urlencoding=utf8 nobomfile lrecl=32767;'; put '%let perf=%sysfunc(datetime());'; put '%put perfdiff: 0;'; put '%let dc_locale=SYSTEM; /* default if not set */'; put '/**'; put '* E8601DT26.6 has widest database support - but not all SAS flavours can'; put '* handle it. Override in the settings STP if needed.'; put '*/'; put 'data _null_;'; put 'dc_dttmtfmt=''"%sysfunc(datetime(),''!!"%mf_fmtdttm()"!!'')"dt'';'; put 'call symputx(''dc_dttmtfmt'',dc_dttmtfmt);'; put 'put dc_dttmtfmt=;'; put 'run;'; put '%put &=dc_dttmtfmt;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc prior to dc_getsettings)'; put ')'; put '%dc_getsettings()'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc after dc_getsettings)'; put ')'; put 'data _null_;'; put 'set &DC_LIBREF..mpe_config(where=('; put 'var_scope="DC"'; put 'and &dc_dttmtfmt lt tx_to'; put 'and var_active=1'; put '));'; put 'call symputx(var_name,var_value,''G'');'; put 'putlog var_name "=" var_value;'; put 'run;'; put '%let mpelib=&dc_libref;'; put '%let mpeadmins=&dc_admin_group;'; put '%let mpelocapprovals=&dc_staging_area;'; put '%let dc_repo_users=&dc_repo_users;'; put '%if &dc_locale ne SYSTEM %then %do;'; put 'options locale=&dc_locale;'; put '%end;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(Problem during compilation or with STP precode (&syswarningtext))'; put ')'; put '%mend mpeinit;'; put '%macro mf_mval(var);'; put '%if %symexist(&var) %then %do;'; put '%superq(&var)'; put '%end;'; put '%mend mf_mval;'; put '%macro mf_trimstr(basestr,trimstr);'; put '%local baselen trimlen trimval;'; put '/* return if basestr is shorter than trimstr (or 0) */'; put '%let baselen=%length(%superq(basestr));'; put '%let trimlen=%length(%superq(trimstr));'; put '%if &baselen < &trimlen or &baselen=0 %then %return;'; put '/* obtain the characters from the end of basestr */'; put '%let trimval=%qsubstr(%superq(basestr)'; put ',%length(%superq(basestr))-&trimlen+1'; put ',&trimlen);'; put '/* compare and if matching, chop it off! */'; put '%if %superq(basestr)=%superq(trimstr) %then %do;'; put '%return;'; put '%end;'; put '%else %if %superq(trimval)=%superq(trimstr) %then %do;'; put '%qsubstr(%superq(basestr),1,%length(%superq(basestr))-&trimlen)'; put '%end;'; put '%else %do;'; put '&basestr'; put '%end;'; put '%mend mf_trimstr;'; put '%macro mf_getplatform(switch'; put ')/*/STORE SOURCE*/;'; put '%local a b c;'; put '%if &switch.NONE=NONE %then %do;'; put '%if %symexist(sasjsprocessmode) %then %do;'; put '%if &sasjsprocessmode=Stored Program %then %do;'; put 'SASJS'; put '%return;'; put '%end;'; put '%end;'; put '%if %symexist(sysprocessmode) %then %do;'; put '%if "&sysprocessmode"="SAS Object Server"'; put 'or "&sysprocessmode"= "SAS Compute Server" %then %do;'; put 'SASVIYA'; put '%end;'; put '%else %if "&sysprocessmode"="SAS Stored Process Server"'; put 'or "&sysprocessmode"="SAS Workspace Server"'; put '%then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if %symexist(_metaport) or %symexist(_metauser) %then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if &switch=SASSTUDIO %then %do;'; put '/* return the version of SAS Studio else 0 */'; put '%if %mf_mval(_CLIENTAPP)=%str(SAS Studio) %then %do;'; put '%let a=%mf_mval(_CLIENTVERSION);'; put '%let b=%scan(&a,1,.);'; put '%if %eval(&b >2) %then %do;'; put '&b'; put '%end;'; put '%else 0;'; put '%end;'; put '%else 0;'; put '%end;'; put '%else %if &switch=VIYARESTAPI %then %do;'; put '%mf_trimstr(%sysfunc(getoption(servicesbaseurl)),/)'; put '%end;'; put '%mend mf_getplatform;'; put '%macro mpeterm();'; put '%local oldloc;'; put 'data _null_;'; put 'if symexist(''SYSPRINTTOLOG'') then oldloc=symget(''SYSPRINTTOLOG'');'; put 'else oldloc=getoption(''LOG'');'; put 'if subpad(oldloc,1,1) not in (''"'',"''",'' '') then oldloc=quote(cats(oldloc));'; put 'call symputx(''oldloc'',oldloc,''l'');'; put 'run;'; put '%if %length(&oldloc)>0 %then %do;'; put 'proc printto log=log;'; put 'run;'; put 'data _null_;'; put 'infile &oldloc;'; put 'input; putlog _infile_;'; put 'run;'; put '%end;'; put '%if %sysfunc(exist(&dc_libref..mpe_requests)) and %mf_getplatform() ne SASVIYA'; put '%then %do;'; put 'data ;'; put 'if 0 then set &dc_libref..mpe_requests;'; put 'request_dttm=%sysfunc(datetime());'; put 'request_user="%mf_getuser()";'; put 'request_service="%scan(&_program,-2,/)/%scan(&_program,-1,/)";'; put 'request_params='''';'; put 'output;stop;'; put 'proc append base=&dc_libref..mpe_requests data=&syslast force nowarn;'; put 'run;'; put '%end;'; put '%mend mpeterm;'; put '%macro mm_getlibs('; put 'outds=work.mm_getLibs'; put ')/*/STORE SOURCE*/;'; put '/*'; put 'flags:'; put 'OMI_SUCCINCT (2048) Do not return attributes with null values.'; put 'OMI_GET_METADATA (256) Executes a GetMetadata call for each object that'; put 'is returned by the GetMetadataObjects method.'; put 'OMI_ALL_SIMPLE (8) Gets all of the attributes of the requested object.'; put '*/'; put 'data _null_;'; put 'flags=2048+256+8;'; put 'call symputx(''flags'',flags,''l'');'; put 'run;'; put '* use a temporary fileref to hold the response;'; put 'filename response temp;'; put '/* get list of libraries */'; put 'proc metadata in='; put ''''; put '$METAREPOSITORY'; put 'SASLibrary'; put ''; put 'SAS'; put '&flags'; put ''; put ''''; put 'out=response;'; put 'run;'; put '/* write the response to the log for debugging */'; put 'data _null_;'; put 'infile response lrecl=32767;'; put 'input;'; put 'put _infile_;'; put 'run;'; put '/* create an XML map to read the response */'; put 'filename sxlemap temp;'; put 'data _null_;'; put 'file sxlemap;'; put 'put '''';'; put 'put '''';'; put 'put ''//Objects/SASLibrary'';'; put 'put ''>17'';'; put 'put ''//Objects/SASLibrary/@Id'';'; put 'put ''256>'';'; put 'put ''//Objects/SASLibrary/@Name'';'; put 'put ''8'';'; put 'put ''//Objects/SASLibrary/@Libref'';'; put 'put ''>12'';'; put 'put ''//Objects/SASLibrary/@Engine'';'; put 'put ''
'';'; put 'run;'; put 'libname _XML_ xml xmlfileref=response xmlmap=sxlemap;'; put '/* sort the response by library name */'; put 'proc sort data=_XML_.saslibrary out=&outds;'; put 'by libraryname;'; put 'run;'; put '/* clear references */'; put 'filename sxlemap clear;'; put 'filename response clear;'; put 'libname _XML_ clear;'; put '%mend mm_getlibs;'; put '%macro mm_getrepos('; put 'outds=work.mm_getrepos'; put ')/*/STORE SOURCE*/;'; put '* use a temporary fileref to hold the response;'; put 'filename response temp;'; put '/* get list of libraries */'; put 'proc metadata in='; put '"1"'; put 'out=response;'; put 'run;'; put '/* write the response to the log for debugging */'; put '/*'; put 'data _null_;'; put 'infile response lrecl=1048576;'; put 'input;'; put 'put _infile_;'; put 'run;'; put '*/'; put '/* create an XML map to read the response */'; put 'filename sxlemap temp;'; put 'data _null_;'; put 'file sxlemap;'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository";'; put 'put "";'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Id";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Name";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Desc";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@DefaultNS";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@RepositoryType";'; put 'put "characterstring20";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@RepositoryFormat";'; put 'put "characterstring10";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@Access";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@CurrentAccess";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@PauseState";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Path";'; put 'put "";'; put 'put "characterstring256";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Engine";'; put 'put "";'; put 'put "characterstring8";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Options";'; put 'put "";'; put 'put "characterstring32";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@MetadataCreated";'; put 'put "characterstring24";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@MetadataUpdated";'; put 'put "characterstring24";'; put 'put '''';'; put 'put ''
'';'; put 'run;'; put 'libname _XML_ xml xmlfileref=response xmlmap=sxlemap;'; put 'proc sort data= _XML_.SASRepos out=&outds;'; put 'by name;'; put 'run;'; put '/* clear references */'; put 'filename sxlemap clear;'; put 'filename response clear;'; put 'libname _XML_ clear;'; put '%mend mm_getrepos;'; put '%macro dc_getlibs(outds=mm_getlibs);'; put '/* take current repository */'; put '%local repo repocnt x;'; put '%let repo=%sysfunc(getoption(metarepository));'; put '/* get list of repositories and filter */'; put '%mm_getrepos(outds=repos)'; put '%let repocnt=0;'; put 'data repos;'; put 'set repos;'; put 'where repositorytype in(''CUSTOM'',''FOUNDATION'')'; put '& upcase(name) ne "%upcase(&repo)";'; put 'keep id name ;'; put 'call symputx(''repo''!!cats(_n_),name,''l'');'; put 'call symputx(''repocnt'',_n_,''l'');'; put 'run;'; put '%put _local_;'; put '%mm_getlibs(outds=&outds)'; put '%do x=1 %to &repocnt;'; put 'options metarepository=&&repo&x;'; put '%mm_getlibs(outds=&outds.a)'; put 'proc append base=&outds data=&outds.a;'; put 'run;'; put '%end;'; put 'options metarepository=&repo;'; put '%mend dc_getlibs;'; put '* SAS Macros end;'; put '* SAS Includes start;'; put '* SAS Includes end;'; put '* Binary Files start;'; put '* Binary Files end;'; put '* ServiceInit start;'; put 'options noquotelenmax ps=max;'; put '/* create dummy macros to prevent stpbegin / stpend from corrupting sessions */'; put '%macro stpbegin();'; put '%put NOTE: the STPBEGIN macro should not be used for web apps!;'; put '%mend stpbegin;'; put '%macro stpend();'; put '%put NOTE: the STPEND macro should not be used for web apps!;'; put '%mend stpend;'; put '* ServiceInit end;'; put '* Service start;'; put '/**'; put '@file'; put '@brief Generic validator for libraries'; put '@details The input table is simply one row from the target table in table'; put 'called "work.source_row".'; put 'Available macro variables:'; put '@li MPELIB - The DC control library'; put '@li LIBDS - The library.dataset being filtered'; put '@li VARIABLE_NM - The column being filtered'; put '

Service Outputs

'; put 'The values provided below are generic samples - we encourage you to replace'; put 'these with realistic values in your own deployments.'; put '
DYNAMIC_VALUES
'; put 'The RAW_VALUE column may be charactor or numeric. If DISPLAY_INDEX is not'; put 'provided, it is added automatically.'; put '|DISPLAY_INDEX:best.|DISPLAY_VALUE:$|RAW_VALUE|'; put '|---|---|---|'; put '|1|$77.43|77.43|'; put '|2|$88.43|88.43|'; put '
DYNAMIC_EXTENDED_VALUES
'; put 'This table is optional. If provided, it will map the DISPLAY_INDEX from the'; put 'DYNAMIC_VALUES table to additional column/value pairs, that will be used to'; put 'populate dropdowns for _other_ cells in the _same_ row.'; put 'Should be used sparingly! The use of large tables here can slow down the'; put 'browser.'; put '|DISPLAY_INDEX:best.|EXTRA_COL_NAME:$32.|DISPLAY_VALUE:$|DISPLAY_TYPE:$1.|RAW_VALUE_NUM|RAW_VALUE_CHAR:$5000|'; put '|---|---|---|'; put '|1|DISCOUNT_RT|"50%"|N|0.5||'; put '|1|DISCOUNT_RT|"40%"|N|0.4||'; put '|1|DISCOUNT_RT|"30%"|N|0.3||'; put '|1|CURRENCY_SYMBOL|"GBP"|C||"GBP"|'; put '|1|CURRENCY_SYMBOL|"RSD"|C||"RSD"|'; put '|2|DISCOUNT_RT|"50%"|N|0.5||'; put '|2|DISCOUNT_RT|"40%"|N|0.4||'; put '|2|CURRENCY_SYMBOL|"EUR"|C||"EUR"|'; put '|2|CURRENCY_SYMBOL|"HKD"|C||"HKD"|'; put '

SAS Macros

'; put '@li dc_getlibs.sas'; put '**/'; put '/**'; put '* get full list of libraries'; put '*/'; put '%dc_getlibs(outds=work.mm_getLibs)'; put 'proc sql;'; put 'create table work.DYNAMIC_VALUES as'; put 'select distinct libraryname as display_value,'; put 'upcase(libraryref) as raw_value'; put 'from work.mm_getLibs'; put 'order by 1;'; put '* Service end;'; run; %mm_createwebservice(path=&appLoc/&path, name=&service, code=sascode, server=&serverName, replace=yes) filename sascode clear; %let service=libraries_editable; filename sascode temp lrecl=32767; data _null_; file sascode; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '/**'; put '@file mp_jsonout.sas'; put '@brief Writes JSON in SASjs format to a fileref'; put '@details This macro can be used to OPEN a JSON stream and send one or more'; put 'tables as arrays of rows, where each row can be an object or a nested array.'; put 'There are two engines available - DATASTEP or PROCJSON.'; put 'PROC JSON is fast but will produce errs like the ones below if'; put 'special chars are encountered.'; put '> (ERR)OR: Some code points did not transcode.'; put '> An object or array close is not valid at this point in the JSON text.'; put '> Date value out of range'; put 'If this happens, try running with ENGINE=DATASTEP.'; put 'The DATASTEP engine is used to handle special SAS missing numerics, and'; put 'can also convert entire datasets to formatted values. Output JSON is always'; put 'in UTF-8.'; put 'Usage:'; put 'filename tmp temp;'; put 'data class; set sashelp.class;run;'; put '%mp_jsonout(OPEN,jref=tmp)'; put '%mp_jsonout(OBJ,class,jref=tmp)'; put '%mp_jsonout(OBJ,class,dslabel=class2,jref=tmp,showmeta=Y)'; put '%mp_jsonout(CLOSE,jref=tmp)'; put 'data _null_;'; put 'infile tmp;'; put 'input;putlog _infile_;'; put 'run;'; put 'If you are building web apps with SAS then you are strongly encouraged to use'; put 'the mX_createwebservice macros in combination with the'; put '[sasjs adapter](https://github.com/sasjs/adapter).'; put 'For more information see https://sasjs.io'; put '@param [in] action Valid values:'; put '@li OPEN - opens the JSON'; put '@li OBJ - sends a table with each row as an object'; put '@li ARR - sends a table with each row in an array'; put '@li CLOSE - closes the JSON'; put '@param [in] ds The dataset to send. Must be a work table.'; put '@param [out] jref= (_webout) The fileref to which to send the JSON'; put '@param [out] dslabel= The name to give the table in the exported JSON'; put '@param [in] fmt= (Y) Whether to keep (Y) or strip (N) formats from the table'; put '@param [in] engine= (DATASTEP) Which engine to use to send the JSON. Options:'; put '@li PROCJSON (default)'; put '@li DATASTEP (more reliable when data has non standard characters)'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to JSON'; put '

Related Files

'; put '@li mp_ds2fmtds.sas'; put '@version 9.2'; put '@author Allan Bowe'; put '@source https://github.com/sasjs/core'; put '**/'; put '%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y'; put ',engine=DATASTEP'; put ',missing=NULL'; put ',showmeta=N'; put ',maxobs=MAX'; put ')/*/STORE SOURCE*/;'; put '%local tempds colinfo fmtds i numcols numobs stmt_obs lastobs optval'; put 'tmpds1 tmpds2 tmpds3 tmpds4;'; put '%let numcols=0;'; put '%if &maxobs ne MAX %then %let stmt_obs=%str(if _n_>&maxobs then stop;);'; put '%if &action=OPEN %then %do;'; put 'options nobomfile;'; put 'data _null_;file &jref encoding=''utf-8'' lrecl=200;'; put 'put ''{"PROCESSED_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''"'';'; put 'run;'; put '%end;'; put '%else %if (&action=ARR or &action=OBJ) %then %do;'; put '/* force variable names to always be uppercase in the JSON */'; put 'options validvarname=upcase;'; put '/* To avoid issues with _webout on EBI - such as encoding diffs and truncation'; put '(https://support.sas.com/kb/49/325.html) we use temporary files */'; put 'filename _sjs1 temp lrecl=200 ;'; put 'data _null_; file _sjs1 encoding=''utf-8'';'; put 'put ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":";'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjs1 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs1 clear;'; put '/* grab col defs */'; put 'proc contents noprint data=&ds'; put 'out=_data_(keep=name type length format formatl formatd varnum label);'; put 'run;'; put '%let colinfo=%scan(&syslast,2,.);'; put 'proc sort data=&colinfo;'; put 'by varnum;'; put 'run;'; put '/* move meta to mac vars */'; put 'data &colinfo;'; put 'if _n_=1 then call symputx(''numcols'',nobs,''l'');'; put 'set &colinfo end=last nobs=nobs;'; put 'name=upcase(name);'; put '/* fix formats */'; put 'if type=2 or type=6 then do;'; put 'typelong=''char'';'; put 'length fmt $49.;'; put 'if format='''' then fmt=cats(''$'',length,''.'');'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else fmt=cats(format,formatl,''.'');'; put 'end;'; put 'else do;'; put 'typelong=''num'';'; put 'if format='''' then fmt=''best.'';'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else if formatd=0 then fmt=cats(format,formatl,''.'');'; put 'else fmt=cats(format,formatl,''.'',formatd);'; put 'end;'; put '/* 32 char unique name */'; put 'newname=''sasjs''!!substr(cats(put(md5(name),$hex32.)),1,27);'; put 'call symputx(cats(''name'',_n_),name,''l'');'; put 'call symputx(cats(''newname'',_n_),newname,''l'');'; put 'call symputx(cats(''length'',_n_),length,''l'');'; put 'call symputx(cats(''fmt'',_n_),fmt,''l'');'; put 'call symputx(cats(''type'',_n_),type,''l'');'; put 'call symputx(cats(''typelong'',_n_),typelong,''l'');'; put 'call symputx(cats(''label'',_n_),coalescec(label,name),''l'');'; put '/* overwritten when fmt=Y and a custom format exists in catalog */'; put 'if typelong=''num'' then call symputx(cats(''fmtlen'',_n_),200,''l'');'; put 'else call symputx(cats(''fmtlen'',_n_),min(32767,ceil((length+10)*1.5)),''l'');'; put 'run;'; put '%let tempds=%substr(_%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql;'; put 'select count(*) into: lastobs from &ds;'; put '%if &maxobs ne MAX %then %let lastobs=%sysfunc(min(&lastobs,&maxobs));'; put '%if &engine=PROCJSON %then %do;'; put '%if &missing=STRING %then %do;'; put '%put &sysmacroname: Special Missings not supported in proc json.;'; put '%put &sysmacroname: Switching to DATASTEP engine;'; put '%goto datastep;'; put '%end;'; put 'data &tempds;'; put 'set &ds;'; put '&stmt_obs;'; put '%if &fmt=N %then format _numeric_ best32.;;'; put '/* PRETTY is necessary to avoid line truncation in large files */'; put 'filename _sjs2 temp lrecl=131068 encoding=''utf-8'';'; put 'proc json out=_sjs2 pretty'; put '%if &action=ARR %then nokeys ;'; put ';export &tempds / nosastags fmtnumeric;'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs2 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs2 clear;'; put '%end;'; put '%else %if &engine=DATASTEP %then %do;'; put '%datastep:'; put '%if %sysfunc(exist(&ds)) ne 1 & %sysfunc(exist(&ds,VIEW)) ne 1'; put '%then %do;'; put '%put &sysmacroname: &ds NOT FOUND!!!;'; put '%return;'; put '%end;'; put '%if &fmt=Y %then %do;'; put '/**'; put '* Extract format definitions'; put '* First, by getting library locations from dictionary.formats'; put '* Then, by exporting the width using proc format'; put '* Cannot use maxw from sashelp.vformat as not always populated'; put '* Cannot use fmtinfo() as not supported in all flavours'; put '*/'; put '%let tmpds1=%substr(fmtsum%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds2=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds3=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds4=%substr(col%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql noprint;'; put 'create table &tmpds1 as'; put 'select cats(libname,''.'',memname) as FMTCAT,'; put 'FMTNAME'; put 'from dictionary.formats'; put 'where fmttype=''F'' and libname is not null'; put 'and fmtname in (select format from &colinfo where format is not null)'; put 'order by 1;'; put 'create table &tmpds2('; put 'FMTNAME char(32),'; put 'LENGTH num'; put ');'; put '%local catlist cat fmtlist i;'; put 'select distinct fmtcat into: catlist separated by '' '' from &tmpds1;'; put '%do i=1 %to %sysfunc(countw(&catlist,%str( )));'; put '%let cat=%scan(&catlist,&i,%str( ));'; put 'proc sql;'; put 'select distinct fmtname into: fmtlist separated by '' '''; put 'from &tmpds1 where fmtcat="&cat";'; put 'proc format lib=&cat cntlout=&tmpds3(keep=fmtname length);'; put 'select &fmtlist;'; put 'run;'; put 'proc sql;'; put 'insert into &tmpds2 select distinct fmtname,length from &tmpds3;'; put '%end;'; put 'proc sql;'; put 'create table &tmpds4 as'; put 'select a.*, b.length as MAXW'; put 'from &colinfo a'; put 'left join &tmpds2 b'; put 'on cats(a.format)=cats(upcase(b.fmtname))'; put 'order by a.varnum;'; put 'data _null_;'; put 'set &tmpds4;'; put 'if not missing(maxw);'; put 'call symputx('; put 'cats(''fmtlen'',_n_),'; put '/* vars need extra padding due to JSON escaping of special chars */'; put 'min(32767,ceil((max(length,maxw)+10)*1.5))'; put ',''l'''; put ');'; put 'run;'; put '/* configure varlenchk - as we are explicitly shortening the variables */'; put '%let optval=%sysfunc(getoption(varlenchk));'; put 'options varlenchk=NOWARN;'; put 'data _data_(compress=char);'; put '/* shorten the new vars */'; put 'length'; put '%do i=1 %to &numcols;'; put '&&name&i $&&fmtlen&i'; put '%end;'; put ';'; put '/* rename on entry */'; put 'set &ds(rename=('; put '%do i=1 %to &numcols;'; put '&&name&i=&&newname&i'; put '%end;'; put '));'; put '&stmt_obs;'; put 'drop'; put '%do i=1 %to &numcols;'; put '&&newname&i'; put '%end;'; put ';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=num %then %do;'; put '&&name&i=cats(put(&&newname&i,&&fmt&i));'; put '%end;'; put '%else %do;'; put '&&name&i=put(&&newname&i,&&fmt&i);'; put '%end;'; put '%end;'; put 'if _error_ then do;'; put 'call symputx(''syscc'',1012);'; put 'stop;'; put 'end;'; put 'run;'; put '%let fmtds=&syslast;'; put 'options varlenchk=&optval;'; put '%end;'; put 'proc format; /* credit yabwon for special null removal */'; put 'value bart (default=40)'; put '%if &missing=NULL %then %do;'; put '._ - .z = null'; put '%end;'; put '%else %do;'; put '._ = [quote()]'; put '. = null'; put '.a - .z = [quote()]'; put '%end;'; put 'other = [best.];'; put 'data &tempds;'; put 'attrib _all_ label='''';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'length &&name&i $&&fmtlen&i...;'; put 'format &&name&i $&&fmtlen&i...;'; put '%end;'; put '%end;'; put '%if &fmt=Y %then %do;'; put 'set &fmtds;'; put '%end;'; put '%else %do;'; put 'set &ds;'; put '%end;'; put '&stmt_obs;'; put 'format _numeric_ bart.;'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'if findc(&&name&i,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put '&&name&i=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,&&name&i)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else &&name&i=quote(cats(&&name&i));'; put '%end;'; put '%end;'; put 'run;'; put 'filename _sjs3 temp lrecl=131068 ;'; put 'data _null_;'; put 'file _sjs3 encoding=''utf-8'';'; put 'if _n_=1 then put "[";'; put 'set &tempds;'; put 'if _n_>1 then put "," @; put'; put '%if &action=ARR %then "[" ; %else "{" ;'; put '%do i=1 %to &numcols;'; put '%if &i>1 %then "," ;'; put '%if &action=OBJ %then """&&name&i"":" ;'; put '"&&name&i"n /* name literal for reserved variable names */'; put '%end;'; put '%if &action=ARR %then "]" ; %else "}" ; ;'; put '/* close out the table */'; put 'data _null_;'; put 'file _sjs3 mod encoding=''utf-8'';'; put 'put '']'';'; put 'run;'; put 'data _null_;'; put 'infile _sjs3 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs3 clear;'; put '%end;'; put 'proc sql;'; put 'drop table &colinfo, &tempds;'; put '%if %substr(&showmeta,1,1)=Y %then %do;'; put 'filename _sjs4 temp lrecl=131068 encoding=''utf-8'';'; put 'data _null_;'; put 'file _sjs4;'; put 'length label $350;'; put 'put ", ""$%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":{""vars"":{";'; put 'do i=1 to &numcols;'; put 'name=quote(trim(symget(cats(''name'',i))));'; put 'format=quote(trim(symget(cats(''fmt'',i))));'; put 'label=quote(prxchange(''s/\\/\\\\/'',-1,trim(symget(cats(''label'',i)))));'; put 'length=quote(trim(symget(cats(''length'',i))));'; put 'type=quote(trim(symget(cats(''typelong'',i))));'; put 'if i>1 then put "," @@;'; put 'put name '':{"format":'' format '',"label":'' label'; put ''',"length":'' length '',"type":'' type ''}'';'; put 'end;'; put 'put ''}}'';'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs4 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs4 clear;'; put '%end;'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put 'data _null_; file &jref encoding=''utf-8'' mod ;'; put 'put "}";'; put 'run;'; put '%end;'; put '%mend mp_jsonout;'; put '/**'; put '@file mm_webout.sas'; put '@brief Send data to/from SAS Stored Processes'; put '@details This macro should be added to the start of each Stored Process,'; put '**immediately** followed by a call to:'; put '%mm_webout(FETCH)'; put 'This will read all the input data and create same-named SAS datasets in the'; put 'WORK library. You can then insert your code, and send data back using the'; put 'following syntax:'; put 'data some datasets; * make some data ;'; put 'retain some columns;'; put 'run;'; put '%mm_webout(OPEN)'; put '%mm_webout(ARR,some) * Array format, fast, suitable for large tables ;'; put '%mm_webout(OBJ,datasets) * Object format, easier to work with ;'; put 'Finally, wrap everything up send some helpful system variables too'; put '%mm_webout(CLOSE)'; put '@param [in] action Either FETCH, OPEN, ARR, OBJ or CLOSE'; put '@param [in] ds The dataset to send back to the frontend'; put '@param [out] dslabel= Value to use instead of table name for sending to JSON'; put '@param [in] fmt= (N) Setting Y converts all vars to their formatted values'; put '@param [out] fref= (_webout) The fileref to which to write the JSON'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] workobs= (0) When set to a positive integer, will create a new'; put 'output object (WORK) which contains this number of observations from all'; put 'tables in the WORK library.'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to output JSON'; put '

SAS Macros

'; put '@li mp_jsonout.sas'; put '

Related Macros

'; put '@li ms_webout.sas'; put '@li mv_webout.sas'; put '@version 9.3'; put '@author Allan Bowe'; put '**/'; put '%macro mm_webout(action,ds,dslabel=,fref=_webout,fmt=N,missing=NULL'; put ',showmeta=N,maxobs=MAX,workobs=0'; put ');'; put '%global _webin_file_count _webin_fileref1 _webin_name1 _program _debug'; put 'sasjs_tables;'; put '%local i tempds jsonengine;'; put '/* see https://github.com/sasjs/core/issues/41 */'; put '%if "%upcase(&SYSENCODING)" ne "UTF-8" %then %let jsonengine=PROCJSON;'; put '%else %let jsonengine=DATASTEP;'; put '%if &action=FETCH %then %do;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'options mprint notes mprintnest;'; put '%end;'; put '%let _webin_file_count=%eval(&_webin_file_count+0);'; put '/* now read in the data */'; put '%do i=1 %to &_webin_file_count;'; put '%if &_webin_file_count=1 %then %do;'; put '%let _webin_fileref1=&_webin_fileref;'; put '%let _webin_name1=&_webin_name;'; put '%end;'; put 'data _null_;'; put 'infile &&_webin_fileref&i termstr=crlf;'; put 'input;'; put 'call symputx(''input_statement'',_infile_);'; put 'putlog "&&_webin_name&i input statement: " _infile_;'; put 'stop;'; put 'data &&_webin_name&i;'; put 'infile &&_webin_fileref&i firstobs=2 dsd termstr=crlf encoding=''utf-8'';'; put 'input &input_statement;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'if _n_<20 then putlog _infile_;'; put '%end;'; put 'run;'; put '%let sasjs_tables=&sasjs_tables &&_webin_name&i;'; put '%end;'; put '%end;'; put '%else %if &action=OPEN %then %do;'; put '/* fix encoding */'; put 'OPTIONS NOBOMFILE;'; put '/**'; put '* check xengine type to avoid the below err message:'; put '* > Function is only valid for filerefs using the CACHE access method.'; put '*/'; put 'data _null_;'; put 'set sashelp.vextfl(where=(fileref="_WEBOUT"));'; put 'if xengine=''STREAM'' then do;'; put 'rc=stpsrv_header(''Content-type'',"text/html; encoding=utf-8");'; put 'end;'; put 'run;'; put '/* setup json */'; put 'data _null_;file &fref encoding=''utf-8'';'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutBEGIN<<'';'; put '%end;'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'run;'; put '%end;'; put '%else %if &action=ARR or &action=OBJ %then %do;'; put '%mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt,jref=&fref'; put ',engine=&jsonengine,missing=&missing,showmeta=&showmeta,maxobs=&maxobs'; put ')'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put '/* To avoid issues with _webout on EBI we use a temporary file */'; put 'filename _sjsref temp lrecl=131068;'; put '%if %str(&workobs) > 0 %then %do;'; put '/* if debug mode, send back first XX records of each work table also */'; put 'data;run;%let tempds=%scan(&syslast,2,.);'; put 'ods output Members=&tempds;'; put 'proc datasets library=WORK memtype=data;'; put '%local wtcnt;%let wtcnt=0;'; put 'data _null_;'; put 'set &tempds;'; put 'if not (upcase(name) =:"DATA"); /* ignore temp datasets */'; put 'i+1;'; put 'call symputx(cats(''wt'',i),name,''l'');'; put 'call symputx(''wtcnt'',i,''l'');'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put ",""WORK"":{";'; put '%do i=1 %to &wtcnt;'; put '%let wt=&&wt&i;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'dsid=open("WORK.&wt",''is'');'; put 'nlobs=attrn(dsid,''NLOBS'');'; put 'nvars=attrn(dsid,''NVARS'');'; put 'rc=close(dsid);'; put 'if &i>1 then put '',''@;'; put 'put " ""&wt"" : {";'; put 'put ''"nlobs":'' nlobs;'; put 'put '',"nvars":'' nvars;'; put '%mp_jsonout(OBJ,&wt,jref=_sjsref,dslabel=first10rows,showmeta=Y'; put ',maxobs=&workobs'; put ')'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put '%end;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put 'run;'; put '%end;'; put '/* close off json */'; put 'data _null_;file _sjsref mod encoding=''utf-8'';'; put 'length SYSPROCESSNAME syserrortext syswarningtext autoexec $512;'; put 'put ",""_DEBUG"" : ""&_debug"" ";'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'autoexec=quote(urlencode(trim(getoption(''autoexec''))));'; put 'put '',"AUTOEXEC" : '' autoexec;'; put 'put ",""MF_GETUSER"" : ""%mf_getuser()"" ";'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'put ",""SYSENCODING"" : ""&sysencoding"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSPROCESSID"" : ""&SYSPROCESSID"" ";'; put 'put ",""SYSPROCESSMODE"" : ""&SYSPROCESSMODE"" ";'; put 'SYSPROCESSNAME=quote(urlencode(cats(SYSPROCESSNAME)));'; put 'put ",""SYSPROCESSNAME"" : " SYSPROCESSNAME;'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put '',"SYSWARNINGTEXT" : '' syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'length memsize $32;'; put 'memsize="%sysfunc(INPUTN(%sysfunc(getoption(memsize)), best.),sizekmg.)";'; put 'memsize=quote(cats(memsize));'; put 'put '',"MEMSIZE" : '' memsize;'; put 'put "}" @;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutEND<<'';'; put '%end;'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjsref lrecl=1 recfm=n;'; put 'file &fref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjsref clear;'; put '%end;'; put '%mend mm_webout;'; put '%macro webout(action,ds,dslabel=,fmt=,missing=NULL,showmeta=NO,maxobs=MAX);'; put '%mm_webout(&action,ds=&ds,dslabel=&dslabel,fmt=&fmt'; put ',missing=&missing'; put ',showmeta=&showmeta'; put ',maxobs=&maxobs'; put ') %mend;'; put '/* provide additional debug info */'; put '%global _program;'; put '%put &=syscc;'; put '%put user=%mf_getuser();'; put '%put pgm=&_program;'; put '%put timestamp=%sysfunc(datetime(),datetime19.);'; put '* Service Variables start;'; put '* Service Variables end;'; put '* SAS Macros start;'; put '%macro mf_getapploc(pgm);'; put '%if "&pgm"="" %then %do;'; put '%if %symexist(_program) %then %let pgm=&_program;'; put '%else %do;'; put '%put &sysmacroname: No value provided and no _program variable available;'; put '%return;'; put '%end;'; put '%end;'; put '%local root;'; put '/**'; put '* First check we are not in the tests/macros folder (which has no subfolders)'; put '* or specifically in the testsetup or testteardown services'; put '*/'; put '%if %index(&pgm,/tests/macros/)'; put 'or %index(&pgm,/tests/testsetup)'; put 'or %index(&pgm,/tests/testteardown)'; put '%then %do;'; put '%let root=%substr(&pgm,1,%index(&pgm,/tests)-1);'; put '&root'; put '%return;'; put '%end;'; put '/**'; put '* Next, move up two levels to avoid matches on subfolder or service name'; put '*/'; put '%let root=%substr(&pgm,1,%length(&pgm)-%length(%scan(&pgm,-1,/))-1);'; put '%let root=%substr(&root,1,%length(&root)-%length(%scan(&root,-1,/))-1);'; put '%if %index(&root,/tests/) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/tests/)-1);'; put '%end;'; put '%else %if %index(&root,/services) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/services)-1);'; put '%end;'; put '%else %if %index(&root,/jobs) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/jobs)-1);'; put '%end;'; put '%else %put &sysmacroname: Could not find an app location from &pgm;'; put '&root'; put '%mend mf_getapploc ;'; put '%macro mf_getuniquefileref(prefix=_,maxtries=1000,lrecl=32767);'; put '%local rc fname;'; put '%if &prefix=0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%end;'; put '%else %do;'; put '%local x len;'; put '%let len=%eval(8-%length(&prefix));'; put '%let x=0;'; put '%do x=0 %to &maxtries;'; put '%let fname=&prefix%substr(%sysfunc(ranuni(0)),3,&len);'; put '%if %sysfunc(fileref(&fname)) > 0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%return;'; put '%end;'; put '%end;'; put '%put unable to find available fileref after &maxtries attempts;'; put '%end;'; put '%mend mf_getuniquefileref;'; put '%macro mp_abort(mac=mp_abort.sas, type=, msg=, iftrue=%str(1=1)'; put ', errds=work.mp_abort_errds'; put ', mode=REGULAR'; put ')/*/STORE SOURCE*/;'; put '%global sysprocessmode sysprocessname sasjs_stpsrv_header_loc sasjsprocessmode;'; put '%local fref fid i;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%put NOTE: /// mp_abort macro executing //;'; put '%if %length(&mac)>0 %then %put NOTE- called by &mac;'; put '%put NOTE - &msg;'; put '%if %symexist(_SYSINCLUDEFILEDEVICE)'; put '/* abort cancel FILE does not restart outside the INCLUDE on Viya 3.5 */'; put 'and %superq(SYSPROCESSNAME) ne %str(Compute Server)'; put '%then %do;'; put '%if "*&_SYSINCLUDEFILEDEVICE*" ne "**" %then %do;'; put 'data &errds;'; put 'iftrue=''1=1'';'; put 'length mac $100 msg $5000;'; put 'mac=symget(''mac'');'; put 'msg=symget(''msg'');'; put 'run;'; put 'data _null_;'; put 'abort cancel FILE;'; put 'run;'; put '%return;'; put '%end;'; put '%end;'; put '/* Web App Context */'; put '%if %symexist(_PROGRAM)'; put 'or %superq(SYSPROCESSNAME) = %str(Compute Server)'; put 'or &mode=INCLUDE'; put '%then %do;'; put 'options obs=max replace mprint;'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5"'; put '%then %do;'; put 'options nosyntaxcheck;'; put '%end;'; put '%if &mode=INCLUDE %then %do;'; put '%if %sysfunc(exist(&errds))=1 %then %do;'; put 'data _null_;'; put 'set &errds;'; put 'call symputx(''iftrue'',iftrue,''l'');'; put 'call symputx(''mac'',mac,''l'');'; put 'call symputx(''msg'',msg,''l'');'; put 'putlog (_all_)(=);'; put 'run;'; put '%if (&iftrue)=0 %then %return;'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: No include errors found;'; put '%return;'; put '%end;'; put '%end;'; put '/* extract log errs / warns, if exist */'; put '%local logloc logline;'; put '%global logmsg; /* capture global messages */'; put '%if %symexist(SYSPRINTTOLOG) %then %let logloc=&SYSPRINTTOLOG;'; put '%else %let logloc=%qsysfunc(getoption(LOG));'; put 'proc printto log=log;run;'; put '%let logline=0;'; put '%if %length(&logloc)>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input; putlog _infile_;'; put 'i=1;'; put 'retain logonce 0;'; put 'if ('; put '_infile_=:"%str(WARN)ING" or _infile_=:"%str(ERR)OR"'; put ') and logonce=0 then'; put 'do;'; put 'call symputx(''logline'',_n_);'; put 'logonce+1;'; put 'end;'; put 'run;'; put '/* capture log including lines BEFORE the err */'; put '%if &logline>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input;'; put 'i=1;'; put 'stoploop=0;'; put 'if _n_ ge &logline-15 and stoploop=0 then do until (i>22);'; put 'call symputx(''logmsg'',catx(''\n'',symget(''logmsg''),_infile_));'; put 'input;'; put 'i+1;'; put 'stoploop=1;'; put 'end;'; put 'if stoploop=1 then stop;'; put 'run;'; put '%end;'; put '%end;'; put '%if %symexist(SYS_JES_JOB_URI) %then %do;'; put '/* setup webout for Viya */'; put 'options nobomfile;'; put '%if "X&SYS_JES_JOB_URI.X"="XX" %then %do;'; put 'filename _webout temp lrecl=999999 mod;'; put '%end;'; put '%else %do;'; put 'filename _webout filesrvc parenturi="&SYS_JES_JOB_URI"'; put 'name="_webout.json" lrecl=999999 mod;'; put '%end;'; put '%end;'; put '%else %if %sysfunc(filename(fref,&sasjs_stpsrv_header_loc))=0 %then %do;'; put 'options nobomfile;'; put '/* set up http header for SASjs Server */'; put '%let fid=%sysfunc(fopen(&fref,A));'; put '%if &fid=0 %then %do;'; put '%put %str(ERR)OR: %sysfunc(sysmsg());'; put '%return;'; put '%end;'; put '%let rc=%sysfunc(fput(&fid,%str(Content-Type: application/json)));'; put '%let rc=%sysfunc(fwrite(&fid));'; put '%let rc=%sysfunc(fclose(&fid));'; put '%let rc=%sysfunc(filename(&fref));'; put '%end;'; put '/* send response in SASjs JSON format */'; put 'data _null_;'; put 'file _webout mod lrecl=32000 encoding=''utf-8'';'; put 'length msg syswarningtext syserrortext $32767 mode $10 ;'; put 'sasdatetime=datetime();'; put 'msg=symget(''msg'');'; put '%if &logline>0 %then %do;'; put 'msg=cats(msg,''\n\nLog Extract:\n'',symget(''logmsg''));'; put '%end;'; put '/* escape the escapes */'; put 'msg=tranwrd(msg,''\'',''\\'');'; put '/* escape the quotes */'; put 'msg=tranwrd(msg,''"'',''\"'');'; put '/* ditch the CRLFs as chrome complains */'; put 'msg=compress(msg,,''kw'');'; put '/* quote without quoting the quotes (which are escaped instead) */'; put 'msg=cats(''"'',msg,''"'');'; put 'if symexist(''_debug'') then debug=quote(trim(symget(''_debug'')));'; put 'else debug=''""'';'; put 'if symget(''sasjsprocessmode'')=''Stored Program'' then mode=''SASJS'';'; put 'if mode ne ''SASJS'' then put ''>>weboutBEGIN<<'';'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'put '',"sasjsAbort" : [{'';'; put 'put '' "MSG":'' msg ;'; put 'put '' ,"MAC": "'' "&mac" ''"}]'';'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'put '',"_DEBUG":'' debug ;'; put 'if symexist(''_metauser'') then do;'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put 'end;'; put 'if symexist(''SYS_JES_JOB_URI'') then do;'; put 'SYS_JES_JOB_URI=quote(trim(symget(''SYS_JES_JOB_URI'')));'; put 'put '',"SYS_JES_JOB_URI": '' SYS_JES_JOB_URI;'; put 'end;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put ",""SYSWARNINGTEXT"" : " syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'put "}" ;'; put 'if mode ne ''SASJS'' then put ''>>weboutEND<<'';'; put 'run;'; put '%put _all_;'; put '%if "&sysprocessmode " = "SAS Stored Process Server " %then %do;'; put 'data _null_;'; put 'putlog ''stpsrvset program err and syscc'';'; put 'rc=stpsrvset(''program error'', 0);'; put 'call symputx("syscc",0,"g");'; put 'run;'; put '%if &sysscp=WIN'; put 'and 1=0 /* deprecating this logic until we figure out a consistent abort */'; put 'and "%substr(%str(&sysvlong ),1,8)"="9.04.01M"'; put 'and "%substr(%str(&sysvlong ),9,1)">"5" %then %do;'; put '/* skip approach (below) does not work in windows m6+ envs */'; put 'endsas;'; put '%end;'; put '%else %do;'; put '/**'; put '* endsas kills 9.4m3 deployments by orphaning multibridges.'; put '* Abort variants are ungraceful (non zero return code)'; put '* This approach lets SAS run silently until the end :-)'; put '* Caution - fails when called within a %include within a macro'; put '* Use mp_include() to handle this.'; put '*/'; put 'filename skip temp;'; put 'data _null_;'; put 'file skip;'; put 'put ''%macro skip();'';'; put 'comment ''%mend skip; -> fix lint '';'; put 'put ''%macro skippy();'';'; put 'comment ''%mend skippy; -> fix lint '';'; put 'run;'; put '%inc skip;'; put '%end;'; put '%end;'; put '%else %if "&sysprocessmode " = "SAS Compute Server " %then %do;'; put '/* endsas kills the session making it harder to fetch results */'; put 'data _null_;'; put 'syswarningtext=symget(''syswarningtext'');'; put 'syserrortext=symget(''syserrortext'');'; put 'abort_msg=symget(''msg'');'; put 'syscc=symget(''syscc'');'; put 'sysuserid=symget(''sysuserid'');'; put 'iftrue=symget(''iftrue'');'; put 'put (_all_)(/=);'; put 'call symputx(''syscc'',0);'; put 'abort cancel nolist;'; put 'run;'; put '%end;'; put '%else %do;'; put '%abort cancel;'; put '%end;'; put '%end;'; put '%else %do;'; put '%put _all_;'; put '%abort cancel;'; put '%end;'; put '%mend mp_abort;'; put '/** @endcond */'; put '%macro mm_getstpcode('; put 'tree=/User Folders/sasdemo/somestp'; put ',name='; put ',outloc=0'; put ',outref=0'; put ',mDebug=1'; put ',showlog=NO'; put ');'; put '%local mD;'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing &sysmacroname..sas;'; put '%&mD.put _local_;'; put '%if %length(&name)>0 %then %let name=/&name;'; put '/* first, check if STP exists */'; put '%local tsuri;'; put '%let tsuri=stopifempty ;'; put 'data _null_;'; put 'format type uri tsuri value $200.;'; put 'call missing (of _all_);'; put 'path="&tree&name(StoredProcess)";'; put '/* first, find the STP ID */'; put 'if metadata_pathobj("",path,"StoredProcess",type,uri)>0 then do;'; put '/* get sourcecode */'; put 'cnt=1;'; put 'do while (metadata_getnasn(uri,"Notes",cnt,tsuri)>0);'; put 'rc=metadata_getattr(tsuri,"Name",value);'; put '&mD.put tsuri= value=;'; put 'if value="SourceCode" then do;'; put '/* found it! */'; put 'rc=metadata_getattr(tsuri,"Id",value);'; put 'call symputx(''tsuri'',value,''l'');'; put 'stop;'; put 'end;'; put 'cnt+1;'; put 'end;'; put 'end;'; put 'else put (_all_)(=);'; put 'run;'; put '%mp_abort(iftrue= (&tsuri=stopifempty)'; put ',mac=mm_getstpcode'; put ',msg=%str(&tree&name.(StoredProcess) not found!)'; put ')'; put '/**'; put '* Now we can extract the textstore'; put '*/'; put 'filename __getdoc temp lrecl=10000000;'; put 'proc metadata'; put 'in="$METAREPOSITORY'; put ''; put 'SAS1"'; put 'out=__getdoc ;'; put 'run;'; put '/* find the beginning of the text */'; put '%local start;'; put 'data _null_;'; put 'infile __getdoc lrecl=10000;'; put 'input;'; put 'start=index(_infile_,''StoredText="'');'; put 'if start then do;'; put 'call symputx("start",start+11);'; put '*putlog ''"'' _infile_ ''"'';'; put 'end;'; put 'stop;'; put '%local outeng;'; put '%if "&outloc"="0" %then %let outeng=TEMP;'; put '%else %let outeng="&outloc";'; put '%local fref;'; put '%if &outref=0 %then %let fref=%mf_getuniquefileref();'; put '%else %let fref=&outref;'; put '/* read the content, byte by byte, resolving escaped chars */'; put 'filename &fref &outeng lrecl=100000;'; put 'data _null_;'; put 'length filein 8 fileid 8;'; put 'filein = fopen("__getdoc","I",1,"B");'; put 'fileid = fopen("&fref","O",1,"B");'; put 'rec = "20"x;'; put 'length entity $6;'; put 'do while(fread(filein)=0);'; put 'x+1;'; put 'if x>&start then do;'; put 'rc = fget(filein,rec,1);'; put 'if rec=''"'' then leave;'; put 'else if rec="&" then do;'; put 'entity=rec;'; put 'do until (rec=";");'; put 'if fread(filein) ne 0 then goto getout;'; put 'rc = fget(filein,rec,1);'; put 'entity=cats(entity,rec);'; put 'end;'; put 'select (entity);'; put 'when (''&'' ) rec=''&'' ;'; put 'when (''<'' ) rec=''<'' ;'; put 'when (''>'' ) rec=''>'' ;'; put 'when (''''') rec="''" ;'; put 'when (''"'') rec=''"'' ;'; put 'when ('' '') rec=''0A''x;'; put 'when ('' '') rec=''0D''x;'; put 'when (''$'' ) rec=''$'' ;'; put 'when ('' '') rec=''09''x;'; put 'otherwise putlog "%str(WARN)ING: missing value for " entity=;'; put 'end;'; put 'rc =fput(fileid, substr(rec,1,1));'; put 'rc =fwrite(fileid);'; put 'end;'; put 'else do;'; put 'rc =fput(fileid,rec);'; put 'rc =fwrite(fileid);'; put 'end;'; put 'end;'; put 'end;'; put 'getout:'; put 'rc=fclose(filein);'; put 'rc=fclose(fileid);'; put 'run;'; put '%if &showlog=YES %then %do;'; put 'data _null_;'; put 'infile &fref lrecl=32767 end=last;'; put 'input;'; put 'if _n_=1 then putlog ''>>stpcodeBEGIN<<'';'; put 'putlog _infile_;'; put 'if last then putlog ''>>stpcodeEND<<'';'; put 'run;'; put '%end;'; put 'filename __getdoc clear;'; put '%if &outref=0 %then %do;'; put 'filename &fref clear;'; put '%end;'; put '%mend mm_getstpcode;'; put '%macro dc_getsettings();'; put '%global _program;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(syscc=&syscc on entry (&syswarningtext &syserrortext))'; put ')'; put '%if %length(&_PROGRAM)>1 %then %let root=&_program;'; put '%else %do;'; put '%global _metauser;'; put '%let _metauser=&sysuserid;'; put '/* to mimic a "real" _program we need to give a dummy role and stp name */'; put '%let root=/dummyRole/dummyName;'; put '%end;'; put '/* the DC precode is stored in the Admin folder in the root of'; put 'the project. Lets find that root. */'; put '%put &=root;'; put '%let root=%mf_getapploc();'; put '%put &=root;'; put '/* Now we know the root location we can retrieve the params */'; put '%let temploc=%sysfunc(pathname(work))/settings.txt;'; put '%mm_getstpcode(tree=&root/services/public'; put ',name=Data_Controller_Settings'; put ',outloc=&temploc'; put ')'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Unable to run getstpcode)'; put ')'; put 'filename _getsets "&temploc" lrecl=2000;'; put '/*'; put 'Do not use mp_include here - this puts a copy in every service, which creates'; put 'compilation problems when calling services from mp_include'; put '*/'; put '%inc _getsets/source2;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Problem running &sysmacroname (&syswarningtext &syserrortext))'; put ')'; put '%mend dc_getsettings;'; put '%macro mf_fmtdttm('; put ')/*/STORE SOURCE*/;'; put '%if "&sysver"="9.2" or "&sysver"="9.3"'; put 'or ("&sysver"="9.4" and "%substr(&SYSVLONG,9,1)" le "3")'; put 'or "%substr(&sysver,1,1)"="4"'; put 'or "%substr(&sysver,1,1)"="5"'; put '%then %do;DATETIME19.3%end;'; put '%else %do;E8601DT26.6%end;'; put '%mend mf_fmtdttm;'; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '%macro mp_init(prefix=SASJS'; put ')/*/STORE SOURCE*/;'; put '%if %symexist(SASJS_PREFIX) %then %return; /* only run once */'; put '%global'; put 'SASJS_PREFIX /* the ONLY hard-coded global macro variable in SASjs */'; put '&prefix._FUNCTIONS /* used in mcf_init() to track core function compilation */'; put '&prefix._INIT_NUM /* initialisation time as numeric */'; put '&prefix._INIT_DTTM /* initialisation time in E8601DT26.6 format */'; put '&prefix.WORK /* avoid typing %sysfunc(pathname(work)) every time */'; put ';'; put '%let sasjs_prefix=&prefix;'; put 'data _null_;'; put 'dttm=datetime();'; put 'call symputx("&sasjs_prefix._init_num",dttm,''g'');'; put 'call symputx("&sasjs_prefix._init_dttm",put(dttm,E8601DT26.6),''g'');'; put 'call symputx("&sasjs_prefix.work",pathname(''WORK''),''g'');'; put 'run;'; put 'options'; put 'compress=CHAR /* default is none so ensure we have something! */'; put 'datastmtchk=ALLKEYWORDS /* protection from overwriting input datasets */'; put 'errorcheck=STRICT /* catch errs in libname/filename statements */'; put 'fmterr /* ensure err when a format cannot be found */'; put 'mergenoby=%str(ERR)OR /* throw err when a merge has no BY variables */'; put 'missing=. /* changing this can cause hard to detect errs */'; put 'noquotelenmax /* avoid warnings for long strings */'; put 'noreplace /* avoid overwriting permanent datasets */'; put 'ps=max /* reduce log size slightly */'; put 'ls=max /* reduce log even more and avoid word truncation */'; put 'validmemname=COMPATIBLE /* avoid special characters etc in table names */'; put 'validvarname=V7 /* avoid special characters etc in variable names */'; put 'varinitchk=%str(ERR)OR /* avoid data mistakes from variable name typos */'; put 'varlenchk=%str(ERR)OR /* fail hard if truncation (data loss) can result */'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5" %then %do;'; put 'noautocorrect /* disallow misspelled procedure names */'; put 'dsoptions=note2err /* undocumented - convert bad NOTEs to ERRs */'; put '%end;'; put ';'; put '%mend mp_init;'; put '%macro mpeinit(fetch=YES);'; put '%global mpeinit'; put 'mpeadmins /* group with unrestricted Meditor access */'; put 'mpelocapprovals /* location for landing and staging files */'; put 'mpelib /* location of configuration tables for DC */'; put 'dc_repo_users /* location of user / group metadata */'; put 'dc_licence_key /* extracted in dc_getsettings */'; put 'dc_activation_key /* extracted in dc_getsettings */'; put 'dc_locale /* extracted in dc_getsettings */'; put 'dc_dttmtfmt /* can be overridden in dc_getsettings */'; put '_debug'; put ';'; put '%if &mpeinit=1 %then %return;'; put '%else %let mpeinit=1;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(Problem on service startup (&syswarningtext &syserrortext))'; put ')'; put '%mp_init()'; put '%if &fetch=YES %then %do;'; put '%webout(FETCH)'; put '%end;'; put '%global _CLIENTNAME;'; put '%mp_abort(iftrue= (&_CLIENTNAME=SAS Enterprise Guide)'; put ',mac=&_program..sas'; put ',msg=%str(Data Controller is a web app and should not be executed from EG)'; put ')'; put 'options urlencoding=utf8 nobomfile lrecl=32767;'; put '%let perf=%sysfunc(datetime());'; put '%put perfdiff: 0;'; put '%let dc_locale=SYSTEM; /* default if not set */'; put '/**'; put '* E8601DT26.6 has widest database support - but not all SAS flavours can'; put '* handle it. Override in the settings STP if needed.'; put '*/'; put 'data _null_;'; put 'dc_dttmtfmt=''"%sysfunc(datetime(),''!!"%mf_fmtdttm()"!!'')"dt'';'; put 'call symputx(''dc_dttmtfmt'',dc_dttmtfmt);'; put 'put dc_dttmtfmt=;'; put 'run;'; put '%put &=dc_dttmtfmt;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc prior to dc_getsettings)'; put ')'; put '%dc_getsettings()'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc after dc_getsettings)'; put ')'; put 'data _null_;'; put 'set &DC_LIBREF..mpe_config(where=('; put 'var_scope="DC"'; put 'and &dc_dttmtfmt lt tx_to'; put 'and var_active=1'; put '));'; put 'call symputx(var_name,var_value,''G'');'; put 'putlog var_name "=" var_value;'; put 'run;'; put '%let mpelib=&dc_libref;'; put '%let mpeadmins=&dc_admin_group;'; put '%let mpelocapprovals=&dc_staging_area;'; put '%let dc_repo_users=&dc_repo_users;'; put '%if &dc_locale ne SYSTEM %then %do;'; put 'options locale=&dc_locale;'; put '%end;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(Problem during compilation or with STP precode (&syswarningtext))'; put ')'; put '%mend mpeinit;'; put '%macro mf_mval(var);'; put '%if %symexist(&var) %then %do;'; put '%superq(&var)'; put '%end;'; put '%mend mf_mval;'; put '%macro mf_trimstr(basestr,trimstr);'; put '%local baselen trimlen trimval;'; put '/* return if basestr is shorter than trimstr (or 0) */'; put '%let baselen=%length(%superq(basestr));'; put '%let trimlen=%length(%superq(trimstr));'; put '%if &baselen < &trimlen or &baselen=0 %then %return;'; put '/* obtain the characters from the end of basestr */'; put '%let trimval=%qsubstr(%superq(basestr)'; put ',%length(%superq(basestr))-&trimlen+1'; put ',&trimlen);'; put '/* compare and if matching, chop it off! */'; put '%if %superq(basestr)=%superq(trimstr) %then %do;'; put '%return;'; put '%end;'; put '%else %if %superq(trimval)=%superq(trimstr) %then %do;'; put '%qsubstr(%superq(basestr),1,%length(%superq(basestr))-&trimlen)'; put '%end;'; put '%else %do;'; put '&basestr'; put '%end;'; put '%mend mf_trimstr;'; put '%macro mf_getplatform(switch'; put ')/*/STORE SOURCE*/;'; put '%local a b c;'; put '%if &switch.NONE=NONE %then %do;'; put '%if %symexist(sasjsprocessmode) %then %do;'; put '%if &sasjsprocessmode=Stored Program %then %do;'; put 'SASJS'; put '%return;'; put '%end;'; put '%end;'; put '%if %symexist(sysprocessmode) %then %do;'; put '%if "&sysprocessmode"="SAS Object Server"'; put 'or "&sysprocessmode"= "SAS Compute Server" %then %do;'; put 'SASVIYA'; put '%end;'; put '%else %if "&sysprocessmode"="SAS Stored Process Server"'; put 'or "&sysprocessmode"="SAS Workspace Server"'; put '%then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if %symexist(_metaport) or %symexist(_metauser) %then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if &switch=SASSTUDIO %then %do;'; put '/* return the version of SAS Studio else 0 */'; put '%if %mf_mval(_CLIENTAPP)=%str(SAS Studio) %then %do;'; put '%let a=%mf_mval(_CLIENTVERSION);'; put '%let b=%scan(&a,1,.);'; put '%if %eval(&b >2) %then %do;'; put '&b'; put '%end;'; put '%else 0;'; put '%end;'; put '%else 0;'; put '%end;'; put '%else %if &switch=VIYARESTAPI %then %do;'; put '%mf_trimstr(%sysfunc(getoption(servicesbaseurl)),/)'; put '%end;'; put '%mend mf_getplatform;'; put '%macro mpeterm();'; put '%local oldloc;'; put 'data _null_;'; put 'if symexist(''SYSPRINTTOLOG'') then oldloc=symget(''SYSPRINTTOLOG'');'; put 'else oldloc=getoption(''LOG'');'; put 'if subpad(oldloc,1,1) not in (''"'',"''",'' '') then oldloc=quote(cats(oldloc));'; put 'call symputx(''oldloc'',oldloc,''l'');'; put 'run;'; put '%if %length(&oldloc)>0 %then %do;'; put 'proc printto log=log;'; put 'run;'; put 'data _null_;'; put 'infile &oldloc;'; put 'input; putlog _infile_;'; put 'run;'; put '%end;'; put '%if %sysfunc(exist(&dc_libref..mpe_requests)) and %mf_getplatform() ne SASVIYA'; put '%then %do;'; put 'data ;'; put 'if 0 then set &dc_libref..mpe_requests;'; put 'request_dttm=%sysfunc(datetime());'; put 'request_user="%mf_getuser()";'; put 'request_service="%scan(&_program,-2,/)/%scan(&_program,-1,/)";'; put 'request_params='''';'; put 'output;stop;'; put 'proc append base=&dc_libref..mpe_requests data=&syslast force nowarn;'; put 'run;'; put '%end;'; put '%mend mpeterm;'; put '* SAS Macros end;'; put '* SAS Includes start;'; put '* SAS Includes end;'; put '* Binary Files start;'; put '* Binary Files end;'; put '* ServiceInit start;'; put 'options noquotelenmax ps=max;'; put '/* create dummy macros to prevent stpbegin / stpend from corrupting sessions */'; put '%macro stpbegin();'; put '%put NOTE: the STPBEGIN macro should not be used for web apps!;'; put '%mend stpbegin;'; put '%macro stpend();'; put '%put NOTE: the STPEND macro should not be used for web apps!;'; put '%mend stpend;'; put '* ServiceInit end;'; put '* Service start;'; put '/**'; put '@file'; put '@brief Generic validator for editable libraries'; put '@details The input table is simply one row from the target table in table'; put 'called "work.source_row".'; put 'Available macro variables:'; put '@li MPELIB - The DC control library'; put '@li LIBDS - The library.dataset being filtered'; put '@li VARIABLE_NM - The column being filtered'; put '

Service Outputs

'; put 'The values provided below are generic samples - we encourage you to replace'; put 'these with realistic values in your own deployments.'; put '
DYNAMIC_VALUES
'; put 'The RAW_VALUE column may be charactor or numeric. If DISPLAY_INDEX is not'; put 'provided, it is added automatically.'; put '|DISPLAY_INDEX:best.|DISPLAY_VALUE:$|RAW_VALUE|'; put '|---|---|---|'; put '|1|$77.43|77.43|'; put '|2|$88.43|88.43|'; put '
DYNAMIC_EXTENDED_VALUES
'; put 'This table is optional. If provided, it will map the DISPLAY_INDEX from the'; put 'DYNAMIC_VALUES table to additional column/value pairs, that will be used to'; put 'populate dropdowns for _other_ cells in the _same_ row.'; put 'Should be used sparingly! The use of large tables here can slow down the'; put 'browser.'; put '|DISPLAY_INDEX:best.|EXTRA_COL_NAME:$32.|DISPLAY_VALUE:$|DISPLAY_TYPE:$1.|RAW_VALUE_NUM|RAW_VALUE_CHAR:$5000|'; put '|---|---|---|'; put '|1|DISCOUNT_RT|"50%"|N|0.5||'; put '|1|DISCOUNT_RT|"40%"|N|0.4||'; put '|1|DISCOUNT_RT|"30%"|N|0.3||'; put '|1|CURRENCY_SYMBOL|"GBP"|C||"GBP"|'; put '|1|CURRENCY_SYMBOL|"RSD"|C||"RSD"|'; put '|2|DISCOUNT_RT|"50%"|N|0.5||'; put '|2|DISCOUNT_RT|"40%"|N|0.4||'; put '|2|CURRENCY_SYMBOL|"EUR"|C||"EUR"|'; put '|2|CURRENCY_SYMBOL|"HKD"|C||"HKD"|'; put '**/'; put '/* send back the raw and formatted values */'; put 'proc sql;'; put 'create table work.DYNAMIC_VALUES as'; put 'select distinct libref as display_value,'; put 'upcase(libref) as raw_value'; put 'from &mpelib..mpe_tables'; put 'where &dc_dttmtfmt. < tx_to'; put 'order by 1;'; put '* Service end;'; run; %mm_createwebservice(path=&appLoc/&path, name=&service, code=sascode, server=&serverName, replace=yes) filename sascode clear; %let service=mpe_alerts.alert_lib; filename sascode temp lrecl=32767; data _null_; file sascode; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '/**'; put '@file mp_jsonout.sas'; put '@brief Writes JSON in SASjs format to a fileref'; put '@details This macro can be used to OPEN a JSON stream and send one or more'; put 'tables as arrays of rows, where each row can be an object or a nested array.'; put 'There are two engines available - DATASTEP or PROCJSON.'; put 'PROC JSON is fast but will produce errs like the ones below if'; put 'special chars are encountered.'; put '> (ERR)OR: Some code points did not transcode.'; put '> An object or array close is not valid at this point in the JSON text.'; put '> Date value out of range'; put 'If this happens, try running with ENGINE=DATASTEP.'; put 'The DATASTEP engine is used to handle special SAS missing numerics, and'; put 'can also convert entire datasets to formatted values. Output JSON is always'; put 'in UTF-8.'; put 'Usage:'; put 'filename tmp temp;'; put 'data class; set sashelp.class;run;'; put '%mp_jsonout(OPEN,jref=tmp)'; put '%mp_jsonout(OBJ,class,jref=tmp)'; put '%mp_jsonout(OBJ,class,dslabel=class2,jref=tmp,showmeta=Y)'; put '%mp_jsonout(CLOSE,jref=tmp)'; put 'data _null_;'; put 'infile tmp;'; put 'input;putlog _infile_;'; put 'run;'; put 'If you are building web apps with SAS then you are strongly encouraged to use'; put 'the mX_createwebservice macros in combination with the'; put '[sasjs adapter](https://github.com/sasjs/adapter).'; put 'For more information see https://sasjs.io'; put '@param [in] action Valid values:'; put '@li OPEN - opens the JSON'; put '@li OBJ - sends a table with each row as an object'; put '@li ARR - sends a table with each row in an array'; put '@li CLOSE - closes the JSON'; put '@param [in] ds The dataset to send. Must be a work table.'; put '@param [out] jref= (_webout) The fileref to which to send the JSON'; put '@param [out] dslabel= The name to give the table in the exported JSON'; put '@param [in] fmt= (Y) Whether to keep (Y) or strip (N) formats from the table'; put '@param [in] engine= (DATASTEP) Which engine to use to send the JSON. Options:'; put '@li PROCJSON (default)'; put '@li DATASTEP (more reliable when data has non standard characters)'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to JSON'; put '

Related Files

'; put '@li mp_ds2fmtds.sas'; put '@version 9.2'; put '@author Allan Bowe'; put '@source https://github.com/sasjs/core'; put '**/'; put '%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y'; put ',engine=DATASTEP'; put ',missing=NULL'; put ',showmeta=N'; put ',maxobs=MAX'; put ')/*/STORE SOURCE*/;'; put '%local tempds colinfo fmtds i numcols numobs stmt_obs lastobs optval'; put 'tmpds1 tmpds2 tmpds3 tmpds4;'; put '%let numcols=0;'; put '%if &maxobs ne MAX %then %let stmt_obs=%str(if _n_>&maxobs then stop;);'; put '%if &action=OPEN %then %do;'; put 'options nobomfile;'; put 'data _null_;file &jref encoding=''utf-8'' lrecl=200;'; put 'put ''{"PROCESSED_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''"'';'; put 'run;'; put '%end;'; put '%else %if (&action=ARR or &action=OBJ) %then %do;'; put '/* force variable names to always be uppercase in the JSON */'; put 'options validvarname=upcase;'; put '/* To avoid issues with _webout on EBI - such as encoding diffs and truncation'; put '(https://support.sas.com/kb/49/325.html) we use temporary files */'; put 'filename _sjs1 temp lrecl=200 ;'; put 'data _null_; file _sjs1 encoding=''utf-8'';'; put 'put ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":";'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjs1 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs1 clear;'; put '/* grab col defs */'; put 'proc contents noprint data=&ds'; put 'out=_data_(keep=name type length format formatl formatd varnum label);'; put 'run;'; put '%let colinfo=%scan(&syslast,2,.);'; put 'proc sort data=&colinfo;'; put 'by varnum;'; put 'run;'; put '/* move meta to mac vars */'; put 'data &colinfo;'; put 'if _n_=1 then call symputx(''numcols'',nobs,''l'');'; put 'set &colinfo end=last nobs=nobs;'; put 'name=upcase(name);'; put '/* fix formats */'; put 'if type=2 or type=6 then do;'; put 'typelong=''char'';'; put 'length fmt $49.;'; put 'if format='''' then fmt=cats(''$'',length,''.'');'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else fmt=cats(format,formatl,''.'');'; put 'end;'; put 'else do;'; put 'typelong=''num'';'; put 'if format='''' then fmt=''best.'';'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else if formatd=0 then fmt=cats(format,formatl,''.'');'; put 'else fmt=cats(format,formatl,''.'',formatd);'; put 'end;'; put '/* 32 char unique name */'; put 'newname=''sasjs''!!substr(cats(put(md5(name),$hex32.)),1,27);'; put 'call symputx(cats(''name'',_n_),name,''l'');'; put 'call symputx(cats(''newname'',_n_),newname,''l'');'; put 'call symputx(cats(''length'',_n_),length,''l'');'; put 'call symputx(cats(''fmt'',_n_),fmt,''l'');'; put 'call symputx(cats(''type'',_n_),type,''l'');'; put 'call symputx(cats(''typelong'',_n_),typelong,''l'');'; put 'call symputx(cats(''label'',_n_),coalescec(label,name),''l'');'; put '/* overwritten when fmt=Y and a custom format exists in catalog */'; put 'if typelong=''num'' then call symputx(cats(''fmtlen'',_n_),200,''l'');'; put 'else call symputx(cats(''fmtlen'',_n_),min(32767,ceil((length+10)*1.5)),''l'');'; put 'run;'; put '%let tempds=%substr(_%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql;'; put 'select count(*) into: lastobs from &ds;'; put '%if &maxobs ne MAX %then %let lastobs=%sysfunc(min(&lastobs,&maxobs));'; put '%if &engine=PROCJSON %then %do;'; put '%if &missing=STRING %then %do;'; put '%put &sysmacroname: Special Missings not supported in proc json.;'; put '%put &sysmacroname: Switching to DATASTEP engine;'; put '%goto datastep;'; put '%end;'; put 'data &tempds;'; put 'set &ds;'; put '&stmt_obs;'; put '%if &fmt=N %then format _numeric_ best32.;;'; put '/* PRETTY is necessary to avoid line truncation in large files */'; put 'filename _sjs2 temp lrecl=131068 encoding=''utf-8'';'; put 'proc json out=_sjs2 pretty'; put '%if &action=ARR %then nokeys ;'; put ';export &tempds / nosastags fmtnumeric;'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs2 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs2 clear;'; put '%end;'; put '%else %if &engine=DATASTEP %then %do;'; put '%datastep:'; put '%if %sysfunc(exist(&ds)) ne 1 & %sysfunc(exist(&ds,VIEW)) ne 1'; put '%then %do;'; put '%put &sysmacroname: &ds NOT FOUND!!!;'; put '%return;'; put '%end;'; put '%if &fmt=Y %then %do;'; put '/**'; put '* Extract format definitions'; put '* First, by getting library locations from dictionary.formats'; put '* Then, by exporting the width using proc format'; put '* Cannot use maxw from sashelp.vformat as not always populated'; put '* Cannot use fmtinfo() as not supported in all flavours'; put '*/'; put '%let tmpds1=%substr(fmtsum%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds2=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds3=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds4=%substr(col%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql noprint;'; put 'create table &tmpds1 as'; put 'select cats(libname,''.'',memname) as FMTCAT,'; put 'FMTNAME'; put 'from dictionary.formats'; put 'where fmttype=''F'' and libname is not null'; put 'and fmtname in (select format from &colinfo where format is not null)'; put 'order by 1;'; put 'create table &tmpds2('; put 'FMTNAME char(32),'; put 'LENGTH num'; put ');'; put '%local catlist cat fmtlist i;'; put 'select distinct fmtcat into: catlist separated by '' '' from &tmpds1;'; put '%do i=1 %to %sysfunc(countw(&catlist,%str( )));'; put '%let cat=%scan(&catlist,&i,%str( ));'; put 'proc sql;'; put 'select distinct fmtname into: fmtlist separated by '' '''; put 'from &tmpds1 where fmtcat="&cat";'; put 'proc format lib=&cat cntlout=&tmpds3(keep=fmtname length);'; put 'select &fmtlist;'; put 'run;'; put 'proc sql;'; put 'insert into &tmpds2 select distinct fmtname,length from &tmpds3;'; put '%end;'; put 'proc sql;'; put 'create table &tmpds4 as'; put 'select a.*, b.length as MAXW'; put 'from &colinfo a'; put 'left join &tmpds2 b'; put 'on cats(a.format)=cats(upcase(b.fmtname))'; put 'order by a.varnum;'; put 'data _null_;'; put 'set &tmpds4;'; put 'if not missing(maxw);'; put 'call symputx('; put 'cats(''fmtlen'',_n_),'; put '/* vars need extra padding due to JSON escaping of special chars */'; put 'min(32767,ceil((max(length,maxw)+10)*1.5))'; put ',''l'''; put ');'; put 'run;'; put '/* configure varlenchk - as we are explicitly shortening the variables */'; put '%let optval=%sysfunc(getoption(varlenchk));'; put 'options varlenchk=NOWARN;'; put 'data _data_(compress=char);'; put '/* shorten the new vars */'; put 'length'; put '%do i=1 %to &numcols;'; put '&&name&i $&&fmtlen&i'; put '%end;'; put ';'; put '/* rename on entry */'; put 'set &ds(rename=('; put '%do i=1 %to &numcols;'; put '&&name&i=&&newname&i'; put '%end;'; put '));'; put '&stmt_obs;'; put 'drop'; put '%do i=1 %to &numcols;'; put '&&newname&i'; put '%end;'; put ';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=num %then %do;'; put '&&name&i=cats(put(&&newname&i,&&fmt&i));'; put '%end;'; put '%else %do;'; put '&&name&i=put(&&newname&i,&&fmt&i);'; put '%end;'; put '%end;'; put 'if _error_ then do;'; put 'call symputx(''syscc'',1012);'; put 'stop;'; put 'end;'; put 'run;'; put '%let fmtds=&syslast;'; put 'options varlenchk=&optval;'; put '%end;'; put 'proc format; /* credit yabwon for special null removal */'; put 'value bart (default=40)'; put '%if &missing=NULL %then %do;'; put '._ - .z = null'; put '%end;'; put '%else %do;'; put '._ = [quote()]'; put '. = null'; put '.a - .z = [quote()]'; put '%end;'; put 'other = [best.];'; put 'data &tempds;'; put 'attrib _all_ label='''';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'length &&name&i $&&fmtlen&i...;'; put 'format &&name&i $&&fmtlen&i...;'; put '%end;'; put '%end;'; put '%if &fmt=Y %then %do;'; put 'set &fmtds;'; put '%end;'; put '%else %do;'; put 'set &ds;'; put '%end;'; put '&stmt_obs;'; put 'format _numeric_ bart.;'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'if findc(&&name&i,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put '&&name&i=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,&&name&i)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else &&name&i=quote(cats(&&name&i));'; put '%end;'; put '%end;'; put 'run;'; put 'filename _sjs3 temp lrecl=131068 ;'; put 'data _null_;'; put 'file _sjs3 encoding=''utf-8'';'; put 'if _n_=1 then put "[";'; put 'set &tempds;'; put 'if _n_>1 then put "," @; put'; put '%if &action=ARR %then "[" ; %else "{" ;'; put '%do i=1 %to &numcols;'; put '%if &i>1 %then "," ;'; put '%if &action=OBJ %then """&&name&i"":" ;'; put '"&&name&i"n /* name literal for reserved variable names */'; put '%end;'; put '%if &action=ARR %then "]" ; %else "}" ; ;'; put '/* close out the table */'; put 'data _null_;'; put 'file _sjs3 mod encoding=''utf-8'';'; put 'put '']'';'; put 'run;'; put 'data _null_;'; put 'infile _sjs3 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs3 clear;'; put '%end;'; put 'proc sql;'; put 'drop table &colinfo, &tempds;'; put '%if %substr(&showmeta,1,1)=Y %then %do;'; put 'filename _sjs4 temp lrecl=131068 encoding=''utf-8'';'; put 'data _null_;'; put 'file _sjs4;'; put 'length label $350;'; put 'put ", ""$%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":{""vars"":{";'; put 'do i=1 to &numcols;'; put 'name=quote(trim(symget(cats(''name'',i))));'; put 'format=quote(trim(symget(cats(''fmt'',i))));'; put 'label=quote(prxchange(''s/\\/\\\\/'',-1,trim(symget(cats(''label'',i)))));'; put 'length=quote(trim(symget(cats(''length'',i))));'; put 'type=quote(trim(symget(cats(''typelong'',i))));'; put 'if i>1 then put "," @@;'; put 'put name '':{"format":'' format '',"label":'' label'; put ''',"length":'' length '',"type":'' type ''}'';'; put 'end;'; put 'put ''}}'';'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs4 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs4 clear;'; put '%end;'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put 'data _null_; file &jref encoding=''utf-8'' mod ;'; put 'put "}";'; put 'run;'; put '%end;'; put '%mend mp_jsonout;'; put '/**'; put '@file mm_webout.sas'; put '@brief Send data to/from SAS Stored Processes'; put '@details This macro should be added to the start of each Stored Process,'; put '**immediately** followed by a call to:'; put '%mm_webout(FETCH)'; put 'This will read all the input data and create same-named SAS datasets in the'; put 'WORK library. You can then insert your code, and send data back using the'; put 'following syntax:'; put 'data some datasets; * make some data ;'; put 'retain some columns;'; put 'run;'; put '%mm_webout(OPEN)'; put '%mm_webout(ARR,some) * Array format, fast, suitable for large tables ;'; put '%mm_webout(OBJ,datasets) * Object format, easier to work with ;'; put 'Finally, wrap everything up send some helpful system variables too'; put '%mm_webout(CLOSE)'; put '@param [in] action Either FETCH, OPEN, ARR, OBJ or CLOSE'; put '@param [in] ds The dataset to send back to the frontend'; put '@param [out] dslabel= Value to use instead of table name for sending to JSON'; put '@param [in] fmt= (N) Setting Y converts all vars to their formatted values'; put '@param [out] fref= (_webout) The fileref to which to write the JSON'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] workobs= (0) When set to a positive integer, will create a new'; put 'output object (WORK) which contains this number of observations from all'; put 'tables in the WORK library.'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to output JSON'; put '

SAS Macros

'; put '@li mp_jsonout.sas'; put '

Related Macros

'; put '@li ms_webout.sas'; put '@li mv_webout.sas'; put '@version 9.3'; put '@author Allan Bowe'; put '**/'; put '%macro mm_webout(action,ds,dslabel=,fref=_webout,fmt=N,missing=NULL'; put ',showmeta=N,maxobs=MAX,workobs=0'; put ');'; put '%global _webin_file_count _webin_fileref1 _webin_name1 _program _debug'; put 'sasjs_tables;'; put '%local i tempds jsonengine;'; put '/* see https://github.com/sasjs/core/issues/41 */'; put '%if "%upcase(&SYSENCODING)" ne "UTF-8" %then %let jsonengine=PROCJSON;'; put '%else %let jsonengine=DATASTEP;'; put '%if &action=FETCH %then %do;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'options mprint notes mprintnest;'; put '%end;'; put '%let _webin_file_count=%eval(&_webin_file_count+0);'; put '/* now read in the data */'; put '%do i=1 %to &_webin_file_count;'; put '%if &_webin_file_count=1 %then %do;'; put '%let _webin_fileref1=&_webin_fileref;'; put '%let _webin_name1=&_webin_name;'; put '%end;'; put 'data _null_;'; put 'infile &&_webin_fileref&i termstr=crlf;'; put 'input;'; put 'call symputx(''input_statement'',_infile_);'; put 'putlog "&&_webin_name&i input statement: " _infile_;'; put 'stop;'; put 'data &&_webin_name&i;'; put 'infile &&_webin_fileref&i firstobs=2 dsd termstr=crlf encoding=''utf-8'';'; put 'input &input_statement;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'if _n_<20 then putlog _infile_;'; put '%end;'; put 'run;'; put '%let sasjs_tables=&sasjs_tables &&_webin_name&i;'; put '%end;'; put '%end;'; put '%else %if &action=OPEN %then %do;'; put '/* fix encoding */'; put 'OPTIONS NOBOMFILE;'; put '/**'; put '* check xengine type to avoid the below err message:'; put '* > Function is only valid for filerefs using the CACHE access method.'; put '*/'; put 'data _null_;'; put 'set sashelp.vextfl(where=(fileref="_WEBOUT"));'; put 'if xengine=''STREAM'' then do;'; put 'rc=stpsrv_header(''Content-type'',"text/html; encoding=utf-8");'; put 'end;'; put 'run;'; put '/* setup json */'; put 'data _null_;file &fref encoding=''utf-8'';'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutBEGIN<<'';'; put '%end;'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'run;'; put '%end;'; put '%else %if &action=ARR or &action=OBJ %then %do;'; put '%mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt,jref=&fref'; put ',engine=&jsonengine,missing=&missing,showmeta=&showmeta,maxobs=&maxobs'; put ')'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put '/* To avoid issues with _webout on EBI we use a temporary file */'; put 'filename _sjsref temp lrecl=131068;'; put '%if %str(&workobs) > 0 %then %do;'; put '/* if debug mode, send back first XX records of each work table also */'; put 'data;run;%let tempds=%scan(&syslast,2,.);'; put 'ods output Members=&tempds;'; put 'proc datasets library=WORK memtype=data;'; put '%local wtcnt;%let wtcnt=0;'; put 'data _null_;'; put 'set &tempds;'; put 'if not (upcase(name) =:"DATA"); /* ignore temp datasets */'; put 'i+1;'; put 'call symputx(cats(''wt'',i),name,''l'');'; put 'call symputx(''wtcnt'',i,''l'');'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put ",""WORK"":{";'; put '%do i=1 %to &wtcnt;'; put '%let wt=&&wt&i;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'dsid=open("WORK.&wt",''is'');'; put 'nlobs=attrn(dsid,''NLOBS'');'; put 'nvars=attrn(dsid,''NVARS'');'; put 'rc=close(dsid);'; put 'if &i>1 then put '',''@;'; put 'put " ""&wt"" : {";'; put 'put ''"nlobs":'' nlobs;'; put 'put '',"nvars":'' nvars;'; put '%mp_jsonout(OBJ,&wt,jref=_sjsref,dslabel=first10rows,showmeta=Y'; put ',maxobs=&workobs'; put ')'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put '%end;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put 'run;'; put '%end;'; put '/* close off json */'; put 'data _null_;file _sjsref mod encoding=''utf-8'';'; put 'length SYSPROCESSNAME syserrortext syswarningtext autoexec $512;'; put 'put ",""_DEBUG"" : ""&_debug"" ";'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'autoexec=quote(urlencode(trim(getoption(''autoexec''))));'; put 'put '',"AUTOEXEC" : '' autoexec;'; put 'put ",""MF_GETUSER"" : ""%mf_getuser()"" ";'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'put ",""SYSENCODING"" : ""&sysencoding"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSPROCESSID"" : ""&SYSPROCESSID"" ";'; put 'put ",""SYSPROCESSMODE"" : ""&SYSPROCESSMODE"" ";'; put 'SYSPROCESSNAME=quote(urlencode(cats(SYSPROCESSNAME)));'; put 'put ",""SYSPROCESSNAME"" : " SYSPROCESSNAME;'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put '',"SYSWARNINGTEXT" : '' syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'length memsize $32;'; put 'memsize="%sysfunc(INPUTN(%sysfunc(getoption(memsize)), best.),sizekmg.)";'; put 'memsize=quote(cats(memsize));'; put 'put '',"MEMSIZE" : '' memsize;'; put 'put "}" @;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutEND<<'';'; put '%end;'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjsref lrecl=1 recfm=n;'; put 'file &fref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjsref clear;'; put '%end;'; put '%mend mm_webout;'; put '%macro webout(action,ds,dslabel=,fmt=,missing=NULL,showmeta=NO,maxobs=MAX);'; put '%mm_webout(&action,ds=&ds,dslabel=&dslabel,fmt=&fmt'; put ',missing=&missing'; put ',showmeta=&showmeta'; put ',maxobs=&maxobs'; put ') %mend;'; put '/* provide additional debug info */'; put '%global _program;'; put '%put &=syscc;'; put '%put user=%mf_getuser();'; put '%put pgm=&_program;'; put '%put timestamp=%sysfunc(datetime(),datetime19.);'; put '* Service Variables start;'; put '* Service Variables end;'; put '* SAS Macros start;'; put '%macro mf_getapploc(pgm);'; put '%if "&pgm"="" %then %do;'; put '%if %symexist(_program) %then %let pgm=&_program;'; put '%else %do;'; put '%put &sysmacroname: No value provided and no _program variable available;'; put '%return;'; put '%end;'; put '%end;'; put '%local root;'; put '/**'; put '* First check we are not in the tests/macros folder (which has no subfolders)'; put '* or specifically in the testsetup or testteardown services'; put '*/'; put '%if %index(&pgm,/tests/macros/)'; put 'or %index(&pgm,/tests/testsetup)'; put 'or %index(&pgm,/tests/testteardown)'; put '%then %do;'; put '%let root=%substr(&pgm,1,%index(&pgm,/tests)-1);'; put '&root'; put '%return;'; put '%end;'; put '/**'; put '* Next, move up two levels to avoid matches on subfolder or service name'; put '*/'; put '%let root=%substr(&pgm,1,%length(&pgm)-%length(%scan(&pgm,-1,/))-1);'; put '%let root=%substr(&root,1,%length(&root)-%length(%scan(&root,-1,/))-1);'; put '%if %index(&root,/tests/) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/tests/)-1);'; put '%end;'; put '%else %if %index(&root,/services) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/services)-1);'; put '%end;'; put '%else %if %index(&root,/jobs) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/jobs)-1);'; put '%end;'; put '%else %put &sysmacroname: Could not find an app location from &pgm;'; put '&root'; put '%mend mf_getapploc ;'; put '%macro mf_getuniquefileref(prefix=_,maxtries=1000,lrecl=32767);'; put '%local rc fname;'; put '%if &prefix=0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%end;'; put '%else %do;'; put '%local x len;'; put '%let len=%eval(8-%length(&prefix));'; put '%let x=0;'; put '%do x=0 %to &maxtries;'; put '%let fname=&prefix%substr(%sysfunc(ranuni(0)),3,&len);'; put '%if %sysfunc(fileref(&fname)) > 0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%return;'; put '%end;'; put '%end;'; put '%put unable to find available fileref after &maxtries attempts;'; put '%end;'; put '%mend mf_getuniquefileref;'; put '%macro mp_abort(mac=mp_abort.sas, type=, msg=, iftrue=%str(1=1)'; put ', errds=work.mp_abort_errds'; put ', mode=REGULAR'; put ')/*/STORE SOURCE*/;'; put '%global sysprocessmode sysprocessname sasjs_stpsrv_header_loc sasjsprocessmode;'; put '%local fref fid i;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%put NOTE: /// mp_abort macro executing //;'; put '%if %length(&mac)>0 %then %put NOTE- called by &mac;'; put '%put NOTE - &msg;'; put '%if %symexist(_SYSINCLUDEFILEDEVICE)'; put '/* abort cancel FILE does not restart outside the INCLUDE on Viya 3.5 */'; put 'and %superq(SYSPROCESSNAME) ne %str(Compute Server)'; put '%then %do;'; put '%if "*&_SYSINCLUDEFILEDEVICE*" ne "**" %then %do;'; put 'data &errds;'; put 'iftrue=''1=1'';'; put 'length mac $100 msg $5000;'; put 'mac=symget(''mac'');'; put 'msg=symget(''msg'');'; put 'run;'; put 'data _null_;'; put 'abort cancel FILE;'; put 'run;'; put '%return;'; put '%end;'; put '%end;'; put '/* Web App Context */'; put '%if %symexist(_PROGRAM)'; put 'or %superq(SYSPROCESSNAME) = %str(Compute Server)'; put 'or &mode=INCLUDE'; put '%then %do;'; put 'options obs=max replace mprint;'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5"'; put '%then %do;'; put 'options nosyntaxcheck;'; put '%end;'; put '%if &mode=INCLUDE %then %do;'; put '%if %sysfunc(exist(&errds))=1 %then %do;'; put 'data _null_;'; put 'set &errds;'; put 'call symputx(''iftrue'',iftrue,''l'');'; put 'call symputx(''mac'',mac,''l'');'; put 'call symputx(''msg'',msg,''l'');'; put 'putlog (_all_)(=);'; put 'run;'; put '%if (&iftrue)=0 %then %return;'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: No include errors found;'; put '%return;'; put '%end;'; put '%end;'; put '/* extract log errs / warns, if exist */'; put '%local logloc logline;'; put '%global logmsg; /* capture global messages */'; put '%if %symexist(SYSPRINTTOLOG) %then %let logloc=&SYSPRINTTOLOG;'; put '%else %let logloc=%qsysfunc(getoption(LOG));'; put 'proc printto log=log;run;'; put '%let logline=0;'; put '%if %length(&logloc)>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input; putlog _infile_;'; put 'i=1;'; put 'retain logonce 0;'; put 'if ('; put '_infile_=:"%str(WARN)ING" or _infile_=:"%str(ERR)OR"'; put ') and logonce=0 then'; put 'do;'; put 'call symputx(''logline'',_n_);'; put 'logonce+1;'; put 'end;'; put 'run;'; put '/* capture log including lines BEFORE the err */'; put '%if &logline>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input;'; put 'i=1;'; put 'stoploop=0;'; put 'if _n_ ge &logline-15 and stoploop=0 then do until (i>22);'; put 'call symputx(''logmsg'',catx(''\n'',symget(''logmsg''),_infile_));'; put 'input;'; put 'i+1;'; put 'stoploop=1;'; put 'end;'; put 'if stoploop=1 then stop;'; put 'run;'; put '%end;'; put '%end;'; put '%if %symexist(SYS_JES_JOB_URI) %then %do;'; put '/* setup webout for Viya */'; put 'options nobomfile;'; put '%if "X&SYS_JES_JOB_URI.X"="XX" %then %do;'; put 'filename _webout temp lrecl=999999 mod;'; put '%end;'; put '%else %do;'; put 'filename _webout filesrvc parenturi="&SYS_JES_JOB_URI"'; put 'name="_webout.json" lrecl=999999 mod;'; put '%end;'; put '%end;'; put '%else %if %sysfunc(filename(fref,&sasjs_stpsrv_header_loc))=0 %then %do;'; put 'options nobomfile;'; put '/* set up http header for SASjs Server */'; put '%let fid=%sysfunc(fopen(&fref,A));'; put '%if &fid=0 %then %do;'; put '%put %str(ERR)OR: %sysfunc(sysmsg());'; put '%return;'; put '%end;'; put '%let rc=%sysfunc(fput(&fid,%str(Content-Type: application/json)));'; put '%let rc=%sysfunc(fwrite(&fid));'; put '%let rc=%sysfunc(fclose(&fid));'; put '%let rc=%sysfunc(filename(&fref));'; put '%end;'; put '/* send response in SASjs JSON format */'; put 'data _null_;'; put 'file _webout mod lrecl=32000 encoding=''utf-8'';'; put 'length msg syswarningtext syserrortext $32767 mode $10 ;'; put 'sasdatetime=datetime();'; put 'msg=symget(''msg'');'; put '%if &logline>0 %then %do;'; put 'msg=cats(msg,''\n\nLog Extract:\n'',symget(''logmsg''));'; put '%end;'; put '/* escape the escapes */'; put 'msg=tranwrd(msg,''\'',''\\'');'; put '/* escape the quotes */'; put 'msg=tranwrd(msg,''"'',''\"'');'; put '/* ditch the CRLFs as chrome complains */'; put 'msg=compress(msg,,''kw'');'; put '/* quote without quoting the quotes (which are escaped instead) */'; put 'msg=cats(''"'',msg,''"'');'; put 'if symexist(''_debug'') then debug=quote(trim(symget(''_debug'')));'; put 'else debug=''""'';'; put 'if symget(''sasjsprocessmode'')=''Stored Program'' then mode=''SASJS'';'; put 'if mode ne ''SASJS'' then put ''>>weboutBEGIN<<'';'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'put '',"sasjsAbort" : [{'';'; put 'put '' "MSG":'' msg ;'; put 'put '' ,"MAC": "'' "&mac" ''"}]'';'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'put '',"_DEBUG":'' debug ;'; put 'if symexist(''_metauser'') then do;'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put 'end;'; put 'if symexist(''SYS_JES_JOB_URI'') then do;'; put 'SYS_JES_JOB_URI=quote(trim(symget(''SYS_JES_JOB_URI'')));'; put 'put '',"SYS_JES_JOB_URI": '' SYS_JES_JOB_URI;'; put 'end;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put ",""SYSWARNINGTEXT"" : " syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'put "}" ;'; put 'if mode ne ''SASJS'' then put ''>>weboutEND<<'';'; put 'run;'; put '%put _all_;'; put '%if "&sysprocessmode " = "SAS Stored Process Server " %then %do;'; put 'data _null_;'; put 'putlog ''stpsrvset program err and syscc'';'; put 'rc=stpsrvset(''program error'', 0);'; put 'call symputx("syscc",0,"g");'; put 'run;'; put '%if &sysscp=WIN'; put 'and 1=0 /* deprecating this logic until we figure out a consistent abort */'; put 'and "%substr(%str(&sysvlong ),1,8)"="9.04.01M"'; put 'and "%substr(%str(&sysvlong ),9,1)">"5" %then %do;'; put '/* skip approach (below) does not work in windows m6+ envs */'; put 'endsas;'; put '%end;'; put '%else %do;'; put '/**'; put '* endsas kills 9.4m3 deployments by orphaning multibridges.'; put '* Abort variants are ungraceful (non zero return code)'; put '* This approach lets SAS run silently until the end :-)'; put '* Caution - fails when called within a %include within a macro'; put '* Use mp_include() to handle this.'; put '*/'; put 'filename skip temp;'; put 'data _null_;'; put 'file skip;'; put 'put ''%macro skip();'';'; put 'comment ''%mend skip; -> fix lint '';'; put 'put ''%macro skippy();'';'; put 'comment ''%mend skippy; -> fix lint '';'; put 'run;'; put '%inc skip;'; put '%end;'; put '%end;'; put '%else %if "&sysprocessmode " = "SAS Compute Server " %then %do;'; put '/* endsas kills the session making it harder to fetch results */'; put 'data _null_;'; put 'syswarningtext=symget(''syswarningtext'');'; put 'syserrortext=symget(''syserrortext'');'; put 'abort_msg=symget(''msg'');'; put 'syscc=symget(''syscc'');'; put 'sysuserid=symget(''sysuserid'');'; put 'iftrue=symget(''iftrue'');'; put 'put (_all_)(/=);'; put 'call symputx(''syscc'',0);'; put 'abort cancel nolist;'; put 'run;'; put '%end;'; put '%else %do;'; put '%abort cancel;'; put '%end;'; put '%end;'; put '%else %do;'; put '%put _all_;'; put '%abort cancel;'; put '%end;'; put '%mend mp_abort;'; put '/** @endcond */'; put '%macro mm_getstpcode('; put 'tree=/User Folders/sasdemo/somestp'; put ',name='; put ',outloc=0'; put ',outref=0'; put ',mDebug=1'; put ',showlog=NO'; put ');'; put '%local mD;'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing &sysmacroname..sas;'; put '%&mD.put _local_;'; put '%if %length(&name)>0 %then %let name=/&name;'; put '/* first, check if STP exists */'; put '%local tsuri;'; put '%let tsuri=stopifempty ;'; put 'data _null_;'; put 'format type uri tsuri value $200.;'; put 'call missing (of _all_);'; put 'path="&tree&name(StoredProcess)";'; put '/* first, find the STP ID */'; put 'if metadata_pathobj("",path,"StoredProcess",type,uri)>0 then do;'; put '/* get sourcecode */'; put 'cnt=1;'; put 'do while (metadata_getnasn(uri,"Notes",cnt,tsuri)>0);'; put 'rc=metadata_getattr(tsuri,"Name",value);'; put '&mD.put tsuri= value=;'; put 'if value="SourceCode" then do;'; put '/* found it! */'; put 'rc=metadata_getattr(tsuri,"Id",value);'; put 'call symputx(''tsuri'',value,''l'');'; put 'stop;'; put 'end;'; put 'cnt+1;'; put 'end;'; put 'end;'; put 'else put (_all_)(=);'; put 'run;'; put '%mp_abort(iftrue= (&tsuri=stopifempty)'; put ',mac=mm_getstpcode'; put ',msg=%str(&tree&name.(StoredProcess) not found!)'; put ')'; put '/**'; put '* Now we can extract the textstore'; put '*/'; put 'filename __getdoc temp lrecl=10000000;'; put 'proc metadata'; put 'in="$METAREPOSITORY'; put ''; put 'SAS1"'; put 'out=__getdoc ;'; put 'run;'; put '/* find the beginning of the text */'; put '%local start;'; put 'data _null_;'; put 'infile __getdoc lrecl=10000;'; put 'input;'; put 'start=index(_infile_,''StoredText="'');'; put 'if start then do;'; put 'call symputx("start",start+11);'; put '*putlog ''"'' _infile_ ''"'';'; put 'end;'; put 'stop;'; put '%local outeng;'; put '%if "&outloc"="0" %then %let outeng=TEMP;'; put '%else %let outeng="&outloc";'; put '%local fref;'; put '%if &outref=0 %then %let fref=%mf_getuniquefileref();'; put '%else %let fref=&outref;'; put '/* read the content, byte by byte, resolving escaped chars */'; put 'filename &fref &outeng lrecl=100000;'; put 'data _null_;'; put 'length filein 8 fileid 8;'; put 'filein = fopen("__getdoc","I",1,"B");'; put 'fileid = fopen("&fref","O",1,"B");'; put 'rec = "20"x;'; put 'length entity $6;'; put 'do while(fread(filein)=0);'; put 'x+1;'; put 'if x>&start then do;'; put 'rc = fget(filein,rec,1);'; put 'if rec=''"'' then leave;'; put 'else if rec="&" then do;'; put 'entity=rec;'; put 'do until (rec=";");'; put 'if fread(filein) ne 0 then goto getout;'; put 'rc = fget(filein,rec,1);'; put 'entity=cats(entity,rec);'; put 'end;'; put 'select (entity);'; put 'when (''&'' ) rec=''&'' ;'; put 'when (''<'' ) rec=''<'' ;'; put 'when (''>'' ) rec=''>'' ;'; put 'when (''''') rec="''" ;'; put 'when (''"'') rec=''"'' ;'; put 'when ('' '') rec=''0A''x;'; put 'when ('' '') rec=''0D''x;'; put 'when (''$'' ) rec=''$'' ;'; put 'when ('' '') rec=''09''x;'; put 'otherwise putlog "%str(WARN)ING: missing value for " entity=;'; put 'end;'; put 'rc =fput(fileid, substr(rec,1,1));'; put 'rc =fwrite(fileid);'; put 'end;'; put 'else do;'; put 'rc =fput(fileid,rec);'; put 'rc =fwrite(fileid);'; put 'end;'; put 'end;'; put 'end;'; put 'getout:'; put 'rc=fclose(filein);'; put 'rc=fclose(fileid);'; put 'run;'; put '%if &showlog=YES %then %do;'; put 'data _null_;'; put 'infile &fref lrecl=32767 end=last;'; put 'input;'; put 'if _n_=1 then putlog ''>>stpcodeBEGIN<<'';'; put 'putlog _infile_;'; put 'if last then putlog ''>>stpcodeEND<<'';'; put 'run;'; put '%end;'; put 'filename __getdoc clear;'; put '%if &outref=0 %then %do;'; put 'filename &fref clear;'; put '%end;'; put '%mend mm_getstpcode;'; put '%macro dc_getsettings();'; put '%global _program;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(syscc=&syscc on entry (&syswarningtext &syserrortext))'; put ')'; put '%if %length(&_PROGRAM)>1 %then %let root=&_program;'; put '%else %do;'; put '%global _metauser;'; put '%let _metauser=&sysuserid;'; put '/* to mimic a "real" _program we need to give a dummy role and stp name */'; put '%let root=/dummyRole/dummyName;'; put '%end;'; put '/* the DC precode is stored in the Admin folder in the root of'; put 'the project. Lets find that root. */'; put '%put &=root;'; put '%let root=%mf_getapploc();'; put '%put &=root;'; put '/* Now we know the root location we can retrieve the params */'; put '%let temploc=%sysfunc(pathname(work))/settings.txt;'; put '%mm_getstpcode(tree=&root/services/public'; put ',name=Data_Controller_Settings'; put ',outloc=&temploc'; put ')'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Unable to run getstpcode)'; put ')'; put 'filename _getsets "&temploc" lrecl=2000;'; put '/*'; put 'Do not use mp_include here - this puts a copy in every service, which creates'; put 'compilation problems when calling services from mp_include'; put '*/'; put '%inc _getsets/source2;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Problem running &sysmacroname (&syswarningtext &syserrortext))'; put ')'; put '%mend dc_getsettings;'; put '%macro mf_fmtdttm('; put ')/*/STORE SOURCE*/;'; put '%if "&sysver"="9.2" or "&sysver"="9.3"'; put 'or ("&sysver"="9.4" and "%substr(&SYSVLONG,9,1)" le "3")'; put 'or "%substr(&sysver,1,1)"="4"'; put 'or "%substr(&sysver,1,1)"="5"'; put '%then %do;DATETIME19.3%end;'; put '%else %do;E8601DT26.6%end;'; put '%mend mf_fmtdttm;'; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '%macro mp_init(prefix=SASJS'; put ')/*/STORE SOURCE*/;'; put '%if %symexist(SASJS_PREFIX) %then %return; /* only run once */'; put '%global'; put 'SASJS_PREFIX /* the ONLY hard-coded global macro variable in SASjs */'; put '&prefix._FUNCTIONS /* used in mcf_init() to track core function compilation */'; put '&prefix._INIT_NUM /* initialisation time as numeric */'; put '&prefix._INIT_DTTM /* initialisation time in E8601DT26.6 format */'; put '&prefix.WORK /* avoid typing %sysfunc(pathname(work)) every time */'; put ';'; put '%let sasjs_prefix=&prefix;'; put 'data _null_;'; put 'dttm=datetime();'; put 'call symputx("&sasjs_prefix._init_num",dttm,''g'');'; put 'call symputx("&sasjs_prefix._init_dttm",put(dttm,E8601DT26.6),''g'');'; put 'call symputx("&sasjs_prefix.work",pathname(''WORK''),''g'');'; put 'run;'; put 'options'; put 'compress=CHAR /* default is none so ensure we have something! */'; put 'datastmtchk=ALLKEYWORDS /* protection from overwriting input datasets */'; put 'errorcheck=STRICT /* catch errs in libname/filename statements */'; put 'fmterr /* ensure err when a format cannot be found */'; put 'mergenoby=%str(ERR)OR /* throw err when a merge has no BY variables */'; put 'missing=. /* changing this can cause hard to detect errs */'; put 'noquotelenmax /* avoid warnings for long strings */'; put 'noreplace /* avoid overwriting permanent datasets */'; put 'ps=max /* reduce log size slightly */'; put 'ls=max /* reduce log even more and avoid word truncation */'; put 'validmemname=COMPATIBLE /* avoid special characters etc in table names */'; put 'validvarname=V7 /* avoid special characters etc in variable names */'; put 'varinitchk=%str(ERR)OR /* avoid data mistakes from variable name typos */'; put 'varlenchk=%str(ERR)OR /* fail hard if truncation (data loss) can result */'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5" %then %do;'; put 'noautocorrect /* disallow misspelled procedure names */'; put 'dsoptions=note2err /* undocumented - convert bad NOTEs to ERRs */'; put '%end;'; put ';'; put '%mend mp_init;'; put '%macro mpeinit(fetch=YES);'; put '%global mpeinit'; put 'mpeadmins /* group with unrestricted Meditor access */'; put 'mpelocapprovals /* location for landing and staging files */'; put 'mpelib /* location of configuration tables for DC */'; put 'dc_repo_users /* location of user / group metadata */'; put 'dc_licence_key /* extracted in dc_getsettings */'; put 'dc_activation_key /* extracted in dc_getsettings */'; put 'dc_locale /* extracted in dc_getsettings */'; put 'dc_dttmtfmt /* can be overridden in dc_getsettings */'; put '_debug'; put ';'; put '%if &mpeinit=1 %then %return;'; put '%else %let mpeinit=1;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(Problem on service startup (&syswarningtext &syserrortext))'; put ')'; put '%mp_init()'; put '%if &fetch=YES %then %do;'; put '%webout(FETCH)'; put '%end;'; put '%global _CLIENTNAME;'; put '%mp_abort(iftrue= (&_CLIENTNAME=SAS Enterprise Guide)'; put ',mac=&_program..sas'; put ',msg=%str(Data Controller is a web app and should not be executed from EG)'; put ')'; put 'options urlencoding=utf8 nobomfile lrecl=32767;'; put '%let perf=%sysfunc(datetime());'; put '%put perfdiff: 0;'; put '%let dc_locale=SYSTEM; /* default if not set */'; put '/**'; put '* E8601DT26.6 has widest database support - but not all SAS flavours can'; put '* handle it. Override in the settings STP if needed.'; put '*/'; put 'data _null_;'; put 'dc_dttmtfmt=''"%sysfunc(datetime(),''!!"%mf_fmtdttm()"!!'')"dt'';'; put 'call symputx(''dc_dttmtfmt'',dc_dttmtfmt);'; put 'put dc_dttmtfmt=;'; put 'run;'; put '%put &=dc_dttmtfmt;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc prior to dc_getsettings)'; put ')'; put '%dc_getsettings()'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc after dc_getsettings)'; put ')'; put 'data _null_;'; put 'set &DC_LIBREF..mpe_config(where=('; put 'var_scope="DC"'; put 'and &dc_dttmtfmt lt tx_to'; put 'and var_active=1'; put '));'; put 'call symputx(var_name,var_value,''G'');'; put 'putlog var_name "=" var_value;'; put 'run;'; put '%let mpelib=&dc_libref;'; put '%let mpeadmins=&dc_admin_group;'; put '%let mpelocapprovals=&dc_staging_area;'; put '%let dc_repo_users=&dc_repo_users;'; put '%if &dc_locale ne SYSTEM %then %do;'; put 'options locale=&dc_locale;'; put '%end;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(Problem during compilation or with STP precode (&syswarningtext))'; put ')'; put '%mend mpeinit;'; put '%macro mf_mval(var);'; put '%if %symexist(&var) %then %do;'; put '%superq(&var)'; put '%end;'; put '%mend mf_mval;'; put '%macro mf_trimstr(basestr,trimstr);'; put '%local baselen trimlen trimval;'; put '/* return if basestr is shorter than trimstr (or 0) */'; put '%let baselen=%length(%superq(basestr));'; put '%let trimlen=%length(%superq(trimstr));'; put '%if &baselen < &trimlen or &baselen=0 %then %return;'; put '/* obtain the characters from the end of basestr */'; put '%let trimval=%qsubstr(%superq(basestr)'; put ',%length(%superq(basestr))-&trimlen+1'; put ',&trimlen);'; put '/* compare and if matching, chop it off! */'; put '%if %superq(basestr)=%superq(trimstr) %then %do;'; put '%return;'; put '%end;'; put '%else %if %superq(trimval)=%superq(trimstr) %then %do;'; put '%qsubstr(%superq(basestr),1,%length(%superq(basestr))-&trimlen)'; put '%end;'; put '%else %do;'; put '&basestr'; put '%end;'; put '%mend mf_trimstr;'; put '%macro mf_getplatform(switch'; put ')/*/STORE SOURCE*/;'; put '%local a b c;'; put '%if &switch.NONE=NONE %then %do;'; put '%if %symexist(sasjsprocessmode) %then %do;'; put '%if &sasjsprocessmode=Stored Program %then %do;'; put 'SASJS'; put '%return;'; put '%end;'; put '%end;'; put '%if %symexist(sysprocessmode) %then %do;'; put '%if "&sysprocessmode"="SAS Object Server"'; put 'or "&sysprocessmode"= "SAS Compute Server" %then %do;'; put 'SASVIYA'; put '%end;'; put '%else %if "&sysprocessmode"="SAS Stored Process Server"'; put 'or "&sysprocessmode"="SAS Workspace Server"'; put '%then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if %symexist(_metaport) or %symexist(_metauser) %then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if &switch=SASSTUDIO %then %do;'; put '/* return the version of SAS Studio else 0 */'; put '%if %mf_mval(_CLIENTAPP)=%str(SAS Studio) %then %do;'; put '%let a=%mf_mval(_CLIENTVERSION);'; put '%let b=%scan(&a,1,.);'; put '%if %eval(&b >2) %then %do;'; put '&b'; put '%end;'; put '%else 0;'; put '%end;'; put '%else 0;'; put '%end;'; put '%else %if &switch=VIYARESTAPI %then %do;'; put '%mf_trimstr(%sysfunc(getoption(servicesbaseurl)),/)'; put '%end;'; put '%mend mf_getplatform;'; put '%macro mpeterm();'; put '%local oldloc;'; put 'data _null_;'; put 'if symexist(''SYSPRINTTOLOG'') then oldloc=symget(''SYSPRINTTOLOG'');'; put 'else oldloc=getoption(''LOG'');'; put 'if subpad(oldloc,1,1) not in (''"'',"''",'' '') then oldloc=quote(cats(oldloc));'; put 'call symputx(''oldloc'',oldloc,''l'');'; put 'run;'; put '%if %length(&oldloc)>0 %then %do;'; put 'proc printto log=log;'; put 'run;'; put 'data _null_;'; put 'infile &oldloc;'; put 'input; putlog _infile_;'; put 'run;'; put '%end;'; put '%if %sysfunc(exist(&dc_libref..mpe_requests)) and %mf_getplatform() ne SASVIYA'; put '%then %do;'; put 'data ;'; put 'if 0 then set &dc_libref..mpe_requests;'; put 'request_dttm=%sysfunc(datetime());'; put 'request_user="%mf_getuser()";'; put 'request_service="%scan(&_program,-2,/)/%scan(&_program,-1,/)";'; put 'request_params='''';'; put 'output;stop;'; put 'proc append base=&dc_libref..mpe_requests data=&syslast force nowarn;'; put 'run;'; put '%end;'; put '%mend mpeterm;'; put '%macro mm_getlibs('; put 'outds=work.mm_getLibs'; put ')/*/STORE SOURCE*/;'; put '/*'; put 'flags:'; put 'OMI_SUCCINCT (2048) Do not return attributes with null values.'; put 'OMI_GET_METADATA (256) Executes a GetMetadata call for each object that'; put 'is returned by the GetMetadataObjects method.'; put 'OMI_ALL_SIMPLE (8) Gets all of the attributes of the requested object.'; put '*/'; put 'data _null_;'; put 'flags=2048+256+8;'; put 'call symputx(''flags'',flags,''l'');'; put 'run;'; put '* use a temporary fileref to hold the response;'; put 'filename response temp;'; put '/* get list of libraries */'; put 'proc metadata in='; put ''''; put '$METAREPOSITORY'; put 'SASLibrary'; put ''; put 'SAS'; put '&flags'; put ''; put ''''; put 'out=response;'; put 'run;'; put '/* write the response to the log for debugging */'; put 'data _null_;'; put 'infile response lrecl=32767;'; put 'input;'; put 'put _infile_;'; put 'run;'; put '/* create an XML map to read the response */'; put 'filename sxlemap temp;'; put 'data _null_;'; put 'file sxlemap;'; put 'put '''';'; put 'put '''';'; put 'put ''//Objects/SASLibrary'';'; put 'put ''>17'';'; put 'put ''//Objects/SASLibrary/@Id'';'; put 'put ''256>'';'; put 'put ''//Objects/SASLibrary/@Name'';'; put 'put ''8'';'; put 'put ''//Objects/SASLibrary/@Libref'';'; put 'put ''>12'';'; put 'put ''//Objects/SASLibrary/@Engine'';'; put 'put ''
'';'; put 'run;'; put 'libname _XML_ xml xmlfileref=response xmlmap=sxlemap;'; put '/* sort the response by library name */'; put 'proc sort data=_XML_.saslibrary out=&outds;'; put 'by libraryname;'; put 'run;'; put '/* clear references */'; put 'filename sxlemap clear;'; put 'filename response clear;'; put 'libname _XML_ clear;'; put '%mend mm_getlibs;'; put '%macro mm_getrepos('; put 'outds=work.mm_getrepos'; put ')/*/STORE SOURCE*/;'; put '* use a temporary fileref to hold the response;'; put 'filename response temp;'; put '/* get list of libraries */'; put 'proc metadata in='; put '"1"'; put 'out=response;'; put 'run;'; put '/* write the response to the log for debugging */'; put '/*'; put 'data _null_;'; put 'infile response lrecl=1048576;'; put 'input;'; put 'put _infile_;'; put 'run;'; put '*/'; put '/* create an XML map to read the response */'; put 'filename sxlemap temp;'; put 'data _null_;'; put 'file sxlemap;'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository";'; put 'put "";'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Id";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Name";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Desc";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@DefaultNS";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@RepositoryType";'; put 'put "characterstring20";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@RepositoryFormat";'; put 'put "characterstring10";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@Access";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@CurrentAccess";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@PauseState";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Path";'; put 'put "";'; put 'put "characterstring256";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Engine";'; put 'put "";'; put 'put "characterstring8";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Options";'; put 'put "";'; put 'put "characterstring32";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@MetadataCreated";'; put 'put "characterstring24";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@MetadataUpdated";'; put 'put "characterstring24";'; put 'put '''';'; put 'put ''
'';'; put 'run;'; put 'libname _XML_ xml xmlfileref=response xmlmap=sxlemap;'; put 'proc sort data= _XML_.SASRepos out=&outds;'; put 'by name;'; put 'run;'; put '/* clear references */'; put 'filename sxlemap clear;'; put 'filename response clear;'; put 'libname _XML_ clear;'; put '%mend mm_getrepos;'; put '%macro dc_getlibs(outds=mm_getlibs);'; put '/* take current repository */'; put '%local repo repocnt x;'; put '%let repo=%sysfunc(getoption(metarepository));'; put '/* get list of repositories and filter */'; put '%mm_getrepos(outds=repos)'; put '%let repocnt=0;'; put 'data repos;'; put 'set repos;'; put 'where repositorytype in(''CUSTOM'',''FOUNDATION'')'; put '& upcase(name) ne "%upcase(&repo)";'; put 'keep id name ;'; put 'call symputx(''repo''!!cats(_n_),name,''l'');'; put 'call symputx(''repocnt'',_n_,''l'');'; put 'run;'; put '%put _local_;'; put '%mm_getlibs(outds=&outds)'; put '%do x=1 %to &repocnt;'; put 'options metarepository=&&repo&x;'; put '%mm_getlibs(outds=&outds.a)'; put 'proc append base=&outds data=&outds.a;'; put 'run;'; put '%end;'; put 'options metarepository=&repo;'; put '%mend dc_getlibs;'; put '* SAS Macros end;'; put '* SAS Includes start;'; put '* SAS Includes end;'; put '* Binary Files start;'; put '* Binary Files end;'; put '* ServiceInit start;'; put 'options noquotelenmax ps=max;'; put '/* create dummy macros to prevent stpbegin / stpend from corrupting sessions */'; put '%macro stpbegin();'; put '%put NOTE: the STPBEGIN macro should not be used for web apps!;'; put '%mend stpbegin;'; put '%macro stpend();'; put '%put NOTE: the STPEND macro should not be used for web apps!;'; put '%mend stpend;'; put '* ServiceInit end;'; put '* Service start;'; put '/**'; put '@file'; put '@brief fetch extended values for alert_lib'; put '@details Fetches libraries from mpe_tables, creates extended values for'; put 'alert_ds, and marks "*ALL*" as the forced (default) value.'; put 'Available macro variables:'; put '@li DC_LIBREF - The DC control library'; put '@li LIBDS - The library.dataset being filtered'; put '@li VARIABLE_NM - The column being filtered'; put '

Service Outputs

'; put 'Output should be a single table called "work.dynamic_values" in the format'; put 'below. display_value should always be character, raw_value is unformatted'; put 'character/numeric.'; put '
DYNAMIC_VALUES
'; put 'The RAW_VALUE column may be charactor or numeric. If DISPLAY_INDEX is not'; put 'provided, it is added automatically.'; put '|DISPLAY_INDEX:best.|DISPLAY_VALUE:$|RAW_VALUE|'; put '|---|---|---|'; put '|1|$77.43|77.43|'; put '|2|$88.43|88.43|'; put '
DYNAMIC_EXTENDED_VALUES
'; put 'This table is optional. If provided, it will map the DISPLAY_INDEX from the'; put 'DYNAMIC_VALUES table to additional column/value pairs, that will be used to'; put 'populate dropdowns for _other_ cells in the _same_ row.'; put 'Should be used sparingly! The use of large tables here can slow down the'; put 'browser.'; put 'The FORCED_VALUE column can be used to force an extended value to be selected'; put 'by default when a particular value is chosen.'; put '|DISPLAY_INDEX:best.|EXTRA_COL_NAME:$32.|DISPLAY_VALUE:$|DISPLAY_TYPE:$1.|RAW_VALUE_NUM|RAW_VALUE_CHAR:$5000|FORCED_VALUE|'; put '|---|---|---|---|'; put '|1|DISCOUNT_RT|"50%"|N|0.5||.|'; put '|1|DISCOUNT_RT|"40%"|N|0.4||0|'; put '|1|DISCOUNT_RT|"30%"|N|0.3||1|'; put '|1|CURRENCY_SYMBOL|"GBP"|C||"GBP"|.|'; put '|1|CURRENCY_SYMBOL|"RSD"|C||"RSD"|.|'; put '|2|DISCOUNT_RT|"50%"|N|0.5||.|'; put '|2|DISCOUNT_RT|"40%"|N|0.4||1|'; put '|2|CURRENCY_SYMBOL|"EUR"|C||"EUR"|.|'; put '|2|CURRENCY_SYMBOL|"HKD"|C||"HKD"|1|'; put '

SAS Macros

'; put '@li dc_getlibs.sas'; put '**/'; put '%mp_abort(iftrue= ("%upcase(&libds)" ne "&DC_LIBREF..MPE_ALERTS" )'; put ',mac=&_program'; put ',msg=%str('; put 'Invalid validation, expected MPE_ALERTS.ALERT_LIB, got %superq(libds)'; put ')'; put ')'; put 'proc sql;'; put 'create table work.source as'; put 'select libref,dsn'; put 'from &DC_LIBREF..MPE_TABLES'; put 'where tx_to > &dc_dttmtfmt.'; put 'order by 1,2;'; put 'data work.DYNAMIC_VALUES (keep=display_index raw_value display_value);'; put 'set work.source end=last;'; put 'by libref;'; put 'if last.libref then do;'; put 'display_index+1;'; put 'raw_value=libref;'; put 'display_value=libref;'; put 'output;'; put 'end;'; put 'if last then do;'; put 'display_index+1;'; put 'raw_value=''*ALL*'';'; put 'display_value=''*ALL*'';'; put 'output;'; put 'end;'; put 'run;'; put 'data work.dynamic_extended_values(keep=display_index extra_col_name display_type'; put 'display_value RAW_VALUE_CHAR raw_value_num forced_value);'; put 'set work.source end=last;'; put 'by libref dsn;'; put 'retain extra_col_name ''ALERT_DS'';'; put 'retain display_type ''C'';'; put 'retain raw_value_num .;'; put 'raw_value_char=dsn;'; put 'display_value=dsn;'; put 'forced_value=0;'; put 'if first.libref then display_index+1;'; put 'if last.libref then do;'; put 'display_value=''*ALL*'';'; put 'raw_value_char=''*ALL*'';'; put 'forced_value=1;'; put 'output;'; put 'end;'; put 'else output;'; put 'run;'; put '* Service end;'; run; %mm_createwebservice(path=&appLoc/&path, name=&service, code=sascode, server=&serverName, replace=yes) filename sascode clear; %let service=mpe_tables.dsn; filename sascode temp lrecl=32767; data _null_; file sascode; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '/**'; put '@file mp_jsonout.sas'; put '@brief Writes JSON in SASjs format to a fileref'; put '@details This macro can be used to OPEN a JSON stream and send one or more'; put 'tables as arrays of rows, where each row can be an object or a nested array.'; put 'There are two engines available - DATASTEP or PROCJSON.'; put 'PROC JSON is fast but will produce errs like the ones below if'; put 'special chars are encountered.'; put '> (ERR)OR: Some code points did not transcode.'; put '> An object or array close is not valid at this point in the JSON text.'; put '> Date value out of range'; put 'If this happens, try running with ENGINE=DATASTEP.'; put 'The DATASTEP engine is used to handle special SAS missing numerics, and'; put 'can also convert entire datasets to formatted values. Output JSON is always'; put 'in UTF-8.'; put 'Usage:'; put 'filename tmp temp;'; put 'data class; set sashelp.class;run;'; put '%mp_jsonout(OPEN,jref=tmp)'; put '%mp_jsonout(OBJ,class,jref=tmp)'; put '%mp_jsonout(OBJ,class,dslabel=class2,jref=tmp,showmeta=Y)'; put '%mp_jsonout(CLOSE,jref=tmp)'; put 'data _null_;'; put 'infile tmp;'; put 'input;putlog _infile_;'; put 'run;'; put 'If you are building web apps with SAS then you are strongly encouraged to use'; put 'the mX_createwebservice macros in combination with the'; put '[sasjs adapter](https://github.com/sasjs/adapter).'; put 'For more information see https://sasjs.io'; put '@param [in] action Valid values:'; put '@li OPEN - opens the JSON'; put '@li OBJ - sends a table with each row as an object'; put '@li ARR - sends a table with each row in an array'; put '@li CLOSE - closes the JSON'; put '@param [in] ds The dataset to send. Must be a work table.'; put '@param [out] jref= (_webout) The fileref to which to send the JSON'; put '@param [out] dslabel= The name to give the table in the exported JSON'; put '@param [in] fmt= (Y) Whether to keep (Y) or strip (N) formats from the table'; put '@param [in] engine= (DATASTEP) Which engine to use to send the JSON. Options:'; put '@li PROCJSON (default)'; put '@li DATASTEP (more reliable when data has non standard characters)'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to JSON'; put '

Related Files

'; put '@li mp_ds2fmtds.sas'; put '@version 9.2'; put '@author Allan Bowe'; put '@source https://github.com/sasjs/core'; put '**/'; put '%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y'; put ',engine=DATASTEP'; put ',missing=NULL'; put ',showmeta=N'; put ',maxobs=MAX'; put ')/*/STORE SOURCE*/;'; put '%local tempds colinfo fmtds i numcols numobs stmt_obs lastobs optval'; put 'tmpds1 tmpds2 tmpds3 tmpds4;'; put '%let numcols=0;'; put '%if &maxobs ne MAX %then %let stmt_obs=%str(if _n_>&maxobs then stop;);'; put '%if &action=OPEN %then %do;'; put 'options nobomfile;'; put 'data _null_;file &jref encoding=''utf-8'' lrecl=200;'; put 'put ''{"PROCESSED_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''"'';'; put 'run;'; put '%end;'; put '%else %if (&action=ARR or &action=OBJ) %then %do;'; put '/* force variable names to always be uppercase in the JSON */'; put 'options validvarname=upcase;'; put '/* To avoid issues with _webout on EBI - such as encoding diffs and truncation'; put '(https://support.sas.com/kb/49/325.html) we use temporary files */'; put 'filename _sjs1 temp lrecl=200 ;'; put 'data _null_; file _sjs1 encoding=''utf-8'';'; put 'put ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":";'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjs1 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs1 clear;'; put '/* grab col defs */'; put 'proc contents noprint data=&ds'; put 'out=_data_(keep=name type length format formatl formatd varnum label);'; put 'run;'; put '%let colinfo=%scan(&syslast,2,.);'; put 'proc sort data=&colinfo;'; put 'by varnum;'; put 'run;'; put '/* move meta to mac vars */'; put 'data &colinfo;'; put 'if _n_=1 then call symputx(''numcols'',nobs,''l'');'; put 'set &colinfo end=last nobs=nobs;'; put 'name=upcase(name);'; put '/* fix formats */'; put 'if type=2 or type=6 then do;'; put 'typelong=''char'';'; put 'length fmt $49.;'; put 'if format='''' then fmt=cats(''$'',length,''.'');'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else fmt=cats(format,formatl,''.'');'; put 'end;'; put 'else do;'; put 'typelong=''num'';'; put 'if format='''' then fmt=''best.'';'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else if formatd=0 then fmt=cats(format,formatl,''.'');'; put 'else fmt=cats(format,formatl,''.'',formatd);'; put 'end;'; put '/* 32 char unique name */'; put 'newname=''sasjs''!!substr(cats(put(md5(name),$hex32.)),1,27);'; put 'call symputx(cats(''name'',_n_),name,''l'');'; put 'call symputx(cats(''newname'',_n_),newname,''l'');'; put 'call symputx(cats(''length'',_n_),length,''l'');'; put 'call symputx(cats(''fmt'',_n_),fmt,''l'');'; put 'call symputx(cats(''type'',_n_),type,''l'');'; put 'call symputx(cats(''typelong'',_n_),typelong,''l'');'; put 'call symputx(cats(''label'',_n_),coalescec(label,name),''l'');'; put '/* overwritten when fmt=Y and a custom format exists in catalog */'; put 'if typelong=''num'' then call symputx(cats(''fmtlen'',_n_),200,''l'');'; put 'else call symputx(cats(''fmtlen'',_n_),min(32767,ceil((length+10)*1.5)),''l'');'; put 'run;'; put '%let tempds=%substr(_%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql;'; put 'select count(*) into: lastobs from &ds;'; put '%if &maxobs ne MAX %then %let lastobs=%sysfunc(min(&lastobs,&maxobs));'; put '%if &engine=PROCJSON %then %do;'; put '%if &missing=STRING %then %do;'; put '%put &sysmacroname: Special Missings not supported in proc json.;'; put '%put &sysmacroname: Switching to DATASTEP engine;'; put '%goto datastep;'; put '%end;'; put 'data &tempds;'; put 'set &ds;'; put '&stmt_obs;'; put '%if &fmt=N %then format _numeric_ best32.;;'; put '/* PRETTY is necessary to avoid line truncation in large files */'; put 'filename _sjs2 temp lrecl=131068 encoding=''utf-8'';'; put 'proc json out=_sjs2 pretty'; put '%if &action=ARR %then nokeys ;'; put ';export &tempds / nosastags fmtnumeric;'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs2 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs2 clear;'; put '%end;'; put '%else %if &engine=DATASTEP %then %do;'; put '%datastep:'; put '%if %sysfunc(exist(&ds)) ne 1 & %sysfunc(exist(&ds,VIEW)) ne 1'; put '%then %do;'; put '%put &sysmacroname: &ds NOT FOUND!!!;'; put '%return;'; put '%end;'; put '%if &fmt=Y %then %do;'; put '/**'; put '* Extract format definitions'; put '* First, by getting library locations from dictionary.formats'; put '* Then, by exporting the width using proc format'; put '* Cannot use maxw from sashelp.vformat as not always populated'; put '* Cannot use fmtinfo() as not supported in all flavours'; put '*/'; put '%let tmpds1=%substr(fmtsum%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds2=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds3=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds4=%substr(col%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql noprint;'; put 'create table &tmpds1 as'; put 'select cats(libname,''.'',memname) as FMTCAT,'; put 'FMTNAME'; put 'from dictionary.formats'; put 'where fmttype=''F'' and libname is not null'; put 'and fmtname in (select format from &colinfo where format is not null)'; put 'order by 1;'; put 'create table &tmpds2('; put 'FMTNAME char(32),'; put 'LENGTH num'; put ');'; put '%local catlist cat fmtlist i;'; put 'select distinct fmtcat into: catlist separated by '' '' from &tmpds1;'; put '%do i=1 %to %sysfunc(countw(&catlist,%str( )));'; put '%let cat=%scan(&catlist,&i,%str( ));'; put 'proc sql;'; put 'select distinct fmtname into: fmtlist separated by '' '''; put 'from &tmpds1 where fmtcat="&cat";'; put 'proc format lib=&cat cntlout=&tmpds3(keep=fmtname length);'; put 'select &fmtlist;'; put 'run;'; put 'proc sql;'; put 'insert into &tmpds2 select distinct fmtname,length from &tmpds3;'; put '%end;'; put 'proc sql;'; put 'create table &tmpds4 as'; put 'select a.*, b.length as MAXW'; put 'from &colinfo a'; put 'left join &tmpds2 b'; put 'on cats(a.format)=cats(upcase(b.fmtname))'; put 'order by a.varnum;'; put 'data _null_;'; put 'set &tmpds4;'; put 'if not missing(maxw);'; put 'call symputx('; put 'cats(''fmtlen'',_n_),'; put '/* vars need extra padding due to JSON escaping of special chars */'; put 'min(32767,ceil((max(length,maxw)+10)*1.5))'; put ',''l'''; put ');'; put 'run;'; put '/* configure varlenchk - as we are explicitly shortening the variables */'; put '%let optval=%sysfunc(getoption(varlenchk));'; put 'options varlenchk=NOWARN;'; put 'data _data_(compress=char);'; put '/* shorten the new vars */'; put 'length'; put '%do i=1 %to &numcols;'; put '&&name&i $&&fmtlen&i'; put '%end;'; put ';'; put '/* rename on entry */'; put 'set &ds(rename=('; put '%do i=1 %to &numcols;'; put '&&name&i=&&newname&i'; put '%end;'; put '));'; put '&stmt_obs;'; put 'drop'; put '%do i=1 %to &numcols;'; put '&&newname&i'; put '%end;'; put ';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=num %then %do;'; put '&&name&i=cats(put(&&newname&i,&&fmt&i));'; put '%end;'; put '%else %do;'; put '&&name&i=put(&&newname&i,&&fmt&i);'; put '%end;'; put '%end;'; put 'if _error_ then do;'; put 'call symputx(''syscc'',1012);'; put 'stop;'; put 'end;'; put 'run;'; put '%let fmtds=&syslast;'; put 'options varlenchk=&optval;'; put '%end;'; put 'proc format; /* credit yabwon for special null removal */'; put 'value bart (default=40)'; put '%if &missing=NULL %then %do;'; put '._ - .z = null'; put '%end;'; put '%else %do;'; put '._ = [quote()]'; put '. = null'; put '.a - .z = [quote()]'; put '%end;'; put 'other = [best.];'; put 'data &tempds;'; put 'attrib _all_ label='''';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'length &&name&i $&&fmtlen&i...;'; put 'format &&name&i $&&fmtlen&i...;'; put '%end;'; put '%end;'; put '%if &fmt=Y %then %do;'; put 'set &fmtds;'; put '%end;'; put '%else %do;'; put 'set &ds;'; put '%end;'; put '&stmt_obs;'; put 'format _numeric_ bart.;'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'if findc(&&name&i,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put '&&name&i=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,&&name&i)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else &&name&i=quote(cats(&&name&i));'; put '%end;'; put '%end;'; put 'run;'; put 'filename _sjs3 temp lrecl=131068 ;'; put 'data _null_;'; put 'file _sjs3 encoding=''utf-8'';'; put 'if _n_=1 then put "[";'; put 'set &tempds;'; put 'if _n_>1 then put "," @; put'; put '%if &action=ARR %then "[" ; %else "{" ;'; put '%do i=1 %to &numcols;'; put '%if &i>1 %then "," ;'; put '%if &action=OBJ %then """&&name&i"":" ;'; put '"&&name&i"n /* name literal for reserved variable names */'; put '%end;'; put '%if &action=ARR %then "]" ; %else "}" ; ;'; put '/* close out the table */'; put 'data _null_;'; put 'file _sjs3 mod encoding=''utf-8'';'; put 'put '']'';'; put 'run;'; put 'data _null_;'; put 'infile _sjs3 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs3 clear;'; put '%end;'; put 'proc sql;'; put 'drop table &colinfo, &tempds;'; put '%if %substr(&showmeta,1,1)=Y %then %do;'; put 'filename _sjs4 temp lrecl=131068 encoding=''utf-8'';'; put 'data _null_;'; put 'file _sjs4;'; put 'length label $350;'; put 'put ", ""$%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":{""vars"":{";'; put 'do i=1 to &numcols;'; put 'name=quote(trim(symget(cats(''name'',i))));'; put 'format=quote(trim(symget(cats(''fmt'',i))));'; put 'label=quote(prxchange(''s/\\/\\\\/'',-1,trim(symget(cats(''label'',i)))));'; put 'length=quote(trim(symget(cats(''length'',i))));'; put 'type=quote(trim(symget(cats(''typelong'',i))));'; put 'if i>1 then put "," @@;'; put 'put name '':{"format":'' format '',"label":'' label'; put ''',"length":'' length '',"type":'' type ''}'';'; put 'end;'; put 'put ''}}'';'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs4 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs4 clear;'; put '%end;'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put 'data _null_; file &jref encoding=''utf-8'' mod ;'; put 'put "}";'; put 'run;'; put '%end;'; put '%mend mp_jsonout;'; put '/**'; put '@file mm_webout.sas'; put '@brief Send data to/from SAS Stored Processes'; put '@details This macro should be added to the start of each Stored Process,'; put '**immediately** followed by a call to:'; put '%mm_webout(FETCH)'; put 'This will read all the input data and create same-named SAS datasets in the'; put 'WORK library. You can then insert your code, and send data back using the'; put 'following syntax:'; put 'data some datasets; * make some data ;'; put 'retain some columns;'; put 'run;'; put '%mm_webout(OPEN)'; put '%mm_webout(ARR,some) * Array format, fast, suitable for large tables ;'; put '%mm_webout(OBJ,datasets) * Object format, easier to work with ;'; put 'Finally, wrap everything up send some helpful system variables too'; put '%mm_webout(CLOSE)'; put '@param [in] action Either FETCH, OPEN, ARR, OBJ or CLOSE'; put '@param [in] ds The dataset to send back to the frontend'; put '@param [out] dslabel= Value to use instead of table name for sending to JSON'; put '@param [in] fmt= (N) Setting Y converts all vars to their formatted values'; put '@param [out] fref= (_webout) The fileref to which to write the JSON'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] workobs= (0) When set to a positive integer, will create a new'; put 'output object (WORK) which contains this number of observations from all'; put 'tables in the WORK library.'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to output JSON'; put '

SAS Macros

'; put '@li mp_jsonout.sas'; put '

Related Macros

'; put '@li ms_webout.sas'; put '@li mv_webout.sas'; put '@version 9.3'; put '@author Allan Bowe'; put '**/'; put '%macro mm_webout(action,ds,dslabel=,fref=_webout,fmt=N,missing=NULL'; put ',showmeta=N,maxobs=MAX,workobs=0'; put ');'; put '%global _webin_file_count _webin_fileref1 _webin_name1 _program _debug'; put 'sasjs_tables;'; put '%local i tempds jsonengine;'; put '/* see https://github.com/sasjs/core/issues/41 */'; put '%if "%upcase(&SYSENCODING)" ne "UTF-8" %then %let jsonengine=PROCJSON;'; put '%else %let jsonengine=DATASTEP;'; put '%if &action=FETCH %then %do;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'options mprint notes mprintnest;'; put '%end;'; put '%let _webin_file_count=%eval(&_webin_file_count+0);'; put '/* now read in the data */'; put '%do i=1 %to &_webin_file_count;'; put '%if &_webin_file_count=1 %then %do;'; put '%let _webin_fileref1=&_webin_fileref;'; put '%let _webin_name1=&_webin_name;'; put '%end;'; put 'data _null_;'; put 'infile &&_webin_fileref&i termstr=crlf;'; put 'input;'; put 'call symputx(''input_statement'',_infile_);'; put 'putlog "&&_webin_name&i input statement: " _infile_;'; put 'stop;'; put 'data &&_webin_name&i;'; put 'infile &&_webin_fileref&i firstobs=2 dsd termstr=crlf encoding=''utf-8'';'; put 'input &input_statement;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'if _n_<20 then putlog _infile_;'; put '%end;'; put 'run;'; put '%let sasjs_tables=&sasjs_tables &&_webin_name&i;'; put '%end;'; put '%end;'; put '%else %if &action=OPEN %then %do;'; put '/* fix encoding */'; put 'OPTIONS NOBOMFILE;'; put '/**'; put '* check xengine type to avoid the below err message:'; put '* > Function is only valid for filerefs using the CACHE access method.'; put '*/'; put 'data _null_;'; put 'set sashelp.vextfl(where=(fileref="_WEBOUT"));'; put 'if xengine=''STREAM'' then do;'; put 'rc=stpsrv_header(''Content-type'',"text/html; encoding=utf-8");'; put 'end;'; put 'run;'; put '/* setup json */'; put 'data _null_;file &fref encoding=''utf-8'';'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutBEGIN<<'';'; put '%end;'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'run;'; put '%end;'; put '%else %if &action=ARR or &action=OBJ %then %do;'; put '%mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt,jref=&fref'; put ',engine=&jsonengine,missing=&missing,showmeta=&showmeta,maxobs=&maxobs'; put ')'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put '/* To avoid issues with _webout on EBI we use a temporary file */'; put 'filename _sjsref temp lrecl=131068;'; put '%if %str(&workobs) > 0 %then %do;'; put '/* if debug mode, send back first XX records of each work table also */'; put 'data;run;%let tempds=%scan(&syslast,2,.);'; put 'ods output Members=&tempds;'; put 'proc datasets library=WORK memtype=data;'; put '%local wtcnt;%let wtcnt=0;'; put 'data _null_;'; put 'set &tempds;'; put 'if not (upcase(name) =:"DATA"); /* ignore temp datasets */'; put 'i+1;'; put 'call symputx(cats(''wt'',i),name,''l'');'; put 'call symputx(''wtcnt'',i,''l'');'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put ",""WORK"":{";'; put '%do i=1 %to &wtcnt;'; put '%let wt=&&wt&i;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'dsid=open("WORK.&wt",''is'');'; put 'nlobs=attrn(dsid,''NLOBS'');'; put 'nvars=attrn(dsid,''NVARS'');'; put 'rc=close(dsid);'; put 'if &i>1 then put '',''@;'; put 'put " ""&wt"" : {";'; put 'put ''"nlobs":'' nlobs;'; put 'put '',"nvars":'' nvars;'; put '%mp_jsonout(OBJ,&wt,jref=_sjsref,dslabel=first10rows,showmeta=Y'; put ',maxobs=&workobs'; put ')'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put '%end;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put 'run;'; put '%end;'; put '/* close off json */'; put 'data _null_;file _sjsref mod encoding=''utf-8'';'; put 'length SYSPROCESSNAME syserrortext syswarningtext autoexec $512;'; put 'put ",""_DEBUG"" : ""&_debug"" ";'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'autoexec=quote(urlencode(trim(getoption(''autoexec''))));'; put 'put '',"AUTOEXEC" : '' autoexec;'; put 'put ",""MF_GETUSER"" : ""%mf_getuser()"" ";'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'put ",""SYSENCODING"" : ""&sysencoding"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSPROCESSID"" : ""&SYSPROCESSID"" ";'; put 'put ",""SYSPROCESSMODE"" : ""&SYSPROCESSMODE"" ";'; put 'SYSPROCESSNAME=quote(urlencode(cats(SYSPROCESSNAME)));'; put 'put ",""SYSPROCESSNAME"" : " SYSPROCESSNAME;'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put '',"SYSWARNINGTEXT" : '' syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'length memsize $32;'; put 'memsize="%sysfunc(INPUTN(%sysfunc(getoption(memsize)), best.),sizekmg.)";'; put 'memsize=quote(cats(memsize));'; put 'put '',"MEMSIZE" : '' memsize;'; put 'put "}" @;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutEND<<'';'; put '%end;'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjsref lrecl=1 recfm=n;'; put 'file &fref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjsref clear;'; put '%end;'; put '%mend mm_webout;'; put '%macro webout(action,ds,dslabel=,fmt=,missing=NULL,showmeta=NO,maxobs=MAX);'; put '%mm_webout(&action,ds=&ds,dslabel=&dslabel,fmt=&fmt'; put ',missing=&missing'; put ',showmeta=&showmeta'; put ',maxobs=&maxobs'; put ') %mend;'; put '/* provide additional debug info */'; put '%global _program;'; put '%put &=syscc;'; put '%put user=%mf_getuser();'; put '%put pgm=&_program;'; put '%put timestamp=%sysfunc(datetime(),datetime19.);'; put '* Service Variables start;'; put '* Service Variables end;'; put '* SAS Macros start;'; put '%macro mf_getapploc(pgm);'; put '%if "&pgm"="" %then %do;'; put '%if %symexist(_program) %then %let pgm=&_program;'; put '%else %do;'; put '%put &sysmacroname: No value provided and no _program variable available;'; put '%return;'; put '%end;'; put '%end;'; put '%local root;'; put '/**'; put '* First check we are not in the tests/macros folder (which has no subfolders)'; put '* or specifically in the testsetup or testteardown services'; put '*/'; put '%if %index(&pgm,/tests/macros/)'; put 'or %index(&pgm,/tests/testsetup)'; put 'or %index(&pgm,/tests/testteardown)'; put '%then %do;'; put '%let root=%substr(&pgm,1,%index(&pgm,/tests)-1);'; put '&root'; put '%return;'; put '%end;'; put '/**'; put '* Next, move up two levels to avoid matches on subfolder or service name'; put '*/'; put '%let root=%substr(&pgm,1,%length(&pgm)-%length(%scan(&pgm,-1,/))-1);'; put '%let root=%substr(&root,1,%length(&root)-%length(%scan(&root,-1,/))-1);'; put '%if %index(&root,/tests/) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/tests/)-1);'; put '%end;'; put '%else %if %index(&root,/services) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/services)-1);'; put '%end;'; put '%else %if %index(&root,/jobs) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/jobs)-1);'; put '%end;'; put '%else %put &sysmacroname: Could not find an app location from &pgm;'; put '&root'; put '%mend mf_getapploc ;'; put '%macro mf_getuniquefileref(prefix=_,maxtries=1000,lrecl=32767);'; put '%local rc fname;'; put '%if &prefix=0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%end;'; put '%else %do;'; put '%local x len;'; put '%let len=%eval(8-%length(&prefix));'; put '%let x=0;'; put '%do x=0 %to &maxtries;'; put '%let fname=&prefix%substr(%sysfunc(ranuni(0)),3,&len);'; put '%if %sysfunc(fileref(&fname)) > 0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%return;'; put '%end;'; put '%end;'; put '%put unable to find available fileref after &maxtries attempts;'; put '%end;'; put '%mend mf_getuniquefileref;'; put '%macro mp_abort(mac=mp_abort.sas, type=, msg=, iftrue=%str(1=1)'; put ', errds=work.mp_abort_errds'; put ', mode=REGULAR'; put ')/*/STORE SOURCE*/;'; put '%global sysprocessmode sysprocessname sasjs_stpsrv_header_loc sasjsprocessmode;'; put '%local fref fid i;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%put NOTE: /// mp_abort macro executing //;'; put '%if %length(&mac)>0 %then %put NOTE- called by &mac;'; put '%put NOTE - &msg;'; put '%if %symexist(_SYSINCLUDEFILEDEVICE)'; put '/* abort cancel FILE does not restart outside the INCLUDE on Viya 3.5 */'; put 'and %superq(SYSPROCESSNAME) ne %str(Compute Server)'; put '%then %do;'; put '%if "*&_SYSINCLUDEFILEDEVICE*" ne "**" %then %do;'; put 'data &errds;'; put 'iftrue=''1=1'';'; put 'length mac $100 msg $5000;'; put 'mac=symget(''mac'');'; put 'msg=symget(''msg'');'; put 'run;'; put 'data _null_;'; put 'abort cancel FILE;'; put 'run;'; put '%return;'; put '%end;'; put '%end;'; put '/* Web App Context */'; put '%if %symexist(_PROGRAM)'; put 'or %superq(SYSPROCESSNAME) = %str(Compute Server)'; put 'or &mode=INCLUDE'; put '%then %do;'; put 'options obs=max replace mprint;'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5"'; put '%then %do;'; put 'options nosyntaxcheck;'; put '%end;'; put '%if &mode=INCLUDE %then %do;'; put '%if %sysfunc(exist(&errds))=1 %then %do;'; put 'data _null_;'; put 'set &errds;'; put 'call symputx(''iftrue'',iftrue,''l'');'; put 'call symputx(''mac'',mac,''l'');'; put 'call symputx(''msg'',msg,''l'');'; put 'putlog (_all_)(=);'; put 'run;'; put '%if (&iftrue)=0 %then %return;'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: No include errors found;'; put '%return;'; put '%end;'; put '%end;'; put '/* extract log errs / warns, if exist */'; put '%local logloc logline;'; put '%global logmsg; /* capture global messages */'; put '%if %symexist(SYSPRINTTOLOG) %then %let logloc=&SYSPRINTTOLOG;'; put '%else %let logloc=%qsysfunc(getoption(LOG));'; put 'proc printto log=log;run;'; put '%let logline=0;'; put '%if %length(&logloc)>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input; putlog _infile_;'; put 'i=1;'; put 'retain logonce 0;'; put 'if ('; put '_infile_=:"%str(WARN)ING" or _infile_=:"%str(ERR)OR"'; put ') and logonce=0 then'; put 'do;'; put 'call symputx(''logline'',_n_);'; put 'logonce+1;'; put 'end;'; put 'run;'; put '/* capture log including lines BEFORE the err */'; put '%if &logline>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input;'; put 'i=1;'; put 'stoploop=0;'; put 'if _n_ ge &logline-15 and stoploop=0 then do until (i>22);'; put 'call symputx(''logmsg'',catx(''\n'',symget(''logmsg''),_infile_));'; put 'input;'; put 'i+1;'; put 'stoploop=1;'; put 'end;'; put 'if stoploop=1 then stop;'; put 'run;'; put '%end;'; put '%end;'; put '%if %symexist(SYS_JES_JOB_URI) %then %do;'; put '/* setup webout for Viya */'; put 'options nobomfile;'; put '%if "X&SYS_JES_JOB_URI.X"="XX" %then %do;'; put 'filename _webout temp lrecl=999999 mod;'; put '%end;'; put '%else %do;'; put 'filename _webout filesrvc parenturi="&SYS_JES_JOB_URI"'; put 'name="_webout.json" lrecl=999999 mod;'; put '%end;'; put '%end;'; put '%else %if %sysfunc(filename(fref,&sasjs_stpsrv_header_loc))=0 %then %do;'; put 'options nobomfile;'; put '/* set up http header for SASjs Server */'; put '%let fid=%sysfunc(fopen(&fref,A));'; put '%if &fid=0 %then %do;'; put '%put %str(ERR)OR: %sysfunc(sysmsg());'; put '%return;'; put '%end;'; put '%let rc=%sysfunc(fput(&fid,%str(Content-Type: application/json)));'; put '%let rc=%sysfunc(fwrite(&fid));'; put '%let rc=%sysfunc(fclose(&fid));'; put '%let rc=%sysfunc(filename(&fref));'; put '%end;'; put '/* send response in SASjs JSON format */'; put 'data _null_;'; put 'file _webout mod lrecl=32000 encoding=''utf-8'';'; put 'length msg syswarningtext syserrortext $32767 mode $10 ;'; put 'sasdatetime=datetime();'; put 'msg=symget(''msg'');'; put '%if &logline>0 %then %do;'; put 'msg=cats(msg,''\n\nLog Extract:\n'',symget(''logmsg''));'; put '%end;'; put '/* escape the escapes */'; put 'msg=tranwrd(msg,''\'',''\\'');'; put '/* escape the quotes */'; put 'msg=tranwrd(msg,''"'',''\"'');'; put '/* ditch the CRLFs as chrome complains */'; put 'msg=compress(msg,,''kw'');'; put '/* quote without quoting the quotes (which are escaped instead) */'; put 'msg=cats(''"'',msg,''"'');'; put 'if symexist(''_debug'') then debug=quote(trim(symget(''_debug'')));'; put 'else debug=''""'';'; put 'if symget(''sasjsprocessmode'')=''Stored Program'' then mode=''SASJS'';'; put 'if mode ne ''SASJS'' then put ''>>weboutBEGIN<<'';'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'put '',"sasjsAbort" : [{'';'; put 'put '' "MSG":'' msg ;'; put 'put '' ,"MAC": "'' "&mac" ''"}]'';'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'put '',"_DEBUG":'' debug ;'; put 'if symexist(''_metauser'') then do;'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put 'end;'; put 'if symexist(''SYS_JES_JOB_URI'') then do;'; put 'SYS_JES_JOB_URI=quote(trim(symget(''SYS_JES_JOB_URI'')));'; put 'put '',"SYS_JES_JOB_URI": '' SYS_JES_JOB_URI;'; put 'end;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put ",""SYSWARNINGTEXT"" : " syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'put "}" ;'; put 'if mode ne ''SASJS'' then put ''>>weboutEND<<'';'; put 'run;'; put '%put _all_;'; put '%if "&sysprocessmode " = "SAS Stored Process Server " %then %do;'; put 'data _null_;'; put 'putlog ''stpsrvset program err and syscc'';'; put 'rc=stpsrvset(''program error'', 0);'; put 'call symputx("syscc",0,"g");'; put 'run;'; put '%if &sysscp=WIN'; put 'and 1=0 /* deprecating this logic until we figure out a consistent abort */'; put 'and "%substr(%str(&sysvlong ),1,8)"="9.04.01M"'; put 'and "%substr(%str(&sysvlong ),9,1)">"5" %then %do;'; put '/* skip approach (below) does not work in windows m6+ envs */'; put 'endsas;'; put '%end;'; put '%else %do;'; put '/**'; put '* endsas kills 9.4m3 deployments by orphaning multibridges.'; put '* Abort variants are ungraceful (non zero return code)'; put '* This approach lets SAS run silently until the end :-)'; put '* Caution - fails when called within a %include within a macro'; put '* Use mp_include() to handle this.'; put '*/'; put 'filename skip temp;'; put 'data _null_;'; put 'file skip;'; put 'put ''%macro skip();'';'; put 'comment ''%mend skip; -> fix lint '';'; put 'put ''%macro skippy();'';'; put 'comment ''%mend skippy; -> fix lint '';'; put 'run;'; put '%inc skip;'; put '%end;'; put '%end;'; put '%else %if "&sysprocessmode " = "SAS Compute Server " %then %do;'; put '/* endsas kills the session making it harder to fetch results */'; put 'data _null_;'; put 'syswarningtext=symget(''syswarningtext'');'; put 'syserrortext=symget(''syserrortext'');'; put 'abort_msg=symget(''msg'');'; put 'syscc=symget(''syscc'');'; put 'sysuserid=symget(''sysuserid'');'; put 'iftrue=symget(''iftrue'');'; put 'put (_all_)(/=);'; put 'call symputx(''syscc'',0);'; put 'abort cancel nolist;'; put 'run;'; put '%end;'; put '%else %do;'; put '%abort cancel;'; put '%end;'; put '%end;'; put '%else %do;'; put '%put _all_;'; put '%abort cancel;'; put '%end;'; put '%mend mp_abort;'; put '/** @endcond */'; put '%macro mm_getstpcode('; put 'tree=/User Folders/sasdemo/somestp'; put ',name='; put ',outloc=0'; put ',outref=0'; put ',mDebug=1'; put ',showlog=NO'; put ');'; put '%local mD;'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing &sysmacroname..sas;'; put '%&mD.put _local_;'; put '%if %length(&name)>0 %then %let name=/&name;'; put '/* first, check if STP exists */'; put '%local tsuri;'; put '%let tsuri=stopifempty ;'; put 'data _null_;'; put 'format type uri tsuri value $200.;'; put 'call missing (of _all_);'; put 'path="&tree&name(StoredProcess)";'; put '/* first, find the STP ID */'; put 'if metadata_pathobj("",path,"StoredProcess",type,uri)>0 then do;'; put '/* get sourcecode */'; put 'cnt=1;'; put 'do while (metadata_getnasn(uri,"Notes",cnt,tsuri)>0);'; put 'rc=metadata_getattr(tsuri,"Name",value);'; put '&mD.put tsuri= value=;'; put 'if value="SourceCode" then do;'; put '/* found it! */'; put 'rc=metadata_getattr(tsuri,"Id",value);'; put 'call symputx(''tsuri'',value,''l'');'; put 'stop;'; put 'end;'; put 'cnt+1;'; put 'end;'; put 'end;'; put 'else put (_all_)(=);'; put 'run;'; put '%mp_abort(iftrue= (&tsuri=stopifempty)'; put ',mac=mm_getstpcode'; put ',msg=%str(&tree&name.(StoredProcess) not found!)'; put ')'; put '/**'; put '* Now we can extract the textstore'; put '*/'; put 'filename __getdoc temp lrecl=10000000;'; put 'proc metadata'; put 'in="$METAREPOSITORY'; put ''; put 'SAS1"'; put 'out=__getdoc ;'; put 'run;'; put '/* find the beginning of the text */'; put '%local start;'; put 'data _null_;'; put 'infile __getdoc lrecl=10000;'; put 'input;'; put 'start=index(_infile_,''StoredText="'');'; put 'if start then do;'; put 'call symputx("start",start+11);'; put '*putlog ''"'' _infile_ ''"'';'; put 'end;'; put 'stop;'; put '%local outeng;'; put '%if "&outloc"="0" %then %let outeng=TEMP;'; put '%else %let outeng="&outloc";'; put '%local fref;'; put '%if &outref=0 %then %let fref=%mf_getuniquefileref();'; put '%else %let fref=&outref;'; put '/* read the content, byte by byte, resolving escaped chars */'; put 'filename &fref &outeng lrecl=100000;'; put 'data _null_;'; put 'length filein 8 fileid 8;'; put 'filein = fopen("__getdoc","I",1,"B");'; put 'fileid = fopen("&fref","O",1,"B");'; put 'rec = "20"x;'; put 'length entity $6;'; put 'do while(fread(filein)=0);'; put 'x+1;'; put 'if x>&start then do;'; put 'rc = fget(filein,rec,1);'; put 'if rec=''"'' then leave;'; put 'else if rec="&" then do;'; put 'entity=rec;'; put 'do until (rec=";");'; put 'if fread(filein) ne 0 then goto getout;'; put 'rc = fget(filein,rec,1);'; put 'entity=cats(entity,rec);'; put 'end;'; put 'select (entity);'; put 'when (''&'' ) rec=''&'' ;'; put 'when (''<'' ) rec=''<'' ;'; put 'when (''>'' ) rec=''>'' ;'; put 'when (''''') rec="''" ;'; put 'when (''"'') rec=''"'' ;'; put 'when ('' '') rec=''0A''x;'; put 'when ('' '') rec=''0D''x;'; put 'when (''$'' ) rec=''$'' ;'; put 'when ('' '') rec=''09''x;'; put 'otherwise putlog "%str(WARN)ING: missing value for " entity=;'; put 'end;'; put 'rc =fput(fileid, substr(rec,1,1));'; put 'rc =fwrite(fileid);'; put 'end;'; put 'else do;'; put 'rc =fput(fileid,rec);'; put 'rc =fwrite(fileid);'; put 'end;'; put 'end;'; put 'end;'; put 'getout:'; put 'rc=fclose(filein);'; put 'rc=fclose(fileid);'; put 'run;'; put '%if &showlog=YES %then %do;'; put 'data _null_;'; put 'infile &fref lrecl=32767 end=last;'; put 'input;'; put 'if _n_=1 then putlog ''>>stpcodeBEGIN<<'';'; put 'putlog _infile_;'; put 'if last then putlog ''>>stpcodeEND<<'';'; put 'run;'; put '%end;'; put 'filename __getdoc clear;'; put '%if &outref=0 %then %do;'; put 'filename &fref clear;'; put '%end;'; put '%mend mm_getstpcode;'; put '%macro dc_getsettings();'; put '%global _program;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(syscc=&syscc on entry (&syswarningtext &syserrortext))'; put ')'; put '%if %length(&_PROGRAM)>1 %then %let root=&_program;'; put '%else %do;'; put '%global _metauser;'; put '%let _metauser=&sysuserid;'; put '/* to mimic a "real" _program we need to give a dummy role and stp name */'; put '%let root=/dummyRole/dummyName;'; put '%end;'; put '/* the DC precode is stored in the Admin folder in the root of'; put 'the project. Lets find that root. */'; put '%put &=root;'; put '%let root=%mf_getapploc();'; put '%put &=root;'; put '/* Now we know the root location we can retrieve the params */'; put '%let temploc=%sysfunc(pathname(work))/settings.txt;'; put '%mm_getstpcode(tree=&root/services/public'; put ',name=Data_Controller_Settings'; put ',outloc=&temploc'; put ')'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Unable to run getstpcode)'; put ')'; put 'filename _getsets "&temploc" lrecl=2000;'; put '/*'; put 'Do not use mp_include here - this puts a copy in every service, which creates'; put 'compilation problems when calling services from mp_include'; put '*/'; put '%inc _getsets/source2;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Problem running &sysmacroname (&syswarningtext &syserrortext))'; put ')'; put '%mend dc_getsettings;'; put '%macro mf_fmtdttm('; put ')/*/STORE SOURCE*/;'; put '%if "&sysver"="9.2" or "&sysver"="9.3"'; put 'or ("&sysver"="9.4" and "%substr(&SYSVLONG,9,1)" le "3")'; put 'or "%substr(&sysver,1,1)"="4"'; put 'or "%substr(&sysver,1,1)"="5"'; put '%then %do;DATETIME19.3%end;'; put '%else %do;E8601DT26.6%end;'; put '%mend mf_fmtdttm;'; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '%macro mp_init(prefix=SASJS'; put ')/*/STORE SOURCE*/;'; put '%if %symexist(SASJS_PREFIX) %then %return; /* only run once */'; put '%global'; put 'SASJS_PREFIX /* the ONLY hard-coded global macro variable in SASjs */'; put '&prefix._FUNCTIONS /* used in mcf_init() to track core function compilation */'; put '&prefix._INIT_NUM /* initialisation time as numeric */'; put '&prefix._INIT_DTTM /* initialisation time in E8601DT26.6 format */'; put '&prefix.WORK /* avoid typing %sysfunc(pathname(work)) every time */'; put ';'; put '%let sasjs_prefix=&prefix;'; put 'data _null_;'; put 'dttm=datetime();'; put 'call symputx("&sasjs_prefix._init_num",dttm,''g'');'; put 'call symputx("&sasjs_prefix._init_dttm",put(dttm,E8601DT26.6),''g'');'; put 'call symputx("&sasjs_prefix.work",pathname(''WORK''),''g'');'; put 'run;'; put 'options'; put 'compress=CHAR /* default is none so ensure we have something! */'; put 'datastmtchk=ALLKEYWORDS /* protection from overwriting input datasets */'; put 'errorcheck=STRICT /* catch errs in libname/filename statements */'; put 'fmterr /* ensure err when a format cannot be found */'; put 'mergenoby=%str(ERR)OR /* throw err when a merge has no BY variables */'; put 'missing=. /* changing this can cause hard to detect errs */'; put 'noquotelenmax /* avoid warnings for long strings */'; put 'noreplace /* avoid overwriting permanent datasets */'; put 'ps=max /* reduce log size slightly */'; put 'ls=max /* reduce log even more and avoid word truncation */'; put 'validmemname=COMPATIBLE /* avoid special characters etc in table names */'; put 'validvarname=V7 /* avoid special characters etc in variable names */'; put 'varinitchk=%str(ERR)OR /* avoid data mistakes from variable name typos */'; put 'varlenchk=%str(ERR)OR /* fail hard if truncation (data loss) can result */'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5" %then %do;'; put 'noautocorrect /* disallow misspelled procedure names */'; put 'dsoptions=note2err /* undocumented - convert bad NOTEs to ERRs */'; put '%end;'; put ';'; put '%mend mp_init;'; put '%macro mpeinit(fetch=YES);'; put '%global mpeinit'; put 'mpeadmins /* group with unrestricted Meditor access */'; put 'mpelocapprovals /* location for landing and staging files */'; put 'mpelib /* location of configuration tables for DC */'; put 'dc_repo_users /* location of user / group metadata */'; put 'dc_licence_key /* extracted in dc_getsettings */'; put 'dc_activation_key /* extracted in dc_getsettings */'; put 'dc_locale /* extracted in dc_getsettings */'; put 'dc_dttmtfmt /* can be overridden in dc_getsettings */'; put '_debug'; put ';'; put '%if &mpeinit=1 %then %return;'; put '%else %let mpeinit=1;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(Problem on service startup (&syswarningtext &syserrortext))'; put ')'; put '%mp_init()'; put '%if &fetch=YES %then %do;'; put '%webout(FETCH)'; put '%end;'; put '%global _CLIENTNAME;'; put '%mp_abort(iftrue= (&_CLIENTNAME=SAS Enterprise Guide)'; put ',mac=&_program..sas'; put ',msg=%str(Data Controller is a web app and should not be executed from EG)'; put ')'; put 'options urlencoding=utf8 nobomfile lrecl=32767;'; put '%let perf=%sysfunc(datetime());'; put '%put perfdiff: 0;'; put '%let dc_locale=SYSTEM; /* default if not set */'; put '/**'; put '* E8601DT26.6 has widest database support - but not all SAS flavours can'; put '* handle it. Override in the settings STP if needed.'; put '*/'; put 'data _null_;'; put 'dc_dttmtfmt=''"%sysfunc(datetime(),''!!"%mf_fmtdttm()"!!'')"dt'';'; put 'call symputx(''dc_dttmtfmt'',dc_dttmtfmt);'; put 'put dc_dttmtfmt=;'; put 'run;'; put '%put &=dc_dttmtfmt;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc prior to dc_getsettings)'; put ')'; put '%dc_getsettings()'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc after dc_getsettings)'; put ')'; put 'data _null_;'; put 'set &DC_LIBREF..mpe_config(where=('; put 'var_scope="DC"'; put 'and &dc_dttmtfmt lt tx_to'; put 'and var_active=1'; put '));'; put 'call symputx(var_name,var_value,''G'');'; put 'putlog var_name "=" var_value;'; put 'run;'; put '%let mpelib=&dc_libref;'; put '%let mpeadmins=&dc_admin_group;'; put '%let mpelocapprovals=&dc_staging_area;'; put '%let dc_repo_users=&dc_repo_users;'; put '%if &dc_locale ne SYSTEM %then %do;'; put 'options locale=&dc_locale;'; put '%end;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(Problem during compilation or with STP precode (&syswarningtext))'; put ')'; put '%mend mpeinit;'; put '%macro mf_mval(var);'; put '%if %symexist(&var) %then %do;'; put '%superq(&var)'; put '%end;'; put '%mend mf_mval;'; put '%macro mf_trimstr(basestr,trimstr);'; put '%local baselen trimlen trimval;'; put '/* return if basestr is shorter than trimstr (or 0) */'; put '%let baselen=%length(%superq(basestr));'; put '%let trimlen=%length(%superq(trimstr));'; put '%if &baselen < &trimlen or &baselen=0 %then %return;'; put '/* obtain the characters from the end of basestr */'; put '%let trimval=%qsubstr(%superq(basestr)'; put ',%length(%superq(basestr))-&trimlen+1'; put ',&trimlen);'; put '/* compare and if matching, chop it off! */'; put '%if %superq(basestr)=%superq(trimstr) %then %do;'; put '%return;'; put '%end;'; put '%else %if %superq(trimval)=%superq(trimstr) %then %do;'; put '%qsubstr(%superq(basestr),1,%length(%superq(basestr))-&trimlen)'; put '%end;'; put '%else %do;'; put '&basestr'; put '%end;'; put '%mend mf_trimstr;'; put '%macro mf_getplatform(switch'; put ')/*/STORE SOURCE*/;'; put '%local a b c;'; put '%if &switch.NONE=NONE %then %do;'; put '%if %symexist(sasjsprocessmode) %then %do;'; put '%if &sasjsprocessmode=Stored Program %then %do;'; put 'SASJS'; put '%return;'; put '%end;'; put '%end;'; put '%if %symexist(sysprocessmode) %then %do;'; put '%if "&sysprocessmode"="SAS Object Server"'; put 'or "&sysprocessmode"= "SAS Compute Server" %then %do;'; put 'SASVIYA'; put '%end;'; put '%else %if "&sysprocessmode"="SAS Stored Process Server"'; put 'or "&sysprocessmode"="SAS Workspace Server"'; put '%then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if %symexist(_metaport) or %symexist(_metauser) %then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if &switch=SASSTUDIO %then %do;'; put '/* return the version of SAS Studio else 0 */'; put '%if %mf_mval(_CLIENTAPP)=%str(SAS Studio) %then %do;'; put '%let a=%mf_mval(_CLIENTVERSION);'; put '%let b=%scan(&a,1,.);'; put '%if %eval(&b >2) %then %do;'; put '&b'; put '%end;'; put '%else 0;'; put '%end;'; put '%else 0;'; put '%end;'; put '%else %if &switch=VIYARESTAPI %then %do;'; put '%mf_trimstr(%sysfunc(getoption(servicesbaseurl)),/)'; put '%end;'; put '%mend mf_getplatform;'; put '%macro mpeterm();'; put '%local oldloc;'; put 'data _null_;'; put 'if symexist(''SYSPRINTTOLOG'') then oldloc=symget(''SYSPRINTTOLOG'');'; put 'else oldloc=getoption(''LOG'');'; put 'if subpad(oldloc,1,1) not in (''"'',"''",'' '') then oldloc=quote(cats(oldloc));'; put 'call symputx(''oldloc'',oldloc,''l'');'; put 'run;'; put '%if %length(&oldloc)>0 %then %do;'; put 'proc printto log=log;'; put 'run;'; put 'data _null_;'; put 'infile &oldloc;'; put 'input; putlog _infile_;'; put 'run;'; put '%end;'; put '%if %sysfunc(exist(&dc_libref..mpe_requests)) and %mf_getplatform() ne SASVIYA'; put '%then %do;'; put 'data ;'; put 'if 0 then set &dc_libref..mpe_requests;'; put 'request_dttm=%sysfunc(datetime());'; put 'request_user="%mf_getuser()";'; put 'request_service="%scan(&_program,-2,/)/%scan(&_program,-1,/)";'; put 'request_params='''';'; put 'output;stop;'; put 'proc append base=&dc_libref..mpe_requests data=&syslast force nowarn;'; put 'run;'; put '%end;'; put '%mend mpeterm;'; put '%macro mm_getlibs('; put 'outds=work.mm_getLibs'; put ')/*/STORE SOURCE*/;'; put '/*'; put 'flags:'; put 'OMI_SUCCINCT (2048) Do not return attributes with null values.'; put 'OMI_GET_METADATA (256) Executes a GetMetadata call for each object that'; put 'is returned by the GetMetadataObjects method.'; put 'OMI_ALL_SIMPLE (8) Gets all of the attributes of the requested object.'; put '*/'; put 'data _null_;'; put 'flags=2048+256+8;'; put 'call symputx(''flags'',flags,''l'');'; put 'run;'; put '* use a temporary fileref to hold the response;'; put 'filename response temp;'; put '/* get list of libraries */'; put 'proc metadata in='; put ''''; put '$METAREPOSITORY'; put 'SASLibrary'; put ''; put 'SAS'; put '&flags'; put ''; put ''''; put 'out=response;'; put 'run;'; put '/* write the response to the log for debugging */'; put 'data _null_;'; put 'infile response lrecl=32767;'; put 'input;'; put 'put _infile_;'; put 'run;'; put '/* create an XML map to read the response */'; put 'filename sxlemap temp;'; put 'data _null_;'; put 'file sxlemap;'; put 'put '''';'; put 'put '''';'; put 'put ''//Objects/SASLibrary'';'; put 'put ''>17'';'; put 'put ''//Objects/SASLibrary/@Id'';'; put 'put ''256>'';'; put 'put ''//Objects/SASLibrary/@Name'';'; put 'put ''8'';'; put 'put ''//Objects/SASLibrary/@Libref'';'; put 'put ''>12'';'; put 'put ''//Objects/SASLibrary/@Engine'';'; put 'put ''
'';'; put 'run;'; put 'libname _XML_ xml xmlfileref=response xmlmap=sxlemap;'; put '/* sort the response by library name */'; put 'proc sort data=_XML_.saslibrary out=&outds;'; put 'by libraryname;'; put 'run;'; put '/* clear references */'; put 'filename sxlemap clear;'; put 'filename response clear;'; put 'libname _XML_ clear;'; put '%mend mm_getlibs;'; put '%macro mm_getrepos('; put 'outds=work.mm_getrepos'; put ')/*/STORE SOURCE*/;'; put '* use a temporary fileref to hold the response;'; put 'filename response temp;'; put '/* get list of libraries */'; put 'proc metadata in='; put '"1"'; put 'out=response;'; put 'run;'; put '/* write the response to the log for debugging */'; put '/*'; put 'data _null_;'; put 'infile response lrecl=1048576;'; put 'input;'; put 'put _infile_;'; put 'run;'; put '*/'; put '/* create an XML map to read the response */'; put 'filename sxlemap temp;'; put 'data _null_;'; put 'file sxlemap;'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository";'; put 'put "";'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Id";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Name";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Desc";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@DefaultNS";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@RepositoryType";'; put 'put "characterstring20";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@RepositoryFormat";'; put 'put "characterstring10";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@Access";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@CurrentAccess";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@PauseState";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Path";'; put 'put "";'; put 'put "characterstring256";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Engine";'; put 'put "";'; put 'put "characterstring8";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Options";'; put 'put "";'; put 'put "characterstring32";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@MetadataCreated";'; put 'put "characterstring24";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@MetadataUpdated";'; put 'put "characterstring24";'; put 'put '''';'; put 'put ''
'';'; put 'run;'; put 'libname _XML_ xml xmlfileref=response xmlmap=sxlemap;'; put 'proc sort data= _XML_.SASRepos out=&outds;'; put 'by name;'; put 'run;'; put '/* clear references */'; put 'filename sxlemap clear;'; put 'filename response clear;'; put 'libname _XML_ clear;'; put '%mend mm_getrepos;'; put '%macro dc_getlibs(outds=mm_getlibs);'; put '/* take current repository */'; put '%local repo repocnt x;'; put '%let repo=%sysfunc(getoption(metarepository));'; put '/* get list of repositories and filter */'; put '%mm_getrepos(outds=repos)'; put '%let repocnt=0;'; put 'data repos;'; put 'set repos;'; put 'where repositorytype in(''CUSTOM'',''FOUNDATION'')'; put '& upcase(name) ne "%upcase(&repo)";'; put 'keep id name ;'; put 'call symputx(''repo''!!cats(_n_),name,''l'');'; put 'call symputx(''repocnt'',_n_,''l'');'; put 'run;'; put '%put _local_;'; put '%mm_getlibs(outds=&outds)'; put '%do x=1 %to &repocnt;'; put 'options metarepository=&&repo&x;'; put '%mm_getlibs(outds=&outds.a)'; put 'proc append base=&outds data=&outds.a;'; put 'run;'; put '%end;'; put 'options metarepository=&repo;'; put '%mend dc_getlibs;'; put '* SAS Macros end;'; put '* SAS Includes start;'; put '* SAS Includes end;'; put '* Binary Files start;'; put '* Binary Files end;'; put '* ServiceInit start;'; put 'options noquotelenmax ps=max;'; put '/* create dummy macros to prevent stpbegin / stpend from corrupting sessions */'; put '%macro stpbegin();'; put '%put NOTE: the STPBEGIN macro should not be used for web apps!;'; put '%mend stpbegin;'; put '%macro stpend();'; put '%put NOTE: the STPEND macro should not be used for web apps!;'; put '%mend stpend;'; put '* ServiceInit end;'; put '* Service start;'; put '/**'; put '@file'; put '@brief fetch extended values for DSN'; put '@details Fetches datasets in a library, and ALSO fetches a list of numeric'; put 'vars for each dataset for use in adjacent columns (such as VAR_PROCESSED,'; put 'TX_TO etc).'; put 'Available macro variables:'; put '@li MPELIB - The DC control library'; put '@li LIBDS - The library.dataset being filtered'; put '@li VARIABLE_NM - The column being filtered'; put '

Service Outputs

'; put 'Output should be a single table called "work.dynamic_values" in the format'; put 'below. display_value should always be character, raw_value is unformatted'; put 'character/numeric.'; put '
DYNAMIC_VALUES
'; put 'The RAW_VALUE column may be charactor or numeric. If DISPLAY_INDEX is not'; put 'provided, it is added automatically.'; put '|DISPLAY_INDEX:best.|DISPLAY_VALUE:$|RAW_VALUE|'; put '|---|---|---|'; put '|1|$77.43|77.43|'; put '|2|$88.43|88.43|'; put '
DYNAMIC_EXTENDED_VALUES
'; put 'This table is optional. If provided, it will map the DISPLAY_INDEX from the'; put 'DYNAMIC_VALUES table to additional column/value pairs, that will be used to'; put 'populate dropdowns for _other_ cells in the _same_ row.'; put 'Should be used sparingly! The use of large tables here can slow down the'; put 'browser.'; put 'The FORCED_VALUE column can be used to force an extended value to be selected'; put 'by default when a particular value is chosen.'; put '|DISPLAY_INDEX:best.|EXTRA_COL_NAME:$32.|DISPLAY_VALUE:$|DISPLAY_TYPE:$1.|RAW_VALUE_NUM|RAW_VALUE_CHAR:$5000|FORCED_VALUE|'; put '|---|---|---|---|'; put '|1|DISCOUNT_RT|"50%"|N|0.5||.|'; put '|1|DISCOUNT_RT|"40%"|N|0.4||0|'; put '|1|DISCOUNT_RT|"30%"|N|0.3||1|'; put '|1|CURRENCY_SYMBOL|"GBP"|C||"GBP"|.|'; put '|1|CURRENCY_SYMBOL|"RSD"|C||"RSD"|.|'; put '|2|DISCOUNT_RT|"50%"|N|0.5||.|'; put '|2|DISCOUNT_RT|"40%"|N|0.4||1|'; put '|2|CURRENCY_SYMBOL|"EUR"|C||"EUR"|.|'; put '|2|CURRENCY_SYMBOL|"HKD"|C||"HKD"|1|'; put '

SAS Macros

'; put '@li dc_getlibs.sas'; put '**/'; put '/* send back the raw and formatted values */'; put '%let tgtlib=0;'; put '%let varlibds=%mf_getuniquename();'; put '%let vartgtlib=%mf_getuniquename();'; put '%let var_is_lib=%mf_getuniquename();'; put 'data _null_;'; put 'length &varlibds $41 &vartgtlib $8;'; put 'set work.source_row;'; put '&varlibds=upcase(symget(''libds''));'; put 'if &varlibds="&mpelib..MPE_TABLES" then &vartgtlib=LIBREF;'; put 'else putlog "something unexpected happened";'; put '/* validate name */'; put 'if nvalid(&vartgtlib,''v7'') then call symputx(''tgtlib'',&vartgtlib);'; put 'call symputx(''vartgtlib'',&vartgtlib);'; put 'putlog (_all_)(=);'; put 'run;'; put '%mp_abort(iftrue= ("&tgtlib" ="0" )'; put ',mac=&_program..sas'; put ',msg=%str(Invalid library - %superq(vartgtlib))'; put ',errds=work.dc_error_response'; put ')'; put '%dc_assignlib(READ,&tgtlib)'; put 'proc sql;'; put 'create table work.source as'; put 'select upcase(memname) as memname'; put ',upcase(name) as name'; put ',type'; put 'from dictionary.columns'; put 'where libname="&TGTLIB"'; put 'and memtype=''DATA'';'; put 'create table work.members as'; put 'select distinct memname as display_value'; put 'from work.source;'; put 'data work.DYNAMIC_VALUES;'; put 'set work.members;'; put 'raw_value=display_value;'; put 'display_index=_n_;'; put 'run;'; put 'proc sql;'; put 'create table work.dynamic_extended_values as'; put 'select a.display_index'; put ',b.name as display_value'; put ',"C" as display_type'; put ',b.name as RAW_VALUE_CHAR'; put ',. as RAW_VALUE_NUM'; put 'from work.dynamic_values a'; put 'left join work.source b'; put 'on a.display_value=b.memname'; put 'where b.type=''num'';'; put 'data work.dynamic_extended_values;'; put 'set work.DYNAMIC_EXTENDED_VALUES;'; put 'extra_col_name=''VAR_PROCESSED'';output;'; put 'extra_col_name=''VAR_TXFROM'';output;'; put 'extra_col_name=''VAR_TXTO'';output;'; put 'extra_col_name=''VAR_BUSFROM'';output;'; put 'extra_col_name=''VAR_BUSTO'';output;'; put 'run;'; put '/* set some force flags */'; put 'data work.dynamic_extended_values;'; put 'set work.DYNAMIC_EXTENDED_VALUES;'; put 'forced_value=0;'; put 'if extra_col_name=''VAR_TXFROM'' & raw_value_char=''TX_FROM'' then forced_value=1;'; put 'if extra_col_name=''VAR_TXTO'' & raw_value_char=''TX_TO'' then forced_value=1;'; put 'run;'; put 'proc sort;'; put 'by extra_col_name display_index;'; put 'run;'; put '* Service end;'; run; %mm_createwebservice(path=&appLoc/&path, name=&service, code=sascode, server=&serverName, replace=yes) filename sascode clear; %let service=mpe_x_test.some_num; filename sascode temp lrecl=32767; data _null_; file sascode; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '/**'; put '@file mp_jsonout.sas'; put '@brief Writes JSON in SASjs format to a fileref'; put '@details This macro can be used to OPEN a JSON stream and send one or more'; put 'tables as arrays of rows, where each row can be an object or a nested array.'; put 'There are two engines available - DATASTEP or PROCJSON.'; put 'PROC JSON is fast but will produce errs like the ones below if'; put 'special chars are encountered.'; put '> (ERR)OR: Some code points did not transcode.'; put '> An object or array close is not valid at this point in the JSON text.'; put '> Date value out of range'; put 'If this happens, try running with ENGINE=DATASTEP.'; put 'The DATASTEP engine is used to handle special SAS missing numerics, and'; put 'can also convert entire datasets to formatted values. Output JSON is always'; put 'in UTF-8.'; put 'Usage:'; put 'filename tmp temp;'; put 'data class; set sashelp.class;run;'; put '%mp_jsonout(OPEN,jref=tmp)'; put '%mp_jsonout(OBJ,class,jref=tmp)'; put '%mp_jsonout(OBJ,class,dslabel=class2,jref=tmp,showmeta=Y)'; put '%mp_jsonout(CLOSE,jref=tmp)'; put 'data _null_;'; put 'infile tmp;'; put 'input;putlog _infile_;'; put 'run;'; put 'If you are building web apps with SAS then you are strongly encouraged to use'; put 'the mX_createwebservice macros in combination with the'; put '[sasjs adapter](https://github.com/sasjs/adapter).'; put 'For more information see https://sasjs.io'; put '@param [in] action Valid values:'; put '@li OPEN - opens the JSON'; put '@li OBJ - sends a table with each row as an object'; put '@li ARR - sends a table with each row in an array'; put '@li CLOSE - closes the JSON'; put '@param [in] ds The dataset to send. Must be a work table.'; put '@param [out] jref= (_webout) The fileref to which to send the JSON'; put '@param [out] dslabel= The name to give the table in the exported JSON'; put '@param [in] fmt= (Y) Whether to keep (Y) or strip (N) formats from the table'; put '@param [in] engine= (DATASTEP) Which engine to use to send the JSON. Options:'; put '@li PROCJSON (default)'; put '@li DATASTEP (more reliable when data has non standard characters)'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to JSON'; put '

Related Files

'; put '@li mp_ds2fmtds.sas'; put '@version 9.2'; put '@author Allan Bowe'; put '@source https://github.com/sasjs/core'; put '**/'; put '%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y'; put ',engine=DATASTEP'; put ',missing=NULL'; put ',showmeta=N'; put ',maxobs=MAX'; put ')/*/STORE SOURCE*/;'; put '%local tempds colinfo fmtds i numcols numobs stmt_obs lastobs optval'; put 'tmpds1 tmpds2 tmpds3 tmpds4;'; put '%let numcols=0;'; put '%if &maxobs ne MAX %then %let stmt_obs=%str(if _n_>&maxobs then stop;);'; put '%if &action=OPEN %then %do;'; put 'options nobomfile;'; put 'data _null_;file &jref encoding=''utf-8'' lrecl=200;'; put 'put ''{"PROCESSED_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''"'';'; put 'run;'; put '%end;'; put '%else %if (&action=ARR or &action=OBJ) %then %do;'; put '/* force variable names to always be uppercase in the JSON */'; put 'options validvarname=upcase;'; put '/* To avoid issues with _webout on EBI - such as encoding diffs and truncation'; put '(https://support.sas.com/kb/49/325.html) we use temporary files */'; put 'filename _sjs1 temp lrecl=200 ;'; put 'data _null_; file _sjs1 encoding=''utf-8'';'; put 'put ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":";'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjs1 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs1 clear;'; put '/* grab col defs */'; put 'proc contents noprint data=&ds'; put 'out=_data_(keep=name type length format formatl formatd varnum label);'; put 'run;'; put '%let colinfo=%scan(&syslast,2,.);'; put 'proc sort data=&colinfo;'; put 'by varnum;'; put 'run;'; put '/* move meta to mac vars */'; put 'data &colinfo;'; put 'if _n_=1 then call symputx(''numcols'',nobs,''l'');'; put 'set &colinfo end=last nobs=nobs;'; put 'name=upcase(name);'; put '/* fix formats */'; put 'if type=2 or type=6 then do;'; put 'typelong=''char'';'; put 'length fmt $49.;'; put 'if format='''' then fmt=cats(''$'',length,''.'');'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else fmt=cats(format,formatl,''.'');'; put 'end;'; put 'else do;'; put 'typelong=''num'';'; put 'if format='''' then fmt=''best.'';'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else if formatd=0 then fmt=cats(format,formatl,''.'');'; put 'else fmt=cats(format,formatl,''.'',formatd);'; put 'end;'; put '/* 32 char unique name */'; put 'newname=''sasjs''!!substr(cats(put(md5(name),$hex32.)),1,27);'; put 'call symputx(cats(''name'',_n_),name,''l'');'; put 'call symputx(cats(''newname'',_n_),newname,''l'');'; put 'call symputx(cats(''length'',_n_),length,''l'');'; put 'call symputx(cats(''fmt'',_n_),fmt,''l'');'; put 'call symputx(cats(''type'',_n_),type,''l'');'; put 'call symputx(cats(''typelong'',_n_),typelong,''l'');'; put 'call symputx(cats(''label'',_n_),coalescec(label,name),''l'');'; put '/* overwritten when fmt=Y and a custom format exists in catalog */'; put 'if typelong=''num'' then call symputx(cats(''fmtlen'',_n_),200,''l'');'; put 'else call symputx(cats(''fmtlen'',_n_),min(32767,ceil((length+10)*1.5)),''l'');'; put 'run;'; put '%let tempds=%substr(_%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql;'; put 'select count(*) into: lastobs from &ds;'; put '%if &maxobs ne MAX %then %let lastobs=%sysfunc(min(&lastobs,&maxobs));'; put '%if &engine=PROCJSON %then %do;'; put '%if &missing=STRING %then %do;'; put '%put &sysmacroname: Special Missings not supported in proc json.;'; put '%put &sysmacroname: Switching to DATASTEP engine;'; put '%goto datastep;'; put '%end;'; put 'data &tempds;'; put 'set &ds;'; put '&stmt_obs;'; put '%if &fmt=N %then format _numeric_ best32.;;'; put '/* PRETTY is necessary to avoid line truncation in large files */'; put 'filename _sjs2 temp lrecl=131068 encoding=''utf-8'';'; put 'proc json out=_sjs2 pretty'; put '%if &action=ARR %then nokeys ;'; put ';export &tempds / nosastags fmtnumeric;'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs2 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs2 clear;'; put '%end;'; put '%else %if &engine=DATASTEP %then %do;'; put '%datastep:'; put '%if %sysfunc(exist(&ds)) ne 1 & %sysfunc(exist(&ds,VIEW)) ne 1'; put '%then %do;'; put '%put &sysmacroname: &ds NOT FOUND!!!;'; put '%return;'; put '%end;'; put '%if &fmt=Y %then %do;'; put '/**'; put '* Extract format definitions'; put '* First, by getting library locations from dictionary.formats'; put '* Then, by exporting the width using proc format'; put '* Cannot use maxw from sashelp.vformat as not always populated'; put '* Cannot use fmtinfo() as not supported in all flavours'; put '*/'; put '%let tmpds1=%substr(fmtsum%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds2=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds3=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds4=%substr(col%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql noprint;'; put 'create table &tmpds1 as'; put 'select cats(libname,''.'',memname) as FMTCAT,'; put 'FMTNAME'; put 'from dictionary.formats'; put 'where fmttype=''F'' and libname is not null'; put 'and fmtname in (select format from &colinfo where format is not null)'; put 'order by 1;'; put 'create table &tmpds2('; put 'FMTNAME char(32),'; put 'LENGTH num'; put ');'; put '%local catlist cat fmtlist i;'; put 'select distinct fmtcat into: catlist separated by '' '' from &tmpds1;'; put '%do i=1 %to %sysfunc(countw(&catlist,%str( )));'; put '%let cat=%scan(&catlist,&i,%str( ));'; put 'proc sql;'; put 'select distinct fmtname into: fmtlist separated by '' '''; put 'from &tmpds1 where fmtcat="&cat";'; put 'proc format lib=&cat cntlout=&tmpds3(keep=fmtname length);'; put 'select &fmtlist;'; put 'run;'; put 'proc sql;'; put 'insert into &tmpds2 select distinct fmtname,length from &tmpds3;'; put '%end;'; put 'proc sql;'; put 'create table &tmpds4 as'; put 'select a.*, b.length as MAXW'; put 'from &colinfo a'; put 'left join &tmpds2 b'; put 'on cats(a.format)=cats(upcase(b.fmtname))'; put 'order by a.varnum;'; put 'data _null_;'; put 'set &tmpds4;'; put 'if not missing(maxw);'; put 'call symputx('; put 'cats(''fmtlen'',_n_),'; put '/* vars need extra padding due to JSON escaping of special chars */'; put 'min(32767,ceil((max(length,maxw)+10)*1.5))'; put ',''l'''; put ');'; put 'run;'; put '/* configure varlenchk - as we are explicitly shortening the variables */'; put '%let optval=%sysfunc(getoption(varlenchk));'; put 'options varlenchk=NOWARN;'; put 'data _data_(compress=char);'; put '/* shorten the new vars */'; put 'length'; put '%do i=1 %to &numcols;'; put '&&name&i $&&fmtlen&i'; put '%end;'; put ';'; put '/* rename on entry */'; put 'set &ds(rename=('; put '%do i=1 %to &numcols;'; put '&&name&i=&&newname&i'; put '%end;'; put '));'; put '&stmt_obs;'; put 'drop'; put '%do i=1 %to &numcols;'; put '&&newname&i'; put '%end;'; put ';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=num %then %do;'; put '&&name&i=cats(put(&&newname&i,&&fmt&i));'; put '%end;'; put '%else %do;'; put '&&name&i=put(&&newname&i,&&fmt&i);'; put '%end;'; put '%end;'; put 'if _error_ then do;'; put 'call symputx(''syscc'',1012);'; put 'stop;'; put 'end;'; put 'run;'; put '%let fmtds=&syslast;'; put 'options varlenchk=&optval;'; put '%end;'; put 'proc format; /* credit yabwon for special null removal */'; put 'value bart (default=40)'; put '%if &missing=NULL %then %do;'; put '._ - .z = null'; put '%end;'; put '%else %do;'; put '._ = [quote()]'; put '. = null'; put '.a - .z = [quote()]'; put '%end;'; put 'other = [best.];'; put 'data &tempds;'; put 'attrib _all_ label='''';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'length &&name&i $&&fmtlen&i...;'; put 'format &&name&i $&&fmtlen&i...;'; put '%end;'; put '%end;'; put '%if &fmt=Y %then %do;'; put 'set &fmtds;'; put '%end;'; put '%else %do;'; put 'set &ds;'; put '%end;'; put '&stmt_obs;'; put 'format _numeric_ bart.;'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'if findc(&&name&i,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put '&&name&i=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,&&name&i)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else &&name&i=quote(cats(&&name&i));'; put '%end;'; put '%end;'; put 'run;'; put 'filename _sjs3 temp lrecl=131068 ;'; put 'data _null_;'; put 'file _sjs3 encoding=''utf-8'';'; put 'if _n_=1 then put "[";'; put 'set &tempds;'; put 'if _n_>1 then put "," @; put'; put '%if &action=ARR %then "[" ; %else "{" ;'; put '%do i=1 %to &numcols;'; put '%if &i>1 %then "," ;'; put '%if &action=OBJ %then """&&name&i"":" ;'; put '"&&name&i"n /* name literal for reserved variable names */'; put '%end;'; put '%if &action=ARR %then "]" ; %else "}" ; ;'; put '/* close out the table */'; put 'data _null_;'; put 'file _sjs3 mod encoding=''utf-8'';'; put 'put '']'';'; put 'run;'; put 'data _null_;'; put 'infile _sjs3 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs3 clear;'; put '%end;'; put 'proc sql;'; put 'drop table &colinfo, &tempds;'; put '%if %substr(&showmeta,1,1)=Y %then %do;'; put 'filename _sjs4 temp lrecl=131068 encoding=''utf-8'';'; put 'data _null_;'; put 'file _sjs4;'; put 'length label $350;'; put 'put ", ""$%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":{""vars"":{";'; put 'do i=1 to &numcols;'; put 'name=quote(trim(symget(cats(''name'',i))));'; put 'format=quote(trim(symget(cats(''fmt'',i))));'; put 'label=quote(prxchange(''s/\\/\\\\/'',-1,trim(symget(cats(''label'',i)))));'; put 'length=quote(trim(symget(cats(''length'',i))));'; put 'type=quote(trim(symget(cats(''typelong'',i))));'; put 'if i>1 then put "," @@;'; put 'put name '':{"format":'' format '',"label":'' label'; put ''',"length":'' length '',"type":'' type ''}'';'; put 'end;'; put 'put ''}}'';'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs4 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs4 clear;'; put '%end;'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put 'data _null_; file &jref encoding=''utf-8'' mod ;'; put 'put "}";'; put 'run;'; put '%end;'; put '%mend mp_jsonout;'; put '/**'; put '@file mm_webout.sas'; put '@brief Send data to/from SAS Stored Processes'; put '@details This macro should be added to the start of each Stored Process,'; put '**immediately** followed by a call to:'; put '%mm_webout(FETCH)'; put 'This will read all the input data and create same-named SAS datasets in the'; put 'WORK library. You can then insert your code, and send data back using the'; put 'following syntax:'; put 'data some datasets; * make some data ;'; put 'retain some columns;'; put 'run;'; put '%mm_webout(OPEN)'; put '%mm_webout(ARR,some) * Array format, fast, suitable for large tables ;'; put '%mm_webout(OBJ,datasets) * Object format, easier to work with ;'; put 'Finally, wrap everything up send some helpful system variables too'; put '%mm_webout(CLOSE)'; put '@param [in] action Either FETCH, OPEN, ARR, OBJ or CLOSE'; put '@param [in] ds The dataset to send back to the frontend'; put '@param [out] dslabel= Value to use instead of table name for sending to JSON'; put '@param [in] fmt= (N) Setting Y converts all vars to their formatted values'; put '@param [out] fref= (_webout) The fileref to which to write the JSON'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] workobs= (0) When set to a positive integer, will create a new'; put 'output object (WORK) which contains this number of observations from all'; put 'tables in the WORK library.'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to output JSON'; put '

SAS Macros

'; put '@li mp_jsonout.sas'; put '

Related Macros

'; put '@li ms_webout.sas'; put '@li mv_webout.sas'; put '@version 9.3'; put '@author Allan Bowe'; put '**/'; put '%macro mm_webout(action,ds,dslabel=,fref=_webout,fmt=N,missing=NULL'; put ',showmeta=N,maxobs=MAX,workobs=0'; put ');'; put '%global _webin_file_count _webin_fileref1 _webin_name1 _program _debug'; put 'sasjs_tables;'; put '%local i tempds jsonengine;'; put '/* see https://github.com/sasjs/core/issues/41 */'; put '%if "%upcase(&SYSENCODING)" ne "UTF-8" %then %let jsonengine=PROCJSON;'; put '%else %let jsonengine=DATASTEP;'; put '%if &action=FETCH %then %do;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'options mprint notes mprintnest;'; put '%end;'; put '%let _webin_file_count=%eval(&_webin_file_count+0);'; put '/* now read in the data */'; put '%do i=1 %to &_webin_file_count;'; put '%if &_webin_file_count=1 %then %do;'; put '%let _webin_fileref1=&_webin_fileref;'; put '%let _webin_name1=&_webin_name;'; put '%end;'; put 'data _null_;'; put 'infile &&_webin_fileref&i termstr=crlf;'; put 'input;'; put 'call symputx(''input_statement'',_infile_);'; put 'putlog "&&_webin_name&i input statement: " _infile_;'; put 'stop;'; put 'data &&_webin_name&i;'; put 'infile &&_webin_fileref&i firstobs=2 dsd termstr=crlf encoding=''utf-8'';'; put 'input &input_statement;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'if _n_<20 then putlog _infile_;'; put '%end;'; put 'run;'; put '%let sasjs_tables=&sasjs_tables &&_webin_name&i;'; put '%end;'; put '%end;'; put '%else %if &action=OPEN %then %do;'; put '/* fix encoding */'; put 'OPTIONS NOBOMFILE;'; put '/**'; put '* check xengine type to avoid the below err message:'; put '* > Function is only valid for filerefs using the CACHE access method.'; put '*/'; put 'data _null_;'; put 'set sashelp.vextfl(where=(fileref="_WEBOUT"));'; put 'if xengine=''STREAM'' then do;'; put 'rc=stpsrv_header(''Content-type'',"text/html; encoding=utf-8");'; put 'end;'; put 'run;'; put '/* setup json */'; put 'data _null_;file &fref encoding=''utf-8'';'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutBEGIN<<'';'; put '%end;'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'run;'; put '%end;'; put '%else %if &action=ARR or &action=OBJ %then %do;'; put '%mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt,jref=&fref'; put ',engine=&jsonengine,missing=&missing,showmeta=&showmeta,maxobs=&maxobs'; put ')'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put '/* To avoid issues with _webout on EBI we use a temporary file */'; put 'filename _sjsref temp lrecl=131068;'; put '%if %str(&workobs) > 0 %then %do;'; put '/* if debug mode, send back first XX records of each work table also */'; put 'data;run;%let tempds=%scan(&syslast,2,.);'; put 'ods output Members=&tempds;'; put 'proc datasets library=WORK memtype=data;'; put '%local wtcnt;%let wtcnt=0;'; put 'data _null_;'; put 'set &tempds;'; put 'if not (upcase(name) =:"DATA"); /* ignore temp datasets */'; put 'i+1;'; put 'call symputx(cats(''wt'',i),name,''l'');'; put 'call symputx(''wtcnt'',i,''l'');'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put ",""WORK"":{";'; put '%do i=1 %to &wtcnt;'; put '%let wt=&&wt&i;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'dsid=open("WORK.&wt",''is'');'; put 'nlobs=attrn(dsid,''NLOBS'');'; put 'nvars=attrn(dsid,''NVARS'');'; put 'rc=close(dsid);'; put 'if &i>1 then put '',''@;'; put 'put " ""&wt"" : {";'; put 'put ''"nlobs":'' nlobs;'; put 'put '',"nvars":'' nvars;'; put '%mp_jsonout(OBJ,&wt,jref=_sjsref,dslabel=first10rows,showmeta=Y'; put ',maxobs=&workobs'; put ')'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put '%end;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put 'run;'; put '%end;'; put '/* close off json */'; put 'data _null_;file _sjsref mod encoding=''utf-8'';'; put 'length SYSPROCESSNAME syserrortext syswarningtext autoexec $512;'; put 'put ",""_DEBUG"" : ""&_debug"" ";'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'autoexec=quote(urlencode(trim(getoption(''autoexec''))));'; put 'put '',"AUTOEXEC" : '' autoexec;'; put 'put ",""MF_GETUSER"" : ""%mf_getuser()"" ";'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'put ",""SYSENCODING"" : ""&sysencoding"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSPROCESSID"" : ""&SYSPROCESSID"" ";'; put 'put ",""SYSPROCESSMODE"" : ""&SYSPROCESSMODE"" ";'; put 'SYSPROCESSNAME=quote(urlencode(cats(SYSPROCESSNAME)));'; put 'put ",""SYSPROCESSNAME"" : " SYSPROCESSNAME;'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put '',"SYSWARNINGTEXT" : '' syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'length memsize $32;'; put 'memsize="%sysfunc(INPUTN(%sysfunc(getoption(memsize)), best.),sizekmg.)";'; put 'memsize=quote(cats(memsize));'; put 'put '',"MEMSIZE" : '' memsize;'; put 'put "}" @;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutEND<<'';'; put '%end;'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjsref lrecl=1 recfm=n;'; put 'file &fref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjsref clear;'; put '%end;'; put '%mend mm_webout;'; put '%macro webout(action,ds,dslabel=,fmt=,missing=NULL,showmeta=NO,maxobs=MAX);'; put '%mm_webout(&action,ds=&ds,dslabel=&dslabel,fmt=&fmt'; put ',missing=&missing'; put ',showmeta=&showmeta'; put ',maxobs=&maxobs'; put ') %mend;'; put '/* provide additional debug info */'; put '%global _program;'; put '%put &=syscc;'; put '%put user=%mf_getuser();'; put '%put pgm=&_program;'; put '%put timestamp=%sysfunc(datetime(),datetime19.);'; put '* Service Variables start;'; put '* Service Variables end;'; put '* SAS Macros start;'; put '%macro mf_getapploc(pgm);'; put '%if "&pgm"="" %then %do;'; put '%if %symexist(_program) %then %let pgm=&_program;'; put '%else %do;'; put '%put &sysmacroname: No value provided and no _program variable available;'; put '%return;'; put '%end;'; put '%end;'; put '%local root;'; put '/**'; put '* First check we are not in the tests/macros folder (which has no subfolders)'; put '* or specifically in the testsetup or testteardown services'; put '*/'; put '%if %index(&pgm,/tests/macros/)'; put 'or %index(&pgm,/tests/testsetup)'; put 'or %index(&pgm,/tests/testteardown)'; put '%then %do;'; put '%let root=%substr(&pgm,1,%index(&pgm,/tests)-1);'; put '&root'; put '%return;'; put '%end;'; put '/**'; put '* Next, move up two levels to avoid matches on subfolder or service name'; put '*/'; put '%let root=%substr(&pgm,1,%length(&pgm)-%length(%scan(&pgm,-1,/))-1);'; put '%let root=%substr(&root,1,%length(&root)-%length(%scan(&root,-1,/))-1);'; put '%if %index(&root,/tests/) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/tests/)-1);'; put '%end;'; put '%else %if %index(&root,/services) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/services)-1);'; put '%end;'; put '%else %if %index(&root,/jobs) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/jobs)-1);'; put '%end;'; put '%else %put &sysmacroname: Could not find an app location from &pgm;'; put '&root'; put '%mend mf_getapploc ;'; put '%macro mf_getuniquefileref(prefix=_,maxtries=1000,lrecl=32767);'; put '%local rc fname;'; put '%if &prefix=0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%end;'; put '%else %do;'; put '%local x len;'; put '%let len=%eval(8-%length(&prefix));'; put '%let x=0;'; put '%do x=0 %to &maxtries;'; put '%let fname=&prefix%substr(%sysfunc(ranuni(0)),3,&len);'; put '%if %sysfunc(fileref(&fname)) > 0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%return;'; put '%end;'; put '%end;'; put '%put unable to find available fileref after &maxtries attempts;'; put '%end;'; put '%mend mf_getuniquefileref;'; put '%macro mp_abort(mac=mp_abort.sas, type=, msg=, iftrue=%str(1=1)'; put ', errds=work.mp_abort_errds'; put ', mode=REGULAR'; put ')/*/STORE SOURCE*/;'; put '%global sysprocessmode sysprocessname sasjs_stpsrv_header_loc sasjsprocessmode;'; put '%local fref fid i;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%put NOTE: /// mp_abort macro executing //;'; put '%if %length(&mac)>0 %then %put NOTE- called by &mac;'; put '%put NOTE - &msg;'; put '%if %symexist(_SYSINCLUDEFILEDEVICE)'; put '/* abort cancel FILE does not restart outside the INCLUDE on Viya 3.5 */'; put 'and %superq(SYSPROCESSNAME) ne %str(Compute Server)'; put '%then %do;'; put '%if "*&_SYSINCLUDEFILEDEVICE*" ne "**" %then %do;'; put 'data &errds;'; put 'iftrue=''1=1'';'; put 'length mac $100 msg $5000;'; put 'mac=symget(''mac'');'; put 'msg=symget(''msg'');'; put 'run;'; put 'data _null_;'; put 'abort cancel FILE;'; put 'run;'; put '%return;'; put '%end;'; put '%end;'; put '/* Web App Context */'; put '%if %symexist(_PROGRAM)'; put 'or %superq(SYSPROCESSNAME) = %str(Compute Server)'; put 'or &mode=INCLUDE'; put '%then %do;'; put 'options obs=max replace mprint;'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5"'; put '%then %do;'; put 'options nosyntaxcheck;'; put '%end;'; put '%if &mode=INCLUDE %then %do;'; put '%if %sysfunc(exist(&errds))=1 %then %do;'; put 'data _null_;'; put 'set &errds;'; put 'call symputx(''iftrue'',iftrue,''l'');'; put 'call symputx(''mac'',mac,''l'');'; put 'call symputx(''msg'',msg,''l'');'; put 'putlog (_all_)(=);'; put 'run;'; put '%if (&iftrue)=0 %then %return;'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: No include errors found;'; put '%return;'; put '%end;'; put '%end;'; put '/* extract log errs / warns, if exist */'; put '%local logloc logline;'; put '%global logmsg; /* capture global messages */'; put '%if %symexist(SYSPRINTTOLOG) %then %let logloc=&SYSPRINTTOLOG;'; put '%else %let logloc=%qsysfunc(getoption(LOG));'; put 'proc printto log=log;run;'; put '%let logline=0;'; put '%if %length(&logloc)>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input; putlog _infile_;'; put 'i=1;'; put 'retain logonce 0;'; put 'if ('; put '_infile_=:"%str(WARN)ING" or _infile_=:"%str(ERR)OR"'; put ') and logonce=0 then'; put 'do;'; put 'call symputx(''logline'',_n_);'; put 'logonce+1;'; put 'end;'; put 'run;'; put '/* capture log including lines BEFORE the err */'; put '%if &logline>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input;'; put 'i=1;'; put 'stoploop=0;'; put 'if _n_ ge &logline-15 and stoploop=0 then do until (i>22);'; put 'call symputx(''logmsg'',catx(''\n'',symget(''logmsg''),_infile_));'; put 'input;'; put 'i+1;'; put 'stoploop=1;'; put 'end;'; put 'if stoploop=1 then stop;'; put 'run;'; put '%end;'; put '%end;'; put '%if %symexist(SYS_JES_JOB_URI) %then %do;'; put '/* setup webout for Viya */'; put 'options nobomfile;'; put '%if "X&SYS_JES_JOB_URI.X"="XX" %then %do;'; put 'filename _webout temp lrecl=999999 mod;'; put '%end;'; put '%else %do;'; put 'filename _webout filesrvc parenturi="&SYS_JES_JOB_URI"'; put 'name="_webout.json" lrecl=999999 mod;'; put '%end;'; put '%end;'; put '%else %if %sysfunc(filename(fref,&sasjs_stpsrv_header_loc))=0 %then %do;'; put 'options nobomfile;'; put '/* set up http header for SASjs Server */'; put '%let fid=%sysfunc(fopen(&fref,A));'; put '%if &fid=0 %then %do;'; put '%put %str(ERR)OR: %sysfunc(sysmsg());'; put '%return;'; put '%end;'; put '%let rc=%sysfunc(fput(&fid,%str(Content-Type: application/json)));'; put '%let rc=%sysfunc(fwrite(&fid));'; put '%let rc=%sysfunc(fclose(&fid));'; put '%let rc=%sysfunc(filename(&fref));'; put '%end;'; put '/* send response in SASjs JSON format */'; put 'data _null_;'; put 'file _webout mod lrecl=32000 encoding=''utf-8'';'; put 'length msg syswarningtext syserrortext $32767 mode $10 ;'; put 'sasdatetime=datetime();'; put 'msg=symget(''msg'');'; put '%if &logline>0 %then %do;'; put 'msg=cats(msg,''\n\nLog Extract:\n'',symget(''logmsg''));'; put '%end;'; put '/* escape the escapes */'; put 'msg=tranwrd(msg,''\'',''\\'');'; put '/* escape the quotes */'; put 'msg=tranwrd(msg,''"'',''\"'');'; put '/* ditch the CRLFs as chrome complains */'; put 'msg=compress(msg,,''kw'');'; put '/* quote without quoting the quotes (which are escaped instead) */'; put 'msg=cats(''"'',msg,''"'');'; put 'if symexist(''_debug'') then debug=quote(trim(symget(''_debug'')));'; put 'else debug=''""'';'; put 'if symget(''sasjsprocessmode'')=''Stored Program'' then mode=''SASJS'';'; put 'if mode ne ''SASJS'' then put ''>>weboutBEGIN<<'';'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'put '',"sasjsAbort" : [{'';'; put 'put '' "MSG":'' msg ;'; put 'put '' ,"MAC": "'' "&mac" ''"}]'';'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'put '',"_DEBUG":'' debug ;'; put 'if symexist(''_metauser'') then do;'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put 'end;'; put 'if symexist(''SYS_JES_JOB_URI'') then do;'; put 'SYS_JES_JOB_URI=quote(trim(symget(''SYS_JES_JOB_URI'')));'; put 'put '',"SYS_JES_JOB_URI": '' SYS_JES_JOB_URI;'; put 'end;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put ",""SYSWARNINGTEXT"" : " syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'put "}" ;'; put 'if mode ne ''SASJS'' then put ''>>weboutEND<<'';'; put 'run;'; put '%put _all_;'; put '%if "&sysprocessmode " = "SAS Stored Process Server " %then %do;'; put 'data _null_;'; put 'putlog ''stpsrvset program err and syscc'';'; put 'rc=stpsrvset(''program error'', 0);'; put 'call symputx("syscc",0,"g");'; put 'run;'; put '%if &sysscp=WIN'; put 'and 1=0 /* deprecating this logic until we figure out a consistent abort */'; put 'and "%substr(%str(&sysvlong ),1,8)"="9.04.01M"'; put 'and "%substr(%str(&sysvlong ),9,1)">"5" %then %do;'; put '/* skip approach (below) does not work in windows m6+ envs */'; put 'endsas;'; put '%end;'; put '%else %do;'; put '/**'; put '* endsas kills 9.4m3 deployments by orphaning multibridges.'; put '* Abort variants are ungraceful (non zero return code)'; put '* This approach lets SAS run silently until the end :-)'; put '* Caution - fails when called within a %include within a macro'; put '* Use mp_include() to handle this.'; put '*/'; put 'filename skip temp;'; put 'data _null_;'; put 'file skip;'; put 'put ''%macro skip();'';'; put 'comment ''%mend skip; -> fix lint '';'; put 'put ''%macro skippy();'';'; put 'comment ''%mend skippy; -> fix lint '';'; put 'run;'; put '%inc skip;'; put '%end;'; put '%end;'; put '%else %if "&sysprocessmode " = "SAS Compute Server " %then %do;'; put '/* endsas kills the session making it harder to fetch results */'; put 'data _null_;'; put 'syswarningtext=symget(''syswarningtext'');'; put 'syserrortext=symget(''syserrortext'');'; put 'abort_msg=symget(''msg'');'; put 'syscc=symget(''syscc'');'; put 'sysuserid=symget(''sysuserid'');'; put 'iftrue=symget(''iftrue'');'; put 'put (_all_)(/=);'; put 'call symputx(''syscc'',0);'; put 'abort cancel nolist;'; put 'run;'; put '%end;'; put '%else %do;'; put '%abort cancel;'; put '%end;'; put '%end;'; put '%else %do;'; put '%put _all_;'; put '%abort cancel;'; put '%end;'; put '%mend mp_abort;'; put '/** @endcond */'; put '%macro mm_getstpcode('; put 'tree=/User Folders/sasdemo/somestp'; put ',name='; put ',outloc=0'; put ',outref=0'; put ',mDebug=1'; put ',showlog=NO'; put ');'; put '%local mD;'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing &sysmacroname..sas;'; put '%&mD.put _local_;'; put '%if %length(&name)>0 %then %let name=/&name;'; put '/* first, check if STP exists */'; put '%local tsuri;'; put '%let tsuri=stopifempty ;'; put 'data _null_;'; put 'format type uri tsuri value $200.;'; put 'call missing (of _all_);'; put 'path="&tree&name(StoredProcess)";'; put '/* first, find the STP ID */'; put 'if metadata_pathobj("",path,"StoredProcess",type,uri)>0 then do;'; put '/* get sourcecode */'; put 'cnt=1;'; put 'do while (metadata_getnasn(uri,"Notes",cnt,tsuri)>0);'; put 'rc=metadata_getattr(tsuri,"Name",value);'; put '&mD.put tsuri= value=;'; put 'if value="SourceCode" then do;'; put '/* found it! */'; put 'rc=metadata_getattr(tsuri,"Id",value);'; put 'call symputx(''tsuri'',value,''l'');'; put 'stop;'; put 'end;'; put 'cnt+1;'; put 'end;'; put 'end;'; put 'else put (_all_)(=);'; put 'run;'; put '%mp_abort(iftrue= (&tsuri=stopifempty)'; put ',mac=mm_getstpcode'; put ',msg=%str(&tree&name.(StoredProcess) not found!)'; put ')'; put '/**'; put '* Now we can extract the textstore'; put '*/'; put 'filename __getdoc temp lrecl=10000000;'; put 'proc metadata'; put 'in="$METAREPOSITORY'; put ''; put 'SAS1"'; put 'out=__getdoc ;'; put 'run;'; put '/* find the beginning of the text */'; put '%local start;'; put 'data _null_;'; put 'infile __getdoc lrecl=10000;'; put 'input;'; put 'start=index(_infile_,''StoredText="'');'; put 'if start then do;'; put 'call symputx("start",start+11);'; put '*putlog ''"'' _infile_ ''"'';'; put 'end;'; put 'stop;'; put '%local outeng;'; put '%if "&outloc"="0" %then %let outeng=TEMP;'; put '%else %let outeng="&outloc";'; put '%local fref;'; put '%if &outref=0 %then %let fref=%mf_getuniquefileref();'; put '%else %let fref=&outref;'; put '/* read the content, byte by byte, resolving escaped chars */'; put 'filename &fref &outeng lrecl=100000;'; put 'data _null_;'; put 'length filein 8 fileid 8;'; put 'filein = fopen("__getdoc","I",1,"B");'; put 'fileid = fopen("&fref","O",1,"B");'; put 'rec = "20"x;'; put 'length entity $6;'; put 'do while(fread(filein)=0);'; put 'x+1;'; put 'if x>&start then do;'; put 'rc = fget(filein,rec,1);'; put 'if rec=''"'' then leave;'; put 'else if rec="&" then do;'; put 'entity=rec;'; put 'do until (rec=";");'; put 'if fread(filein) ne 0 then goto getout;'; put 'rc = fget(filein,rec,1);'; put 'entity=cats(entity,rec);'; put 'end;'; put 'select (entity);'; put 'when (''&'' ) rec=''&'' ;'; put 'when (''<'' ) rec=''<'' ;'; put 'when (''>'' ) rec=''>'' ;'; put 'when (''''') rec="''" ;'; put 'when (''"'') rec=''"'' ;'; put 'when ('' '') rec=''0A''x;'; put 'when ('' '') rec=''0D''x;'; put 'when (''$'' ) rec=''$'' ;'; put 'when ('' '') rec=''09''x;'; put 'otherwise putlog "%str(WARN)ING: missing value for " entity=;'; put 'end;'; put 'rc =fput(fileid, substr(rec,1,1));'; put 'rc =fwrite(fileid);'; put 'end;'; put 'else do;'; put 'rc =fput(fileid,rec);'; put 'rc =fwrite(fileid);'; put 'end;'; put 'end;'; put 'end;'; put 'getout:'; put 'rc=fclose(filein);'; put 'rc=fclose(fileid);'; put 'run;'; put '%if &showlog=YES %then %do;'; put 'data _null_;'; put 'infile &fref lrecl=32767 end=last;'; put 'input;'; put 'if _n_=1 then putlog ''>>stpcodeBEGIN<<'';'; put 'putlog _infile_;'; put 'if last then putlog ''>>stpcodeEND<<'';'; put 'run;'; put '%end;'; put 'filename __getdoc clear;'; put '%if &outref=0 %then %do;'; put 'filename &fref clear;'; put '%end;'; put '%mend mm_getstpcode;'; put '%macro dc_getsettings();'; put '%global _program;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(syscc=&syscc on entry (&syswarningtext &syserrortext))'; put ')'; put '%if %length(&_PROGRAM)>1 %then %let root=&_program;'; put '%else %do;'; put '%global _metauser;'; put '%let _metauser=&sysuserid;'; put '/* to mimic a "real" _program we need to give a dummy role and stp name */'; put '%let root=/dummyRole/dummyName;'; put '%end;'; put '/* the DC precode is stored in the Admin folder in the root of'; put 'the project. Lets find that root. */'; put '%put &=root;'; put '%let root=%mf_getapploc();'; put '%put &=root;'; put '/* Now we know the root location we can retrieve the params */'; put '%let temploc=%sysfunc(pathname(work))/settings.txt;'; put '%mm_getstpcode(tree=&root/services/public'; put ',name=Data_Controller_Settings'; put ',outloc=&temploc'; put ')'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Unable to run getstpcode)'; put ')'; put 'filename _getsets "&temploc" lrecl=2000;'; put '/*'; put 'Do not use mp_include here - this puts a copy in every service, which creates'; put 'compilation problems when calling services from mp_include'; put '*/'; put '%inc _getsets/source2;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Problem running &sysmacroname (&syswarningtext &syserrortext))'; put ')'; put '%mend dc_getsettings;'; put '%macro mf_fmtdttm('; put ')/*/STORE SOURCE*/;'; put '%if "&sysver"="9.2" or "&sysver"="9.3"'; put 'or ("&sysver"="9.4" and "%substr(&SYSVLONG,9,1)" le "3")'; put 'or "%substr(&sysver,1,1)"="4"'; put 'or "%substr(&sysver,1,1)"="5"'; put '%then %do;DATETIME19.3%end;'; put '%else %do;E8601DT26.6%end;'; put '%mend mf_fmtdttm;'; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '%macro mp_init(prefix=SASJS'; put ')/*/STORE SOURCE*/;'; put '%if %symexist(SASJS_PREFIX) %then %return; /* only run once */'; put '%global'; put 'SASJS_PREFIX /* the ONLY hard-coded global macro variable in SASjs */'; put '&prefix._FUNCTIONS /* used in mcf_init() to track core function compilation */'; put '&prefix._INIT_NUM /* initialisation time as numeric */'; put '&prefix._INIT_DTTM /* initialisation time in E8601DT26.6 format */'; put '&prefix.WORK /* avoid typing %sysfunc(pathname(work)) every time */'; put ';'; put '%let sasjs_prefix=&prefix;'; put 'data _null_;'; put 'dttm=datetime();'; put 'call symputx("&sasjs_prefix._init_num",dttm,''g'');'; put 'call symputx("&sasjs_prefix._init_dttm",put(dttm,E8601DT26.6),''g'');'; put 'call symputx("&sasjs_prefix.work",pathname(''WORK''),''g'');'; put 'run;'; put 'options'; put 'compress=CHAR /* default is none so ensure we have something! */'; put 'datastmtchk=ALLKEYWORDS /* protection from overwriting input datasets */'; put 'errorcheck=STRICT /* catch errs in libname/filename statements */'; put 'fmterr /* ensure err when a format cannot be found */'; put 'mergenoby=%str(ERR)OR /* throw err when a merge has no BY variables */'; put 'missing=. /* changing this can cause hard to detect errs */'; put 'noquotelenmax /* avoid warnings for long strings */'; put 'noreplace /* avoid overwriting permanent datasets */'; put 'ps=max /* reduce log size slightly */'; put 'ls=max /* reduce log even more and avoid word truncation */'; put 'validmemname=COMPATIBLE /* avoid special characters etc in table names */'; put 'validvarname=V7 /* avoid special characters etc in variable names */'; put 'varinitchk=%str(ERR)OR /* avoid data mistakes from variable name typos */'; put 'varlenchk=%str(ERR)OR /* fail hard if truncation (data loss) can result */'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5" %then %do;'; put 'noautocorrect /* disallow misspelled procedure names */'; put 'dsoptions=note2err /* undocumented - convert bad NOTEs to ERRs */'; put '%end;'; put ';'; put '%mend mp_init;'; put '%macro mpeinit(fetch=YES);'; put '%global mpeinit'; put 'mpeadmins /* group with unrestricted Meditor access */'; put 'mpelocapprovals /* location for landing and staging files */'; put 'mpelib /* location of configuration tables for DC */'; put 'dc_repo_users /* location of user / group metadata */'; put 'dc_licence_key /* extracted in dc_getsettings */'; put 'dc_activation_key /* extracted in dc_getsettings */'; put 'dc_locale /* extracted in dc_getsettings */'; put 'dc_dttmtfmt /* can be overridden in dc_getsettings */'; put '_debug'; put ';'; put '%if &mpeinit=1 %then %return;'; put '%else %let mpeinit=1;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(Problem on service startup (&syswarningtext &syserrortext))'; put ')'; put '%mp_init()'; put '%if &fetch=YES %then %do;'; put '%webout(FETCH)'; put '%end;'; put '%global _CLIENTNAME;'; put '%mp_abort(iftrue= (&_CLIENTNAME=SAS Enterprise Guide)'; put ',mac=&_program..sas'; put ',msg=%str(Data Controller is a web app and should not be executed from EG)'; put ')'; put 'options urlencoding=utf8 nobomfile lrecl=32767;'; put '%let perf=%sysfunc(datetime());'; put '%put perfdiff: 0;'; put '%let dc_locale=SYSTEM; /* default if not set */'; put '/**'; put '* E8601DT26.6 has widest database support - but not all SAS flavours can'; put '* handle it. Override in the settings STP if needed.'; put '*/'; put 'data _null_;'; put 'dc_dttmtfmt=''"%sysfunc(datetime(),''!!"%mf_fmtdttm()"!!'')"dt'';'; put 'call symputx(''dc_dttmtfmt'',dc_dttmtfmt);'; put 'put dc_dttmtfmt=;'; put 'run;'; put '%put &=dc_dttmtfmt;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc prior to dc_getsettings)'; put ')'; put '%dc_getsettings()'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc after dc_getsettings)'; put ')'; put 'data _null_;'; put 'set &DC_LIBREF..mpe_config(where=('; put 'var_scope="DC"'; put 'and &dc_dttmtfmt lt tx_to'; put 'and var_active=1'; put '));'; put 'call symputx(var_name,var_value,''G'');'; put 'putlog var_name "=" var_value;'; put 'run;'; put '%let mpelib=&dc_libref;'; put '%let mpeadmins=&dc_admin_group;'; put '%let mpelocapprovals=&dc_staging_area;'; put '%let dc_repo_users=&dc_repo_users;'; put '%if &dc_locale ne SYSTEM %then %do;'; put 'options locale=&dc_locale;'; put '%end;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(Problem during compilation or with STP precode (&syswarningtext))'; put ')'; put '%mend mpeinit;'; put '%macro mf_mval(var);'; put '%if %symexist(&var) %then %do;'; put '%superq(&var)'; put '%end;'; put '%mend mf_mval;'; put '%macro mf_trimstr(basestr,trimstr);'; put '%local baselen trimlen trimval;'; put '/* return if basestr is shorter than trimstr (or 0) */'; put '%let baselen=%length(%superq(basestr));'; put '%let trimlen=%length(%superq(trimstr));'; put '%if &baselen < &trimlen or &baselen=0 %then %return;'; put '/* obtain the characters from the end of basestr */'; put '%let trimval=%qsubstr(%superq(basestr)'; put ',%length(%superq(basestr))-&trimlen+1'; put ',&trimlen);'; put '/* compare and if matching, chop it off! */'; put '%if %superq(basestr)=%superq(trimstr) %then %do;'; put '%return;'; put '%end;'; put '%else %if %superq(trimval)=%superq(trimstr) %then %do;'; put '%qsubstr(%superq(basestr),1,%length(%superq(basestr))-&trimlen)'; put '%end;'; put '%else %do;'; put '&basestr'; put '%end;'; put '%mend mf_trimstr;'; put '%macro mf_getplatform(switch'; put ')/*/STORE SOURCE*/;'; put '%local a b c;'; put '%if &switch.NONE=NONE %then %do;'; put '%if %symexist(sasjsprocessmode) %then %do;'; put '%if &sasjsprocessmode=Stored Program %then %do;'; put 'SASJS'; put '%return;'; put '%end;'; put '%end;'; put '%if %symexist(sysprocessmode) %then %do;'; put '%if "&sysprocessmode"="SAS Object Server"'; put 'or "&sysprocessmode"= "SAS Compute Server" %then %do;'; put 'SASVIYA'; put '%end;'; put '%else %if "&sysprocessmode"="SAS Stored Process Server"'; put 'or "&sysprocessmode"="SAS Workspace Server"'; put '%then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if %symexist(_metaport) or %symexist(_metauser) %then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if &switch=SASSTUDIO %then %do;'; put '/* return the version of SAS Studio else 0 */'; put '%if %mf_mval(_CLIENTAPP)=%str(SAS Studio) %then %do;'; put '%let a=%mf_mval(_CLIENTVERSION);'; put '%let b=%scan(&a,1,.);'; put '%if %eval(&b >2) %then %do;'; put '&b'; put '%end;'; put '%else 0;'; put '%end;'; put '%else 0;'; put '%end;'; put '%else %if &switch=VIYARESTAPI %then %do;'; put '%mf_trimstr(%sysfunc(getoption(servicesbaseurl)),/)'; put '%end;'; put '%mend mf_getplatform;'; put '%macro mpeterm();'; put '%local oldloc;'; put 'data _null_;'; put 'if symexist(''SYSPRINTTOLOG'') then oldloc=symget(''SYSPRINTTOLOG'');'; put 'else oldloc=getoption(''LOG'');'; put 'if subpad(oldloc,1,1) not in (''"'',"''",'' '') then oldloc=quote(cats(oldloc));'; put 'call symputx(''oldloc'',oldloc,''l'');'; put 'run;'; put '%if %length(&oldloc)>0 %then %do;'; put 'proc printto log=log;'; put 'run;'; put 'data _null_;'; put 'infile &oldloc;'; put 'input; putlog _infile_;'; put 'run;'; put '%end;'; put '%if %sysfunc(exist(&dc_libref..mpe_requests)) and %mf_getplatform() ne SASVIYA'; put '%then %do;'; put 'data ;'; put 'if 0 then set &dc_libref..mpe_requests;'; put 'request_dttm=%sysfunc(datetime());'; put 'request_user="%mf_getuser()";'; put 'request_service="%scan(&_program,-2,/)/%scan(&_program,-1,/)";'; put 'request_params='''';'; put 'output;stop;'; put 'proc append base=&dc_libref..mpe_requests data=&syslast force nowarn;'; put 'run;'; put '%end;'; put '%mend mpeterm;'; put '%macro mm_getlibs('; put 'outds=work.mm_getLibs'; put ')/*/STORE SOURCE*/;'; put '/*'; put 'flags:'; put 'OMI_SUCCINCT (2048) Do not return attributes with null values.'; put 'OMI_GET_METADATA (256) Executes a GetMetadata call for each object that'; put 'is returned by the GetMetadataObjects method.'; put 'OMI_ALL_SIMPLE (8) Gets all of the attributes of the requested object.'; put '*/'; put 'data _null_;'; put 'flags=2048+256+8;'; put 'call symputx(''flags'',flags,''l'');'; put 'run;'; put '* use a temporary fileref to hold the response;'; put 'filename response temp;'; put '/* get list of libraries */'; put 'proc metadata in='; put ''''; put '$METAREPOSITORY'; put 'SASLibrary'; put ''; put 'SAS'; put '&flags'; put ''; put ''''; put 'out=response;'; put 'run;'; put '/* write the response to the log for debugging */'; put 'data _null_;'; put 'infile response lrecl=32767;'; put 'input;'; put 'put _infile_;'; put 'run;'; put '/* create an XML map to read the response */'; put 'filename sxlemap temp;'; put 'data _null_;'; put 'file sxlemap;'; put 'put '''';'; put 'put '''';'; put 'put ''//Objects/SASLibrary'';'; put 'put ''>17'';'; put 'put ''//Objects/SASLibrary/@Id'';'; put 'put ''256>'';'; put 'put ''//Objects/SASLibrary/@Name'';'; put 'put ''8'';'; put 'put ''//Objects/SASLibrary/@Libref'';'; put 'put ''>12'';'; put 'put ''//Objects/SASLibrary/@Engine'';'; put 'put ''
'';'; put 'run;'; put 'libname _XML_ xml xmlfileref=response xmlmap=sxlemap;'; put '/* sort the response by library name */'; put 'proc sort data=_XML_.saslibrary out=&outds;'; put 'by libraryname;'; put 'run;'; put '/* clear references */'; put 'filename sxlemap clear;'; put 'filename response clear;'; put 'libname _XML_ clear;'; put '%mend mm_getlibs;'; put '%macro mm_getrepos('; put 'outds=work.mm_getrepos'; put ')/*/STORE SOURCE*/;'; put '* use a temporary fileref to hold the response;'; put 'filename response temp;'; put '/* get list of libraries */'; put 'proc metadata in='; put '"1"'; put 'out=response;'; put 'run;'; put '/* write the response to the log for debugging */'; put '/*'; put 'data _null_;'; put 'infile response lrecl=1048576;'; put 'input;'; put 'put _infile_;'; put 'run;'; put '*/'; put '/* create an XML map to read the response */'; put 'filename sxlemap temp;'; put 'data _null_;'; put 'file sxlemap;'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository";'; put 'put "";'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Id";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Name";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Desc";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@DefaultNS";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@RepositoryType";'; put 'put "characterstring20";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@RepositoryFormat";'; put 'put "characterstring10";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@Access";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@CurrentAccess";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@PauseState";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Path";'; put 'put "";'; put 'put "characterstring256";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Engine";'; put 'put "";'; put 'put "characterstring8";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Options";'; put 'put "";'; put 'put "characterstring32";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@MetadataCreated";'; put 'put "characterstring24";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@MetadataUpdated";'; put 'put "characterstring24";'; put 'put '''';'; put 'put ''
'';'; put 'run;'; put 'libname _XML_ xml xmlfileref=response xmlmap=sxlemap;'; put 'proc sort data= _XML_.SASRepos out=&outds;'; put 'by name;'; put 'run;'; put '/* clear references */'; put 'filename sxlemap clear;'; put 'filename response clear;'; put 'libname _XML_ clear;'; put '%mend mm_getrepos;'; put '%macro dc_getlibs(outds=mm_getlibs);'; put '/* take current repository */'; put '%local repo repocnt x;'; put '%let repo=%sysfunc(getoption(metarepository));'; put '/* get list of repositories and filter */'; put '%mm_getrepos(outds=repos)'; put '%let repocnt=0;'; put 'data repos;'; put 'set repos;'; put 'where repositorytype in(''CUSTOM'',''FOUNDATION'')'; put '& upcase(name) ne "%upcase(&repo)";'; put 'keep id name ;'; put 'call symputx(''repo''!!cats(_n_),name,''l'');'; put 'call symputx(''repocnt'',_n_,''l'');'; put 'run;'; put '%put _local_;'; put '%mm_getlibs(outds=&outds)'; put '%do x=1 %to &repocnt;'; put 'options metarepository=&&repo&x;'; put '%mm_getlibs(outds=&outds.a)'; put 'proc append base=&outds data=&outds.a;'; put 'run;'; put '%end;'; put 'options metarepository=&repo;'; put '%mend dc_getlibs;'; put '* SAS Macros end;'; put '* SAS Includes start;'; put '* SAS Includes end;'; put '* Binary Files start;'; put '* Binary Files end;'; put '* ServiceInit start;'; put 'options noquotelenmax ps=max;'; put '/* create dummy macros to prevent stpbegin / stpend from corrupting sessions */'; put '%macro stpbegin();'; put '%put NOTE: the STPBEGIN macro should not be used for web apps!;'; put '%mend stpbegin;'; put '%macro stpend();'; put '%put NOTE: the STPEND macro should not be used for web apps!;'; put '%mend stpend;'; put '* ServiceInit end;'; put '* Service start;'; put '/**'; put '@file'; put '@brief Generic validator for libraries'; put '@details The input table is simply one row from the target table in table'; put 'called "work.source_row".'; put 'Available macro variables:'; put '@li DC_LIBREF - The DC control library'; put '@li LIBDS - The library.dataset being filtered'; put '@li VARIABLE_NM - The column being filtered'; put '

Service Outputs

'; put 'Output should be a single table called "work.dynamic_values" in the format'; put 'below. display_value should always be character, raw_value is unformatted'; put 'character/numeric.'; put '|DISPLAY_VALUE:$|RAW_VALUE:??|'; put '|---|---|'; put '|$44.00|44|'; put '

SAS Macros

'; put '@li dc_getlibs.sas'; put '**/'; put 'proc sql;'; put 'create table work.DYNAMIC_VALUES as'; put 'select distinct cats(some_num) as display_value,'; put 'some_num as raw_value'; put 'from &libds'; put 'order by 1;'; put '* Service end;'; run; %mm_createwebservice(path=&appLoc/&path, name=&service, code=sascode, server=&serverName, replace=yes) filename sascode clear; %let service=sas_groups; filename sascode temp lrecl=32767; data _null_; file sascode; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '/**'; put '@file mp_jsonout.sas'; put '@brief Writes JSON in SASjs format to a fileref'; put '@details This macro can be used to OPEN a JSON stream and send one or more'; put 'tables as arrays of rows, where each row can be an object or a nested array.'; put 'There are two engines available - DATASTEP or PROCJSON.'; put 'PROC JSON is fast but will produce errs like the ones below if'; put 'special chars are encountered.'; put '> (ERR)OR: Some code points did not transcode.'; put '> An object or array close is not valid at this point in the JSON text.'; put '> Date value out of range'; put 'If this happens, try running with ENGINE=DATASTEP.'; put 'The DATASTEP engine is used to handle special SAS missing numerics, and'; put 'can also convert entire datasets to formatted values. Output JSON is always'; put 'in UTF-8.'; put 'Usage:'; put 'filename tmp temp;'; put 'data class; set sashelp.class;run;'; put '%mp_jsonout(OPEN,jref=tmp)'; put '%mp_jsonout(OBJ,class,jref=tmp)'; put '%mp_jsonout(OBJ,class,dslabel=class2,jref=tmp,showmeta=Y)'; put '%mp_jsonout(CLOSE,jref=tmp)'; put 'data _null_;'; put 'infile tmp;'; put 'input;putlog _infile_;'; put 'run;'; put 'If you are building web apps with SAS then you are strongly encouraged to use'; put 'the mX_createwebservice macros in combination with the'; put '[sasjs adapter](https://github.com/sasjs/adapter).'; put 'For more information see https://sasjs.io'; put '@param [in] action Valid values:'; put '@li OPEN - opens the JSON'; put '@li OBJ - sends a table with each row as an object'; put '@li ARR - sends a table with each row in an array'; put '@li CLOSE - closes the JSON'; put '@param [in] ds The dataset to send. Must be a work table.'; put '@param [out] jref= (_webout) The fileref to which to send the JSON'; put '@param [out] dslabel= The name to give the table in the exported JSON'; put '@param [in] fmt= (Y) Whether to keep (Y) or strip (N) formats from the table'; put '@param [in] engine= (DATASTEP) Which engine to use to send the JSON. Options:'; put '@li PROCJSON (default)'; put '@li DATASTEP (more reliable when data has non standard characters)'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to JSON'; put '

Related Files

'; put '@li mp_ds2fmtds.sas'; put '@version 9.2'; put '@author Allan Bowe'; put '@source https://github.com/sasjs/core'; put '**/'; put '%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y'; put ',engine=DATASTEP'; put ',missing=NULL'; put ',showmeta=N'; put ',maxobs=MAX'; put ')/*/STORE SOURCE*/;'; put '%local tempds colinfo fmtds i numcols numobs stmt_obs lastobs optval'; put 'tmpds1 tmpds2 tmpds3 tmpds4;'; put '%let numcols=0;'; put '%if &maxobs ne MAX %then %let stmt_obs=%str(if _n_>&maxobs then stop;);'; put '%if &action=OPEN %then %do;'; put 'options nobomfile;'; put 'data _null_;file &jref encoding=''utf-8'' lrecl=200;'; put 'put ''{"PROCESSED_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''"'';'; put 'run;'; put '%end;'; put '%else %if (&action=ARR or &action=OBJ) %then %do;'; put '/* force variable names to always be uppercase in the JSON */'; put 'options validvarname=upcase;'; put '/* To avoid issues with _webout on EBI - such as encoding diffs and truncation'; put '(https://support.sas.com/kb/49/325.html) we use temporary files */'; put 'filename _sjs1 temp lrecl=200 ;'; put 'data _null_; file _sjs1 encoding=''utf-8'';'; put 'put ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":";'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjs1 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs1 clear;'; put '/* grab col defs */'; put 'proc contents noprint data=&ds'; put 'out=_data_(keep=name type length format formatl formatd varnum label);'; put 'run;'; put '%let colinfo=%scan(&syslast,2,.);'; put 'proc sort data=&colinfo;'; put 'by varnum;'; put 'run;'; put '/* move meta to mac vars */'; put 'data &colinfo;'; put 'if _n_=1 then call symputx(''numcols'',nobs,''l'');'; put 'set &colinfo end=last nobs=nobs;'; put 'name=upcase(name);'; put '/* fix formats */'; put 'if type=2 or type=6 then do;'; put 'typelong=''char'';'; put 'length fmt $49.;'; put 'if format='''' then fmt=cats(''$'',length,''.'');'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else fmt=cats(format,formatl,''.'');'; put 'end;'; put 'else do;'; put 'typelong=''num'';'; put 'if format='''' then fmt=''best.'';'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else if formatd=0 then fmt=cats(format,formatl,''.'');'; put 'else fmt=cats(format,formatl,''.'',formatd);'; put 'end;'; put '/* 32 char unique name */'; put 'newname=''sasjs''!!substr(cats(put(md5(name),$hex32.)),1,27);'; put 'call symputx(cats(''name'',_n_),name,''l'');'; put 'call symputx(cats(''newname'',_n_),newname,''l'');'; put 'call symputx(cats(''length'',_n_),length,''l'');'; put 'call symputx(cats(''fmt'',_n_),fmt,''l'');'; put 'call symputx(cats(''type'',_n_),type,''l'');'; put 'call symputx(cats(''typelong'',_n_),typelong,''l'');'; put 'call symputx(cats(''label'',_n_),coalescec(label,name),''l'');'; put '/* overwritten when fmt=Y and a custom format exists in catalog */'; put 'if typelong=''num'' then call symputx(cats(''fmtlen'',_n_),200,''l'');'; put 'else call symputx(cats(''fmtlen'',_n_),min(32767,ceil((length+10)*1.5)),''l'');'; put 'run;'; put '%let tempds=%substr(_%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql;'; put 'select count(*) into: lastobs from &ds;'; put '%if &maxobs ne MAX %then %let lastobs=%sysfunc(min(&lastobs,&maxobs));'; put '%if &engine=PROCJSON %then %do;'; put '%if &missing=STRING %then %do;'; put '%put &sysmacroname: Special Missings not supported in proc json.;'; put '%put &sysmacroname: Switching to DATASTEP engine;'; put '%goto datastep;'; put '%end;'; put 'data &tempds;'; put 'set &ds;'; put '&stmt_obs;'; put '%if &fmt=N %then format _numeric_ best32.;;'; put '/* PRETTY is necessary to avoid line truncation in large files */'; put 'filename _sjs2 temp lrecl=131068 encoding=''utf-8'';'; put 'proc json out=_sjs2 pretty'; put '%if &action=ARR %then nokeys ;'; put ';export &tempds / nosastags fmtnumeric;'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs2 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs2 clear;'; put '%end;'; put '%else %if &engine=DATASTEP %then %do;'; put '%datastep:'; put '%if %sysfunc(exist(&ds)) ne 1 & %sysfunc(exist(&ds,VIEW)) ne 1'; put '%then %do;'; put '%put &sysmacroname: &ds NOT FOUND!!!;'; put '%return;'; put '%end;'; put '%if &fmt=Y %then %do;'; put '/**'; put '* Extract format definitions'; put '* First, by getting library locations from dictionary.formats'; put '* Then, by exporting the width using proc format'; put '* Cannot use maxw from sashelp.vformat as not always populated'; put '* Cannot use fmtinfo() as not supported in all flavours'; put '*/'; put '%let tmpds1=%substr(fmtsum%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds2=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds3=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds4=%substr(col%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql noprint;'; put 'create table &tmpds1 as'; put 'select cats(libname,''.'',memname) as FMTCAT,'; put 'FMTNAME'; put 'from dictionary.formats'; put 'where fmttype=''F'' and libname is not null'; put 'and fmtname in (select format from &colinfo where format is not null)'; put 'order by 1;'; put 'create table &tmpds2('; put 'FMTNAME char(32),'; put 'LENGTH num'; put ');'; put '%local catlist cat fmtlist i;'; put 'select distinct fmtcat into: catlist separated by '' '' from &tmpds1;'; put '%do i=1 %to %sysfunc(countw(&catlist,%str( )));'; put '%let cat=%scan(&catlist,&i,%str( ));'; put 'proc sql;'; put 'select distinct fmtname into: fmtlist separated by '' '''; put 'from &tmpds1 where fmtcat="&cat";'; put 'proc format lib=&cat cntlout=&tmpds3(keep=fmtname length);'; put 'select &fmtlist;'; put 'run;'; put 'proc sql;'; put 'insert into &tmpds2 select distinct fmtname,length from &tmpds3;'; put '%end;'; put 'proc sql;'; put 'create table &tmpds4 as'; put 'select a.*, b.length as MAXW'; put 'from &colinfo a'; put 'left join &tmpds2 b'; put 'on cats(a.format)=cats(upcase(b.fmtname))'; put 'order by a.varnum;'; put 'data _null_;'; put 'set &tmpds4;'; put 'if not missing(maxw);'; put 'call symputx('; put 'cats(''fmtlen'',_n_),'; put '/* vars need extra padding due to JSON escaping of special chars */'; put 'min(32767,ceil((max(length,maxw)+10)*1.5))'; put ',''l'''; put ');'; put 'run;'; put '/* configure varlenchk - as we are explicitly shortening the variables */'; put '%let optval=%sysfunc(getoption(varlenchk));'; put 'options varlenchk=NOWARN;'; put 'data _data_(compress=char);'; put '/* shorten the new vars */'; put 'length'; put '%do i=1 %to &numcols;'; put '&&name&i $&&fmtlen&i'; put '%end;'; put ';'; put '/* rename on entry */'; put 'set &ds(rename=('; put '%do i=1 %to &numcols;'; put '&&name&i=&&newname&i'; put '%end;'; put '));'; put '&stmt_obs;'; put 'drop'; put '%do i=1 %to &numcols;'; put '&&newname&i'; put '%end;'; put ';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=num %then %do;'; put '&&name&i=cats(put(&&newname&i,&&fmt&i));'; put '%end;'; put '%else %do;'; put '&&name&i=put(&&newname&i,&&fmt&i);'; put '%end;'; put '%end;'; put 'if _error_ then do;'; put 'call symputx(''syscc'',1012);'; put 'stop;'; put 'end;'; put 'run;'; put '%let fmtds=&syslast;'; put 'options varlenchk=&optval;'; put '%end;'; put 'proc format; /* credit yabwon for special null removal */'; put 'value bart (default=40)'; put '%if &missing=NULL %then %do;'; put '._ - .z = null'; put '%end;'; put '%else %do;'; put '._ = [quote()]'; put '. = null'; put '.a - .z = [quote()]'; put '%end;'; put 'other = [best.];'; put 'data &tempds;'; put 'attrib _all_ label='''';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'length &&name&i $&&fmtlen&i...;'; put 'format &&name&i $&&fmtlen&i...;'; put '%end;'; put '%end;'; put '%if &fmt=Y %then %do;'; put 'set &fmtds;'; put '%end;'; put '%else %do;'; put 'set &ds;'; put '%end;'; put '&stmt_obs;'; put 'format _numeric_ bart.;'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'if findc(&&name&i,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put '&&name&i=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,&&name&i)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else &&name&i=quote(cats(&&name&i));'; put '%end;'; put '%end;'; put 'run;'; put 'filename _sjs3 temp lrecl=131068 ;'; put 'data _null_;'; put 'file _sjs3 encoding=''utf-8'';'; put 'if _n_=1 then put "[";'; put 'set &tempds;'; put 'if _n_>1 then put "," @; put'; put '%if &action=ARR %then "[" ; %else "{" ;'; put '%do i=1 %to &numcols;'; put '%if &i>1 %then "," ;'; put '%if &action=OBJ %then """&&name&i"":" ;'; put '"&&name&i"n /* name literal for reserved variable names */'; put '%end;'; put '%if &action=ARR %then "]" ; %else "}" ; ;'; put '/* close out the table */'; put 'data _null_;'; put 'file _sjs3 mod encoding=''utf-8'';'; put 'put '']'';'; put 'run;'; put 'data _null_;'; put 'infile _sjs3 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs3 clear;'; put '%end;'; put 'proc sql;'; put 'drop table &colinfo, &tempds;'; put '%if %substr(&showmeta,1,1)=Y %then %do;'; put 'filename _sjs4 temp lrecl=131068 encoding=''utf-8'';'; put 'data _null_;'; put 'file _sjs4;'; put 'length label $350;'; put 'put ", ""$%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":{""vars"":{";'; put 'do i=1 to &numcols;'; put 'name=quote(trim(symget(cats(''name'',i))));'; put 'format=quote(trim(symget(cats(''fmt'',i))));'; put 'label=quote(prxchange(''s/\\/\\\\/'',-1,trim(symget(cats(''label'',i)))));'; put 'length=quote(trim(symget(cats(''length'',i))));'; put 'type=quote(trim(symget(cats(''typelong'',i))));'; put 'if i>1 then put "," @@;'; put 'put name '':{"format":'' format '',"label":'' label'; put ''',"length":'' length '',"type":'' type ''}'';'; put 'end;'; put 'put ''}}'';'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs4 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs4 clear;'; put '%end;'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put 'data _null_; file &jref encoding=''utf-8'' mod ;'; put 'put "}";'; put 'run;'; put '%end;'; put '%mend mp_jsonout;'; put '/**'; put '@file mm_webout.sas'; put '@brief Send data to/from SAS Stored Processes'; put '@details This macro should be added to the start of each Stored Process,'; put '**immediately** followed by a call to:'; put '%mm_webout(FETCH)'; put 'This will read all the input data and create same-named SAS datasets in the'; put 'WORK library. You can then insert your code, and send data back using the'; put 'following syntax:'; put 'data some datasets; * make some data ;'; put 'retain some columns;'; put 'run;'; put '%mm_webout(OPEN)'; put '%mm_webout(ARR,some) * Array format, fast, suitable for large tables ;'; put '%mm_webout(OBJ,datasets) * Object format, easier to work with ;'; put 'Finally, wrap everything up send some helpful system variables too'; put '%mm_webout(CLOSE)'; put '@param [in] action Either FETCH, OPEN, ARR, OBJ or CLOSE'; put '@param [in] ds The dataset to send back to the frontend'; put '@param [out] dslabel= Value to use instead of table name for sending to JSON'; put '@param [in] fmt= (N) Setting Y converts all vars to their formatted values'; put '@param [out] fref= (_webout) The fileref to which to write the JSON'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] workobs= (0) When set to a positive integer, will create a new'; put 'output object (WORK) which contains this number of observations from all'; put 'tables in the WORK library.'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to output JSON'; put '

SAS Macros

'; put '@li mp_jsonout.sas'; put '

Related Macros

'; put '@li ms_webout.sas'; put '@li mv_webout.sas'; put '@version 9.3'; put '@author Allan Bowe'; put '**/'; put '%macro mm_webout(action,ds,dslabel=,fref=_webout,fmt=N,missing=NULL'; put ',showmeta=N,maxobs=MAX,workobs=0'; put ');'; put '%global _webin_file_count _webin_fileref1 _webin_name1 _program _debug'; put 'sasjs_tables;'; put '%local i tempds jsonengine;'; put '/* see https://github.com/sasjs/core/issues/41 */'; put '%if "%upcase(&SYSENCODING)" ne "UTF-8" %then %let jsonengine=PROCJSON;'; put '%else %let jsonengine=DATASTEP;'; put '%if &action=FETCH %then %do;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'options mprint notes mprintnest;'; put '%end;'; put '%let _webin_file_count=%eval(&_webin_file_count+0);'; put '/* now read in the data */'; put '%do i=1 %to &_webin_file_count;'; put '%if &_webin_file_count=1 %then %do;'; put '%let _webin_fileref1=&_webin_fileref;'; put '%let _webin_name1=&_webin_name;'; put '%end;'; put 'data _null_;'; put 'infile &&_webin_fileref&i termstr=crlf;'; put 'input;'; put 'call symputx(''input_statement'',_infile_);'; put 'putlog "&&_webin_name&i input statement: " _infile_;'; put 'stop;'; put 'data &&_webin_name&i;'; put 'infile &&_webin_fileref&i firstobs=2 dsd termstr=crlf encoding=''utf-8'';'; put 'input &input_statement;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'if _n_<20 then putlog _infile_;'; put '%end;'; put 'run;'; put '%let sasjs_tables=&sasjs_tables &&_webin_name&i;'; put '%end;'; put '%end;'; put '%else %if &action=OPEN %then %do;'; put '/* fix encoding */'; put 'OPTIONS NOBOMFILE;'; put '/**'; put '* check xengine type to avoid the below err message:'; put '* > Function is only valid for filerefs using the CACHE access method.'; put '*/'; put 'data _null_;'; put 'set sashelp.vextfl(where=(fileref="_WEBOUT"));'; put 'if xengine=''STREAM'' then do;'; put 'rc=stpsrv_header(''Content-type'',"text/html; encoding=utf-8");'; put 'end;'; put 'run;'; put '/* setup json */'; put 'data _null_;file &fref encoding=''utf-8'';'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutBEGIN<<'';'; put '%end;'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'run;'; put '%end;'; put '%else %if &action=ARR or &action=OBJ %then %do;'; put '%mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt,jref=&fref'; put ',engine=&jsonengine,missing=&missing,showmeta=&showmeta,maxobs=&maxobs'; put ')'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put '/* To avoid issues with _webout on EBI we use a temporary file */'; put 'filename _sjsref temp lrecl=131068;'; put '%if %str(&workobs) > 0 %then %do;'; put '/* if debug mode, send back first XX records of each work table also */'; put 'data;run;%let tempds=%scan(&syslast,2,.);'; put 'ods output Members=&tempds;'; put 'proc datasets library=WORK memtype=data;'; put '%local wtcnt;%let wtcnt=0;'; put 'data _null_;'; put 'set &tempds;'; put 'if not (upcase(name) =:"DATA"); /* ignore temp datasets */'; put 'i+1;'; put 'call symputx(cats(''wt'',i),name,''l'');'; put 'call symputx(''wtcnt'',i,''l'');'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put ",""WORK"":{";'; put '%do i=1 %to &wtcnt;'; put '%let wt=&&wt&i;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'dsid=open("WORK.&wt",''is'');'; put 'nlobs=attrn(dsid,''NLOBS'');'; put 'nvars=attrn(dsid,''NVARS'');'; put 'rc=close(dsid);'; put 'if &i>1 then put '',''@;'; put 'put " ""&wt"" : {";'; put 'put ''"nlobs":'' nlobs;'; put 'put '',"nvars":'' nvars;'; put '%mp_jsonout(OBJ,&wt,jref=_sjsref,dslabel=first10rows,showmeta=Y'; put ',maxobs=&workobs'; put ')'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put '%end;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put 'run;'; put '%end;'; put '/* close off json */'; put 'data _null_;file _sjsref mod encoding=''utf-8'';'; put 'length SYSPROCESSNAME syserrortext syswarningtext autoexec $512;'; put 'put ",""_DEBUG"" : ""&_debug"" ";'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'autoexec=quote(urlencode(trim(getoption(''autoexec''))));'; put 'put '',"AUTOEXEC" : '' autoexec;'; put 'put ",""MF_GETUSER"" : ""%mf_getuser()"" ";'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'put ",""SYSENCODING"" : ""&sysencoding"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSPROCESSID"" : ""&SYSPROCESSID"" ";'; put 'put ",""SYSPROCESSMODE"" : ""&SYSPROCESSMODE"" ";'; put 'SYSPROCESSNAME=quote(urlencode(cats(SYSPROCESSNAME)));'; put 'put ",""SYSPROCESSNAME"" : " SYSPROCESSNAME;'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put '',"SYSWARNINGTEXT" : '' syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'length memsize $32;'; put 'memsize="%sysfunc(INPUTN(%sysfunc(getoption(memsize)), best.),sizekmg.)";'; put 'memsize=quote(cats(memsize));'; put 'put '',"MEMSIZE" : '' memsize;'; put 'put "}" @;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutEND<<'';'; put '%end;'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjsref lrecl=1 recfm=n;'; put 'file &fref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjsref clear;'; put '%end;'; put '%mend mm_webout;'; put '%macro webout(action,ds,dslabel=,fmt=,missing=NULL,showmeta=NO,maxobs=MAX);'; put '%mm_webout(&action,ds=&ds,dslabel=&dslabel,fmt=&fmt'; put ',missing=&missing'; put ',showmeta=&showmeta'; put ',maxobs=&maxobs'; put ') %mend;'; put '/* provide additional debug info */'; put '%global _program;'; put '%put &=syscc;'; put '%put user=%mf_getuser();'; put '%put pgm=&_program;'; put '%put timestamp=%sysfunc(datetime(),datetime19.);'; put '* Service Variables start;'; put '* Service Variables end;'; put '* SAS Macros start;'; put '%macro mf_getapploc(pgm);'; put '%if "&pgm"="" %then %do;'; put '%if %symexist(_program) %then %let pgm=&_program;'; put '%else %do;'; put '%put &sysmacroname: No value provided and no _program variable available;'; put '%return;'; put '%end;'; put '%end;'; put '%local root;'; put '/**'; put '* First check we are not in the tests/macros folder (which has no subfolders)'; put '* or specifically in the testsetup or testteardown services'; put '*/'; put '%if %index(&pgm,/tests/macros/)'; put 'or %index(&pgm,/tests/testsetup)'; put 'or %index(&pgm,/tests/testteardown)'; put '%then %do;'; put '%let root=%substr(&pgm,1,%index(&pgm,/tests)-1);'; put '&root'; put '%return;'; put '%end;'; put '/**'; put '* Next, move up two levels to avoid matches on subfolder or service name'; put '*/'; put '%let root=%substr(&pgm,1,%length(&pgm)-%length(%scan(&pgm,-1,/))-1);'; put '%let root=%substr(&root,1,%length(&root)-%length(%scan(&root,-1,/))-1);'; put '%if %index(&root,/tests/) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/tests/)-1);'; put '%end;'; put '%else %if %index(&root,/services) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/services)-1);'; put '%end;'; put '%else %if %index(&root,/jobs) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/jobs)-1);'; put '%end;'; put '%else %put &sysmacroname: Could not find an app location from &pgm;'; put '&root'; put '%mend mf_getapploc ;'; put '%macro mf_getuniquefileref(prefix=_,maxtries=1000,lrecl=32767);'; put '%local rc fname;'; put '%if &prefix=0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%end;'; put '%else %do;'; put '%local x len;'; put '%let len=%eval(8-%length(&prefix));'; put '%let x=0;'; put '%do x=0 %to &maxtries;'; put '%let fname=&prefix%substr(%sysfunc(ranuni(0)),3,&len);'; put '%if %sysfunc(fileref(&fname)) > 0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%return;'; put '%end;'; put '%end;'; put '%put unable to find available fileref after &maxtries attempts;'; put '%end;'; put '%mend mf_getuniquefileref;'; put '%macro mp_abort(mac=mp_abort.sas, type=, msg=, iftrue=%str(1=1)'; put ', errds=work.mp_abort_errds'; put ', mode=REGULAR'; put ')/*/STORE SOURCE*/;'; put '%global sysprocessmode sysprocessname sasjs_stpsrv_header_loc sasjsprocessmode;'; put '%local fref fid i;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%put NOTE: /// mp_abort macro executing //;'; put '%if %length(&mac)>0 %then %put NOTE- called by &mac;'; put '%put NOTE - &msg;'; put '%if %symexist(_SYSINCLUDEFILEDEVICE)'; put '/* abort cancel FILE does not restart outside the INCLUDE on Viya 3.5 */'; put 'and %superq(SYSPROCESSNAME) ne %str(Compute Server)'; put '%then %do;'; put '%if "*&_SYSINCLUDEFILEDEVICE*" ne "**" %then %do;'; put 'data &errds;'; put 'iftrue=''1=1'';'; put 'length mac $100 msg $5000;'; put 'mac=symget(''mac'');'; put 'msg=symget(''msg'');'; put 'run;'; put 'data _null_;'; put 'abort cancel FILE;'; put 'run;'; put '%return;'; put '%end;'; put '%end;'; put '/* Web App Context */'; put '%if %symexist(_PROGRAM)'; put 'or %superq(SYSPROCESSNAME) = %str(Compute Server)'; put 'or &mode=INCLUDE'; put '%then %do;'; put 'options obs=max replace mprint;'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5"'; put '%then %do;'; put 'options nosyntaxcheck;'; put '%end;'; put '%if &mode=INCLUDE %then %do;'; put '%if %sysfunc(exist(&errds))=1 %then %do;'; put 'data _null_;'; put 'set &errds;'; put 'call symputx(''iftrue'',iftrue,''l'');'; put 'call symputx(''mac'',mac,''l'');'; put 'call symputx(''msg'',msg,''l'');'; put 'putlog (_all_)(=);'; put 'run;'; put '%if (&iftrue)=0 %then %return;'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: No include errors found;'; put '%return;'; put '%end;'; put '%end;'; put '/* extract log errs / warns, if exist */'; put '%local logloc logline;'; put '%global logmsg; /* capture global messages */'; put '%if %symexist(SYSPRINTTOLOG) %then %let logloc=&SYSPRINTTOLOG;'; put '%else %let logloc=%qsysfunc(getoption(LOG));'; put 'proc printto log=log;run;'; put '%let logline=0;'; put '%if %length(&logloc)>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input; putlog _infile_;'; put 'i=1;'; put 'retain logonce 0;'; put 'if ('; put '_infile_=:"%str(WARN)ING" or _infile_=:"%str(ERR)OR"'; put ') and logonce=0 then'; put 'do;'; put 'call symputx(''logline'',_n_);'; put 'logonce+1;'; put 'end;'; put 'run;'; put '/* capture log including lines BEFORE the err */'; put '%if &logline>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input;'; put 'i=1;'; put 'stoploop=0;'; put 'if _n_ ge &logline-15 and stoploop=0 then do until (i>22);'; put 'call symputx(''logmsg'',catx(''\n'',symget(''logmsg''),_infile_));'; put 'input;'; put 'i+1;'; put 'stoploop=1;'; put 'end;'; put 'if stoploop=1 then stop;'; put 'run;'; put '%end;'; put '%end;'; put '%if %symexist(SYS_JES_JOB_URI) %then %do;'; put '/* setup webout for Viya */'; put 'options nobomfile;'; put '%if "X&SYS_JES_JOB_URI.X"="XX" %then %do;'; put 'filename _webout temp lrecl=999999 mod;'; put '%end;'; put '%else %do;'; put 'filename _webout filesrvc parenturi="&SYS_JES_JOB_URI"'; put 'name="_webout.json" lrecl=999999 mod;'; put '%end;'; put '%end;'; put '%else %if %sysfunc(filename(fref,&sasjs_stpsrv_header_loc))=0 %then %do;'; put 'options nobomfile;'; put '/* set up http header for SASjs Server */'; put '%let fid=%sysfunc(fopen(&fref,A));'; put '%if &fid=0 %then %do;'; put '%put %str(ERR)OR: %sysfunc(sysmsg());'; put '%return;'; put '%end;'; put '%let rc=%sysfunc(fput(&fid,%str(Content-Type: application/json)));'; put '%let rc=%sysfunc(fwrite(&fid));'; put '%let rc=%sysfunc(fclose(&fid));'; put '%let rc=%sysfunc(filename(&fref));'; put '%end;'; put '/* send response in SASjs JSON format */'; put 'data _null_;'; put 'file _webout mod lrecl=32000 encoding=''utf-8'';'; put 'length msg syswarningtext syserrortext $32767 mode $10 ;'; put 'sasdatetime=datetime();'; put 'msg=symget(''msg'');'; put '%if &logline>0 %then %do;'; put 'msg=cats(msg,''\n\nLog Extract:\n'',symget(''logmsg''));'; put '%end;'; put '/* escape the escapes */'; put 'msg=tranwrd(msg,''\'',''\\'');'; put '/* escape the quotes */'; put 'msg=tranwrd(msg,''"'',''\"'');'; put '/* ditch the CRLFs as chrome complains */'; put 'msg=compress(msg,,''kw'');'; put '/* quote without quoting the quotes (which are escaped instead) */'; put 'msg=cats(''"'',msg,''"'');'; put 'if symexist(''_debug'') then debug=quote(trim(symget(''_debug'')));'; put 'else debug=''""'';'; put 'if symget(''sasjsprocessmode'')=''Stored Program'' then mode=''SASJS'';'; put 'if mode ne ''SASJS'' then put ''>>weboutBEGIN<<'';'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'put '',"sasjsAbort" : [{'';'; put 'put '' "MSG":'' msg ;'; put 'put '' ,"MAC": "'' "&mac" ''"}]'';'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'put '',"_DEBUG":'' debug ;'; put 'if symexist(''_metauser'') then do;'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put 'end;'; put 'if symexist(''SYS_JES_JOB_URI'') then do;'; put 'SYS_JES_JOB_URI=quote(trim(symget(''SYS_JES_JOB_URI'')));'; put 'put '',"SYS_JES_JOB_URI": '' SYS_JES_JOB_URI;'; put 'end;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put ",""SYSWARNINGTEXT"" : " syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'put "}" ;'; put 'if mode ne ''SASJS'' then put ''>>weboutEND<<'';'; put 'run;'; put '%put _all_;'; put '%if "&sysprocessmode " = "SAS Stored Process Server " %then %do;'; put 'data _null_;'; put 'putlog ''stpsrvset program err and syscc'';'; put 'rc=stpsrvset(''program error'', 0);'; put 'call symputx("syscc",0,"g");'; put 'run;'; put '%if &sysscp=WIN'; put 'and 1=0 /* deprecating this logic until we figure out a consistent abort */'; put 'and "%substr(%str(&sysvlong ),1,8)"="9.04.01M"'; put 'and "%substr(%str(&sysvlong ),9,1)">"5" %then %do;'; put '/* skip approach (below) does not work in windows m6+ envs */'; put 'endsas;'; put '%end;'; put '%else %do;'; put '/**'; put '* endsas kills 9.4m3 deployments by orphaning multibridges.'; put '* Abort variants are ungraceful (non zero return code)'; put '* This approach lets SAS run silently until the end :-)'; put '* Caution - fails when called within a %include within a macro'; put '* Use mp_include() to handle this.'; put '*/'; put 'filename skip temp;'; put 'data _null_;'; put 'file skip;'; put 'put ''%macro skip();'';'; put 'comment ''%mend skip; -> fix lint '';'; put 'put ''%macro skippy();'';'; put 'comment ''%mend skippy; -> fix lint '';'; put 'run;'; put '%inc skip;'; put '%end;'; put '%end;'; put '%else %if "&sysprocessmode " = "SAS Compute Server " %then %do;'; put '/* endsas kills the session making it harder to fetch results */'; put 'data _null_;'; put 'syswarningtext=symget(''syswarningtext'');'; put 'syserrortext=symget(''syserrortext'');'; put 'abort_msg=symget(''msg'');'; put 'syscc=symget(''syscc'');'; put 'sysuserid=symget(''sysuserid'');'; put 'iftrue=symget(''iftrue'');'; put 'put (_all_)(/=);'; put 'call symputx(''syscc'',0);'; put 'abort cancel nolist;'; put 'run;'; put '%end;'; put '%else %do;'; put '%abort cancel;'; put '%end;'; put '%end;'; put '%else %do;'; put '%put _all_;'; put '%abort cancel;'; put '%end;'; put '%mend mp_abort;'; put '/** @endcond */'; put '%macro mm_getstpcode('; put 'tree=/User Folders/sasdemo/somestp'; put ',name='; put ',outloc=0'; put ',outref=0'; put ',mDebug=1'; put ',showlog=NO'; put ');'; put '%local mD;'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing &sysmacroname..sas;'; put '%&mD.put _local_;'; put '%if %length(&name)>0 %then %let name=/&name;'; put '/* first, check if STP exists */'; put '%local tsuri;'; put '%let tsuri=stopifempty ;'; put 'data _null_;'; put 'format type uri tsuri value $200.;'; put 'call missing (of _all_);'; put 'path="&tree&name(StoredProcess)";'; put '/* first, find the STP ID */'; put 'if metadata_pathobj("",path,"StoredProcess",type,uri)>0 then do;'; put '/* get sourcecode */'; put 'cnt=1;'; put 'do while (metadata_getnasn(uri,"Notes",cnt,tsuri)>0);'; put 'rc=metadata_getattr(tsuri,"Name",value);'; put '&mD.put tsuri= value=;'; put 'if value="SourceCode" then do;'; put '/* found it! */'; put 'rc=metadata_getattr(tsuri,"Id",value);'; put 'call symputx(''tsuri'',value,''l'');'; put 'stop;'; put 'end;'; put 'cnt+1;'; put 'end;'; put 'end;'; put 'else put (_all_)(=);'; put 'run;'; put '%mp_abort(iftrue= (&tsuri=stopifempty)'; put ',mac=mm_getstpcode'; put ',msg=%str(&tree&name.(StoredProcess) not found!)'; put ')'; put '/**'; put '* Now we can extract the textstore'; put '*/'; put 'filename __getdoc temp lrecl=10000000;'; put 'proc metadata'; put 'in="$METAREPOSITORY'; put ''; put 'SAS1"'; put 'out=__getdoc ;'; put 'run;'; put '/* find the beginning of the text */'; put '%local start;'; put 'data _null_;'; put 'infile __getdoc lrecl=10000;'; put 'input;'; put 'start=index(_infile_,''StoredText="'');'; put 'if start then do;'; put 'call symputx("start",start+11);'; put '*putlog ''"'' _infile_ ''"'';'; put 'end;'; put 'stop;'; put '%local outeng;'; put '%if "&outloc"="0" %then %let outeng=TEMP;'; put '%else %let outeng="&outloc";'; put '%local fref;'; put '%if &outref=0 %then %let fref=%mf_getuniquefileref();'; put '%else %let fref=&outref;'; put '/* read the content, byte by byte, resolving escaped chars */'; put 'filename &fref &outeng lrecl=100000;'; put 'data _null_;'; put 'length filein 8 fileid 8;'; put 'filein = fopen("__getdoc","I",1,"B");'; put 'fileid = fopen("&fref","O",1,"B");'; put 'rec = "20"x;'; put 'length entity $6;'; put 'do while(fread(filein)=0);'; put 'x+1;'; put 'if x>&start then do;'; put 'rc = fget(filein,rec,1);'; put 'if rec=''"'' then leave;'; put 'else if rec="&" then do;'; put 'entity=rec;'; put 'do until (rec=";");'; put 'if fread(filein) ne 0 then goto getout;'; put 'rc = fget(filein,rec,1);'; put 'entity=cats(entity,rec);'; put 'end;'; put 'select (entity);'; put 'when (''&'' ) rec=''&'' ;'; put 'when (''<'' ) rec=''<'' ;'; put 'when (''>'' ) rec=''>'' ;'; put 'when (''''') rec="''" ;'; put 'when (''"'') rec=''"'' ;'; put 'when ('' '') rec=''0A''x;'; put 'when ('' '') rec=''0D''x;'; put 'when (''$'' ) rec=''$'' ;'; put 'when ('' '') rec=''09''x;'; put 'otherwise putlog "%str(WARN)ING: missing value for " entity=;'; put 'end;'; put 'rc =fput(fileid, substr(rec,1,1));'; put 'rc =fwrite(fileid);'; put 'end;'; put 'else do;'; put 'rc =fput(fileid,rec);'; put 'rc =fwrite(fileid);'; put 'end;'; put 'end;'; put 'end;'; put 'getout:'; put 'rc=fclose(filein);'; put 'rc=fclose(fileid);'; put 'run;'; put '%if &showlog=YES %then %do;'; put 'data _null_;'; put 'infile &fref lrecl=32767 end=last;'; put 'input;'; put 'if _n_=1 then putlog ''>>stpcodeBEGIN<<'';'; put 'putlog _infile_;'; put 'if last then putlog ''>>stpcodeEND<<'';'; put 'run;'; put '%end;'; put 'filename __getdoc clear;'; put '%if &outref=0 %then %do;'; put 'filename &fref clear;'; put '%end;'; put '%mend mm_getstpcode;'; put '%macro dc_getsettings();'; put '%global _program;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(syscc=&syscc on entry (&syswarningtext &syserrortext))'; put ')'; put '%if %length(&_PROGRAM)>1 %then %let root=&_program;'; put '%else %do;'; put '%global _metauser;'; put '%let _metauser=&sysuserid;'; put '/* to mimic a "real" _program we need to give a dummy role and stp name */'; put '%let root=/dummyRole/dummyName;'; put '%end;'; put '/* the DC precode is stored in the Admin folder in the root of'; put 'the project. Lets find that root. */'; put '%put &=root;'; put '%let root=%mf_getapploc();'; put '%put &=root;'; put '/* Now we know the root location we can retrieve the params */'; put '%let temploc=%sysfunc(pathname(work))/settings.txt;'; put '%mm_getstpcode(tree=&root/services/public'; put ',name=Data_Controller_Settings'; put ',outloc=&temploc'; put ')'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Unable to run getstpcode)'; put ')'; put 'filename _getsets "&temploc" lrecl=2000;'; put '/*'; put 'Do not use mp_include here - this puts a copy in every service, which creates'; put 'compilation problems when calling services from mp_include'; put '*/'; put '%inc _getsets/source2;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Problem running &sysmacroname (&syswarningtext &syserrortext))'; put ')'; put '%mend dc_getsettings;'; put '%macro mf_fmtdttm('; put ')/*/STORE SOURCE*/;'; put '%if "&sysver"="9.2" or "&sysver"="9.3"'; put 'or ("&sysver"="9.4" and "%substr(&SYSVLONG,9,1)" le "3")'; put 'or "%substr(&sysver,1,1)"="4"'; put 'or "%substr(&sysver,1,1)"="5"'; put '%then %do;DATETIME19.3%end;'; put '%else %do;E8601DT26.6%end;'; put '%mend mf_fmtdttm;'; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '%macro mp_init(prefix=SASJS'; put ')/*/STORE SOURCE*/;'; put '%if %symexist(SASJS_PREFIX) %then %return; /* only run once */'; put '%global'; put 'SASJS_PREFIX /* the ONLY hard-coded global macro variable in SASjs */'; put '&prefix._FUNCTIONS /* used in mcf_init() to track core function compilation */'; put '&prefix._INIT_NUM /* initialisation time as numeric */'; put '&prefix._INIT_DTTM /* initialisation time in E8601DT26.6 format */'; put '&prefix.WORK /* avoid typing %sysfunc(pathname(work)) every time */'; put ';'; put '%let sasjs_prefix=&prefix;'; put 'data _null_;'; put 'dttm=datetime();'; put 'call symputx("&sasjs_prefix._init_num",dttm,''g'');'; put 'call symputx("&sasjs_prefix._init_dttm",put(dttm,E8601DT26.6),''g'');'; put 'call symputx("&sasjs_prefix.work",pathname(''WORK''),''g'');'; put 'run;'; put 'options'; put 'compress=CHAR /* default is none so ensure we have something! */'; put 'datastmtchk=ALLKEYWORDS /* protection from overwriting input datasets */'; put 'errorcheck=STRICT /* catch errs in libname/filename statements */'; put 'fmterr /* ensure err when a format cannot be found */'; put 'mergenoby=%str(ERR)OR /* throw err when a merge has no BY variables */'; put 'missing=. /* changing this can cause hard to detect errs */'; put 'noquotelenmax /* avoid warnings for long strings */'; put 'noreplace /* avoid overwriting permanent datasets */'; put 'ps=max /* reduce log size slightly */'; put 'ls=max /* reduce log even more and avoid word truncation */'; put 'validmemname=COMPATIBLE /* avoid special characters etc in table names */'; put 'validvarname=V7 /* avoid special characters etc in variable names */'; put 'varinitchk=%str(ERR)OR /* avoid data mistakes from variable name typos */'; put 'varlenchk=%str(ERR)OR /* fail hard if truncation (data loss) can result */'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5" %then %do;'; put 'noautocorrect /* disallow misspelled procedure names */'; put 'dsoptions=note2err /* undocumented - convert bad NOTEs to ERRs */'; put '%end;'; put ';'; put '%mend mp_init;'; put '%macro mpeinit(fetch=YES);'; put '%global mpeinit'; put 'mpeadmins /* group with unrestricted Meditor access */'; put 'mpelocapprovals /* location for landing and staging files */'; put 'mpelib /* location of configuration tables for DC */'; put 'dc_repo_users /* location of user / group metadata */'; put 'dc_licence_key /* extracted in dc_getsettings */'; put 'dc_activation_key /* extracted in dc_getsettings */'; put 'dc_locale /* extracted in dc_getsettings */'; put 'dc_dttmtfmt /* can be overridden in dc_getsettings */'; put '_debug'; put ';'; put '%if &mpeinit=1 %then %return;'; put '%else %let mpeinit=1;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(Problem on service startup (&syswarningtext &syserrortext))'; put ')'; put '%mp_init()'; put '%if &fetch=YES %then %do;'; put '%webout(FETCH)'; put '%end;'; put '%global _CLIENTNAME;'; put '%mp_abort(iftrue= (&_CLIENTNAME=SAS Enterprise Guide)'; put ',mac=&_program..sas'; put ',msg=%str(Data Controller is a web app and should not be executed from EG)'; put ')'; put 'options urlencoding=utf8 nobomfile lrecl=32767;'; put '%let perf=%sysfunc(datetime());'; put '%put perfdiff: 0;'; put '%let dc_locale=SYSTEM; /* default if not set */'; put '/**'; put '* E8601DT26.6 has widest database support - but not all SAS flavours can'; put '* handle it. Override in the settings STP if needed.'; put '*/'; put 'data _null_;'; put 'dc_dttmtfmt=''"%sysfunc(datetime(),''!!"%mf_fmtdttm()"!!'')"dt'';'; put 'call symputx(''dc_dttmtfmt'',dc_dttmtfmt);'; put 'put dc_dttmtfmt=;'; put 'run;'; put '%put &=dc_dttmtfmt;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc prior to dc_getsettings)'; put ')'; put '%dc_getsettings()'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc after dc_getsettings)'; put ')'; put 'data _null_;'; put 'set &DC_LIBREF..mpe_config(where=('; put 'var_scope="DC"'; put 'and &dc_dttmtfmt lt tx_to'; put 'and var_active=1'; put '));'; put 'call symputx(var_name,var_value,''G'');'; put 'putlog var_name "=" var_value;'; put 'run;'; put '%let mpelib=&dc_libref;'; put '%let mpeadmins=&dc_admin_group;'; put '%let mpelocapprovals=&dc_staging_area;'; put '%let dc_repo_users=&dc_repo_users;'; put '%if &dc_locale ne SYSTEM %then %do;'; put 'options locale=&dc_locale;'; put '%end;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(Problem during compilation or with STP precode (&syswarningtext))'; put ')'; put '%mend mpeinit;'; put '%macro mf_mval(var);'; put '%if %symexist(&var) %then %do;'; put '%superq(&var)'; put '%end;'; put '%mend mf_mval;'; put '%macro mf_trimstr(basestr,trimstr);'; put '%local baselen trimlen trimval;'; put '/* return if basestr is shorter than trimstr (or 0) */'; put '%let baselen=%length(%superq(basestr));'; put '%let trimlen=%length(%superq(trimstr));'; put '%if &baselen < &trimlen or &baselen=0 %then %return;'; put '/* obtain the characters from the end of basestr */'; put '%let trimval=%qsubstr(%superq(basestr)'; put ',%length(%superq(basestr))-&trimlen+1'; put ',&trimlen);'; put '/* compare and if matching, chop it off! */'; put '%if %superq(basestr)=%superq(trimstr) %then %do;'; put '%return;'; put '%end;'; put '%else %if %superq(trimval)=%superq(trimstr) %then %do;'; put '%qsubstr(%superq(basestr),1,%length(%superq(basestr))-&trimlen)'; put '%end;'; put '%else %do;'; put '&basestr'; put '%end;'; put '%mend mf_trimstr;'; put '%macro mf_getplatform(switch'; put ')/*/STORE SOURCE*/;'; put '%local a b c;'; put '%if &switch.NONE=NONE %then %do;'; put '%if %symexist(sasjsprocessmode) %then %do;'; put '%if &sasjsprocessmode=Stored Program %then %do;'; put 'SASJS'; put '%return;'; put '%end;'; put '%end;'; put '%if %symexist(sysprocessmode) %then %do;'; put '%if "&sysprocessmode"="SAS Object Server"'; put 'or "&sysprocessmode"= "SAS Compute Server" %then %do;'; put 'SASVIYA'; put '%end;'; put '%else %if "&sysprocessmode"="SAS Stored Process Server"'; put 'or "&sysprocessmode"="SAS Workspace Server"'; put '%then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if %symexist(_metaport) or %symexist(_metauser) %then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if &switch=SASSTUDIO %then %do;'; put '/* return the version of SAS Studio else 0 */'; put '%if %mf_mval(_CLIENTAPP)=%str(SAS Studio) %then %do;'; put '%let a=%mf_mval(_CLIENTVERSION);'; put '%let b=%scan(&a,1,.);'; put '%if %eval(&b >2) %then %do;'; put '&b'; put '%end;'; put '%else 0;'; put '%end;'; put '%else 0;'; put '%end;'; put '%else %if &switch=VIYARESTAPI %then %do;'; put '%mf_trimstr(%sysfunc(getoption(servicesbaseurl)),/)'; put '%end;'; put '%mend mf_getplatform;'; put '%macro mpeterm();'; put '%local oldloc;'; put 'data _null_;'; put 'if symexist(''SYSPRINTTOLOG'') then oldloc=symget(''SYSPRINTTOLOG'');'; put 'else oldloc=getoption(''LOG'');'; put 'if subpad(oldloc,1,1) not in (''"'',"''",'' '') then oldloc=quote(cats(oldloc));'; put 'call symputx(''oldloc'',oldloc,''l'');'; put 'run;'; put '%if %length(&oldloc)>0 %then %do;'; put 'proc printto log=log;'; put 'run;'; put 'data _null_;'; put 'infile &oldloc;'; put 'input; putlog _infile_;'; put 'run;'; put '%end;'; put '%if %sysfunc(exist(&dc_libref..mpe_requests)) and %mf_getplatform() ne SASVIYA'; put '%then %do;'; put 'data ;'; put 'if 0 then set &dc_libref..mpe_requests;'; put 'request_dttm=%sysfunc(datetime());'; put 'request_user="%mf_getuser()";'; put 'request_service="%scan(&_program,-2,/)/%scan(&_program,-1,/)";'; put 'request_params='''';'; put 'output;stop;'; put 'proc append base=&dc_libref..mpe_requests data=&syslast force nowarn;'; put 'run;'; put '%end;'; put '%mend mpeterm;'; put '%macro mm_getGroups('; put 'user='; put ',outds=work.mm_getGroups'; put ',repo=foundation'; put ',mDebug=0'; put ')/*/STORE SOURCE*/;'; put '%local mD oldrepo;'; put '%let oldrepo=%sysfunc(getoption(metarepository));'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing mm_getGroups.sas;'; put '%&mD.put _local_;'; put '/* on some sites, user / group info is in a different metadata repo to the'; put 'default */'; put '%if &oldrepo ne &repo %then %do;'; put 'options metarepository=&repo;'; put '%end;'; put '%if %length(&user)=0 %then %do;'; put 'data &outds (keep=groupuri groupname groupdesc);'; put 'length groupuri groupname groupdesc group_or_role $256;'; put 'call missing(of _all_);'; put 'i+1;'; put 'do while'; put '(metadata_getnobj("omsobj:IdentityGroup?@Id contains ''.''",i,groupuri)>0);'; put 'rc=metadata_getattr(groupuri, "Name", groupname);'; put 'rc=metadata_getattr(groupuri, "Desc", groupdesc);'; put 'rc=metadata_getattr(groupuri,"PublicType",group_or_role);'; put 'if Group_or_Role = ''UserGroup'' then output;'; put 'i+1;'; put 'end;'; put 'run;'; put '%end;'; put '%else %do;'; put 'data &outds (keep=groupuri groupname groupdesc);'; put 'length uri groupuri groupname groupdesc group_or_role $256;'; put 'call missing(of _all_);'; put 'rc=metadata_getnobj("omsobj:Person?@Name=''&user''",1,uri);'; put 'if rc<=0 then do;'; put 'putlog "%str(WARN)ING: rc=" rc "&user not found "'; put '", or there was an issue reading the repository.";'; put 'stop;'; put 'end;'; put 'a=1;'; put 'grpassn=metadata_getnasn(uri,"IdentityGroups",a,groupuri);'; put 'if grpassn in (-3,-4) then do;'; put 'putlog "%str(WARN)ING: No metadata groups found for &user";'; put 'output;'; put 'end;'; put 'else do while (grpassn > 0);'; put 'rc=metadata_getattr(groupuri, "Name", groupname);'; put 'rc=metadata_getattr(groupuri, "Desc", groupdesc);'; put 'a+1;'; put 'rc=metadata_getattr(groupuri,"PublicType",group_or_role);'; put 'if Group_or_Role = ''UserGroup'' then output;'; put 'grpassn=metadata_getnasn(uri,"IdentityGroups",a,groupuri);'; put 'end;'; put 'run;'; put '%end;'; put '%if &oldrepo ne &repo %then %do;'; put 'options metarepository=&oldrepo;'; put '%end;'; put '%mend mm_getGroups;'; put '%macro mm_getrepos('; put 'outds=work.mm_getrepos'; put ')/*/STORE SOURCE*/;'; put '* use a temporary fileref to hold the response;'; put 'filename response temp;'; put '/* get list of libraries */'; put 'proc metadata in='; put '"1"'; put 'out=response;'; put 'run;'; put '/* write the response to the log for debugging */'; put '/*'; put 'data _null_;'; put 'infile response lrecl=1048576;'; put 'input;'; put 'put _infile_;'; put 'run;'; put '*/'; put '/* create an XML map to read the response */'; put 'filename sxlemap temp;'; put 'data _null_;'; put 'file sxlemap;'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository";'; put 'put "";'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Id";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Name";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Desc";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@DefaultNS";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@RepositoryType";'; put 'put "characterstring20";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@RepositoryFormat";'; put 'put "characterstring10";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@Access";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@CurrentAccess";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@PauseState";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Path";'; put 'put "";'; put 'put "characterstring256";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Engine";'; put 'put "";'; put 'put "characterstring8";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Options";'; put 'put "";'; put 'put "characterstring32";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@MetadataCreated";'; put 'put "characterstring24";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@MetadataUpdated";'; put 'put "characterstring24";'; put 'put '''';'; put 'put ''
'';'; put 'run;'; put 'libname _XML_ xml xmlfileref=response xmlmap=sxlemap;'; put 'proc sort data= _XML_.SASRepos out=&outds;'; put 'by name;'; put 'run;'; put '/* clear references */'; put 'filename sxlemap clear;'; put 'filename response clear;'; put 'libname _XML_ clear;'; put '%mend mm_getrepos;'; put '%macro dc_getgroups(outds=mm_getlibs);'; put '%local repo repocnt x;'; put '%let repo=%sysfunc(getoption(metarepository));'; put '/* get list of repositories and filter */'; put '%mm_getrepos(outds=repos)'; put '%let repocnt=0;'; put 'data repos;'; put 'set repos;'; put 'where repositorytype in(''CUSTOM'',''FOUNDATION'')'; put '& upcase(name) ne "%upcase(&repo)";'; put 'keep id name ;'; put 'call symputx(cats(''repo'',_n_),name,''l'');'; put 'call symputx(''repocnt'',_n_,''l'');'; put 'run;'; put '%put _local_;'; put '%mm_getgroups(outds=&outds,repo=foundation)'; put '%do x=1 %to &repocnt;'; put '%mm_getgroups(outds=&outds.a, repo=&&repo&x)'; put 'proc append base=&outds data=&outds.a;'; put 'run;'; put '%end;'; put 'proc sort data=&outds noduprec;'; put 'by groupname;'; put 'run;'; put '%mend dc_getgroups;'; put '* SAS Macros end;'; put '* SAS Includes start;'; put '* SAS Includes end;'; put '* Binary Files start;'; put '* Binary Files end;'; put '* ServiceInit start;'; put 'options noquotelenmax ps=max;'; put '/* create dummy macros to prevent stpbegin / stpend from corrupting sessions */'; put '%macro stpbegin();'; put '%put NOTE: the STPBEGIN macro should not be used for web apps!;'; put '%mend stpbegin;'; put '%macro stpend();'; put '%put NOTE: the STPEND macro should not be used for web apps!;'; put '%mend stpend;'; put '* ServiceInit end;'; put '* Service start;'; put '/**'; put '@file'; put '@brief validating the mpe_security.sas_group column'; put '@details The input table is simply one row from the target table in table'; put 'called "work.source_row".'; put 'Available macro variables:'; put '@li LIBDS - The library.dataset being filtered'; put '@li VARIABLE_NM - The column being filtered'; put '

Service Outputs

'; put 'The values provided below are generic samples - we encourage you to replace'; put 'these with realistic values in your own deployments.'; put '
DYNAMIC_VALUES
'; put 'The RAW_VALUE column may be charactor or numeric. If DISPLAY_INDEX is not'; put 'provided, it is added automatically.'; put '|DISPLAY_INDEX:best.|DISPLAY_VALUE:$|RAW_VALUE|'; put '|---|---|---|'; put '|1|$77.43|77.43|'; put '|2|$88.43|88.43|'; put '
DYNAMIC_EXTENDED_VALUES
'; put 'This table is optional. If provided, it will map the DISPLAY_INDEX from the'; put 'DYNAMIC_VALUES table to additional column/value pairs, that will be used to'; put 'populate dropdowns for _other_ cells in the _same_ row.'; put 'Should be used sparingly! The use of large tables here can slow down the'; put 'browser.'; put '|DISPLAY_INDEX:best.|EXTRA_COL_NAME:$32.|DISPLAY_VALUE:$|DISPLAY_TYPE:$1.|RAW_VALUE_NUM|RAW_VALUE_CHAR:$5000|'; put '|---|---|---|'; put '|1|DISCOUNT_RT|"50%"|N|0.5||'; put '|1|DISCOUNT_RT|"40%"|N|0.4||'; put '|1|DISCOUNT_RT|"30%"|N|0.3||'; put '|1|CURRENCY_SYMBOL|"GBP"|C||"GBP"|'; put '|1|CURRENCY_SYMBOL|"RSD"|C||"RSD"|'; put '|2|DISCOUNT_RT|"50%"|N|0.5||'; put '|2|DISCOUNT_RT|"40%"|N|0.4||'; put '|2|CURRENCY_SYMBOL|"EUR"|C||"EUR"|'; put '|2|CURRENCY_SYMBOL|"HKD"|C||"HKD"|'; put '

SAS Macros

'; put '@li dc_getgroups.sas'; put '**/'; put '%dc_getgroups(outds=groups)'; put 'proc sql;'; put 'create table work.DYNAMIC_VALUES as'; put 'select distinct groupname as display_value,'; put 'groupname as raw_value'; put 'from work.groups'; put 'order by 1;'; put '* Service end;'; run; %mm_createwebservice(path=&appLoc/&path, name=&service, code=sascode, server=&serverName, replace=yes) filename sascode clear; %let service=tables_all; filename sascode temp lrecl=32767; data _null_; file sascode; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '/**'; put '@file mp_jsonout.sas'; put '@brief Writes JSON in SASjs format to a fileref'; put '@details This macro can be used to OPEN a JSON stream and send one or more'; put 'tables as arrays of rows, where each row can be an object or a nested array.'; put 'There are two engines available - DATASTEP or PROCJSON.'; put 'PROC JSON is fast but will produce errs like the ones below if'; put 'special chars are encountered.'; put '> (ERR)OR: Some code points did not transcode.'; put '> An object or array close is not valid at this point in the JSON text.'; put '> Date value out of range'; put 'If this happens, try running with ENGINE=DATASTEP.'; put 'The DATASTEP engine is used to handle special SAS missing numerics, and'; put 'can also convert entire datasets to formatted values. Output JSON is always'; put 'in UTF-8.'; put 'Usage:'; put 'filename tmp temp;'; put 'data class; set sashelp.class;run;'; put '%mp_jsonout(OPEN,jref=tmp)'; put '%mp_jsonout(OBJ,class,jref=tmp)'; put '%mp_jsonout(OBJ,class,dslabel=class2,jref=tmp,showmeta=Y)'; put '%mp_jsonout(CLOSE,jref=tmp)'; put 'data _null_;'; put 'infile tmp;'; put 'input;putlog _infile_;'; put 'run;'; put 'If you are building web apps with SAS then you are strongly encouraged to use'; put 'the mX_createwebservice macros in combination with the'; put '[sasjs adapter](https://github.com/sasjs/adapter).'; put 'For more information see https://sasjs.io'; put '@param [in] action Valid values:'; put '@li OPEN - opens the JSON'; put '@li OBJ - sends a table with each row as an object'; put '@li ARR - sends a table with each row in an array'; put '@li CLOSE - closes the JSON'; put '@param [in] ds The dataset to send. Must be a work table.'; put '@param [out] jref= (_webout) The fileref to which to send the JSON'; put '@param [out] dslabel= The name to give the table in the exported JSON'; put '@param [in] fmt= (Y) Whether to keep (Y) or strip (N) formats from the table'; put '@param [in] engine= (DATASTEP) Which engine to use to send the JSON. Options:'; put '@li PROCJSON (default)'; put '@li DATASTEP (more reliable when data has non standard characters)'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to JSON'; put '

Related Files

'; put '@li mp_ds2fmtds.sas'; put '@version 9.2'; put '@author Allan Bowe'; put '@source https://github.com/sasjs/core'; put '**/'; put '%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y'; put ',engine=DATASTEP'; put ',missing=NULL'; put ',showmeta=N'; put ',maxobs=MAX'; put ')/*/STORE SOURCE*/;'; put '%local tempds colinfo fmtds i numcols numobs stmt_obs lastobs optval'; put 'tmpds1 tmpds2 tmpds3 tmpds4;'; put '%let numcols=0;'; put '%if &maxobs ne MAX %then %let stmt_obs=%str(if _n_>&maxobs then stop;);'; put '%if &action=OPEN %then %do;'; put 'options nobomfile;'; put 'data _null_;file &jref encoding=''utf-8'' lrecl=200;'; put 'put ''{"PROCESSED_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''"'';'; put 'run;'; put '%end;'; put '%else %if (&action=ARR or &action=OBJ) %then %do;'; put '/* force variable names to always be uppercase in the JSON */'; put 'options validvarname=upcase;'; put '/* To avoid issues with _webout on EBI - such as encoding diffs and truncation'; put '(https://support.sas.com/kb/49/325.html) we use temporary files */'; put 'filename _sjs1 temp lrecl=200 ;'; put 'data _null_; file _sjs1 encoding=''utf-8'';'; put 'put ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":";'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjs1 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs1 clear;'; put '/* grab col defs */'; put 'proc contents noprint data=&ds'; put 'out=_data_(keep=name type length format formatl formatd varnum label);'; put 'run;'; put '%let colinfo=%scan(&syslast,2,.);'; put 'proc sort data=&colinfo;'; put 'by varnum;'; put 'run;'; put '/* move meta to mac vars */'; put 'data &colinfo;'; put 'if _n_=1 then call symputx(''numcols'',nobs,''l'');'; put 'set &colinfo end=last nobs=nobs;'; put 'name=upcase(name);'; put '/* fix formats */'; put 'if type=2 or type=6 then do;'; put 'typelong=''char'';'; put 'length fmt $49.;'; put 'if format='''' then fmt=cats(''$'',length,''.'');'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else fmt=cats(format,formatl,''.'');'; put 'end;'; put 'else do;'; put 'typelong=''num'';'; put 'if format='''' then fmt=''best.'';'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else if formatd=0 then fmt=cats(format,formatl,''.'');'; put 'else fmt=cats(format,formatl,''.'',formatd);'; put 'end;'; put '/* 32 char unique name */'; put 'newname=''sasjs''!!substr(cats(put(md5(name),$hex32.)),1,27);'; put 'call symputx(cats(''name'',_n_),name,''l'');'; put 'call symputx(cats(''newname'',_n_),newname,''l'');'; put 'call symputx(cats(''length'',_n_),length,''l'');'; put 'call symputx(cats(''fmt'',_n_),fmt,''l'');'; put 'call symputx(cats(''type'',_n_),type,''l'');'; put 'call symputx(cats(''typelong'',_n_),typelong,''l'');'; put 'call symputx(cats(''label'',_n_),coalescec(label,name),''l'');'; put '/* overwritten when fmt=Y and a custom format exists in catalog */'; put 'if typelong=''num'' then call symputx(cats(''fmtlen'',_n_),200,''l'');'; put 'else call symputx(cats(''fmtlen'',_n_),min(32767,ceil((length+10)*1.5)),''l'');'; put 'run;'; put '%let tempds=%substr(_%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql;'; put 'select count(*) into: lastobs from &ds;'; put '%if &maxobs ne MAX %then %let lastobs=%sysfunc(min(&lastobs,&maxobs));'; put '%if &engine=PROCJSON %then %do;'; put '%if &missing=STRING %then %do;'; put '%put &sysmacroname: Special Missings not supported in proc json.;'; put '%put &sysmacroname: Switching to DATASTEP engine;'; put '%goto datastep;'; put '%end;'; put 'data &tempds;'; put 'set &ds;'; put '&stmt_obs;'; put '%if &fmt=N %then format _numeric_ best32.;;'; put '/* PRETTY is necessary to avoid line truncation in large files */'; put 'filename _sjs2 temp lrecl=131068 encoding=''utf-8'';'; put 'proc json out=_sjs2 pretty'; put '%if &action=ARR %then nokeys ;'; put ';export &tempds / nosastags fmtnumeric;'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs2 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs2 clear;'; put '%end;'; put '%else %if &engine=DATASTEP %then %do;'; put '%datastep:'; put '%if %sysfunc(exist(&ds)) ne 1 & %sysfunc(exist(&ds,VIEW)) ne 1'; put '%then %do;'; put '%put &sysmacroname: &ds NOT FOUND!!!;'; put '%return;'; put '%end;'; put '%if &fmt=Y %then %do;'; put '/**'; put '* Extract format definitions'; put '* First, by getting library locations from dictionary.formats'; put '* Then, by exporting the width using proc format'; put '* Cannot use maxw from sashelp.vformat as not always populated'; put '* Cannot use fmtinfo() as not supported in all flavours'; put '*/'; put '%let tmpds1=%substr(fmtsum%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds2=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds3=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds4=%substr(col%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql noprint;'; put 'create table &tmpds1 as'; put 'select cats(libname,''.'',memname) as FMTCAT,'; put 'FMTNAME'; put 'from dictionary.formats'; put 'where fmttype=''F'' and libname is not null'; put 'and fmtname in (select format from &colinfo where format is not null)'; put 'order by 1;'; put 'create table &tmpds2('; put 'FMTNAME char(32),'; put 'LENGTH num'; put ');'; put '%local catlist cat fmtlist i;'; put 'select distinct fmtcat into: catlist separated by '' '' from &tmpds1;'; put '%do i=1 %to %sysfunc(countw(&catlist,%str( )));'; put '%let cat=%scan(&catlist,&i,%str( ));'; put 'proc sql;'; put 'select distinct fmtname into: fmtlist separated by '' '''; put 'from &tmpds1 where fmtcat="&cat";'; put 'proc format lib=&cat cntlout=&tmpds3(keep=fmtname length);'; put 'select &fmtlist;'; put 'run;'; put 'proc sql;'; put 'insert into &tmpds2 select distinct fmtname,length from &tmpds3;'; put '%end;'; put 'proc sql;'; put 'create table &tmpds4 as'; put 'select a.*, b.length as MAXW'; put 'from &colinfo a'; put 'left join &tmpds2 b'; put 'on cats(a.format)=cats(upcase(b.fmtname))'; put 'order by a.varnum;'; put 'data _null_;'; put 'set &tmpds4;'; put 'if not missing(maxw);'; put 'call symputx('; put 'cats(''fmtlen'',_n_),'; put '/* vars need extra padding due to JSON escaping of special chars */'; put 'min(32767,ceil((max(length,maxw)+10)*1.5))'; put ',''l'''; put ');'; put 'run;'; put '/* configure varlenchk - as we are explicitly shortening the variables */'; put '%let optval=%sysfunc(getoption(varlenchk));'; put 'options varlenchk=NOWARN;'; put 'data _data_(compress=char);'; put '/* shorten the new vars */'; put 'length'; put '%do i=1 %to &numcols;'; put '&&name&i $&&fmtlen&i'; put '%end;'; put ';'; put '/* rename on entry */'; put 'set &ds(rename=('; put '%do i=1 %to &numcols;'; put '&&name&i=&&newname&i'; put '%end;'; put '));'; put '&stmt_obs;'; put 'drop'; put '%do i=1 %to &numcols;'; put '&&newname&i'; put '%end;'; put ';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=num %then %do;'; put '&&name&i=cats(put(&&newname&i,&&fmt&i));'; put '%end;'; put '%else %do;'; put '&&name&i=put(&&newname&i,&&fmt&i);'; put '%end;'; put '%end;'; put 'if _error_ then do;'; put 'call symputx(''syscc'',1012);'; put 'stop;'; put 'end;'; put 'run;'; put '%let fmtds=&syslast;'; put 'options varlenchk=&optval;'; put '%end;'; put 'proc format; /* credit yabwon for special null removal */'; put 'value bart (default=40)'; put '%if &missing=NULL %then %do;'; put '._ - .z = null'; put '%end;'; put '%else %do;'; put '._ = [quote()]'; put '. = null'; put '.a - .z = [quote()]'; put '%end;'; put 'other = [best.];'; put 'data &tempds;'; put 'attrib _all_ label='''';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'length &&name&i $&&fmtlen&i...;'; put 'format &&name&i $&&fmtlen&i...;'; put '%end;'; put '%end;'; put '%if &fmt=Y %then %do;'; put 'set &fmtds;'; put '%end;'; put '%else %do;'; put 'set &ds;'; put '%end;'; put '&stmt_obs;'; put 'format _numeric_ bart.;'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'if findc(&&name&i,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put '&&name&i=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,&&name&i)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else &&name&i=quote(cats(&&name&i));'; put '%end;'; put '%end;'; put 'run;'; put 'filename _sjs3 temp lrecl=131068 ;'; put 'data _null_;'; put 'file _sjs3 encoding=''utf-8'';'; put 'if _n_=1 then put "[";'; put 'set &tempds;'; put 'if _n_>1 then put "," @; put'; put '%if &action=ARR %then "[" ; %else "{" ;'; put '%do i=1 %to &numcols;'; put '%if &i>1 %then "," ;'; put '%if &action=OBJ %then """&&name&i"":" ;'; put '"&&name&i"n /* name literal for reserved variable names */'; put '%end;'; put '%if &action=ARR %then "]" ; %else "}" ; ;'; put '/* close out the table */'; put 'data _null_;'; put 'file _sjs3 mod encoding=''utf-8'';'; put 'put '']'';'; put 'run;'; put 'data _null_;'; put 'infile _sjs3 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs3 clear;'; put '%end;'; put 'proc sql;'; put 'drop table &colinfo, &tempds;'; put '%if %substr(&showmeta,1,1)=Y %then %do;'; put 'filename _sjs4 temp lrecl=131068 encoding=''utf-8'';'; put 'data _null_;'; put 'file _sjs4;'; put 'length label $350;'; put 'put ", ""$%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":{""vars"":{";'; put 'do i=1 to &numcols;'; put 'name=quote(trim(symget(cats(''name'',i))));'; put 'format=quote(trim(symget(cats(''fmt'',i))));'; put 'label=quote(prxchange(''s/\\/\\\\/'',-1,trim(symget(cats(''label'',i)))));'; put 'length=quote(trim(symget(cats(''length'',i))));'; put 'type=quote(trim(symget(cats(''typelong'',i))));'; put 'if i>1 then put "," @@;'; put 'put name '':{"format":'' format '',"label":'' label'; put ''',"length":'' length '',"type":'' type ''}'';'; put 'end;'; put 'put ''}}'';'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs4 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs4 clear;'; put '%end;'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put 'data _null_; file &jref encoding=''utf-8'' mod ;'; put 'put "}";'; put 'run;'; put '%end;'; put '%mend mp_jsonout;'; put '/**'; put '@file mm_webout.sas'; put '@brief Send data to/from SAS Stored Processes'; put '@details This macro should be added to the start of each Stored Process,'; put '**immediately** followed by a call to:'; put '%mm_webout(FETCH)'; put 'This will read all the input data and create same-named SAS datasets in the'; put 'WORK library. You can then insert your code, and send data back using the'; put 'following syntax:'; put 'data some datasets; * make some data ;'; put 'retain some columns;'; put 'run;'; put '%mm_webout(OPEN)'; put '%mm_webout(ARR,some) * Array format, fast, suitable for large tables ;'; put '%mm_webout(OBJ,datasets) * Object format, easier to work with ;'; put 'Finally, wrap everything up send some helpful system variables too'; put '%mm_webout(CLOSE)'; put '@param [in] action Either FETCH, OPEN, ARR, OBJ or CLOSE'; put '@param [in] ds The dataset to send back to the frontend'; put '@param [out] dslabel= Value to use instead of table name for sending to JSON'; put '@param [in] fmt= (N) Setting Y converts all vars to their formatted values'; put '@param [out] fref= (_webout) The fileref to which to write the JSON'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] workobs= (0) When set to a positive integer, will create a new'; put 'output object (WORK) which contains this number of observations from all'; put 'tables in the WORK library.'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to output JSON'; put '

SAS Macros

'; put '@li mp_jsonout.sas'; put '

Related Macros

'; put '@li ms_webout.sas'; put '@li mv_webout.sas'; put '@version 9.3'; put '@author Allan Bowe'; put '**/'; put '%macro mm_webout(action,ds,dslabel=,fref=_webout,fmt=N,missing=NULL'; put ',showmeta=N,maxobs=MAX,workobs=0'; put ');'; put '%global _webin_file_count _webin_fileref1 _webin_name1 _program _debug'; put 'sasjs_tables;'; put '%local i tempds jsonengine;'; put '/* see https://github.com/sasjs/core/issues/41 */'; put '%if "%upcase(&SYSENCODING)" ne "UTF-8" %then %let jsonengine=PROCJSON;'; put '%else %let jsonengine=DATASTEP;'; put '%if &action=FETCH %then %do;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'options mprint notes mprintnest;'; put '%end;'; put '%let _webin_file_count=%eval(&_webin_file_count+0);'; put '/* now read in the data */'; put '%do i=1 %to &_webin_file_count;'; put '%if &_webin_file_count=1 %then %do;'; put '%let _webin_fileref1=&_webin_fileref;'; put '%let _webin_name1=&_webin_name;'; put '%end;'; put 'data _null_;'; put 'infile &&_webin_fileref&i termstr=crlf;'; put 'input;'; put 'call symputx(''input_statement'',_infile_);'; put 'putlog "&&_webin_name&i input statement: " _infile_;'; put 'stop;'; put 'data &&_webin_name&i;'; put 'infile &&_webin_fileref&i firstobs=2 dsd termstr=crlf encoding=''utf-8'';'; put 'input &input_statement;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'if _n_<20 then putlog _infile_;'; put '%end;'; put 'run;'; put '%let sasjs_tables=&sasjs_tables &&_webin_name&i;'; put '%end;'; put '%end;'; put '%else %if &action=OPEN %then %do;'; put '/* fix encoding */'; put 'OPTIONS NOBOMFILE;'; put '/**'; put '* check xengine type to avoid the below err message:'; put '* > Function is only valid for filerefs using the CACHE access method.'; put '*/'; put 'data _null_;'; put 'set sashelp.vextfl(where=(fileref="_WEBOUT"));'; put 'if xengine=''STREAM'' then do;'; put 'rc=stpsrv_header(''Content-type'',"text/html; encoding=utf-8");'; put 'end;'; put 'run;'; put '/* setup json */'; put 'data _null_;file &fref encoding=''utf-8'';'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutBEGIN<<'';'; put '%end;'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'run;'; put '%end;'; put '%else %if &action=ARR or &action=OBJ %then %do;'; put '%mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt,jref=&fref'; put ',engine=&jsonengine,missing=&missing,showmeta=&showmeta,maxobs=&maxobs'; put ')'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put '/* To avoid issues with _webout on EBI we use a temporary file */'; put 'filename _sjsref temp lrecl=131068;'; put '%if %str(&workobs) > 0 %then %do;'; put '/* if debug mode, send back first XX records of each work table also */'; put 'data;run;%let tempds=%scan(&syslast,2,.);'; put 'ods output Members=&tempds;'; put 'proc datasets library=WORK memtype=data;'; put '%local wtcnt;%let wtcnt=0;'; put 'data _null_;'; put 'set &tempds;'; put 'if not (upcase(name) =:"DATA"); /* ignore temp datasets */'; put 'i+1;'; put 'call symputx(cats(''wt'',i),name,''l'');'; put 'call symputx(''wtcnt'',i,''l'');'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put ",""WORK"":{";'; put '%do i=1 %to &wtcnt;'; put '%let wt=&&wt&i;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'dsid=open("WORK.&wt",''is'');'; put 'nlobs=attrn(dsid,''NLOBS'');'; put 'nvars=attrn(dsid,''NVARS'');'; put 'rc=close(dsid);'; put 'if &i>1 then put '',''@;'; put 'put " ""&wt"" : {";'; put 'put ''"nlobs":'' nlobs;'; put 'put '',"nvars":'' nvars;'; put '%mp_jsonout(OBJ,&wt,jref=_sjsref,dslabel=first10rows,showmeta=Y'; put ',maxobs=&workobs'; put ')'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put '%end;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put 'run;'; put '%end;'; put '/* close off json */'; put 'data _null_;file _sjsref mod encoding=''utf-8'';'; put 'length SYSPROCESSNAME syserrortext syswarningtext autoexec $512;'; put 'put ",""_DEBUG"" : ""&_debug"" ";'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'autoexec=quote(urlencode(trim(getoption(''autoexec''))));'; put 'put '',"AUTOEXEC" : '' autoexec;'; put 'put ",""MF_GETUSER"" : ""%mf_getuser()"" ";'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'put ",""SYSENCODING"" : ""&sysencoding"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSPROCESSID"" : ""&SYSPROCESSID"" ";'; put 'put ",""SYSPROCESSMODE"" : ""&SYSPROCESSMODE"" ";'; put 'SYSPROCESSNAME=quote(urlencode(cats(SYSPROCESSNAME)));'; put 'put ",""SYSPROCESSNAME"" : " SYSPROCESSNAME;'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put '',"SYSWARNINGTEXT" : '' syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'length memsize $32;'; put 'memsize="%sysfunc(INPUTN(%sysfunc(getoption(memsize)), best.),sizekmg.)";'; put 'memsize=quote(cats(memsize));'; put 'put '',"MEMSIZE" : '' memsize;'; put 'put "}" @;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutEND<<'';'; put '%end;'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjsref lrecl=1 recfm=n;'; put 'file &fref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjsref clear;'; put '%end;'; put '%mend mm_webout;'; put '%macro webout(action,ds,dslabel=,fmt=,missing=NULL,showmeta=NO,maxobs=MAX);'; put '%mm_webout(&action,ds=&ds,dslabel=&dslabel,fmt=&fmt'; put ',missing=&missing'; put ',showmeta=&showmeta'; put ',maxobs=&maxobs'; put ') %mend;'; put '/* provide additional debug info */'; put '%global _program;'; put '%put &=syscc;'; put '%put user=%mf_getuser();'; put '%put pgm=&_program;'; put '%put timestamp=%sysfunc(datetime(),datetime19.);'; put '* Service Variables start;'; put '* Service Variables end;'; put '* SAS Macros start;'; put '%macro mf_getapploc(pgm);'; put '%if "&pgm"="" %then %do;'; put '%if %symexist(_program) %then %let pgm=&_program;'; put '%else %do;'; put '%put &sysmacroname: No value provided and no _program variable available;'; put '%return;'; put '%end;'; put '%end;'; put '%local root;'; put '/**'; put '* First check we are not in the tests/macros folder (which has no subfolders)'; put '* or specifically in the testsetup or testteardown services'; put '*/'; put '%if %index(&pgm,/tests/macros/)'; put 'or %index(&pgm,/tests/testsetup)'; put 'or %index(&pgm,/tests/testteardown)'; put '%then %do;'; put '%let root=%substr(&pgm,1,%index(&pgm,/tests)-1);'; put '&root'; put '%return;'; put '%end;'; put '/**'; put '* Next, move up two levels to avoid matches on subfolder or service name'; put '*/'; put '%let root=%substr(&pgm,1,%length(&pgm)-%length(%scan(&pgm,-1,/))-1);'; put '%let root=%substr(&root,1,%length(&root)-%length(%scan(&root,-1,/))-1);'; put '%if %index(&root,/tests/) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/tests/)-1);'; put '%end;'; put '%else %if %index(&root,/services) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/services)-1);'; put '%end;'; put '%else %if %index(&root,/jobs) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/jobs)-1);'; put '%end;'; put '%else %put &sysmacroname: Could not find an app location from &pgm;'; put '&root'; put '%mend mf_getapploc ;'; put '%macro mf_getuniquefileref(prefix=_,maxtries=1000,lrecl=32767);'; put '%local rc fname;'; put '%if &prefix=0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%end;'; put '%else %do;'; put '%local x len;'; put '%let len=%eval(8-%length(&prefix));'; put '%let x=0;'; put '%do x=0 %to &maxtries;'; put '%let fname=&prefix%substr(%sysfunc(ranuni(0)),3,&len);'; put '%if %sysfunc(fileref(&fname)) > 0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%return;'; put '%end;'; put '%end;'; put '%put unable to find available fileref after &maxtries attempts;'; put '%end;'; put '%mend mf_getuniquefileref;'; put '%macro mp_abort(mac=mp_abort.sas, type=, msg=, iftrue=%str(1=1)'; put ', errds=work.mp_abort_errds'; put ', mode=REGULAR'; put ')/*/STORE SOURCE*/;'; put '%global sysprocessmode sysprocessname sasjs_stpsrv_header_loc sasjsprocessmode;'; put '%local fref fid i;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%put NOTE: /// mp_abort macro executing //;'; put '%if %length(&mac)>0 %then %put NOTE- called by &mac;'; put '%put NOTE - &msg;'; put '%if %symexist(_SYSINCLUDEFILEDEVICE)'; put '/* abort cancel FILE does not restart outside the INCLUDE on Viya 3.5 */'; put 'and %superq(SYSPROCESSNAME) ne %str(Compute Server)'; put '%then %do;'; put '%if "*&_SYSINCLUDEFILEDEVICE*" ne "**" %then %do;'; put 'data &errds;'; put 'iftrue=''1=1'';'; put 'length mac $100 msg $5000;'; put 'mac=symget(''mac'');'; put 'msg=symget(''msg'');'; put 'run;'; put 'data _null_;'; put 'abort cancel FILE;'; put 'run;'; put '%return;'; put '%end;'; put '%end;'; put '/* Web App Context */'; put '%if %symexist(_PROGRAM)'; put 'or %superq(SYSPROCESSNAME) = %str(Compute Server)'; put 'or &mode=INCLUDE'; put '%then %do;'; put 'options obs=max replace mprint;'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5"'; put '%then %do;'; put 'options nosyntaxcheck;'; put '%end;'; put '%if &mode=INCLUDE %then %do;'; put '%if %sysfunc(exist(&errds))=1 %then %do;'; put 'data _null_;'; put 'set &errds;'; put 'call symputx(''iftrue'',iftrue,''l'');'; put 'call symputx(''mac'',mac,''l'');'; put 'call symputx(''msg'',msg,''l'');'; put 'putlog (_all_)(=);'; put 'run;'; put '%if (&iftrue)=0 %then %return;'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: No include errors found;'; put '%return;'; put '%end;'; put '%end;'; put '/* extract log errs / warns, if exist */'; put '%local logloc logline;'; put '%global logmsg; /* capture global messages */'; put '%if %symexist(SYSPRINTTOLOG) %then %let logloc=&SYSPRINTTOLOG;'; put '%else %let logloc=%qsysfunc(getoption(LOG));'; put 'proc printto log=log;run;'; put '%let logline=0;'; put '%if %length(&logloc)>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input; putlog _infile_;'; put 'i=1;'; put 'retain logonce 0;'; put 'if ('; put '_infile_=:"%str(WARN)ING" or _infile_=:"%str(ERR)OR"'; put ') and logonce=0 then'; put 'do;'; put 'call symputx(''logline'',_n_);'; put 'logonce+1;'; put 'end;'; put 'run;'; put '/* capture log including lines BEFORE the err */'; put '%if &logline>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input;'; put 'i=1;'; put 'stoploop=0;'; put 'if _n_ ge &logline-15 and stoploop=0 then do until (i>22);'; put 'call symputx(''logmsg'',catx(''\n'',symget(''logmsg''),_infile_));'; put 'input;'; put 'i+1;'; put 'stoploop=1;'; put 'end;'; put 'if stoploop=1 then stop;'; put 'run;'; put '%end;'; put '%end;'; put '%if %symexist(SYS_JES_JOB_URI) %then %do;'; put '/* setup webout for Viya */'; put 'options nobomfile;'; put '%if "X&SYS_JES_JOB_URI.X"="XX" %then %do;'; put 'filename _webout temp lrecl=999999 mod;'; put '%end;'; put '%else %do;'; put 'filename _webout filesrvc parenturi="&SYS_JES_JOB_URI"'; put 'name="_webout.json" lrecl=999999 mod;'; put '%end;'; put '%end;'; put '%else %if %sysfunc(filename(fref,&sasjs_stpsrv_header_loc))=0 %then %do;'; put 'options nobomfile;'; put '/* set up http header for SASjs Server */'; put '%let fid=%sysfunc(fopen(&fref,A));'; put '%if &fid=0 %then %do;'; put '%put %str(ERR)OR: %sysfunc(sysmsg());'; put '%return;'; put '%end;'; put '%let rc=%sysfunc(fput(&fid,%str(Content-Type: application/json)));'; put '%let rc=%sysfunc(fwrite(&fid));'; put '%let rc=%sysfunc(fclose(&fid));'; put '%let rc=%sysfunc(filename(&fref));'; put '%end;'; put '/* send response in SASjs JSON format */'; put 'data _null_;'; put 'file _webout mod lrecl=32000 encoding=''utf-8'';'; put 'length msg syswarningtext syserrortext $32767 mode $10 ;'; put 'sasdatetime=datetime();'; put 'msg=symget(''msg'');'; put '%if &logline>0 %then %do;'; put 'msg=cats(msg,''\n\nLog Extract:\n'',symget(''logmsg''));'; put '%end;'; put '/* escape the escapes */'; put 'msg=tranwrd(msg,''\'',''\\'');'; put '/* escape the quotes */'; put 'msg=tranwrd(msg,''"'',''\"'');'; put '/* ditch the CRLFs as chrome complains */'; put 'msg=compress(msg,,''kw'');'; put '/* quote without quoting the quotes (which are escaped instead) */'; put 'msg=cats(''"'',msg,''"'');'; put 'if symexist(''_debug'') then debug=quote(trim(symget(''_debug'')));'; put 'else debug=''""'';'; put 'if symget(''sasjsprocessmode'')=''Stored Program'' then mode=''SASJS'';'; put 'if mode ne ''SASJS'' then put ''>>weboutBEGIN<<'';'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'put '',"sasjsAbort" : [{'';'; put 'put '' "MSG":'' msg ;'; put 'put '' ,"MAC": "'' "&mac" ''"}]'';'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'put '',"_DEBUG":'' debug ;'; put 'if symexist(''_metauser'') then do;'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put 'end;'; put 'if symexist(''SYS_JES_JOB_URI'') then do;'; put 'SYS_JES_JOB_URI=quote(trim(symget(''SYS_JES_JOB_URI'')));'; put 'put '',"SYS_JES_JOB_URI": '' SYS_JES_JOB_URI;'; put 'end;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put ",""SYSWARNINGTEXT"" : " syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'put "}" ;'; put 'if mode ne ''SASJS'' then put ''>>weboutEND<<'';'; put 'run;'; put '%put _all_;'; put '%if "&sysprocessmode " = "SAS Stored Process Server " %then %do;'; put 'data _null_;'; put 'putlog ''stpsrvset program err and syscc'';'; put 'rc=stpsrvset(''program error'', 0);'; put 'call symputx("syscc",0,"g");'; put 'run;'; put '%if &sysscp=WIN'; put 'and 1=0 /* deprecating this logic until we figure out a consistent abort */'; put 'and "%substr(%str(&sysvlong ),1,8)"="9.04.01M"'; put 'and "%substr(%str(&sysvlong ),9,1)">"5" %then %do;'; put '/* skip approach (below) does not work in windows m6+ envs */'; put 'endsas;'; put '%end;'; put '%else %do;'; put '/**'; put '* endsas kills 9.4m3 deployments by orphaning multibridges.'; put '* Abort variants are ungraceful (non zero return code)'; put '* This approach lets SAS run silently until the end :-)'; put '* Caution - fails when called within a %include within a macro'; put '* Use mp_include() to handle this.'; put '*/'; put 'filename skip temp;'; put 'data _null_;'; put 'file skip;'; put 'put ''%macro skip();'';'; put 'comment ''%mend skip; -> fix lint '';'; put 'put ''%macro skippy();'';'; put 'comment ''%mend skippy; -> fix lint '';'; put 'run;'; put '%inc skip;'; put '%end;'; put '%end;'; put '%else %if "&sysprocessmode " = "SAS Compute Server " %then %do;'; put '/* endsas kills the session making it harder to fetch results */'; put 'data _null_;'; put 'syswarningtext=symget(''syswarningtext'');'; put 'syserrortext=symget(''syserrortext'');'; put 'abort_msg=symget(''msg'');'; put 'syscc=symget(''syscc'');'; put 'sysuserid=symget(''sysuserid'');'; put 'iftrue=symget(''iftrue'');'; put 'put (_all_)(/=);'; put 'call symputx(''syscc'',0);'; put 'abort cancel nolist;'; put 'run;'; put '%end;'; put '%else %do;'; put '%abort cancel;'; put '%end;'; put '%end;'; put '%else %do;'; put '%put _all_;'; put '%abort cancel;'; put '%end;'; put '%mend mp_abort;'; put '/** @endcond */'; put '%macro mm_getstpcode('; put 'tree=/User Folders/sasdemo/somestp'; put ',name='; put ',outloc=0'; put ',outref=0'; put ',mDebug=1'; put ',showlog=NO'; put ');'; put '%local mD;'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing &sysmacroname..sas;'; put '%&mD.put _local_;'; put '%if %length(&name)>0 %then %let name=/&name;'; put '/* first, check if STP exists */'; put '%local tsuri;'; put '%let tsuri=stopifempty ;'; put 'data _null_;'; put 'format type uri tsuri value $200.;'; put 'call missing (of _all_);'; put 'path="&tree&name(StoredProcess)";'; put '/* first, find the STP ID */'; put 'if metadata_pathobj("",path,"StoredProcess",type,uri)>0 then do;'; put '/* get sourcecode */'; put 'cnt=1;'; put 'do while (metadata_getnasn(uri,"Notes",cnt,tsuri)>0);'; put 'rc=metadata_getattr(tsuri,"Name",value);'; put '&mD.put tsuri= value=;'; put 'if value="SourceCode" then do;'; put '/* found it! */'; put 'rc=metadata_getattr(tsuri,"Id",value);'; put 'call symputx(''tsuri'',value,''l'');'; put 'stop;'; put 'end;'; put 'cnt+1;'; put 'end;'; put 'end;'; put 'else put (_all_)(=);'; put 'run;'; put '%mp_abort(iftrue= (&tsuri=stopifempty)'; put ',mac=mm_getstpcode'; put ',msg=%str(&tree&name.(StoredProcess) not found!)'; put ')'; put '/**'; put '* Now we can extract the textstore'; put '*/'; put 'filename __getdoc temp lrecl=10000000;'; put 'proc metadata'; put 'in="$METAREPOSITORY'; put ''; put 'SAS1"'; put 'out=__getdoc ;'; put 'run;'; put '/* find the beginning of the text */'; put '%local start;'; put 'data _null_;'; put 'infile __getdoc lrecl=10000;'; put 'input;'; put 'start=index(_infile_,''StoredText="'');'; put 'if start then do;'; put 'call symputx("start",start+11);'; put '*putlog ''"'' _infile_ ''"'';'; put 'end;'; put 'stop;'; put '%local outeng;'; put '%if "&outloc"="0" %then %let outeng=TEMP;'; put '%else %let outeng="&outloc";'; put '%local fref;'; put '%if &outref=0 %then %let fref=%mf_getuniquefileref();'; put '%else %let fref=&outref;'; put '/* read the content, byte by byte, resolving escaped chars */'; put 'filename &fref &outeng lrecl=100000;'; put 'data _null_;'; put 'length filein 8 fileid 8;'; put 'filein = fopen("__getdoc","I",1,"B");'; put 'fileid = fopen("&fref","O",1,"B");'; put 'rec = "20"x;'; put 'length entity $6;'; put 'do while(fread(filein)=0);'; put 'x+1;'; put 'if x>&start then do;'; put 'rc = fget(filein,rec,1);'; put 'if rec=''"'' then leave;'; put 'else if rec="&" then do;'; put 'entity=rec;'; put 'do until (rec=";");'; put 'if fread(filein) ne 0 then goto getout;'; put 'rc = fget(filein,rec,1);'; put 'entity=cats(entity,rec);'; put 'end;'; put 'select (entity);'; put 'when (''&'' ) rec=''&'' ;'; put 'when (''<'' ) rec=''<'' ;'; put 'when (''>'' ) rec=''>'' ;'; put 'when (''''') rec="''" ;'; put 'when (''"'') rec=''"'' ;'; put 'when ('' '') rec=''0A''x;'; put 'when ('' '') rec=''0D''x;'; put 'when (''$'' ) rec=''$'' ;'; put 'when ('' '') rec=''09''x;'; put 'otherwise putlog "%str(WARN)ING: missing value for " entity=;'; put 'end;'; put 'rc =fput(fileid, substr(rec,1,1));'; put 'rc =fwrite(fileid);'; put 'end;'; put 'else do;'; put 'rc =fput(fileid,rec);'; put 'rc =fwrite(fileid);'; put 'end;'; put 'end;'; put 'end;'; put 'getout:'; put 'rc=fclose(filein);'; put 'rc=fclose(fileid);'; put 'run;'; put '%if &showlog=YES %then %do;'; put 'data _null_;'; put 'infile &fref lrecl=32767 end=last;'; put 'input;'; put 'if _n_=1 then putlog ''>>stpcodeBEGIN<<'';'; put 'putlog _infile_;'; put 'if last then putlog ''>>stpcodeEND<<'';'; put 'run;'; put '%end;'; put 'filename __getdoc clear;'; put '%if &outref=0 %then %do;'; put 'filename &fref clear;'; put '%end;'; put '%mend mm_getstpcode;'; put '%macro dc_getsettings();'; put '%global _program;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(syscc=&syscc on entry (&syswarningtext &syserrortext))'; put ')'; put '%if %length(&_PROGRAM)>1 %then %let root=&_program;'; put '%else %do;'; put '%global _metauser;'; put '%let _metauser=&sysuserid;'; put '/* to mimic a "real" _program we need to give a dummy role and stp name */'; put '%let root=/dummyRole/dummyName;'; put '%end;'; put '/* the DC precode is stored in the Admin folder in the root of'; put 'the project. Lets find that root. */'; put '%put &=root;'; put '%let root=%mf_getapploc();'; put '%put &=root;'; put '/* Now we know the root location we can retrieve the params */'; put '%let temploc=%sysfunc(pathname(work))/settings.txt;'; put '%mm_getstpcode(tree=&root/services/public'; put ',name=Data_Controller_Settings'; put ',outloc=&temploc'; put ')'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Unable to run getstpcode)'; put ')'; put 'filename _getsets "&temploc" lrecl=2000;'; put '/*'; put 'Do not use mp_include here - this puts a copy in every service, which creates'; put 'compilation problems when calling services from mp_include'; put '*/'; put '%inc _getsets/source2;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Problem running &sysmacroname (&syswarningtext &syserrortext))'; put ')'; put '%mend dc_getsettings;'; put '%macro mf_fmtdttm('; put ')/*/STORE SOURCE*/;'; put '%if "&sysver"="9.2" or "&sysver"="9.3"'; put 'or ("&sysver"="9.4" and "%substr(&SYSVLONG,9,1)" le "3")'; put 'or "%substr(&sysver,1,1)"="4"'; put 'or "%substr(&sysver,1,1)"="5"'; put '%then %do;DATETIME19.3%end;'; put '%else %do;E8601DT26.6%end;'; put '%mend mf_fmtdttm;'; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '%macro mp_init(prefix=SASJS'; put ')/*/STORE SOURCE*/;'; put '%if %symexist(SASJS_PREFIX) %then %return; /* only run once */'; put '%global'; put 'SASJS_PREFIX /* the ONLY hard-coded global macro variable in SASjs */'; put '&prefix._FUNCTIONS /* used in mcf_init() to track core function compilation */'; put '&prefix._INIT_NUM /* initialisation time as numeric */'; put '&prefix._INIT_DTTM /* initialisation time in E8601DT26.6 format */'; put '&prefix.WORK /* avoid typing %sysfunc(pathname(work)) every time */'; put ';'; put '%let sasjs_prefix=&prefix;'; put 'data _null_;'; put 'dttm=datetime();'; put 'call symputx("&sasjs_prefix._init_num",dttm,''g'');'; put 'call symputx("&sasjs_prefix._init_dttm",put(dttm,E8601DT26.6),''g'');'; put 'call symputx("&sasjs_prefix.work",pathname(''WORK''),''g'');'; put 'run;'; put 'options'; put 'compress=CHAR /* default is none so ensure we have something! */'; put 'datastmtchk=ALLKEYWORDS /* protection from overwriting input datasets */'; put 'errorcheck=STRICT /* catch errs in libname/filename statements */'; put 'fmterr /* ensure err when a format cannot be found */'; put 'mergenoby=%str(ERR)OR /* throw err when a merge has no BY variables */'; put 'missing=. /* changing this can cause hard to detect errs */'; put 'noquotelenmax /* avoid warnings for long strings */'; put 'noreplace /* avoid overwriting permanent datasets */'; put 'ps=max /* reduce log size slightly */'; put 'ls=max /* reduce log even more and avoid word truncation */'; put 'validmemname=COMPATIBLE /* avoid special characters etc in table names */'; put 'validvarname=V7 /* avoid special characters etc in variable names */'; put 'varinitchk=%str(ERR)OR /* avoid data mistakes from variable name typos */'; put 'varlenchk=%str(ERR)OR /* fail hard if truncation (data loss) can result */'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5" %then %do;'; put 'noautocorrect /* disallow misspelled procedure names */'; put 'dsoptions=note2err /* undocumented - convert bad NOTEs to ERRs */'; put '%end;'; put ';'; put '%mend mp_init;'; put '%macro mpeinit(fetch=YES);'; put '%global mpeinit'; put 'mpeadmins /* group with unrestricted Meditor access */'; put 'mpelocapprovals /* location for landing and staging files */'; put 'mpelib /* location of configuration tables for DC */'; put 'dc_repo_users /* location of user / group metadata */'; put 'dc_licence_key /* extracted in dc_getsettings */'; put 'dc_activation_key /* extracted in dc_getsettings */'; put 'dc_locale /* extracted in dc_getsettings */'; put 'dc_dttmtfmt /* can be overridden in dc_getsettings */'; put '_debug'; put ';'; put '%if &mpeinit=1 %then %return;'; put '%else %let mpeinit=1;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(Problem on service startup (&syswarningtext &syserrortext))'; put ')'; put '%mp_init()'; put '%if &fetch=YES %then %do;'; put '%webout(FETCH)'; put '%end;'; put '%global _CLIENTNAME;'; put '%mp_abort(iftrue= (&_CLIENTNAME=SAS Enterprise Guide)'; put ',mac=&_program..sas'; put ',msg=%str(Data Controller is a web app and should not be executed from EG)'; put ')'; put 'options urlencoding=utf8 nobomfile lrecl=32767;'; put '%let perf=%sysfunc(datetime());'; put '%put perfdiff: 0;'; put '%let dc_locale=SYSTEM; /* default if not set */'; put '/**'; put '* E8601DT26.6 has widest database support - but not all SAS flavours can'; put '* handle it. Override in the settings STP if needed.'; put '*/'; put 'data _null_;'; put 'dc_dttmtfmt=''"%sysfunc(datetime(),''!!"%mf_fmtdttm()"!!'')"dt'';'; put 'call symputx(''dc_dttmtfmt'',dc_dttmtfmt);'; put 'put dc_dttmtfmt=;'; put 'run;'; put '%put &=dc_dttmtfmt;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc prior to dc_getsettings)'; put ')'; put '%dc_getsettings()'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc after dc_getsettings)'; put ')'; put 'data _null_;'; put 'set &DC_LIBREF..mpe_config(where=('; put 'var_scope="DC"'; put 'and &dc_dttmtfmt lt tx_to'; put 'and var_active=1'; put '));'; put 'call symputx(var_name,var_value,''G'');'; put 'putlog var_name "=" var_value;'; put 'run;'; put '%let mpelib=&dc_libref;'; put '%let mpeadmins=&dc_admin_group;'; put '%let mpelocapprovals=&dc_staging_area;'; put '%let dc_repo_users=&dc_repo_users;'; put '%if &dc_locale ne SYSTEM %then %do;'; put 'options locale=&dc_locale;'; put '%end;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(Problem during compilation or with STP precode (&syswarningtext))'; put ')'; put '%mend mpeinit;'; put '%macro mf_mval(var);'; put '%if %symexist(&var) %then %do;'; put '%superq(&var)'; put '%end;'; put '%mend mf_mval;'; put '%macro mf_trimstr(basestr,trimstr);'; put '%local baselen trimlen trimval;'; put '/* return if basestr is shorter than trimstr (or 0) */'; put '%let baselen=%length(%superq(basestr));'; put '%let trimlen=%length(%superq(trimstr));'; put '%if &baselen < &trimlen or &baselen=0 %then %return;'; put '/* obtain the characters from the end of basestr */'; put '%let trimval=%qsubstr(%superq(basestr)'; put ',%length(%superq(basestr))-&trimlen+1'; put ',&trimlen);'; put '/* compare and if matching, chop it off! */'; put '%if %superq(basestr)=%superq(trimstr) %then %do;'; put '%return;'; put '%end;'; put '%else %if %superq(trimval)=%superq(trimstr) %then %do;'; put '%qsubstr(%superq(basestr),1,%length(%superq(basestr))-&trimlen)'; put '%end;'; put '%else %do;'; put '&basestr'; put '%end;'; put '%mend mf_trimstr;'; put '%macro mf_getplatform(switch'; put ')/*/STORE SOURCE*/;'; put '%local a b c;'; put '%if &switch.NONE=NONE %then %do;'; put '%if %symexist(sasjsprocessmode) %then %do;'; put '%if &sasjsprocessmode=Stored Program %then %do;'; put 'SASJS'; put '%return;'; put '%end;'; put '%end;'; put '%if %symexist(sysprocessmode) %then %do;'; put '%if "&sysprocessmode"="SAS Object Server"'; put 'or "&sysprocessmode"= "SAS Compute Server" %then %do;'; put 'SASVIYA'; put '%end;'; put '%else %if "&sysprocessmode"="SAS Stored Process Server"'; put 'or "&sysprocessmode"="SAS Workspace Server"'; put '%then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if %symexist(_metaport) or %symexist(_metauser) %then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if &switch=SASSTUDIO %then %do;'; put '/* return the version of SAS Studio else 0 */'; put '%if %mf_mval(_CLIENTAPP)=%str(SAS Studio) %then %do;'; put '%let a=%mf_mval(_CLIENTVERSION);'; put '%let b=%scan(&a,1,.);'; put '%if %eval(&b >2) %then %do;'; put '&b'; put '%end;'; put '%else 0;'; put '%end;'; put '%else 0;'; put '%end;'; put '%else %if &switch=VIYARESTAPI %then %do;'; put '%mf_trimstr(%sysfunc(getoption(servicesbaseurl)),/)'; put '%end;'; put '%mend mf_getplatform;'; put '%macro mpeterm();'; put '%local oldloc;'; put 'data _null_;'; put 'if symexist(''SYSPRINTTOLOG'') then oldloc=symget(''SYSPRINTTOLOG'');'; put 'else oldloc=getoption(''LOG'');'; put 'if subpad(oldloc,1,1) not in (''"'',"''",'' '') then oldloc=quote(cats(oldloc));'; put 'call symputx(''oldloc'',oldloc,''l'');'; put 'run;'; put '%if %length(&oldloc)>0 %then %do;'; put 'proc printto log=log;'; put 'run;'; put 'data _null_;'; put 'infile &oldloc;'; put 'input; putlog _infile_;'; put 'run;'; put '%end;'; put '%if %sysfunc(exist(&dc_libref..mpe_requests)) and %mf_getplatform() ne SASVIYA'; put '%then %do;'; put 'data ;'; put 'if 0 then set &dc_libref..mpe_requests;'; put 'request_dttm=%sysfunc(datetime());'; put 'request_user="%mf_getuser()";'; put 'request_service="%scan(&_program,-2,/)/%scan(&_program,-1,/)";'; put 'request_params='''';'; put 'output;stop;'; put 'proc append base=&dc_libref..mpe_requests data=&syslast force nowarn;'; put 'run;'; put '%end;'; put '%mend mpeterm;'; put '%macro mm_assignlib('; put 'libref'; put ',mAbort=HARD'; put ')/*/STORE SOURCE*/;'; put '%local mp_abort msg;'; put '%let mp_abort=0;'; put '%if %sysfunc(libref(&libref)) %then %do;'; put 'data _null_;'; put 'length liburi LibName msg $200;'; put 'call missing(of _all_);'; put 'nobj=metadata_getnobj("omsobj:SASLibrary?@Libref=''&libref''",1,liburi);'; put 'if nobj=1 then do;'; put 'rc=metadata_getattr(liburi,"Name",LibName);'; put '/* now try and assign it */'; put 'if libname("&libref",,''meta'',cats(''liburi="'',liburi,''";'')) ne 0 then do;'; put 'putlog "&libref could not be assigned";'; put 'putlog liburi=;'; put '/**'; put '* Fetch the system message for display in the abort modal. This is'; put '* not always helpful though. One example, previously received:'; put '* NOTE: Libref XX refers to the same library metadata as libref XX.'; put '*/'; put 'msg=sysmsg();'; put 'if msg=:''ERROR: Libref SAVE is not assigned.'' then do;'; put 'msg=catx(" ",'; put '"Could not assign %upcase(&libref).",'; put '"Please check metadata permissions! Libname:",libname,'; put '"Liburi:",liburi'; put ');'; put 'end;'; put 'else if msg="ERROR: User does not have appropriate authorization "!!'; put '"level for library SAVE."'; put 'then do;'; put 'msg=catx(" ",'; put '"ERROR: User does not have appropriate authorization level",'; put '"for library %upcase(&libref), libname:",libname,'; put '"Liburi:",liburi'; put ');'; put 'end;'; put 'call symputx(''msg'',msg,''l'');'; put 'if "&mabort"=''HARD'' then call symputx(''mp_abort'',1,''l'');'; put 'end;'; put 'else do;'; put 'put (_all_)(=);'; put 'call symputx(''libname'',libname,''L'');'; put 'call symputx(''liburi'',liburi,''L'');'; put 'end;'; put 'end;'; put 'else if nobj>1 then do;'; put 'if "&mabort"=''HARD'' then call symputx(''mp_abort'',1);'; put 'call symputx(''msg'',"More than one library with libref=&libref");'; put 'end;'; put 'else do;'; put 'if "&mabort"=''HARD'' then call symputx(''mp_abort'',1);'; put 'call symputx(''msg'',"Library &libref not found in metadata");'; put 'end;'; put 'run;'; put '%put NOTE: &msg;'; put '%end;'; put '%else %do;'; put '%put NOTE: Library &libref is already assigned;'; put '%end;'; put '%mp_abort(iftrue= (&mp_abort=1)'; put ',mac=mm_assignlib.sas'; put ',msg=%superq(msg)'; put ')'; put '%mend mm_assignlib;'; put '/** @cond */'; put '%macro mf_getengine(libref'; put ')/*/STORE SOURCE*/;'; put '%local dsid engnum rc engine;'; put '/* in case the parameter is a libref.tablename, pull off just the libref */'; put '%let libref = %upcase(%scan(&libref, 1, %str(.)));'; put '%let dsid=%sysfunc('; put 'open(sashelp.vlibnam(where=(libname="%upcase(&libref)")),i)'; put ');'; put '%if (&dsid ^= 0) %then %do;'; put '%let engnum=%sysfunc(varnum(&dsid,ENGINE));'; put '%let rc=%sysfunc(fetch(&dsid));'; put '%let engine=%sysfunc(getvarc(&dsid,&engnum));'; put '%put &libref. ENGINE is &engine.;'; put '%let rc= %sysfunc(close(&dsid));'; put '%end;'; put '%upcase(&engine)'; put '%mend mf_getengine;'; put '/** @endcond */'; put '%macro mm_assigndirectlib('; put 'libref'; put ',open_passthrough='; put ',sql_options='; put ',mDebug=0'; put ',mAbort=0'; put ')/*/STORE SOURCE*/;'; put '%local mD;'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing mm_assigndirectlib.sas;'; put '%&mD.put _local_;'; put '%if &mAbort=1 %then %let mAbort=;'; put '%else %let mAbort=%str(*);'; put '%&mD.put NOTE: Creating direct (non META) connection to &libref library;'; put '%local cur_engine;'; put '%let cur_engine=%mf_getengine(&libref);'; put '%if &cur_engine ne META and &cur_engine ne %then %do;'; put '%put NOTE: &libref already has a direct (&cur_engine) libname connection;'; put '%return;'; put '%end;'; put '%else %if %upcase(&libref)=WORK %then %do;'; put '%put NOTE: We already have a direct connection to WORK :-) ;'; put '%return;'; put '%end;'; put '/* need to determine the library ENGINE first */'; put '%local engine;'; put 'data _null_;'; put 'length lib_uri engine $256;'; put 'call missing (of _all_);'; put '/* get URI for the particular library */'; put 'rc1=metadata_getnobj("omsobj:SASLibrary?@Libref =''&libref''",1,lib_uri);'; put '/* get the Engine attribute of the previous object */'; put 'rc2=metadata_getattr(lib_uri,''Engine'',engine);'; put 'putlog "mm_assigndirectlib for &libref:" rc1= lib_uri= rc2= engine=;'; put 'call symputx("liburi",lib_uri,''l'');'; put 'call symputx("engine",engine,''l'');'; put 'run;'; put '/* now obtain engine specific connection details */'; put '%if &engine=BASE %then %do;'; put '%&mD.put NOTE: Retrieving BASE library path;'; put 'data _null_;'; put 'length up_uri $256 path cat_path $1024;'; put 'retain cat_path;'; put 'call missing (of _all_);'; put '/* get all the filepaths of the UsingPackages association */'; put 'i=1;'; put 'rc3=metadata_getnasn("&liburi",''UsingPackages'',i,up_uri);'; put 'do while (rc3>0);'; put '/* get the DirectoryName attribute of the previous object */'; put 'rc4=metadata_getattr(up_uri,''DirectoryName'',path);'; put 'if i=1 then path = ''("''!!trim(path)!!''" '';'; put 'else path ='' "''!!trim(path)!!''" '';'; put 'cat_path = trim(cat_path) !! " " !! trim(path) ;'; put 'i+1;'; put 'rc3=metadata_getnasn("&liburi",''UsingPackages'',i,up_uri);'; put 'end;'; put 'cat_path = trim(cat_path) !! ")";'; put '&mD.putlog "NOTE: Getting physical path for &libref library";'; put '&mD.putlog rc3= up_uri= rc4= cat_path= path=;'; put '&mD.putlog "NOTE: Libname cmd will be:";'; put '&mD.putlog "libname &libref" cat_path;'; put 'call symputx("filepath",cat_path,''l'');'; put 'run;'; put '%if %sysevalf(&sysver<9.4) %then %do;'; put 'libname &libref &filepath;'; put '%end;'; put '%else %do;'; put '/* apply the new filelocks option to cater for temporary locks */'; put 'libname &libref &filepath filelockwait=5;'; put '%end;'; put '%end;'; put '%else %if &engine=REMOTE %then %do;'; put 'data x;'; put 'length rcCon rcProp rc k 3 uriCon uriProp PropertyValue PropertyName'; put 'Delimiter $256 properties $2048;'; put 'retain properties;'; put 'rcCon = metadata_getnasn("&liburi", "LibraryConnection", 1, uriCon);'; put 'rcProp = metadata_getnasn(uriCon, "Properties", 1, uriProp);'; put 'k = 1;'; put 'rcProp = metadata_getnasn(uriCon, "Properties", k, uriProp);'; put 'do while (rcProp > 0);'; put 'rc = metadata_getattr(uriProp , "DefaultValue",PropertyValue);'; put 'rc = metadata_getattr(uriProp , "PropertyName",PropertyName);'; put 'rc = metadata_getattr(uriProp , "Delimiter",Delimiter);'; put 'properties = trim(properties) !! " " !! trim(PropertyName)'; put '!! trim(Delimiter) !! trim(PropertyValue);'; put 'output;'; put 'k+1;'; put 'rcProp = metadata_getnasn(uriCon, "Properties", k, uriProp);'; put 'end;'; put '%&mD.put NOTE: Getting properties for REMOTE SHARE &libref library;'; put '&mD.put _all_;'; put '%&mD.put NOTE: Libname cmd will be:;'; put '%&mD.put libname &libref &engine &properties slibref=&libref;'; put 'call symputx ("properties",trim(properties),''l'');'; put 'run;'; put 'libname &libref &engine &properties slibref=&libref;'; put '%end;'; put '%else %if &engine=OLEDB %then %do;'; put '%&mD.put NOTE: Retrieving OLEDB connection details;'; put 'data _null_;'; put 'length domain datasource provider properties schema'; put 'connx_uri domain_uri conprop_uri lib_uri schema_uri value $256.;'; put 'call missing (of _all_);'; put '/* get source connection ID */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,connx_uri);'; put '/* get connection domain */'; put 'rc1=metadata_getnasn(connx_uri,''Domain'',1,domain_uri);'; put 'rc2=metadata_getattr(domain_uri,''Name'',domain);'; put '&mD.putlog / ''NOTE: '' // ''NOTE- connection id: '' connx_uri ;'; put '&mD.putlog ''NOTE- domain: '' domain;'; put '/* get DSN and PROVIDER from connection properties */'; put 'i=0;'; put 'do until (rc<0);'; put 'i+1;'; put 'rc=metadata_getnasn(connx_uri,''Properties'',i,conprop_uri);'; put 'rc2=metadata_getattr(conprop_uri,''Name'',value);'; put 'if value=''Connection.OLE.Property.DATASOURCE.Name.xmlKey.txt'' then do;'; put 'rc3=metadata_getattr(conprop_uri,''DefaultValue'',datasource);'; put 'end;'; put 'else if value=''Connection.OLE.Property.PROVIDER.Name.xmlKey.txt'' then do;'; put 'rc4=metadata_getattr(conprop_uri,''DefaultValue'',provider);'; put 'end;'; put 'else if value=''Connection.OLE.Property.PROPERTIES.Name.xmlKey.txt'' then'; put 'do;'; put 'rc5=metadata_getattr(conprop_uri,''DefaultValue'',properties);'; put 'end;'; put 'end;'; put '&mD.putlog ''NOTE- dsn/provider/properties: '' /'; put 'datasource provider properties;'; put '&mD.putlog ''NOTE- schema: '' schema // ''NOTE-'';'; put '/* get SCHEMA */'; put 'rc6=metadata_getnasn("&liburi",''UsingPackages'',1,lib_uri);'; put 'rc7=metadata_getattr(lib_uri,''SchemaName'',schema);'; put 'call symputx(''SQL_domain'',domain,''l'');'; put 'call symputx(''SQL_dsn'',datasource,''l'');'; put 'call symputx(''SQL_provider'',provider,''l'');'; put 'call symputx(''SQL_properties'',properties,''l'');'; put 'call symputx(''SQL_schema'',schema,''l'');'; put 'run;'; put '%if %length(&open_passthrough)>0 %then %do;'; put 'proc sql &sql_options;'; put 'connect to OLEDB as &open_passthrough(INSERT_SQL=YES'; put '/* need additional properties to make this work */'; put 'properties=(''Integrated Security''=SSPI'; put '''Persist Security Info''=True'; put '%sysfunc(compress(%str(&SQL_properties),%str(())))'; put ')'; put 'DATASOURCE=&sql_dsn PROMPT=NO'; put 'PROVIDER=&sql_provider SCHEMA=&sql_schema CONNECTION = GLOBAL);'; put '%end;'; put '%else %do;'; put 'LIBNAME &libref OLEDB PROPERTIES=&sql_properties'; put 'DATASOURCE=&sql_dsn PROVIDER=&sql_provider SCHEMA=&sql_schema'; put '%if %length(&sql_domain)>0 %then %do;'; put 'authdomain="&sql_domain"'; put '%end;'; put 'connection=shared;'; put '%end;'; put '%end;'; put '%else %if &engine=ODBC %then %do;'; put '%&mD.put NOTE: Retrieving ODBC connection details;'; put 'data _null_;'; put 'length connx_uri conprop_uri value datasource up_uri schema domprop_uri authdomain $256.;'; put 'call missing (of _all_);'; put '/* get source connection ID */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,connx_uri);'; put '/* get connection properties */'; put 'i=0;'; put 'do until (rc2<0);'; put 'i+1;'; put 'rc2=metadata_getnasn(connx_uri,''Properties'',i,conprop_uri);'; put 'rc3=metadata_getattr(conprop_uri,''Name'',value);'; put 'if value=''Connection.ODBC.Property.DATASRC.Name.xmlKey.txt'' then do;'; put 'rc4=metadata_getattr(conprop_uri,''DefaultValue'',datasource);'; put 'rc2=-1;'; put 'end;'; put 'end;'; put '/* get auth domain */'; put 'autrc=metadata_getnasn(connx_uri,"Domain",1,domprop_uri);'; put 'arc=metadata_getattr(domprop_uri,"Name",authdomain);'; put 'if not missing(authdomain) then authdomain=cats(''AUTHDOMAIN='',authdomain);'; put 'call symputx(''authdomain'',authdomain,''l'');'; put '/* get SCHEMA */'; put 'rc6=metadata_getnasn("&liburi",''UsingPackages'',1,up_uri);'; put 'rc7=metadata_getattr(up_uri,''SchemaName'',schema);'; put '&mD.put rc= connx_uri= rc2= conprop_uri= rc3= value= rc4= datasource='; put 'rc6= up_uri= rc7= schema=;'; put 'call symputx(''SQL_schema'',schema,''l'');'; put 'call symputx(''SQL_dsn'',datasource,''l'');'; put 'run;'; put '%if %length(&open_passthrough)>0 %then %do;'; put 'proc sql &sql_options;'; put 'connect to ODBC as &open_passthrough'; put '(INSERT_SQL=YES DATASRC=&sql_dsn. CONNECTION=global);'; put '%end;'; put '%else %do;'; put 'libname &libref ODBC DATASRC=&sql_dsn SCHEMA=&sql_schema &authdomain;'; put '%end;'; put '%end;'; put '%else %if &engine=POSTGRES %then %do;'; put '%put NOTE: Obtaining POSTGRES library details;'; put 'data _null_;'; put 'length database ignore_read_only_columns direct_exe preserve_col_names'; put 'preserve_tab_names server schema authdomain user password'; put 'prop name value uri urisrc $256.;'; put 'call missing (of _all_);'; put '/* get database value */'; put 'prop=''Connection.DBMS.Property.DB.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,database,"");'; put 'if database^='''' then database=''database=''!!quote(trim(database));'; put 'call symputx(''database'',database,''l'');'; put '/* get IGNORE_READ_ONLY_COLUMNS value */'; put 'prop=''Library.DBMS.Property.DBIROC.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,ignore_read_only_columns,"");'; put 'if ignore_read_only_columns^='''' then ignore_read_only_columns='; put '''ignore_read_only_columns=''!!ignore_read_only_columns;'; put 'call symputx(''ignore_read_only_columns'',ignore_read_only_columns,''l'');'; put '/* get DIRECT_EXE value */'; put 'prop=''Library.DBMS.Property.DirectExe.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,direct_exe,"");'; put 'if direct_exe^='''' then direct_exe=''direct_exe=''!!direct_exe;'; put 'call symputx(''direct_exe'',direct_exe,''l'');'; put '/* get PRESERVE_COL_NAMES value */'; put 'prop=''Library.DBMS.Property.PreserveColNames.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,preserve_col_names,"");'; put 'if preserve_col_names^='''' then preserve_col_names='; put '''preserve_col_names=''!!preserve_col_names;'; put 'call symputx(''preserve_col_names'',preserve_col_names,''l'');'; put '/* get PRESERVE_TAB_NAMES value */'; put '/* be careful with PRESERVE_TAB_NAMES=YES - it will mean your table will'; put 'become case sensitive!! */'; put 'prop=''Library.DBMS.Property.PreserveTabNames.Name.xmlKey.txt'';'; put 'rc=metadata_getprop("&liburi",prop,preserve_tab_names,"");'; put 'if preserve_tab_names^='''' then preserve_tab_names='; put '''preserve_tab_names=''!!preserve_tab_names;'; put 'call symputx(''preserve_tab_names'',preserve_tab_names,''l'');'; put '/* get SERVER value */'; put 'if metadata_getnasn("&liburi","LibraryConnection",1,uri)>0 then do;'; put 'prop=''Connection.DBMS.Property.SERVER.Name.xmlKey.txt'';'; put 'rc=metadata_getprop(uri,prop,server,"");'; put 'end;'; put 'if server^='''' then server=''server=''!!quote(cats(server));'; put 'call symputx(''server'',server,''l'');'; put '/* get SCHEMA value */'; put 'if metadata_getnasn("&liburi","UsingPackages",1,uri)>0 then do;'; put 'rc=metadata_getattr(uri,"SchemaName",schema);'; put 'end;'; put 'if schema^='''' then schema=''schema=''!!schema;'; put 'call symputx(''schema'',schema,''l'');'; put '/* get AUTHDOMAIN value */'; put '/* this is only useful if the user account contains that auth domain'; put 'if metadata_getnasn("&liburi","DefaultLogin",1,uri)>0 then do;'; put 'rc=metadata_getnasn(uri,"Domain",1,urisrc);'; put 'rc=metadata_getattr(urisrc,"Name",authdomain);'; put 'end;'; put 'if authdomain^='''' then authdomain=''authdomain=''!!quote(trim(authdomain));'; put '*/'; put 'call symputx(''authdomain'',authdomain,''l'');'; put '/* get user & pass */'; put 'if authdomain='''' & metadata_getnasn("&liburi","DefaultLogin",1,uri)>0 then'; put 'do;'; put 'rc=metadata_getattr(uri,"UserID",user);'; put 'rc=metadata_getattr(uri,"Password",password);'; put 'end;'; put 'if user^='''' then do;'; put 'user=''user=''!!quote(trim(user));'; put 'password=''password=''!!quote(trim(password));'; put 'end;'; put 'call symputx(''user'',user,''l'');'; put 'call symputx(''password'',password,''l'');'; put '&md.put _all_;'; put 'run;'; put '%if %length(&open_passthrough)>0 %then %do;'; put '%put %str(WARN)ING: Passthrough option for postgres not yet supported;'; put '%return;'; put '%end;'; put '%else %do;'; put '%if &mdebug=1 %then %do;'; put '%put NOTE: Executing the following:/;'; put '%put NOTE- libname &libref POSTGRES &database &ignore_read_only_columns;'; put '%put NOTE- &direct_exe &preserve_col_names &preserve_tab_names;'; put '%put NOTE- &server &schema &authdomain &user &password //;'; put '%end;'; put 'libname &libref POSTGRES &database &ignore_read_only_columns &direct_exe'; put '&preserve_col_names &preserve_tab_names &server &schema &authdomain'; put '&user &password;'; put '%end;'; put '%end;'; put '%else %if &engine=ORACLE %then %do;'; put '%put NOTE: Obtaining &engine library details;'; put 'data _null_;'; put 'length assocuri1 assocuri2 assocuri3 authdomain path schema $256;'; put 'call missing (of _all_);'; put '/* get auth domain */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,assocuri1);'; put 'rc=metadata_getnasn(assocuri1,''Domain'',1,assocuri2);'; put 'rc=metadata_getattr(assocuri2,"Name",authdomain);'; put 'call symputx(''authdomain'',authdomain,''l'');'; put '/* path */'; put 'rc=metadata_getprop(assocuri1,'; put '''Connection.Oracle.Property.PATH.Name.xmlKey.txt'',path);'; put 'call symputx(''path'',path,''l'');'; put '/* schema */'; put 'rc=metadata_getnasn("&liburi",''UsingPackages'',1,assocuri3);'; put 'rc=metadata_getattr(assocuri3,''SchemaName'',schema);'; put 'call symputx(''schema'',schema,''l'');'; put 'run;'; put '%put NOTE: Executing the following:/; %put NOTE-;'; put '%put NOTE- libname &libref ORACLE path=&path schema=&schema;'; put '%put NOTE- authdomain=&authdomain;'; put '%put NOTE-;'; put 'libname &libref ORACLE path=&path schema=&schema authdomain=&authdomain;'; put '%end;'; put '%else %if &engine=SQLSVR %then %do;'; put '%put NOTE: Obtaining &engine library details;'; put 'data _null;'; put 'length assocuri1 assocuri2 assocuri3 authdomain path schema userid'; put 'passwd $256;'; put 'call missing (of _all_);'; put 'rc=metadata_getnasn("&liburi",''DefaultLogin'',1,assocuri1);'; put 'rc=metadata_getattr(assocuri1,"UserID",userid);'; put 'rc=metadata_getattr(assocuri1,"Password",passwd);'; put 'call symputx(''user'',userid,''l'');'; put 'call symputx(''pass'',passwd,''l'');'; put '/* path */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,assocuri2);'; put 'rc=metadata_getprop(assocuri2,'; put '''Connection.SQL.Property.Datasrc.Name.xmlKey.txt'',path);'; put 'call symputx(''path'',path,''l'');'; put '/* schema */'; put 'rc=metadata_getnasn("&liburi",''UsingPackages'',1,assocuri3);'; put 'rc=metadata_getattr(assocuri3,''SchemaName'',schema);'; put 'call symputx(''schema'',schema,''l'');'; put 'run;'; put '%put NOTE: Executing the following:/; %put NOTE-;'; put '%put NOTE- libname &libref SQLSVR datasrc=&path schema=&schema ;'; put '%put NOTE- user="&user" pass="XXX";'; put '%put NOTE-;'; put 'libname &libref SQLSVR datasrc=&path schema=&schema user="&user" pass="&pass";'; put '%end;'; put '%else %if &engine=TERADATA %then %do;'; put '%put NOTE: Obtaining &engine library details;'; put 'data _null;'; put 'length assocuri1 assocuri2 assocuri3 authdomain path schema userid'; put 'passwd $256;'; put 'call missing (of _all_);'; put '/* get auth domain */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,assocuri1);'; put 'rc=metadata_getnasn(assocuri1,''Domain'',1,assocuri2);'; put 'rc=metadata_getattr(assocuri2,"Name",authdomain);'; put 'call symputx(''authdomain'',authdomain,''l'');'; put '/*'; put 'rc=metadata_getnasn("&liburi",''DefaultLogin'',1,assocuri1);'; put 'rc=metadata_getattr(assocuri1,"UserID",userid);'; put 'rc=metadata_getattr(assocuri1,"Password",passwd);'; put 'call symputx(''user'',userid,''l'');'; put 'call symputx(''pass'',passwd,''l'');'; put '*/'; put '/* path */'; put 'rc=metadata_getnasn("&liburi",''LibraryConnection'',1,assocuri2);'; put 'rc=metadata_getprop(assocuri2,'; put '''Connection.Teradata.Property.SERVER.Name.xmlKey.txt'',path);'; put 'call symputx(''path'',path,''l'');'; put '/* schema */'; put 'rc=metadata_getnasn("&liburi",''UsingPackages'',1,assocuri3);'; put 'rc=metadata_getattr(assocuri3,''SchemaName'',schema);'; put 'call symputx(''schema'',schema,''l'');'; put 'run;'; put '%put NOTE: Executing the following:/; %put NOTE-;'; put '%put NOTE- libname &libref TERADATA server="&path" schema=&schema ;'; put '%put NOTe- authdomain=&authdomain;'; put '%put NOTE-;'; put 'libname &libref TERADATA server="&path" schema=&schema authdomain=&authdomain;'; put '%end;'; put '%else %if &engine= %then %do;'; put '%put NOTE: Libref &libref is not registered in metadata;'; put '%&mAbort.mp_abort('; put 'msg=%str(ERR)OR: Libref &libref is not registered in metadata'; put ',mac=mm_assigndirectlib.sas);'; put '%return;'; put '%end;'; put '%else %do;'; put '%put %str(WARN)ING: Engine &engine is currently unsupported;'; put '%put %str(WARN)ING- Please contact your support team.;'; put '%return;'; put '%end;'; put '%mend mm_assigndirectlib;'; put '%macro mm_getrepos('; put 'outds=work.mm_getrepos'; put ')/*/STORE SOURCE*/;'; put '* use a temporary fileref to hold the response;'; put 'filename response temp;'; put '/* get list of libraries */'; put 'proc metadata in='; put '"1"'; put 'out=response;'; put 'run;'; put '/* write the response to the log for debugging */'; put '/*'; put 'data _null_;'; put 'infile response lrecl=1048576;'; put 'input;'; put 'put _infile_;'; put 'run;'; put '*/'; put '/* create an XML map to read the response */'; put 'filename sxlemap temp;'; put 'data _null_;'; put 'file sxlemap;'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository";'; put 'put "";'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Id";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Name";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Desc";'; put 'put "";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@DefaultNS";'; put 'put "characterstring200";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@RepositoryType";'; put 'put "characterstring20";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@RepositoryFormat";'; put 'put "characterstring10";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@Access";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@CurrentAccess";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@PauseState";'; put 'put "characterstring16";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Path";'; put 'put "";'; put 'put "characterstring256";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Engine";'; put 'put "";'; put 'put "characterstring8";'; put 'put '''';'; put 'put '''';'; put 'put "/GetRepositories/Repositories/Repository/@Options";'; put 'put "";'; put 'put "characterstring32";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@MetadataCreated";'; put 'put "characterstring24";'; put 'put '''';'; put 'put '''';'; put 'put "";'; put 'put "/GetRepositories/Repositories/Repository/@MetadataUpdated";'; put 'put "characterstring24";'; put 'put '''';'; put 'put ''
'';'; put 'run;'; put 'libname _XML_ xml xmlfileref=response xmlmap=sxlemap;'; put 'proc sort data= _XML_.SASRepos out=&outds;'; put 'by name;'; put 'run;'; put '/* clear references */'; put 'filename sxlemap clear;'; put 'filename response clear;'; put 'libname _XML_ clear;'; put '%mend mm_getrepos;'; put '%macro dc_assignlib(type,libref,passthru=);'; put '%put &sysmacroname entry vars:;'; put '%put _local_;'; put '/* take current repository */'; put '%local repo repocnt x;'; put '%let repo=%sysfunc(getoption(metarepository));'; put '/* get list of repositories and filter */'; put '%mm_getrepos(outds=repos)'; put '%let repocnt=0;'; put 'data repos;'; put 'set repos;'; put 'where repositorytype in(''CUSTOM'',''FOUNDATION'')'; put '& upcase(name) ne "%upcase(&repo)";'; put 'keep id name ;'; put 'call symputx(cats(''repo'',_n_),name,''l'');'; put 'call symputx(''repocnt'',_n_,''l'');'; put 'run;'; put '/* find out which of these repositories has the libref we are searching for */'; put '%local lib_uri;'; put '%let lib_uri=NOTFOUND;'; put 'data _null_; /* check default repo first */'; put 'length lib_uri $256;'; put 'call missing (of _all_);'; put 'rc=metadata_getnobj("omsobj:SASLibrary?@Libref =''&libref''",1,lib_uri);'; put 'call symputx(''lib_uri'',coalescec(lib_uri,''NOTFOUND''),''l'');'; put 'run;'; put '%if &lib_uri=NOTFOUND %then %do;'; put '%do x=1 %to &repocnt;'; put 'options metarepository=&&repo&x;'; put 'data _null_;'; put 'length lib_uri $256;'; put 'call missing (of _all_);'; put 'rc=metadata_getnobj("omsobj:SASLibrary?@Libref =''&libref''",1,lib_uri);'; put 'call symputx(''lib_uri'',coalescec(lib_uri,''NOTFOUND''),''l'');'; put 'run;'; put '%if &lib_uri ne NOTFOUND %then %let x=%eval(&repocnt+1);'; put '%end;'; put '%end;'; put '%if &type=READ %then %do;'; put '%mm_assignlib(&libref)'; put '%end;'; put '%else %if &type=WRITE %then %do;'; put '%mm_assigndirectlib(&libref,open_passthrough=&passthru)'; put '%end;'; put 'options metarepository=&repo;'; put '%mend dc_assignlib;'; put '* SAS Macros end;'; put '* SAS Includes start;'; put '* SAS Includes end;'; put '* Binary Files start;'; put '* Binary Files end;'; put '* ServiceInit start;'; put 'options noquotelenmax ps=max;'; put '/* create dummy macros to prevent stpbegin / stpend from corrupting sessions */'; put '%macro stpbegin();'; put '%put NOTE: the STPBEGIN macro should not be used for web apps!;'; put '%mend stpbegin;'; put '%macro stpend();'; put '%put NOTE: the STPEND macro should not be used for web apps!;'; put '%mend stpend;'; put '* ServiceInit end;'; put '* Service start;'; put '/**'; put '@file'; put '@brief Generic validator for tables in a library'; put '@details The input table is simply one row from the target table in table'; put 'called "work.source_row".'; put 'Available macro variables:'; put '@li MPELIB - The DC control library'; put '@li LIBDS - The library.dataset being filtered'; put '@li VARIABLE_NM - The column being filtered'; put '

Service Outputs

'; put 'The values provided below are generic samples - we encourage you to replace'; put 'these with realistic values in your own deployments.'; put '
DYNAMIC_VALUES
'; put 'The RAW_VALUE column may be charactor or numeric. If DISPLAY_INDEX is not'; put 'provided, it is added automatically.'; put '|DISPLAY_INDEX:best.|DISPLAY_VALUE:$|RAW_VALUE|'; put '|---|---|---|'; put '|1|$77.43|77.43|'; put '|2|$88.43|88.43|'; put '
DYNAMIC_EXTENDED_VALUES
'; put 'This table is optional. If provided, it will map the DISPLAY_INDEX from the'; put 'DYNAMIC_VALUES table to additional column/value pairs, that will be used to'; put 'populate dropdowns for _other_ cells in the _same_ row.'; put 'Should be used sparingly! The use of large tables here can slow down the'; put 'browser.'; put '|DISPLAY_INDEX:best.|EXTRA_COL_NAME:$32.|DISPLAY_VALUE:$|DISPLAY_TYPE:$1.|RAW_VALUE_NUM|RAW_VALUE_CHAR:$5000|'; put '|---|---|---|'; put '|1|DISCOUNT_RT|"50%"|N|0.5||'; put '|1|DISCOUNT_RT|"40%"|N|0.4||'; put '|1|DISCOUNT_RT|"30%"|N|0.3||'; put '|1|CURRENCY_SYMBOL|"GBP"|C||"GBP"|'; put '|1|CURRENCY_SYMBOL|"RSD"|C||"RSD"|'; put '|2|DISCOUNT_RT|"50%"|N|0.5||'; put '|2|DISCOUNT_RT|"40%"|N|0.4||'; put '|2|CURRENCY_SYMBOL|"EUR"|C||"EUR"|'; put '|2|CURRENCY_SYMBOL|"HKD"|C||"HKD"|'; put '

SAS Macros

'; put '@li dc_assignlib.sas'; put '**/'; put '/* send back the raw and formatted values */'; put '%let tgtlib=0;'; put '%let varlibds=%mf_getuniquename();'; put '%let vartgtlib=%mf_getuniquename();'; put '%let var_is_lib=%mf_getuniquename();'; put 'data _null_;'; put 'length &varlibds $41 &vartgtlib $8 libref $8 rls_libref $8;'; put 'if _n_=1 then call missing(of _all_);'; put 'set work.source_row;'; put '&varlibds=upcase(symget(''libds''));'; put 'if &varlibds="&mpelib..MPE_TABLES" then &vartgtlib=LIBREF;'; put 'else if &varlibds="&mpelib..MPE_ROW_LEVEL_SECURITY"'; put 'then &vartgtlib=RLS_LIBREF;'; put 'else if &varlibds="&mpelib..MPE_COLUMN_LEVEL_SECURITY"'; put 'then &vartgtlib=CLS_LIBREF;'; put '/* validate name */'; put 'if nvalid(&vartgtlib,''v7'') then call symputx(''tgtlib'',&vartgtlib);'; put 'call symputx(''vartgtlib'',&vartgtlib);'; put 'putlog (_all_)(=);'; put 'run;'; put '%mp_abort(iftrue= ("&tgtlib" ="0" )'; put ',mac=&_program..sas'; put ',msg=%str(Invalid library - %superq(vartgtlib))'; put ',errds=work.dc_error_response'; put ')'; put '%dc_assignlib(READ,&tgtlib)'; put 'data members; /* empty table */'; put 'name='' '';'; put 'run;'; put 'ods output Members=Members;'; put 'proc datasets library=&tgtlib ;'; put 'run;'; put '/* send back the raw and formatted values */'; put 'proc sql;'; put 'create table work.DYNAMIC_VALUES as'; put 'select distinct name as display_value,'; put 'upcase(name) as raw_value'; put 'from work.members'; put 'where MemType=''DATA'''; put 'order by 1;'; put '* Service end;'; run; %mm_createwebservice(path=&appLoc/&path, name=&service, code=sascode, server=&serverName, replace=yes) filename sascode clear; %let service=tables_editable; filename sascode temp lrecl=32767; data _null_; file sascode; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '/**'; put '@file mp_jsonout.sas'; put '@brief Writes JSON in SASjs format to a fileref'; put '@details This macro can be used to OPEN a JSON stream and send one or more'; put 'tables as arrays of rows, where each row can be an object or a nested array.'; put 'There are two engines available - DATASTEP or PROCJSON.'; put 'PROC JSON is fast but will produce errs like the ones below if'; put 'special chars are encountered.'; put '> (ERR)OR: Some code points did not transcode.'; put '> An object or array close is not valid at this point in the JSON text.'; put '> Date value out of range'; put 'If this happens, try running with ENGINE=DATASTEP.'; put 'The DATASTEP engine is used to handle special SAS missing numerics, and'; put 'can also convert entire datasets to formatted values. Output JSON is always'; put 'in UTF-8.'; put 'Usage:'; put 'filename tmp temp;'; put 'data class; set sashelp.class;run;'; put '%mp_jsonout(OPEN,jref=tmp)'; put '%mp_jsonout(OBJ,class,jref=tmp)'; put '%mp_jsonout(OBJ,class,dslabel=class2,jref=tmp,showmeta=Y)'; put '%mp_jsonout(CLOSE,jref=tmp)'; put 'data _null_;'; put 'infile tmp;'; put 'input;putlog _infile_;'; put 'run;'; put 'If you are building web apps with SAS then you are strongly encouraged to use'; put 'the mX_createwebservice macros in combination with the'; put '[sasjs adapter](https://github.com/sasjs/adapter).'; put 'For more information see https://sasjs.io'; put '@param [in] action Valid values:'; put '@li OPEN - opens the JSON'; put '@li OBJ - sends a table with each row as an object'; put '@li ARR - sends a table with each row in an array'; put '@li CLOSE - closes the JSON'; put '@param [in] ds The dataset to send. Must be a work table.'; put '@param [out] jref= (_webout) The fileref to which to send the JSON'; put '@param [out] dslabel= The name to give the table in the exported JSON'; put '@param [in] fmt= (Y) Whether to keep (Y) or strip (N) formats from the table'; put '@param [in] engine= (DATASTEP) Which engine to use to send the JSON. Options:'; put '@li PROCJSON (default)'; put '@li DATASTEP (more reliable when data has non standard characters)'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to JSON'; put '

Related Files

'; put '@li mp_ds2fmtds.sas'; put '@version 9.2'; put '@author Allan Bowe'; put '@source https://github.com/sasjs/core'; put '**/'; put '%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y'; put ',engine=DATASTEP'; put ',missing=NULL'; put ',showmeta=N'; put ',maxobs=MAX'; put ')/*/STORE SOURCE*/;'; put '%local tempds colinfo fmtds i numcols numobs stmt_obs lastobs optval'; put 'tmpds1 tmpds2 tmpds3 tmpds4;'; put '%let numcols=0;'; put '%if &maxobs ne MAX %then %let stmt_obs=%str(if _n_>&maxobs then stop;);'; put '%if &action=OPEN %then %do;'; put 'options nobomfile;'; put 'data _null_;file &jref encoding=''utf-8'' lrecl=200;'; put 'put ''{"PROCESSED_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''"'';'; put 'run;'; put '%end;'; put '%else %if (&action=ARR or &action=OBJ) %then %do;'; put '/* force variable names to always be uppercase in the JSON */'; put 'options validvarname=upcase;'; put '/* To avoid issues with _webout on EBI - such as encoding diffs and truncation'; put '(https://support.sas.com/kb/49/325.html) we use temporary files */'; put 'filename _sjs1 temp lrecl=200 ;'; put 'data _null_; file _sjs1 encoding=''utf-8'';'; put 'put ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":";'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjs1 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs1 clear;'; put '/* grab col defs */'; put 'proc contents noprint data=&ds'; put 'out=_data_(keep=name type length format formatl formatd varnum label);'; put 'run;'; put '%let colinfo=%scan(&syslast,2,.);'; put 'proc sort data=&colinfo;'; put 'by varnum;'; put 'run;'; put '/* move meta to mac vars */'; put 'data &colinfo;'; put 'if _n_=1 then call symputx(''numcols'',nobs,''l'');'; put 'set &colinfo end=last nobs=nobs;'; put 'name=upcase(name);'; put '/* fix formats */'; put 'if type=2 or type=6 then do;'; put 'typelong=''char'';'; put 'length fmt $49.;'; put 'if format='''' then fmt=cats(''$'',length,''.'');'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else fmt=cats(format,formatl,''.'');'; put 'end;'; put 'else do;'; put 'typelong=''num'';'; put 'if format='''' then fmt=''best.'';'; put 'else if formatl=0 then fmt=cats(format,''.'');'; put 'else if formatd=0 then fmt=cats(format,formatl,''.'');'; put 'else fmt=cats(format,formatl,''.'',formatd);'; put 'end;'; put '/* 32 char unique name */'; put 'newname=''sasjs''!!substr(cats(put(md5(name),$hex32.)),1,27);'; put 'call symputx(cats(''name'',_n_),name,''l'');'; put 'call symputx(cats(''newname'',_n_),newname,''l'');'; put 'call symputx(cats(''length'',_n_),length,''l'');'; put 'call symputx(cats(''fmt'',_n_),fmt,''l'');'; put 'call symputx(cats(''type'',_n_),type,''l'');'; put 'call symputx(cats(''typelong'',_n_),typelong,''l'');'; put 'call symputx(cats(''label'',_n_),coalescec(label,name),''l'');'; put '/* overwritten when fmt=Y and a custom format exists in catalog */'; put 'if typelong=''num'' then call symputx(cats(''fmtlen'',_n_),200,''l'');'; put 'else call symputx(cats(''fmtlen'',_n_),min(32767,ceil((length+10)*1.5)),''l'');'; put 'run;'; put '%let tempds=%substr(_%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql;'; put 'select count(*) into: lastobs from &ds;'; put '%if &maxobs ne MAX %then %let lastobs=%sysfunc(min(&lastobs,&maxobs));'; put '%if &engine=PROCJSON %then %do;'; put '%if &missing=STRING %then %do;'; put '%put &sysmacroname: Special Missings not supported in proc json.;'; put '%put &sysmacroname: Switching to DATASTEP engine;'; put '%goto datastep;'; put '%end;'; put 'data &tempds;'; put 'set &ds;'; put '&stmt_obs;'; put '%if &fmt=N %then format _numeric_ best32.;;'; put '/* PRETTY is necessary to avoid line truncation in large files */'; put 'filename _sjs2 temp lrecl=131068 encoding=''utf-8'';'; put 'proc json out=_sjs2 pretty'; put '%if &action=ARR %then nokeys ;'; put ';export &tempds / nosastags fmtnumeric;'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs2 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs2 clear;'; put '%end;'; put '%else %if &engine=DATASTEP %then %do;'; put '%datastep:'; put '%if %sysfunc(exist(&ds)) ne 1 & %sysfunc(exist(&ds,VIEW)) ne 1'; put '%then %do;'; put '%put &sysmacroname: &ds NOT FOUND!!!;'; put '%return;'; put '%end;'; put '%if &fmt=Y %then %do;'; put '/**'; put '* Extract format definitions'; put '* First, by getting library locations from dictionary.formats'; put '* Then, by exporting the width using proc format'; put '* Cannot use maxw from sashelp.vformat as not always populated'; put '* Cannot use fmtinfo() as not supported in all flavours'; put '*/'; put '%let tmpds1=%substr(fmtsum%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds2=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds3=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put '%let tmpds4=%substr(col%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);'; put 'proc sql noprint;'; put 'create table &tmpds1 as'; put 'select cats(libname,''.'',memname) as FMTCAT,'; put 'FMTNAME'; put 'from dictionary.formats'; put 'where fmttype=''F'' and libname is not null'; put 'and fmtname in (select format from &colinfo where format is not null)'; put 'order by 1;'; put 'create table &tmpds2('; put 'FMTNAME char(32),'; put 'LENGTH num'; put ');'; put '%local catlist cat fmtlist i;'; put 'select distinct fmtcat into: catlist separated by '' '' from &tmpds1;'; put '%do i=1 %to %sysfunc(countw(&catlist,%str( )));'; put '%let cat=%scan(&catlist,&i,%str( ));'; put 'proc sql;'; put 'select distinct fmtname into: fmtlist separated by '' '''; put 'from &tmpds1 where fmtcat="&cat";'; put 'proc format lib=&cat cntlout=&tmpds3(keep=fmtname length);'; put 'select &fmtlist;'; put 'run;'; put 'proc sql;'; put 'insert into &tmpds2 select distinct fmtname,length from &tmpds3;'; put '%end;'; put 'proc sql;'; put 'create table &tmpds4 as'; put 'select a.*, b.length as MAXW'; put 'from &colinfo a'; put 'left join &tmpds2 b'; put 'on cats(a.format)=cats(upcase(b.fmtname))'; put 'order by a.varnum;'; put 'data _null_;'; put 'set &tmpds4;'; put 'if not missing(maxw);'; put 'call symputx('; put 'cats(''fmtlen'',_n_),'; put '/* vars need extra padding due to JSON escaping of special chars */'; put 'min(32767,ceil((max(length,maxw)+10)*1.5))'; put ',''l'''; put ');'; put 'run;'; put '/* configure varlenchk - as we are explicitly shortening the variables */'; put '%let optval=%sysfunc(getoption(varlenchk));'; put 'options varlenchk=NOWARN;'; put 'data _data_(compress=char);'; put '/* shorten the new vars */'; put 'length'; put '%do i=1 %to &numcols;'; put '&&name&i $&&fmtlen&i'; put '%end;'; put ';'; put '/* rename on entry */'; put 'set &ds(rename=('; put '%do i=1 %to &numcols;'; put '&&name&i=&&newname&i'; put '%end;'; put '));'; put '&stmt_obs;'; put 'drop'; put '%do i=1 %to &numcols;'; put '&&newname&i'; put '%end;'; put ';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=num %then %do;'; put '&&name&i=cats(put(&&newname&i,&&fmt&i));'; put '%end;'; put '%else %do;'; put '&&name&i=put(&&newname&i,&&fmt&i);'; put '%end;'; put '%end;'; put 'if _error_ then do;'; put 'call symputx(''syscc'',1012);'; put 'stop;'; put 'end;'; put 'run;'; put '%let fmtds=&syslast;'; put 'options varlenchk=&optval;'; put '%end;'; put 'proc format; /* credit yabwon for special null removal */'; put 'value bart (default=40)'; put '%if &missing=NULL %then %do;'; put '._ - .z = null'; put '%end;'; put '%else %do;'; put '._ = [quote()]'; put '. = null'; put '.a - .z = [quote()]'; put '%end;'; put 'other = [best.];'; put 'data &tempds;'; put 'attrib _all_ label='''';'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'length &&name&i $&&fmtlen&i...;'; put 'format &&name&i $&&fmtlen&i...;'; put '%end;'; put '%end;'; put '%if &fmt=Y %then %do;'; put 'set &fmtds;'; put '%end;'; put '%else %do;'; put 'set &ds;'; put '%end;'; put '&stmt_obs;'; put 'format _numeric_ bart.;'; put '%do i=1 %to &numcols;'; put '%if &&typelong&i=char or &fmt=Y %then %do;'; put 'if findc(&&name&i,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put '&&name&i=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,&&name&i)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else &&name&i=quote(cats(&&name&i));'; put '%end;'; put '%end;'; put 'run;'; put 'filename _sjs3 temp lrecl=131068 ;'; put 'data _null_;'; put 'file _sjs3 encoding=''utf-8'';'; put 'if _n_=1 then put "[";'; put 'set &tempds;'; put 'if _n_>1 then put "," @; put'; put '%if &action=ARR %then "[" ; %else "{" ;'; put '%do i=1 %to &numcols;'; put '%if &i>1 %then "," ;'; put '%if &action=OBJ %then """&&name&i"":" ;'; put '"&&name&i"n /* name literal for reserved variable names */'; put '%end;'; put '%if &action=ARR %then "]" ; %else "}" ; ;'; put '/* close out the table */'; put 'data _null_;'; put 'file _sjs3 mod encoding=''utf-8'';'; put 'put '']'';'; put 'run;'; put 'data _null_;'; put 'infile _sjs3 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs3 clear;'; put '%end;'; put 'proc sql;'; put 'drop table &colinfo, &tempds;'; put '%if %substr(&showmeta,1,1)=Y %then %do;'; put 'filename _sjs4 temp lrecl=131068 encoding=''utf-8'';'; put 'data _null_;'; put 'file _sjs4;'; put 'length label $350;'; put 'put ", ""$%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":{""vars"":{";'; put 'do i=1 to &numcols;'; put 'name=quote(trim(symget(cats(''name'',i))));'; put 'format=quote(trim(symget(cats(''fmt'',i))));'; put 'label=quote(prxchange(''s/\\/\\\\/'',-1,trim(symget(cats(''label'',i)))));'; put 'length=quote(trim(symget(cats(''length'',i))));'; put 'type=quote(trim(symget(cats(''typelong'',i))));'; put 'if i>1 then put "," @@;'; put 'put name '':{"format":'' format '',"label":'' label'; put ''',"length":'' length '',"type":'' type ''}'';'; put 'end;'; put 'put ''}}'';'; put 'run;'; put '/* send back to webout */'; put 'data _null_;'; put 'infile _sjs4 lrecl=1 recfm=n;'; put 'file &jref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjs4 clear;'; put '%end;'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put 'data _null_; file &jref encoding=''utf-8'' mod ;'; put 'put "}";'; put 'run;'; put '%end;'; put '%mend mp_jsonout;'; put '/**'; put '@file mm_webout.sas'; put '@brief Send data to/from SAS Stored Processes'; put '@details This macro should be added to the start of each Stored Process,'; put '**immediately** followed by a call to:'; put '%mm_webout(FETCH)'; put 'This will read all the input data and create same-named SAS datasets in the'; put 'WORK library. You can then insert your code, and send data back using the'; put 'following syntax:'; put 'data some datasets; * make some data ;'; put 'retain some columns;'; put 'run;'; put '%mm_webout(OPEN)'; put '%mm_webout(ARR,some) * Array format, fast, suitable for large tables ;'; put '%mm_webout(OBJ,datasets) * Object format, easier to work with ;'; put 'Finally, wrap everything up send some helpful system variables too'; put '%mm_webout(CLOSE)'; put '@param [in] action Either FETCH, OPEN, ARR, OBJ or CLOSE'; put '@param [in] ds The dataset to send back to the frontend'; put '@param [out] dslabel= Value to use instead of table name for sending to JSON'; put '@param [in] fmt= (N) Setting Y converts all vars to their formatted values'; put '@param [out] fref= (_webout) The fileref to which to write the JSON'; put '@param [in] missing= (NULL) Special numeric missing values can be sent as NULL'; put '(eg `null`) or as STRING values (eg `".a"` or `".b"`)'; put '@param [in] showmeta= (N) Set to Y to output metadata alongside each table,'; put 'such as the column formats and types. The metadata is contained inside an'; put 'object with the same name as the table but prefixed with a dollar sign - ie,'; put '`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`'; put '@param [in] workobs= (0) When set to a positive integer, will create a new'; put 'output object (WORK) which contains this number of observations from all'; put 'tables in the WORK library.'; put '@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows'; put 'that should be converted to output JSON'; put '

SAS Macros

'; put '@li mp_jsonout.sas'; put '

Related Macros

'; put '@li ms_webout.sas'; put '@li mv_webout.sas'; put '@version 9.3'; put '@author Allan Bowe'; put '**/'; put '%macro mm_webout(action,ds,dslabel=,fref=_webout,fmt=N,missing=NULL'; put ',showmeta=N,maxobs=MAX,workobs=0'; put ');'; put '%global _webin_file_count _webin_fileref1 _webin_name1 _program _debug'; put 'sasjs_tables;'; put '%local i tempds jsonengine;'; put '/* see https://github.com/sasjs/core/issues/41 */'; put '%if "%upcase(&SYSENCODING)" ne "UTF-8" %then %let jsonengine=PROCJSON;'; put '%else %let jsonengine=DATASTEP;'; put '%if &action=FETCH %then %do;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'options mprint notes mprintnest;'; put '%end;'; put '%let _webin_file_count=%eval(&_webin_file_count+0);'; put '/* now read in the data */'; put '%do i=1 %to &_webin_file_count;'; put '%if &_webin_file_count=1 %then %do;'; put '%let _webin_fileref1=&_webin_fileref;'; put '%let _webin_name1=&_webin_name;'; put '%end;'; put 'data _null_;'; put 'infile &&_webin_fileref&i termstr=crlf;'; put 'input;'; put 'call symputx(''input_statement'',_infile_);'; put 'putlog "&&_webin_name&i input statement: " _infile_;'; put 'stop;'; put 'data &&_webin_name&i;'; put 'infile &&_webin_fileref&i firstobs=2 dsd termstr=crlf encoding=''utf-8'';'; put 'input &input_statement;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'if _n_<20 then putlog _infile_;'; put '%end;'; put 'run;'; put '%let sasjs_tables=&sasjs_tables &&_webin_name&i;'; put '%end;'; put '%end;'; put '%else %if &action=OPEN %then %do;'; put '/* fix encoding */'; put 'OPTIONS NOBOMFILE;'; put '/**'; put '* check xengine type to avoid the below err message:'; put '* > Function is only valid for filerefs using the CACHE access method.'; put '*/'; put 'data _null_;'; put 'set sashelp.vextfl(where=(fileref="_WEBOUT"));'; put 'if xengine=''STREAM'' then do;'; put 'rc=stpsrv_header(''Content-type'',"text/html; encoding=utf-8");'; put 'end;'; put 'run;'; put '/* setup json */'; put 'data _null_;file &fref encoding=''utf-8'';'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutBEGIN<<'';'; put '%end;'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'run;'; put '%end;'; put '%else %if &action=ARR or &action=OBJ %then %do;'; put '%mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt,jref=&fref'; put ',engine=&jsonengine,missing=&missing,showmeta=&showmeta,maxobs=&maxobs'; put ')'; put '%end;'; put '%else %if &action=CLOSE %then %do;'; put '/* To avoid issues with _webout on EBI we use a temporary file */'; put 'filename _sjsref temp lrecl=131068;'; put '%if %str(&workobs) > 0 %then %do;'; put '/* if debug mode, send back first XX records of each work table also */'; put 'data;run;%let tempds=%scan(&syslast,2,.);'; put 'ods output Members=&tempds;'; put 'proc datasets library=WORK memtype=data;'; put '%local wtcnt;%let wtcnt=0;'; put 'data _null_;'; put 'set &tempds;'; put 'if not (upcase(name) =:"DATA"); /* ignore temp datasets */'; put 'i+1;'; put 'call symputx(cats(''wt'',i),name,''l'');'; put 'call symputx(''wtcnt'',i,''l'');'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put ",""WORK"":{";'; put '%do i=1 %to &wtcnt;'; put '%let wt=&&wt&i;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'dsid=open("WORK.&wt",''is'');'; put 'nlobs=attrn(dsid,''NLOBS'');'; put 'nvars=attrn(dsid,''NVARS'');'; put 'rc=close(dsid);'; put 'if &i>1 then put '',''@;'; put 'put " ""&wt"" : {";'; put 'put ''"nlobs":'' nlobs;'; put 'put '',"nvars":'' nvars;'; put '%mp_jsonout(OBJ,&wt,jref=_sjsref,dslabel=first10rows,showmeta=Y'; put ',maxobs=&workobs'; put ')'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put '%end;'; put 'data _null_; file _sjsref mod encoding=''utf-8'';'; put 'put "}";'; put 'run;'; put '%end;'; put '/* close off json */'; put 'data _null_;file _sjsref mod encoding=''utf-8'';'; put 'length SYSPROCESSNAME syserrortext syswarningtext autoexec $512;'; put 'put ",""_DEBUG"" : ""&_debug"" ";'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'autoexec=quote(urlencode(trim(getoption(''autoexec''))));'; put 'put '',"AUTOEXEC" : '' autoexec;'; put 'put ",""MF_GETUSER"" : ""%mf_getuser()"" ";'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'put ",""SYSENCODING"" : ""&sysencoding"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSPROCESSID"" : ""&SYSPROCESSID"" ";'; put 'put ",""SYSPROCESSMODE"" : ""&SYSPROCESSMODE"" ";'; put 'SYSPROCESSNAME=quote(urlencode(cats(SYSPROCESSNAME)));'; put 'put ",""SYSPROCESSNAME"" : " SYSPROCESSNAME;'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put '',"SYSWARNINGTEXT" : '' syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'length memsize $32;'; put 'memsize="%sysfunc(INPUTN(%sysfunc(getoption(memsize)), best.),sizekmg.)";'; put 'memsize=quote(cats(memsize));'; put 'put '',"MEMSIZE" : '' memsize;'; put 'put "}" @;'; put '%if %str(&_debug) ge 131 %then %do;'; put 'put ''>>weboutEND<<'';'; put '%end;'; put 'run;'; put '/* now write to _webout 1 char at a time */'; put 'data _null_;'; put 'infile _sjsref lrecl=1 recfm=n;'; put 'file &fref mod lrecl=1 recfm=n;'; put 'input sourcechar $char1. @@;'; put 'format sourcechar hex2.;'; put 'put sourcechar char1. @@;'; put 'run;'; put 'filename _sjsref clear;'; put '%end;'; put '%mend mm_webout;'; put '%macro webout(action,ds,dslabel=,fmt=,missing=NULL,showmeta=NO,maxobs=MAX);'; put '%mm_webout(&action,ds=&ds,dslabel=&dslabel,fmt=&fmt'; put ',missing=&missing'; put ',showmeta=&showmeta'; put ',maxobs=&maxobs'; put ') %mend;'; put '/* provide additional debug info */'; put '%global _program;'; put '%put &=syscc;'; put '%put user=%mf_getuser();'; put '%put pgm=&_program;'; put '%put timestamp=%sysfunc(datetime(),datetime19.);'; put '* Service Variables start;'; put '* Service Variables end;'; put '* SAS Macros start;'; put '%macro mf_getapploc(pgm);'; put '%if "&pgm"="" %then %do;'; put '%if %symexist(_program) %then %let pgm=&_program;'; put '%else %do;'; put '%put &sysmacroname: No value provided and no _program variable available;'; put '%return;'; put '%end;'; put '%end;'; put '%local root;'; put '/**'; put '* First check we are not in the tests/macros folder (which has no subfolders)'; put '* or specifically in the testsetup or testteardown services'; put '*/'; put '%if %index(&pgm,/tests/macros/)'; put 'or %index(&pgm,/tests/testsetup)'; put 'or %index(&pgm,/tests/testteardown)'; put '%then %do;'; put '%let root=%substr(&pgm,1,%index(&pgm,/tests)-1);'; put '&root'; put '%return;'; put '%end;'; put '/**'; put '* Next, move up two levels to avoid matches on subfolder or service name'; put '*/'; put '%let root=%substr(&pgm,1,%length(&pgm)-%length(%scan(&pgm,-1,/))-1);'; put '%let root=%substr(&root,1,%length(&root)-%length(%scan(&root,-1,/))-1);'; put '%if %index(&root,/tests/) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/tests/)-1);'; put '%end;'; put '%else %if %index(&root,/services) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/services)-1);'; put '%end;'; put '%else %if %index(&root,/jobs) %then %do;'; put '%let root=%substr(&root,1,%index(&root,/jobs)-1);'; put '%end;'; put '%else %put &sysmacroname: Could not find an app location from &pgm;'; put '&root'; put '%mend mf_getapploc ;'; put '%macro mf_getuniquefileref(prefix=_,maxtries=1000,lrecl=32767);'; put '%local rc fname;'; put '%if &prefix=0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%end;'; put '%else %do;'; put '%local x len;'; put '%let len=%eval(8-%length(&prefix));'; put '%let x=0;'; put '%do x=0 %to &maxtries;'; put '%let fname=&prefix%substr(%sysfunc(ranuni(0)),3,&len);'; put '%if %sysfunc(fileref(&fname)) > 0 %then %do;'; put '%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));'; put '%if &rc %then %put %sysfunc(sysmsg());'; put '&fname'; put '%return;'; put '%end;'; put '%end;'; put '%put unable to find available fileref after &maxtries attempts;'; put '%end;'; put '%mend mf_getuniquefileref;'; put '%macro mp_abort(mac=mp_abort.sas, type=, msg=, iftrue=%str(1=1)'; put ', errds=work.mp_abort_errds'; put ', mode=REGULAR'; put ')/*/STORE SOURCE*/;'; put '%global sysprocessmode sysprocessname sasjs_stpsrv_header_loc sasjsprocessmode;'; put '%local fref fid i;'; put '%if not(%eval(%unquote(&iftrue))) %then %return;'; put '%put NOTE: /// mp_abort macro executing //;'; put '%if %length(&mac)>0 %then %put NOTE- called by &mac;'; put '%put NOTE - &msg;'; put '%if %symexist(_SYSINCLUDEFILEDEVICE)'; put '/* abort cancel FILE does not restart outside the INCLUDE on Viya 3.5 */'; put 'and %superq(SYSPROCESSNAME) ne %str(Compute Server)'; put '%then %do;'; put '%if "*&_SYSINCLUDEFILEDEVICE*" ne "**" %then %do;'; put 'data &errds;'; put 'iftrue=''1=1'';'; put 'length mac $100 msg $5000;'; put 'mac=symget(''mac'');'; put 'msg=symget(''msg'');'; put 'run;'; put 'data _null_;'; put 'abort cancel FILE;'; put 'run;'; put '%return;'; put '%end;'; put '%end;'; put '/* Web App Context */'; put '%if %symexist(_PROGRAM)'; put 'or %superq(SYSPROCESSNAME) = %str(Compute Server)'; put 'or &mode=INCLUDE'; put '%then %do;'; put 'options obs=max replace mprint;'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5"'; put '%then %do;'; put 'options nosyntaxcheck;'; put '%end;'; put '%if &mode=INCLUDE %then %do;'; put '%if %sysfunc(exist(&errds))=1 %then %do;'; put 'data _null_;'; put 'set &errds;'; put 'call symputx(''iftrue'',iftrue,''l'');'; put 'call symputx(''mac'',mac,''l'');'; put 'call symputx(''msg'',msg,''l'');'; put 'putlog (_all_)(=);'; put 'run;'; put '%if (&iftrue)=0 %then %return;'; put '%end;'; put '%else %do;'; put '%put &sysmacroname: No include errors found;'; put '%return;'; put '%end;'; put '%end;'; put '/* extract log errs / warns, if exist */'; put '%local logloc logline;'; put '%global logmsg; /* capture global messages */'; put '%if %symexist(SYSPRINTTOLOG) %then %let logloc=&SYSPRINTTOLOG;'; put '%else %let logloc=%qsysfunc(getoption(LOG));'; put 'proc printto log=log;run;'; put '%let logline=0;'; put '%if %length(&logloc)>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input; putlog _infile_;'; put 'i=1;'; put 'retain logonce 0;'; put 'if ('; put '_infile_=:"%str(WARN)ING" or _infile_=:"%str(ERR)OR"'; put ') and logonce=0 then'; put 'do;'; put 'call symputx(''logline'',_n_);'; put 'logonce+1;'; put 'end;'; put 'run;'; put '/* capture log including lines BEFORE the err */'; put '%if &logline>0 %then %do;'; put 'data _null_;'; put 'infile &logloc lrecl=5000;'; put 'input;'; put 'i=1;'; put 'stoploop=0;'; put 'if _n_ ge &logline-15 and stoploop=0 then do until (i>22);'; put 'call symputx(''logmsg'',catx(''\n'',symget(''logmsg''),_infile_));'; put 'input;'; put 'i+1;'; put 'stoploop=1;'; put 'end;'; put 'if stoploop=1 then stop;'; put 'run;'; put '%end;'; put '%end;'; put '%if %symexist(SYS_JES_JOB_URI) %then %do;'; put '/* setup webout for Viya */'; put 'options nobomfile;'; put '%if "X&SYS_JES_JOB_URI.X"="XX" %then %do;'; put 'filename _webout temp lrecl=999999 mod;'; put '%end;'; put '%else %do;'; put 'filename _webout filesrvc parenturi="&SYS_JES_JOB_URI"'; put 'name="_webout.json" lrecl=999999 mod;'; put '%end;'; put '%end;'; put '%else %if %sysfunc(filename(fref,&sasjs_stpsrv_header_loc))=0 %then %do;'; put 'options nobomfile;'; put '/* set up http header for SASjs Server */'; put '%let fid=%sysfunc(fopen(&fref,A));'; put '%if &fid=0 %then %do;'; put '%put %str(ERR)OR: %sysfunc(sysmsg());'; put '%return;'; put '%end;'; put '%let rc=%sysfunc(fput(&fid,%str(Content-Type: application/json)));'; put '%let rc=%sysfunc(fwrite(&fid));'; put '%let rc=%sysfunc(fclose(&fid));'; put '%let rc=%sysfunc(filename(&fref));'; put '%end;'; put '/* send response in SASjs JSON format */'; put 'data _null_;'; put 'file _webout mod lrecl=32000 encoding=''utf-8'';'; put 'length msg syswarningtext syserrortext $32767 mode $10 ;'; put 'sasdatetime=datetime();'; put 'msg=symget(''msg'');'; put '%if &logline>0 %then %do;'; put 'msg=cats(msg,''\n\nLog Extract:\n'',symget(''logmsg''));'; put '%end;'; put '/* escape the escapes */'; put 'msg=tranwrd(msg,''\'',''\\'');'; put '/* escape the quotes */'; put 'msg=tranwrd(msg,''"'',''\"'');'; put '/* ditch the CRLFs as chrome complains */'; put 'msg=compress(msg,,''kw'');'; put '/* quote without quoting the quotes (which are escaped instead) */'; put 'msg=cats(''"'',msg,''"'');'; put 'if symexist(''_debug'') then debug=quote(trim(symget(''_debug'')));'; put 'else debug=''""'';'; put 'if symget(''sasjsprocessmode'')=''Stored Program'' then mode=''SASJS'';'; put 'if mode ne ''SASJS'' then put ''>>weboutBEGIN<<'';'; put 'put ''{"SYSDATE" : "'' "&SYSDATE" ''"'';'; put 'put '',"SYSTIME" : "'' "&SYSTIME" ''"'';'; put 'put '',"sasjsAbort" : [{'';'; put 'put '' "MSG":'' msg ;'; put 'put '' ,"MAC": "'' "&mac" ''"}]'';'; put 'put ",""SYSUSERID"" : ""&sysuserid"" ";'; put 'put '',"_DEBUG":'' debug ;'; put 'if symexist(''_metauser'') then do;'; put '_METAUSER=quote(trim(symget(''_METAUSER'')));'; put 'put ",""_METAUSER"": " _METAUSER;'; put '_METAPERSON=quote(trim(symget(''_METAPERSON'')));'; put 'put '',"_METAPERSON": '' _METAPERSON;'; put 'end;'; put 'if symexist(''SYS_JES_JOB_URI'') then do;'; put 'SYS_JES_JOB_URI=quote(trim(symget(''SYS_JES_JOB_URI'')));'; put 'put '',"SYS_JES_JOB_URI": '' SYS_JES_JOB_URI;'; put 'end;'; put '_PROGRAM=quote(trim(resolve(symget(''_PROGRAM''))));'; put 'put '',"_PROGRAM" : '' _PROGRAM ;'; put 'put ",""SYSCC"" : ""&syscc"" ";'; put 'syserrortext=cats(symget(''syserrortext''));'; put 'if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syserrortext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syserrortext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syserrortext=cats(''"'',syserrortext,''"'');'; put 'put '',"SYSERRORTEXT" : '' syserrortext;'; put 'put ",""SYSHOSTNAME"" : ""&syshostname"" ";'; put 'put ",""SYSJOBID"" : ""&sysjobid"" ";'; put 'put ",""SYSSCPL"" : ""&sysscpl"" ";'; put 'put ",""SYSSITE"" : ""&syssite"" ";'; put 'sysvlong=quote(trim(symget(''sysvlong'')));'; put 'put '',"SYSVLONG" : '' sysvlong;'; put 'syswarningtext=cats(symget(''syswarningtext''));'; put 'if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do;'; put 'syswarningtext=''"''!!trim('; put 'prxchange(''s/"/\\"/'',-1, /* double quote */'; put 'prxchange(''s/\x0A/\n/'',-1, /* new line */'; put 'prxchange(''s/\x0D/\r/'',-1, /* carriage return */'; put 'prxchange(''s/\x09/\\t/'',-1, /* tab */'; put 'prxchange(''s/\x00/\\u0000/'',-1, /* NUL */'; put 'prxchange(''s/\x0E/\\u000E/'',-1, /* SS */'; put 'prxchange(''s/\x0F/\\u000F/'',-1, /* SF */'; put 'prxchange(''s/\x01/\\u0001/'',-1, /* SOH */'; put 'prxchange(''s/\x02/\\u0002/'',-1, /* STX */'; put 'prxchange(''s/\x10/\\u0010/'',-1, /* DLE */'; put 'prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */'; put 'prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */'; put 'prxchange(''s/\\/\\\\/'',-1,syswarningtext)'; put ')))))))))))))!!''"'';'; put 'end;'; put 'else syswarningtext=cats(''"'',syswarningtext,''"'');'; put 'put ",""SYSWARNINGTEXT"" : " syswarningtext;'; put 'put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" '';'; put 'put "}" ;'; put 'if mode ne ''SASJS'' then put ''>>weboutEND<<'';'; put 'run;'; put '%put _all_;'; put '%if "&sysprocessmode " = "SAS Stored Process Server " %then %do;'; put 'data _null_;'; put 'putlog ''stpsrvset program err and syscc'';'; put 'rc=stpsrvset(''program error'', 0);'; put 'call symputx("syscc",0,"g");'; put 'run;'; put '%if &sysscp=WIN'; put 'and 1=0 /* deprecating this logic until we figure out a consistent abort */'; put 'and "%substr(%str(&sysvlong ),1,8)"="9.04.01M"'; put 'and "%substr(%str(&sysvlong ),9,1)">"5" %then %do;'; put '/* skip approach (below) does not work in windows m6+ envs */'; put 'endsas;'; put '%end;'; put '%else %do;'; put '/**'; put '* endsas kills 9.4m3 deployments by orphaning multibridges.'; put '* Abort variants are ungraceful (non zero return code)'; put '* This approach lets SAS run silently until the end :-)'; put '* Caution - fails when called within a %include within a macro'; put '* Use mp_include() to handle this.'; put '*/'; put 'filename skip temp;'; put 'data _null_;'; put 'file skip;'; put 'put ''%macro skip();'';'; put 'comment ''%mend skip; -> fix lint '';'; put 'put ''%macro skippy();'';'; put 'comment ''%mend skippy; -> fix lint '';'; put 'run;'; put '%inc skip;'; put '%end;'; put '%end;'; put '%else %if "&sysprocessmode " = "SAS Compute Server " %then %do;'; put '/* endsas kills the session making it harder to fetch results */'; put 'data _null_;'; put 'syswarningtext=symget(''syswarningtext'');'; put 'syserrortext=symget(''syserrortext'');'; put 'abort_msg=symget(''msg'');'; put 'syscc=symget(''syscc'');'; put 'sysuserid=symget(''sysuserid'');'; put 'iftrue=symget(''iftrue'');'; put 'put (_all_)(/=);'; put 'call symputx(''syscc'',0);'; put 'abort cancel nolist;'; put 'run;'; put '%end;'; put '%else %do;'; put '%abort cancel;'; put '%end;'; put '%end;'; put '%else %do;'; put '%put _all_;'; put '%abort cancel;'; put '%end;'; put '%mend mp_abort;'; put '/** @endcond */'; put '%macro mm_getstpcode('; put 'tree=/User Folders/sasdemo/somestp'; put ',name='; put ',outloc=0'; put ',outref=0'; put ',mDebug=1'; put ',showlog=NO'; put ');'; put '%local mD;'; put '%if &mDebug=1 %then %let mD=;'; put '%else %let mD=%str(*);'; put '%&mD.put Executing &sysmacroname..sas;'; put '%&mD.put _local_;'; put '%if %length(&name)>0 %then %let name=/&name;'; put '/* first, check if STP exists */'; put '%local tsuri;'; put '%let tsuri=stopifempty ;'; put 'data _null_;'; put 'format type uri tsuri value $200.;'; put 'call missing (of _all_);'; put 'path="&tree&name(StoredProcess)";'; put '/* first, find the STP ID */'; put 'if metadata_pathobj("",path,"StoredProcess",type,uri)>0 then do;'; put '/* get sourcecode */'; put 'cnt=1;'; put 'do while (metadata_getnasn(uri,"Notes",cnt,tsuri)>0);'; put 'rc=metadata_getattr(tsuri,"Name",value);'; put '&mD.put tsuri= value=;'; put 'if value="SourceCode" then do;'; put '/* found it! */'; put 'rc=metadata_getattr(tsuri,"Id",value);'; put 'call symputx(''tsuri'',value,''l'');'; put 'stop;'; put 'end;'; put 'cnt+1;'; put 'end;'; put 'end;'; put 'else put (_all_)(=);'; put 'run;'; put '%mp_abort(iftrue= (&tsuri=stopifempty)'; put ',mac=mm_getstpcode'; put ',msg=%str(&tree&name.(StoredProcess) not found!)'; put ')'; put '/**'; put '* Now we can extract the textstore'; put '*/'; put 'filename __getdoc temp lrecl=10000000;'; put 'proc metadata'; put 'in="$METAREPOSITORY'; put ''; put 'SAS1"'; put 'out=__getdoc ;'; put 'run;'; put '/* find the beginning of the text */'; put '%local start;'; put 'data _null_;'; put 'infile __getdoc lrecl=10000;'; put 'input;'; put 'start=index(_infile_,''StoredText="'');'; put 'if start then do;'; put 'call symputx("start",start+11);'; put '*putlog ''"'' _infile_ ''"'';'; put 'end;'; put 'stop;'; put '%local outeng;'; put '%if "&outloc"="0" %then %let outeng=TEMP;'; put '%else %let outeng="&outloc";'; put '%local fref;'; put '%if &outref=0 %then %let fref=%mf_getuniquefileref();'; put '%else %let fref=&outref;'; put '/* read the content, byte by byte, resolving escaped chars */'; put 'filename &fref &outeng lrecl=100000;'; put 'data _null_;'; put 'length filein 8 fileid 8;'; put 'filein = fopen("__getdoc","I",1,"B");'; put 'fileid = fopen("&fref","O",1,"B");'; put 'rec = "20"x;'; put 'length entity $6;'; put 'do while(fread(filein)=0);'; put 'x+1;'; put 'if x>&start then do;'; put 'rc = fget(filein,rec,1);'; put 'if rec=''"'' then leave;'; put 'else if rec="&" then do;'; put 'entity=rec;'; put 'do until (rec=";");'; put 'if fread(filein) ne 0 then goto getout;'; put 'rc = fget(filein,rec,1);'; put 'entity=cats(entity,rec);'; put 'end;'; put 'select (entity);'; put 'when (''&'' ) rec=''&'' ;'; put 'when (''<'' ) rec=''<'' ;'; put 'when (''>'' ) rec=''>'' ;'; put 'when (''''') rec="''" ;'; put 'when (''"'') rec=''"'' ;'; put 'when ('' '') rec=''0A''x;'; put 'when ('' '') rec=''0D''x;'; put 'when (''$'' ) rec=''$'' ;'; put 'when ('' '') rec=''09''x;'; put 'otherwise putlog "%str(WARN)ING: missing value for " entity=;'; put 'end;'; put 'rc =fput(fileid, substr(rec,1,1));'; put 'rc =fwrite(fileid);'; put 'end;'; put 'else do;'; put 'rc =fput(fileid,rec);'; put 'rc =fwrite(fileid);'; put 'end;'; put 'end;'; put 'end;'; put 'getout:'; put 'rc=fclose(filein);'; put 'rc=fclose(fileid);'; put 'run;'; put '%if &showlog=YES %then %do;'; put 'data _null_;'; put 'infile &fref lrecl=32767 end=last;'; put 'input;'; put 'if _n_=1 then putlog ''>>stpcodeBEGIN<<'';'; put 'putlog _infile_;'; put 'if last then putlog ''>>stpcodeEND<<'';'; put 'run;'; put '%end;'; put 'filename __getdoc clear;'; put '%if &outref=0 %then %do;'; put 'filename &fref clear;'; put '%end;'; put '%mend mm_getstpcode;'; put '%macro dc_getsettings();'; put '%global _program;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(syscc=&syscc on entry (&syswarningtext &syserrortext))'; put ')'; put '%if %length(&_PROGRAM)>1 %then %let root=&_program;'; put '%else %do;'; put '%global _metauser;'; put '%let _metauser=&sysuserid;'; put '/* to mimic a "real" _program we need to give a dummy role and stp name */'; put '%let root=/dummyRole/dummyName;'; put '%end;'; put '/* the DC precode is stored in the Admin folder in the root of'; put 'the project. Lets find that root. */'; put '%put &=root;'; put '%let root=%mf_getapploc();'; put '%put &=root;'; put '/* Now we know the root location we can retrieve the params */'; put '%let temploc=%sysfunc(pathname(work))/settings.txt;'; put '%mm_getstpcode(tree=&root/services/public'; put ',name=Data_Controller_Settings'; put ',outloc=&temploc'; put ')'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Unable to run getstpcode)'; put ')'; put 'filename _getsets "&temploc" lrecl=2000;'; put '/*'; put 'Do not use mp_include here - this puts a copy in every service, which creates'; put 'compilation problems when calling services from mp_include'; put '*/'; put '%inc _getsets/source2;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&sysmacroname'; put ',msg=%str(Problem running &sysmacroname (&syswarningtext &syserrortext))'; put ')'; put '%mend dc_getsettings;'; put '%macro mf_fmtdttm('; put ')/*/STORE SOURCE*/;'; put '%if "&sysver"="9.2" or "&sysver"="9.3"'; put 'or ("&sysver"="9.4" and "%substr(&SYSVLONG,9,1)" le "3")'; put 'or "%substr(&sysver,1,1)"="4"'; put 'or "%substr(&sysver,1,1)"="5"'; put '%then %do;DATETIME19.3%end;'; put '%else %do;E8601DT26.6%end;'; put '%mend mf_fmtdttm;'; put '%macro mf_getuser('; put ')/*/STORE SOURCE*/;'; put '%local user;'; put '%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;'; put '%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;'; put '%let user=&SYS_COMPUTE_SESSION_OWNER;'; put '%end;'; put '%else %if %symexist(_metaperson) %then %do;'; put '%if %length(&_metaperson)=0 %then %let user=&sysuserid;'; put '/* sometimes SAS will add @domain extension - remove for consistency */'; put '/* but be sure to quote in case of usernames with commas */'; put '%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));'; put '%end;'; put '%else %let user=&sysuserid;'; put '%quote(&user)'; put '%mend mf_getuser;'; put '%macro mp_init(prefix=SASJS'; put ')/*/STORE SOURCE*/;'; put '%if %symexist(SASJS_PREFIX) %then %return; /* only run once */'; put '%global'; put 'SASJS_PREFIX /* the ONLY hard-coded global macro variable in SASjs */'; put '&prefix._FUNCTIONS /* used in mcf_init() to track core function compilation */'; put '&prefix._INIT_NUM /* initialisation time as numeric */'; put '&prefix._INIT_DTTM /* initialisation time in E8601DT26.6 format */'; put '&prefix.WORK /* avoid typing %sysfunc(pathname(work)) every time */'; put ';'; put '%let sasjs_prefix=&prefix;'; put 'data _null_;'; put 'dttm=datetime();'; put 'call symputx("&sasjs_prefix._init_num",dttm,''g'');'; put 'call symputx("&sasjs_prefix._init_dttm",put(dttm,E8601DT26.6),''g'');'; put 'call symputx("&sasjs_prefix.work",pathname(''WORK''),''g'');'; put 'run;'; put 'options'; put 'compress=CHAR /* default is none so ensure we have something! */'; put 'datastmtchk=ALLKEYWORDS /* protection from overwriting input datasets */'; put 'errorcheck=STRICT /* catch errs in libname/filename statements */'; put 'fmterr /* ensure err when a format cannot be found */'; put 'mergenoby=%str(ERR)OR /* throw err when a merge has no BY variables */'; put 'missing=. /* changing this can cause hard to detect errs */'; put 'noquotelenmax /* avoid warnings for long strings */'; put 'noreplace /* avoid overwriting permanent datasets */'; put 'ps=max /* reduce log size slightly */'; put 'ls=max /* reduce log even more and avoid word truncation */'; put 'validmemname=COMPATIBLE /* avoid special characters etc in table names */'; put 'validvarname=V7 /* avoid special characters etc in variable names */'; put 'varinitchk=%str(ERR)OR /* avoid data mistakes from variable name typos */'; put 'varlenchk=%str(ERR)OR /* fail hard if truncation (data loss) can result */'; put '%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5" %then %do;'; put 'noautocorrect /* disallow misspelled procedure names */'; put 'dsoptions=note2err /* undocumented - convert bad NOTEs to ERRs */'; put '%end;'; put ';'; put '%mend mp_init;'; put '%macro mpeinit(fetch=YES);'; put '%global mpeinit'; put 'mpeadmins /* group with unrestricted Meditor access */'; put 'mpelocapprovals /* location for landing and staging files */'; put 'mpelib /* location of configuration tables for DC */'; put 'dc_repo_users /* location of user / group metadata */'; put 'dc_licence_key /* extracted in dc_getsettings */'; put 'dc_activation_key /* extracted in dc_getsettings */'; put 'dc_locale /* extracted in dc_getsettings */'; put 'dc_dttmtfmt /* can be overridden in dc_getsettings */'; put '_debug'; put ';'; put '%if &mpeinit=1 %then %return;'; put '%else %let mpeinit=1;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(Problem on service startup (&syswarningtext &syserrortext))'; put ')'; put '%mp_init()'; put '%if &fetch=YES %then %do;'; put '%webout(FETCH)'; put '%end;'; put '%global _CLIENTNAME;'; put '%mp_abort(iftrue= (&_CLIENTNAME=SAS Enterprise Guide)'; put ',mac=&_program..sas'; put ',msg=%str(Data Controller is a web app and should not be executed from EG)'; put ')'; put 'options urlencoding=utf8 nobomfile lrecl=32767;'; put '%let perf=%sysfunc(datetime());'; put '%put perfdiff: 0;'; put '%let dc_locale=SYSTEM; /* default if not set */'; put '/**'; put '* E8601DT26.6 has widest database support - but not all SAS flavours can'; put '* handle it. Override in the settings STP if needed.'; put '*/'; put 'data _null_;'; put 'dc_dttmtfmt=''"%sysfunc(datetime(),''!!"%mf_fmtdttm()"!!'')"dt'';'; put 'call symputx(''dc_dttmtfmt'',dc_dttmtfmt);'; put 'put dc_dttmtfmt=;'; put 'run;'; put '%put &=dc_dttmtfmt;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc prior to dc_getsettings)'; put ')'; put '%dc_getsettings()'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program'; put ',msg=%str(syscc=&syscc after dc_getsettings)'; put ')'; put 'data _null_;'; put 'set &DC_LIBREF..mpe_config(where=('; put 'var_scope="DC"'; put 'and &dc_dttmtfmt lt tx_to'; put 'and var_active=1'; put '));'; put 'call symputx(var_name,var_value,''G'');'; put 'putlog var_name "=" var_value;'; put 'run;'; put '%let mpelib=&dc_libref;'; put '%let mpeadmins=&dc_admin_group;'; put '%let mpelocapprovals=&dc_staging_area;'; put '%let dc_repo_users=&dc_repo_users;'; put '%if &dc_locale ne SYSTEM %then %do;'; put 'options locale=&dc_locale;'; put '%end;'; put '%mp_abort(iftrue= (&syscc ne 0)'; put ',mac=&_program..sas'; put ',msg=%str(Problem during compilation or with STP precode (&syswarningtext))'; put ')'; put '%mend mpeinit;'; put '%macro mf_mval(var);'; put '%if %symexist(&var) %then %do;'; put '%superq(&var)'; put '%end;'; put '%mend mf_mval;'; put '%macro mf_trimstr(basestr,trimstr);'; put '%local baselen trimlen trimval;'; put '/* return if basestr is shorter than trimstr (or 0) */'; put '%let baselen=%length(%superq(basestr));'; put '%let trimlen=%length(%superq(trimstr));'; put '%if &baselen < &trimlen or &baselen=0 %then %return;'; put '/* obtain the characters from the end of basestr */'; put '%let trimval=%qsubstr(%superq(basestr)'; put ',%length(%superq(basestr))-&trimlen+1'; put ',&trimlen);'; put '/* compare and if matching, chop it off! */'; put '%if %superq(basestr)=%superq(trimstr) %then %do;'; put '%return;'; put '%end;'; put '%else %if %superq(trimval)=%superq(trimstr) %then %do;'; put '%qsubstr(%superq(basestr),1,%length(%superq(basestr))-&trimlen)'; put '%end;'; put '%else %do;'; put '&basestr'; put '%end;'; put '%mend mf_trimstr;'; put '%macro mf_getplatform(switch'; put ')/*/STORE SOURCE*/;'; put '%local a b c;'; put '%if &switch.NONE=NONE %then %do;'; put '%if %symexist(sasjsprocessmode) %then %do;'; put '%if &sasjsprocessmode=Stored Program %then %do;'; put 'SASJS'; put '%return;'; put '%end;'; put '%end;'; put '%if %symexist(sysprocessmode) %then %do;'; put '%if "&sysprocessmode"="SAS Object Server"'; put 'or "&sysprocessmode"= "SAS Compute Server" %then %do;'; put 'SASVIYA'; put '%end;'; put '%else %if "&sysprocessmode"="SAS Stored Process Server"'; put 'or "&sysprocessmode"="SAS Workspace Server"'; put '%then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if %symexist(_metaport) or %symexist(_metauser) %then %do;'; put 'SASMETA'; put '%return;'; put '%end;'; put '%else %do;'; put 'BASESAS'; put '%return;'; put '%end;'; put '%end;'; put '%else %if &switch=SASSTUDIO %then %do;'; put '/* return the version of SAS Studio else 0 */'; put '%if %mf_mval(_CLIENTAPP)=%str(SAS Studio) %then %do;'; put '%let a=%mf_mval(_CLIENTVERSION);'; put '%let b=%scan(&a,1,.);'; put '%if %eval(&b >2) %then %do;'; put '&b'; put '%end;'; put '%else 0;'; put '%end;'; put '%else 0;'; put '%end;'; put '%else %if &switch=VIYARESTAPI %then %do;'; put '%mf_trimstr(%sysfunc(getoption(servicesbaseurl)),/)'; put '%end;'; put '%mend mf_getplatform;'; put '%macro mpeterm();'; put '%local oldloc;'; put 'data _null_;'; put 'if symexist(''SYSPRINTTOLOG'') then oldloc=symget(''SYSPRINTTOLOG'');'; put 'else oldloc=getoption(''LOG'');'; put 'if subpad(oldloc,1,1) not in (''"'',"''",'' '') then oldloc=quote(cats(oldloc));'; put 'call symputx(''oldloc'',oldloc,''l'');'; put 'run;'; put '%if %length(&oldloc)>0 %then %do;'; put 'proc printto log=log;'; put 'run;'; put 'data _null_;'; put 'infile &oldloc;'; put 'input; putlog _infile_;'; put 'run;'; put '%end;'; put '%if %sysfunc(exist(&dc_libref..mpe_requests)) and %mf_getplatform() ne SASVIYA'; put '%then %do;'; put 'data ;'; put 'if 0 then set &dc_libref..mpe_requests;'; put 'request_dttm=%sysfunc(datetime());'; put 'request_user="%mf_getuser()";'; put 'request_service="%scan(&_program,-2,/)/%scan(&_program,-1,/)";'; put 'request_params='''';'; put 'output;stop;'; put 'proc append base=&dc_libref..mpe_requests data=&syslast force nowarn;'; put 'run;'; put '%end;'; put '%mend mpeterm;'; put '* SAS Macros end;'; put '* SAS Includes start;'; put '* SAS Includes end;'; put '* Binary Files start;'; put '* Binary Files end;'; put '* ServiceInit start;'; put 'options noquotelenmax ps=max;'; put '/* create dummy macros to prevent stpbegin / stpend from corrupting sessions */'; put '%macro stpbegin();'; put '%put NOTE: the STPBEGIN macro should not be used for web apps!;'; put '%mend stpbegin;'; put '%macro stpend();'; put '%put NOTE: the STPEND macro should not be used for web apps!;'; put '%mend stpend;'; put '* ServiceInit end;'; put '* Service start;'; put '/**'; put '@file'; put '@brief Generic validator for editable libraries'; put '@details The input table is simply one row from the target table in table'; put 'called "work.source_row".'; put 'Available macro variables:'; put '@li MPELIB - The DC control library'; put '@li LIBDS - The library.dataset being filtered'; put '@li VARIABLE_NM - The column being filtered'; put '

Service Inputs

'; put '
work.source_row
'; put '|libref:$8|'; put '|somelib|'; put '

Service Outputs

'; put 'The values provided below are generic samples - we encourage you to replace'; put 'these with realistic values in your own deployments.'; put '
DYNAMIC_VALUES
'; put 'The RAW_VALUE column may be charactor or numeric. If DISPLAY_INDEX is not'; put 'provided, it is added automatically.'; put '|DISPLAY_INDEX:best.|DISPLAY_VALUE:$|RAW_VALUE|'; put '|---|---|---|'; put '|1|$77.43|77.43|'; put '|2|$88.43|88.43|'; put '
DYNAMIC_EXTENDED_VALUES
'; put 'This table is optional. If provided, it will map the DISPLAY_INDEX from the'; put 'DYNAMIC_VALUES table to additional column/value pairs, that will be used to'; put 'populate dropdowns for _other_ cells in the _same_ row.'; put 'Should be used sparingly! The use of large tables here can slow down the'; put 'browser.'; put '|DISPLAY_INDEX:best.|EXTRA_COL_NAME:$32.|DISPLAY_VALUE:$|DISPLAY_TYPE:$1.|RAW_VALUE_NUM|RAW_VALUE_CHAR:$5000|'; put '|---|---|---|---|---|---|'; put '|1|DISCOUNT_RT|"50%"|N|0.5|` `|'; put '|1|DISCOUNT_RT|"40%"|N|0.4|` `|'; put '|1|DISCOUNT_RT|"30%"|N|0.3|` `|'; put '|1|CURRENCY_SYMBOL|"GBP"|C|` `|"GBP"|'; put '|1|CURRENCY_SYMBOL|"RSD"|C|` `|"RSD"|'; put '|2|DISCOUNT_RT|"50%"|N|0.5|` `|'; put '|2|DISCOUNT_RT|"40%"|N|0.4|` `|'; put '|2|CURRENCY_SYMBOL|"EUR"|C|` `|"EUR"|'; put '|2|CURRENCY_SYMBOL|"HKD"|C|` `|"HKD"|'; put '**/'; put '/* send back the raw and formatted values */'; put 'data _null_;'; put 'var=symget(''variable_nm'');'; put 'libds=symget(''libds'');'; put 'if libds="&mpelib..MPE_EXCEL_CONFIG" and var=''XL_TABLE'' then do;'; put 'call symputx(''srccol'',''XL_LIBREF'');'; put 'end;'; put 'else call symputx(''srccol'',''libref'');'; put 'run;'; put 'proc sql;'; put 'create table work.DYNAMIC_VALUES as'; put 'select distinct dsn as display_value,'; put 'upcase(dsn) as raw_value'; put 'from &mpelib..mpe_tables'; put '(where=(&dc_dttmtfmt. < tx_to))'; put 'where libref in (select &srccol from work.source_row)'; put 'order by 1;'; put '* Service end;'; run; %mm_createwebservice(path=&appLoc/&path, name=&service, code=sascode, server=&serverName, replace=yes) filename sascode clear; options notes; data _null_; format url $256.; rc=METADATA_GETURI("Stored Process Web App",url); url=coalescec(url,"localhost/SASStoredProcess"); urlEscaped = tranwrd(trim(url)," ","%20"); putlog "NOTE: SASjs Streaming App Created! Check it out here:" ; putlog "NOTE- ";putlog "NOTE- ";putlog "NOTE- ";putlog "NOTE- "; putlog "NOTE- " urlEscaped +(-1) "?_program=&appLoc/services/clickme" ; putlog "NOTE- ";putlog "NOTE- ";putlog "NOTE- ";putlog "NOTE- "; run;