Seems like a pointless exercise to me. A question to ask is what is the objective for this?

There is a quick way to do this but it depends on how accurate you want the count to be. In the Management Portal, under System Explorer, Classes there is the option to export classes to an external XML file; there is also the option to include/exclude system classes and select individual or all classes.

This will export the classes into an XML file which you can open in a text file editor to get the number of lines. Note though, the XML export adds additional lines for tags to differentiate the code sections so the count will be greater than the actual lines shown when actually editing the code in the usual editor. 

But it could be an option for you.

You need to understand the difference between $order and $query; they both handle globals/arrays differently for different purposes.
$order is the more straightforward to use (and more commonly used); it scans down a particular node of a global/array. For example for your example:

`set n=""

for{

set n=$order(^TestGlobal("Not Configured",n))

quit:n=""

; do something with n

} `

$query is more useful if the global structure is unknown or less structured. It starts at the top of the global and returns the entire subscript string at a given level. So again for your example, the first $query would return ("Not Configured","Value 1"). You could then use $QL to get the number of subscripts at that level (2 in this case), and $qs(subscripts,position) to get the value of the subscript.

set gbl="TestGlobal"
set node=$q(@gbl@(""))
w node ; this would return TestGlobal("Not Configured","Value 1")
w $ql(node) ; this would return 2
w $qs(node,1) ; this would return "not configured"
w $qs(node,2) ; this would return "Value 1"
w @node ; returns the data at that node; in this example null

$order is the more commonly used.

You need to understand how to use globals and arrays to efficiently store data, and how to traverse them using $order. Globals and arrays are handled the same except globals are store on disk (persistant/permanent) and arrays are stored in memory (volatile/temporary).

Hope this helps.

I have also looked into installing the db onto a NAS drive (but not using Docker).

From my understanding, you cannot do this with the free community version of Iris/Cache, I believe a licenced version would enable this functionality. I haven't purchased a licence to prove this as I can't justify buying a personal licence for myself.

Professionally though, where the company I work for have purchased licences, a local installation of Iris/Cache can access different db's on remote servers.

I hope that helps.

Interesting article, thanks Rob.

Have you written any documents (or aware of any documentation) that details how the globals work in the background? I know when I started coding Mumps in the early 90's, I assumed that every time you referred to a global in the code the data was being accessed directly from the disk. However I know that isn't the case and have a vague understanding that the global data is stored in data blocks stored in a b-tree structure but I would like a deeper understanding of how globals are actually stored and managed.  

This is how I would do it.  Use the following query to insert the data (note the colons before the variable names);

&sql(
        insert into tableX
            (firstname,middleName,surname)
        values
            (:firstname,:middleName,:surname)
    )

I don't know how you have your data stored, but assuming that the data is on an array, try something along these lines:

set idx=""
for {
    set idx=$order(personArray(idx))
    quit:idx=""

    set firstname=personArray(idx,"firstName")
    set middleName=personArray(idx,"middleName")
    set surname=personArray(idx,"surname")
     
    &sql(
        insert into tableX
            (firstname,middleName,surname)
        values
            (:firstname,:middleName,:surname)
    )
}

Hope this is useful.

Thanks again for the code, it does exactly what I am looking for.  However, I have amended the code to list all of the abstract methods required to be implemented for the subclass in the error message.

Class User.Abstract.AbstractChecker
{
ClassMethod Check() As %Status [ CodeMode = objectgenerator, ForceGenerate ] {
    k absCheck
    #Dim sc As %Status = $$$OK
    
    // Get class name from %compiledclass object which is an instance of a currently compiled class
    Set class = %compiledclass.Name
    
    ; get the abstract methods
    set methodCnt=..FindAbstractMethods(class,.methods)
    
    ; check whether the abstract methods have been implemented
    set methodCnt=$order(methods(""),-1)
    for i=1:1:methodCnt {
        set method=methods(i)
           set origin = $$$comMemberKeyGet(class, $$$cCLASSmethod, method, $$$cMETHorigin)
           set errtext=$$$FormatText("%1 [abstract] (origin %2)", method,origin)
           ; build array of error messages
           set absCheck("AbsNotImp",$order(absCheck("AbsNotImp",""),-1)+1)=errtext
    }
    
    ; if there are error messages build the error string
    set str=""
    if $data(absCheck("AbsNotImp")) {
        set ind="",ind=$order(absCheck("AbsNotImp",ind))
        while ind {
            set $piece(str,", ",ind)="("_ind_") "_$get(absCheck("AbsNotImp",ind))
            set ind=$order(absCheck("AbsNotImp",ind))
        }
        set str="In class '"_class_"' the following "_$order(absCheck("AbsNotImp",""),-1)_" abstract classes are to be implemented: "_str
        set sc=$$$ERROR($$$GeneralError,str)
    }
    quit $get(sc)
}

ClassMethod FindAbstractMethods(class As %String, ByRef methods As %String) As %Integer {
    kill methods
    // Iterate over class methods.
    // You can also use %class object to iterate
    Set method=$$$comMemberNext(class, $$$cCLASSmethod, "")
       
    While method'="" {
        // Get method abstract state
        Set abstract = $$$comMemberKeyGet(class, $$$cCLASSmethod, method, $$$cMETHabstract)
        if abstract{
            set methods($order(methods(""),-1)+1)=method
        }
        Set method=$$$comMemberNext(class, $$$cCLASSmethod, method)
    }    
    quit +$order(methods(""),-1)
}
}

