Jon Willeke · Sep 28, 2020 go to post

As I recall, some of the conventions of the object library date back to Caché 2.0 in the late nineties, whereas try-catch wasn't introduced until Caché 4.0.

One arguable advantage of %Status-based interfaces is that they work well with the noisy %UnitTest framework. If you $$$AssertStatusOK on a series of method calls, the test log reads like a transcript of your activity. You can often understand the failure without even needing to read the test program.

Also, try-catch is kind of annoying to use without finally, which was never implemented.

Jon Willeke · Sep 11, 2020 go to post

One of my stylistic peeves is that parentheses around an if condition are not necessary, and I prefer to omit them for readability. You'll often need parens for a function call and/or to evaluate operators correctly, so best not to clutter the code with a superfluous pair.

Jon Willeke · Sep 9, 2020 go to post

It has been a while since I've looked at the Python binding, but my recollection is that it is based on the C binding (not to be confused with the C callin API), which is based on the C++ binding, which does not have to be on the same system as the Caché server. However, you do need to satisfy those dependencies on the client.

Jon Willeke · Aug 31, 2020 go to post

You're getting a "Datatype validation" error, which suggests that the XML parsed fine, but that the value "/shared/BENANDERSON" is not valid for the PArray property of the ExecuteResult class.

If PArray is an array, I don't remember how that's typically projected to XML, and I can't find an example in the documentation. Lists are projected as a container element with the contents nested as elements within. Are you reading in something that was written using an %XML.Writer, or are you designing a class to read an XML file from somewhere else?

It might help to see more context in the XML, and the relevant definitions from the ExecuteResult class.

Jon Willeke · Aug 12, 2020 go to post

You need to identify what character(s) the trailing squares actually are. Typically an empty box is not whitespace, but something for which your font doesn't have a glyph. Can you post the output of zzdump for one of the strings?

Jon Willeke · Jul 20, 2020 go to post

FIPS 180-4 describes SHA-512 et al., FIPS 198-1 describes HMAC, and PKCS #5 describes PBKDF2, which depends on HMAC-SHA. As for NIST, special publication 800-132 (now ten years old) states: "This Recommendation approves PBKDF2 as the PBKDF using HMAC with any approved hash function as the PRF." For more recent guidance, consider special publication 800-63B.

As I understand it, none of the weaknesses in SHA affect HMAC or PBKDF2. However, if SHA-1 is no longer FIPS approved, the NIST guidance would indicate replacing it with, say, SHA-2 or SHA-3.

In terms of strength, PBKDF2 essentially has two parameters, the hash function, and the iteration count. For the hash function, bigger is usually slower, therefore stronger. For the iteration count, PKCS #5 and NIST 800-132 both suggest a minimum of 1,000. NIST 800-63B states: "the iteration count SHOULD be as large as verification server performance will allow, typically at least 10,000 iterations."

Jon Willeke · Jul 8, 2020 go to post

I see this behavior in the examples included with that post; e.g., "Age":"66". The %ZJSON package is large, and I don't see a trivial fix offhand. You might play around with the GenExportLiteral() method, which has a mind-numbing quantity of quotation marks. Otherwise, I suggest opening an issue on Github.

Jon Willeke · May 20, 2020 go to post

This is for a simple train-test split as part of testing for IntegratedML, currently in beta.

Jon Willeke · May 20, 2020 go to post

One thing I've done to split machine learning datasets is to use an auxiliary table that maps IDs to a random number. I write a stored procedure that returns a random number. I create a table with two columns, one for the ID of the source table, and one to hold a random number. I populate the column for the source IDs:

insert into random_table (source_id)
select id from source_table

I then populate the column for the random number:

update random_table
set random_number = MySchema.MySP_Random(1E9)

Then I can select with an ORDER BY clause on the random number:

select top 10 source_id
from random_table
order by random_number, source_id

It depends on your use case whether this will be appropriate for a source table with millions of rows. It's an expensive way to select just one row.

Jon Willeke · Apr 24, 2020 go to post

I agree that the most likely path to a Rust binding is to wrap a C or C++ API. If you're content with a local client, callin and/or callout is the place to start. As you said, it shouldn't be too hard to write a callout library in Rust. Callin, on the other hand, (and callback from a callout library) is a bit more involved, requiring a lot of unsafe code.

