Eduard Lebedyuk · Sep 1, 2022 go to post

Except that calling Python is about 10x slower, a

Not really, more like faster if you need to call it more than once:

 

Code

Class Utils.Time
{

/// do ##class(Utils.Time).Test()
ClassMethod Test()
{
	do ..Main(1)
	do ..Main(1)
	do ..Main(1)
	do ..Main(2)
	do ..Main(3)
	do ..Main(5)
	do ..Main(10)
	do ..Main(100)
	do ..Main(1000)
	do ..Main(10000)
}

/// do ##class(Utils.Time).Main(100)
ClassMethod Main(count = 100)
{
	set od=$io
	set nul="\\.\nul" // /dev/null/ - UNIX
	open nul
	use nul
	
	s startA = $NOW()
	do ..JobA(count)
	s endA = $NOW()
	s timeA = $p(endA,",",*) - $p(startA,",",*)
	
	s startB = $NOW()
	do ..JobB(count)
	s endB = $NOW()
	s timeB = $p(endB,",",*) - $p(startB,",",*)
	
	use od
	close nul
	
	w "Iterations: ",count,!
	w "Time JobA: ",timeA,!
	w "Time JobB: ",timeB,!
	w "JobA takes ",$FN(timeA/timeB*100,"",2) _ "% of JobB time",!,!
}

ClassMethod JobA(count = 100) As %Status
{
	for i=1:1:count {
		set now = $now()
		set ts = $zdt(now,-2) _ "." _ $p(now, ".", 2)
	}
}

ClassMethod JobB(count = 100) As %Status
{
	set time = ##class(%SYS.Python).Import("time")
	for i=1:1:count {	
		set ts = time.time()
	}
}
}

Results in:

 

Output

Iterations: 1
Time JobA: .0000257
Time JobB: .0000728
JobA takes 35.30% of JobB time
 
Iterations: 1
Time JobA: .0000154
Time JobB: .0000206
JobA takes 74.76% of JobB time
 
Iterations: 1
Time JobA: .0000157
Time JobB: .0000158
JobA takes 99.37% of JobB time
 
Iterations: 2
Time JobA: .0000214
Time JobB: .0000161
JobA takes 132.92% of JobB time
 
Iterations: 3
Time JobA: .0000264
Time JobB: .0000158
JobA takes 167.09% of JobB time
 
Iterations: 5
Time JobA: .0000528
Time JobB: .0000258
JobA takes 204.65% of JobB time
 
Iterations: 10
Time JobA: .0000987
Time JobB: .0000256
JobA takes 385.55% of JobB time
 
Iterations: 100
Time JobA: .0007513
Time JobB: .0000606
JobA takes 1239.77% of JobB time
 
Iterations: 1000
Time JobA: .0057593
Time JobB: .0002278
JobA takes 2528.23% of JobB time
 
Iterations: 10000
Time JobA: .0535617
Time JobB: .0036764
JobA takes 1456.91% of JobB time
Eduard Lebedyuk · Sep 1, 2022 go to post

1. Yes. Stopping primary makes backup a new primary. Before stopping you might want to validate that backup is caught up:

set sql = "SELECT CASE WHEN DatabaseLatency=? THEN 1 ELSE 0 END IsCaughtUp FROM SYS.Mirror_MemberStatusList() WHERE CurrentRole = ?"
set rs = ##class(%SQL.Statement).%ExecDirect(,sql, "Caught up", "Backup")
do rs.%Next()
if rs.IsCaughtUp {
	write "Caught up"
	halt
} else {
	write "Not caught up"
	do $system.Process.Terminate(,1)
}

Or at least is not too far behind (check this post).

2.  No. ^MIRROR does the same.

Eduard Lebedyuk · Sep 1, 2022 go to post

There's a distinction between Background Jobs and Background Tasks.

