#ObjectScript

0 フォロワー · 149 投稿

InterSystems ObjectScriptは、InterSystemsデータプラットフォームの任意のデータモデル(オブジェクト、リレーショナル、Key-Value、ドキュメント、グローバル)を使用してデータを操作し、InterSystemsデータプラットフォーム上のサーバーサイドアプリケーションのビジネスロジックを開発するためのスクリプト言語です。 ドキュメント

記事 Toshihiko Minamoto · 5月 23, 2023 8m read

Web スクレイピングとは:

簡単に言えば、Web スクレイピングWeb ハーベスティング、または Web データ抽出とは、Web サイトから大量のデータ(非構造化)を収集する自動プロセスです。 ユーザーは特定のサイトのすべてのデータまたは要件に従う特定のデータを抽出できます。 収集されたデータは、さらに分析するために、構造化された形式で保存することができます。

Web スクレイピングとは? — James Le

Web スクレイピングの手順:

  1. スクレイピングする Web ページの URL を見つけます。
  2. 検査により、特定の要素を選択します。
  3. 選択した要素のコンテンツを取得するコードを記述します。
  4. 必要な形式でデータを保存します。

たったそれだけです!!

Web スクレイピングに使用される一般的なライブラリ/ツール

  • Selenium - Web アプリケーションをテストするためのフレームワーク
  • BeautifulSoup – HTML、XML、およびその他のマークアップ言語からデータを取得するための Python ライブラリ
  • Pandas - データ操作と分析用の Python ライブラリ

Beauthiful Soup とは?

Beautiful Soup は、Web サイトから構造化データを抽出するための純粋な Python ライブラリです。 HTML と XML ファイルからデータを解析できます。 これはヘルパーモジュールとして機能し、利用できる他の開発者ツールを使って Web ページを操作する方法と同じ方法かより優れた方法で HTML と対話します。

  • lxmlhtml5lib などの使い慣れたパーサーと連携して、有機的な Python の方法で、解析ツリーを移動操作、検索、および変更できるようにするため、通常、プログラマーは数時間または数日間に及ぶ作業を節約できます。
  • Beautiful Soup のもう 1 つの強力で便利な機能は、フェッチされるドキュメントを Unicode に変換し、送信されるドキュメントを UTF-8 に変換するインテリジェンスです。 ドキュメント自体にエンコーディングが指定されていないか、Beautiful Soup がエンコーディングを検出できない場合を除き、開発者がその操作に注意する必要はありません。
  • 他の一般的な解析またはスクレイピング手法と比較した場合も高速と見なされています。

今日の記事では、Embedded Python と Object Script を使用して、ae.indeed.com にある Python の求人情報と企業をスクレイピングします。

ステップ 1 - スクレイピングする Web ページの URL を見つけます。

Url = https://ae.indeed.com/jobs?q=python&l=Dubai&start=0

スクレイピングするデータのある Web ページは以下のようになります。

  単純化と学習の目的で、"Job Title"(役職)と "Company"(会社)を抽出します。出力は以下のスクリーンショットのようになります。

 

以下の 2 つの Python ライブラリを使用します。

  • requests Requests は、Python プログラミング言語の HTTP ライブラリです。 プロジェクトの目標は、HTTP リクエストを単純化し、人間が読みやすくすることです。  
  • bs4 for BeautifulSoup Beautiful Soup は、HTML と XML ドキュメントを解析するための Python パッケージです。 HTML からデータを抽出するために使用できる解析済みページの解析ツリーを作成します。Web スクレイピングに役立ちます。

以下の Python パッケージをインストールしましょう(Windows)。

irispip install --target C:\InterSystems\IRISHealth\mgr\python bs4

irispip install --target C:\InterSystems\IRISHealth\mgr\python requests

Python ライブラリを ObjectScript にインポートしましょう
 

