@RobertCemper - our paths have crossed a time or two. I hope they will again.
- Log in to post comments
@RobertCemper - our paths have crossed a time or two. I hope they will again.
Hi Enrico,
I am listening! $system.external does not replace the old Java Gateway but the old Java and JDBC Gateways should still be available. I am not intimately familiar with either of those although I did some of the refactoring work with them both. I am not aware of any user code changes that are necessary to keep using them.
If you want to use the new External Java Server then you will necessarily need to write new code. I am happy to provide demos showing how this can be done. I can also assist you with implementing your own project to get you started. Just let me know how you wish to proceed.
As for leveraging existing Java libraries, you should be able to do many things without writing new Java code but not everything. If you have some examples of libraries you have attempted to use but were not successful, please provide information so that I can attempt to replicate your issues.
As always, I am here, listening, and happy to help!!
-Dan
Enrico,
For some reason I didn't get an email alert that you had replied. I'm sure it is buried in my "group" mail somehwere - that's my fault and I'll try to get that sorted.
%Net.Remote.DotNet.Test - I can't help much with that but I certainly can with %Net.Remote.Java.Test. I'll take a look at that now.
$system.external.* is just a helper to get a gateway connection to an external language server such as the Java Server. I refer to the "gateway" as the connection. Maybe that doesn't work for every one. Sorry :(
Once you have a gateway connection you can interact with the external Server using methods supported by that connection. Some of those methods are still unknown to me so I'll have to do some research.
Stay tuned!
Dan
In general, a <LIST> error indicates a malformed $list value.
USER>set bad = "something wrong"
USER>write $li(bad,1)
WRITE $LI(bad,1)
^
<LIST>
USER>set good = $listbuild("something right")
USER>write $li(good,1)
something rightIn %SerializeObject, this typically is caused by an expectation that a value is a valid list but it is not. In a more complex class that extends %SerialObject, one that contains at least one property whose type class is a swizzlable (persistent, serial, stream), $list(value, n) is used to extract the object value (id or serial value) from a value that is expected to be an OID. While OID's an be more complex, the typical OID format is $list(<serialized object value>, <most specific type class>). <serialized object value> can be a simple ID and <most specific type class> (MSTC) is the class of which the object is an instance but not an instance of any subclass of MSTC.
That isn't the only possibility but it is the most likely. I can't be more specific without seeing the full class definition.
-Dan
Along with a co-worker, I worked on this for three days. The good news is that I have something to show. The not-so-good news is that we discovered a bug and a deficiency. The better news is that the next version of JDBC to be released will include these fixes and enhancements. Also good news is that most of the old %Net.Remote.Java.Test class works with minor refactoring.
Mo Cheng will be adding information on how to leverage a more backward compatible Java Gateway. The legacy Java Gateway used generated classes and the new Dynamic Gateway ($system.external) does not. I'll leave that description for Mo to provide.
For now - pending review - I am ready to share a new class (actually a full project). I am just waiting for a place for this to be posted. In the meantime, I could post code snips here. Let me know.
We are working on a project to post to the InterSystems Corporation Github. The project will include Java, ObjectScript, and DotNet. Stay tuned!
I came up with this quick and dirty example. I did discover a problem when I executed this using the Database Console in IntelliJ - evidently the IRIS Server requires statement results to be only ResultSets! (I've reported that problem). If the result of executing the insert statements is not added to the %sqlcontext then this works through client connections, otherwise an error is reported. I'll include the result of executing this from the command line and then displaying the results. Also, the results from executing this from a client with the insert results suppressed.
DROPTABLEIFEXISTS demo_person;
DROPPROCEDUREIFEXISTS multi_results;
CREATETABLE demo_person (nameVARCHAR(20), age INT, home_city VARCHAR(40));
CREATEPROCEDURE multi_results() RESULTSETSLANGUAGE OBJECTSCRIPT
{
set ins = $system.SQL.Prepare("insert into demo_person(name, age, home_city) VALUES(?,?,?)")
do $system.SQL.Execute("TRUNCATE TABLE demo_person")
do %sqlcontext.AddResultSet(ins.execute("Dan", 25, "Miami"))
do %sqlcontext.AddResultSet(ins.execute("Jorge", 32, "Tampa"))
do %sqlcontext.AddResultSet(ins.execute("Enrico", 29, "Turin"))
do %sqlcontext.AddResultSet(ins.execute("Alexy", 21, "London"))
do %sqlcontext.AddResultSet($system.SQL.Execute("SELECT name, age, home_city FROM demo_person ORDER BY age DESC"))
};
CALL multi_results();
USER>set result = $system.SQL.Execute("call multi_results()")
USER>do result.%Display()
Dumping result #1
1 Row Affected
Dumping result #2
1 Row Affected
Dumping result #3
1 Row Affected
Dumping result #4
1 Row Affected
Dumping result #5
name age home_city
Jorge 32 Tampa
Enrico 29 Turin
Dan 25 Miami
Alexy 21 London
4 Rows(s) AffectedThe results when not adding the insert results as displayed by a database console:
| name | age | home_city |
|---|---|---|
| Jorge | 32 | Tampa |
| Enrico | 29 | Turin |
| Dan | 25 | Miami |
| Alexy | 21 | London |
Can you please zwrite sqlResult? It should be an instance of %Library.ProcedureContext. It has properties for sqlcode, message, result set sequence, etc. Knowing what is there will be very helpful.
We posted a sample project to the community github account. There are java and dotnet versions of the project. Take a look and we would appreciate any feedback you may want to offer.
https://github.com/intersystems-community/samples-dynamicgateway-java
https://github.com/intersystems-community/samples-dynamicgateway-dotnet
Please note that there are some deficiencies when working with parameterized Java types when using the new External Java Server. Some of these were discovered during the refactoring of the %Net.Remote.Java.Test code. Some have already been addressed but not yet included in a release.
Hi,
When you say "purging" do you mean that you are deleting the class definition? Or do you simply mean that you are deleting the data - perhaps by invoking %DeleteExtent or %KillExtent?
IRIS has three types of Classes - datatypes (not instantiable), registered (instantiable), and dynamic (not registered but instantiable). Your question seems to involve only registered types. While any data can be serialized/deserialized, we define an interface for doing so - the so-called Storage Interface. Storage Definition is a class member that can be included in just about any class but we only recognize and process it when the class is either a serial or persistent class. A serial class is a class that extends %SerialObject (meaning that %SerialObject is in its primary superclass hierarchy) and a persistent class is a class that extends %Persistent (again, %Persistent is in its primary superclass hierarchy). For these classes, the class compiler will ensure there is a fully defined Storage Definition. As with all class members, the Storage Definition has a type class. This type class is often referred to as the Storage Class - it is a class that implements the Storage Interface. The Storage Definition defines the format of the serialized object. For serial classes, this serialized object value is not directly stored anywhere. Rather, the serialized value is expected to be stored as a value embedded inside of another object. For persistent classes, an object is serialized, perhaps into multiple nodes, and stored in a global at a location defined by the IDKEY index. Persistent classes can also use globals for storing other things such as indexes, counters, metadata and so on. The set of all globals used by a persistent class is referred to as the Extent Set which is, by default, registered with the Extent Manager.
When you remove the Storage Definition from a serial or persistent class, then it is possible that any existing data serialized using the removed Storage Definition will not be able to be deserialized as it might not be compatible with a newly defined Storage Definition. We only recommend removing a Storage Definition from a class if there are no serialized/stored instances of the class present.
When you delete a persistent class, you can also delete the data and the Extent Set by adding the /deleteextent qualifier (or the 'e' flag) to the command. You can also delete the data by invoking %DeleteExtent (logical delete) or %KillExtent (physical delete - no constraints are enforced). That only deletes the data and leaves the Extent Set registered with the Extent Manager. You can delete the Extent Set from the Extent Registry by using utilities we provide. Start here: https://docs.intersystems.com/irislatest/csp/documatic/%25CSP.Documatic…
I am happy to help if you wish (I wrote much of the core serialization/deserialization/storage code) but I will need to know more about the types of classes you are using and the issues you have.
-Dan
I don't know if this works on Windows systems but for macOS and Linux (I use Ubuntu), I defined a file in my home folder named .iris_init with these contents:
:alias pp do ##class(%ASQ.SetUtils).pp($*)
:alias parse try { do ##class(%ASQ.SetUtils).pp(##class(%ASQ.Parser).parse("$*").toDao()) } catch e { w !,e.AsSQLMessage() }
:alias asq try { do ##class(%ASQ.SetUtils).pp($1.apply("$2")) } catch e { w !,e.AsSQLMessage() }
:alias find do find^%z($1,$2,$3,$4)
This file is automatically loaded each time I establish an IRIS session.
Perhaps this will help: https://docs.intersystems.com/irislatest/csp/docbook/platforms/DocBook…
The ISO Standard syntax for JSON_TABLE includes a FORMAT keyword. We are working on a binary compressed format for dynamic arrays and object but it isn't available yet. In addition to the binary format, we hope support other formats in the future. The default FORMAT, as defined by the Standard, is JSON.
Yes, of course. JSON_TABLE in the FROM clause is just another table and can be used as such. If you have wish to use indexes on JSON values stored in columns then you can always define indexes on computed values. That has been possible even before JSON_TABLE. Keep in mind that dynamic data does not always follow expectations as a field in a dynamic object can be a literal, another object or an array of values.
I previously posted about ASQ. You can use ASQ to derive a computed value in your class definition.
I can provide a solution for this but it comes with warnings - SQL doesn't do well with polymorphic embedded objects. If that isn't an issue then consider using a factory pattern. You must define a base class that extends %SerialObject and it must be instantiable. Then, override %OnNew() to dispatch to the concrete class that you wish to instantiate. Perhaps pass in a type argument. That %OnNew() can return an OREF (or a %Status if an error occurs).
If you are interested in this model then I can provide a small demo.
-Dan
You should be able to use Java 17 now for your projects. Are you specifically asking for when we will support Java 17 (and perhaps later versions) when using External Java Server/Java Gateway? If that is the case then I believe you can do that in 2024.1.0.
Thank you for that information. I researched this and was told that we support Java 8+, not just 8 and 11. We submitted a request to correct the documentation.
The simplest answer is that %KillExtent performs a physical delete of the extent (data and indexes) while %DeleteExtent performs a logical delete. The physical delete does not enforce any constraints and does not invoke referential actions. The logical delete does enforce all constraints and performs referential actions.
%KillExtent should not be used in production except in very controlled circumstances. It was meant for use in a development environment. It's only advantage is that it is fast.
And the first two lines can be combined:
set str = ##class(%Stream.FileCharacter).%OpenId("c:\myfolder\file.csv")
I am a huge fan of try/catch. The try is virtually cost-free with overhead only encountered when an exception is caught. I do agree with Jon Willeke - when I am in older code I normally do not refactor $ZTRAP unless the changes I have to make are more than a simple edit. I also use RETURN when exiting a function/method instead of QUIT.
It is possible to simluate a finally but it isn't as nice as having a real finally block would be.
Sorry - I just read Matt Gage's reply. My reply is redundant.
I like @Norman W. Freeman 's answer. That is the factory pattern and I use it regularly. %OnNew is a single method that is implemented in each class. There is a little known feature of %OnNew - it can return an oref whose type class is different from the class that implements %OnNew. In other words, %OnNew can be that factory method. Norman's Create() solution is cleaner IMO. The sole advantage (again, IMO) of a polymorphic %OnNew() is that it works with %New. I can provide a demo is anyone is interested.
Actually, the problem is that %Exception.AbstractException already has five parameters. The error message you received is because the fifth argument has a type of %Exception.AbstractException and your fifth argument has a type of %String.
It is entirely possible to override the implementation of a member inherited from a super class so long as the inherited member is not defined as final and the signature of the local override is compatible with the inherited signature. The class compiler does perform a signature check to ensure the class does not violate the prime directive: Every instance of a class is implicitly a valid instance of its primary super class. Rewording that slightly - the interface of a class must be compatible with the interface of its primary super class. You can add new parameters to an inherited method but the existing parameters, those inherited, must declare a compatible type. A compatible type can be more specific (a subclass of the inherited type) but it cannot be a competely different type.
I tried adding a sixth parameter and also corrected the type of the fifth:
This class compiled cleanly.
ObjectScript does not allow method overloading which is a different thing.
%SQL.Statement is part of the IRIS implementation of Dynamic SQL and it is based on the SQL Standard's Call Level Interface (CLI). It allows the user to prepare and execute any SQL statement, including DDL. The result of executing a dynamic statement is an instance of %SQL.StatementResult. That result contains at least %SQLCODE and %Message, indicating success/failure along with some minimal information about the failure.
Part of Dynamic SQL is also a result set interface - %SQL.IResultSet. There is at least one extention of %SQL.IResultSet, %SQL.ISelectResult. When a statement result is a result set or includes one or more result sets then those result sets are likely instances of %SQL.IResultSet.
The other result set classes (some deprecated) are not necessarily instances of %SQL.IResultSet but do implement the most common members of that interface.
There is another class, %SQL.CustomQuery, that can be used to implement custom queries that are more intuitive than class queries. The documentation for %SQL.CustomQuery contains information on how to implement your own custom query along with an example.
I was tempted to criticize the Mixtral response. I did implement the original versions of both of these items so I know them both well. The Mixtral code example for %SQL.Statement is incorrect in a few ways but I'm surprised that it was as close as it is.
Once that temptation was put aside, I realized that Mixtral can only analyze what it finds. If it can't find the solution then perhaps our class documentation is not going to provide the answer to a user. Hmmm...
The PackageDefinition class contains only packages where we need to keep metadata about a package. Most packages do not have any extra metadata.
The SQL solution is the correct solution IMO. If I were a developer on the Object team I would advocate for a method in $system.OBJ - PackageExists(<package_name>) perhaps. :)
Basically the same as Enrico's:
LATEST:USER>set result = $system.SQL.Execute("select * from %SYS.Namespace_List()")
LATEST:USER>do result.%Display()
Nsp Status Remote
%SYS 1 0
B360 1 0
USER 1 0
3 Rows(s) Affected
set stream = ##class(%Stream.FileCharacter).%OpenId("/Users/.../data/continents-en.json")Did you know that you can just open a file as a stream?
And for iterating - this has been in the product for a while now. This is the code for my :pp alias.
ClassMethod pp(setAs%AbstractSet, offset As%Integer = 0)
{
#define QUOTE(%val) $zu(144,1,%val)
try {
set isLabeled = set."_isLabeled"()
if (isLabeled) {
write"{"setclose = "}"
} else {
write"["setclose = "]"
}
set it = set.iterator()
while it.hasNext() {
set next = it.next()
if$isobject(next.value) {
write !,?(offset+2)
write:isLabeled $$$QUOTE(next.key),": "do..pp(next.value, offset + 2)
} else {
write !,?(offset+2),$select(isLabeled:$$$QUOTE(next.key)_": ",1:""),$$$QUOTE(next.value)
}
if it.hasNext() {
write","
}
}
write !,?offset,close
} catch exc {
write !,"Exception caught: ",exc.AsSQLMessage()
}
return
}This is in %ASQ.SetUtils. My alias, pp - pretty print, is this:
pp do ##class(%ASQ.SetUtils).pp($*)
@Andre Larsen Barbosa - I am the author of the intiial dynamic SQL 'feature'. I no longer work in this area and many significant enhancements have been made since my initial work. That said, I applaud your article. It is quite nice.
There is one feature that not many people know about - there is an Object mode that you can use with Dynamic Statement. Object mode is interesting when your query selects column values whose underlying type is a IRIS ObjectScript Class that is an object type (Persistent, Serial, Stream). Here is a trivial example of how it might be used.
LATEST:USER>set statement = ##class(%SQL.Statement).%New()
LATEST:USER>set statement.%ObjectSelectMode = 1
LATEST:USER>do statement.prepare("select name,address from person")
LATEST:USER>set result = statement.execute()
LATEST:USER>write result.%Next()
1
LATEST:USER>write result.name
po1
LATEST:USER>write result.address
7@User.Address
LATEST:USER>write result.address.city
Boston
LATEST:USER>write result.address.state
MA