Question Corentin Blondeau · Jul 3

How get properties of a class, sorted by order in storage

Hello
I want to get the property of a class, sorted by order in storage.

I know we can use
 

Set definition = ##class(%Dictionary.ClassDefinition).%OpenId(className)
        Set listProperty = definition.Properties
        For ii = 1:1:listProperty.Count(){
                    write listProperty.GetAt(ii).Name

But using GetAt sorts the results alphabetically.

Example :

Class Test.classExtends (%SerialObject, %XML.Adaptor, %JSON.Adaptor)
{

    Property tiers As%String;Property journal As%String;
}

listProperty.GetAt(1).Name = "journal" and listProperty.GetAt(2).Name = "tiers"

But I would like it to be instead:  listProperty.GetAt(1).Name = "tiers" and listProperty.GetAt(2).Name = "journal"


Do you have a solution, please?
Corentin

Product version: IRIS 2024.1
$ZV: IRIS for Windows (x86-64) 2024.1.3 (Build 456U) Thu Jan 9 2025 12:47:03 EST

Comments

Ashok Kumar T · Jul 3

Hello @Corentin Blondeau

Here is the code to retrieve the properties in the same order they are defined in the class.

ClassMethod GetPropOnDefinedSequence(pClass As%String = "", Output pProperties)
{
	For {
		Setproperty=$$$comMemberNext(pClass,$$$cCLASSproperty,property)
		Ifproperty=""QuitIfproperty="%%OID"!(property="%Concurrency") continueSet pProperties(
			    +$$$defMemberKeyGet(pClass,$$$cCLASSproperty,property,$$$cPROPsequencenumber),
			    property)=""
	}
	Return$$$OK
}

 The properties assigned to the pProperties parameter

properties(1,"Name")=""
properties(2,"City")=""
properties(3,"IsActive")=""
properties(4,"State")=""
0
Dan Pasco · Jul 3

Previous version of my reply was for the storage order for data stored. Sorry.

0
Julius Kavay · Jul 3

An easy and simple (assuming standard storage strategy) way to get those informations (the slot numbers, where a property is stored) could be achieved with a simple classmethod

Class DC.PropInfo [ Abstract ]
{

/// Info about properties of a class:/// - list of all properties stored in a list/// - slot number for a given property/// /// 1) add this class to your class definition///    class some.class extends (%Persistent, DC.StorageInfo)  or///    class some.class extends (%SerialClass, DC.StorageInfo)///           /// 2) then use it as follows///    write ##class(some.class).PropInfo()      --> list of property names///    write ##class(some.class).PropInfo("Age") --> slot number for the Age property/// /// write ##class(ClassMethod PropInfo(name = "") As%String [ CodeMode = objectgenerator ]
{
	set sto=%compiledclass.Storages, prp=0, list=""if sto.Count()=1 {
		set dat=sto.GetAt(1).Data
		for i=1:1:dat.Count() if dat.GetAt(i).Structure="listnode"set prp=dat.GetAt(i) quitif prp {
			if%compiledclass.ClassType="serial" { set list="", i=1 } else { set list=$lb(""), i=2 }
			for i=i:1:prp.Values.Count() set list=list_$lb(prp.Values.GetAt(i).Value)
		}
		
		do%code.WriteLine(" if name="""" quit """_$lts(list)_"""")
		do%code.WriteLine(" quit $lf($lfs("""_$lts(list)_"""),name)")
	}
	
	if list=""write !,"*** No properties found! ***"quit$$$OK
}

}

Two test classes

Class DC.TestPerson Extends (%Persistent, DC.PropInfo)
{
Property Name As%String;Property Addr As DC.Address;Property Salary As%Numeric;Property Expertise As list Of %String;
}


Class DC.Address Extends (%SerialObject,DC.PropInfo)
{
Property Street As%String;Property City As%String;
}

Now you can do things like:

write##class(DC.TestPerson).PropInfo()         --> ,Name,Addr,Salary,Expertise
write##class(DC.TestPerson).PropInfo("Salary") --> 4// you can use the slot number for direct data access:write$list(^DC.TestPersonD(id),slotnumber)  gives you the same value aswrite##class(DC.TestPerson).SalaryGetStored(id)

// the same game for serial classeswrite##class(DC.Address).PropInfo()       --> Street,City
write##class(DC.Address).PropInfo("City") --> 2// in case you have an instanceset pers=##class(DC.TestPerson).%OpenId(id)
write pers.PropInfo()      --> ,Name,Addr,Salary,Expertise
write pers.Addr.PropInfo() --> Street,City

// etc.
0

Thanks for your responses !!!
I found a way to do it with SQL :

Set sqlQuery = "SELECT * FROM %Dictionary.PropertyDefinition WHERE parent = '"_ className _"' ORDER BY SequenceNumber"Set resultSet = ##class(%SQL.Statement).%New()
    Set status = resultSet.%Prepare(sqlQuery)
    $$$ThrowOnError(status)
    Set tResult = resultSet.%Execute()
    
    While tResult.%Next() {
        Set Name = tResult.%Get("Name")
        ...
        }

Corentin

0
Enrico Parisi  Jul 4 to Corentin Blondeau

Never, ever, concatenate parameters to an SQL Query!

Fixed for you:

Set sqlQuery = "SELECT * FROM %Dictionary.PropertyDefinition WHERE parent = ? ORDER BY SequenceNumber"Set resultSet = ##class(%SQL.Statement).%New()
    Set status = resultSet.%Prepare(sqlQuery)
    $$$ThrowOnError(status)
    Set tResult = resultSet.%Execute(className)
    
    While tResult.%Next() {
        Set Name = tResult.%Get("Name")
        ...
        }

Back to your initial question, what is your definition of "sorted by Storage"?

If you need the ($list) position of properties within the global, then your query does not answer your question.
I'm writing this in case other community members read this question/answer.

0
Dan Pasco  Jul 7 to Enrico Parisi

In response to my good friend Enrico's comment about "storage":

SELECT cc.Name as class_name,cs.Name as storage_name,cd.Name as data_node_name,cd.structure as structure, cd.subscript as subscript,cd.attribute asattribute, cv.name as value_name, cv.value asvalueFROM %Dictionary.CompiledClass cc
    JOIN %Dictionary.CompiledStorage cs on cc.%ID = cs.parent
    JOIN %Dictionary.CompiledStorageData cd on cs.%ID = cd.parent
    LEFTOUTERJOIN %Dictionary.CompiledStorageDataValue cv on cd.%ID = cv.parent
 WHERE cc.name = 'User.Person'

And a general comment about dynamic SQL. In the early days of Caché, someone made the decision to return a status value from methods to indicate success/failure. We didn't have try/catch at the time so it made some sense. Now we have try/catch (regrettably, no finally) and dynamic SQL follows a pattern that I started using many years ago - the traditional %<capital case> methods return a %Status value and a non-percent, lower camelCase method throws an exception. For dynamic SQL, that is prepare() and execute().
The primary benefit in using the exception-throwing method interface is that you cannot completely ignore errors unless you use a specific pattern to do so.

LATEST:USER>zw sql
sql=6
sql(1)="SELECT cc.Name as class_name,cs.Name as storage_name,cd.Name as data_node_name,cd.structure as structure, cd.subscript as subscript,cd.attribute as attribute, cv.name as value_name, cv.value as value"
sql(2)="  FROM %Dictionary.CompiledClass cc"
sql(3)="    JOIN %Dictionary.CompiledStorage cs on cc.%ID = cs.parent"
sql(4)="    JOIN %Dictionary.CompiledStorageData cd on cs.%ID = cd.parent"
sql(5)="    LEFT OUTER JOIN %Dictionary.CompiledStorageDataValue cv on cd.%ID = cv.parent"
sql(6)=" WHERE cc.name = 'User.Person'"

LATEST:USER>do statement.prepare(.sql)

LATEST:USER>set result = statement.execute()

LATEST:USER>do result.%Display()
class_name    storage_name    data_node_name    structure    subscript    attribute    value_name    value
User.Person    Default    DescriptionNode    node    d    description
User.Person    Default    PersonDefaultData    listnode            1    name
User.Person    Default    PersonDefaultData    listnode            2    dob
User.Person    Default    PersonDefaultData    listnode            3    address
User.Person    Default    PersonDefaultData    listnode            4    counter

5 Rows(s) Affected

0
Julius Kavay  Jul 7 to Dan Pasco

I think, Enrico was more aiming for the point that “concatenation is the gateway for SQL injection.”

0
Dan Pasco  Jul 7 to Julius Kavay

Perhaps I should have been more clear. I was responding to Enrico's comment about sequencing properties as they are stored in a global.

Back to your initial question, what is your definition of "sorted by Storage"?

If you need the ($list) position of properties within the global, then your query does not answer your question. I'm writing this in case other community members read this question/answer.

0
Herman Slagman  Jul 8 to Dan Pasco

And there, my friend, we differ completely. ;-)
"someone made the decision to return a status value from methods to indicate success/failure."
A brilliant idea! (Joe D. ?)
In my code I don't want to catch anything, I want to know if a certain method/function did what it supposed to do and if not: report tat to me in a polite fashion (%Status).

0
Dan Pasco  Jul 9 to Herman Slagman

@Herman Slagman - Perhaps we will meet again and we can have a vigorous debate! Your turn to buy ;)

0
Herman Slagman · Jul 8

It is an interesting exercise, but what is the point of it ?

0
Jean Cruz · Aug 14

I think the easiest way is by defining the storage

0