Jon Willeke · Oct 1, 2019 go to post

Note that for your specific case, our examples have been off by one:

USER>w $e("16175551234",*-10,*)
16175551234

You actually want something like this:

USER>w $e("16175551234",*-9,*)
6175551234

If you're not sure whether the transformation is being applied at all, maybe do something more dramatic, like $reverse, as a sanity check?

Jon Willeke · Oct 29, 2019 go to post

FWIW, you can also replace the call to $extract with two-argument $ascii:

$a(md5hash,i)
Jon Willeke · Nov 5, 2019 go to post

It appears that your link is to a Docker image of the application installed on YottaDB, a fork of GT.M. I followed the link at the bottom of the page to the FOIA release page:

https://code.osehra.org/journal/journal/view/1576

After downloading the latest copy and skimming the documentation, this release includes a CACHE.DAT database and extensive installation and configuration instructions for Caché. If you want to run the application on InterSystems IRIS, you would do better to start there than the YottaDB-based image.

For a container deployment, there are three things you'll want to be aware of: durable %SYS, bind mounts, and ports.

I believe that durable %SYS is covered in the documentation. Basically, it will store all of the database and mapping configuration that you do outside of the container.

You'll need a bind mount for durable %SYS, and for the RPMS database.

You'll want to export the web server port so that you can access the management portal.

One other suggestion: if you're new to InterSystems IRIS and/or containers, you may want to start with Docker locally on your system before tackling a cloud deployment.

Good luck, and have fun.

Jon Willeke · Nov 13, 2019 go to post

This is a simple way to make a secure connection using the same certificates as, say, your browser. If you trust Safari to go to https://example.com/, then you can use this to do the same in InterSystems IRIS. If the certificate on the other end was issued by a well known CA, then it will likely work.

Jon Willeke · Mar 15, 2016 go to post

Are you aware of the /exportversion qualifier (documentation for which is hidden in the COS reference for the $system variable)? If your code is otherwise portable, this qualifier is intended to allow "reverse flow" of code by omitting anachronisms during XML export.

Jon Willeke · Apr 19, 2016 go to post

You're asking two things: how to persist an object, and how to implement a singleton.

A global on its own is not able to save an object. Something needs to map the structure of its class to a global layout of some kind. The simplest way to do this is to subclass %Persistent, rather than %RegisteredObject, then call %Save().

I notice, however, that you're using %ZEN.proxyObject, presumably to avoid defining a class/schema upfront. In that case, you may be interested in looking at the document data model (DocDM) in the 2016.2 field test.

As for implementing a singleton, it depends on the context. In general, I would look at overriding %OnNew() to load an existing object if it exists. If you want to persist its state, you'll need to consider concurrency.

Jon Willeke · Apr 29, 2016 go to post

A quick test suggests that data migration is indeed required after changing the type of a property from %GlobalCharacterStream to %Stream.GlobalCharacter.

  1. Populate a class with some instances, each containing stream data.
  2. The char_length() function in SQL returns the length of the stream field.
  3. Change the type of the property and populate the class with some more instances.
  4. The char_length() function returns the length for the new instances, but null for the old ones.

I imagine the supported way to migrate is to make a new stream field and copy from the old field using CopyFrom(). I'd be tempted to diddle the stream references to point to the existing data. In any case, if it ain't broke ...

Jon Willeke · Jun 20, 2016 go to post

Ugly as they are, naked references can significantly improve performance for repeated references to the same subscript level of a global.

Jon Willeke · Jun 23, 2016 go to post

You can use the information from %SYS.LockQuery to graph the locks with their owners and waiters. Then do a depth-first traversal of each node, looking for a cycle.

Here's a sketch of building the graph:

s rs=##class(%ResultSet).%New("%SYS.LockQuery:Detail")
s status=rs.Execute()
k graph
f i=1:1 q:'rs.%Next()  d
. s ref="L"_i,graph(ref,rs.Owner)=1
. f j=1:1:$l(rs.WaiterPID," ") d
. . s pid=$p(rs.WaiterPID," ",j) s:pid]"" graph(pid,ref)=1

