The problem is NOT the QUIT in an IF statement, the problem is, using a QUIT WITH ARGUMENT in a TRY/CATCH statement.
- Log in to post comments
The problem is NOT the QUIT in an IF statement, the problem is, using a QUIT WITH ARGUMENT in a TRY/CATCH statement.
First, there is no NaN in JSON.
set dynObj = {"val":($double("NAN"))}
write dynObj.val + 123 ---> "NAN
zwrite dynObj
dynObj={"val":($double("NAN"))} ; <DYNAMIC OBJECT>
/* Question @devcommunity manager: how to exit the "insert code block" function? */
Second, red this for more understanding
I'm not sure, do I understand you correctly...
If you had a class and copied that class to a new class with some more properties, something like this
Class DC.OldData Extends %Persistent
{
Property FirstName As %String;
Property LastName As %String;
/// Redirect the %Open(), %OpenId() to the new class
///
ClassMethod %OnDetermineClass(oid As %ObjectIdentity, ByRef class As %String) As %Status [ ServerOnly = 1 ]
{
Set class="DC.NewData"
Quit $$$OK
}
/// Prevent creating a new instance of the old class
Method %OnNew() As %Status [ Private, ServerOnly = 1 ]
{
Quit '$$$OK
}
}
Class DC.NewData Extends %Persistent
{
Property FirstName As %String;
Property LastName As %String;
Property MartialStatus As %String;
}Assuming, your XML stream never has more then MAXSTRING characters AND if you can live with a "quick-and-dirty" solution then try this one
Class DC.XML.abc Extends (%RegisteredObject, %XML.Adaptor)
{
Property def As %String;
Property ghi As %String;
Property jkl As jkl;
ClassMethod Test(str)
{
if $isobject(str) { set:str.Rewind() xml=str.Read(str.Size) } else { set xml=str }
for{set i=$f(xml,"<![CDATA[",$g(i)) q:'i
set j=$f(xml,"]]>",i) zt:'j "XMLE"
set $e(xml,j-3,j-1)="", $e(xml,i-9,i-1)="", i=i-9
}
set rdr=##class(%XML.Reader).%New()
if 'rdr.OpenString(xml) write "OpenErr",! quit
do rdr.Correlate("abc","DC.XML.abc")
while rdr.Next(.abc,.st) { zzdo abc }
}
}
Class DC.XML.jkl Extends (%RegisteredObject, %XML.Adaptor)
{
Property mno As mno;
}
Class DC.XML.mno Extends (%RegisteredObject, %XML.Adaptor)
{
Property pqr As %String;
Property stu As %String;
}
And some tests...
set s1="<abc><def>010203</def><ghi>040506</ghi><jkl><mno><pqr>070809</pqr><stu>101112</stu></mno></jkl></abc>"
set s2="<abc><def>010203</def><ghi>040506</ghi><jkl><![CDATA[<mno><pqr>070809</pqr><stu>101112</stu></mno>]]></jkl></abc>"
do ##class(DC.XML.abc).Test(s1)
def................................: 010203
ghi................................: 040506
jkl.mno.pqr........................: 070809
jkl.mno.stu........................: 101112
do ##class(DC.XML.abc).Test(s2)
def................................: 010203
ghi................................: 040506
jkl.mno.pqr........................: 070809
jkl.mno.stu........................: 101112
Note: the above ZZDO command takes an oref as argument and prints it, you can replace it with a simple zw oref.
I have made several callouts but never a callin (with userlogin), like you trying to do.
You wrote, you have several instances (Cache and IRIS) installed, presumably on the same hardware respective OS. Take a look in one of your Cache installation: <InstallDirectory>\dev\Cache\callin\
There are three files worth to study: sampcallin.c, sampcallint.c and shdir.c
The callin mechanism does not needs IPs and ports. To find out if anything has changed as a result of switching from Cache to IRIS, please contact WRC.
You do not need to reference 'localhost', your C++ program and IRIS are located (and running) on the same 'localhost', the source of your problem is something else
Nota Bene: I adopted Robert Cemper's suggestion ISOS, as shorthand for InterSystemsObjectScript
According to my opinion, your ISOS example (and the way, how JSON was implemented by ISC), is a mix of a little bit ISOS and a little bit JSON.
Why? Take this example:
set myObject = { "p1":"abcd", "p2":true, "p3":value, "p4":(sum+2) }
<- ObjScript -><-------------------- JSON------------------------->The example starts with an ISOS syntax and proceeds with JSON syntax.
Unfortunately, the last two properties (p3, p4) are invalid JSON properties.
So we have a perfect mixture!
Remember, ISOS does not have literal constants (like true, false and null) and JSON neither has variables (like value) nor expressions, like (sum+2).
Since the current implementation has been in use for several years, a reimplementation is (probably) impossible. With other words, the bottom line is, unwillingly, but we have to accept the status quo...
Apparently, someone at ISC got a huge discount on parentheses (the easy solution) instead of requiring that these damned parentheses only be used when the assignment is an expression (and the user-friendly version would be: only when the expression contains the [ or ] operators!).
justmy2cents
Put your variables into parentheses, i.e.
return {"value1":(val1), "value2":(val2)}Could you please run the following commands in a terminal session:
write $zv
set exl=##class(%SYS.Python).Import("openpyxl")
set wbk=exl."load_workbook"( "your path_and_filename.xlsx" )
set sht=wbk.active
set cel=sht.cell(row,col) // ROW and COL should point to a date-cell
write cel."data_type"
write cel.value.strftime("%d.%m.%Y")Please post a screenshot here, even if you get some kind of error
Excel store date values as decimal values (or better, as flotaing poin) as dddd.tttt where ddd is the number of days since a base date (usually 1899-12-30) and tttt is: numberOfSecondsSinceMidnight / 86400, hence the value, you get is an date-object, and you have to format it according to your needs
Class DC.PyExcel Extends %RegisteredObject
{
ClassMethod Test(fn = "/home/kav/test/readtest.xlsx")
{
set exl=##class(%SYS.Python).Import("openpyxl")
set wbk=exl."load_workbook"(fn)
set sht=wbk.active
for row=1,2,3 {
for col=1:1:3 {
set cel=sht.cell(row,col)
set typ=cel."data_type"
set val=cel.value
write ?col-1*15,$case(typ, "s":val, "n":$fn(val,",",2), "d":val.strftime("%a, %d.%m.%Y"), :"")
}
write !
}
}
}
I'm just curious, what do you get, if you type write $view(-1,$job) in the output window of Studio?
See my screenshot, red=my input, yellow=Studio output
More info, please. What is $stack(0,...) supposed to represent?
Your lines can be shortened to
if $system.Process.ClientExecutableName($j) = "..." { ... } else { ... }justmy2cents
Port 1972 is the default port, your actual port may be different. That's why I added a comment to the answer above. Check the parameter file of your installation:
for IRIS : see iris.cpf, section [Startup], DefaultPort=...
for Cache: see cache.cpf, section [Startup], DefaultPort=...
At least, there are four different ways to get that info
ClassMethod StudioTest()
{
write "Call from Studio1: ",..InStudio1(),!
write "Call from Studio2: ",..InStudio2(),!
}
/// Is the invocation from the Studio?
ClassMethod InStudio1()
{
for i=$st:-1:1 if $st(i,"PLACE")["%Studio.General." ret 1
ret 0
}
/// Is there a connection to Superserver?
ClassMethod InStudio2()
{
set port=1972 // see iris.cpf, section [Startup], DefaultPort=...
quit $p($v(-1,$j),"^",3)[("|TCP|"_port_"|")
}
Compiling routine DC.Util.1
Compilation finished successfully in 0.024s.
do ##class(DC.Util).StudioTest()
Call from Studio1: 1
Call from Studio2: 1
ICINDY:USER>; from PuTTY -------------------
ICINDY:USER>do ##class(DC.Util).StudioTest()
Call from Studio1: 0
Call from Studio2: 0
The other two methods are: checking for the presence of
a) a dedicated variable or
b) a dedicated object
but both require a usage of undocumented functions
I can't reproduce the problem
Well, I'm neither part of the ObjectScript nor the Objects developer team, hence I can't answer the "why" part of your question but fact is, timing mesuremenst show a significat higher speeds for the literal versions:
ClassMethod DynObject()
{
while $zh#1 {} set t1=$zh for i=1:1:1E6 { if ##class(%DynamicArray).%New() } set t1=$zh-t1
while $zh#1 {} set t2=$zh for i=1:1:1E6 { if ##class(%DynamicObject).%New() } set t2=$zh-t2
while $zh#1 {} set t3=$zh for i=1:1:1E6 { if [] } set t3=$zh-t3
while $zh#1 {} set t4=$zh for i=1:1:1E6 { if {} } set t4=$zh-t4
write "Times for : Class Literal Diff",!
write "DynArray :", $j(t1,9,3), $j(t3,9,3), $j(t1/t3-1*100,9,2),"%",!
write "DynObject :", $j(t2,9,3), $j(t4,9,3), $j(t2/t4-1*100,9,2),"%",!
}
The output will depend on
- IRIS/Cache version in use and
- on the underlying hardware
My values are
USER>d ##class(DC.Times).DynObject()
Times for : Class Literal Diff
DynArray : 0.665 0.401 65.90%
DynObject : 0.649 0.401 61.87%
Maybe someone else or the WRC has an explanation...
JavaScript uses underscores as separators in (big)number literals.
<script>alert(1_234);</script> displays 1234
<script>alert(12_345_678_901_234_567_890_123n);</script> displays 12345678901234567890123
Maybe there is a quirk/problem in VS terminal...
Oh yes, you are able to create an instance of
Class Your.Page Extends %CSP.Page
{
Property Name As %String [ InitialExpression = "Joe" ];
}class because the inheritance goes as follows:
Your.Page <-- %CSP.Page <-- %Library.Base <-- %Library.SystemBase
and the %Library.SystemBase donates you the %New() method.
My guess is, because abstract classes (%CSP.Page is an abstract class) can't be instantiated, your subclass lacks the generator methos for property initialisation.
But there is a simple solution:
Class Your.Page Extends (%RegisteredObject, %CSP.Page)
{
Property Name As %String [ InitialExpression = "Joe" ];
}Well, the world is right again
set page=##class(Your.Page).%New()
write page.Name --> JoeHa ha ha 😂, that's a big mistake. Those are old functions (for even older applications, maintained for backward compatibility only) in the mean time all replaced by the $list...() functions.
Try it this way...
Class DC.OldStuff Extends %Persistent [ StorageStrategy = NewStorage1 ]
{
Property Rec As %Integer [ Identity ];
Property Name As %String;
Property City As %String;
Property Phone As %String;
Storage NewStorage1
{
<SQLMap name="Map1">
<Data name="City">
<RetrievalCode>s {*}=$zel(^myGlo("R",{L2}),2)</RetrievalCode>
</Data>
<Data name="Name">
<RetrievalCode>s {*}=$zel(^myGlo("R",{L2}),1)</RetrievalCode>
</Data>
<Data name="Phone">
<RetrievalCode>s {*}=$zel(^myGlo("R",{L2}),3)</RetrievalCode>
</Data>
<Global>^myGlo</Global>
<Subscript name="1">
<Expression>"R"</Expression>
</Subscript>
<Subscript name="2">
<Expression>{Rec}</Expression>
</Subscript>
<Type>data</Type>
</SQLMap>
<StreamLocation>^DC.OldS</StreamLocation>
<Type>%CacheSQLStorage</Type>
}
}
A short test shows, it works
USER>k ^myGlo
USER>s ^myGlo("R",1)=$zlp("John,Boston,11-22-33")
USER>s ^myGlo("R",5)=$zlp("Laura,New York,333-444-555")
USER>s ^myGlo("R",7)=$zlp("Paul,Chicago,556-666-777")
USER>d $system.SQL.Shell()
SQL Command Line Shell
----------------------------------------------------
The command prefix is currently set to: <<nothing>>.
Enter q to quit, ? for help.
USER>>select * from DC.OldStuff
3. select * from DC.OldStuff
ID City Name Phone Rec
1 Boston John 11-22-33 1
5 New York Laura 333-444-555 5
7 Chicago Paul 556-666-777 7
3 Rows(s) Affected
statement prepare time(s)/globals/lines/disk: 0.0003s/5/159/0ms
execute time(s)/globals/lines/disk: 0.0003s/13/1136/0ms
cached query class: %sqlcq.USER.cls43
---------------------------------------------------------------------------
USER>>quit
or as objects
USER>s obj=##class(DC.OldStuff).%OpenId(7)
USER>w obj.Name,!,obj.City,!,obj.Phone
Paul
Chicago
556-666-777
USER>
First step: create your own method, for example
Class DC.Unix [ Abstract ]
{
/// Convert Posix time into Timestamp
///
/// posix: posix time
/// df: date format
/// tf: time format
/// dp: decimal places
ClassMethod PosixToTimeStamp(posix, df = 3, tf = 1, dp = 0)
{
set posix=posix-1152921504606846976/1E6
quit $zdt(posix\86400+47117_","_(posix#86400),df,tf,dp)
}
}
Next step, if you need more speed, instead of the parameters <df> and <tf> use constants and remove <dp>. The very last step: shorten the method name from PosixToTimeStamp() to P2TS()
This way I got the (nearly) the same times as with (%Library.PosixTime).LogicalToTimeStamp, but without the need for string manipulation
ICINDY:USER>k
ICINDY:USER>set posix = 1154669852181849976
ICINDY:USER>while $zh#1 {} s t=$zh f i=1:1:1E6 { s x=##class(%Library.PosixTime).LogicalToTimeStamp(posix) } w $zh-t
.902538
ICINDY:USER>while $zh#1 {} s t=$zh f i=1:1:1E6 { s x=##class(%Library.PosixTime).LogicalToTimeStamp(posix) } w $zh-t
.90609
ICINDY:USER>
ICINDY:USER>while $zh#1 {} s t=$zh f i=1:1:1E6 { s x=##class(DC.Unix).PosixToTimeStamp(posix) } w $zh-t
.934834
ICINDY:USER>while $zh#1 {} s t=$zh f i=1:1:1E6 { s x=##class(DC.Unix).PosixToTimeStamp(posix) } w $zh-t
.944418
ICINDY:USER>
ICINDY:USER>while $zh#1 {} s t=$zh f i=1:1:1E6 { s x=##class(DC.Unix).P2TS(posix) } w $zh-t
.913609
ICINDY:USER>while $zh#1 {} s t=$zh f i=1:1:1E6 { s x=##class(DC.Unix).P2TS(posix) } w $zh-t
.905303
ICINDY:USER>
ICINDY:USER>w $zv
IRIS for UNIX (Ubuntu Server LTS for x86-64) 2021.2 (Build 649U) Thu Jan 20 2022 08:49:51 EST
ICINDY:USER>w x
2025-05-27 12:06:15
ICINDY:USER>
You have a status (or error code), OK, now take that code and make it more readable:
set code=<put here your code>
write $system.Status.GetErrorText(code)I think, it will be some kind of authentication issue...
In SystemManagementPortal goto:
SystemAdministration --> Configuration --> AdditionalSettings --> StartupSettings: JobServers
I think, Enrico was more aiming for the point that “concatenation is the gateway for SQL injection.”
An easy and simple (assuming standard storage strategy) way to get those informations (the slot numbers, where a property is stored) could be achieved with a simple classmethod
Class DC.PropInfo [ Abstract ]
{
/// Info about properties of a class:
/// - list of all properties stored in a list
/// - slot number for a given property
///
/// 1) add this class to your class definition
/// class some.class extends (%Persistent, DC.StorageInfo) or
/// class some.class extends (%SerialClass, DC.StorageInfo)
///
/// 2) then use it as follows
/// write ##class(some.class).PropInfo() --> list of property names
/// write ##class(some.class).PropInfo("Age") --> slot number for the Age property
///
/// write ##class(
ClassMethod PropInfo(name = "") As %String [ CodeMode = objectgenerator ]
{
set sto=%compiledclass.Storages, prp=0, list=""
if sto.Count()=1 {
set dat=sto.GetAt(1).Data
for i=1:1:dat.Count() if dat.GetAt(i).Structure="listnode" set prp=dat.GetAt(i) quit
if prp {
if %compiledclass.ClassType="serial" { set list="", i=1 } else { set list=$lb(""), i=2 }
for i=i:1:prp.Values.Count() set list=list_$lb(prp.Values.GetAt(i).Value)
}
do %code.WriteLine(" if name="""" quit """_$lts(list)_"""")
do %code.WriteLine(" quit $lf($lfs("""_$lts(list)_"""),name)")
}
if list="" write !,"*** No properties found! ***"
quit $$$OK
}
}
Two test classes
Class DC.TestPerson Extends (%Persistent, DC.PropInfo)
{
Property Name As %String;
Property Addr As DC.Address;
Property Salary As %Numeric;
Property Expertise As list Of %String;
}
Class DC.Address Extends (%SerialObject,DC.PropInfo)
{
Property Street As %String;
Property City As %String;
}Now you can do things like:
write ##class(DC.TestPerson).PropInfo() --> ,Name,Addr,Salary,Expertise
write ##class(DC.TestPerson).PropInfo("Salary") --> 4
// you can use the slot number for direct data access:
write $list(^DC.TestPersonD(id),slotnumber) gives you the same value as
write ##class(DC.TestPerson).SalaryGetStored(id)
// the same game for serial classes
write ##class(DC.Address).PropInfo() --> Street,City
write ##class(DC.Address).PropInfo("City") --> 2
// in case you have an instance
set pers=##class(DC.TestPerson).%OpenId(id)
write pers.PropInfo() --> ,Name,Addr,Salary,Expertise
write pers.Addr.PropInfo() --> Street,City
// etc.