Danny Wijnschenk · Jan 6, 2020 go to post

Hi Evgeny,

the info is at least unreliable, so i vote to ask the author to delete this information from the article. There is already too much fake news on the internet, lets make this little part of the internet as reliable and accurate as possible !

Danny Wijnschenk · Jan 7, 2020 go to post

Hi Raj,

I use different IDE's and combinations :

- Caché Studio without any extensions for development where i am self-employed and no other developers are involved

- Caché Studio with Serenji source control from Gerorge James for a particular customer where i am one of the developers

- Visual Studio Code with the vscode-objectscript extention from Dmitry Maslennikov for another customer where i am one of the developers.

I like Caché Studio since I use it from day 1, because it is build-in so no need to configure anything. I use the 'projects' feature a lot, and export my projects regularly as a sort of 'light' source control. (I even edit my html and js files in Studio, even when they contain no COS code at all.)

But when developing in teams  a good build-in source control is essential, so that's why i also use the other options. I am still learning Visual Studio Code but it seems the way for the future.

Danny Wijnschenk · Apr 1, 2017 go to post

You can put some code in the OnHandleCorsRequest to debug or monitor the CORS handling :

/// This is the CORS request handler. User should override this method in their subclass
/// if they don't want the default behavior
ClassMethod OnHandleCorsRequest(pUrl As %String) As %Status
{
    #; The default implementation is simply to dispatch to the
    #; default handler
    Set tOrigin=$Get(%request.CgiEnvs("HTTP_ORIGIN"))
    Set ip = $Get(%request.CgiEnvs("REMOTE_ADDR"))
    Set method = $Get(%request.CgiEnvs("REQUEST_METHOD"))
    set ^debug($i(^debug))=$lb($ZD($H,8),$ZT($P($H,",",2)),pUrl,tOrigin,ip,method)
    Quit ..HandleDefaultCorsRequest(pUrl)
}

Danny Wijnschenk · Apr 27, 2017 go to post

Can you look in the directory r:\data\blabla :

there should be a CACHE.DAT, and it should not be locked by another Caché instance.

You can verify who locked the CACHE.DAT file by looking at the cache.lck file in the same directory, this file contains the mgr directory  of the Caché instance who mounted the CACHE.DAT database :

  • If Caché is not running, there should not be a cache.lck file.
  • If Caché is running, the cache.lck should contain the mgr directory and server name of the Cache instance.

You can stop Caché and delete the cache.lck file if any of the previous conditions are false. After startup and mount, the cache.lck file should automatically be created with the proper mgr directory name. (mount will occur by accessing the database or namespace using the database).

Danny Wijnschenk · Apr 29, 2017 go to post