The graph looks something like this:

graph(3330,"L5")=1
graph(4380,"L4")=1
graph("L1",3309)=1
graph("L2",3326)=1
graph("L3",3327)=1
graph("L4",3330)=1
graph("L5",4380)=1

I've generated IDs for the locks to avoid a SUBSCRIPT error for long references. You'll want to keep a list of the original lock names.

Here's a (minimally tested) traversal method that returns an error if it finds a cycle:

ClassMethod dfs(byref graph, node as %String, byref visited) as %Status {
	s status=$$$OK
	i $d(node) d
	. i $d(visited(node)) d  q
	. . s status=$$$ERROR($$$GeneralError,"found a cycle at node "_node)
	. s visited(node)=1
	. s next=""
	. f  s next=$o(graph(node,next)) q:""=next  d  q:$$$ISERR(status)
	. . s status=..dfs(.graph,next,.visited)
	e  d
	. s root=""
	. f  s root=$o(graph(root)) q:""=root  d  q:$$$ISERR(status)
	. . k visited
	. . s status=..dfs(.graph,root,.visited)
	q status
}

If you try it on the previous graph, it will return an error like the following:

USER>s status=##class(deadlock).dfs(.graph) 

USER>d $system.OBJ.DisplayError(status)    

ERROR #5001: found a cycle at node L5
Jon Willeke · Jun 23, 2016 go to post

If I thought the root might be an integer, I guess I would just check:

USER>s n=27,root=n**(1/3),int=$fn(root,"",0) w $s(int**3=n:int,1:root)
3

I was also going to suggest trying logarithms, but someone already suggested that on sql.ru:

USER>w 10**($zlog(27)/3)                                              
2.99999999999999997
USER>w $zexp($zln(27)/3)                                              
2.999999999999999998
Jon Willeke · Jul 8, 2016 go to post

I dug up a pre-dynamic objects version of a utility method from a REST test and cleaned it up a bit (hopefully not introducing any bugs in the process):

