Question Pravin Barton · Feb 14, 2025

For some build scripting with the InterSystems Package Manager, I'd like to first uninstall a package with `zpm "uninstall"` and then load it from disk using `zpm "load"`. That way anything that got deleted from the source will also be deleted from IRIS. 

The problem is that `zpm "uninstall"` takes in a module name and I only have the directory path to the package source. Is there a way to get the module name for an installed IPM module given the source path? It's okay  to assume that it has previously been installed from that same directory with `zpm "load"`.

1
0 88
Question Pravin Barton · Apr 25, 2024

Is there a way to exclude specific members from a class when exporting to an XML or UDL file? Bonus question: is there a way to import from that file without overwriting those members that were excluded?

The use case is to export an interoperability production class without the ProductionDefinition XDATA. We plan to source control the production items through the Ensemble Deployment Manager, but we still need to export any custom code in the class definition itself.

3
0 184
Question Pravin Barton · Sep 12, 2023

I have a VS Code workspace using server-side editing with separate folders for different namespaces on the instance:

{
	"folders": [
		{
			"name": "SYS",
			"uri": "isfs://server:%SYS/"
		},
		{
			"name": "cls",
			"uri": "isfs://server:USER/?filter=*.cls"
		}
	]
}


The VS Code search works well to find symbols in ObjectScript files, but it searches across all folders. Is there a way to include or exclude certain folders from the search? I've tried adding some patterns to the "files to exclude" dialog, but none of these seem to work: SYS/**, /SYS/**, %.*, \%.*

3
0 791
Question Pravin Barton · Apr 18, 2023

I have a class using the JSON adaptor:

Class pbarton.test Extends (%RegisteredObject, %JSON.Adaptor)
{

Property Version As %String;

}

The Version property could be either a string or a number in the JSON source data. If it's a string, importing it will succeed. If it's a number, importing fails.

set t = ##class(pbarton.test).%New()

w t.%JSONImport({"Version": "3"})
1

zw t.%JSONImport({"Version": 3})
"0 "_$lb($lb(9406,"Version","class base",,,,,,,$lb(,"USER",$lb("e^%JSONImportInternal+16^pbarton.test.1^1","e^%JSONImport+12^%JSON.Adaptor.1^1","e^^^0"))))/* ERROR #9406: Unexpected format for value of field, Version, using class base mapping [e^%JSONImportInternal+16^pbarton.test.1^1 e^%JSONImport+12^%JSON.Adaptor.1^1 e^^^0:USER] */

Is there anything I can do to make the class resilient to either string or number formatted values in the imported JSON?

1
0 342
Question Pravin Barton · Apr 7, 2023

On an IRIS system, we expect the default string collation for SQL columns to be SQLUPPER. This means WHERE conditions will be case-insensitive. However, when I make a WHERE condition on a SQL procedure that returns a string, it's case sensitive.For example:

Class Sample.Person Extends%Persistent
{

Property Name As%String;ClassMethod Test() As%String [ SqlProc ]
{
    return"Abe Lincoln"
}

} 
2
0 425
Article Pravin Barton · Sep 1, 2022 4m read

Say I've been developing a web application that uses IRIS as the back end. I've been working on it with unauthenticated access. It's getting to the point where I would like to deploy it to users, but first I need to add authentication. Rather than using the default IRIS password authentication, I'd like users to sign in with my organization's Single Sign On, or some other popular identity provider like Google or GitHub. I've read that OpenID Connect is a common authentication standard, and it's supported by IRIS. What is the simplest way to get up and running?

Example 1: a plain CSP app

2
4 1253
Article Pravin Barton · May 12, 2022 1m read

Say I have a persistent class in IRIS with an optional property EmailOptIn:

Class Person Extends %Persistent
{
Property Name As %String;
Property EmailOptIn As %Boolean;
}

I later realize that I'm doing a lot of null-checking on this property where I shouldn't need to. The solution is to make this a required property:

Class Person Extends %Persistent
{
Property Name As %String;
Property EmailOptIn As %Boolean [ Required ];
}

When I make this change I'll need to update all the existing data to set a reasonable default where it is null. Otherwise I'm in a bad state where updating the Name of a Person will fail if their EmailOptIn is null. I might expect to fix this in SQL as follows:

