go to post Joel Solon · 9 hr ago I clarified what I meant by "confusing" in my post. Maybe "useless" would have been better.
go to post Joel Solon · Dec 30, 2025 VS Code sees the signature of EmployeeSearch() and knows that Employee is a DM.Employee, so you'll get code completion automatically here also.
go to post Joel Solon · Dec 29, 2025 I'm still not a big fan of #dim. It was useful for code completion in Studio. But it hasn't been necessary for code completion in VS Code for a while. Check this post from last year for more details. Perhaps code clarity is the only remaining reason to use it, to help developers who are used to declaring variables in other languages (like Python type hints). My main reason for discouraging its use is that it makes it seem like it's necessary to declare variables in ObjectScript, which it isn't. There is no variable declaration in ObjectScript, as you can read at the top of the documentation that @Vachan C Rannore cited. He is not the first developer to write something like this: "#DIM declares the variable and it's Data Type at COMPILE TIME." #dim causes confusion for developers. As @Evgeny Shvarov says, the compiler skips #dim. But as the examples prove (thanks @Vitaliy Serdtsev), the compiler doesn't skip #dim that includes variable assignment, which it helpfully changes into Set statements. More confusion... The first example looks a little weird because it uses #dim but doesn't include "as datatype". #dim a,b,c = ##class(Sample.Person).%New() Why use #dim syntax that supposedly declares something, and then avoid declaring anything? Confusing. The compiler just changes it into this standard Set syntax. Just use the Set syntax in the first place. set (a,b,c) = ##class(Sample.Person).%New() For the second example, I admit that it's slightly useful to get 3 different objects with a single statement. I never knew about this. The compiler turns this #dim statement into 3 separate Set statements, so it does save some typing. set a = ##class(Sample.Person).%New(), b = ##class(Sample.Person).%New(), c = ##class(Sample.Person).%New() Some developers will like this "insider trick." But I think using an obscure non-command instead of just calling %New() 3 times for this one special case is not worth it. Just use Set statements. By the way, what do you think this statement does? Note the %OpenId(20) at the end instead of %New(). #dim a,b,c As Sample.Person = ##class(Sample.Person).%OpenId(20) Confusing. The addition of "As Sample.Person" has no effect when used with %OpenId(). How about this statement? #dim a,b,c As Sample.Company = ##class(Sample.Person).%New() Do you get 3 companies or 3 persons? Does it try to "cast" persons as companies? Do you get an error at compile time or run time? Would it work if Sample.Company extends Sample.Person? More confusion... The addition of "As Sample.Company" has the same effect as "As Sample.Person" or "As Bla.bla" would. No casting, no error, inheritance doesn't matter. You just get 3 new persons.
go to post Joel Solon · Dec 22, 2025 Nice post. I just want to point out that "handle them carefully" does not mean "check your indexes every day to make sure they're in sync with the database. It does not mean "make sure you run %BuildIndices() on a regular basis to make sure the indexes are in sync with the database." Applications, old or new, ensure that any updates to the data (insert, update, delete) are automatically reflected in the indexes."Handle them carefully" does mean, as Karthick points out: If you're running a query and you're not seeing data that you know is there, investigate whether the index is up to date. You can exclude the index like this: WHERE %NOINDEX Age = 26. If the missing records appear, you know that the index probably needs to be rebuilt. It would be good to investigate why the index got out of date, but that may be difficult. Which is why... ...rarely if ever manipulate the index globals directly, like this: "we accidentally deleted two entries from the index structure (not from the main table)."
go to post Joel Solon · Sep 15, 2025 COS? Creative ObjectScript? Comforting ObjectScript? Conventional ObjectScript? Contemplative ObjectScript? 😁🤣
go to post Joel Solon · Sep 15, 2025 @David Hockenbroch What about setting that variable (say tlevel) equal to the $TLEVEL at the start of the Try, and then in the Catch, only if $TLEVEL = (tlevel +1) (meaning only your TSTART happened), do a TROLLBACK 1? Because if $TLEVEL is >= 2 more than tlevel, it means that your Try called a method that started its own transaction and didn't correctly commit it or roll it back, and perhaps that method called another method that did the same thing, etc. I think coping with that in your Catch is not really your code's job. Do you concur?
go to post Joel Solon · Jul 7, 2025 I don't use docker compose. I just use a shell script and the docker command. I pass --hostname ABC123 which shows up at the top left of the Portal (server ABC123). I think that would be hostname: ABC123 in a docker compose file.
go to post Joel Solon · Jun 30, 2025 One quick answer is a helper ObjectScript class with a wrapper classmethod for each macro. If the helper class can inherit from the class that provides the macros (for example: CustomPackage.TestCase extends %UnitTest.TestCase) then you can replace $$$Macro(args) with self.Macro(args). Otherwise, your helper class must include the INC file that defines the macros, and other classes can call the class methods directly: iris.CustomPackage.HelperClass.Macro(args).
go to post Joel Solon · Jun 16, 2025 @Harry Tong Would %vid help with this (https://docs.intersystems.com/irisforhealth20251/csp/docbook/DocBook.UI....)?
go to post Joel Solon · Jun 16, 2025 Suggestion: Since IRIS is the flagship product for 5+ years, calling the programming language "Caché ObjectScript" doesn't make much sense anymore. "ObjectScript" is enough.
go to post Joel Solon · Jun 6, 2025 I'd use LIKE and OR directly in the query. If there are more than 2-3 comparisons (which would make something like InLike() useful), I'd write a class method that uses Dynamic SQL to add an OR for each comparison to the final WHERE clause.
go to post Joel Solon · May 2, 2025 Are you asking this out of curiosity? Or is there another reason for the question? How would you like to use $this?
go to post Joel Solon · May 2, 2025 Clarification: you can't use * alone as a global mapping. If you want NS1 to get all of its globals from DB1, then the "Default database for globals" field should be DB1. You can then add mappings for certain globals (using * at the end if you want) to additional databases. (changing your question a little) If you add a mapping B* to DB1, and a mapping of BA* to DB2, BA* mapping takes precedence.
go to post Joel Solon · Apr 7, 2025 Can also be used as #include inside a method (applies only to that method)
go to post Joel Solon · Mar 12, 2025 ...and it makes sense that using $listbuild to add at the end would be better than the other two options, which are general purpose ("find the position in the list and put something there").
go to post Joel Solon · Feb 10, 2025 As @Yaron Munz and @Alexander Koblov correctly pointed out, you can use Embedded SQL or Dynamic SQL. You can also use Class Queries. Using Embedded SQL, host variables in IRIS (:varname) can be inputs or outputs, just like host variables in SQL Server and Oracle, but without any DECLARE statement. Host variables used as inputs are automatically sanitized. Examples: WHERE amount > :var1 INSERT INTO ... VALUES (:var1, :var2, :var3) SELECT ... INTO :var1, :var2 FROM ... Dynamic SQL allows ? placeholders for inputs only instead of host variables, also automatically sanitized. Examples: WHERE amount > ? INSERT INTO ... VALUES (?, ?, ?) Dynamic SQL returns output values in the result set object it returns, which you can access and copy into variables. Examples: set resultSet = ##class(%SQL.Statement).%ExecDirect(, sql, values for any placeholders) while resultSet.%Next() { set var1 = resultSet.column1 set var2= resultSet.column2 } Class queries use host variables (:varname) for inputs (like Embedded SQL), and return output values in the result set object (like Dynamic SQL).
go to post Joel Solon · Jan 10, 2025 I think the other commenters clarified it all, but I thought I'd add a little more. Try this variant, with the variable x intentionally undefined. write (1=0) && (x = 0)0 Since 1=0 is 0, you don't get an <UNDEFINED> for x, since the right-hand (x=0) expression is ignored, and the entire statement is 0. Now try this: write 1=0 && x=01 which, as @Roger Merchberger showed, is really write (((1=0) && x) = 0) Since 1=0 is 0, you again don't get an <UNDEFINED> for x, since the x expression (only) is ignored, and the ((1=0) && x) expression is 0. Finally, 0=0 is 1.
go to post Joel Solon · Nov 15, 2024 Thanks for the thoughts! To clarify, I was primarily asking about where to install IRIS itself. Within the context of that question, it's always good to include information about directories for the other important stuff, such as Journals and databases, so thanks for that.
go to post Joel Solon · Nov 11, 2024 Thanks for posting this Joel! I've been wondering about this for a while too! 🤣
go to post Joel Solon · Oct 29, 2024 As others in this thread have said, custom audit events from applications are added to the Audit Log using a call to $system.Security.Audit(). The events must be defined ahead of time in the Portal, using Configure User Events. All events (system and custom) have a 3-part name: Source, Type, and Name. I think of these parts as Main Category, Subcategory, and Name. The person(s) defining the custom events can choose all 3 parts of the name, in order to categorize all the custom events they're defining. So you could use Source = ABC, Type = DEF, and Name = XYZ. The names are totally up to you. Once defined, the event gets added by some code calling $system.Security.Audit("ABC", "DEF", "XYZ").