Mike Henderson · Apr 9, 2025 go to post

It appears the inbound text is double-encoded UTF-8 - the problem character is the fancy-quote.  I've seen this in IRIS pipelines where the UTF8 data is read into a character stream without setting the TranslateTable, then exported out through a UTF8 encoder (eg a REST call)

%SYS>s x="Can’t"

%SYS>zzdump x

0000: 0043 0061 006E 2019 0074                                Can’t
%SYS>s y=$ZCVT(x,"O","UTF8")

%SYS>zw y
y="Canâ"_$c(128,153)_"t"

%SYS>w y
Can�t
%SYS>s z=$ZCVT(y,"O","UTF8")

%SYS>zw z
z="CanâÂ"_$c(128)_"Â"_$c(153)_"t"

%SYS>w z
Can�t
Mike Henderson · Feb 17, 2025 go to post

For this specific case I recommend using the %SQL_Util.CSV stored procedure since it handles CRLF on linux correctly. Note - RFC 4180 uses CRLF as the line terminator.

In situations where you need to manually read line-by-line simply trim trailing whitespace including CR

While 'file.AtEnd {
  Set line = file.ReadLine()
  Set line = $ZStrip(line, "<>W", $C(13))
  // ...
}
Mike Henderson · Mar 18, 2024 go to post

FWIW I was able to pull this successfully on Apple M2 macOS 14.4 docker engine 25.0.3. 

Mike Henderson · Aug 23, 2022 go to post

A word of caution - you can exhaust RAM when correlating a object with a collection like this. The pattern I use for collections of unknown size is to correlate the collection tag and loop over the items. If the collection parent (eg Envelope here) is also needed you can use XSL to reorganize the XML nodes to make it easier for the XML reader to iterate. 

Mike Henderson · May 24, 2016 go to post

I've worked around this by creating a comprehensive work-space with Atelier projects for all the databases I need to search. Then I can just drop into shell and use grep/ack. Definitely not as good as Find-in-Files but good enough until Atelier 1.1 and definitely better than firing up a windows VM.

Warning - there is a large storage overhead - mostly in the Atelier/Eclipse .metadata cache especially for large codebases. However operations such as synchronizing and autocomplete perform well.

Mike Henderson · May 13, 2016 go to post

Open enough tabs to get the >> chevron icon on the far right of tab bar. Clicking it will list all the tabs including the hidden ones.

Mike Henderson · Apr 20, 2016 go to post

Thanks for pointing out the toolbar button. 

It would be nice if this was also a menu-item (preferably with hotkey) for those of us that don't use toolbars.

Mike Henderson · Apr 18, 2016 go to post

What do you do when you need to cleanup things like locks?  Put the unlock code both in the Catch and before the Quit?

Mike Henderson · Apr 18, 2016 go to post

Most of my methods look like this:

