Question
Patrik Spisak · Nov 4, 2022

Recursive ClassMethod

How do I create recursive classmethod which stop all methods from running when condition are meet?

ClassMethod workpieceUnit(mainArticle As %String, article As %String) As %String
{
	
	&SQL(SELECT unit->unitCode INTO :unitCode FROM production_article.composition WHERE mainArticle = :mainArticle AND article = :article)
	
	IF SQLCODE = 0
	{
		W !, "Has Value"
		QUIT unitCode
	}
	ELSE
	{
		SET sql = "SELECT article, unit->unitCode,  production_article.composition_sqlArticleCode(article, type) AS articleCode FROM production_article.composition WHERE mainArticle = "_mainArticle_" AND type = 1",
		 	 rs = ##class(%SQL.Statement).%ExecDirect(,sql)
		 
		WHILE rs.%Next()
		{
			W !, "BEGENING OF LOOP FOR: "_rs.articleCode
			IF (rs.article = article)
			{
				SET unitCode = rs.unitCode
				QUIT
			}
			
			// CHECK IF WORKPIECE HAS COMPOSITION
			&SQL(SELECT COUNT(*) INTO :composition FROM production_article.composition WHERE mainArticle = :rs.article)
			
			IF (composition > 0)
			{
				W !, "START RECURSIVE"
				DO ..workpieceUnit(rs.article, article)
			}
		}
			
		W !, "END OF LOOP"
		QUIT unitCode
	}
	
	QUIT
}

Output from terminal. As you can see recursive still continue even has value condition is true. So once has value is TRUE all recursive should stop. For other programming language is enough to call RETURN, but this have no impact in IRIS. 

 

BEGENING OF LOOP FOR: H2652.S
START RECURSIVE
BEGENING OF LOOP FOR: MQ0605.B
END OF LOOP
BEGENING OF LOOP FOR: H2641.B
BEGENING OF LOOP FOR: M1910.15.40
BEGENING OF LOOP FOR: GK6202
BEGENING OF LOOP FOR: GK6201.B
BEGENING OF LOOP FOR: IX08.0925
START RECURSIVE
END OF LOOP
BEGENING OF LOOP FOR: XQA1.AI12.1425.1020.C
START RECURSIVE
BEGENING OF LOOP FOR: XAL446.1250.1.AI12.L.A
START RECURSIVE
BEGENING OF LOOP FOR: AL446.1255
END OF LOOP
BEGENING OF LOOP FOR: XAL446.1250.1.AI12.R.A
START RECURSIVE
BEGENING OF LOOP FOR: AL446.1255
END OF LOOP
BEGENING OF LOOP FOR: AL448.1024
BEGENING OF LOOP FOR: XAL447.0970.A
START RECURSIVE
BEGENING OF LOOP FOR: AL447.0975
END OF LOOP
BEGENING OF LOOP FOR: H2640.A
BEGENING OF LOOP FOR: H2641.C
BEGENING OF LOOP FOR: H2650.A
BEGENING OF LOOP FOR: H2701.1020
BEGENING OF LOOP FOR: XAL465.1110.A0
START RECURSIVE
Has Value
BEGENING OF LOOP FOR: XAL465.1110.A1
START RECURSIVE
BEGENING OF LOOP FOR: XAL465.1110.A0
START RECURSIVE
Has Value
END OF LOOP
BEGENING OF LOOP FOR: XAL465.1110.A2
START RECURSIVE
BEGENING OF LOOP FOR: XAL465.1110.A0
START RECURSIVE
Has Value
END OF LOOP
BEGENING OF LOOP FOR: H2707.R.S
START RECURSIVE
BEGENING OF LOOP FOR: H2707
BEGENING OF LOOP FOR: H2698
BEGENING OF LOOP FOR: H2697
END OF LOOP
BEGENING OF LOOP FOR: H2707.L.S
START RECURSIVE
BEGENING OF LOOP FOR: H2707
BEGENING OF LOOP FOR: H2698
BEGENING OF LOOP FOR: H2697
END OF LOOP
END OF LOOP
END OF LOOP
Product version: IRIS 2022.1
$ZV: 2022.1 (209U)
0
0 260
Discussion (6)2
Log in or sign up to continue

It's a bit hard to follow the output. I think things will become clearer if you add "mainArticle" and "article" to every WRITE statement, for example:

W !, $G(mainArticle),"-",$G(article)," Has Value"
...
W !, $G(mainArticle),"-",$G(article)," BEGENING OF LOOP FOR: "_rs.articleCode
...
W !, $G(mainArticle),"-",$G(article)," START RECURSIVE"
...
W !, $G(mainArticle),"-",$G(article)," END OF LOOP"

Hi Patrik,

For a better readability of code, switch the Quit command by Return in the portions of your code. where you relay want to return.

For example in this codesnippet in the While:

			W !, "BEGENING OF LOOP FOR: "_rs.articleCode
			IF (rs.article = article)
			{
				SET unitCode = rs.unitCode
				QUIT
			}

This Quit, only exit form While not from Method. Other problem is that the last line with Quit without argument can cause a <FUNCTION> error because doesn't return any value.

If I'm not miss understood, you can add a third parameter to use internally of method to check if the method needs to run or not. For example if the condition to stop is the unitCode is not blank change the signature of method and add the following line in the beginning of method:

A possible version to method is:

ClassMethod workpieceUnit(mainArticle As %String, article As %String, unitCode As %string = "") As %String
{
    Return:(unitCode '= "") unitCode
    
	&SQL(SELECT unit->unitCode INTO :unitCode FROM production_article.composition WHERE mainArticle = :mainArticle AND article = :article)
	
	IF SQLCODE = 0
	{
		W !, "Has Value"
		Return unitCode
	}
	ELSE
	{
		SET sql = "SELECT article, unit->unitCode,  production_article.composition_sqlArticleCode(article, type) AS articleCode FROM production_article.composition WHERE mainArticle = "_mainArticle_" AND type = 1",
		 	 rs = ##class(%SQL.Statement).%ExecDirect(,sql)
		 
		WHILE rs.%Next()
		{
			W !, "BEGENING OF LOOP FOR: "_rs.articleCode
			IF (rs.article = article)
			{
				SET unitCode = rs.unitCode
				Return unitCode
			}
			
			// CHECK IF WORKPIECE HAS COMPOSITION
			&SQL(SELECT COUNT(*) INTO :composition FROM production_article.composition WHERE mainArticle = :rs.article)
			
			IF (composition > 0)
			{
				W !, "START RECURSIVE"
				DO ..workpieceUnit(rs.article, article)
			}
		}
			
		W !, "END OF LOOP"
		Return unitCode
	}
	
	Return ""
}

Hi,

I replaced all QUIT with Return and add conditional check based on third parameter at the begening of the ClassMethod as you suggest.

When I tried to run, the output is excatly the same as with QUIT. Function still running even value has been found.

This is output from terminal where I added $G(mainArticle),"-",$G(article) from suggestion above.

9430954-9428572 BEGENING OF LOOP FOR: H2652.S
9430954-9428572 START RECURSIVE
9408921-9428572 BEGENING OF LOOP FOR: MQ0605.B
9408921-9428572 END OF LOOP
9430954-9428572 BEGENING OF LOOP FOR: H2641.B
9430954-9428572 BEGENING OF LOOP FOR: M1910.15.40
9430954-9428572 BEGENING OF LOOP FOR: GK6202
9430954-9428572 BEGENING OF LOOP FOR: GK6201.B
9430954-9428572 BEGENING OF LOOP FOR: IX08.0925
9430954-9428572 START RECURSIVE
9408925-9428572 END OF LOOP
9430954-9428572 BEGENING OF LOOP FOR: XQA1.AI12.1425.1020.C
9430954-9428572 START RECURSIVE
9413847-9428572 BEGENING OF LOOP FOR: XAL446.1250.1.AI12.L.A
9413847-9428572 START RECURSIVE
9408591-9428572 BEGENING OF LOOP FOR: AL446.1255
9408591-9428572 END OF LOOP
9413847-9428572 BEGENING OF LOOP FOR: XAL446.1250.1.AI12.R.A
9413847-9428572 START RECURSIVE
9408593-9428572 BEGENING OF LOOP FOR: AL446.1255
9408593-9428572 END OF LOOP
9413847-9428572 BEGENING OF LOOP FOR: AL448.1024
9413847-9428572 BEGENING OF LOOP FOR: XAL447.0970.A
9413847-9428572 START RECURSIVE
9408603-9428572 BEGENING OF LOOP FOR: AL447.0975
9408603-9428572 END OF LOOP
9413847-9428572 BEGENING OF LOOP FOR: H2640.A
9413847-9428572 BEGENING OF LOOP FOR: H2641.C
9413847-9428572 BEGENING OF LOOP FOR: H2650.A
9413847-9428572 BEGENING OF LOOP FOR: H2701.1020
9413847-9428572 BEGENING OF LOOP FOR: XAL465.1110.A0
9413847-9428572 START RECURSIVE
9421986-9428572 Has Value
9413847-9428572 BEGENING OF LOOP FOR: XAL465.1110.A1
9413847-9428572 START RECURSIVE
9421987-9428572 BEGENING OF LOOP FOR: XAL465.1110.A0
9421987-9428572 START RECURSIVE
9421986-9428572 Has Value
9421987-9428572 END OF LOOP
9413847-9428572 BEGENING OF LOOP FOR: XAL465.1110.A2
9413847-9428572 START RECURSIVE
9421990-9428572 BEGENING OF LOOP FOR: XAL465.1110.A0
9421990-9428572 START RECURSIVE
9421986-9428572 Has Value
9421990-9428572 END OF LOOP
9413847-9428572 BEGENING OF LOOP FOR: H2707.R.S
9413847-9428572 START RECURSIVE
9432315-9428572 BEGENING OF LOOP FOR: H2707
9432315-9428572 BEGENING OF LOOP FOR: H2698
9432315-9428572 BEGENING OF LOOP FOR: H2697
9432315-9428572 END OF LOOP
9413847-9428572 BEGENING OF LOOP FOR: H2707.L.S
9413847-9428572 START RECURSIVE
9432316-9428572 BEGENING OF LOOP FOR: H2707
9432316-9428572 BEGENING OF LOOP FOR: H2698
9432316-9428572 BEGENING OF LOOP FOR: H2697
9432316-9428572 END OF LOOP
9413847-9428572 END OF LOOP
9430954-9428572 END OF LOOP

// EDIT

I was able to QUIT only when I used global variable

ClassMethod workpieceUnit(mainArticle As %String, article As %String) As %String
{
	&SQL(SELECT unit->unitCode INTO :unitCode FROM production_article.composition WHERE mainArticle = :mainArticle AND article = :article)
	
	IF (SQLCODE = 0)
	{
		W !, $G(mainArticle),"-",$G(article)," Has Value"
		SET ^pasp("unitCode") = unitCode
		RETURN unitCode
	}
	ELSE
	{
		SET sql = "SELECT article, unit->unitCode,  production_article.composition_sqlArticleCode(article, type) AS articleCode FROM production_article.composition WHERE mainArticle = "_mainArticle_" AND type = 1",
		 	 rs = ##class(%SQL.Statement).%ExecDirect(,sql)
		 
		WHILE rs.%Next()
		{
			RETURN:$GET(^pasp("unitCode"))'="" ^pasp("unitCode")
			
			W !, $G(mainArticle),"-",$G(article)," BEGENING OF LOOP FOR: "_rs.articleCode
			
			IF (rs.article = article)
			{
				RETURN rs.unitCode	
			}
			
			// CHECK IF WORKPIECE HAS COMPOSITION
			&SQL(SELECT COUNT(*) INTO :composition FROM production_article.composition WHERE mainArticle = :rs.article)
			
			IF (composition > 0)
			{
				W !, $G(mainArticle),"-",$G(article)," START RECURSIVE"
				DO ..workpieceUnit(rs.article, article)
			}
		}
			
		W !, $G(mainArticle),"-",$G(article)," END OF LOOP"
		RETURN $GET(^pasp("unitCode"),"")
		KILL ^pasp("unitCode")
	}
	
	RETURN ""
}

This solution will work because global variable is visible on all stack levels (local variable is not).

But you'd use process private global varialble ^||pasp("unitcode") instead of  ^pasp("unitCode") which is visible from all processes and you can have collisons in multi user environment.

Adding the additional logging helped. Looking at it now, it looks like it is working as coded:

9413847-9428572 BEGENING OF LOOP FOR: XAL465.1110.A0
9413847-9428572 START RECURSIVE
9421986-9428572 Has Value
9413847-9428572 BEGENING OF LOOP FOR: XAL465.1110.A1

The call for 9413847 is looping through all results of the query. It finds that 9421986 has a composition so it calls itself recursively. The call for 9421986 finds that it already has a value so it returns control to the 9413847 loop which continues iterating.

If you want all super and sub loops to quit as soon as any sub-loop finds "has value" then you will need to check for this condition inside the loop.

Hi Patrik,

I did a mistake, when you call the method recursively you need to pass the unitCode like this:

    DO ..workpieceUnit(rs.article, article, unitCode)