Laura Cavanaugh · Jul 19, 2019 go to post

Thank you.  Does this count toward the $storage value? or is the $storage affected only by variables (and not PPGs)?

Laura Cavanaugh · Sep 5, 2019 go to post

Michael,

Thanks for this tip.  I'm using this utility to programatically export part of a very large global to a file, for archiving data.  I've decided that using the %Global.Export() method is the best way to go, since I have to do this in code without user input from a terminal. I considered ^%GOGEN and $system.OBJ.ExportToStream as well (comments on this?)

Previously I was merging the global part to a temp global in another namespace (the namespace would then be deleted), and then deleting the subscript from the main global.  Obviously this could temporarily greatly increase the amount of space this global uses; hence the switch to something better.

The global I'm archiving is very large, so I'd like to ensure that this utility doesn't copy the global to a temp global, a PPG, or any kind of format that would take up disk space (aside form the space the file is using).  

Do you know if this utility works directly from the global to the file, or does it use any temp globals along the way?

Thanks,

Laura

Laura Cavanaugh · Oct 17, 2019 go to post

I use ByRef a lot, but mostly to indicate that an array or object will be changed in the method. 

In fact, until now, I thought it was necessary to use ByRef in order to "continue the changes to the object back to the calling code".  However, after playing around with some code, including your example, I see that it's not necessary at all, which makes me think that it's useful as an "annotation", so that the programmer knows that the object WILL BE changed. 

Now, I feel that the real "pass by reference" is the dot in front of the variable, and the ByRef is merely an indicator that this variable could be changed when returned.

Is this true?  I'm not really sure what "passing a reference by reference" would produce. I came upon this article because I want several methods in a row to update a log file, which I sometimes add to the method signature as ByRef, and sometimes I don't, and I wanted to know why it still worked (i.e. my log file is created, and is updated in the calling method).

Here is your original example, but without the ByRef:

ClassMethod Test(UseByRef As %Boolean = {$$$YES})
{
    Set Obj = {}
    Set Obj.Property = 1
    Write "At the begining ",Obj.Property,!
    Do ..RefMethod(Obj)
    Write "After Value pass ",Obj.Property,!
    Do ..RefMethod(.Obj)  // pass by reference because I want the object to change
    Write "After ByRef pass ",Obj.Property,!
}

/// notice there's no ByRef in the method signature

ClassMethod RefMethod(Obj)
{
    Set Obj = {}
    Set Obj.Property = 500
}

USER>Do ##class(Utils.ObjByRef).Test()


At the begining 1
After Value pass 1
After ByRef pass 500

Laura Cavanaugh · Nov 2, 2019 go to post

Hi Samuel,

No, we haven't tried that, and I wouldn't even have thought of it.  So you don't think it's a security or privilege thing? I'll request a restart, but it could take a few days since it's a production server.  I'll let you know - thanks.

Laura Cavanaugh · Nov 7, 2019 go to post

Hi Samuel,

We restarted just the instance last night, and that seemed to work; we can now use the print icon in the widget to print a dashboard pivot table.  Thanks!

Laura Cavanaugh · Nov 7, 2019 go to post

Hi Eduard,

We're not using relationships partly because of the time it takes to load up a property that is a relationship, and also this might just be how it was designed in the initial phase of the project, and now we live with it.

(We do use relationships elsewhere).

Good to know about the $increment not getting rolled  back, and I agree that using the ClassA.PropertyB.%Id() isn't a good practice; I will pass that on. I also am going to go make some simple classes and have some fun with them, to see what happens to the Ids.

Thanks for the input!

Laura

Laura Cavanaugh · Nov 7, 2019 go to post

Eduard,

Just a note; you are absolutely correct about the $increment value of the ID not getting rolled back, and a simple test of classes saved inside a transaction show that the ID is incremented and not rolled back on error.

All that aside, we're going to stick with the %OnAfterSave, and the slower method of getting the PropertyB ID from a ClassA object. 

Thanks for great info.

Laura Cavanaugh · Sep 1, 2016 go to post

I only know what to use on the COS side; you can use the %SYSTEM.Status class with methods IsOK, IsError, GetErrorText, GetErrorCodes, like this:

s returnStatus = ...code that returns %Status...

s isOK = ##class(%SYSTEM.Status).IsOK(returnStatus)