Class PythonTesting.WebScraper Extends%Persistent
{

// pUrl = https://ae.indeed.com/jobs?q=python&l=Dubai&start=// pPage = 0ClassMethod ScrapeWebPage(pUrl, pPage)
{
    // imports the requests python libraryset requests = ##class(%SYS.Python).Import("requests")
    // import the bs4 python libraryset soup = ##class(%SYS.Python).Import("bs4")
    // import builtins package which contains all of the built-in identifiersset builtins = ##class(%SYS.Python).Import("builtins")
}

Requests を使って HTML データを収集しましょう。

注意: 「my user agent」でグーグル検索し取得した、ユーザーエージェント
URL は "https://ae.indeed.com/jobs?q=python&l=Dubai&start=" で、pPage はページ番号です。

Requests を使って URL に HTTP GET リクエストを行い、そのレスポンスを "req" に格納します。

set headers  = {"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36"}
    set url = "https://ae.indeed.com/jobs?q=python&l=Dubai&start="_pPage
    
    set req = requests.get(url,"headers="_headers)

req オブジェクトには、Web ページから返された HTML が含まれます。

これを BeautifulSoup HTML パーサーで実行し、求人データを抽出できるようにします。

set soupData = soup.BeautifulSoup(req.content, "html.parser")
set title = soupData.title.text
W !,title

タイトルは以下のように表示されます

ステップ 2 - 検査し、必要な要素を選択します。

このシナリオでは、通常 <div> タグに含まれる求人リストに注目しています。ブラウザ内で要素を検査すると、その div クラスが見つかります。

ここでは、必要な情報は、「 <div class="cardOutline tapItem ... </div>」 に格納されています。

ステップ 3 - 選択した要素のコンテンツを取得するコードを記述します。

BeautifulSoup の find_all 機能を使用して、クラス名 "cardOutline" を含むすべての <div> タグを検索します。

//parameters to python would be sent as a python dictionaryset divClass = {"class":"cardOutline"}
set divsArr = soupData."find_all"("div",divClass...)

これによりリストが返されます。これをループ処理すると、Job Titles と Company を抽出できます。

###ステップ 4 - 必要なフォーマットでデータを保存/表示します。

以下の例では、データをターミナルに書き出します。

set len = builtins.len(divsArr)
    
W !, "Job Title",$C(9)_" --- "_$C(9),"Company"for i = 1:1:len {
    Set item = divsArr."__getitem__"(i - 1)
    set title = $ZSTRIP(item.find("a").text,"<>W")
    set companyClass = {"class_":"companyName"}
    set company = $ZSTRIP(item.find("span", companyClass...).text,"<>W")
    W !,title,$C(9)," --- ",$C(9),company
}

builtins.len() を使用して、divsArr リストの長さを取得していることに注意してください。

識別子名: ObjectScript と Python の識別子の命名規則は異なります。 たとえば、Python のメソッド名ではアンダースコア(_)を使用でき、_getitem_ や _class_ のようにいわゆる「ダンダー」といわれる特殊なメソッドや属性で実際に広く使用されています(「ダンダー」は「double underscore = 二重アンダースコア」の略です)。 このような識別子を ObjectScript で使用するには、二重引用符で囲みます:

識別子名に関する InterSystems ドキュメント

クラスメソッドの例

ClassMethod ScrapeWebPage(pUrl, pPage)
// pUrl = https://ae.indeed.com/jobs?q=python&l=Dubai&start=// pPage = 0ClassMethod ScrapeWebPage(pUrl, pPage)
{
    set requests = ##class(%SYS.Python).Import("requests")
<span class="hljs-keyword">set</span> soup = <span class="hljs-keyword">##class</span>(<span class="hljs-built_in">%SYS.Python</span>).Import(<span class="hljs-string">"bs4"</span>)

<span class="hljs-keyword">set</span> builtins = <span class="hljs-keyword">##class</span>(<span class="hljs-built_in">%SYS.Python</span>).Builtins()

<span class="hljs-keyword">set</span> headers  = {<span class="hljs-string">"User-Agent"</span>: <span class="hljs-string">"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36"</span>}
<span class="hljs-keyword">set</span> url = pUrl_pPage

<span class="hljs-keyword">set</span> req = requests.get(url,<span class="hljs-string">"headers="</span>_headers)

<span class="hljs-keyword">set</span> soupData = soup.BeautifulSoup(req.content, <span class="hljs-string">"html.parser"</span>)

<span class="hljs-keyword">set</span> title = soupData.title.text

<span class="hljs-keyword">W</span> !,title

<span class="hljs-keyword">set</span> divClass = {<span class="hljs-string">"class_"</span>:<span class="hljs-string">"cardOutline"</span>}
<span class="hljs-keyword">set</span> divsArr = soupData.<span class="hljs-string">"find_all"</span>(<span class="hljs-string">"div"</span>,divClass...)

<span class="hljs-keyword">set</span> len = builtins.len(divsArr)

<span class="hljs-keyword">W</span> !, <span class="hljs-string">"Job Title"</span>,<span class="hljs-built_in">$C</span>(<span class="hljs-number">9</span>)_<span class="hljs-string">" --- "</span>_<span class="hljs-built_in">$C</span>(<span class="hljs-number">9</span>),<span class="hljs-string">"Company"</span>
<span class="hljs-keyword">for</span> i = <span class="hljs-number">1</span>:<span class="hljs-number">1</span>:len {
        <span class="hljs-keyword">Set</span> item = divsArr.<span class="hljs-string">"__getitem__"</span>(i - <span class="hljs-number">1</span>)
        <span class="hljs-keyword">set</span> title = <span class="hljs-built_in">$ZSTRIP</span>(item.find(<span class="hljs-string">"a"</span>).text,<span class="hljs-string">"<>W"</span>)
        <span class="hljs-keyword">set</span> companyClass = {<span class="hljs-string">"class_"</span>:<span class="hljs-string">"companyName"</span>}
        <span class="hljs-keyword">set</span> company = <span class="hljs-built_in">$ZSTRIP</span>(item.find(<span class="hljs-string">"span"</span>, companyClass...).text,<span class="hljs-string">"<>W"</span>)
        <span class="hljs-keyword">W</span> !,title,<span class="hljs-built_in">$C</span>(<span class="hljs-number">9</span>),<span class="hljs-string">" --- "</span>,<span class="hljs-built_in">$C</span>(<span class="hljs-number">9</span>),company
 }

}

</div>

今後の内容..

ObjectScript とEmbedded Python と数行のコードを使用して、いつも使用する求人サイトのデータをスクレイピングし、求人タイトル、会社、給料、職務内容、メールアドレス/リンクを簡単に収集できます。

たとえば、ページが複数ある場合、ページを使用して簡単にそれらをトラバースできます。 このデータを Pandas データフレームに追加して重複を削除したら、関心のある特定のキーワードに基づいてフィルターを適用できます。 このデータを NumPy で実行してラインチャートを取得します。 または、One-Hot エンコーディングをデータに実行し、ML モデルを作成/トレーニングします。興味のある特定の求人情報がある場合は、自分に通知を送信するようにします。 😉

それではコーディングをお楽しみください!!!

「いいね」ボタンも忘れずに押してください 😃

0
0 431
ディスカッション Miqueias Santos · 5月 18, 2023

こんにちは、大丈夫ですか?

助けを求めに来ました。インターシステムズ ポルトガル語のコンテストに参加しているので、リンクからこの記事を作成しました。私の記事にアクセスしてフィードバックをいただけますか。「いいね!」も歓迎です。

ここ: https://pt.community.intersystems.com/post/veremos-seguir-como-o-intersystems-iris-aliado-com-machine-learning-pode-transformar-para


読むには、ブラジル系ポルトガル語からあなたの言語に翻訳するだけです。

0
0 121
記事 Megumi Kakechi · 5月 15, 2023 2m read

これは InterSystems FAQ サイトの記事です。

サブスクリプトレベルマッピングされたグローバルを、トップノードから全体をkillする場合、<SLMSPAN>エラーとなり削除ができません。

これは、サブスクリプトレベルマッピングされたグローバルについては、マッピングを跨いだサブスクリプト範囲の kill コマンドは行えないためです。

// 以下のように、別々のデータベースにサブスクリプトマッピンググローバルが存在する場合:^TEST(A*~K*) -> database A
^TEST(L*~Z*) -> database B

// Topレベルから Kill しようとすると、<SLMSPAN>エラーとなる
NAMESPACE>Kill^TEST
<SLMSPAN> <- このエラーが出力


現ネームスペース(データベース)のグローバルのみ削除するには、次のように指定して下さい。

NAMESPACE>Kill ^["^^."]TEST

サブスクリプトレベルでマッピングしているグローバルは、データベースに移動して直接Killする必要があります。
データベースに移動する場合は以下のようにします。

0
0 118
記事 Megumi Kakechi · 5月 1, 2023 6m read

IRISTEMPというデータベースをご存じでしょうか?

特定の処理に対してデータを無期限に保存する必要がなく、「同一プロセス内でのみ使用したい場合」や「IRISが起動中のみ使用したい場合」に、IRISTEMPデータベースに保存されるグローバルを使用できます。
IRISTEMPデータベースに保存されるグローバルに対する操作は ”一切ジャーナルされない” ため、効率性を最大限にしたい作業に使用できます。

IRISTEMPデータベースに保存されるグローバル(データ)には、以下の種類があります。

0
1 302
記事 Megumi Kakechi · 3月 28, 2023 3m read

これは InterSystems FAQ サイトの記事です。
 

テーブル名/カラム名/インデックス名を変更したい場合、以下のケース別に変更方法をご案内します。

A. テーブル名・カラム名の変更
B. インデックス名の変更
 

-------------------------------------------------------------------------
A. テーブル名・カラム名の変更する方法
-------------------------------------------------------------------------

テーブル(クラス)名とカラム(プロパティ)名は基本的には変えないようにしてください。

もし「SQLアクセス時の名前だけ変更したい」場合は、以下のように新しい名前を SqlTableName(テーブル名)、SqlFieldName(カラム名) として指定することができます。

Class User.test Extends%Persistent [ SqlTableName = test2 ] {
    Property p1 As%Integer [ SqlFieldName = xx ];
    ....
0
1 233
InterSystems公式 Toshihiko Minamoto · 3月 12, 2023 3m read

IRIS 2023.1でのメソッドコードの生成と呼び出し方法の変更について、お知らせしたいと思います。

IRIS のクラスは、2 つの主要なランタイムコンポーネントで構成されています。

  1. クラスディスクリプタ - クラスを構成するメソッド、プロパティ、クラスパラメータ、およびこれらのそれぞれに関連する属性(パブリック/プライベート設定など)が最適化されたリストです。
  2. ObjectScriptコード - メソッドが呼び出されたときに実行される ObjectScript コードを含むルーチンのセットです。

クラス/オブジェクトのメソッドを呼び出すと、ディスパッチコードがクラス記述子からメソッドを探し、呼び出しが許可されているかどうかを確認し、正しいクラスコンテキストを設定し(その過程で $this を更新)、最後に関連するクラスルーチンにある ObjectScript コードを呼び出します。

0
0 256
記事 Megumi Kakechi · 3月 8, 2023 2m read

これは InterSystems FAQ サイトの記事です。
 

^%GCMP ユーティリティにて2つのグローバルの内容を比較することができます。

例としてUSERとSAMPLESネームスペースにある、^testと^testを比較する場合は以下のようになります。
※以下の例では、2つのネームスペースに全く同じグローバルを700個作り、その中の一つの中身を変えて検出対象としています。

0
0 141
記事 Mihoko Iijima · 2月 21, 2023 3m read

開発者の皆さん、こんにちは!

開発者向け情報を集めた「Developer Hub」ページが新たに登場しました!

(2025/10/9更新: 新たなチュートリアルが加わりましたので情報更新しました。)

このページには、5種類のチュートリアルが用意されています。チュートリアはブラウザ上で動作し、VSCodeやIRISターミナル、管理ポータルなどチュートリアルで使用するすべての画面が1つのタブ内で開くようになっています。

チュートリアルを試すための事前準備は不要で、クリック1回ですぐにお試しいただけます!(ユーザ登録も不要です)(チュートリアル開始方法は、ページ末尾をご覧ください。)

0
0 335
記事 Toshihiko Minamoto · 1月 31, 2023 1m read

皆さん、こんにちは。


最近話題のChat GPTですが、試しに使ってみました(今更かもしれませんが)。

ObjectScriptを知っているみたいですyes

なので、

と質問すると、

おおーすごい! ObjectScript プログラムとその説明が返ってきました!!

これでプログラミングできるかもcheeky

0
0 415
記事 Toshihiko Minamoto · 11月 22, 2022 5m read

タイムトラベルとはパリを訪ねるようなもの。 ガイドを読むだけじゃなく、そこに足を踏み入れなければならない。 ご飯を食べて、動詞の使い方を間違えたり、2 倍の料金を請求されたり、見ず知らずの人とキスするものさ。

ドクター

これから時空を超えた旅をしましょう。未来と過去の日付を見て、様々なフォーマットで計算する方法を説明します。 ターディスは待ちません。コントロールに就いてしっかりつかまりましょう。

ターディスでの旅

明日へ向かおう

前回の記事で見たように、内部フォーマットの日付は 1841 年 1 月 1 日からの日数と 00:00 時間からの秒数に分けられています。

日にちを増やす場合は、日付の部分に値を追加するかそれから差し引けば... すでに時間を旅している...と考えられます。

SET Today = $ZDATE($HOROLOG,4)
SET InternalDate = $ZDATEH(Today, 4)
SET Future = InternalDate + 2 
SET FutureDate = $DATE(Future, 4)
WRITE "Today: "_Today,!,"Internal date: "_InternalDate,!,"Future: "_Future,!,"Future date: "_FutureDate
> Today: 02/09/2022
Internal date: 66149
Future: 66151
Future date: 04/09/2022

とても簡単です。日にちを移動する方法はわかっていますが、 ただし、次の月に移動するとなると、こうはいきません。 カレンダーには、毎月同じ日数があるわけではないため、日付に 30 日を足しても意味がありません。 時間の加算と減算を行うコマンドをご紹介いたしましょう。

T-SQL ユーザーであれば、DATEADD と言うコマンドに聞きなれていることでしょう。

これは $SYSTEM.SQL.Functions ライブラリに含まれています。

バック・トゥ・ザ・フューチャー

月数、日数、年数を追加したり、指定された日付から除外したりできます。

SET InternalDate = $ZDATEH("09/02/2022", 4)
SET Future = $SYSTEM.SQL.DATEADD("mm",1,InternalDate)
WRITE "InternalDate "_InternalDate,!,"Future: "_Future
InternalDate: 66149
Future: 2022-03-09 00:00:00

日数、月数、年数を除外する場合は、部分ごとにそれぞれ個別に行う必要があります。

Set InternalDate = $ZDATEH("09/02/2022", 4)
Set PastDays = $SYSTEM.SQL.DATEADD("dd",12,InternalDate)
Set PastMonths = $SYSTEM.SQL.DATEADD("mm",8,PastDays)
Set PastYears = $SYSTEM.SQL.DATEADD("yy",-7,PastMonths)
WRITE PastYears
2015-10-21 00:00:00

Mc FLY、居るのか~??

今、僕の誕生日...じゃない?

知ろうとしていることが、2 つの日付の差であれば、DATEDIFF コマンドを使用します。

Set internalDate = $ZDATEH("09/02/2022", 4)
Set myBirthday = $ZDATEH("01/02/2023", 4)
Set days = $SYSTEM.SQL.DATEDIFF("dd",internalDate, myBirthday)
WRITE !days," days left until my birthday"

> 357 days left until my birthday

上手くいきましたね... 2 つの日付の差を日数、月数、年数などで見る方法が分かりました。

コロンブスのアメリカ大陸発見に居合わせたい

前にも言ったように、時間フォーマットの値 1 は 01/01/1841 ですが、コロンブスがアメリカ大陸に上陸したのは 1492 年 10 月 12 日でした。そこで、「1 の日付から 1492 年にさかのぼって負の値を付ければいい!」なんて考えるでしょうね。

WRITE $ZDATE(-1)

<VALUE OUT OF RANGE>

おっと... ヒューストン、問題が発生しました。 負の値を使って 1841 年より前の日付を取得することはできません。 でも、テキストの日付から内部フォーマットへの変換を行って、戻り値を見てみましょう。

WRITE $ZDATEH("12/10/1492",4) // European format

<VALUE OUT OF RANGE>

ムムムムムム!!! 競ってくれるのはありがたいですが、正しい答えではありません。
どうやって解決できるでしょうか?

これは、日付に、それ以上にならないように最低値が定義されているためで、したがって mindata パラメーターを使用すると、内部フォーマットで負の値を使用できるようになります。

WRITE $ZDATEH("12/10/1492",4,,,,,-672045)
> -127184

パラメーターの 7 番目の位置にある値は最低許容値が -672045 となることを示しており、好奇心を満たすために言えば、これは 01/01/0001 に対応する値です。また、ここで使用できる最低値でもあります。

「カンマ」を数えてこのパラメーターを追加する際に混乱してしまった場合は、各国語サポート(NLS)を構成してアクティブのままにするとよいでしょう。

SET originalValue= ##class(%SYS.NLS.Format).GetFormatItem("DateMinimum")
WRITE originalValue,!
DO ##class(%SYS.NLS.Format).SetFormatItem("DateMinimum", -672045)
WRITE $ZDATEH("12/10/1492", 4),!
DO ##class(%SYS.NLS.Format).SetFormatItem("DateMinimum", originalValue)  // Return to the minimum value originally established

> 0
-127184

すると、問題なく時間を超えた旅を行えるようになります。

次回の記事: 世界時

謝辞

負の $Horolog に関する記事を執筆していただいた @Robert Cemper に感謝します。この記事を書き上げる上で役立ちました。参考記事は次のリンクでご覧ください。

https://community.intersystems.com/post/date-dec1840-negative-horolog

0
0 142
記事 Toshihiko Minamoto · 11月 2, 2022 7m read

良識のある人にはルールなんていらない。

ドクター

日付と時間のマスターになるのは簡単なことではありません。いつも問題になる上、どのようなプログラミング言語でも混乱することがあります。そこでこのタスクが可能な限り単純になるように、分かりやすく説明していくつかのヒントをご紹介しましょう。

さぁ、ターディスに乗り込みましょう。あなたを時間の支配者にして差し上げます。

ターディス

基本から始めよう

普段、他の言語を使用しているのであれば、InterSystems Object Script(以降「IOS」と呼びますが、Apple モバイルと勘違いしないように)の日付は少し独特です。
ターミナルで $HOROLOG を実行して現在の日付と時刻を取得する場合、2 つの部分に分割されているのがわかります。

WRITE $HOROLOG

> 66149,67164

最初の値は日にちです。正確には 1840 年 12 月 31 日以来の日数であり、つまり値 1 は 1841 年 1 月 1 日となります。2 つ目の値は今日の 00:00 時以来の秒数です。

この例では、66149 は 09/02/2022(欧州フォーマットの dd/mm/yyyy なので 2 月 9 日)、67164 は 18:39:24 に対応しています。 このフォーマットを日付と時刻の内部フォーマットと呼ぶことにします。

混乱してきましたか? (日付と時刻の)宇宙の大きな秘密を解明し始めることにしましょう。

内部フォーマットをわかりやすいフォーマットに変換するには?

それについては、$ZDATETIME コマンドを使用します。

基本的なコマンドは以下のようになります。

SET RightNow = $HOROLOG
WRITE RightNow

> 66149,67164
WRITE $ZDATETIME(RightNow)

> 02/09/2022 18:39:24

デフォルトでは、アメリカ式フォーマット(mm/dd/yyyy)を使用します。 別のフォーマットで日付を表す場合は、欧州フォーマット(dd/mm/yyyy)など2 つ目のパラメーターを使用し、この場合は値 4 を指定します(詳細については、ドキュメントの $ZDATETIME.dformat をご覧ください)。

SET RightNow = $HOROLOG
WRITE RightNow

> 66149,67164

WRITE $ZDATETIME(RightNow,4)

> 09/02/2022 18:39:24

このオプションは、ローカル変数で定義したものを区切り文字と年フォーマットを使用します。

また、別の時間フォーマットを使用したい場合、たとえば 24 時間制ではなく 12 時間制(午前/午後)を使用したい場合は、3 つ目のパラメータを値 3 とし、秒数を表示しない場合は、値 4 を使用します(ドキュメントの $ZDATETIME.tformat を参照)。

SET RightNow = $HOROLOG
WRITE RightNow

> 66149,67164

WRITE $ZDATETIME(RightNow,4,3)

> 09/02/2022 06:39:24PM

WRITE $ZDATETIME(RightNow,4,4)

> 09/02/2022 06:39PM

わかってきましたか? ではもっと詳しく見てみましょう。

ODBC フォーマット

このフォーマットはローカル構成に依存しておらず、常に yyyy-mm-dd として表示されます。この値は 3 です。 CSV、HL7 などのファイルでエクスポートされるデータを作成する際は、これを使用することをお勧めします。

SET RightNow = $HOROLOG
WRITE RightNow

> 66149,67164

WRITE $ZDATETIME(RightNow,3)

> 2022-02-09 18:39:24

曜日、曜日名、年間通産日

説明
10曜日は 0 から 6 の値で、日曜日は 0、土曜日は 6 です。
11曜日の略名で、ユーザーが定義するローカル構成に基づいて返します。IRIS のデフォルトインストールは enuw(英語、米国、Unicode)です。
12ロング形式の曜日名。 11 と同じです。
14年間通産日。つまり 1 月 1 日からの日数です。

日付と時刻を個別に扱う場合は、それぞれ $ZDATE コマンドと $ZTIME コマンドを使用します。 フォーマットのパラメーターは、$ZDATETIME.dformat$ZDATETIME.tformatで定義されているパラメーターと同じです。

SET RightNow = $HOROLOG
WRITE RightNow

> 66149,67164

WRITE $ZDATE(RightNow,10)

> 3

WRITE $ZDATE(RightNow,11)

> Wed

WRITE $ZDATE(RightNow,12)

> Wednesday

日付を内部フォーマットに変換するには?

では、逆のステップを見てみましょう。日付のあるテキストを IOS フォーマットに変換する方法です。 このタスクでは、$ZDATETIMEH コマンドを使用します。

今度は、日付と時刻に使用されているフォーマット($ZDATETIMEH を使用している場合)、または日付($ZDATEH)または時刻($ZTIMEH)に使用されているフォーマットを個別に示す必要があります。

フォーマットは同じです。つまり、ODBC フォーマット(yyyy-mm-dd)の日付を持つ文字列がある場合は、値 3 を使用します。

SET MyDatetime = "2022-02-09 18:39:24"
SET Interna1 = $ZDATETIMEH(MyDatetime, 3, 1) // ODBC フォーマット

SET MyDatetime = "09/02/2022 18:39:24"
SET Interna2 = $ZDATETIMEH(MyDatetime, 4, 1) // 欧州フォーマット

SET MyDatetime = "02/09/2022 06:39:24PM"
SET Interna3 = $ZDATETIMEH(MyDatetime, 1, 3) // 米国フォーマット、12 時間制 AM/PM
WRITE Interna1,!,Interna2,!,Interna3

> 66149,67164
66149,67164
66149,67164

論理的に、文字列が特定のフォーマットを使用しており、誤ったパラメーターを指定した場合は、2 月 9 日ではなく 9 月 2 日として理解するなど、どのようなことでも起こりえます。

フォーマットを混在させないようにしましょう。後で問題になります。

SET MyDatetime = "09/02/2022"

/// 米国フォーマット
SET InternalDate = $ZDATEH(MyDatetime, 1) 

/// 欧州フォーマット
SET OtherDate = $ZDATETIME(InternalDate, 4)
WRITE InternalDate,!,OtherDate

> 66354
02/09/2022

もちろん、欧州式の日付を設定し、米国式に変換しようとすると ... バレンタインデーはどうなってしまうのでしょうか?

SET MyDatetime = "14/02/2022"
SET InternalDate = $ZDATEH(MyDatetime, 1) // 米国フォーマット。 14 月は存在しません!!!
^
&lt;ILLEGAL VALUE>

ハートブレイクなバレンタインデーと同じように... コードブレイクしてしまいました。

では、学習した内容を使って、何かやってみましょう。

READ !,"Please indicate your date of birth (dd/mm/yyyy): ",dateOfBirth
SET internalFormat = $ZDATEH(dateOfBirth, 4)
SET dayOfWeek= $ZDATE(internalFormat, 10)
SET nameOfDay = $ZDATE(internalFormat, 12)
WRITE !,"The day of the week of your birth is: ",nameOfDay
IF dayOfWeek = 5 WRITE "you always liked to party!!!" // 金曜日生まれ

後で、これを他のやり方で行う方法と、エラーの処理方法を見ることにしましょう。

 

次の章: タイムトラベルの方法

トリビア

なぜ 1841年1月1日の値が値1 となるのでしょうか? それが選択された理由は、オブジェクトスクリプトを拡張した MUMPS プログラミング言語が設計されたときに、南北戦争の退役軍人として当時生存していた最年長 121 歳のアメリカ人が生まれる前のうるう年でない年であったためです。

1
0 333
記事 Mihoko Iijima · 10月 19, 2022 1m read

開発者の皆さん、こんにちは!

ターミナルでクラスメソッドを実行するとき、パッケージ名やクラス名などの入力候補が出てこないので、 ##class(パッケージ名.クラス名).メソッド名() の記述をミスったりちょっと面倒だな・・と感じること、ありませんか??
第1回 InterSystems Idea-A-Thon(アイデアソン)  でも、もっとシンプルに呼び出せるようにしよう!のアイデアが投稿されていたようです。)

(私も含めて)そんな方に、朗報です!📣

なんと、VSCode ObjectScriptエクステンションに新機能「Copy Invocation」が追加されました!

クラスメソッドを記述すると、定義の上に Copy Invocation のリンクが表示され、クリックするとクラスメソッドの実行文がバッファにコピーされるんです!laugh

Copy Invocation をクリックした後で、ターミナルで 右クリック→貼り付け をやってみてください。

ちゃんと実行文がコピーされていることを確認できます(下図の黄色い線の文章がコピーされます)。

あとは、Do や Write や Set 文を記述するだけでいいんです!laugh

ぜひ、お試しください!​​​​​​

VSCode contributorの皆さん、素敵な機能追加をありがとうございました!

0
0 512
記事 Toshihiko Minamoto · 9月 26, 2022 8m read

プログラムによる本番環境アクセス

プログラムで本番環境(インターフェース)を編集するには、相互運用性 apis と SQL クエリを組み合わせて使用できます。

現在のネームスペース

大まかに言えば、その時点で作業しているネームスペースと本番環境を知ることが重要です。

// Object script 
// アクティブなネームスペースはこの変数に格納される
$$$NAMESPACE 
// ネームスペースを出力
Write $$$NAMESPACE
# Python
import iris
# このメソッドからアクティブなネームスペースが返される
iris.utils._OriginalNamespace()
# ネームスペースを出力
print(iris.utils._OriginalNamespace())
>>> DEMONSTRATION

現在の本番環境(アクティブまたは最後に実行した本番環境)

本番環境の名前を知ることも重要です。次の API を使用してネームスペース内のアクティブな本番環境を取得できます。

// ObjectScript
USER>ZN "DEMONSTRATION"
// 現在または最後に実行した本番環境を取得
DEMONSTRATION>W ##class(Ens.Director).GetActiveProductionName()
>>> Hospital.HospitalProduction
#  Python
import os
os.environ['IRISNAMESPACE'] = 'DEMONSTRATION'
import iris
active_production = iris.cls('Ens.Director').GetActiveProductionName()
print(active_production)
>>> Hospital.HospitalProduction

本番環境内の項目を検索する

ObjectScript または Python を使用して、本番環境内のアクティブな項目を検索できます。

1. SQL クエリで本番環境内の項目を調べる

SELECT Name FROM Ens_Config.Item Where Production = 'Hospital.HospitalProduction'
-- 
['From_Athena_Multi']
['From_Athena_Multi_Router']
['From_Cerner_ADT']
['From_Cerner_ADT_Router']
['From_Cerner_Orders']
['From_Cerner_Orders_Router']
['From_Dictaphone_Results']
['From_Dictaphone_Results_Router']
['From_Lab_Results']
['From_Lab_Results_Router']
['From_Radiology_Results']
['From_Radiology_Results_Router']
['HS.IHE.XDSb.DocumentSource.Operations']
['HS.IHE.XDSb.Repository.Operations']
['To_Cerner_Results']
['To_Dictaphone']
['To_Intellilab']
['To_Lab']
['To_Radiology']
-- 

2. SQL クエリで本番環境内のアクティブな項目を調べる

SELECT Name, ClassName 
FROM Ens_Config.Item 
WHERE Production = 'Hospital.HospitalProduction' 
  AND Enabled = 1

-- 
Name                                    ClassName
To_Radiology                            EnsLib.HL7.Operation.FileOperation
To_Lab                                  EnsLib.HL7.Operation.FileOperation
To_Dictaphone                           EnsLib.HL7.Operation.FileOperation
From_Cerner_ADT                         EnsLib.HL7.Service.FileService
From_Cerner_ADT_Router                  EnsLib.HL7.MsgRouter.RoutingEngine
From_Radiology_Results_Router           EnsLib.HL7.MsgRouter.RoutingEngine
From_Lab_Results_Router                 EnsLib.HL7.MsgRouter.RoutingEngine
From_Dictaphone_Results_Router          EnsLib.HL7.MsgRouter.RoutingEngine
To_Intellilab                           EnsLib.HL7.Operation.FileOperation
To_Cerner_Results                       EnsLib.HL7.Operation.FileOperation
From_Cerner_Orders_Router               EnsLib.HL7.MsgRouter.RoutingEngine
From_Athena_Multi_Router                EnsLib.HL7.MsgRouter.RoutingEngine
HS.IHE.XDSb.DocumentSource.Operations   HS.IHE.XDSb.DocumentSource.Operations
-- 

3. 本番環境内の項目にアクセスするオブジェクト

// ObjectScript 
// アクティブな本番環境内のすべての項目を取得するアクセス
// 項目のリストを返す
ClassMethod ListItemsInProduction()
{
    Set productionName =  ##class(Ens.Director).GetActiveProductionName()
    Set items = []
    &sql(Declare curr cursor FOR Select Name into :newId from Ens_Config.Item Where Production = :productionName)
    &sql(OPEN curr)
    For {
        &sql(FETCH curr)
        Quit:SQLCODE
        Do items.%Push(newId)
    }
    &sql(CLOSE curr)
    quit items
}

>>> zw ##class(ISC.SE.ProductionTools).ListItemsInProduction()

["From_Athena_Multi","From_Athena_Multi_Router","From_Cerner_ADT","From_Cerner_ADT_Router","From_Cerner_Orders","From_Cerner_Orders_Router","From_Dictaphone_Results","From_Dictaphone_Results_Router"
,"From_Lab_Results","From_Lab_Results_Router","From_Radiology_Results","From_Radiology_Results_Router","HS.IHE.XDSb.DocumentSource.Operations","HS.IHE.XDSb.Repository.Operations","To_Cerner_Results"
,"To_Dictaphone","To_Intellilab","To_Lab","To_Radiology"]  ; <DYNAMIC ARRAY>
# Python
# Get Dataframe of active production items

import os
# Set environment variables
os.environ['IRISNAMESPACE'] = 'DEMONSTRATION'
import iris

def getActiveProductionItems():
    productionName = iris.cls('Ens.Director').GetActiveProductionName()
    df = iris.sql.exec("SELECT Name FROM Ens_Config.Item Where Production = '{}'".format(productionName))
    return df

production_items_df = getActiveProductionItems().dataframe()

#                                      name
# 0                       From_Athena_Multi
# 1                From_Athena_Multi_Router
# 2                         From_Cerner_ADT
# 3                  From_Cerner_ADT_Router
# 4                      From_Cerner_Orders
# 5               From_Cerner_Orders_Router
# 6                 From_Dictaphone_Results
# 7          From_Dictaphone_Results_Router
# 8                        From_Lab_Results
# 9                 From_Lab_Results_Router
# 10                 From_Radiology_Results
# 11          From_Radiology_Results_Router
# 12  HS.IHE.XDSb.DocumentSource.Operations
# 13      HS.IHE.XDSb.Repository.Operations
# 14                      To_Cerner_Results
# 15                          To_Dictaphone
# 16                          To_Intellilab
# 17                                 To_Lab
# 18                           To_Radiology

API 経由で本番環境を操作する

1. コンポーネントを追加する

// ObjectScript
set productionName = ##class(Ens.Director).GetActiveProductionName()
//新しい xml ファイルサービスを作成
set classname="EnsLib.XML.FileService"  //この項目のクラス
set name="NewService"           //構成名
set item=##class(Ens.Config.Item).%New(classname)

set item.Name=name
set item.Comment = "Test Service"
set item.PoolSize = "1"
set item.Enabled = 1
do item.%Save()
//  
// 本番環境クラスを開く
// prod="Test.configtest" を設定   //本番環境名を手動で設定
// または
set prod = productionName
set prodObj=##class(Ens.Config.Production).%OpenId(prod)
// 新しい項目を保存
set tSC=prodObj.Items.Insert(item)
set tSC=prodObj.SaveToClass(item)
set tSC=prodObj.%Save()

// 上記から項目を削除
set tSC = prodObj.RemoveItem(item)
set tSC = prodObj.SaveToClass()
set tSC=prodObj.%Save()
# Python
import os
os.environ['IRISNAMESPACE'] = 'DEMONSTRATION'
import iris
active_production = iris.cls('Ens.Director').GetActiveProductionName()
print("Current Production {}".format(active_production))

# コンポーネントに関するメタデータ
classname="EnsLib.XML.FileService"   # この項目のクラス
name="NewService"                    # 構成名
item=iris.cls('Ens.Config.Item')._New(classname) # 新しいコンポーネントを作る
item.Name=name
item.Comment = "Test Service"
item.PoolSize = "1"
item.Enabled = 1
item._Save()

# 本番環境クラスを開く
# prod="Test.configtest"    # 本番環境名を手動で設定
# または上記のアクティブな本番環境名を使用
prod = active_production

prodObj=iris.cls('Ens.Config.Production')._OpenId(prod)
# 項目を挿入したら本番環境を保存
tSC=prodObj.Items.Insert(item)
tSC=prodObj.SaveToClass(item)
tSC=prodObj._Save()

# 上記から項目を削除
tSC = prodObj.RemoveItem(item)
tSC = prodObj.SaveToClass()
tSC=prodObj._Save()

2. コンポーネントを無効化/有効化する

// ObjectScript
set productionName = ##class(Ens.Director).GetActiveProductionName()
set itemName = "My.Inbound.HL7"
// 項目の有効化に必要
Set componentName = productionName _ "||" _ itemName _ "|"
// 無効化または有効化
Set enable = 1 // または 0
Do ##class(Ens.Director).EnableConfigItem(componentName, enable, 1)

/// 本番環境内で構成項目を有効化または無効化します。 本番環境は実行中であってもなくても構いません。
/// pConfigItemName 引数は、有効化または無効化される構成項目名を指定します。
/// 同じ構成名を持つ項目が複数一致し、そのいずれかがすでに有効化されている場合は、 
///  pEnable=1 オプションの効果はなく、pEnable=0 オプションによって実行中の一致する
///   本番環境項目が無効化されます。実行中でない場合は、最初に一致する有効な項目が無効化されます。
///   
/// 構成項目名の仕様の文字列の完全な構文については、Ens.Director.ParseConfigName() メソッドをご覧ください。
ClassMethod EnableConfigItem(pConfigItemName As %String, pEnable As %Boolean = 1, pDoUpdate As %Boolean = 1)
# Python
import os
os.environ['IRISNAMESPACE'] = 'DEMONSTRATION'
import iris

active_production = iris.cls('Ens.Director').GetActiveProductionName()
item_name = "My.Inbound.HL7"
componentName = active_production + "||" + item_name + "|"

enable = 1 # or 0
iris.cls('Ens.Director').EnableConfigItem(componentName, enable, 1)

API による本番環境ステータス

// ObjectScript
/// このメソッドは、出力パラメーター経由で本番環境のステータスを返します。
/// pProductionName: ステータスが running、suspended、または troubled の場合に、その本番環境名を返します。
/// pState: 本番環境のステータスを出力します。 有効な値:
///          $$$eProductionStateRunning == 1
///          $$$eProductionStateStopped == 2
///          $$$eProductionStateSuspended == 3
///          $$$eProductionStateTroubled == 4
Set sc = ##class(Ens.Director).GetProductionStatus(.productionName, .productionState) 
Write productionName, " -- ", productionState
import os
# ネームスペースを難しい方法で設定
os.environ['IRISNAMESPACE'] = 'DEMONSTRATION'

import iris

# 出力変数を使用したテスト2
productionName, productionState = iris.ref('productionName'), iris.ref('productionState')
status = iris.cls('Ens.Director').GetProductionStatus(productionName, productionState) 

print("Status: {}".format(status))
# .value を表示
print("Production: {}".format(productionName.value))
# .value を表示
print("Production State: {}".format(productionState.value))
0
0 204
記事 Megumi Kakechi · 7月 31, 2022 2m read

これは InterSystems FAQ サイトの記事です。
TRYブロック内で任意のカスタムエラーを発生させたい場合、以下のように throw で例外を渡すことが可能です。

以下のサンプルでは、Stcount が 1 より小さい場合にカスタムエラーを発生させています。

Class User.Test
{

ClassMethod ExceptionTest()
 {
    try
    {
      // : some codesif (Stcount<1) {
          throw##class(%Exception.General).%New(" ユーザ定義エラー","5001","ロケーション","エラー時のデータ")  
          // ユーザ作成エラーは5001以降
      }
    }
    catch ex
    {
      write"エラー #", ex.Code, ": ", ex.Name, " : ", ex.Location, " ", ex.Data
      return
    }
 }
}