&sql(update Person set EmailOptIn = 0 where EmailOptIn is null) 

However, running this query will leave me with no rows affected. I can also use a SELECT statement to verify that the WHERE condition matches no rows. I interpret this as SQL assuming none of the entries are null since the property is required. The workaround is a little unintuitive. Even though no index exists, using the %NOINDEX keyword in the condition forces the update to actually check for null entries.

&sql(update Person set EmailOptIn = 0 where %NOINDEX EmailOptIn is null) 

This edge case tripped me up recently. Hopefully this article can save another developer a little time.

7
0 551
Announcement Pravin Barton · Nov 2, 2021

Hello everybody! We’re happy to announce a new way to download Community Edition kits of InterSystems IRIS and IRIS for Health. The Community Edition is a no-cost developer edition that makes it easy to start developing solutions with IRIS. You can now download these kits through the Evaluation service: evaluation.intersystems.com.

If you already have an InterSystems single sign-on account (for example, to post here on the Developer Community), you can log in with those credentials. If you're new to InterSystems, please follow the link from the login page to register for a new account.

3
3 1661
Question Pravin Barton · Jun 14, 2021

I'm working with a REST API that will sometimes rate limit our requests by returning an HTTP 429 Too Many Requests status code. Is there a good way in a business operation to handle this by throttling our requests until we get a success response? My initial thoughts are:

  • Reduce the pool size on the business operation to 1 so all requests are first in, first out.
  • If the API returns a 429 response, set %rate = $get(%rate,1)*2, then hang %rate, then send the request to the back of the queue.
  • If the API returns a success response, kill %rate

I'm wondering if there's a better way to do this. Particularly if this is possible without limiting the business operation to run in a single process.

1
0 374
Article Pravin Barton · May 1, 2020 1m read

ObjectScript doesn't include any built-in method for appending one JSON dynamic array to another. Here's a code snippet I use that's equivalent to the JavaScript concat() method.

Call it with any number of arguments to concatenate them into a new array. If an argument is a dynamic array, its elements will be added. Otherwise the argument itself will be added.

ClassMethod ConcatArrays(pArgs...) As %DynamicArray
{
	set outArray = ##class(%DynamicArray).%New()
	for i=1:1:pArgs {
		set arg = pArgs(i)
		if ($IsObject(arg) && arg.%IsA("%DynamicArray")) {
			set iter = arg.%GetIterator()
			while iter.%GetNext(.key, .value) {
				do outArray.%Push(value)
			}
		} else {
			do outArray.%Push(arg)
		}
	}
	return outArray
}

Feel free to let me know if there's a better way to do this!

6
4 942
Question Pravin Barton · Aug 23, 2019

Hello all, I'm trying to write tests for an interoperability production using %UnitTest.TestProduction. I'd like to control the behavior of the SOAP operations so they don't actually connect to external systems when I'm testing. My first thought for this is to create a mock outbound adapter class that answers with some configured class method:

Class UnitTest.Helper.Integration.MockSoapAdapter Extends EnsLib.SOAP.OutboundAdapter
{

Property MockAdapterAnswerClass As %String;

Property MockAdapterAnswerMethod As %String;

Parameter SETTINGS = "MockAdapterAnswerClass,MockAdapterAnswerMethod";

Method InvokeMethod(pMethodName As %String, Output pResult As %RegisteredObject, pArgs...) As %Status
{
	return $classmethod(..MockAdapterAnswerClass, ..MockAdapterAnswerMethod, pMethodName, .pResult, pArgs...)
}

}

And then in my test I could inject the mock adapter and the answer method through the settings:

ClassMethod SomeAnswer(pMethodName As %String, Output pResult As %RegisteredObject, pArgs...) As %Status
{
	// define some test behavior here
}

Method OnAfterProductionStart() As %Status
{
	$$$QuitOnError(..ChangeSetting(,"Some Operation", "Adapter", "UnitTest.Helper.Integration.MockSoapAdapter"))
	$$$QuitOnError(..ChangeSetting(,"Some Operation", "MockAdapterAnswerClass", ..%ClassName(1)))
	$$$QuitOnError(..ChangeSetting(,"Some Operation", "MockAdapterAnswerMethod", "SomeAnswer"))
	// create request, send request, verify response
}