And you can even use the Output window in Studio to execute commands (but don't do Halt !)

Danny Wijnschenk · May 11, 2017 go to post

- If the two directories should have the same classes, you could use class mapping to point the two namespaces to the same code base ...

Beside getting a source code tool, there are several ways to compare  :

- If you want to write your own compare logic, you can use the %Dictionary.* classes (a lot of work,  since there are a lot of classes /properties to compare). Use this to get a list of classes in the two directories and to see which is missing.

- If you want to compare  two classes quick-and-dirty : you can have a look at the global ^oddDEF. It contains the class definition of all classes : methods, properties, indexes, storage etc. are all in this global. You could $ZOrder your way through the global and compare the two namespaces. But since this is internal stuff, it could change in new versions of Caché!

Here is an example, but test before you use it !!!

Class Utils.Classes{

/// Compare two classes :
/// If ##class(Utils.Classes).Diff("USER","TEST","Data.MyClass", .reason) {
///   Write $List(reason, 1)," = ",$List(reason, 2) ;global from USER///   Write $List(reason, 3)," = ",$List(reason, 4) ;global from TEST
/// }ClassMethod Diff(ns1 As %String, ns2 As %String, class As %String, ByRef reason As %String){   Set diff = ..Diff1way(ns1, ns2, class, .reason)   If 'diff Set diff = ..Diff1way(ns2, ns1, class, .reason)   Quit diff}ClassMethod Diff1way(ns1 As %String, ns2 As %String, class As %String, ByRef reason As %String){   Set diff = 0   Set startref1="^["""_ns1_"""]oddDEF("""_class   Set startref2="^["""_ns2_"""]oddDEF("""_class   Set global1="^["""_ns1_"""]oddDEF("""_class_""")"   For {       Set global1=$ZOrder(@global1) Quit:global1=""  Quit:global1'[startref1       Set data1=$Get(@global1)       Set subs=$Piece(global1,class,2,*)       Set global2=startref2_subs       Set data2=$Get(@global2)       If (subs=""",63)")!(subs=""",64)") Continue  ;properties time changed (63) & time created (64) can be different       If data1'=data2 Set diff=1,reason=$lb(global1,data1,global2,data2) Quit   }   Quit diff}

}
Danny Wijnschenk · May 11, 2017 go to post

Hi Amir,

You should convert all incoming data that is not in your native encoding : you don't want data in your database that is coming from ODBC, Web, Files or other to have different encodings !

I use $ZCONVERT( ... , "I", "UTF8") for all incoming data thru rest, and use $ZCONVERT( ... , "O", "UTF8") for all data that is sent via rest. I also use the "u" format flag when i use %WriteJSONFromSQL.

Danny Wijnschenk · Jul 12, 2017 go to post

Global memory works with blocks and not with nodes :

When a global  node is accessed, the block where this global node is stored will be put into memory.

Any routine that needs to access the nodes that are in this block will be served from memory.

So, in your example, if the nodes (1), (1,2) and (1,7) are all stored in the same block, these will all be served from memory after the initial access by routineA.

Blocks are nowadays 8Kb (used to be 2Kb).

Danny Wijnschenk · Sep 19, 2017 go to post

Hi Steve,

It also depends if you are indexing all the sources at once or a bunch per day  : typically, a batch load will grow your database, and will leave it 50% empty at the end, after deleting  temporary storage. This is not a problem if you load new sources regularly, since this empty space will be used the next time.

If these temporary iKnow globals are mapped to cachetemp, dont forget to take this needed disk space into account as well, even if it will be released after restart. (especially since cachetemp is by default installed on the same drive as Caché, and could have less free disk space as other drives where you put your databases)

Danny Wijnschenk · Nov 14, 2017 go to post

Hi Laura,

Can you look at the directory where your cache.dat is located.

If a database is mounted, there should be a cache.lck file with the root directory  and name of the Caché that mounted it.

If the root/name is not the same as your Caché system, it will not mount it as RW.

Best is to stop Caché (or dismount the database), remove the cache.lck, and mount/start Caché again.

(If you are sure that this DB is not used by another Caché).

Danny Wijnschenk · Nov 14, 2017 go to post

Hi Laura,

Can you check in the cconsole.log for any error messages ? If your cache.dat is not RW accessible by Caché, you get an error like :

11/14/17-16:55:39:138 (16952) 0 DKMOUNT: Mounted SFN 6 DB 'c:\intersystems\cache\mgr\user\' as Read Only DB. File or filesystem allows read-only access. 

Danny Wijnschenk · Nov 20, 2017 go to post

Turn on Auditing (Portal -> System Admin -> Security -> Auditing -> Enable Auditing, configure system events to audit login failures ).

It can give you a clue on why you are getting Access Denied.

It could be an issue of licensing : check the system dashboard for Licensing use (maybe you have too many connections open).

If all this does not help, you can also turn on ODBC logging in the ODBC DSN.

Danny Wijnschenk · Jan 17, 2018 go to post

If you substract the current day-of-the month from the current date in $horolog format, you will end up with the first day of the current month in $horolog format :

Write $horolog - $zdate($horolog, 4) + 1

If your date is in another format, convert it using $zdateh/$zdate, see also :

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…

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 · 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 · 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 · 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 · 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 · 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 · 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 · 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 · 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 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 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 · 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,!
}