If you want a remote client, you could look at wrapping the C or C++ binding, but that's a dead end that is not supported in IRIS. You might also look into relational access or an ORM. Diesel looks promising, but I don't know whether it (or Rust in general) works well with ODBC.

Jon Willeke · Mar 25, 2020 go to post

Although $list is just a string serialization format, it is conventional to declare such a string in a method parameter or return type as %List (i.e., %Library.List).

Jon Willeke · Feb 27, 2020 go to post

I found this limitation surprising too. My understanding is that it has to do with the current device being the Native API's TCP connection to the server.

I can never remember how to mention someone on this forum, but I'll try to make @Raj Singh aware of your feedback.

Jon Willeke · Feb 3, 2020 go to post

In Caché, the dev/cpp and dev/python directories contain the C++ and Python bindings, respectively. As far as I know, these are not available for InterSystems IRIS. They were shipped as source, so maybe they would work with IRIS? I doubt that it's been tested.

The dev/iris-callin directory contains the headers for the C callin API. This includes files that are compatible with programs written using headers that are shipped with Caché in the dev/cache/callin directory.

Jon Willeke · Nov 13, 2019 go to post

This is a simple way to make a secure connection using the same certificates as, say, your browser. If you trust Safari to go to https://example.com/, then you can use this to do the same in InterSystems IRIS. If the certificate on the other end was issued by a well known CA, then it will likely work.

Jon Willeke · Nov 5, 2019 go to post

It appears that your link is to a Docker image of the application installed on YottaDB, a fork of GT.M. I followed the link at the bottom of the page to the FOIA release page:

https://code.osehra.org/journal/journal/view/1576

After downloading the latest copy and skimming the documentation, this release includes a CACHE.DAT database and extensive installation and configuration instructions for Caché. If you want to run the application on InterSystems IRIS, you would do better to start there than the YottaDB-based image.

For a container deployment, there are three things you'll want to be aware of: durable %SYS, bind mounts, and ports.

I believe that durable %SYS is covered in the documentation. Basically, it will store all of the database and mapping configuration that you do outside of the container.

You'll need a bind mount for durable %SYS, and for the RPMS database.

You'll want to export the web server port so that you can access the management portal.

One other suggestion: if you're new to InterSystems IRIS and/or containers, you may want to start with Docker locally on your system before tackling a cloud deployment.

Good luck, and have fun.

Jon Willeke · Oct 29, 2019 go to post

FWIW, you can also replace the call to $extract with two-argument $ascii:

$a(md5hash,i)
Jon Willeke · Oct 28, 2019 go to post

Whereas the digest methods in the %SYSTEM .Encryption class return binary strings, which are documented in terms of their byte length, it is indeed conventional to display them using hexadecimal. Instead of $select, I more usually see $translate and $justify:

s h0x=h0x_$tr($j(chr,2)," ",0)

If %xsd.hexBinary is covenient, though, I'd say use that.

For a sanity check, you can compare whatever implementation you choose with the zzdump command:

USER>s digest=$system.Encryption.MD5Hash("12345678")

USER>w ##class(%xsd.hexBinary).LogicalToXSD(digest)
25D55AD283AA400AF464C76D713C07AD
USER>zzdump digest

0000: 25 D5 5A D2 83 AA 40 0A F4 64 C7 6D 71 3C 07 AD
Jon Willeke · Oct 1, 2019 go to post

Note that for your specific case, our examples have been off by one:

USER>w $e("16175551234",*-10,*)
16175551234

You actually want something like this:

USER>w $e("16175551234",*-9,*)
6175551234

If you're not sure whether the transformation is being applied at all, maybe do something more dramatic, like $reverse, as a sanity check?

Jon Willeke · Sep 30, 2019 go to post

If you've specified the language as "basic", then I would expect the Right() function to be available. Otherwise, for "objectscript", you'll want to use something like $extract(string,*-10,*).

Edit: if there's some reason to prefer using the methods supplied by the wizard, you could also do something like this: ..SubString(string,..Length(string)-10)

Jon Willeke · Sep 6, 2019 go to post