上の例では、Stcount が 1 より小さい場合、以下のようなエラーが出力されます。

0
1 356
記事 Toshihiko Minamoto · 4月 26, 2022 8m read

@Ming Zhou から素晴らしい質問をいただきました。その回答は、まさに私がObjectScriptを愛用している理由を表しています。

初めて誰かに ObjectScript や IRIS を説明する際、必ず、クラスを記述してコンパイルし、テーブルを取得して、オブジェクトまたはリレーショナルのいずれか最も自然な観点からデータを操作できると説明しています。 いずれにせよ、これは単に、グローバルと呼ばれる非常に高速な内部データ構造を囲む薄めのラッパーであり、速度をさらにバーストさせる必要がある場合にも使用できます。

オタクレベルの人と話すときには、ObjectScipt はあらゆる類の凝ったメタプログラミングが可能だと説明します。たった今書いたクラスが、オブジェクトやリレーショナルというアクセス方法から、さらなる高速なアクセスが必要な時に内部データ構造を使う方法まで操作ができるためです。

「継承されたプロパティも含め、クラス内のすべてのプロパティを取得するにはどうすればよいですか?」という質問に対する回答がわかるでしょう。

同じ回答を得られる 3 つの異なる方法を以下に示します。

Class DC.Demo.PropertyQuery Extends %Persistent
{

Property Foo As %String;

Property Bar As %Boolean;

/// 特定の目的を実現するためのすべての方法を紹介します
ClassMethod Run()
{
    for method = "FromRelationship","WithQuery","AsQuicklyAsPossible" {
        write !,method,":"
        kill properties
        do $classmethod($classname(),"GetProperties"_method,.properties)
        do ..Print(.properties)
        write !
    }
}

ClassMethod Benchmark()
{
    for method = "FromRelationship","WithQuery","AsQuicklyAsPossible" {
        write !,method,":",!
        set start = $zhorolog
        set startGlobalRefs = $system.Process.GlobalReferences($job)
        set startLines = $system.Process.LinesExecuted($job)
        for i=1:1:1000 {
            kill properties
            do $classmethod($classname(),"GetProperties"_method,.properties)
        }
        set endLines = $system.Process.LinesExecuted($job)
        set endGlobalRefs = $system.Process.GlobalReferences($job)
        write "Elapsed time (1000x): ",($zhorolog-start)," seconds; ",(endGlobalRefs-startGlobalRefs)," global references; ",(endLines-startLines)," routine lines",!
    }
}

/// %Dictionary.CompiledClass 内のプロパティ関係を使用してプロパティを取得します
ClassMethod GetPropertiesFromRelationship(Output properties)
{
    // 些細な問題: %OpenId と Properties.GetNext() は、絶対的に必要なデータ以上のデータを読み込むため、低速です。
    // グローバル参照が多いほど、処理に時間がかかります。
    set class = ##class(%Dictionary.CompiledClass).IDKEYOpen($classname(),,.sc)
    $$$ThrowOnError(sc)
    set key = ""
    for {
        set property = class.Properties.GetNext(.key)
        quit:key=""
        set properties(property.Name) = $listbuild(property.Type,property.Origin)
        // 余分なメモリの消費を回避します
        do class.Properties.%UnSwizzleAt(key)
    }
}

/// %Dictionary.CompiledProperty に対してクエリし、プロパティを取得します
ClassMethod GetPropertiesWithQuery(Output properties)
{
    // SQL でプロパティを取得することで、不要な参照のオーバーヘッドを回避します
    set result = ##class(%SQL.Statement).%ExecDirect(,
        "select Name,Type,Origin from %Dictionary.CompiledProperty where parent = ?",
        $classname())
    if result.%SQLCODE < 0 {
        throw ##class(%Exception.SQL).CreateFromSQLCODE(result.%SQLCODE,result.%Message)
    }
    while result.%Next(.sc) {
        $$$ThrowOnError(sc)
        set properties(result.Name) = $listbuild(result.Type,result.Origin)
    }
    $$$ThrowOnError(sc)
}

