Eduard Lebedyuk · Mar 17, 2022 go to post

What does this code return for you:

Class test.ts Extends %Persistent
{

Property ts As %TimeStamp(MINVAL = "0001-01-01 00:00:00.000");

/// do ##class(test.ts).Test()
ClassMethod Test()
{
	set obj = ..%New()
	set obj.ts = "1800-12-25T00:00:00"
	set sc = obj.%Save()
	
	if $$$ISERR(sc) {
		write $System.Status.GetErrorText(sc)
	} else {
		write "Save successful"
	}
}
Eduard Lebedyuk · Mar 17, 2022 go to post

By default timestamps are limited  to December 31, 1840, same as horolog.

If you want earlier dates, timestamp property must be defined as:

Property ts As %TimeStamp(MINVAL="0001-01-01 00:00:00.000");
Eduard Lebedyuk · Mar 14, 2022 go to post

Does your IRIS license include InterSystems BI (DeepSee)? In that case you can use that.

Depending on what exactly do you want to do, KNIME might be a solution (if you're ok with self-hosting HTML files  as KNIME server is a commercial product).

But most BI tools are proprietary. Any tool which support xDBC datasources can work with InterSystems IRIS.

Eduard Lebedyuk · Mar 10, 2022 go to post

You can replace:

Set tQuery = "GRANT EXECUTE ON %Library.RoutineMgr_StudioOpenDialog TO VSCODE"
Set tStatement = ##class(%SQL.Statement).%New()
Set tSC = tStatement.%Prepare(tQuery)
Set tResultSet = tStatement.%Execute()

with:

Set tSC = ##class(%SQL.Statement).%ExecDirect(, "GRANT EXECUTE ON %Library.RoutineMgr_StudioOpenDialog TO VSCODE")

Still, unfortunate there's no object way to do that, only relational. And %EnsembleMgr:addSQLPrivilege is private.

Eduard Lebedyuk · Mar 9, 2022 go to post

Use this:

import datetime
horolog = 66177
datetime.date.fromordinal(672046+horolog)

fromordinal counts days from 1st Jan 1, $horolog counts days from 1st Jan 1841, so to get ordinal date from horolog date you need to add ordinal value of 31st Dec 1840 which is 672046.

Eduard Lebedyuk · Mar 7, 2022 go to post

Ah, no. Won't work without parenthesis.

For example "0Z" would return true instead of false.

Eduard Lebedyuk · Mar 5, 2022 go to post

Write a classmethod:

ClassMethod GetStatus(dbFolder) As %String
{
   new $namespace
   set $namespace = "%SYS"
   set db = ##class(SYS.Database).%OpenId(dbFolder)
   quit db.GetStatus()
}

And call it from Python.

Eduard Lebedyuk · Mar 5, 2022 go to post

GetStatus is available in 2017.1 but it's an instance method, not a class method, so you can't call it this way.

You need to wrap it in a class method.

Eduard Lebedyuk · Mar 1, 2022 go to post

Consider updating to InterSystems IRIS and using Native API for Python (2022.1 preview provides db-api connections, previous versions are pyODBC/JayDeBeAPI compatible).

Eduard Lebedyuk · Mar 1, 2022 go to post

Well, sure.

When you execute python script at the beginning add this:

import sys
sys.path.append(r'C:\Temp')

Or set PYTHONPATH variable (requires IRIS restart).

Eduard Lebedyuk · Feb 28, 2022 go to post

1. Open <IRIS>\CSP\bin\CSP.ini

2. Edit Username/Password in [LOCAL] section.

3. Using Management Portal or ^SECURITY routine set the same user as (2) if they do not match.

4. Restart InterSystems IRIS.

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.