From e3b8ee69a906d53a4d6b3f623b8bc61ca9302f68 Mon Sep 17 00:00:00 2001 From: Trevor Moody Date: Wed, 18 Mar 2026 14:09:18 +0000 Subject: [PATCH 1/2] feat(mf_getvalue)!: specify row and raise SYSCC value upon issue --- base/mf_getvalue.sas | 35 ++++++++++--- tests/base/mf_getvalue.test.sas | 91 +++++++++++++++++++++++++++++++++ 2 files changed, 118 insertions(+), 8 deletions(-) create mode 100644 tests/base/mf_getvalue.test.sas diff --git a/base/mf_getvalue.sas b/base/mf_getvalue.sas index 63bfb340..af56b5be 100644 --- a/base/mf_getvalue.sas +++ b/base/mf_getvalue.sas @@ -1,7 +1,8 @@ /** @file - @brief Retrieves a value from a dataset. If no filter supplied, then first - record is used. + @brief Retrieves a value from a dataset. Returned value is fetched from the + 'fetchobs=' record (row 1 by default), after applying the optional filter. + @details Be sure to %quote() your where clause. Example usage: %put %mf_getvalue(sashelp.class,name,filter=%quote(age=15)); @@ -16,21 +17,39 @@ @param [in] libds dataset to query @param [in] variable the variable which contains the value to return. @param [in] filter= (1) contents of where clause + @param [in] fetchobs= (1) observation to fetch. NB: Filter applies first. @version 9.2 @author Allan Bowe **/ -%macro mf_getvalue(libds,variable,filter=1 +%macro mf_getvalue(libds,variable,filter=1,fetchobs=1 )/*/STORE SOURCE*/; - %if %mf_getattrn(&libds,NLOBS)>0 %then %do; - %local dsid rc &variable; - %let dsid=%sysfunc(open(&libds(where=(&filter)))); + %local dsid; + + %let dsid=%sysfunc(open(&libds(where=(&filter)))); + %if (&dsid) %then %do; + %local rc &variable; %syscall set(dsid); - %let rc = %sysfunc(fetch(&dsid)); + %let rc = %sysfunc(fetchobs(&dsid,&fetchobs)); + %if (&rc ne 0) %then %do; + %put NOTE: Problem reading obs &fetchobs from &libds..; + %put %sysfunc(sysmsg()); + /* Coerce an rc value of -1 (read past end of data) to a 4 + that, in SAS condition code terms, represents the sysmsg + w@rning it generates. */ + %if &rc eq -1 %then %let rc = 4; + /* And update SYSCC if the &rc value is higher */ + %let syscc = %sysfunc(max(&syscc,&rc)); + %end; %let rc = %sysfunc(close(&dsid)); %trim(&&&variable) %end; -%mend mf_getvalue; \ No newline at end of file + %else %do; + %put %sysfunc(sysmsg()); + %let syscc = %sysfunc(max(&syscc,%sysfunc(sysrc()))); + %end; + +%mend mf_getvalue; diff --git a/tests/base/mf_getvalue.test.sas b/tests/base/mf_getvalue.test.sas new file mode 100644 index 00000000..15d45f55 --- /dev/null +++ b/tests/base/mf_getvalue.test.sas @@ -0,0 +1,91 @@ +/** + @file + @brief Testing mf_getvalue macro + +

SAS Macros

+ @li mf_getvalue.sas + @li mp_assert.sas + @li mp_assertscope.sas + +**/ + +data work.test_data; + do i = 1 to 10; + output; + end; + stop; +run; + +/* - Test 1 - + Get value from default first observation. + No filter. +*/ +%mp_assertscope(SNAPSHOT) +%let test_value=%mf_getvalue(work.test_data,i); +%mp_assertscope(COMPARE,ignorelist=test_value) + +%mp_assert( + iftrue=(&test_value=1 and &syscc eq 0), + desc=Basic test fetching value from default first obs, + outds=work.test_results +) + +/* - Test 2 - + Get value from 10th observation. + No filter. +*/ +%let test_value=%mf_getvalue(work.test_data,i,fetchobs=10); +%mp_assert( + iftrue=(&test_value=10 and &syscc eq 0), + desc=Test fetching value from specifically the 10th row, + outds=work.test_results +) + +/* - Test 3 - + Get value from default first observation. + With filter. +*/ +%let test_value=%mf_getvalue(work.test_data,i,filter=(i>4)); +%mp_assert( + iftrue=(&test_value=5 and &syscc eq 0), + desc=Test fetching value from default row of filtered data, + outds=work.test_results +) + +/* - Test 4 - + Get value from specified observation. + With filter. +*/ +%let test_value=%mf_getvalue(work.test_data,i,filter=(i>4),fetchobs=5); +%mp_assert( + iftrue=(&test_value=9 and &syscc eq 0), + desc=Test fetching value from 5th row of filtered data, + outds=work.test_results +) + +/* - Test 5 - + Get value from default observation. + Filter removes all rows. This simulates providing an empty dataset + or specifying an observation number beyond the set returned by the filter. +*/ +%let test_value=%mf_getvalue(work.test_data,i,filter=(i>10)); +%mp_assert( + iftrue=(&test_value=%str() and &syscc eq 4), + desc=Test fetching value from 1st row of empty (filtered) data, + outds=work.test_results +) + +%let syscc = 0; /* Reset w@rning To ensure confidence in next test */ + +/* - Test 6 - + Get value from default observation. + Dataset does not exist. +*/ +%let test_value=%mf_getvalue(work.test_data_x,i); +%mp_assert( + iftrue=(&test_value=%str() and &syscc gt 0), + desc=Test fetching value from 1st row of non-existent data, + outds=work.test_results +) + +%let syscc = 0; /* To reset expected error and allow test job to exit clean. */ From ae7f93aa4e489691f7efea4d98a89662e79e6e3a Mon Sep 17 00:00:00 2001 From: github-actions Date: Wed, 18 Mar 2026 14:10:05 +0000 Subject: [PATCH 2/2] chore: updating all.sas --- all.sas | 36 ++++++++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/all.sas b/all.sas index 2cdf56c3..3f1090cf 100644 --- a/all.sas +++ b/all.sas @@ -1215,8 +1215,9 @@ or %index(&pgm,/tests/testteardown) %mend mf_getuser; /** @file - @brief Retrieves a value from a dataset. If no filter supplied, then first - record is used. + @brief Retrieves a value from a dataset. Returned value is fetched from the + 'fetchobs=' record (row 1 by default), after applying the optional filter. + @details Be sure to %quote() your where clause. Example usage: %put %mf_getvalue(sashelp.class,name,filter=%quote(age=15)); @@ -1231,24 +1232,43 @@ or %index(&pgm,/tests/testteardown) @param [in] libds dataset to query @param [in] variable the variable which contains the value to return. @param [in] filter= (1) contents of where clause + @param [in] fetchobs= (1) observation to fetch. NB: Filter applies first. @version 9.2 @author Allan Bowe **/ -%macro mf_getvalue(libds,variable,filter=1 +%macro mf_getvalue(libds,variable,filter=1,fetchobs=1 )/*/STORE SOURCE*/; - %if %mf_getattrn(&libds,NLOBS)>0 %then %do; - %local dsid rc &variable; - %let dsid=%sysfunc(open(&libds(where=(&filter)))); + %local dsid; + + %let dsid=%sysfunc(open(&libds(where=(&filter)))); + %if (&dsid) %then %do; + %local rc &variable; %syscall set(dsid); - %let rc = %sysfunc(fetch(&dsid)); + %let rc = %sysfunc(fetchobs(&dsid,&fetchobs)); + %if (&rc ne 0) %then %do; + %put NOTE: Problem reading obs &fetchobs from &libds..; + %put %sysfunc(sysmsg()); + /* Coerce an rc value of -1 (read past end of data) to a 4 + that, in SAS condition code terms, represents the sysmsg + w@rning it generates. */ + %if &rc eq -1 %then %let rc = 4; + /* And update SYSCC if the &rc value is higher */ + %let syscc = %sysfunc(max(&syscc,&rc)); + %end; %let rc = %sysfunc(close(&dsid)); %trim(&&&variable) %end; -%mend mf_getvalue;/** + %else %do; + %put %sysfunc(sysmsg()); + %let syscc = %sysfunc(max(&syscc,%sysfunc(sysrc()))); + %end; + +%mend mf_getvalue; +/** @file @brief Returns number of variables in a dataset @details Useful to identify those renagade datasets that have no columns!