/// ダイレクトグローバル参照をラッピングするマクロを使用してプロパティを取得します
ClassMethod GetPropertiesAsQuicklyAsPossible(Output properties)
{
    // マクロでラップされたダイレクトグローバル参照を介してプロパティを取得する方法は読み取りにくいですが、
    // 一番速い方法です。
    set key = ""
    set class = $classname()
    for {
        set key = $$$comMemberNext(class,$$$cCLASSproperty,key)
        quit:key=""
        set type = $$$comMemberKeyGet(class,$$$cCLASSproperty,key,$$$cPROPtype)
        set origin = $$$comMemberKeyGet(class,$$$cCLASSproperty,key,$$$cPROPorigin)
        set properties(key) = $listbuild(type,origin)
    }
}

ClassMethod Print(ByRef properties)
{
    set key = ""
    for {
        set key = $order(properties(key),1,data)
        quit:key=""
        set $listbuild(type,origin) = data
        write !,"property: ",key,"; type: ",type,"; origin: ",origin
    }
}

Storage Default
{
<Data name="PropertyQueryDefaultData">
<Value name="1">
<Value>%%CLASSNAME</Value>
</Value>
<Value name="2">
<Value>Foo</Value>
</Value>
<Value name="3">
<Value>Bar</Value>
</Value>
</Data>
<DataLocation>^DC.Demo.PropertyQueryD</DataLocation>
<DefaultData>PropertyQueryDefaultData</DefaultData>
<IdLocation>^DC.Demo.PropertyQueryD</IdLocation>
<IndexLocation>^DC.Demo.PropertyQueryI</IndexLocation>
<StreamLocation>^DC.Demo.PropertyQueryS</StreamLocation>
<Type>%Storage.Persistent</Type>
}

}

そしてもちろん、これらいずれかのアプローチを使用しても、答えは同じです。

d ##class(DC.Demo.PropertyQuery).Run()

FromRelationship:property: %%OID; type: %Library.RawString; origin: %Library.RegisteredObjectproperty: %Concurrency; type: %Library.RawString; origin: %Library.Persistentproperty: Bar; type: %Library.Boolean; origin: DC.Demo.PropertyQueryproperty: Foo; type: %Library.String; origin: DC.Demo.PropertyQuery

WithQuery:property: %%OID; type: %Library.RawString; origin: %Library.RegisteredObjectproperty: %Concurrency; type: %Library.RawString; origin: %Library.Persistentproperty: Bar; type: %Library.Boolean; origin: DC.Demo.PropertyQueryproperty: Foo; type: %Library.String; origin: DC.Demo.PropertyQuery

AsQuicklyAsPossible:property: %%OID; type: %Library.RawString; origin: %Library.RegisteredObjectproperty: %Concurrency; type: %Library.RawString; origin: %Library.Persistentproperty: Bar; type: %Library.Boolean; origin: DC.Demo.PropertyQueryproperty: Foo; type: %Library.String; origin: DC.Demo.PropertyQuery

パフォーマンスを比較します。


d ##class(DC.Demo.PropertyQuery).Benchmark()

FromRelationship:Elapsed time (1000x): .78834 seconds; 1056000 global references; 2472003 routine lines

WithQuery:Elapsed time (1000x): .095235 seconds; 28001 global references; 537007 routine lines

AsQuicklyAsPossible:Elapsed time (1000x): .016422 seconds; 25000 global references; 33003 routine lines

これをちょっと分析する限り、まったく期待どおりの結果です。 クラスとプロパティのオブジェクトアクセスは、クラス定義とコンパイルされたクラスのメタデータが多数のグローバルに保存されているため、はるかにコストがかかります。$listbuild リストにすべてのデータが格納されているのではなく(グローバル参照を減らすため)、各グローバルノードに単一の値でツリーに格納されています。 オブジェクトを開くというのは、これらすべてを読み取ることになるため、もちろん「FromRelationship」手法がはるかに最も低速になります。 もちろん、これは IRIS における一般的なオブジェクトアクセスのパフォーマンスを表すものではありません。このケースは、オブジェクトを使用する際の特に悪いケースとしてたまたま発生しただけです。

このクエリと生のグローバルベースのアプローチは、ルーチン行ではなくグローバル参照という意味で似ています。 Dynamic SQL を使用した上記の単純なアプローチには、イテレーションごとに行うクエリ準備のオーバーヘッドがあります。 このオーバーヘッドの一部を回避するには、準備しておいた %SQL.Statement を再利用するか、カーソル付きの埋め込み SQLを使用するか(いくつかの理由で、私は気に入っていません)、次のように厄介な方法を取ることができます。

/// %Dictionary.CompiledProperty に対するクエリを使用して、プロパティを取得します
ClassMethod GetPropertiesWithEmbeddedQuery(Output properties)
{
    set classname = $classname()
    
    // 簡易: 1 行を返すクエリを実行した後に、カーソルの記述を省略して、データの抽出のみを行います。
    // 次のアプローチのパフォーマンスはカーソルを上回ります(演習として残しておきます)。いずれにしても私はカーソルの操作が苦手です。
    &SQL(SELECT %DLIST($ListBuild(Name,Type,Origin)) INTO :allProperties FROM %Dictionary.CompiledProperty WHERE parent = :classname)
    if (SQLCODE < 0) {
        throw ##class(%Exception.SQL).CreateFromSQLCODE(SQLCODE,%msg)
    }
    if (SQLCODE = 100) {
        quit
    }
    set pointer = 0
    while $listnext(allProperties,pointer,propertyInfo) {
        set properties($list(propertyInfo)) = $list(propertyInfo,2,3)
    }
}

これをベンチマークに追加すると、次のようになります。

WithEmbeddedQuery:
Elapsed time (1000x): .024862 seconds; 25000 global references; 95003 routine lines

AsQuicklyAsPossible:
Elapsed time (1000x): .016422 seconds; 25000 global references; 33003 routine lines

非常に近い結果です!

0
0 453
記事 Mihoko Iijima · 3月 29, 2022 3m read

これは、InterSystems FAQサイトの記事です。

メール送付のコードを記述する前に、管理ポータルで SSL/TLS 構成を作成します。

管理ポータル > システム管理 > セキュリティ > SSL/TLS 構成

メール送付までの流れは以下の通りです。

  1. メールメッセージ用クラス:%Net.MailMessage のインスタンスを作成し、送信元メールアドレス、宛先メールアドレス、件名、本文を設定します。
  2. 認証情報設定用クラス:%Net.Authenticator のインスタンスを作成し、メール送付時に使用する認証情報を設定します。
  3. SMTP用クラス:%Net.SMTP のインスタンスを作成し、SMTP サーバの設定、管理ポータルで作成した SSL/TLS 構成名の指定、2で作成した認証情報と 1で作成したメールメッセージを使用して、メールを送信します。

ターミナルからの実行例は以下の通りです(Gmail を利用しています)。

《メモ》
現在(2022年5月30日以降)、Gmailを利用したメール送付を行う場合 OAuth2.0 の利用が必須となりました。Gmailを利用する場合の手順ついては、「OAuth 2.0 を利用して IRIS から Gmail を送信する」をご参照ください。

0
0 528
記事 Tomoko Furuzono · 3月 29, 2022 1m read

これは、InterSystems FAQサイトの記事です。
 

現在実行中のコードの位置は、$STACK関数を使用して $STACK($STACK,"PLACE") で取得できます。

サンプルコードは以下ドキュメントをご参照ください。

現在実行中のコードの位置を取得する方法について【IRIS】
現在実行中のコードの位置を取得する方法について

0
0 128
記事 Tomoko Furuzono · 3月 29, 2022 1m read

これは、InterSystems FAQサイトの記事です。
 

SQLの場合、NULLデータと空文字列 ('')は区別されます。
それぞれの設定・確認方法は、以下のようになります。

(1) NULLデータ

【SQL】 

insert into test (a) values (NULL)
select * from test where a IS NULL

【InterSystems ObjectScript】

set x=##class(User.test).%New()
set x.a=""

(2) 空文字列 ('')

【SQL】

insert into test (a) values ('')
select * from test where a = ''

【InterSystems ObjectScript】

set x=##class(User.test).%New()
set x.a=$C(0)

詳細については、以下ドキュメントをご参照ください。

NULL および空文字列【IRIS】
NULL および空文字列

0
0 337
記事 Toshihiko Minamoto · 3月 10, 2022 4m read

背景

先日、ObjectScript の永続(Persistent)クラスのプロパティを編集していたところ、ストレージ定義が最後の変更を反映するように更新されていないことに気づきました。

この場合、クラス定義に不要となったプロパティを削除した上で保存し、再コンパイルしましたが、それでもストレージ定義に残ったままになっていました。

それでも焦ることはありませんでした。 ストレージ定義がコンパイル時に自動生成されるのであれば、それを_削除_して、クラスを再コンパイルすればよいからです。 もちろん、この後、削除されたプロパティはストレージ定義に表示されなくなりました。

問題解決... ですよね?

(ブブー、間違いです)

後になって、このアプローチはクラスにデータが保存されていないときに機能することがわかりました。 ただし、既存のデータがあるのであれば、重大なデータ参照の問題が発生する可能性があります。

そもそもストレージ定義とは?