Backgound job is anything you run using JOB command. Background Task is a limited set of named actions (AuditCopy, AuditExport, AuditPurge, Backup, CompactDBSpace, CopyNamespaceMappings, CreateDatabase, Compile, DatabaseIntegrityCheck, DataMigration, DefragmentDB, Delete, EnableEnsNamespace, Export, ExternalLangServers, FileMan, Import, SQLExport, SQLImport, SQLExportStatement, SQLImportStatement, QueryExport, JournalIntegrityCheck, LinkTable, LinkProcedure, MirrorActivateCatchupDB, MirrorRemoveDB, MirrorMountDB, MirrorAddDatabases, ModifyDatabaseSize, RebuildIndices, TuneTable, TuneTables, PurgeAllCachedQueries, JobShowPlan, JobSaveQuery, JobPossiblePlans, JobComparePlans, ShardActivate, ShardAssign, ShardVerify, ShardRebalance) runnable through a special interface. Docs.

You can run a Background Task (but probably should not  - it's a system action) by calling:

set parms("ClassName") = "Sample.Person"
set tSC = ##class(%CSP.UI.System.BackgroundTask).RunTask("RebuildIndices", "SAMPLES", .parms, .job)

As HIHLib.Support.GetHL7MessageStat:ISBListingQuery is not on the list you can't run it as a Background Task, but you can run it as a Job.

Next - no output. Note that you do not specify these parameters:

principal-input Principal input device for the process. The default is the null device.
principal-output
Principal output device for the process. The default is the device you specify for principal-input or the null device if neither device is specified.
UNIX®: If you do not specify either device, the process uses the default principal device for processes started with the JOB command, which is /dev/null.

Further:

Only one process can own a device at a time. This means that a job executing in a JOB Server is unable to perform input or output to your principal I/O devices even though you may close device 0.

So by default you run your job with stdio set to /dev/null and that's why there's no output.

You can either pass a device to write output to or write a wrapper which would handle the output and job that.

Eduard Lebedyuk · Aug 27, 2022 go to post

Sounds like you're writing SELECT * queries. While fine for debugging or development, remember to always explicitly specify the list of columns in actual production queries.

Eduard Lebedyuk · Aug 24, 2022 go to post

Users certainly should not be able access Ens_Config.Credentials table, maybe some user has permissions too broad?

What you can do additionally is to store credentials in a separate SECONDARY database. When you create a new interoperability namespace (in non HS installs), it should be created automatically. Still, you can manually create this DB and related mappings by calling CreateNewDBForSecondary.

After creating secondary db, check that no one has R on DB resouce.

Additionally you can encrypt the db file.

Eduard Lebedyuk · Aug 24, 2022 go to post

Sure. There are two ways:

1. Switch to another namespace to execute your query:

/// Get a list of all mirrored databases as a $lb()
ClassMethod GetMirroredDBs() As %List
{
    new $namespace
    set $namespace = "%SYS"
    set sql = "SELECT LIST(Name) dbs FROM Config.Databases_MirrorDatabaseList()"
    set rs = ##class(%SQL.Statement).%ExecDirect(,sql)
    do rs.%Next()
    set dbs = $lfs(rs.dbs)
    kill rs
    quit dbs
}

Now you can have this method in USER namespace and it would automatically swith into %SYS, execute query, iterate over the results, write results into a local variable, switch back into USER namespace and return the value back to you.

The main thing you need to remember is that result set iteration MUST happen in a target namespace.

2. Map classes and globals to your namespace. Docs. Table would be available as if it was created in your original namespace.

Eduard Lebedyuk · Aug 20, 2022 go to post

What are recommend habits inside and outside, during you own time and during your work time, to be focused during you coding session and daily tasks?

 

My habit/advice/etc. and something that really helped me is a sincere acceptance of the fact long uninterrupted stretches of time do not exist. 

Stop trying to carve out an entire week for a new product feature or a day to prepare a perfect demo. The best you can get is half an hour between meetings.

After I accepted that, planning and accomplishing work (and anything else really) became much more manageable. And much less frustrating as I don't expect to complete any task uninterrupted by something else. 

To do that, first, I split any task into 15-minute-to-half-hour chunks. For example, if I'm writing code, first I just create stubs for all the parts (usually methods and classes), beginning to end, even if I don't know how they should be implemented. Each method is one or several chunks. After that, I start on a chunk: implementing one method I know for sure. If I have no idea how anything works at all, I'll implement invocation arguments/objects and so on. If still no idea: split one chunk into several and try again.

It works out for almost everything. Writing articles, I first write a title and a list of sections. Usually, I have ten or more of these stubs lying around, and then inspiration strikes me - why not fill one section? Or several if there's time. 

 

Another trick is leaving the day's last item unfinished. When I'm close to eob and actually see how to complete something, I often leave it as a task to start the next day. This way, I know where to start and what to do in the morning. And I can score a win pretty much immediately. 

Eduard Lebedyuk · Aug 19, 2022 go to post

Say you have this query:

SELECT a, b, c
FROM mytable
WHERE d=1 AND e=2

If you want to change fields in SELECT or WHERE, you'll need to rewrite your query by adding or removing it's parts. Source control diff would also show a whole line change.

But if you write it like this:

SELECT 1
  , a 
  , b 
  , c
FROM mytable
WHERE 1=1
      AND d=1 
      AND e=2

You can comment out any field or condition simply by adding --:

SELECT 1
  --, a 
  , b 
  , c
FROM mytable
WHERE 1=1
      --AND d=1 
      AND e=2

when you have a lot of conditions and need to iterate fast, this way of writing queries is much better for debugging and source control since diff is always contianed to the one line you edit.

Eduard Lebedyuk · Aug 18, 2022 go to post

Improves readability as all conditions start on the same indent level.

It's also useful when you have a query with several conditions and you need to debug by changing which conditions you apply. This way you can easily add/remove conditions by commenting lines in/out.

Eduard Lebedyuk · Aug 18, 2022 go to post

Ens, EnsLib and EnsPortal are system packages, developes can subclass them.

To get class count call something like this:

SELECT 
  count(ID)
FROM %Dictionary.ClassDefinition
WHERE 1=1 AND
      System = 0 AND 
      NOT (Name %STARTSWITH '%' OR 
           Name %STARTSWITH 'Ens.' OR 
           Name %STARTSWITH 'EnsLib.' OR 
           Name %STARTSWITH 'EnsPortal.' OR 
           Name %STARTSWITH 'HS.' OR 
           Name %STARTSWITH 'SchemaMap')

Missing: SQL way to filter out mapped classes.

Eduard Lebedyuk · Aug 16, 2022 go to post

Yes.

Alternatively you can sync call some BO to make an API call, wait for a response and send that to DTL.

It might make sense if you already have a BO/some-other-preexisting-API-caller so you don't have to reimplement the API call code in BS.

Eduard Lebedyuk · Aug 15, 2022 go to post

Use a Business Service with a basic Ens.InboundAdapter:

Class test.BS Extends Ens.BusinessService
{

Parameter ADAPTER = "Ens.InboundAdapter";

Method OnProcessInput(pInput As %RegisteredObject, Output pOutput As %RegisteredObject) As %Status
{
	$$$LOGINFO("In OnProcessInput")
	Quit $$$OK
}

}

Add it to production and set Call Interval to X seconds (and Pool Size to 1). That would ensure OnProcessInput being called every X seconds after it's last completion.

Eduard Lebedyuk · Aug 13, 2022 go to post

Can skip $Job, leaving just:

Do $System.Process.Terminate(,<desired error code>)

it would terminate the current job.

Question: are you using the error code? I'm always exiting with error code 1 in case of any errors, but is there an advantage in using some custom error codes here?

Eduard Lebedyuk · Aug 12, 2022 go to post

Say you have this string:

1111111111:authoredOn=ge2022-01-10:authoredOn=le2022-01-13

How do you want to slice it:

authoredOn=ge2022-01-10

authoredOn=le2022-01-13

what's 1111111111 doing there?

Something like this should work:

set str = "1111111111:authoredOn=ge2022-01-10:authoredOn=le2022-01-13"
set str = $lfs(str, ":")
for i=1:1:$ll(str) {
	set param = $lg(str,i)
	if $l(param,"=")=2 {
		set key = $p(param,"=",1)
		set value = $p(param,"=",2)
		set params(key, $i(params(key)))=value
	}
}
zw params