Method MyMethod() As %Status {
  // Initialization - note locking and transactions only when needed
  Set sc = $$$OK
  Lock +^SomeGlobal:5 If '$TEST Quit $$$ERROR($$$GeneralError,"Failed to obtain lock")

  Try {
    TSTART

    // When error is significant
    $$$ThrowOnError(..SomeMethod())

    // When error can be ignored
    Do ..SomeMethod()

    // When only certain errors apply
    Set sc = ..SomeMethod()
    If $$$ISERR(sc),$SYSTEM.Status.GetErrorText(sc)["Something" $$$ThrowStatus(sc)
  
    TCOMMIT
  }
  Catch e {
    TROLLBACK
    Set sc = e.AsStatus()
  }
  Lock -^SomeGlobal
  Quit sc
}
Mike Henderson · Apr 17, 2016 go to post

I agree with both Nic and Rich.

The issue with e.Log() is that it clutters up the error log with repetitive entries, each subsequent one with less detail than the prior. The same thing happens in Ensemble when errors bubble through the host jobs.

The trick here is knowing when to log an error verses when to just bubble it up. Using Nic's method we lose the context of the stack since the log isn't written until the entry method with the Try/Catch. Using your method we get noise in the logs, but at least the first one has the detail we'll need.

I believe the root problem here is re-throwing the status. An exception should represent something fatal and usually out of the applications control (e.g. FILEFULL) while a %Status is a call success indicator. To that end your code could be refactored to just Quit on an error instead of throwing it. That way a single log is generated in the method that triggered the exception and the stack is accurate.

However this doesn't work well in nested loops. In that case a Return would work unless there is a cleanup chore (e.g. releasing locks, closing transactions, etc). I haven't come up with a pattern for nested loops that doesn't clutter up the source with a bunch of extra $$$ISERR checks that are easily missed and lead to subtle bugs.

Personally I use your style without logging because:

  • Every method uses the same control structure
  • Works with nested loops without extra thought
  • Can ignore errors by simply invoking with Do instead of $$$TOE/$$$ThrowOnError
  • Cleanup code is easy to find or insert
  • Using ByRef/Output parameters makes it trivial to refactor to return more than one value

I do lose the ability to see an accurate stack trace but most of the time the line reference in the error is enough for me to quickly debug an issue so it is an acceptable trade-off. Only in the most trivial methods is the Try/Catch skipped.

All that said Nic's style is a solid approach too. By removing a bunch of boilerplate Try/Catch code it lets the programmer focus on the logic and makes the codebase much easier on the eyes.

Mike Henderson · Apr 17, 2016 go to post

Low priority (since we can compile from a csession) but it would be nice if on P4 revert the class (and dependents) were recompiled.

Mike Henderson · Apr 17, 2016 go to post

Here you go - this will compile in USER namespace

Class User.SQLForSome Extends %Persistent {

Property Name As %String;

Property Domains As list Of %String;

Index Domains On Domains(ELEMENTS) [ Type = bitmap ];

Query TestQuery(Favorites As %List) As %SQLQuery(ROWSPEC = "Name:%String") {
    SELECT Name
    FROM   SQLForSome
    WHERE  FOR SOME %ELEMENT(Domains)(%VALUE %INLIST :Favorites)
}

// Broken when table alias used. Still will save/compile though
Query TestQueryBroken(Favorites As %List) As %SQLQuery(ROWSPEC = "Name:%String") {
    SELECT foo.Name
    FROM   SQLForSome foo
    WHERE  FOR SOME %ELEMENT(foo.Domains)(%VALUE %INLIST :Favorites)
}

Storage Default
{
<Data name="SQLForSomeDefaultData">
<Value name="1">
<Value>%%CLASSNAME</Value>
</Value>
<Value name="2">
<Value>Domains</Value>
</Value>
<Value name="3">
<Value>Name</Value>
</Value>
</Data>
<DataLocation>^User.SQLForSomeD</DataLocation>
<DefaultData>SQLForSomeDefaultData</DefaultData>
<IdLocation>^User.SQLForSomeD</IdLocation>
<IndexLocation>^User.SQLForSomeI</IndexLocation>
<StreamLocation>^User.SQLForSomeS</StreamLocation>
<Type>%Library.CacheStorage</Type>
}

}

Mike Henderson · Apr 12, 2016 go to post

Two issues:

First in 2016.2 (Cache for UNIX (Apple Mac OS X for x86-64) 2016.2 (Build 618U) Wed Mar 16 2016 19:23:28 EDT) two processes are created and both call the Server method. Looks like CSP is invoking %CSP.WebSocket:Page multiple times during the initial connection. The following seems to fix the issue:

Method Server() As %Status
{
    Quit:$D(^CacheTemp.Chat.WebSockets(..WebSocketID,"$J"))
    Set ^CacheTemp.Chat.WebSockets(..WebSocketID,"$J")=$J
    // ...
 

 

Second in WebKit browsers (Safari, Chrome) if the page is refreshed the WebSocket will open but no ..StatusUpdate is received and at least one process starts spinning in an infinite loop. Sometimes it may take more than one refresh, other times multiple processes start spinning.  This may be due to a long-standing bug in chromium where WebSocket close is not called on refresh/close like Firefox does (IE not tested). Adding an addition listener to init() solved the issue and seems to work cross browser:

window.addEventListener("beforeunload", function (event) {
  if (ws) {
    ws.close();
    ws = null
  }
});