ストレージ定義はクラスプロパティとデータベース内のその物理ストレージ場所をリンクするマップとして機能します。

複数のスロットのある本棚とどのスロットに本が収まっているかを追跡するためのリストがあるとします。 新しい本は、常にリスト上で空いている一番左のスロットに配置され、スロットは昇順で番号が付けられています。

<td>
  ストレージ定義
</td>

<td>
  ステータス
</td>

<td>
  本棚での喩え
</td>
<td>
  クラスにはまだデータが保存されていない状態で、ストレージ定義を変更します。
</td>

<td>
  OK                                                                                             
</td>

<td>
  棚に本がない状態で、後に本が収納されるべきスロットのマップを変更します。 データなし = データ参照の問題はありません。
</td>
<td>
  クラスにデータが保存されている状態で、データを保存せずに新しいプロパティを追加し、その後でプロパティを削除して別のプロパティを追加します。 (つまり、データが保存される前に、最後に追加されたプロパティの名前を変更します)
</td>

<td>
  OK
</td>

<td>
  スロット 3 は、次に空いている棚のスロットです。 本 C がスロット 3 に配置されることを書き留めても、実際にはまだスロット 3 には配置しません。その状態で、スロット 3 は本 D 用に予約されているとしてマップを更新します。
</td>
<td>
  クラスにデータが保存されている状態で、データが保存されたプロパティをストレージ定義から削除します。
</td>

<td>
  エラー
</td>

<td>
  本棚のスロット 1~3 には本が収まっています。 マップには(スロット 1: 本 A、スロット 2: 本 B、スロット 3: 本 C)と示されています。 本 B をマップからのみ削除します。 本 B はまだ棚にありますが、マップではスロット 2 が空になっている状態です。 次に新しい本を追加する際に、マップにはスロット 2 を利用できることが示されているため、無意識にスロットをダブルブッキングすることになります。
</td>
シナリオ
1
2
3

 

シナリオ 3 の続き

この問題は、マップから本 B を削除するときに、物理的にスロットからも取り除くことで回避したいところですが、 _それをやってはいけません。_誤ってデータテーブルを破損してしまう可能性があります。 驚いたことに、データを手動で変更する方が、棚から本を取り出すことよりもはるかに危険です。

<td>
  ストレージ定義
</td>

<td>
  本棚での喩え
</td>
<td>
  何もしません。 データとストレージ定義はそのままにしておきます。
</td>

<td>
  本 B を二度と読むことがないのであれば、それを探すこともないため、マップに存在したままでも問題はありません。
</td>
<td>
  ストレージ定義に使用しなくなったプロパティが残っていることが気になるのであれば、データはそのままにして、使用されなくなったことを示すように、ストレージ定義内のプロパティ名を更新します(例: propertyA => propertyADeprecated)
</td>

<td>
  本 B は棚に置いたままにし、マップ内のスロット 2 のタイトルを「本 B」から「本 B(関連性なし)」に変更します。
</td>
オプション
1
2

 

お読みいただきありがとうございました! 

0
0 126
記事 Toshihiko Minamoto · 3月 8, 2022 8m read

InterSystemsを使用してExcelファイルを生成する方法はたくさんあります。ZENレポートやIRISレポート(Logiレポートまたは正式にはJReportsと呼ばれるレポート)のほか、サードパーティのJavaライブラリを使用するなど、可能性はほぼ無限です。

しかし、Caché ObjectScriptだけで単純なスプレッドシートを作成したい場合はどうでしょうか。 (サードパーティアプリケーションを使用せずに、です)

私の場合、大量の生データを含むレポート(金融関係の人たちが好むレポート)を生成する必要がありますが、私のZEN/IRISでは対応できません。私が呼ぶところの「ゼロバイトファイル」が生成され、基本的にJavaのメモリ不足となり、レポーティングサーバーに大きな負荷を生じてしまいます。

これは、Office Open XML(OOXML)を使って実現できます。 Office Open XML形式は、多数のXMLファイルで構成されるZIPパケージです。 つまり基本的には、これらのXMLファイルを生成してZIP圧縮し、.xslxに名前を変更すればよいのです。 それくらい単純です。

ファイルは、Open Packaging Conventionsという単純な命名規則に従っています。 パーツのコンテンツタイプを宣言し、消費するアプリケーションにどこから開始するかを指示する必要があります。

単純なスプレッドシートを作成するには、少なくとも5つのファイルが必要です。

  • workbook.xml
  • worksheet.xml
  • [Content_Types].xml
  • styles.xml
  • _rels
    • .rels
    • workbook.xml.rels

workbook.xml
workbookは、様々なワークシートをまとめるコンテナーです。 workbookでは、スタイルパーツ、共有文字列テーブル、およびスプレッドシートファイル全体に適用するその他の情報を参照できます。

