Eduard Lebedyuk · Feb 28, 2022 go to post

Can shorten to:

/// Change database permissions
/// <ul>
/// <li><var>dbDir</var> Path to the database.</li>
/// <li><var>mode</var> Permission mode. 0 = read/write, 1 = read-only. Optional</li>
/// </ul>
ClassMethod SetupDBPermissions(dbDir as %String, mode as %Integer = 0) As %Status {
  New $NAMESPACE
  Set $NAMESPACE = "%SYS"

  Set sc = $$$OK

  Set db = ##class(SYS.Database).%OpenId(dbDir)
  Write "Setting database permission for " _ db.Directory _ ". Setting ReadOnly from " _ db.ReadOnly _ " to " _ mode, !
  Set db.ReadOnly = mode
  $$$ThrowOnError(db.%Save())

  Return sc
}
Eduard Lebedyuk · Feb 23, 2022 go to post

Cool.

Do you autogenerate?

Or did you actually wrote 20 000 classes?

Genuinely curious.

Eduard Lebedyuk · Feb 23, 2022 go to post

You have a lucky design without indices. That makes life with 2 distinct globals easy.

I though you have a common index for current and archive versions. I guess I misunderstood your point.

A simple MERGE ^archive(....)=^source(....)  just doesn't maintain any index.

I think the biggest issue is that it can store only one (previous) version. Or if you merge:

merge ^archive(id, ts) = ^source(id)

you'll need a custom storage for a composite id key.

Eduard Lebedyuk · Feb 23, 2022 go to post

Okay, why would you want to index active versions and old versions together?

In my design I explicitly create indices for the active version only. Old versions are only indexed on the parent field.

Eduard Lebedyuk · Feb 21, 2022 go to post

I'm using a modified second approach.

1. Create a base abstract class with all properties:

Class model.PersonBase Extends (%XML.Adaptor, %JSON.Adaptor) [ Abstract ]
{

Property Name;

}

2. Create persistent class:

Class model.Person Extends (%Persistent, model.PersonBase, Utils.Copyable
{
/// Indices, fkeys and relationships go here
}

Utils.Copyable allows easy copy/compare.

3. Create snapshot class:

Class model.PersonSnapshot Extends (%Persistent,  model.PersonBase, Utils.Copyable)
{

Index parentIndex On parent;

/// Creation timestamp (UTC)
Property createdOn As %TimeStamp(POPORDER = -1, XMLPROJECTION = "NONE") [ InitialExpression = {$ZDATETIME($ZTIMESTAMP, 3, 1, 3)}, Required ];

Property parent As model.Person(XMLPROJECTION = "NONE");

Method %OnNew(parentId As %Integer) As %Status [ Private, ServerOnly = 1 ]
{
    #dim sc As %Status = $$$OK
    
    set ..parent = ##class(model.Person).%OpenId(parentId,, .sc)
    if $$$ISERR(sc) quit sc

    quit ..copyFrom(..parent, ##class(model.PersonBase).%ClassName(1))
}

}

And done.

This way you can add a snapshot of object at any point of time and by calling compareTo calculate a diff between any two versions.

There's also a hash function you can use to calculate hash from some or all object properties to speed up equivalence checks.

Eduard Lebedyuk · Feb 21, 2022 go to post

Use calculated properties for one offs:

Property "status_code" As %Status(XMLPROJECTION = "none");

Property "status_code_xml" As %VarString(XMLNAME = "status_code") [ SqlComputeCode = {set {*} = $System.Status.GetErrorText({"status_code"}}, SqlComputed, Transient ];

Custom datatypes if it's a repeatable issue.

Eduard Lebedyuk · Feb 21, 2022 go to post

Yes, I caught the same behavior. Looks like a bug.

Reported. Please file a wrc if you want to be notified about any changes.

Eduard Lebedyuk · Feb 15, 2022 go to post

Something like this?

SELECT
    ID,
    name,
    processedDate,
    processedTime
FROM MYprocesses m
WHERE NOT EXISTS (  SELECT 1
                    FROM MYprocesses m1
                    WHERE 1=1
                        AND m1.name=m.name
                        AND TO_POSIXTIME(m1.processedDate||' '||m1.processedTime, 'yyyy/mm/dd hh:mm:ss') > TO_POSIXTIME(m.processedDate||' '||m.processedTime, 'yyyy/mm/dd hh:mm:ss'))

Replace TO_POSIXTIME with TO_TIMESTAMP on older versions.

If record ids are aligned with time (meaning higher id has higher processed date/time) you can simplify and speed up the query:

SELECT
    ID,
    name,
    processedDate,
    processedTime
FROM MYprocesses m
WHERE NOT EXISTS (  SELECT 1
                    FROM MYprocesses m1
                    WHERE 1=1
                        AND m1.name=m.name
                        AND m1.id>m.id)
Eduard Lebedyuk · Feb 14, 2022 go to post

Interoperability callbacks can now be written in Python.

Class dc.DFOperation Extends Ens.BusinessOperation
{

Method OnMessage(ByRef request As Ens.StringContainer, Output response As Ens.Response) As %Status [ Language = python ]
{
    import pandas
    import iris

    query = request.value.StringValue
    response.value = iris.cls('Ens.Response')._New()
    
    stmt = iris.sql.prepare(query)
    rs = stmt.execute()
    df = rs.dataframe()
    
    iris.cls('Ens.Util.Log').LogInfo("dc.DFOperation", "OnMessage", "Dataframe load success")
    
    return iris.cls('%SYSTEM.Status').OK()
}

}
Eduard Lebedyuk · Feb 12, 2022 go to post

Just checked XML spec and there's nothing about escaping $c(10) or $c(13).

The only symbols which must be escaped are:

  • " &quot;
  • & &amp;
  • ‘ &apos;
  • ' &apos;
  • < &lt;
  • > &gt;

You can check $zcvt - it produces the same output:

zw $zcvt("< > &" _$c(10)_$c(13)_ "TEST", "O", "XML")

If you need byte for byte compatibility you'll need a custom datatype with a redefined LogicalToXSD method. Something like:

Class test.XMLString Extends %String
{

ClassMethod LogicalToXSD(%val As %String) As %String [ CodeMode = objectgenerator, ServerOnly = 1 ]
{
    quit:%mode'="propertymethod" $$$OK
    Do %code.WriteLine($c(9) _ "set %val = $zcvt(%val,""O"",""XML"")")
    For replace=$lb("$c(10)","""&#xA"""),$lb("$c(13)","""&#xD""") {
        Set from = $lg(replace, 1)
        Set to = $lg(replace, 2)
        Do %code.WriteLine($c(9) _ "set %val = $replace(%val," _ from _ ", " _ to _")")
    }
    Do %code.WriteLine($c(9) _ "quit %val")
    Quit $$$OK
}

}
Eduard Lebedyuk · Feb 12, 2022 go to post

219 characters:

s r=##class(%Net.HttpRequest).%New(),r.Server="pm.community.intersystems.com",r.SSLConfiguration="ISC.FeatureTracker.SSL.Config" d r.Get("/packages/zpm/latest/installer"),$system.OBJ.LoadStream(r.HttpResponse.Data,"c")
Eduard Lebedyuk · Feb 12, 2022 go to post

Is it just a SELECT * FROM table query or is it something else?

Run the query in a Management Portal and compare the timings.

Eduard Lebedyuk · Feb 11, 2022 go to post

I think this would be enough:

Set P("Globals")="%DEFAULTDB"
Set tSC=##class(Config.Namespaces).Create("%All",.P)
Eduard Lebedyuk · Feb 11, 2022 go to post

No. %Save never throws an exception, just returns a %Status variable. In your scenario it would return an error indicating validation failure on LastName.

Class User.Person Extends %Persistent
{

Property LastName As %String(MAXLEN = 30);

/// do ##class(User.Person).Test()
ClassMethod Test()
{
    set obj = ..%New()
    set obj.LastName = $j("", 50)
    set sc = obj.%Save()    
    w $System.Status.GetErrorText(sc)
}
}

Results in:

ERROR #7201: Datatype value ' ' length longer than MAXLEN allowed of 30
  > ERROR #5802: Datatype validation failed on property 'User.Person:LastName', with value equal to " "
Eduard Lebedyuk · Feb 11, 2022 go to post
SELECT Name
FROM %Dictionary.CompiledIndex
WHERE PrimaryKey = 0 AND parent = 'your.class'
Eduard Lebedyuk · Feb 11, 2022 go to post

Fascinating. Maybe we can pack a separate container with a telnet client and a web page connected to it to debug business hosts in a browser.

Eduard Lebedyuk · Feb 11, 2022 go to post

Does foreground mode work only for windows with cterm/iristerm client or can any telnet client connect?

Eduard Lebedyuk · Feb 10, 2022 go to post

From the docs:

  • <STRINGSTACK> An expression is too long, there are too many expressions in an argument for a single command, or an expression contains many very long strings. Simplify the expression.
  • <MAXSTRING> There has been an attempt to specify or create a data string longer than the implementation allows. The maximum string size is 3,641,144 characters. Attempting to concatenate strings that would result in a string exceeding this maximum string size results in a <MAXSTRING> error.

MAXSTRING is always related to maximum length of 3,641,144 characters for one string (assuming long strings are enabled). STRINGSTACK can be raised in several different circumstances, including a lot of small strings, recursion and so on.

Increasing bbsiz may help avoid STRINGSTACK error, but not MAXSTRING.

In your case use streams by replacing:

set file=object.%Get(i).file

with:

set file=object.%Get(i).%Get("file",,"stream")
Eduard Lebedyuk · Feb 7, 2022 go to post

The issue is it would actually hang the process for 10 seconds. MakeTimerCall implementation would not - BP could process other messages in the meantime.