There is a shortcut:

s isOK = $system.Status.IsOK(returnStatus)

I find .GetErrorText(returnStatus) and .DisplayError(returnStatus) methods to be quite useful when I want to display the error text in an alert window or on the screen (respectively).

See the class reference for more methods.

I don't know of a utility on the Java side;  that would be nice to know. Perhaps you can call out to COS again to parse it and return a Java type.  However, the first character of a %Status is always  a 1 if it's OK, and a 0 if it's an error.

Is this what you were looking for?

Thanks,

Laura

Laura Cavanaugh · Sep 1, 2016 go to post

Ah, so you need to map the %SYSTEM.Status class from cache to a Java object.  I've never done that.  Did the "Java Proxy Class Mapping" have anything helpful?

http://docs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KEY…

and

http://docs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KEY…

I'm thinking you might have to create your own Status class that extens %Status; like creating your own exception class in Java. Then you can edit your Status class so that when compiled it will create a Java class. 

Sounds fun. Let me know if this works for you.  

Laura Cavanaugh · Sep 15, 2016 go to post

method() is an instance method; you need a car object in order to call it.

set car = ##class(Car).%New()

d car.PrintCar()

ClassMethod() you dont' need an object (provided it's not private).

d ##class(Car).PrintAllCars()

If you know Java, ClassMethod is like a static method; method is like an instance method.

Laura Cavanaugh · Mar 16, 2017 go to post

Oh, hello John!  I was not aware of it, AND it seems to work for an average user without requiring access to the %SYS namespace.  That is great!  Thanks so much.  

That was easy!

Laura

Laura Cavanaugh · Mar 17, 2017 go to post

Since we're on the subject, do you have a magic way of letting users kill their own sessions (in case the application is not letting them log in) in the CSP Sessions global? I'd love to give them a "Log out of all sessions" button.

Thanks,

Laura

Laura Cavanaugh · May 12, 2017 go to post

I could try that... but this is for the user-friendly page, and I don't want that info on it.  I was hoping to log the info in the error trap.

I just tested the error trap, and found that you can, actually, load the details of an object, while still in the error traap ^%ER.  I had no idea:

Error: 7
 
 7. <UNDEFINED> --routine name -- *undef : CSP Error  at  9:41 am.   $I=|TCP|1972|16772   ($X=327  $Y=14056)                                                                                                                       $J=16772  $ZA=24576   $ZB=$c(0)   $ZS=262144 ($S=267019816)
              set x=undef //LLC testing
 
Variable: %request (copying data ... done)
(base stack level = 20)
          %request =     <OBJECT REFERENCE>[1@%CSP.Request]
Show object details? yes
+----------------- general information ---------------
|      oref value: 1
|      class name: %CSP.Request
| reference count: 6

... etc

So, as long as the error trap has that info available, I'm pretty good.  Does that change in 2016?  Why is the %CSP.ErrorLog page in 2016 so careful to save off the request, session, and response objects inthe error log?

Thanks,

Laura

Laura Cavanaugh · May 23, 2017 go to post

That's it!  Thank you!  You might have been thinking "it's so obvious", but I wasn't thinking of adding the attributes via html, just for that cell.  I was only thinking of "write", or using the column object to set its cellTitle property, which is not feasible since I can't get the column obejct.  But substituting html works great.  Thanks!

I was able to change the style just for cells whose columns are greater than pSeed (nice touch there), and I removed the showValueInToolTip for the rest of the cells.  I'm sure that would have been noticed at some point.

Laura

Laura Cavanaugh · Jul 18, 2017 go to post

I think it's a bit safer anyway to know exactly how many parameters to expect (avoid a sql injection attack?); I can see how long the one statement with a known number of parameters takes compared to other statements.  So far I have just the one unknown parameter, and all the rest are built into the sql.  Thanks.

Oh -- the parameter is a $lb(); the sql is using %INLIST, which, in the example in the documentation, seems to require a parameter passed in the %Execute() rather than built into a value in the sql statement.  The user might or might not search on this field.  It's more like:

select * from person where ID %INLIST ?

I was thinking that since I need to pass this in to the Execute, I might as well set up a structure in case we add more parameters to the statement in the future; but perhaps it's better to just pass in a known number.

Thanks.

Laura Cavanaugh · Aug 2, 2017 go to post

