Vitaliy Serdtsev · Feb 27, 2020 go to post

Through %ZEN.proxyObject is unlikely to work, since the q parameter cannot be disabled in this case

q - output numeric values unquoted even when they come from a non-numeric property

Use your own class, for example:

Class dc.test Extends %RegisteredObject
{

Property articlenumber As %String;

}
object ##class(dc.test).%New()
object.articlenumber "15049950"

##class(%ZEN.Auxiliary.jsonProvider).%WriteJSONStreamFromObject(.json,object,,,,"aelotw")

Output:

{
  "articlenumber":"15049950"
}
Vitaliy Serdtsev · Feb 27, 2020 go to post

Conclusion: the quote will be removed from the documentation since it's no longer true.

Then besides this, in the documentation for %STARTSWITH need to add the note DEPRECATED and the recommendation "use LIKE 'XXX%'"

I also did an analysis for Caché 2018.1

Class del.t Extends %Persistent
{

Index ip On p;

Property As %VarString;

/// d ##class(del.t).Fill()
ClassMethod Fill(1000000)
{
  DISABLE^%NOJRN
  ^del.tD,^del.tI

  i=1:1:^del.tD(i)=$lb("","test"_i)
  ^del.tD=N
  ENABLE^%NOJRN

  ..%BuildIndices(,,,$$$NO)
  d $system.SQL.TuneTable($classname(),$$$YES)
  d $system.OBJ.Compile($classname(),"cu-d")
}
}

Although the plans are exactly the same in SMP the results of the metrics differ:

select count(*from del.where like 'test7%'
Row count: 1 Performance: 0.291 seconds 333340 global references 2000537 lines executed

select count(*from del.where %startswith 'test7'
Row count: 1 Performance: 0.215 seconds 333340 global references 1889349 lines executed

For the next two queries, the INT code matches:

&sql(select * from del.where like 'test7%')
&sql(select * from del.where %startswith 'test7')

But for these - is already different, so the metrics in SMP are different:

&sql(select * from del.where like :a)
&sql(select * from del.where %startswith :a)

In IRIS 2020.1, the work with embedded queries was changed, but I can't check it.

Vitaliy Serdtsev · Feb 26, 2020 go to post

Quote from the documentation:

For performance reasons, the predicate %STARTSWITH 'abc' is preferable to the equivalent predicate LIKE 'abc%'. proof

Vitaliy Serdtsev · Feb 26, 2020 go to post

I think that the author meant that the simplest queries of the form
like 'text%'
automatically worked as/converted to
%startswith 'text'

Vitaliy Serdtsev · Feb 25, 2020 go to post

I would look towards modern frameworks like React, Vue, etc., especially considering that CSP/ZEN/Mojo no longer develops.

Vitaliy Serdtsev · Feb 24, 2020 go to post

If you have old version of Caché, you can use %ZEN.Auxiliary.jsonProvider or %ZEN.Auxiliary.altJSONProvider, which have a bunch of useful methods, for example:
%ArrayToJSON
%WriteJSONFromArray
%WriteJSONStreamFromArray
etc.

Here are two small examples:

set array=##class(%ListOfDataTypes).%New()
for i=1:1:4000000 array.Insert("item"_i)
   
write "count = ",array.Count(),!
   
do ##class(%ZEN.Auxiliary.altJSONProvider).%WriteJSONStreamFromObject(.stream,array,,,1,"aeloq")
   
; here you can save stream to a file or send it to the client
set meta=$lb("nameA","nameB","nameC")
   
for i=1:1:4000000 set data(i)=$lb("itemA"_i,"itemB"_i,"itemC"_i)
   
do ##class(%ZEN.Auxiliary.altJSONProvider).%ArrayToJSON(.meta,.data)
Vitaliy Serdtsev · Jan 30, 2020 go to post

Are you sure that the "cdate" type is a string ? Maybe %Date or %PosixTime?

Even if leave a string, the result is highly dependent on ExtentSize. For example, if ExtentSize=10, the plans match.

Vitaliy Serdtsev · Jan 24, 2020 go to post

It should be noted that this applies not only to the try/catch block, but also to other blocks, for example:

i=1:1:2 {
  ; runtime error, but the line is highlighted
}

do {
  ; compilation error
}while(0)

while {
  ; compilation error
}
Vitaliy Serdtsev · Jan 23, 2020 go to post
 

And more food for thought:

Class dc.test Abstract ]
{

/// d ##class(dc.test).test()
ClassMethod test(As %Integer 10)
{
  ..Run1(N),..Run2(N),..Run3(N),..Run4(N)
}

ClassMethod Run1(As %Integer)
{
  i=1:1:4 {
    time(i,"start")=$zh
    f j=1:1:{
        i=2 {
          a=20
        ElseIf i=3 {
          a=30
        ElseIf i=1 {
          a=10
        Else {
          a=-4
        }
    }
    time(i,"end")=$zh
  }

  "If i=2: ",?12,time(2,"end")-time(2,"start")," seconds",!,
    "ElseIf i=3: ",?12,time(3,"end")-time(3,"start")," seconds",!,
    "ElseIf i=1: ",?12,time(1,"end")-time(1,"start")," seconds",!,
    "Else: ",?12,time(4,"end")-time(4,"start")," seconds",!!
}

ClassMethod Run2(As %Integer)
{
  i=2,3,1,4 {
    time(i,"start")=$zh

    f j=1:1:a=$case(i,1:10,2:20,3:30,:-4)

    time(i,"end")=$zh
  }

  "i=1: ",time(1,"end")-time(1,"start")," seconds",!,
    "i=2: ",time(2,"end")-time(2,"start")," seconds",!,
    "i=3: ",time(3,"end")-time(3,"start")," seconds",!,
    "i=4: ",time(4,"end")-time(4,"start")," seconds",!!
}

ClassMethod Run3(As %Integer)
{
  i=1,2,3,4 {
    time(i,"start")=$zh

    f j=1:1:a=$case(i,2:20,3:30,1:10,:-4)

    time(i,"end")=$zh
  }

  "i=1: ",time(1,"end")-time(1,"start")," seconds",!,
    "i=2: ",time(2,"end")-time(2,"start")," seconds",!,
    "i=3: ",time(3,"end")-time(3,"start")," seconds",!,
    "i=4: ",time(4,"end")-time(4,"start")," seconds",!!
}

ClassMethod Run4(As %Integer) [ ProcedureBlock = 0 ]
{
  i,time,j,a
  
  i=2,3,1,4 {
    time(i,"start")=$zh

    f j=1:1:d $case(i,1:a1,2:a2,3:a3,:a4)

    time(i,"end")=$zh
  }

  "i=1: ",time(1,"end")-time(1,"start")," seconds",!,
    "i=2: ",time(2,"end")-time(2,"start")," seconds",!,
    "i=3: ",time(3,"end")-time(3,"start")," seconds",!,
    "i=4: ",time(4,"end")-time(4,"start")," seconds",!!
  q
a1 a=10 q
a2 a=20 q
a3 a=30 q
a4 a=-4 q
}

}

USER>##class(dc.test).test(1000000)
If i=2:     .027962 seconds
ElseIf i=3: .043612 seconds
ElseIf i=1: .073138 seconds
Else:       .068023 seconds
 
i=1: .035705 seconds
i=2: .035941 seconds
i=3: .03498 seconds
i=4: .033288 seconds
 
i=1: .078231 seconds
i=2: .052477 seconds
i=3: .066045 seconds
i=4: .07372 seconds
 
i=1: .051371 seconds
i=2: .052017 seconds
i=3: .050972 seconds
i=4: .052397 seconds
 
 
USER>##class(dc.test).test(1e6)
If i=2:     .060933 seconds
ElseIf i=3: .071999 seconds
ElseIf i=1: .094509 seconds
Else:       .096684 seconds
 
i=1: .058554 seconds
i=2: .058182 seconds
i=3: .059299 seconds
i=4: .054863 seconds
 
i=1: .09973 seconds
i=2: .074734 seconds
i=3: .087673 seconds
i=4: .097123 seconds
 
i=1: .082372 seconds
i=2: .084538 seconds
i=3: .081647 seconds
i=4: .079353 seconds
Vitaliy Serdtsev · Jan 23, 2020 go to post

Hi Peter.

Ok.

"Time for If (i=2): ",time(2,"end")-time(2,"start")," seconds",!,
  "Time for ElseIf #1 (i=3): ",time(3,"end")-time(3,"start")," seconds",!,
  "Time for ElseIf #2 (i=1): ",time(1,"end")-time(1,"start")," seconds",!,
  "Time for Else (i=4): ",time(4,"end")-time(4,"start")," seconds",!!

1e6
Time for If (i=2): .030974 seconds
Time for ElseIf #1 (i=3): .045126 seconds
Time for ElseIf #2 (i=1): .07144 seconds
Time for Else (i=4): .087353 seconds

1e9
Time for If (i=2): 28.59286 seconds
Time for ElseIf #1 (i=3): 43.044261 seconds
Time for ElseIf #2 (i=1): 82.277535 seconds
Time for Else (i=4): 69.212718 seconds
Vitaliy Serdtsev · Jan 23, 2020 go to post

I have (2019.1.1CE) this is not confirmed:

i=1:1:4 {
  time(i,"start")=$zh
  f j=1:1:1e6 {
      i=2 {
        a=2
      ElseIf i=3 {
        a=3
      ElseIf i=1 {
        a=1
      Else {
        a=0
      }
  }
  time(i,"end")=$zh
}

"Time for If (i=2): ",time(1,"end")-time(1,"start")," seconds",!,
  "Time for ElseIf #1 (i=3): ",time(2,"end")-time(2,"start")," seconds",!,
  "Time for ElseIf #2 (i=1): ",time(3,"end")-time(3,"start")," seconds",!,
  "Time for Else (i=4): ",time(4,"end")-time(4,"start")," seconds",!

Time for If (i=2): .109283 seconds
Time for ElseIf #1 (i=3): .060785 seconds
Time for ElseIf #2 (i=1): .08026 seconds
Time for Else (i=4): .109974 seconds
Vitaliy Serdtsev · Jan 23, 2020 go to post

Hi Nigel.

I hasten to inform you that new classes have been added to IRIS for writing/reading streams in JSON format: %Stream.DynamicBinary, %Stream.DynamicCharacter.

 

Here is a sample code:

Class dc.test Extends (%RegisteredObject%JSON.Adaptor)
{

Property string As %VarString(%JSONFIELDNAME "longstring");

Property cs As %Stream.TmpCharacter(%JSONFIELDNAME "cstream");

/// d ##class(dc.test).test()
ClassMethod test()
{
  #define fill(%len,%val) $tr($j("",%len)," ",%val)

  try{
    
    tmp=..%New()
    
    ; the maximum string length when exporting to JSON depends on the length of the property name
    tmp.string=$$$fill($$$MaxLocalLength-($l("longstring")+5),$c(351))
    
    $$$ThrowOnError(tmp.cs.Write($$$fill($$$MaxLocalLength,$c(355))))
    $$$ThrowOnError(tmp.cs.Write($$$fill($$$MaxLocalLength,$c(354))))

    "[Export] len(string): ",$l(tmp.string),", len(cs): ",tmp.cs.Size,!!

    file=##class(%Stream.FileCharacter).%New()
    file.Filename="VeryBigObject.json"
    
    $$$ThrowOnError(tmp.%JSONExportToStream(file))
    $$$ThrowOnError(file.%Save())
    
    ;------------------
    
    dobj={}.%FromJSON(file)
    "1. [Import    %DynamicObject] len(longstring): ",$l(dobj.longstring),", len(cstream): ",dobj.%Get("cstream",,"stream").Size,!

    obj1=..%JSONNew(dobj)
    "2. [Import %RegisteredObject] len(string): ",$l(obj1.string),", len(cs): ",obj1.cs.Size,!

    /*
    
    The following command will generate an error (tested on IRIS 2019.1.1 CE, Unicode):
    
    ERROR: <MAXSTRING>  zWrite^%Stream.TmpCharacter.1
    
    To avoid errors, you need to fix the code in the GenImportCharacterStream method (see the patch below)
    
    */
    
    obj2=..%New()
    $$$ThrowOnError(obj2.%JSONImport(file))
    "3. [Import %RegisteredObject] len(string): ",$l(obj2.string),", len(cs): ",obj2.cs.Size,!

  }catch(ex{
    "ERROR: ",ex.DisplayString(),!
  }
}

/// Get an instance of an JSON enabled class.<br><br>
/// 
/// You may override this method to do custom processing (such as initializing
/// the object instance) before returning an instance of this class.
/// However, this method should not be called directly from user code.<br>
/// Arguments:<br>
///     dynamicObject is the dynamic object with thee values to be assigned to the new object.<br>
///     containerOref is the containing object instance when called from JSONImport.
ClassMethod %JSONNew(
  dynamicObject As %DynamicObject,
  containerOref As %RegisteredObject ""As %RegisteredObject GenerateAfter = %JSONGenerate, ServerOnly = 1 ]
{
  #dim As dc.test=$s($IsObject(containerOref):containerOref,1:..%New())
  r.string=dynamicObject.longstring
  d r.cs.CopyFrom(dynamicObject.%Get("cstream",,"stream"))
  r
}

}

Result:

USER>##class(dc.test).test()
[Export] len(string): 3641129, len(cs): 7282288
 
1. [Import    %DynamicObject] len(longstring): 3641129, len(cstream): 7282288
2. [Import %RegisteredObject] len(string): 3641129, len(cs): 7282288
3. [Import %RegisteredObject] len(string): 3641129, len(cs): 7282288

Patch for 2019.1.1CE:

Instead of

		$$$GENERATE(indent_"  Set sc=stream.Write(%JSONObject."_$$$QN($$$jsonfieldname(propertyMap))_") If $$$ISERR(sc) Goto %JSONImportExit")

need

		$$$GENERATE(indent_"  Set testInvalidField=0, sc=stream.CopyFrom(%JSONObject.%Get(field,,""stream"")) If $$$ISERR(sc) Goto %JSONImportExit")

It is possible that in version 2019.4.x has already fixed everything.

Vitaliy Serdtsev · Jan 22, 2020 go to post

Can I safely read any size %GlobalBinaryStream into a %Binary?
No. Any string (%Binary, %String, %VarString, etc.) has size limit of 3,6MB

PS: if it's not a secret, why do you need to read from a stream to a string?

Vitaliy Serdtsev · Jan 22, 2020 go to post

The example below works even in Caché:

#include %systemInclude n try{   $$$AddAllRoleTemporaryInTry   n $namespace   s $namespace="SAMPLES"      person=##class(Sample.Person).%OpenId(1)      ##class(%ZEN.Auxiliary.altJSONProvider).%WriteJSONStreamFromObject(.stream,person,,,$$$YES,"aeloq")   "json (string): ",stream.Read($$$MaxLocalLength),!!      ##class(%ZEN.Auxiliary.altJSONProvider).%ConvertJSONToObject(stream,"Sample.Person",.obj)   "obj.Home.Street: ",obj.Home.Street ; or d $system.OBJ.Dump(obj)    }catch(ex){   "Error "ex.DisplayString(),! }
Result: USER>^test json (string): {"Name":"Pascal,Martin F.","SSN":"502-68-5767","DOB":43307,"Home":{"Street":"9347 Franklin Drive","City":"Denver","State":"VA","Zip":66346},"Office":{"Street":"4897 Main Blvd","City":"Miami","State":"MO","Zip":60084},"Spouse":"","FavoriteColors":[],"Age":"60"} obj.Home.Street: 9347 Franklin Drive

Or use %JSON.Adaptor: Exporting and Importing

Vitaliy Serdtsev · Jan 17, 2020 go to post

See answer by @Brendan Bannon about TuneTable.

Documentation:

“Relative cost” is an integer value which is computed from many factors as an abstract number for comparing the efficiency of different execution plans for the same query. This calculation takes into account (among other factors) the complexity of the query, the presence of indices, and the size of the table(s). Relative cost is not useful for comparing two different queries. proof

Vitaliy Serdtsev · Jan 17, 2020 go to post

Could you cite the test code for $$select^LVBEPVIS and run it from a VBScript program from Windows Explorer.
Also give your $zv (interested primarily in 8-bit or Unicode).

My test for the Caché Unicode:

LVBEPVIS.MAC:
select(n)   PLIST=4   PLIST(1)="018625110"   PLIST(2)="01862511"_$c(9,233,769)   PLIST(3)="F"_$c(1)_"Female"_$c(1)_"Y"   PLIST(4)=35633   1
test.vbs:
Set AxVisM1 = CreateObject("VISM.VisMCtrl.1") AxVisM1.Server="CN_IPTCP:localhost[1972]:_system:@SYS" AxVisM1.NameSpace="USER" AxVisM1.Execute("s err="""",err=$$select^LVBEPVIS(""018625110"")") WScript.Echo AxVisM1.PLIST
Result:

Vitaliy Serdtsev · Jan 16, 2020 go to post

Example with one file for Windows:

Class dc.test Abstract ]
{

/// d ##class(dc.test).test()
ClassMethod test()
{
  ts=##class(%PosixTime).LogicalToUnixTime(##class(%PosixTime).CurrentUTCTimeStamp()),
    oldName="C:\Tmp\test",
    newName=oldName_$$$FormatText("a%1.txt",ts)
  
  f=##class(%Stream.FileBinary).%New()
  f.LinkToFile(oldName)
  
  gz=##class(%Stream.FileBinaryGzip).%New()
  gz.Filename=newName
  gz.CopyFromAndSave(f)

  ##class(%File).Delete(oldName)
}

}
Vitaliy Serdtsev · Jan 16, 2020 go to post

@Julius Kavay got a point.

I will offer two more options:


Class ABC.Try
{

/// d ##class(ABC.Try).PackageExists()
ClassMethod PackageExists(package "ABC")
{
  ; option by Julius Kavay
  list=##class(%Dictionary.PackageDefinition).GetPackageList()
  ''list.Find($zcvt(package,"U")),!
  
  ; option 2
  ##class(%Library.RoutineMgr).Exists($zcvt(package,"U")_".PKG"),!
  
  ; option 3
  list
  d $system.OBJ.GetPackageList(.list,package)
  ''$d(list),!
}

}

USER>##class(ABC.Try).PackageExists("ab")
0
0
0
 
USER>##class(ABC.Try).PackageExists("abc")
1
1
1

UPD:
Take another look at the %Dictionary.PackageDefinitionQuery:SubPackage/FlatPackage, %ZEN.Utils:EnumeratePackages

Vitaliy Serdtsev · Jan 14, 2020 go to post

You can solve the issue "head-on", namely, to re-convert character stream using %IO.StringStream:

Class dc.test Abstract ]
{

ClassMethod test() {   char="тест",     bin=$zcvt(char,"O","UTF8")

  "dump char:" zzdump char   !!,"dump binary:" zzdump bin

  !!,"Char:",?21,$system.Encryption.Base64Encode($system.Encryption.SHAHash(256,char)),!,      "Binary:",?21,$system.Encryption.Base64Encode($system.Encryption.SHAHash(256,bin)),!!        cs=##class(%Stream.TmpCharacter).%New()   cs.Write(char)      stream=##class(%IO.StringStream).%New()   stream.CharEncoding="UTF8" ; here should be the encoding of your CSV file   stream.CopyFrom(cs; or d stream.CopyFrom(source.Stream)   stream.CharEncoding="Binary"

  /*

  Attention! The following code is needed to work around an error in method the SHAHashStream,   since it expects "Rewind() As %Status", but class the %IO.StringStream uses "Rewind(Output pSC As %Status)"

  */      bs=##class(%Stream.TmpBinary).%New()   bs.CopyFrom(stream)

  "Char (stream):",?21,$system.Encryption.Base64Encode($system.Encryption.SHAHashStream(256,cs)),!,   "Binary (stream):",?21,$system.Encryption.Base64Encode($system.Encryption.SHAHashStream(256,bs)),!! }

}

Result:
dump char:
0000: 0442 0435 0441 0442                                     тест

dump binary: 0000: D1 82 D0 B5 D1 81 D1 82 Ñ.еÑ.Ñ.

Char: 2eEII+27ZRfvbZvK4XNsx7WPDb+82DymPPOAdJ0p1SQ= Binary: 409t7BLE9FmeugePMa6BOUINIbG9LXztfSKwnCB0+0g=

Char (stream): 2eEII+27ZRfvbZvK4XNsx7WPDb+82DymPPOAdJ0p1SQ= Binary (stream): 409t7BLE9FmeugePMa6BOUINIbG9LXztfSKwnCB0+0g=