Interesting. I added a loop for if $increment(num) {} (i.e., a new-style if statement that doesn't set $test): no measurable improvement over legacy if.

I also added a loop for do $increment(num) (i.e., a do statement that neither sets $test nor returns a value): ever so slightly slower.

USER>d ^Times(1,8)
   count   num+1   1+num   =$i()    $i()  $i(){}  d $i()
               times in microseconds
--------------------------------------------------------
       1   1.000   0.000   1.000   0.000   0.000   1.000
      10   0.000   0.000   0.100   0.100   0.000   0.000
     100   0.010   0.010   0.490   0.030   0.030   0.040
    1000   0.042   0.011   0.029   0.034   0.029   0.033
   10000   0.011   0.010   0.030   0.032   0.032   0.031
  100000   0.009   0.010   0.030   0.028   0.027   0.031
 1000000   0.009   0.010   0.028   0.028   0.027   0.031
10000000   0.010   0.010   0.028   0.028   0.028   0.031

Incidentally, here are some results with num renamed to ^num:

USER>d ^Times(1,8)
   count   num+1   1+num   =$i()    $i()  $i(){}  d $i()
               times in microseconds
--------------------------------------------------------
       1   2.000   0.000   2.000   1.000   0.000   0.000
      10   0.100   0.200   0.100   0.100   0.100   0.100
     100   1.070   0.280   0.130   0.110   0.100   0.110
    1000   0.142   0.144   0.142   0.102   0.102   0.106
   10000   0.142   0.141   0.110   0.116   0.104   0.108
  100000   0.142   0.141   0.102   0.101   0.100   0.104
 1000000   0.139   0.140   0.100   0.098   0.100   0.102
10000000   0.138   0.138   0.098   0.098   0.099   0.102

For "=$i()", I assigned a local, rather than redundantly assigning the global.

Jon Willeke · Sep 5, 2019 go to post

set count=count+1 and set count=1+count generate identical object code, so I think we found your margin of error.

if $increment(count) has to set $test, so I would expect it to be slower for a local variable. (I'm not sure about a global.) In IRIS 2018.2 and later, do $increment(count) may close the gap a bit.

Jon Willeke · Aug 29, 2019 go to post

There is a way to do something similar in Caché and IRIS. In a Russian locale, you have access to the "KOI8R" I/O translation table. KOI8-R has the funny property that if you mask out the high-order bit, you get a sort of readable transliteration. Here's an example using a Unicode instance in the "rusw" locale:

    USER>s koi8=$zcvt("Пример для Кода","O","KOI8R")

    USER>s ascii="" f i=1:1:$l(koi8) s ascii=ascii_$c($zb($a(koi8,i),127,1))

    USER>zw ascii
    ascii="pRIMER DLQ kODA"
Jon Willeke · Aug 23, 2019 go to post

When you're using subscript indirection with a recursive $order traversal, you may find the $name function useful; e.g.,

    do ..RecursiveGlobalCount($na(@pGlobalName@(tKey)),"",.tCount)

As the other answers suggest, you probably want $query instead of $order, but $order can be useful for summarizing on multiple subscript levels (e.g., count, min, and max per country, state, and city).

Jon Willeke · Jul 25, 2019 go to post

You can also change it after installation by running cinstall setserviceusername in the bin directory.

Jon Willeke · Jul 10, 2019 go to post

It occurs to me that the Visual Basic solution works with minimal modification as Caché Basic: weaken the DIM statements, change Debug.Print to Print, and replace the call to Format$().

Option Explicit

Const VECSIZE = 3350
Const BUFSIZE = 201
Dim buffer
Dim vect
Dim more, karray, num, k, l, n

  For n = 1 To VECSIZE
    vect(n) = 2
  Next n
  For n = 1 To BUFSIZE
    karray = 0
    For l = VECSIZE To 1 Step -1
      num = 100000 * vect(l) + karray * l
      karray = num \ (2 * l - 1)
      vect(l) = num - karray * (2 * l - 1)
    Next l
    k = karray \ 100000
    buffer(n) = more + k
    more = karray - k * 100000
  Next n
  Print buffer(1)
  Print "."
  l = 0
  For n = 2 To BUFSIZE
    Print Right("00000" & buffer(n), 5)
    l = l + 1
    If l = 10 Then
      l = 0
      Print 'line feed
    End If
  Next n
Jon Willeke · Jul 9, 2019 go to post

I would look at the implementations that do not depend on a "big number" package. Java and C# use BigInteger; Python uses long; Ada, C, and others use GMP; etc. You might try porting the Pascal implementation to ObjectScript. Where you see div, use the "" operator, and be aware of operator precedence.