I often forget to mention that we are on 2014.  Thanks - at least I can tell him we can't do it but maybe in a future release we can change the IS logo.

Thanks,

Laura

Laura Cavanaugh · Aug 17, 2017 go to post

Hi Timothy,

Yes, indeed, thanks.  I already have a SessionEvents class set up for all my web applications involved, so I stuck in this method:

ClassMethod OnLogin() As %Status
{
if ^ZPMGSYSTEM("%DOWNFLAG")=1 quit $$$ERROR($$$GeneralError,"Logins are currently disabled")
quit 1
}
 

This caused an actual login that got around our flag (via special testing, in this case) to get a response of 

Not Found

The requested URL /Works/PMG.Works.Home.cls was not found on this server.

Is that the expected behavior?

So, I would say this works.  Unfortunately, it also removes my backdoor -- it keeps out everyone!  I guess I could add a little backdoor into this method as well; a screen door, if you will, on my back door.

Laura Cavanaugh · Sep 8, 2017 go to post

Yan, yes, this looks like it's working.  The OnCreateResultSet method is called, but not the OnExecuteResultSet.  Thanks!

Laura

Laura Cavanaugh · Nov 14, 2017 go to post

Hi Danny,

That seemed promising, but when I stop Caché, the cache.lck file is removed, and appears again when I restart the instance. 

Yes, the root database name is not the same as my caché system.  Caché is installed in the typical c:\intersystems\instance while these namespaces' databases are on another drive - e:\datasets.  Also, we changed the underlying Windows security recently for our 2016 upgrade, so I'll have to make sure the instance's service user has access to the e:\ drive.

This is what appears in the cache.lck file:

c:\intersystems\rhs01p\mgr\

SERVER01

where rhs01p is the instance name, and SERVER01 is the server name.

I'll check the Windows security and let you know.

Thanks,

Laura

Laura Cavanaugh · Nov 14, 2017 go to post

Great idea! Yes indeed! this is what it says:

 DKMOUNT: Mounted SFN 5 DB 'e:\datasets\demo\' as Read Only DB. File or filesystem allows read-only access. 

Looks like it's related to our new windows security setup, which, by the way, was implemented as part of the 2016 upgrade.  The 2016 version wants a windows group called Cache_Instance_instancename and the username that runs the instance is moved to this group.  We had to run something to change this windows security after we installed this instance as 2016.  Origainlly, the instance was installed to run as SYSTEM.

I wonder - should I uninstall the instance and reinstall so that it's run as the user?  It's still early enough in the game that I could do that.  Would that automatically give my user access to the e:\datasets\ folder since I'm creating namespaces in the Installer manifest?  not sure.

Anyway, after I've figured out the windows security for this folder, I'll let you know if that helps.

Thanks! 

Laura Cavanaugh · Mar 31, 2020 go to post

Yes, very helpful, and easy to $order through.  Thanks.

