Written by

Senior Cloud Architect at InterSystems
Article Eduard Lebedyuk · Jan 7 1m read

Remove deleted classes after import

When you deploy code from a repo, class (file) deletion might not be reflected by your CICD system.
Here's a simple one-liner to automatically delete all classes in a specified package that have not been imported. It can be easily adjusted for a variety of adjunct tasks:

set packages = "USER.*,MyCustomPackage.*"set dir = "C:\InterSystems\src\"set sc = $SYSTEM.OBJ.LoadDir(dir,"ck", .err, 1, .loaded)
set sc = $SYSTEM.OBJ.Delete(packages _ ",'" _ $LTS($LI($LFS(loaded_",",".cls,"), 1, *-1), ",'"),, .err2)

The first command compiles classes and also returns a list of loaded classes. The second command deletes all classes from specified packages, except for the classes loaded just before that.

Comments

Evgeny Shvarov · Jan 8

What about the data (globals) left related to deleted classes? SHould it be deleted as well?

0
Eduard Lebedyuk  Jan 8 to Evgeny Shvarov

If the class to delete is persistent, include the 'e' flag or '/deleteextent' qualifier to delete the extent data and extent metadata.

0
Eduard Lebedyuk  Jan 10 to Ben Spead

Thank you, Ben!

LoadDir returns loaded list with extensions (.cls) but Delete accepts only wildcards without extensions.

If not for that minor detail, it would be a completely trivial two-liner. But accounting for this requires some interesting $LFS/$LTS wrangling in the middle.

0
Evgeny Shvarov · Jan 10

Isn't it more safe to have a clear namesepace/database for the code base with every CI/CD phase? 

0
Eduard Lebedyuk  Jan 10 to Evgeny Shvarov

How do you keep production running during update in that case?

0
Corentin Blondeau · Jan 13

 Hello,
I have an other way to do it, it's a (little) bigger.
It doesn't need to indicate the package at the beginning.
The little addition is that this method can return all the classes that are differents without delete them.
 

/// Take as parameters /// ClassList whitch is the list return by ImportDir/// pDeleteIfExist : boolean to know if we have to delete classes/// listClassDiffer : lists of differents classes between the directory from ImportDir and InterSystems/// Return listClassDiffer; if error, return an exceptionClassMethod SynchroClass(ByRef classList As%String, pDeleteIfExist As%Boolean = 0, Output listClassDiffer As%ListOfDataTypes) As%Status
{
	Set status = $$$OK#dim exception As%Exception.AbstractExceptionSet tKey = ""Set listclass = ##class(%ListOfDataTypes).%New()
	Set listClassDiffer = ##class(%ListOfDataTypes).%New()

	Try{
		// insert the differentes classes from classList in a new list with a "while"Do{
			Set tKey=$ORDER(classList(tKey))
			//Write " tkey " ,tKey,!Do listclass.Insert(tKey)
		}While(tKey'="")

		Set SQLquery = "SELECT Name "Set SQLquery = SQLquery _ " FROM %Library.RoutineMgr_StudioOpenDialog('*.cls',1,1,0,1,0,0,,,0) "Set stmt = ##class(%SQL.Statement).%New()
		Set status = stmt.%Prepare(SQLquery)		
		Set rset = stmt.%Execute()
		#dim rset as%SQL.StatementResult/* get the table of result of the SQL request as classes of server and compare them to the classes from listclass
		if classes are different, we add them to the zreturn's parameter and we delete them */While rset.%Next(){
			Set name = rset.%Get("Name")
	
			//Write " name class and index ", name,idx, !If (listclass.Find(name) = ""){
				Write !,name
				Set status = listClassDiffer.Insert(name)
				$$$ThrowOnError(status)

				If (pDeleteIfExist){
					Set status = $SYSTEM.OBJ.Delete(name,"e")
					$$$ThrowOnError(status)
				}				
			}		
		}
	}Catch exception {
		Set status = exception.AsStatus()
	}
	Quit status
}
0
Eduard Lebedyuk  Jan 24 to Corentin Blondeau

%Library.RoutineMgr_StudioOpenDialog is a useful query!

Originally I started writing something similar, but went into %Dictionary package instead, which of course created issues for me about System and Mapped classes, so I decided to go a different way (ended with what's presented in this article).

With your approach I'll advice first building a list of classes to delete and then passing it to $SYSTEM.OBJ.Delete - it will be faster than deleting classes one by one. That said usually deletions are small in number (or at least in frequency) so speed does not really matter here.

0