Eduard,

Many thanks for the code it does what I am looking for. Do you know if there are plans for the compiler to check for the implementation of abstract methods instead of having to inherit code to do this?

A couple of things I have noticed:

  • If the method is inherited in a Persistent class, it won't compile.
  • It looks as though it checks for the name of the abstract method but not the number of parameters the method requires.

But otherwise that is what I am looking for, I just have to remember to add the AbstractChecker to the inheritance list.

Thanks again.

Hi Jenna,

Thanks for your response.

Fully understand your response but unless I am doing something wrong, Cache does not seem to throw an error/warning if the subclass does not implement the abstract methods defined in the superclass.

My understanding is that inherited abstract methods in subclasses must be implemented (even if the implemented method is empty) to standardise class interfaces but in my code this does not seem to be being enforced.

Thanks again,

Ken

Thanks Robert,
How do I ensure that  the USER database is part of the durable environment? I am attempting this just locally on my pc (not using any external servers so I am expecting the DB to be stored locally), so do I have to add/change anything to the Docker Run command below?

docker run --detach --publish 52773:52773 --volume /data/dur:/dur --env ISC_DATA_DIRECTORY=/dur/iconfig --name iris22 store/intersystems/iris-community:2020.1.0.199.0

Many thanks

Use $QUERY, $QL and $QS to traverse the array

ClassMethod Array()
{
; Note: $query only works on globals or
; public variables
; not local array in an object

set ^tmp(1,2,3)="Joe"
set ^tmp(1,2,3,4,1)="Paul"
set ^tmp(1,2,3,4,2)="Paula"
set ^tmp(1,2,3,5,6)="Tom" ; use indirection to traverse the global


; get the data nodes
set sub="^tmp"
set sub=$query(@sub)
while sub]""
{
      write !,sub,"=",@sub

      ; use $QL to get the number of subscripts for the data node
      set subCnt=$ql(sub)
      w !,"Number of subscripts = ",subCnt

      ; use $QS to get the subscript value
      for i=1:1:subCnt !,"subscript ",i,"=",$qs(sub,i)

      ; get the next data node
     set sub=$query(@sub)
}

Hope this helps.

Interesting question and one I hadn't considered.

If it helps, you need to think about  the type of data you are referencing as they work on different data structures:

  • $GET works on variables, arrays and global references but does not refer to object data. 
  • GetAt is a specific object function.

I cannot see any need to combine both in the same instruction, both return null if the item does not exist.

Hope this helps.
 

As we don't have source control, I have been experimenting with Tortoise SVN to create a local source control on my own PC.

This involves exporting the source files from Cache in .xml format to my source control folders and managing the commits via the Tortoise SVN file controls 

Its not ideal but it has worked for me as an experiment to have personal control for my work.

To call the method from the terminal use the following code (note that you have to initialise the variable and indicate that you are passing the value by reference by using the '.' before the relevant parameter):

set var="" do ##class(classname).DoMyWork(.var, 0) write var

Therefore in your method, the value you assign to pWorkCount will be returned to var. You don't need to return the variable, it will be automatically returned as the variable has been passed by reference.

The dot before variable name (.var) in the method call indicates that the variable is being passed by reference (by default values are passed by value), therefore I believe the "Output" keyword is optional (but it does make it clear in the method signature how the value is being passed).

I hope this makes sense; this depends on understanding the difference between passing by reference and value.