In particular, I found ^EnsEDI.X12.Description("HIPAA_5010","SS",

Is this what is behind the class query EnsLib.EDI.X12.Document:EnumerateSegTypes (which I also just found with a little digging).

Thanks,

Laura

Laura Cavanaugh · May 11, 2020 go to post

Here is some pseudo code, redacted a little:

So, A Parent object has a one->many relationship with Child, as defined in the class definition.

Class Parent {
Relationship Children As Child [ Cardinality = many, Inverse = Parent ];

Property MostRecentChild as Child;
}

Class Child {
Relationship Parent As Parent [ Cardinality = one, Inverse = Children ];

// a few other objects, both as parents, and object properties
}

Set ParentID="",Child=""
for {

    // order is by parent, then by child DESC (youngest first, or really, when they were added to our database)

    set Parent  ID=$o(^||TEMP($j,"Parent",ParentID)) q:ParentID=""

    // clones everything; including the Instance's relationship, and the Instance's Objects
    set Parent Obj=##class(Parent).%OpenId(ParentID)
    set clone=ParentObj.%ConstructClone(1)
    for {

      //looping on count as well - just took that part out

     {
       set ChildID=""
       set ChildID=$o(^||TEMP($j,"Parent",ParentID,"Children",count,ChildID)) quit:ChildID=""
       Set ChildObj=##class(Child).%OpenId(ChildID)
       set key=clone.Children.FindOref(ChildObj)  // SEEMS TO WORK! Should it?
       set youngest=clone.Children.GetAt(key)
       set clone.MostRecentChild=youngest
       Set ok=..EvaluateClone(clone) // do lots of stuff here; evaluate as if the clone has only these children
       // here, I'm removing the children from the relationship one at a time
       do clone.Children.RemoveAt(key) // update clone

       //update clone's properties

       //e.g. set clone.TotalAgeOfChildren = clone.TotalAgeOfChildren-youngest.Age

       kill youngest,ChildObj
    }

  }
    kill ParentObj,clone
}

Basically, I have a temp global of the parents, and their children, in reverse "added to database" order. 

A. I clone the Parent

B. set the clone's MostRecentChild to the "youngest" object (which is redundant the first time around), evaluate it with all the clone's current children

C.  then remove the youngest object from the clone.  

D. I get the next Child object, which according to the temp global, is the next youngest in our database.  Loop from B.

I'm evaluating the Parent with fewer and fewer children.  

The objects aren't really this simple; the Child is also the child of a different parent, which is also cloned in the Deep clone, which is a child of a different parent, also cloned. Do I need to kill more stuff?

Assume a Parent has on average 5 children, but could have 1 to 40.  After about 25k children, I get a STORE error.

Thanks,

Laura

Laura Cavanaugh · May 12, 2020 go to post

I'll debug it today, and check the $storage.  I know I have references hanging around even after I kill objects, because the original Parent object and the cloned object still point to their children -- that is, the code is somewhat disjointed where I remove a child, but the "Youngest" property still points to the removed child, until the next loop, when I point "Youngest" to a new child. At that point, though, I was hoping the object would be removed from memory.

Laura Cavanaugh · May 19, 2020 go to post

Just a note: I haven't had time to log the $s values yet, but I'm thinking that's the way to go to track down the "leak".  I did add the %UnSwizzle, but it didn't help enough. When I point the clone's Youngest property to a new Child, the previous youngest Child still exists in memory, and I feel like that is a problem.

I'll update this thread when I get a chance to test.

Thanks for the help,

Laura

Laura Cavanaugh · May 19, 2020 go to post

Hi Peter and Jude,

I am also trying to use the idea of INTERSECT. Instead of intersect, I'm using a RIGHT JOIN or a LEFT JOIN, but the query plan isn't using the faster table to loop through the data.

I found that changing the Group By to the table that I want the query plan to use for the loop, made it much faster.

Laura Cavanaugh · May 20, 2020 go to post

Hi Arnold,

I do exactly that with the built in tablePane method %DrawHTML()

My method uses code like this:

//get tablePane component - copy to new object? (pointers, etc.)
#dim tblPane as %ZEN.Component.tablePane
set tblPane=%page.%GetComponentById("tblPaneData")
// you have access to the columns here, if you need to change things up
set numCols = tblPane.columns.Size
set lastCol = tblPane.columns.GetAt(numCols)
set hidden = lastCol.hidden
set lastCol.hidden=1

// I'm writing this to a file, for use in a moment...
$$$ThrowOnError(File.Open("WNS",10))Use File.Name

//Draw HTML headers - Note: HTML is not my strong point
"<!DOCTYPE HTML PUBLIC ""-//W3C//DTD HTML 4.0 transitional//EN"">",!
"<html>",!

//custom style just for this table (see elements in browser debugger! )

//I must have looked at the tablePane in the browser debugger
"<style>",!
  W "h1,h2 {",!
  W "font-size: 20px;",!
  W "font-weight:bold;",!
  W "font-family: verdana;",!
  W "text-align: left;",!
"}",!
// etc. etc.
"</style>",!

"<head>",!
 W "<meta http-equiv=""Content-Type"" content=""text/css"" charset=""UTF-8"">",!
"</head>",!
"<body>",!

//Write specific table header
set header=$g(%session.Data("Heading One Here"))
"<h1>"_header_"</h1>",!
"<h2>Heading Two Here</h2>",!

//write tablepane element to file
do tblPane.%DrawHTML()

//Finish HTML
" </body>",!
"</html>"

//Close File
do File.Close()

 

Perhaps you can return  string of HTML for your email. We use the html  converted to PDF using a third party converter  tool.  This lets the user print the table.

I hope this helps!