Danny Wijnschenk · Aug 8, 2019 go to post

Hi Robert,

take a look at the %SYS.Journal.File , %SYS.Journal.Record and %SYS.Journal.SetKillRecord classes.

It contains methods & properties to loop through the journalfiles, and get the information of all changes. You could then write this info in a (summarized) file and email it.

ClassMethod ShowJrn(file = "C:\InterSystems\Cache\mgr\journal\20190804.001")
{
  Set jrnforef = ##class(%SYS.Journal.File).%OpenId(file)
  set record = jrnforef.FirstRecordGet()
  While record'="" {
      If record.%ClassName()="SetKillRecord" {
        Write record.Type,! ;6 = SET, 7 = KILL
        Write record.GlobalReference,!
        Write record.OldValue,!
        Write record.NewValue,!
    }
    Set record = jrnforef.GetRecordAt(record.NextAddress)
  }
}
   
Danny Wijnschenk · Aug 6, 2019 go to post

Hi Sergio,

I have the following code that will do what you need, but i don't see this as the best approach since it is more complicated than a bunch of If's. 

But it works, and it shows that you do anything in ObjectScript!

Class MyPackage.MyClass
{
ClassMethod MyMethod(p1 = 1, p2 = 2, p3 = 3)
{
Write p1,"-",p2,"-",p3,!
}
ClassMethod Test()
{
Do ..FromJson({})
Do ..FromJson({"P1":"first value""P2":"second value"})
Do ..FromJson({"P1":"first value""P3":"third value"})
Do ..FromJson({"P2":"second value"})
}
ClassMethod FromJson(json)
{
Set call="(json) Do ##class(MyPackage.MyClass).MyMethod("  //we need to pass json as input param to xecute
Set first = 1
For jsonProp = "P1","P2","P3" {
If 'first Set call=call_","
//Set call=call_$Select(json.%IsDefined(jsonProp):"json."_jsonProp,1:"") //same as $Property, but doc mentions to use $Property instead
Set call=call_$Select(json.%IsDefined(jsonProp):"$Property(json,"""_jsonProp_""")",1:"")
Set first = 0
}
Set call=call_")"
Xecute (call,json) //pass json as input parameter
}
}
Danny Wijnschenk · Jul 26, 2019 go to post

Hi John,

Can you show me your class definitions, because when i execute the second SQL on my classes and some random data, it will show the results i expect :

(maybe you need to re-index your tables)

Danny Wijnschenk · Jul 25, 2019 go to post

Hi John,

I tried to create the classes you are describing, and they seem to give the correct results in the two SQL statements.

The two query plans are also giving the same access path.

Can you tell me what the outcome (data/error) of the second SQL statement is giving you ? 

I used following classes :

Class User.immst Extends %Persistent
{
Relationship PhysicalRecord As User.pytrn [ Cardinality = many, Inverse = ItemRecord ];
Property imdesc As %String;
}

Class User.pytrn Extends %Persistent
{
Relationship ReportRecords As User.pyrpt [ Cardinality = many, Inverse = PhysicalRecord ];
Relationship ItemRecord As User.immst [ Cardinality = one, Inverse = PhysicalRecord ];
Property pypqty As %String;
}

Class User.pyrpt Extends %Persistent
{
Relationship PhysicalRecord As User.pytrn [ Cardinality = one, Inverse = ReportRecords ];
Property pypqty As %String;
}

 
Danny Wijnschenk · Jul 25, 2019 go to post

Hi Vitaliy,

This is the same as 'write $SYSTEM.INetInfo.EthernetAddress(0)' in recent versions.

As said we prefer to not use $zu anymore, but since availability of  the helper classes depend on what version is used, $zu might be the only possibility unless they upgrade.

Danny Wijnschenk · Jul 16, 2019 go to post

To start debugging in Studio, you first have to set your method as a debug-target : right-click on the method header and choose 'Set DoMyWork as debug target'.

Then set a breakpoint  anywhere in the method you want to start debugging (click on F9  to toggle breakpoints)

Then start the debugger in the menu  or Ctrl-F5.

If you want to call your method with some predefined arguments, it is better to create another method that will call ..DoMyWork(.arg1, arg2) and use this method as your debug-target, or  better, in the debug menu, click on debug-target and add proper arguments in the classmethod.

If you wish to debug in terminal, you need to use the command Break with some options :

USER> Break "S+" Do ##class(MyPackage.MyClass).DoMyWork(.arg1)
^
<BREAK>zDoMyWork+1^...
USER 2d1> 
 

Option S or L with + or - depending if you want to debug instruction by instruction (S) or Line by line (L), debug into methods (+) or execute them(-).

Use G (or Goto) to go step by step, Use Break "C" to clear debugging.

More info on Break : https://docs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KEY=GCOS_debug

Danny Wijnschenk · Jul 15, 2019 go to post

Hi Jimmy,

I think the Send* methods have an extra argument where you can specify the REST type (GET, PUT or POST), and the methods with the Get, Put or Post in the names are just shortcuts that internally call the Send method. (This is true in the %Net.HttpRequest class, so i guess it is the same in the adapter)

The SendFormDataArray and SendFormDataUrl have also an extra argument where you can pass  a %HttpRequest instance. So you could create this instance to pre-populate properties that are not possible to set using the adapter, like the ContentType property.

Set objHttpRequest ##class(%Net.HttpRequest).%New()
Set objHttpRequest.ContentType "application/json"
Set tSC=..Adapter.SendFormDataURL(tURL,.tHttpResponse,"POST",objHttpRequest,,pRequest1.%ToJSON()).
 

I haven't tested this code (don't have Ensemble) but looking at the docs it should work.

Danny Wijnschenk · Jul 12, 2019 go to post

Hi Jimmy,

I don't use Ensemble , but maybe the 4th parameter is not in the right format : see doc(https://docs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KEY=EHTTP_outbound#EHTTP_C7322547) :

The pData argument is an array. The top node of the array is not used. Each subnode is subscripted by the index of the corresponding form variable in the pFormVarNames list. The value at a given subscript should be specified as follows:

  • For a form variable (varname) with a single value, the value at pData(“varname”) should be the form data value to send. There should be no subnodes.

  • For a form variable (varname) with multiple values, the value pData(“varname”) should be the count of the values. Each of the values for this form variable should appear in a subnode, subscripted by its position in the node.

  • To send a request body instead of form variables, leave pFormVarNames empty and pass the body text as the pData argument.

So, can you try to call the PostURL with pRequest.%ToJSON()

In Caché - using %Net.HttpRequest - it works like this :

Set objHttpRequest = ##class(%Net.HttpRequest).%New()
Set objHttpRequest.ContentType = "application/json"
Set objHttpRequest.Server = "dummy.restapiexample.com"
Set pRequest = {"name":"abc1jim23","salary":"123","age":"23"}
Do objHttpRequest.EntityBody.Write(pRequest.%ToJSON())
If objHttpRequest.Send("POST", "/api/v1/create") {
Set objHttpResponse = objHttpRequest.HttpResponse
If $IsObject(objHttpResponse.Data) {
     Set objStream = objHttpResponse.Data
     Set json =""
     While ('objStream.AtEnd) {
          Set json = json _ objStream.ReadLine()
     }
Else {
     Set json = objHttpResponse.Data
}
Set httpStatus = objHttpResponse.StatusCode
Write "Status : ",httpStatus,!
Write "Response : ",json,!
}
Danny Wijnschenk · Jun 29, 2019 go to post

If i need one value, i use GetStored, if i need a few values, i use SQL, if i need a lot of them or need the references to other objects, I open the object instance.

To open an instance and using only 1 property of a class with lots of properties is potentially slower than GetStored or SQL.

Danny Wijnschenk · Jun 27, 2019 go to post

Any of the following is possible, but this will only work if you want to keep the data in the two namespaces for that class exactly the same : 

  • Map the globals that are used by the persistent class to the other namespace.

OR

  • Change the global names in the Class Storage definition (DataLocation, IDLocation, IndexLocation, StreamLocation) and add a namespace reference to it, like this :
Storage Default
{
<Data name="customerDefaultData">
...
<DataLocation>^["USER"]test.customerD</DataLocation>
<DefaultData>customerDefaultData</DefaultData>
<IdLocation>^["USER"]test.customerD</IdLocation>
<IndexLocation>^["USER"]test.customerI</IndexLocation>
<StreamLocation>^["USER"]test.customerS</StreamLocation>
<Type>%Library.CacheStorage</Type>

Be careful if properties are pointing to other persistent classes as well !

Danny Wijnschenk · Jun 26, 2019 go to post

You cannot pass an object (oref) as a parameter using Job.

You have to save its properties to a variable/array and pass that, or save the object and pass its id.

Danny Wijnschenk · Jun 25, 2019 go to post

You can run the Job command with a timeout parameter, and check if the job has really started with the $TEST variable :

USER> JOB ##class(TestFramework.API.E2E.Service).RunCases()::0

USER> Write $TEST

Should be 1 if the classmethod was started, 0 if the job timed out and did not start

In the second case it can be a license problem, check  https://docs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KEY=GSA_license#GSA_license_capacity

Danny Wijnschenk · Jun 7, 2019 go to post

Did you check if you have enough licenses available (e.g. Portal -> View system dashboard -> Highest License Use)

You can also look at View Audit Database to see if there are any related messages

Danny Wijnschenk · May 9, 2019 go to post

Hi Kurt,

Some old terminal emulators cannot work with UTF8.

I used AniTa which had the same problem connecting to a Caché Unicode server.

I tried TeraTerm which is free and can at least work with UTF8 and VT320 emulation, but i haven't tried all our terminal tricks yet to see if it can do the job for 100%.

Once you start TeraTerm, don't forget to change in Setup->General the language to UTF-8,.

Setup -> Save Setup will save all your changes to a Teraterm.ini file so they will be in effect the next time.

Danny Wijnschenk · Jan 20, 2019 go to post

We will finalize the agenda for the Caché User Group @BeneluxSymposium shortly.

All questions or suggestions for demos or presentations you want to do are still welcome here or at CUG Benelux Blog

Danny Wijnschenk · Sep 7, 2018 go to post

Are you unable to POST anything, or are you looking for an easy way to construct JSON in 2012.1.4 ?

The class to POST something is %Net.HttpRequest, you need to send the form as a  stream.

In recent (2016.2 upward) versions, you can create a dynamic object and convert it to a JSON string, but 2012.1.4 is lacking this  JSON support,.

But you should still  be able to send a string in the body part formed (manually) as correct JSON :

    Set httprequest=##class(%Net.HttpRequest).%New()
    Set httprequest.Server="apitest.authorize.net"
    Set httprequest.Https=1
    Set httprequest.SSLConfiguration = "TEST"  ;make sure this SSL is created via mgmt security portal
    Do httprequest.EntityBody.Write("{""createTransactionRequest"":{""merchantAuthentication"":{""name"":""gfufet9QVgT5P"",""transactionKey"":""8pg6FJjxuekeY62m""},""refId"":""123456"",""transactionRequest"":{""transactionType"":""authCaptureTransaction"",""amount"":""5"",""payment"":{""creditCard"":{""cardNumber"":""5424000000000015"",""expirationDate"":""2020-12"",""cardCode"":""999""}}}}})")
    Do httprequest.Post("/xml/v1/request.api")
    Do httprequest.HttpResponse.OutputToDevice()
Danny Wijnschenk · Aug 21, 2018 go to post

Do you have a user with the %ALL Role to check if the problem is user-based, 

if that user cannot login either, it could be that some services are disabled.

Again, you can know for sure by enabling auditing and look at the login failures.

Danny Wijnschenk · Aug 16, 2018 go to post

If Caché Auditing is enabled, you can check the login failure messages to see if  the problem is a disabled user or wrong username/password.

(Auditing can be enabled in Caché portal ->  system admin -> Security -> Auditing -> Enable Auditing.

Use 'configure Auditing events' to enable Login Failure messages.

Danny Wijnschenk · Aug 16, 2018 go to post

Can you still connect with Studio on the same machine ?

Apparently, the factory.Connect is failing, what is the value of ConnStr ?

You could change the line by 

ConnStr = factory.ConnectDlg()

which will display a dialog with options to login, or give you an error if it cannot reach Caché.

Danny Wijnschenk · Jun 20, 2018 go to post

Hi Eduard,

I have found some old code to visualize a WordCloud using iKnow.

This code is showing the concepts of a source, and adds weight according to a simple tf/idf score.

I have put the (old) code on github here

The code is using %iKnow.Queries.EntityAPI.GetBySource, and the CSP page is using the jquery library from AwesomeCloud to render the wordcloud. (Today I would not use CSP pages anymore with Caché script or CSP tags, but like i said, it is old code;)

After importing the xml from github, the url to call looks like this :

http://localhost:57772/csp/user/WordCloud.csp?domain=pubmed&source=240

and the result would be something like :

Danny Wijnschenk · May 18, 2018 go to post

Make sure the file cache.lck is not present : it indicated the cache.dat is mounted by (another) Caché instance.

Also verify the block size of the cache.dat : nowadays it is default 8Kb, but in some previous versions, you could specify other block sizes, and if it is the case, you need to allocate global buffers that are minimum that size.

Danny Wijnschenk · May 17, 2018 go to post

Caché can call any OS command (if it has enough rights) by using $ZF(-1 (see also article https://community.intersystems.com/post/callexecute-exe-windows-objects…)

In the OS command script, you can use ccontrol to the other Caché instance (see article https://community.intersystems.com/post/starting-routine-windows-comman… as an example)

But i would prefer using Webservices to call a routine from one Caché instance the other :  then you don't need to put the two Caché instances on the same server : see  webservices doc https://docs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KE…

Danny Wijnschenk · Jan 31, 2018 go to post

In the real world, there are lots of programs still using this style, every developer using Caché Object Script should be able to read this and understand the side-effects, pitfalls, even if we recommend to not use it anymore... 

Danny Wijnschenk · Jan 30, 2018 go to post

Hi Scott,

Here is some code that stores the sizes of all globals for a database, if you create an entry in the task manager to run this code every day, you will see in the global  ^tempSize which globals grow faster than others :

do ##class(Utils.Database).GlobalSize()

(please change the code to store the results in a global that does not exist, or better, in a persistent class)

Class Utils.Database{ClassMethod GlobalSize(dir As %String = ""){   If dir="" Set dir = $zu(12,"") ;current directory   #Dim today as %Integer = $zdate($H,8)   #Dim sqlStatement as %SQL.Statement = ##class(%SQL.Statement).%New()   #Dim sqlResult as %SQL.StatementResult   #Dim sc as %Status = sqlStatement.%PrepareClassQuery("%SYS.GlobalQuery","Size")   Set sqlResult = sqlStatement.%Execute(dir,,,,,1) ;1=fast mode   While sqlResult.%Next() {      set ^tempSize(sqlResult.%GetData(1),today)=sqlResult.%GetData(2) ;^tempSize(yyyymmdd,global)=allocated blocks   }}}
Danny Wijnschenk · Jan 25, 2018 go to post

Yes, same here, i also use following code a lot:

Open <some device>:"R":0 Else  Write "could not open device" Quit

I always use Else on the same line just after the command that changes $test,

having too much instructions between them creates problems. 

Danny Wijnschenk · Jan 25, 2018 go to post

Besides all that is mentioned, the If without braces has a side-effect that the If with braces does not have : it affects the system variable $Test.

USER>set $test=0
 
USER>w $test
0
USER>if 1=1 { write "one" }
one
USER>w $test
0
USER>if 1=1 write "one"
one
USER>w $test
1
USER>

Not important, only if you use the ELSE (legacy) command...