ClassMethod GenerateWorkbookXML(){
    set status =$$$OK
    set xmlfile = tempDirectoryPath_"workbook.xml"
    try{
    set stream = ##class(%Stream.FileCharacter).%New()
    set sc=stream.LinkToFile(xmlfile)
    do stream.WriteLine("<?xml version='1.0' encoding='UTF-8' standalone='yes'?>")
    do stream.WriteLine("<workbook xmlns='http://schemas.openxmlformats.org/spreadsheetml/2006/main' xmlns:r='http://schemas.openxmlformats.org/officeDocument/2006/relationships'>")
    do stream.WriteLine("<sheets> <sheet name='"_workSheetName_"' sheetId='1' <span style="color:#2ecc71;">r:id='rId1'</span>/>")
    do stream.WriteLine("</sheets> </workbook>")
    
        do stream.%Save()
    }catch{
        set status=$$$NO
    }
    kill stream
    return status
}

_rels/workbook.xml.rels
workbook.xmlパーツからの参照に一致するように、rId1というIDを持つリレーションを作成する必要があります。

ClassMethod CreateRelsXML(){
set status =$$$OK
    
    set isunix=$zcvt($p($zv," ",3,$l($p($zv," (")," ")),"U")["UNIX"
    if isunix {
        set ext="/"
    }else{
        set ext="\"
    }
    set xmlfile = fileDirectory_"_rels"_ext_"workbook.xml.rels"
    set stream = ##class(%Stream.FileCharacter).%New()
    set sc=stream.LinkToFile(xmlfile)
    do stream.WriteLine("<?xml version='1.0' encoding='UTF-8' standalone='yes'?>")
    do stream.WriteLine("<Relationships xmlns='http://schemas.openxmlformats.org/package/2006/relationships'>")
      do stream.WriteLine("<Relationship Id='rId1' Type='http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet' Target='worksheet.xml'/>")
      do stream.WriteLine("<Relationship Id='rId2' Type='http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles' Target='styles.xml' />")
    do stream.WriteLine("</Relationships>")
    try{
        do stream.%Save()
    }catch{
        set status=$$$NO
    }
    kill stream
    set xmlfile = fileDirectory_"_rels"_ext_".rels"
    set stream = ##class(%Stream.FileCharacter).%New()
    set sc=stream.LinkToFile(xmlfile)
    
    do stream.WriteLine("

<?xml version='1.0' encoding='UTF-8' standalone='yes'?>")
    do stream.WriteLine("<Relationships xmlns='http://schemas.openxmlformats.org/package/2006/relationships'>")
      do stream.WriteLine("<Relationship Id='rId1' Type='http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument' Target='workbook.xml'/>")
    do stream.WriteLine("</Relationships>")
    try{
        do stream.%Save()
    }catch{
        set status=$$$NO
    }
    kill stream
    return status
}

[Content_Types].xml
静的ファイル(現時点では、ワークシートの数に応じた動的ファイル)はworkbookのワークシートとスタイルを紐づけます。Office Open XMLファイルごとに、ZIPパッケージ使用されるコンテンツタイプを宣言する必要があります。 これは、[Content_Types].xmlファイルで行います。  

ClassMethod GenerateConntentTypesXML(){
    set status =$$$OK
    set xmlfile = tempDirectoryPath_"[Content_Types].xml"
    set stream = ##class(%Stream.FileCharacter).%New()
    set sc=stream.LinkToFile(xmlfile)
    try{
        do stream.WriteLine("<?xml version='1.0' encoding='UTF-8' standalone='yes'?>")
        do stream.WriteLine("<Types xmlns='http://schemas.openxmlformats.org/package/2006/content-types'>")
          do stream.WriteLine("<Default Extension='rels' ContentType='application/vnd.openxmlformats-package.relationships+xml'/>")
        do stream.WriteLine("<Override PartName='/workbook.xml' ContentType='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml'/>")
          do stream.WriteLine("<Override PartName='/worksheet.xml' ContentType='application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml'/>")
          do stream.WriteLine("<Override PartName='/styles.xml' ContentType='application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml' />")
        do stream.WriteLine("</Types>")
        do stream.%Save()
    }catch{
        set status=$$$NO
    }
    kill stream
    return status
}

styles.xml
すべてのフォーマットがこのファイルに含まれます。現時点では、静的スタイルが追加されています(より動的なworkbook固有のスタイルに変換する予定です)。

<th>
  スタイル
</th>

<th>
  Excelフォーマット
</th>
<td>
  デフォルト
</td>

<td>
  テキスト
</td>
<td>
  #;[Red]-#
</td>

<td>
  数値
</td>
<td>
  #.##;[Red]-#.##
</td>

<td>
  数値
</td>
<td>
  yyyy/mm/dd
</td>

<td>
  日付
</td>
<td>
  hh:mm
</td>

<td>
  日付
</td>
<td>
  ヘッダーと中央揃え
</td>

<td>
  テキスト
</td>
<td>
  ヘッダー2左寄せ
</td>

<td>
  テキスト
</td>
<td>
  良い(緑ハイライト)
</td>

<td>
  全般
</td>
<td>
  悪い(赤ハイライト)
</td>

<td>
  全般
</td>
<td>
  どちらでもない(オレンジハイライト)
</td>

<td>
  全般
</td>
<td>
  yyyy/mm/dd hh:mm
</td>

<td>
  日付
</td>
Excelスタイル
ID
1
2
3
4
5
6
7
8
9
10
11
ClassMethod CreateStylesXML(){
    set status =$$$OK
    set xmlfile = tempDirectoryPath_"styles.xml"
    try{
        set stream = ##class(%Stream.FileCharacter).%New()
        set sc=stream.LinkToFile(xmlfile)
         do stream.WriteLine("<?xml version=""1.0"" encoding=""UTF-8"" standalone=""yes""?>")
        do stream.WriteLine("<styleSheet xmlns=""http://schemas.openxmlformats.org/spreadsheetml/2006/main"" xmlns:mc=""http://schemas.openxmlformats.org/markup-compatibility/2006"" mc:Ignorable=""x14ac x16r2 xr"" xmlns:x14ac=""http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac"" xmlns:x16r2=""http://schemas.microsoft.com/office/spreadsheetml/2015/02/main"" xmlns:xr=""http://schemas.microsoft.com/office/spreadsheetml/2014/revision"">")
        do stream.WriteLine("<numFmts count=""4"">")
        do stream.WriteLine("<numFmt numFmtId=""166"" formatCode=""#,##0;[Red]\-#,##0""/>")
        do stream.WriteLine("<numFmt numFmtId=""168"" formatCode=""#,##0.00;[Red]\-#,##0.00""/>")
        do stream.WriteLine("<numFmt numFmtId=""169"" formatCode=""dd\/mm\/yyyy;@""/>")
        do stream.WriteLine("<numFmt numFmtId=""170"" formatCode=""dd/mm/yyyy\ hh:mm""/></numFmts>")
        do stream.WriteLine("<fonts count=""5"" x14ac:knownFonts=""1"">")
        do stream.WriteLine("<font><sz val=""10""/><color theme=""1""/><name val=""Calibri""/><family val=""2""/><scheme val=""minor""/></font>")
        do stream.WriteLine("<font><sz val=""10""/><color rgb=""FF006100""/><name val=""Calibri""/><family val=""2""/><scheme val=""minor""/></font>")
        do stream.WriteLine("<font><sz val=""10""/><color rgb=""FF9C0006""/><name val=""Calibri""/><family val=""2""/><scheme val=""minor""/></font>")
        do stream.WriteLine("<font><sz val=""10""/><color rgb=""FF9C5700""/><name val=""Calibri""/><family val=""2""/><scheme val=""minor""/></font>")
        do stream.WriteLine("<font><b/><sz val=""10""/><color theme=""1""/><name val=""Calibri""/><family val=""2""/><scheme val=""minor""/></font></fonts>")
        do stream.WriteLine("<fills count=""5"">")
        do stream.WriteLine("<fill><patternFill patternType=""none""/></fill>")
        do stream.WriteLine("<fill><patternFill patternType=""gray125""/></fill>")
        do stream.WriteLine("<fill><patternFill patternType=""solid""><fgColor rgb=""FFC6EFCE""/></patternFill></fill>")
        do stream.WriteLine("<fill><patternFill patternType=""solid""><fgColor rgb=""FFFFC7CE""/></patternFill></fill>")
        do stream.WriteLine("<fill><patternFill patternType=""solid""><fgColor rgb=""FFFFEB9C""/></patternFill></fill></fills>")
        do stream.WriteLine("<borders count=""1""><border><left/><right/><top/><bottom/><diagonal/></border></borders>")
        do stream.WriteLine("<cellStyleXfs count=""4"">")
        do stream.WriteLine("<xf numFmtId=""0"" fontId=""0"" fillId=""0"" borderId=""0""/>")
        do stream.WriteLine("<xf numFmtId=""0"" fontId=""1"" fillId=""2"" borderId=""0"" applyNumberFormat=""0"" applyBorder=""0"" applyAlignment=""0"" applyProtection=""0""/>")
        do stream.WriteLine("<xf numFmtId=""0"" fontId=""2"" fillId=""3"" borderId=""0"" applyNumberFormat=""0"" applyBorder=""0"" applyAlignment=""0"" applyProtection=""0""/>")
        do stream.WriteLine("<xf numFmtId=""0"" fontId=""3"" fillId=""4"" borderId=""0"" applyNumberFormat=""0"" applyBorder=""0"" applyAlignment=""0"" applyProtection=""0""/></cellStyleXfs>")
        do stream.WriteLine("<cellXfs count=""12""><xf numFmtId=""0"" fontId=""0"" fillId=""0"" borderId=""0"" xfId=""0""/>")
        do stream.WriteLine("<xf numFmtId=""49"" fontId=""0"" fillId=""0"" borderId=""0"" xfId=""0"" quotePrefix=""1"" applyNumberFormat=""1""/>")
        do stream.WriteLine("<xf numFmtId=""166"" fontId=""0"" fillId=""0"" borderId=""0"" xfId=""0"" applyNumberFormat=""1""/>")
        do stream.WriteLine("<xf numFmtId=""168"" fontId=""0"" fillId=""0"" borderId=""0"" xfId=""0"" applyNumberFormat=""1""/>")
        do stream.WriteLine("<xf numFmtId=""169"" fontId=""0"" fillId=""0"" borderId=""0"" xfId=""0"" applyNumberFormat=""1""/>")
        do stream.WriteLine("<xf numFmtId=""20"" fontId=""0"" fillId=""0"" borderId=""0"" xfId=""0"" applyNumberFormat=""1""/>")
        do stream.WriteLine("<xf numFmtId=""49"" fontId=""4"" fillId=""0"" borderId=""0"" xfId=""0"" applyNumberFormat=""1"" applyFont=""1""/>")
        do stream.WriteLine("<xf numFmtId=""49"" fontId=""4"" fillId=""0"" borderId=""0"" xfId=""0"" applyNumberFormat=""1"" applyFont=""1"" applyAlignment=""1""><alignment horizontal=""center""/>")
        do stream.WriteLine("</xf>")
        do stream.WriteLine("<xf numFmtId=""49"" fontId=""1"" fillId=""2"" borderId=""0"" xfId=""1"" applyNumberFormat=""1""/>")
        do stream.WriteLine("<xf numFmtId=""0"" fontId=""2"" fillId=""3"" borderId=""0"" xfId=""2""/>")
        do stream.WriteLine("<xf numFmtId=""0"" fontId=""3"" fillId=""4"" borderId=""0"" xfId=""3""/>")
        do stream.WriteLine("<xf numFmtId=""170"" fontId=""0"" fillId=""0"" borderId=""0"" xfId=""0"" applyNumberFormat=""1""/></cellXfs>")
        do stream.WriteLine("<cellStyles count=""4""><cellStyle name=""Bad"" xfId=""2"" builtinId=""27""/>")
        do stream.WriteLine("<cellStyle name=""Good"" xfId=""1"" builtinId=""26""/><cellStyle name=""Neutral"" xfId=""3"" builtinId=""28""/>")
        do stream.WriteLine("<cellStyle name=""Normal"" xfId=""0"" builtinId=""0""/></cellStyles><dxfs count=""0""/>")
        do stream.WriteLine("<tableStyles count=""0"" defaultTableStyle=""TableStyleMedium2"" defaultPivotStyle=""PivotStyleLight16""/>    ")
        do stream.WriteLine("<extLst><ext uri=""{EB79DEF2-80B8-43e5-95BD-54CBDDF9020C}"" xmlns:x14=""http://schemas.microsoft.com/office/spreadsheetml/2009/9/main"">")
        do stream.WriteLine("<x14:slicerStyles defaultSlicerStyle=""SlicerStyleLight1""/></ext><ext uri=""{9260A510-F301-46a8-8635-F512D64BE5F5}"" xmlns:x15=""http://schemas.microsoft.com/office/spreadsheetml/2010/11/main"">")
        do stream.WriteLine("<x15:timelineStyles defaultTimelineStyle=""TimeSlicerStyleLight1""/></ext></extLst>")
        do stream.WriteLine("</styleSheet>")
        do stream.%Save()
    }catch{
        set status=$$$NO
    }
    kill stream
    return status
}

worksheet.xml
このファイルに日付が含まれます。 シートの最初の行は列のタイトルです。 次の行には、最初の列にのみデータが含まれます。
デフォルトで列が自動調整されない場合は、ここで各列の列幅を定義します。

サンプルworksheet.xml

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<worksheet xmlns="https://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="https://schemas.openxmlformats.org/officeDocument/2006/relationships">
<sheetData>
<row>
    <c t="inlineStr">
        <is>
            <t>Name</t>
        </is>
    </c>
    <c t="inlineStr">
        <is>
            <t>Amount</t>
        </is>
    </c>
</row>
<row>
    <c t="inlineStr">
        <is>
            <t>Jhon Smith</t>
        </is>
    </c>
    <c>
        <v>1000.74</v>
    </c>
</row>
<row>
    <c t="inlineStr">
        <is>
            <t>Tracy A</t>
        </is>
    </c>
    <c>
        <v>6001.74</v>
    </c>
</row>
</sheetData>
</worksheet>

サンプルExcel

_worksheet内の数式は、関数 タグ_を使って表現できます。
 

<c >
<f>B2*0.08</f >
</c >
<c >
<f>B2+C2</f >
</c>

そして最後にそれらをzip圧縮し、名前を.xlsxに変更します(unix zipを使用)。

set cmd ="cd "_fileDirectory_" && find . -type f | xargs zip .."_ext_xlsxFile

 

Excelドキュメントを生成します。

以下のサンプルコードは、Excelドキュメントを生成します。

set file = "/temp/test.xlsx"
set excelObj = ##class(XLSX.writer).%New(file)
do excelObj.SetWorksheetName("test1")
set status = excelObj.BeginWorksheet()
set row = 0
set row = row+1
;----------- excelObj.Cells(rowNumber,columnNumber,style,content)

set status = excelObj.Cells(row,1,1,"Header1")
set row = row+1
set status = excelObj.Cells(row,1,2,"Content 1")
set status = excelObj.EndWorksheet()
W !,excelObj.fileName

ExcelのWriterクラスは、こちらのxlsx.writer.xml.zipにあります。

0
0 457
記事 Tomoko Furuzono · 3月 1, 2022 2m read

これは、InterSystems FAQサイトの記事です。

オブジェクトに対する一般的な処理をサポートする各種関数を提供しています。
詳細は以下ドキュメントをご参照ください。
オブジェクトへの動的アクセス【IRIS】
オブジェクトへの動的アクセス


サンプルクラス(Sample.Person)を使用して、利用例をご紹介します。

【InterSystems IRIS でご確認いただく場合】
InterSystems IRISでは、サンプル定義の一部を GitHub 上に公開しています。
ダウンロード情報やインポート、データ作成手順については、下記の記事をご参照ください 。
サンプル(Sample.Person)のクラス定義ダウンロードとサンプルデータの作成について

【Caché/Ensemble/HealthShare でご確認いただく場合】
SAMPLES ネームスペースへ移動してご確認ください。

(1) クラス名の取得:$CLASSNAME(インスタンス

SAMPLES>set p=##class(Sample.Person).%OpenId(1)
SAMPLES>write $CLASSNAME(p)
Sample.Person
SAMPLES>write $CLASSNAME(p.Home)  // オブジェクト参照のプロパティ
Sample.Address
SAMPLES>
0
0 514
記事 Tomoko Furuzono · 3月 1, 2022 1m read

これは、InterSystems FAQサイトの記事です。

マップされたクラス・ルーチンも含めてコンパイルする場合は、コンパイラ修飾子に "/mapped=1" もしくは "/mapped" を指定します。 例えば、以下のように行います。

【例1】クラスリストを取得してコンパイル 

 do $System.OBJ.GetClassList(.list,"/mapped")
 // build your list starting from .list
 do $System.OBJ.Compile(.list) 

【例2】全てのクラスをコンパイル 

 do $system.OBJ.CompileAll("/mapped") 
0
0 171
質問 Akio Hashimoto · 2月 3, 2022

皆さん
こんにちは。

最近ではVSCodeでIRISのプログラミングを行っている方も多いと思います。
クラスメソッドなんかをコーディングし、実行してエラーとなった場合に、以下のようなエラーメッセージが取得できると思います。

> <UNDEFINED> zNewMethod+32^ClassName.1

これは、NewMethodというメソッド内の32行目でUNDEFINEDが発生した事を指していますが、これは実際にはClassName.clsのNewMethod内の32行目ではなく、ClassName.1というルーチン(int)のzNewMethodでの32行目を示しています。
.clsから生成されるintは空行が削られたり、複数ルーチンに分割されたりしますので、実際のClassName.clsを開いてNewMethodの32行目を見ても実際のエラー箇所では無い場合があります。
VSCodeで開発をしていると、これが結構面倒なのですが、エラーメッセージに出力される行番号を .clsや、.mac 上での行番号に変える方法があれば教えて頂きたく、よろしくお願い致します。

2
0 539
記事 Toshihiko Minamoto · 2月 9, 2022 2m read

これは、前回の「DockerマイクロサーバーとしてのIRIS Native APIを使用するWebSocketクライアントJS」のフォローアップです。

すべてのピースが1つのDockerイメージにまとめられたため、インストールがはるかに簡単になりました。
作業が楽になります。 ただしもちろん、マイクロサービスの原則はわかりにくくなくなっています。
オールインワンのバンドルパッケージであるため、 コンパクトになっています。

動作には変更がないため、 下位互換性のあるマイナーリリースと解釈されてしまう可能性があります。

これには、intersystems/iris-community:2020.2.0.204.0が含まれています。

実行するのは、以下の通りです。

  • docker pull rcemper/rcc:iris-nodejs-compact(1回)
  • docker run --rm --init -d \ --name=iris1 -p 52773:52773 -p 51773:51773 \ rcemper/rcc:iris-nodejs-compact (コンテナーを起動)
  • docker exec -it iris1 bash wsgo.sh (マイクロサービスを起動)
  • docker exec -it iris1 bash wsdemo.sh (テストデータを作成する制御を開始し、echoServerに送信して受信)
  • docker exec -it iris1 bash wsstop.sh (制御から実行されない場合は、最終的にサービスを停止)
  • docker stop iris1 (コンテナーを終了)

それだけです。

2021年7月7日:
正規化されたV2がリリースされました。

  • git clone https://github.com/rcemper/IRIS-NativeAPI-Nodejs-compact.git
  • docker-compose up -d
  • docker-compose exec iris iris session iris %ZSocket
  • docker-compose exec iris node WsockIris.js
0
0 280
記事 Mihoko Iijima · 2月 4, 2022 7m read

開発者の皆さん、Python好きの皆さん、こんにちは!

ドキュメントをみながら IRIS 2021.2 に追加された Embedded Python を試してみました!

IRIS にログインしてるのに Pythonシェルに切り替えできて Python のコードが書けたり、Python で import iris するだけで SQL を実行できたりグローバルを操作できるので、おぉ!✨という感じです。

ぜひ、みなさんも体感してみてください!

では早速。

まず、IRISにログインします。Windows ならターミナルを開きます。Windows 以外は以下実行します。

IRIS のインストール方法を確認されたい方は、【はじめての InterSystems IRIS】セルフラーニングビデオ:基本その1:InterSystems IRIS Community Edition をインストールしてみよう!をチェックしてみてください!

iris session iris
0
1 838
記事 Toshihiko Minamoto · 2月 1, 2022 5m read
これはIRIS 2020.2で動作するコーディングの例です 
最新バージョンとは同期していません。
また、InterSystemsのサポートによるサービスはありません

動作中のデモを確認できるデモビデオを以下で公開しています。https://youtu.be/dSV-0RJ5Olg

皆さんこんにちは
完全に新しいIRISイメージと**たった4行**のDockerコマンドを使って実行するイメージを使ってマイクロサービスのデモを行いましょう。 
2020年6月1日 - rcc

すべてのパーツを1つのコンテナイメージにまとめたコンパクトなオールインワンバージョンが公開されました。
詳細はこちら: IRIS-NativeAPI-Nodejs-compact
2020年5月24日 - rcc

Dockerを使った簡易インストールを追加しました。コンテキストを参照
2020年5月25日 - rcc

Linux & Windowsに最適な検証済みの強化スクリプトはこちら
https://github.com/rcemper/WSockClientMicroSV/blob/master/READMEwindows.MD
2020年5月26日 - rcc

このデモは、Caché用にすでに存在するNode.jsに基づくWebSocketクライアントを再設計したものです。 主に以下のような変更点があります。

  • 新しいIRIS Native API for Node.jsの使用。特にグローバル配列を操作する場合
  • 直接トリガーされたクライアントからサーバー設計への変更
  • マイクロサービス/マイクロサーバーの例として、結果を別のdockerイメージに配置
  • マイクロサービスの実行を制御するための単純なインターフェースをIRISに追加

ユーティリティをIRISホストで直接呼び出す代わりに、System Interoperability(別名 ENSEMBLE)で通常行うようにして、work-packagesをMicroServiceに送信します。
もちろん、2つ以上のWebSocketサーバーオプションがあります。
WebSocketクライアントサービスがジョブを完了したら、その結果を取得します。

組み込みのWebSocketクライアントに対するメリットは、すべてのネットワーク、セキュリティ、ファイアウォールの問題を主要なデータサーバーに寄せ付けないことです。 Node.js がこの分野で持っている経験と品質については言うまでもありません。

デモは wss: デフォルトのechoサーバーとして使用します。
次に、テキストを数行入力します。
独自のテキストの任意の場所に「Lorem Ipsum」を追加して、コンテンツを長くすると良いでしょう。
そして、それをサービスに送信して、エコーを待ちます。
また、制御プログラムをExitまたはサービスをStopする前に、
テキストを変更するオプションもあります。

この処理はすべて非同期で実行されます。
完了を待たずに、それまでにechoサーバーから受信した内容を、リスナーが
定期的に表示します。

インストールするには、以下が必要です。

  • IRISのdocker イメージ(intersystems/iris-community:2020.2.0.199.0)

  • WebSocketマイクロサーバーのdockerイメージ(docker pull rcemper/rcc:demoJS

  • 簡略化されたinit: initに次のdockerコマンドを実行してください。 docker run --name ini1 --init -it --rm \ -privileged -v $(pwd):/external \ rcemper/rcc:demoJS bash /rcc/init.sh ### 元のアプローチは有効ではありますが、必須ではありません。 ###

  • IRIS-Docker-micro-Durabilityを利用するためにOpen Exchangeまたはここから入手したWSockClientMicroSV.tar.gz

  • demoディレクトリの確認: Dockerイメージはnobodyであるため、保護をrwx (chmod 777) に設定します。###

これを実行するには、まずdemoディレクトリからIRISを起動します(-dまたは-itを実行して動作を観察します)。(!)

docker run --name iris1 --init --rm -d \
-p 52773:52773 -p 51773:51773 \
-v $(pwd):/external \
intersystems/iris-community:2020.2.0.199.0 \
-b /external/pre.copy

次にMicroServerを起動します。

docker run --name rcc1 --init -it --rm \
rcemper/rcc:demoJS /usr/bin/node /rcc/nodejs/WSockIris.js $(hostname -I)

. -itを使って起動したため、以下が表示されます。

platform = linux: ubuntu  

    *****************************  
    Connect to IRIS on: 192.168.0.23  
Successfully connected to InterSystems IRIS.  
    *** wait 3sec for request ***  
    ******* Startup done ********  

    *** wait 3sec for request ***  
    *** wait 3sec for request ***  

次に、新しいLinuxターミナルで制御アプリケーションを実行します。

docker exec -it iris1 iris session iris ZSocket  

以下が表示されます。

*** Welcome to WebSocket Micoservice demo ***  
Known Hosts (*=Exit) [1]:  
1  wss://echo.websocket.org/  
2  --- server 2 ----  
3  --- server 3 ----  
select (1):  ==> wss://echo.websocket.org/  
#
Enter text to get echoed from WebSocketClient Service
Terminate with * at first position
or get generated text by %
or append new text with @

1    hello socket microServer
2    now you got 2 lines
3    *

Select action for WebClient Service
New EchoServer (E), Send+Listen(S),New Text(N),Exit(X), Exit+Stop Client(Z) [S]s
%%%%%%%%%%%%%%%%%%%%%%%%%%

******* 0 Replies *******

******* 2 Replies *******
1    hello socket microServer
2    now you got 2 lines


Select action for WebClient Service

マイクロサービスでは以下が表示されます。

*** wait 3sec for request ***
echoserver:  wss://echo.websocket.org/
** Lines to process: 1 **
********* next turn *********
* WebSocket Client connected *
****** Client is ready ****** 

Line: 1 text> 'hello socket microServer '
Received: 1 > 'hello socket microServer '

Line: 2 text> 'now you got 2 lines '
Received: 2 > 'now you got 2 lines '

******* lines sent: 2 ******
*** replies received: 2 ****

*** wait 3sec for request ***

事後警告。 イメージのバージョンを必ず確認してください!
私はntersystems/iris-community:2020.2.0.
204にハマってしまいました。

注意: すべてのスクリプトはLinuxでのみテストされています!

0
0 313
記事 Megumi Kakechi · 1月 30, 2022 1m read

これは、InterSystems FAQサイトの記事です。

TRY-CATCHで行うことが可能です。
以下のように行ってください。

 #dim ex As %Exception.AbstractException
 TRY {
    "<何かエラーを発生させる処理>"
  }
  CATCH ex {
     do ex.Log()
  }


^%ETNを使用する場合は、^%ETNを呼び出す際にBACKエントリ(BACK^%ETN)から呼び出してください。


あわせて、以下の関連記事も是非ご覧ください。

アプリケーションエラー(^ERRORS)をコマンドで取得する方法
【FAQ】アプリケーションから明示的にエラー詳細情報をログ出力する方法を教えてください。

0
1 342
記事 Tomoko Furuzono · 12月 26, 2021 1m read

これは、InterSystems FAQサイトの記事です。
$ZTIMESTAMPはUTC形式で日付時刻を返すため、ローカルのタイムゾーンに変更するためには以下システムオブジェクトを利用します。 

 $SYSTEM.Util.UTCtoLocalWithZTIMEZONE($ZTIMESTAMP)


上記出力結果は ddddd,sssss.fff の形式で出力されます。

    ddddd:$HOROLOGの日付と同じ形式
 sssss:現在の日付の午前 0 時からの経過秒数を示す整数
 fff :秒の小数部を示す可変桁数

※ $HOROLOGに似ていますが、$HOROLOGには秒の小数部分は含まれません。 

SAMPLES>write $SYSTEM.Util.UTCtoLocalWithZTIMEZONE($ZTIMESTAMP)," - ", $horolog,!
63895,64252.66 - 63895,64252
SAMPLES>write $ZDATETIME($SYSTEM.Util.UTCtoLocalWithZTIMEZONE($ZTIMESTAMP),8)
20151209 17:45:56
SAMPLES>write $ZDATETIME($ZTIMESTAMP,8)
20151209 08:46:05
SAMPLES>
0
0 313
記事 Toshihiko Minamoto · 11月 9, 2021 18m read

IRISインターオペラビリティのメッセージビューワで何かを変更できるとしたら、何を変更しますか?

Dashboard IRIS History Monitor」の記事を公開したところ、素晴らしいフィードバックやリクエストをいただきました。 中には、メッセージビューワの拡張に関するリクエストがありました。
 

まだプロジェクトを確認していない方は、ぜひご覧ください。絶対に見る価値がありますし、2019年の最高のInterSystems Open Exchange開発者およびアプリケーションの1つとしてブロンズ賞を受賞しました。

「新しい」メッセージビューワに含めようと思う機能についてのアイデアを書き留め始めましたが、これらのリソースをどのようにすれば素早く簡単に見せることができるのでしょうか。

まずは、 一般的に、相互運用性の本番環境をセットアップし、ドキュメントの指示のとおりに、ターゲットシステムにエクスポートしてデプロイすることから始めます。 これは私があまり好まないプロセスです。 特に何か悪いというわけではありませんが、 コードを使ってすべてを行う考えがあるためです。

誰かがこういったプロジェクトを実行するたびに、次のように開始することを期待しています。

$ docker-compose build

$ docker-compose up -d

いかがでしょうか!!!

たったこれだけのステップを思い浮かべながら、InterSystemsコミュニティを調べ始めると、いくつかのヒントが見つかりました。 ある投稿では、私が自問していた質問が挙げられていました。「ルーチンを使って本番環境を作成するにはどうすればよいのか。

その投稿の中で、@Eduard Lebedyuk が、コードを使って本番環境を作成する方法を次のように回答しています。

「本番クラスを自動的に作成するには、次を行う必要があります。

  1. テストプロダクション用の%Dictionary.ClassDefinitionオブジェクトを作成します。
  2. Ens.Config.Productionオブジェクトを作成します。
  3. %Dictionary.XDataDefinitionを作成します。
  4. (2) を (3) にシリアル化します。
  5. XData (3) を (1) に挿入します。
  6. (1) を保存してコンパイルします。」

@Jenny Amesのコメントにも、次のように書かれていました。

 「私たちがよくお勧めしているベストプラクティスは、逆方向に構築することです。 ビジネスオペレーションを先に構築してから、ビジネスプロセス、そしてビジネスサービスを構築していく方法です...」

というわけで、早速やってみましょう!

リクエスト、ビジネスオペレーション、およびビジネスサービス

クラスdiashenrique.messageviewer.util.InstallerProduction.clsは、名前から想像できるように、プロダクションのインストールを担当するクラスです。 インストーラのマニフェストは、そのクラスからClassMethod Installを呼び出します。

/// 拡張ビューワの表示機能にプロダクションをインストールするヘルパー

ClassMethod Install() As %Status

{

    Set sc = $$$OK

    Try {

        Set sc = $$$ADDSC(sc,..InstallProduction()) quit:$$$ISERR(sc)

        Set sc = $$$ADDSC(sc,..GenerateMessages()) quit:$$$ISERR(sc)

        Set sc = $$$ADDSC(sc,..GenerateUsingEnsDirector()) quit:$$$ISERR(sc)

    }

    Catch (err) {

        Set sc = $$$ADDSC(sc,err.AsStatus())

    }

    Return sc

}

クラスメソッドInstallProductionは、次を作成することで、プロダクションを作成するためのメインの構造をまとめます。

  • リクエスト
  • ビジネスオペレーション
  • ビジネスサービス
  • 相互運用性プロダクション

コードを使用して相互運用性プロダクションを作成しようと考えているため、完全なコーディングモードに移行して、リクエスト、ビジネスオペレーション、およびビジネスサービスの全クラスを作成しましょう。 これを行うには、いくつかのInterSystemsライブラリパッケージを広範に使用します。

  • %Dictionary.ClassDefinition
  • %Dictionary.PropertyDefinition
  • %Dictionary.XDataDefinition
  • %Dictionary.MethodDefinition
  • %Dictionary.ParameterDefinition

クラスメソッドInstallProductionは、次のコードを使用して、Ens.Requestを継承した2つのクラスを作成します。

Set sc = $$$ADDSC(sc,..CreateRequest("diashenrique.messageviewer.Message.SimpleRequest","Message")) quit:$$$ISERR(sc)

Set sc = $$$ADDSC(sc,..CreateRequest("diashenrique.messageviewer.Message.AnotherRequest","Something")) quit:$$$ISERR(sc)

ClassMethod CreateRequest(classname As %String, prop As %String) As %Status [ Private ]

{

    New $Namespace

    Set $Namespace = ..#NAMESPACE

    Set sc = $$$OK

    Try {

        Set class = ##class(%Dictionary.ClassDefinition).%New(classname)

        Set class.GeneratedBy = $ClassName()

        Set class.Super = "Ens.Request"

        Set class.ProcedureBlock = 1

        Set class.Inheritance = "left"

        Set sc = $$$ADDSC(sc,class.%Save())

        #; create adapter

        Set property = ##class(%Dictionary.PropertyDefinition).%New(classname)

        Set property.Name = prop

        Set property.Type = "%String"

        Set sc = $$$ADDSC(sc,property.%Save())

        Set sc = $$$ADDSC(sc,$System.OBJ.Compile(classname,"fck-dv"))

    }

    Catch (err) {

        Set sc = $$$ADDSC(sc,err.AsStatus())

    }

    Return sc

}

 

では、Ens.BusinessOperationを継承したビジネスオペレーションのクラスを作成しましょう。

Set sc = $$$ADDSC(sc,..CreateOperation()) quit:$$$ISERR(sc)

このクラスを作成するほかに、MessageMapとメソッドConsumeを作成します。

ClassMethod CreateOperation() As %Status [ Private ]
{
    New $Namespace
    Set $Namespace = ..#NAMESPACE
    Set sc = $$$OK
    Try {
        Set classname = "diashenrique.messageviewer.Operation.Consumer"
        Set class = ##class(%Dictionary.ClassDefinition).%New(classname)
        Set class.GeneratedBy = $ClassName()
        Set class.Super = "Ens.BusinessOperation"
        Set class.ProcedureBlock = 1
        Set class.Inheritance = "left"
 
        Set xdata = ##class(%Dictionary.XDataDefinition).%New()
        Set xdata.Name = "MessageMap"
        Set xdata.XMLNamespace = "http://www.intersystems.com/urlmap"
        Do xdata.Data.WriteLine("<MapItems>")
        Do xdata.Data.WriteLine("<MapItem MessageType=""diashenrique.messageviewer.Message.SimpleRequest"">")
        Do xdata.Data.WriteLine("<Method>Consume</Method>")
        Do xdata.Data.WriteLine("</MapItem>")
        Do xdata.Data.WriteLine("<MapItem MessageType=""diashenrique.messageviewer.Message.AnotherRequest"">")
        Do xdata.Data.WriteLine("<Method>Consume</Method>")
        Do xdata.Data.WriteLine("</MapItem>")
        Do xdata.Data.WriteLine("</MapItems>")      
        Do class.XDatas.Insert(xdata)
        Set sc = $$$ADDSC(sc,class.%Save())
 
        Set method = ##class(%Dictionary.MethodDefinition).%New(classname)
        Set method.Name = "Consume"
        Set method.ClassMethod = 0
        Set method.ReturnType = "%Status"
        Set method.FormalSpec = "input:diashenrique.messageviewer.Message.SimpleRequest,&output:Ens.Response"
        Set stream = ##class(%Stream.TmpCharacter).%New()
        Do stream.WriteLine("   set sc = $$$OK")
        Do stream.WriteLine("   $$$TRACE(input.Message)")
        Do stream.WriteLine("   return sc")
        Set method.Implementation = stream
        Set sc = $$$ADDSC(sc,method.%Save())
 
        Set sc = $$$ADDSC(sc,$System.OBJ.Compile(classname,"fck-dv"))
    }
    Catch (err) {
        Set sc = $$$ADDSC(sc,err.AsStatus())
    }
    Return sc
}

相互運用性プロダクションを作成する直前のステップでは、ビジネスサービスクラスを作成しましょう。

Set sc = $$$ADDSC(sc,..CreateRESTService()) quit:$$$ISERR(sc)

このクラスにはHttpリクエストを受信するためのUrlMapとRoutesがあります。

ClassMethod CreateRESTService() As %Status [ Private ]
{
    New $Namespace
    Set $Namespace = ..#NAMESPACE
    Set sc = $$$OK
    Try {
        Set classname = "diashenrique.messageviewer.Service.REST"
        Set class = ##class(%Dictionary.ClassDefinition).%New(classname)
        Set class.GeneratedBy = $ClassName()
        Set class.Super = "EnsLib.REST.Service, Ens.BusinessService"
        Set class.ProcedureBlock = 1
        Set class.Inheritance = "left"
 
        Set xdata = ##class(%Dictionary.XDataDefinition).%New()
        Set xdata.Name = "UrlMap"
        Set xdata.XMLNamespace = "http://www.intersystems.com/urlmap"
        Do xdata.Data.WriteLine("<Routes>")
        Do xdata.Data.WriteLine("<Route Url=""/send/message"" Method=""POST"" Call=""SendMessage""/>")
        Do xdata.Data.WriteLine("<Route Url=""/send/something"" Method=""POST"" Call=""SendSomething""/>")
        Do xdata.Data.WriteLine("</Routes>")
        Do class.XDatas.Insert(xdata)
        Set sc = $$$ADDSC(sc,class.%Save())
 
        #; create adapter
        Set adapter = ##class(%Dictionary.ParameterDefinition).%New(classname)
        Set class.GeneratedBy = $ClassName()
        Set adapter.Name = "ADAPTER"
        Set adapter.SequenceNumber = 1
        Set adapter.Default = "EnsLib.HTTP.InboundAdapter"
        Set sc = $$$ADDSC(sc,adapter.%Save())
 
        #; add prefix
        Set prefix = ##class(%Dictionary.ParameterDefinition).%New(classname)
        Set prefix.Name = "EnsServicePrefix"
        Set prefix.SequenceNumber = 2
        Set prefix.Default = "|demoiris"
        Set sc = $$$ADDSC(sc,prefix.%Save())
 
        Set method = ##class(%Dictionary.MethodDefinition).%New(classname)
        Set method.Name = "SendMessage"
        Set method.ClassMethod = 0
        Set method.ReturnType = "%Status"
        Set method.FormalSpec = "input:%Library.AbstractStream,&output:%Stream.Object"
        Set stream = ##class(%Stream.TmpCharacter).%New()
        Do stream.WriteLine("   set sc = $$$OK")
        Do stream.WriteLine("   set request = ##class(diashenrique.messageviewer.Message.SimpleRequest).%New()")
        Do stream.WriteLine("   set data = {}.%FromJSON(input)")
        Do stream.WriteLine("   set request.Message = data.Message")
        Do stream.WriteLine("   set sc = $$$ADDSC(sc,..SendRequestSync(""diashenrique.messageviewer.Operation.Consumer"",request,.response))")
        Do stream.WriteLine("   return sc")
        Set method.Implementation = stream
        Set sc = $$$ADDSC(sc,method.%Save())
 
        Set method = ##class(%Dictionary.MethodDefinition).%New(classname)
        Set method.Name = "SendSomething"
        Set method.ClassMethod = 0
        Set method.ReturnType = "%Status"
        Set method.FormalSpec = "input:%Library.AbstractStream,&output:%Stream.Object"
        Set stream = ##class(%Stream.TmpCharacter).%New()
        Do stream.WriteLine("   set sc = $$$OK")
        Do stream.WriteLine("   set request = ##class(diashenrique.messageviewer.Message.AnotherRequest).%New()")
        Do stream.WriteLine("   set data = {}.%FromJSON(input)")
        Do stream.WriteLine("   set request.Something = data.Something")
        Do stream.WriteLine("   set sc = $$$ADDSC(sc,..SendRequestSync(""diashenrique.messageviewer.Operation.Consumer"",request,.response))")
        Do stream.WriteLine("   return sc")
        Set method.Implementation = stream
        Set sc = $$$ADDSC(sc,method.%Save())
 
        Set sc = $$$ADDSC(sc,$System.OBJ.Compile(classname,"fck-dv"))
    }
    Catch (err) {
        Set sc = $$$ADDSC(sc,err.AsStatus())
    }
    Return sc
}

 

Visual Studioコードの使用

%Dictionaryパッケージを使用してクラスを作成するのは困難な場合があり、読みにくくもありますが、非常に便利です。 コードの可読性を良くしてアプローチをもう少しわかりやすくするために、Visual Studioコードを使用して新しいリクエスト、ビジネスサービス、およびビジネスオペレーションクラスを作成することにします。

  • diashenrique.messageviewer.Message.SimpleMessage.cls
  • diashenrique.messageviewer.Operation.ConsumeMessageClass.cls
  • diashenrique.messageviewer.Service.SendMessage.cls
    Class diashenrique.messageviewer.Message.SimpleMessage Extends Ens.Request [ Inheritance = left, ProcedureBlock ]
    {
     Property ClassMessage As %String; 
    }
     
    Class diashenrique.messageviewer.Operation.ConsumeMessageClass Extends Ens.BusinessOperation [ Inheritance = left, ProcedureBlock ]
    {
     Method Consume(input As diashenrique.messageviewer.Message.SimpleMessage, ByRef output As Ens.Response) As %Status
    { 
        Set sc = $$$OK
        $$$TRACE(pRequest.ClassMessage)
        Return sc
    }
    XData MessageMap [ XMLNamespace = "http://www.intersystems.com/urlmap" ]
    {
       
         
           Consume
         
       
     }
    }
    
    Class diashenrique.messageviewer.Service.SendMessage Extends Ens.BusinessService [ ProcedureBlock ]
    { 
    Method OnProcessInput(input As %Library.AbstractStream, ByRef output As %Stream.Object) As %Status
    {
        Set tSC = $$$OK
        // リクエストメッセージを作成
        Set request = ##class(diashenrique.messageviewer.Message.SimpleMessage).%New()
        // リクエストメッセージプロパティに値をセット
        Set request.ClassMessage = input
        // ビジネスプロセスに同期呼び出しを行い、レスポンスメッセージをレスポンスとして使用 
        Set tSC = ..SendRequestSync("diashenrique.messageviewer.Operation.ConsumeMessageClass",request,.output)
        Quit tSC
    }
    }

コードの可読性の観点では、大きな差があります! 

相互運用性プロダクションの作成

相互運用性プロダクションを仕上げましょう。 これを行うには、プロダクションクラスを作成してから、それをビジネスオペレーションとサービスクラスに関連付けます。

Set sc = $$$ADDSC(sc,..CreateProduction()) quit:$$$ISERR(sc)

ClassMethod CreateProduction(purge As %Boolean = 0) As %Status [ Private ]
{
    New $Namespace
    Set $Namespace = ..#NAMESPACE
    Set sc = $$$OK
    Try {
         #; create new production
        Set class = ##class(%Dictionary.ClassDefinition).%New(..#PRODUCTION)
        Set class.ProcedureBlock = 1
        Set class.Super = "Ens.Production"
        Set class.GeneratedBy = $ClassName()
        Set xdata = ##class(%Dictionary.XDataDefinition).%New()
        Set xdata.Name = "ProductionDefinition"
        Do xdata.Data.Write("<Production Name="""_..#PRODUCTION_""" LogGeneralTraceEvents=""true""></Production>")  
        Do class.XDatas.Insert(xdata)
        Set sc = $$$ADDSC(sc,class.%Save())
        Set sc = $$$ADDSC(sc,$System.OBJ.Compile(..#PRODUCTION,"fck-dv"))
        Set production = ##class(Ens.Config.Production).%OpenId(..#PRODUCTION)
        Set item = ##class(Ens.Config.Item).%New()
        Set item.ClassName = "diashenrique.messageviewer.Service.REST"
        Do production.Items.Insert(item)
        Set sc = $$$ADDSC(sc,production.%Save())
        Set item = ##class(Ens.Config.Item).%New()
        Set item.ClassName = "diashenrique.messageviewer.Operation.Consumer"
        Do production.Items.Insert(item)
        Set sc = $$$ADDSC(sc,production.%Save())    
        Set item = ##class(Ens.Config.Item).%New()
        Set item.ClassName = "diashenrique.messageviewer.Service.SendMessage"
        Do production.Items.Insert(item)
        Set sc = $$$ADDSC(sc,production.%Save())    
        Set item = ##class(Ens.Config.Item).%New()
        Set item.ClassName = "diashenrique.messageviewer.Operation.ConsumeMessageClass"
        Do production.Items.Insert(item)
        Set sc = $$$ADDSC(sc,production.%Save())    
    }
    Catch (err) {
        Set sc = $$$ADDSC(sc,err.AsStatus())
    }
    Return sc
}

プロダクションクラスをビジネスオペレーションとサービスクラスに関連付けるために、クラスEns.Config.Itemを使用します。 これは、クラスの作成に%Dictionaryパッケージを使用したのか、VS Code、Studio、またはAtelierを使用したかに関係なく使用できます。

いずれにしても、達成できました! コードを使用して相互運用性プロダクションを作成できました。

ただし、このコードの元の目的を忘れてはいけません。拡張メッセージビューワの機能を示すプロダクションとメッセージを作成するという目的です。 以降のクラスメソッドを使用して、両方のビジネスサービスを実行し、メッセージを生成します。 

%Net.HttpRequestを使用たメッセージの生成:

ClassMethod GenerateMessages() As %Status [ Private ]
{
    New $Namespace
    Set $Namespace = ..#NAMESPACE
    Set sc = $$$OK
    Try {
        Set action(0) = "/demoiris/send/message"
        Set action(1) = "/demoiris/send/something"
        For i=1:1:..#LIMIT {
            Set content = { }
            Set content.Message = "Hi, I'm just a random message named "_$Random(30000)
            Set content.Something = "Hi, I'm just a random something named "_$Random(30000)
            Set httprequest = ##class(%Net.HttpRequest).%New()
            Set httprequest.SSLCheckServerIdentity = 0
            Set httprequest.SSLConfiguration = ""
            Set httprequest.Https = 0
            Set httprequest.Server = "localhost"
            Set httprequest.Port = 9980
            Set serverUrl = action($Random(2))
            Do httprequest.EntityBody.Write(content.%ToJSON())
            Set sc = httprequest.Post(serverUrl) 
            Quit:$$$ISERR(sc)
        }
    }
    Catch (err) {
        Set sc = $$$ADDSC(sc,err.AsStatus())
    }
    Return sc
}

EnsDirectorを使用したメッセージの生成:

ClassMethod GenerateUsingEnsDirector() As %Status [ Private ]
{
    New $Namespace
    Set $Namespace = ..#NAMESPACE
    Set sc = $$$OK
    Try {
        For i=1:1:..#LIMIT {
            Set tSC = ##class(Ens.Director).CreateBusinessService("diashenrique.messageviewer.Service.SendMessage",.tService)
            Set message = "Message Generated By CreateBusinessService "_$Random(1000)
            Set tSC = tService.ProcessInput(message,.output)
            Quit:$$$ISERR(sc)
        }
    }
    Catch (err) {
        Set sc = $$$ADDSC(sc,err.AsStatus())
    }
    Return sc
}
 
}

コードは以上です。 完全なプロジェクトは、https://github.com/diashenrique/iris-message-viewerをご覧ください。

プロジェクトの実行

では、プロジェクトの実際の動作を確認しましょう。 まず、git cloneまたはgit pullで、任意のローカルディレクトリにリポジトリを作成します。

git clone https://github.com/diashenrique/iris-message-viewer.git

次に、このディレクトリでターミナルを開き、次を実行します。

docker-compose build

最後に、プロジェクトでIRISコンテナを実行します。

docker-compose up -d

さらに、http://localhost:52773/csp/sys/UtilHome.cspを使用して管理ポータルにアクセスします。 次の画像のように、相互運用性のネームスペースMSGVIEWERが表示されます。

そしてこれが、私たちの愛らしいプロダクションです。2つのビジネスサービスと2つのビジネスオペレーションがあります。

非常にたくさんのメッセージがあります。

カスタムメッセージビューワですべてが稼働しているので、その機能を見てみましょう。

拡張メッセージビューワ

相互運用性プロダクションに有効になっているネームスペースのみが表示されることに注意してください。

http://localhost:52773/csp/msgviewer/messageviewer.csp

Interoperability Message Viewer
 

拡張メッセージビューワには、さまざまなフィルタの作成、nレベルへの列のグループ化、Excelへのエクスポートなどを行える機能と柔軟性が備わっています。
Interoperability Message Viewer
さまざまなフィルタを使用して、必要な結果を得ることができます。 また、Shiftキーを押しながら列のヘッダーをクリックすると、複数の並べ替えを使用することも可能です。 データグリッドをExcelにエクスポートすることもできるのです!

さらに、フィルタビルダーオプションを使用して、複雑なフィルタを作成することができます。

使用できる任意の列に対してデータをグループ化し、必要なnレベルを使用して情報をまとめることができます。 デフォルトでは、このグループはDate Created(作成日)フィールドを使用して作成されます。
データのグループ化

また、列を選択できる機能があります。 次のページには、Ens.MessageHeaderのすべての列があります。デフォルトの列のみが初期ビューに表示されていますが、 「Column Chooser」(列選択)ボタンを使って、ほかの列を選択することができます。

列セレクター

すべてのグループはワンクリックで折りたたみと展開が可能です。

SessionId(セッションID)フィールドの情報には、ビジュアルトレース機能へのリンクがあります。

ビジュアルトレース

必要に応じて、メッセージを再送することができます。 必要なメッセージを選択し、Resend をクリックするだけで再送信は完了です。 この機能には、次のクラスメソッドが使用されています。

##class(Ens.MessageHeader).ResendDuplicatedMessage(id)

最後に、前述のように、データグリッドをExcelにエクスポートすることができます。

Excelの結果には、キャッシュサーバーページ(CSP)に定義されているものと同じフォーマット、コンテンツ、およびグループが表示されます。

追伸: この問題への取り組みで大いに助けてくれた@Renan.Lourencoに、特に深くお礼申し上げます。

0
0 276