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 · 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 · Nov 2, 2016 go to post

In the following screenshot, I've just typed "prop", a unique prefix of the property template, then pressed Cmd-/ (the Content Assist shortcut for Emacs key bindings on a Mac):

 

Since its auto insert attribute is on, I expected the template to expand immediately, rather than presenting the popup.

Jon Willeke · Oct 14, 2016 go to post

Worth mentioning that O/S authentication is simpler and potentially more secure than fiddling with passwords in a script. If you do prompt for a password, consider using the -s option of the read command to hide the input.

Jon Willeke · Aug 26, 2016 go to post

I don't understand what you mean when you say that your clinicians aren't database users, but it sounds like you need to give them access to the %Service_SQL service.

Jon Willeke · Aug 23, 2016 go to post

$fromJSON(stream) works for me in 2016.1.0. What problem are you having?

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 · 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 26, 2016 go to post

Does this call do what you want?

USER>w $zstrip(pHL7,">","|^")
PID|1|12345||DOE^JOHN
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 · 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 · Jun 23, 2016 go to post

Unless I've misunderstood the context, it's simpler to use the ERROR macro:

$$$ERROR($$$GeneralError,"DXL Testing Run Error")

You don't need %occErrors.inc to get GeneralError, because of how ERROR (like ERRORCODE) is defined.

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 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 16, 2016 go to post

In general, no, you can't apply string functions directly to streams. I found one such enhancement request from eight years ago that has since been closed. (There may have been others.)

For the specific case of $replace, it should be straightforward to implement Eduard's suggestion using the FindAt() method.

Jon Willeke · Jun 7, 2016 go to post

If you want to wait for either a signal or for termination of a process, you can use a lock:

  1. Process A takes out an exclusive lock.
  2. Process B attempts to lock the same name.
  3. Process A either releases its lock or terminates.
  4. Process B will then get its lock.

You may need an extra synchronization step between steps 1 and 2 to ensure that A gets the lock before B does.

In steps 2 and 4, multiples processes can wait for a shared lock on the same name, and they will all be triggered at the same time.

Jon Willeke · Jun 2, 2016 go to post

$lts and $lfs are the abbreviated forms of $listtostring and $listfromstring, respectively. $lb(1,2,3,",",5) returns a string, which seems to be what you want. I don't understand why you are then converting it to a delimited string with $listtostring.

Jon Willeke · Jun 2, 2016 go to post

Even after looking at Timur and Eduard's answers, I don't understand the question. $lb(1,2,3,",",5) returns a string. Why are you trying to round trip it through a delimited string using $lts and $lfs?

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 · 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 · Mar 16, 2016 go to post

The /exportversion qualifier probably would not have helped in this case. It just strips new keywords from the class definition.

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 · Mar 4, 2016 go to post

Visual Studio Code got a lot of praise in a recent Hacker News discussion of an extension for the Go language. One user mentioned that the PHP extension is quite good, and works well with XDebug. It's also supposed to be good for JavaScript and TypeScript.

I've been using Xamarin Studio to write NUnit tests (C#) on the Mac. I loaded the project into VSC, and it's not bad. It doesn't seem particularly fast or lightweight, as some have claimed, but it's adequate. OmniSharp is included, so you can hover over method calls to see the signature, and each method definition is preceded by a count of how many times it's called.

Java support is not nearly as good out of the box, but there may be some extensions to improve that.

For COS, you could probably configure a task runner to use Atelier's REST APIs to save and compile files to a server. Syntax highlighting and debugging would take some doing, but the hope is that Atelier will scratch that itch better than VSC ever could.

I'd say that Visual Studio Code doesn't have enough to lure me away from Emacs for any extended period of time, but it's a capable, extensible editor.

Jon Willeke · Feb 25, 2016 go to post

Different people are going to have different workflows for unit testing, but I would encourage you to settle on a way to get those tests out of the database. I'd be nervous about running tests in a namespace that contains my only copy, since %UnitTest is designed to load and delete by default.

If you're using Studio with the source control hooks, it works pretty well to write the tests in a development namespace, then run them in a test namespace.