But I'm finding I can't actually change the outbound adapter class as a setting on the operation. Does anybody have ideas on how to connect an operation to a mock outbound adapter class in a test context? Or is there something simpler I should be doing? Thanks.

4
1 333
Article Pravin Barton · Mar 28, 2019 2m read

ObjectScript has at least three ways of handling errors (status codes, exceptions, SQLCODE, etc.). Most of the system code uses statuses but exceptions are easier to handle for a number of reasons. Working with legacy code you spend some time translating between the different techniques. I use these snippets a lot for reference. Hopefully they're useful to others as well.

5
23 3887
Question Pravin Barton · Nov 20, 2018

For each instance of an XML-enabled class I want to control whether a property is projected as an empty element or not projected at all. With string properties, I can set the value as $c(0) for an empty element, or an empty string for no projection. But it looks like this isn't consistent for properties with type %Boolean or %Integer. With booleans $c(0) gets projected as "false". With integers $c(0) stays as a zero-width space in the XML output (which gives me a SAX parser error if I try to read it).

Is there a way to do this that works for all datatype properties?

2
0 666
Question Pravin Barton · Sep 6, 2018

I'm using Caché as an OAuth authorization server and I want to accept the password credentials grant type. I've found that if I make an authorize request, the Caché authorization server requires some URL parameters that shouldn't be required in password grant (redirect_uri, state, scope, and response_type). If I include these parameters, it calls my DirectLogin() method instead of just calling ValidateUser() as I would expect from the docs. I have two questions:

1. Why does the authorize request fail without these additional parameters?

3
1 757
Question Pravin Barton · Jan 31, 2018

This might be more of a math problem than a Caché question.
I have a SQL query that joins two tables. I want to assign a unique ID to each row of the product table.

  1. I could append the GUIDs of the rows in the two tables, but there are a number of clients that expect a maximum length of 50 on this unique ID. Two GUIDs appended make 72 characters.
  2. I could append the two GUIDs and then truncate the result, but now I'm worried about collision.

What's the chance of collision if I append the GUIDs and truncate the result to 50 characters? Is there a good way to solve this without updating the clients?

12
0 1851
Question Pravin Barton · Oct 30, 2017

I want to limit the length of the value of a textarea in Zen. In HTML the textarea element has a 'maxlength' attribute, but the Zen component doesn't have an equivalent property. Is there any way to add a maximum length in Zen short of creating my own custom component?

3
0 787
Question Pravin Barton · Aug 1, 2017

I'm purging a lot of management data from an Ensemble production, which is creating 100s of GBs of journals. Has anybody succeeded in disabling journaling on an Ensemble purge? The user interface doesn't have an option for this, but I'm thinking you might be able to identify the process and externally disable journaling on it.

3
0 979
Question Pravin Barton · May 2, 2017

It would be nice if there were a permalink displayed for each comment. That way I could bookmark or send a link to a specific comment. I can see links to my own comments in the My Account page, but I don't see any way to get permalinks for other people's comments.

3
0 239
Article Pravin Barton · Jan 13, 2017 2m read

I thought I'd share some issues I had today with Zen development in a custom component. Say we have created a Zen composite component with a few javascript methods that modify its child components. In order to modify these components, we can use something like the following method:

ClientMethod exampleFunction() [ Language = javascript ]
{
    var component this.getChildById('Control1');
    component.setValue('');
}

In JavaScript methods on the client, this refers to the current component or page. The equivalent for expressions that are values of XML attributes is zenThis (see documentation).

0
0 432
Question Pravin Barton · Jan 20, 2016

I'd like to have an array as a parameter for a SQL 'WHERE... IN' statement. The array would be modified in javascript on the browser. Here's a simplified example:

<tablePane  width="25%" id="testTable" sql="SELECT Id from Tracking_Data.Person WHERE Id IN (?)" showQuery="true">
<parameter/>
</tablePane>
<button caption="Test" onclick="zenThis.composite.testTestTable();"/>

ClientMethod testTestTable() [ Language = javascript ]
{
  var table zenThis.composite.getChildById("testTable");
  table.setProperty('parameters', 1, [1,2]);
}

2
0 486