ClassMethod compareArrays(ByRef actual, ByRef expected) As %Status [ PublicList = (actual, expected) ]
{
	; compare root node
	set deix=$d(expected,eval),daix=$d(actual,aval)
	if deix'=daix {
		quit $$$ERROR($$$GeneralError,"$d(actual)="_daix_" instead of "_deix)
	}
	if deix#2,aval'=eval {
		quit $$$ERROR($$$GeneralError,"actual="""_aval_""" instead of """_eval_"""")
	}

	set status=$$$OK
	set eix="expected",aix="actual"
	for i=1:1 {
		set eix=$q(@eix),aix=$q(@aix)
		quit:""=eix&&(""=aix)

		set seix="("_$p(eix,"(",2,*),saix="("_$p(aix,"(",2,*)
		if seix'=saix {
			set status=$$$ERROR($$$GeneralError,"found """_aix_""" instead of """_eix_""" at position "_i)
			quit
		}

		set deix=$d(@eix,eval),daix=$d(@aix,aval)
		if deix'=daix {
			set status=$$$ERROR($$$GeneralError,"$d(aix)="_daix_" instead of "_deix_" at position "_i)
			quit
		}

		if deix#2,aval'=eval {
			set status=$$$ERROR($$$GeneralError,"actual("""_aix_"""))="""_aval_""" instead of """_eval_""" at position "_i)
			quit
		}
	}
	quit status
}

Comparing them, I only see two things I prefer in my version. First, in this line of your method I would use four-argument $piece with * as the fourth argument, just in case the subscript contains "first" or "second":

    If ($Piece(tRef1,"first",2) '= $Piece(tRef2,"second",2)) {

Second, I would use a public list with first and second, rather than turning off procedure block for the entire method.

Jon Willeke · Jul 26, 2016 go to post

Christian in QD came up with a bit of a hack for this. You can use one of the DSNs that is installed automatically with the product, but override all of its attributes; e.g.:

s dsn=dsn_";Database="_$namespace_";Port="_^%SYS("SSPort")_";UID="_usn_";PWD="_pwd

To find an existing DSN, use the SQLDataSources query in the %GTWCatalog class.

He used this technique to make the TestODBC() method in the %UnitTest.SQLRegression class more reliable. I don't know for sure that it will work with a different DBMS and driver, but it's worth a shot.

Jon Willeke · Jul 29, 2016 go to post

Greetings. Did you consult at InterSystems fifteen years ago? Long time no see.

My favorite technique to keep myself and others honest is to test with random inputs. This gives rise to two challenges: generating the input, and verifying the output. The input obviously depends on the problem: string, number, list, etc. When it comes to output, I look for invariants: x*y=(y*x), sort(x)=sort(shuffle(x)), etc. I sometimes even write another version of the code under test to act as an oracle that's perhaps slower, or not as general.

Caché has at least three ways to generate random numbers: $random(), $system.Encryption.GenCryptRand(), and the Basic Rnd() function.

$random(n) returns a number from 0 to n-1, where n'>1E17.

GenCryptRand(n) returns n bytes of cryptographically random data. You can convert it to a number using one of the $ascii() functions:

  • $a($system.Encryption.GenCryptRand(1))
  • $zwa($system.Encryption.GenCryptRand(2))
  • $zla($system.Encryption.GenCryptRand(4))
  • $zqa($system.Encryption.GenCryptRand(8)) - may be negative

Rnd() is interesting for a tester, because you can seed it with Randomize. If you don't use a seeded PRNG, you'll want to log your inputs somehow. It's frustrating to find a one in a billion bug, but not be able to reproduce it.

Jon Willeke · Dec 5, 2016 go to post

To handle this in the general case, you would decompose the string, then strip out non-spacing marks. Unicode normalization has been requested previously, and will hopefully make it into the product at some point.

Jon Willeke · Jan 9, 2017 go to post

I'm not sure I understand the objection to %VID. I've seen a few different recommendations on how best to use it, but I think it does what you want in the following example:

select *
from (
    select top all *
    from Sample.Person
    order by DOB
) where %vid between 10 and 19

Regardless of the where or order by clause that you put in the sub-query, %VID refers to the position in the result set.

Jon Willeke · Jan 17, 2017 go to post

The CHUI routine editor is part of the FDBMS, which hasn't been a standard part of the product for a very long time. You could try the line editor with xecute ^%, but it's kind of painful, and I think it only supports .INT routines.

I suggest using the editor of your choice with XML export files.

Jon Willeke · Jan 31, 2017 go to post

I can't reproduce this in a 2016.2 instance I have handy:

USER>s filename="DKA0:[QD.CACHEJCWG2.MGR.USER]CACHE.DAT"

USER>w $zf("GETFILE",filename,"UIC")                    
[1,1]

Can you get the output of $zu(56,2) after the error? Anything unusual about your filename?

Jon Willeke · Feb 13, 2017 go to post

The instance is assigned a GUID that you can retrieve using the InstanceGUID() method of the %SYS.System class:

USER>w ##class(%SYS.System).InstanceGUID()
C74E6F76-F21F-11E6-9BB8-A860B607521C

Databases are not assigned such an identifier.

Jon Willeke · Mar 9, 2017 go to post

I don't really understand the question. It sounds like you're trying to convert from one eight-bit character set to another, but French generally uses the same character set as English, as far as I know. If you're using a Unicode instance of Caché, your first resort should be I/O translation: translate the input to Unicode, then translate on output to the desired character set. Doing it in COS is slower and less convenient.

That said, here are some things in your code that don't really make sense:

  1. a."i"; should probably be a.%Get(i).
  2. a-128; maybe a.%Get(c-128)?
  3. I don't understand the purpose of the first loop or the return statements.
  4. value(j); ...?

If I were to hazard a guess at rewriting this, my first try might be something like this:

for i=1:1:$l(str) {
    s c=$e(str,i)
    if $a(c)<128) {
        w c
    } else {
        w $c(a.%Get($a(c)-128))
    }
}
Jon Willeke · Jun 8, 2017 go to post

This method probably first shipped with Caché 4.0 in December 2000, but the oldest instance I have handy is Caché 4.0.4:

USER>w $system.Version.GetMajor()
4
Jon Willeke · Aug 9, 2017 go to post

For the most part, dynamic objects use local memory that is accounted for by $zstorage and $storage. However, some of it comes from other heap-allocated memory, similar to long strings. You can round trip a large JSON object through a dynamic object with %FromJSON()/%ToJSON(), even if an individual field exceeds the string limit. However, it is not currently possible to get the value of such a field within Caché.

Note that you should call %ToJSON() such that it outputs to a device or writes to a stream:

USER>d o.%ToJSON(stream)

USER>d o.%ToJSON()

Otherwise, you may get a STRINGSTACK error:

USER>w o.%ToJSON()

W o.%ToJSON()
^
<STRINGSTACK>
Jon Willeke · Jan 11, 2018 go to post

The two concepts are orthogonal. A class method doesn't need an instance; a private method is only visible from within the class. As a contrived example, consider a public hello() class method that calls a private greet() class method.

Is it safe to change a class method from private to public? In the immediate term, sure, but now it's a public part of the class's interface with all the maintenance responsibilities that entails. I don't see how that's obviously any different than an instance method.

Jon Willeke · Mar 13, 2018 go to post

The statement in your post is not well formed. The value for the description field starts with a single quote, contains a couple of double quotes, but is not terminated with a single quote.

Jon Willeke · Jul 16, 2018 go to post

You should create properties for keys that you want to query. Take a look at this section in the online documentation:

https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=GDOCDB_cos#GDOCDB_cos_createprop

Also, there is a brief description in the reference for the %DocDB.Database class:

http://localhost:52773/csp/documatic/%25CSP.Documatic.cls

For example:

USER>s db=$system.DocDB.CreateDatabase("Fitabase1")

USER>w db.%CreateProperty("TotalSteps","%Integer","$.TotalSteps")
2@%DocDB.Database

Now the ISC.DM.Fitabase1 class (or whatever your generated class is) has a TotalSteps property. If you already have data, you need to populate the associated index. A quick and dirty way to do this is an SQL query like the following:

update ISC_DM.Fitabase1 set %Doc = %Doc

I'm not that familiar with DeepSee / Analytics, but the output of %CreateDatabase() and %CreateProperty() is a standard persistent class.

Jon Willeke · Jul 17, 2018 go to post

Yes. Use the SYS.Database class to create a .DAT file, Config.Databases to create a database definition (what we used to call a dataset), and Config.Namespaces to create a namespace. These are all in %SYS, so you'll need the appropriate privileges.

Jon Willeke · Jul 23, 2018 go to post

When you call %ToJSON() in a DO context, it writes to the current device, which should not cause a MAXSTRING error. However, due to an interaction with I/O redirection, you should preface the call with WRITE "":

write ""
do specimenArray.%ToJSON()

This may be fixed in recent versions.

Jon Willeke · Aug 22, 2018 go to post

In some cases I prefer the multiple set form for readability and maintainability, as it makes explicit that all of the variables should get the same value. With separate set commands, you could change one without changing the other.

It is known/expected that multiple set is slower than separate set commands, although you shouldn't see as big of a difference when the target is a global or a subscripted local.

You could also abuse set $listbuild for a task like this, although it's probably even slower:

s $lb(v1,v2,v3)=$lb("v1","v1","v2")

For benchmarking, you may want to use a high-precision timer like $zh, rather than $h.