クリアフィルター
記事
Hiroshi Sato · 2021年1月27日
これは InterSystems FAQ サイトの記事です。
クラス定義のプロパティの表示順は、スタジオのプロパティウィザードを利用して登録した場合は、末尾に追記されます。
また、エディタ上の任意の場所でプロパティ定義文を記述する場合は、その場所に追記され、クラス定義が登録されます。
つまり、定義者が記述した順番に登録されます。
(スタジオが並び換えを行ったりはしません。)
作成したクラス定義が、PersistentやSerialのようにデータベースに格納する属性を持ったクラス定義である場合、”初回のコンパイル”で クラス定義に対応するグローバル変数の定義情報=ストレージ定義を作成します。
初回コンパイル以降に、プロパティ定義の追加が行われれば、そのプロパティに対応するグローバル変数のスロット番号を、末尾に追加し、ストレージ定義を更新します。
以下の例は、クラス定義に対応するストレージ定義の例です。
(初回コンパイル時の状態)
Class Sample.Person Extends %Persistent{ Property Name As %String; /// 誕生日Property DOB As %Date; <storage name="Default"><data name="PersonDefaultData"><value name="1"><value>%%CLASSNAME</value></value> <value name="2"><value>Name</value></value> <value name="3"><value>DOB</value></value></data> <datalocation>^Sample.PersonD</datalocation><defaultdata>PersonDefaultData</defaultdata><extentsize>100000</extentsize><idlocation>^Sample.PersonD</idlocation><indexlocation>^Sample.PersonI</indexlocation><streamlocation>^Sample.PersonS</streamlocation><type>%Library.CacheStorage</type></storage> }
<storage name="Default">から</storage>までの表示が、クラス定義の初回コンパイルで作成されるストレージ定義情報です。
作成したSample.Personクラスの格納先グローバル変数は、ストレージ定義の<datalocation> <indexlocation> <streamlocatoin> を参照するとわかります。
また、各プロパティ定義が、指定グローバル変数のどこに格納されるかは、<value name="2"> と <value name="3">を参照するとわかります。<value name="2"><value>Name</value>%lt;/value> <value name="3"><value>DOB</value></value>
つまり、Nameプロパティは、^Sample.PersonDの $ListBuild()構造の2番目に格納され、DOBは3番目に格納されることがわかります。
ここで、Addressプロパティを、クラス定義の表示上、一番上に追加します。
Class Sample.Person Extends %Persistent{ Property Address As %String; Property Name As %String; /// 誕生日Property DOB As %Date; <storage name="Default"><data name="PersonDefaultData"><value name="1"><value>%%CLASSNAME</value></value> <value name="2"><value>Name</value></value> <value name="3"><value>DOB</value></value> <value name="4"><value>Address</value></value></data> <datalocation>^Sample.PersonD</datalocation><defaultdata>PersonDefaultData</defaultdata><extentsize>100000</extentsize><idlocation>^Sample.PersonD</idlocation><indexlocation>^Sample.PersonI</indexlocation><streamlocation>^Sample.PersonS</streamlocation><type>%Library.CacheStorage</type> }
ストレージ定義情報を参照すると、クラス定義の表示上1番上に登録したAddressプロパティは、
<value name="4"><value>Address</value></value>
$ListBuildの4番目に格納される定義として追加されています。
つまり、ストレージ定義は、クラス定義の表示上、どこに追記されても、後から追加されたプロパティについては、格納位置として、一番最後に追加していく仕組みがわかります。
ということで、クラス定義上の表示順と、ストレージ定義の格納順は必ずしも一致しない事があります。
なお、ストレージの格納順序を変更することはできます。
以下の例は、AddressとNameの格納順を、
≪現在≫ Address → 4番目 Name → 2番目≪変更後≫ Address → 2番目 Name → 4番目に変更した状態のストレージ定義例です。(一部抜粋)
<data name="PersonDefaultData"><value name="1"><value>%%CLASSNAME</value></value> <value name="4"><value>Name</value></value> <value name="3"><value>DOB</value></value> <value name="2"><value>Address</value></value></data>
ご覧いただいた通り、AddressプロパティとNameプロパティのスロット番号を入れ替えるだけで、ストレージ定義の格納順の変更が行えます。
ただ、ストレージ定義情報の順番のみが入れ替わるだけであり、既存グローバル変数のデータが入れ替わる事はありません。
この点、ご注意ください。(修正したストレージ定義に合わせて、グローバル変数を入れ替えたい場合は、手動でグローバル変数の中身を変更する必要があります。)
記事
Megumi Kakechi · 2023年8月31日
IRISをアップグレードすると、SQLクエリオプティマイザの機能向上により、旧バージョンとは異なるクエリプランによるクエリ実行コード(クエリキャッシュ)が作成される場合があります。ほとんどの場合はパフォーマンスが向上するのですが、稀にパフォーマンスが低下するケースもあります。
・アップグレードによりオプティマイザが改善しているとはいえ、中には遅くなるクエリがあるのではないか?・予期しないSQLの問題が起きるのではないか?・アップグレード後に全てのクエリパターンをテストするには時間と労力がかかりすぎる
このように、機能向上よりも安定性を優先して「今までのプランのまま実行したい」というご要望もあることでしょう。こちらの記事では、そのようなお客様への解決策をご案内します。
解決策 = 凍結プラン(Frozen Plan)を使用する:
Cache2016.2以降(IRISはすべてのバージョン)で、クエリプランの凍結機能 が実装されました。この機能により、IRISを新しいバージョンにアップグレードする際、既存のクエリプランは自動的に凍結されます。これにより、アップグレードによる既存のクエリのパフォーマンスの低下の可能性を防げます(古いバージョンと同じプランが使用されるため)。新しいクエリについてはもちろん、アップグレード後のクエリオプティマイザによるプランが使用されます。
(凍結プランの)良い点:クエリパフォーマンスが予測できる(古いバージョンと変わらない)悪い点:クエリパフォーマンスは向上しない
管理ポータルでクエリプランを確認すると、凍結されたプランは「Frozen Plan」と表示されます。
クエリパフォーマンスの検証方法(新/旧プランのパフォーマンス比較方法):
では、アップグレード後、重要なクエリのパフォーマンスを検証したいときはどうすればよいのでしょうか?以下に、凍結プラン(旧プラン)と新しいプランのどちらを使用すべきかについて検証する手順をご紹介します。
《手順》
1.対象のプランが「Frozen Plan」である場合、アップグレード前に作成された、最適化されたクエリプランが使用されています(旧プラン使用)。
2.%NOFPLAN キーワードをクエリに追加して実行すると、アップグレード後のクエリオプティマイザを使用して、クエリプランが最適化されます(新プラン使用)。
例:
SELECT %NOFPLAN Name , Age FROM Sample.Person .....
3.1 と 2 のパフォーマンスを比較します。
1のパフォーマンスが良い場合: ⇒ アップグレードによりクエリプランの効率が低下するため、そのまま凍結プランを使用します。
2のパフォーマンスが良い場合: ⇒ アップグレード後のクエリプランの効率が向上しているため、クエリプランの凍結を解除します。 解除後、ネームスペース内のクエリキャッシュの削除を行います。
クエリプランの凍結解除の方法:
コマンドで行う場合は、ターミナルより以下のように実行します。
// すべてのプランを凍結解除する場合:
set st=$SYSTEM.SQL.Statement.UnfreezeAll()
// 個別に凍結解除する場合:
set st=$SYSTEM.SQL.Statement.UnfreezeSchema("Sample")
または
set st=$SYSTEM.SQL.Statement.UnfreezeRelation("Sample.Person")
または
set st=$SYSTEM.SQL.Statement.UnfreezeStatement("3DgIqc72NS+Np6nybddb719NKb8=") // Statement単位はHash指定
/// Hashは、以下のクエリで確認できます
/// SELECT Hash, Statement FROM INFORMATION_SCHEMA.STATEMENTS WHERE Frozen=1 OR Frozen=2
// 解除後、クエリキャッシュの削除を行う
do $SYSTEM.SQL.PurgeAllNamespaces() // ネームスペース内の全キャッシュ
または
do $SYSTEM.SQL.PurgeForTable("Sample.Person") // テーブル指定
※既定では、Frozen/Explicit または Frozen/Upgrade とマークされたすべてのクエリプランの凍結を解除します。 詳細はクラスリファレンスを参照してください。
管理ポータルで解除する場合は、[システムエクスプローラ] > [SQL] よりSQLステートメントタブから該当のSQLをクリックします。
[プランを凍結解除] をクリックします。 ※解除後クエリキャッシュを削除します
詳細は以下のドキュメントをご覧ください。InterSystems SQL 最適化ガイド > 凍結プランの構成
記事
Toshihiko Minamoto · 2022年12月16日
母体リスクは、医学界でよく知られているいくつかのパラメーターから測定できます。 この測定により、医学界とコンピューター化されたシステム(特に AI)を支援すべく、科学者である Yasir Hussein Shakir は、母体リスクの検出/予測における ML アルゴリズムをトレーニングするための非常に便利なデータセットを公開しました。 このデータセットは、ML の最大級のデータリポジトリとして最もよく知られている Kaggle に公開されています。
https://www.kaggle.com/code/yasserhessein/classification-maternal-health-5-algorithms-ml
## データセットについて
妊娠中と出産後の母体のヘルスケアに関する情報の不足により、妊娠中の女性の多くは、妊娠に関わる問題で死亡しています。 これは、農村地域や新興国の下位中流家庭の間でより一般的に起きている問題です。 妊娠中は、状態を絶えず観察することで、胎児の適切な成長と安全な出産を保証する必要があります(出典: https://www.kaggle.com/code/yasserhessein/classification-maternal-health-5-algorithms-ml)。
データは、IoT ベースのリスク監視システムを通じて、様々な病院、地域の診療所、妊産婦ヘルスケアから収集されています。
* Age: 女性が妊娠したときの年齢
* SystolicBP: 最高血圧(mmHg)。妊娠中に重要な属性の 1 つ。
* DiastolicBP: 最低血圧(mmHg)。妊娠中に重要な属性の 1 つ。
* BS: モル濃度(mmol/L)による血糖値。
* HeartRate: 1 分あたりの通常の安静時心拍数。
* Risk Level: 前の属性を考慮した妊娠中の予測リスク強度レベル。
## Kaggle から母体リスクデータを取得する
Kaggle の母体リスクデータは、Health-Dataset アプリケーション(https://openexchange.intersystems.com/package/Health-Dataset)を使って IRIS テーブルに読み込めます。 これを行うには、module.xml プロジェクトから依存関係(Health Dataset 用の ModuleReference)を設定します。
Health Dataset アプリケーションリファレンスを含む Module.xml
<?xml version="1.0" encoding="UTF-8"?>
<Export
generator="Cache" version="25">
<Document name="predict-diseases.ZPM">
<Module>
<Name>predict-diseases</Name>
<Version>1.0.0</Version>
<Packaging>module</Packaging>
<SourcesRoot<src/iris</SourcesRoot>
<Resource Name="dc.predict.disease.PKG"/>
<Dependencies>
<ModuleReference>
<Name>swagger-ui</Name>
<Version>1.*.*</Version>
</ModuleReference>
<ModuleReference>
<Name>dataset-health</Name>
<Version>*</Version>
</ModuleReference>
</Dependencies>
<CSPApplication
Url="/predict-diseases"
DispatchClass="dc.predict.disease.PredictDiseaseRESTApp"
MatchRoles=":{$dbrole}"
PasswordAuthEnabled="1"
UnauthenticatedEnabled="1"
Recurse="1"
UseCookies="2"
CookiePath="/predict-diseases"
/>
<CSPApplication
CookiePath="/disease-predictor/"
DefaultTimeout="900"
SourcePath="/src/csp"
DeployPath="${cspdir}/csp/${namespace}/"
MatchRoles=":{$dbrole}"
PasswordAuthEnabled="0"
Recurse="1"
ServeFiles="1"
ServeFilesTimeout="3600"
UnauthenticatedEnabled="1"
Url="/disease-predictor"
UseSessionCookie="2"
/>
</Module>
</Document>
</Export>
## 母体リスクを予測するための Web フロントエンドとバックエンドのアプリケーション
Open Exchange アプリのリンク(https://openexchange.intersystems.com/package/Disease-Predictor)に移動し、以下の手順に従います。
* リポジトリを任意のローカルディレクトリに Clone/git pull します。
$ git clone https://github.com/yurimarx/predict-diseases.git
* このディレクトリで Docker ターミナルを開き、以下を実行します。
$ docker-compose build
* IRIS コンテナを実行します。
$ docker-compose up -d
* AI モデルをトレーニングするための Execute Query into Management Portal(http://localhost:52773/csp/sys/exp/%25CSP.UI.Portal.SQL.Home.zen?$NAMESPACE=USER)に移動します。
* トレーニングに使用するビューを作成します。
CREATE VIEW MaternalRiskTrain AS SELECT BS, BodyTemp, DiastolicBP, HeartRate, RiskLevel, SystolicBP, age FROM dc_data_health.MaternalHealthRisk
* ビューを使用して AI モデルを作成します。
CREATE MODEL MaternalRiskModel PREDICTING (RiskLevel) FROM MaternalRiskTrain
* モデルをトレーニングします。
TRAIN MODEL MaternalRiskModel
* [http://localhost:52773/disease-predictor/index.html](http://localhost:52773/disease-predictor/index.html) に移動し、Disease Predictor フロントエンドを使用して、以下のように疾患を予測します。

## 背後の処理
### 母体リスク疾患を予測するためのバックエンドのクラスメソッド
InterSystems IRIS では、前に作成されたモデルを使って、SELECT の実行により予測することができます。
母体リスク疾患を予測するためのバックエンドのクラスメソッド
/// 母体リスクの予測
ClassMethod PredictMaternalRisk() As %Status
{
Try {
Set data = {}.%FromJSON(%request.Content)
Set %response.Status = 200
Set %response.Headers("Access-Control-Allow-Origin")="*"
Set qry = "SELECT PREDICT(MaternalRiskModel) As PredictedMaternalRisk, "
_"age, BS, BodyTemp, DiastolicBP, HeartRate, SystolicBP "
_"FROM (SELECT "_data.BS_" AS BS, "
_data.BodyTemp_" As BodyTemp, "
_data.DiastolicBP_" AS DiastolicBP, "
_data.HeartRate_" AS HeartRate, "
_data.SystolicBP_" As SystolicBP, "
_data.Age_" AS age)"
Set tStatement = ##class(%SQL.Statement).%New()
Set qStatus = tStatement.%Prepare(qry)
If qStatus'=1 {WRITE "%Prepare failed:" DO $System.Status.DisplayError(qStatus) QUIT}
Set rset = tStatement.%Execute()
Do rset.%Next()
Set Response = {}
Set Response.PredictedMaternalRisk = rset.PredictedMaternalRisk
Set Response.Age = rset.Age
Set Response.SystolicBP = rset.SystolicBP
Set Response.DiastolicBP = rset.DiastolicBP
Set Response.BS = rset.BS
Set Response.BodyTemp = rset.BodyTemp
Set Response.HeartRate = rset.HeartRate
Write Response.%ToJSON()
Return 1
} Catch err {
write !, "Error name: ", ?20, err.Name,
!, "Error code: ", ?20, err.Code,
!, "Error location: ", ?20, err.Location,
!, "Additional data: ", ?20, err.Data, !
Return
}
}
これで、どの Web アプリケーションもこの予測を使用して、結果を表示できるようになりました。 predict-diseases アプリケーションのソースコードは、frontend フォルダをご覧ください。
記事
Megumi Kakechi · 2022年12月1日
複数のインスタンス間でライセンスを共有する際に、ライセンスサーバを立ててライセンスの使用量を管理します。IRISライセンスサーバには、ライセンスの使用量管理に加えて便利な新しい機能が追加されました。
-- ライセンスサーバでできること --1. マルチサーバライセンス(共有ライセンス)の統合管理(使用量の管理)2. 各インスタンスへのライセンスキーの配布・管理【New】
1 は従来からのライセンスサーバの機能で、関連記事 にて機能紹介をしております。
2 はIRIS以降使用できるようになった新しい機能です。
複数のインスタンスを構成している場合、中央管理しているディレクトリに格納されているライセンスキーファイル(*.key)を各インスタンスに配布・管理するようにライセンスサーバを構成することができます。その場合、個々のインスタンスにライセンスキー(iris.key)を配置する必要はありません。ライセンスキーはユニークな LicenseID (※1, ※2) によって識別され、各インスタンスの起動時にロード&有効化されます。
※1 ライセンスキーをテキストエディタで開くと、[ConfigFile] セクションにて設定されている LicenseID を確認できます。※2 ライセンスキーに記載されたLicenseIDの値を各インスタンスで事前に設定します(方法詳細は後述)。 ライセンス反映時、ライセンスキーに記載されたLicenseIDと各インスタンスの設定値を照らし合わせ、一致した場合にキーが各インスタンスにロードされます。
【設定方法】1.ライセンスサーバを構成するインスタンスで KeyDirectory プロパティを設定します。
KeyDirectory で指定したディレクトリ内に、すべての ライセンスキーを配置します。 複数のライセンスキーを配置する場合は、LicenseID で区別するようにします(同じ LicenseID のキーは複数配置できません)。 KeyDirectoryを設定している場合、起動時にそのインスタンスで見つかった有効な *.key ファイルをすべて読み取り、ライセンスサーバに送信します。 ライセンスサーバの設定は、管理ポータルで行います。
管理ポータル: [システム管理] > [ライセンス] > [ライセンス・サーバ]
2.各インスタンスで LicenseID プロパティ(キーファイルの新しいプロパティ)を 設定します。
各インスタンスは LicenseID プロパティを使用して、起動時にライセンスサーバからライセンスキーを要求することができます。 LicenseID が設定されていると、インスタンス起動時に指定したLicenseID のライセンスキーがライセンスサーバーよりロードされて有効化されます。 インスタンスは定期的に、期限切れのキーまたはアップグレードされた新しいキーがあるかどうかを確認します。 LicenseID の設定は、管理ポータルで行います。
管理ポータル: [システム管理] > [構成] > [追加設定] > [開始]:LicenseID
もしくは、構成ファイル(iris.cpf)で直接設定することも可能です 。CPF の [Startup] セクションの LicenseID を設定します。
ライセンスサーバはディレクトリからキーファイルを手動で再ロードしてライセンスを更新することも可能です(※3)。
※3 %SYS ネームスペースで do ReloadKeys^%SYS.LICENSE を実行します。 ライセンスの種類によっては、各インスタンスでインスタンスの再起動が求められる場合があります。
ライセンスサーバーを起動するインスタンスは、LicenseIDが異なる複数のキーをロードすることができます。ライセンスキーは、一意の LicenseID により各インスタンス上で識別されることになります。
コンテナを使用している場合は、すべてのコンテナライセンスを外部ディレクトリ(KeyDirectory プロパティで設定)に配置することもできます。そうすることで、最終的にコンテナ (および /mgr) 内にライセンスファイルを置かないようにすることが可能となります。
詳細は以下のドキュメントをご覧ください。InterSystems IRIS ライセンスの管理
【関連】複数インスタンスでライセンスを共有する場合に必要な設定
記事
Toshihiko Minamoto · 2022年7月26日
 [Jupyter Notebook](https://jupyter.org/) は、多数の異なるマークアップ言語とプログラミング言語でコードを実行できるセルで構成された対話型環境です。
Jupyter はこれを実現するために適切なカーネルに接続しなければなりませんが、 ObjectScript カーネルがなかったため、それを作成することにしました。
[こちら](objectscriptkernel.eastus.cloudapp.azure.com)から試すことができます。
結果を少し覗いてみましょう。

## Jupyter カーネルの基礎
[Jupyter カーネル](https://jupyter-client.readthedocs.io/en/stable/kernels.html)はいくつかの方法で作成できます。 ここでは、Python ラッパーカーネルを作成することにしましょう。
`ipykernel.kernelbase.Kernel` のサブクラスを作成して、特定の言語で実行されるコードを受け取る `do_execute` メソッドを実装する必要があります。
つまり、ある ObjectScript コードを取得して、何らかの方法で実行し、ノートブックにその結果を返すという概念です。
でも、実際にはどうすればよいのでしょうか。 では、その方法をさらに噛み砕いて説明しましょう。
## ObjectScript コードを IRIS に送る
まず初めに、コードを IRIS に送る必要があります。 ここで使用するのが、[Python 用の IRIS Native API](https://irisdocs.intersystems.com/irislatest/csp/docbook/Doc.View.cls?KEY=PAGE_PYTHON_NATIVE) です。
ここでは、`irisnative` パッケージをインポートして、接続を確立するだけです。
```
def get_iris_object():
# InterSystems IRIS への接続を作成する
connection = irisnative.createConnection('iris', 51773, 'IRISAPP', '_SYSTEM', 'SYS')
# iris オブジェクトを作成する
return irisnative.createIris(connection)
```
その後で、この接続を使用して、IRIS データベースに格納されているクラスを呼び出すことができます。
```
def execute_code(self, code):
class_name = "JupyterKernel.CodeExecutor"
return self.iris.classMethodValue(class_name, "CodeResult", code)
```
`CodeExecutor` クラスと `CodeResult` メソッドは何に使用されているのでしょうか。
ではそれを見てみましょう。
## ObjectScript コードを実行する
このクラスの目的は、1 行の ObjectScript コードを実行して、実行の結果を含む JSON オブジェクトを返すことです。 コードを `CodeResult` の `vstrCommand` 変数に渡します。
まず、IO を現在のルーチンにリダイレクトします。その後、渡されたコードを [XECUTE](https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=RCOS_cxecute) コマンドで実行し、IO を元の場所にリダイレクトして、結果を返します。
```
Include %sySystem
Class JupyterKernel.CodeExecutor
{
ClassMethod CodeResult(vstrCommand As %String) As %String [ ProcedureBlock = 0 ]
{
set tOldIORedirected = ##class(%Device).ReDirectIO()
set tOldMnemonic = ##class(%Device).GetMnemonicRoutine()
set tOldIO = $io
try {
set str=""
set status = 1
//IO を現在のルーチンにリダイレクトする。以下に定義するラベルを利用します
use $io::("^"_$ZNAME)
//リダイレクトを有効にする
do ##class(%Device).ReDirectIO(1)
XECUTE (vstrCommand)
} catch ex {
set str = ex.DisplayString()
set status = 0
}
//元のリダイレクト/ニーモニックルーチン設定に戻す
if (tOldMnemonic '= "") {
use tOldIO::("^"_tOldMnemonic)
} else {
use tOldIO
}
do ##class(%Device).ReDirectIO(tOldIORedirected)
quit {"status":(status), "out":(str)}.%ToJSON()
rchr(c)
quit
rstr(sz,to)
quit
wchr(s)
do output($char(s))
quit
wff()
do output($char(12))
quit
wnl()
do output($char(13,10))
quit
wstr(s)
do output(s)
quit
wtab(s)
do output($char(9))
quit
output(s)
set str = str _ s
quit
}
}
```
## 結果を表示する
ObjectScript コードを実行しましたが、次はどうすればよいでしょうか。 その結果を表示する必要があります。
例外がなければ、行単位で結果を表示するだけで済みます。
が、渡されたコードで例外が発生したのであれば、実行を停止し、失敗した行番号、行そのもの、そして発生した例外を表示しなければなりません。

## アプリを起動する
このカーネルを自分で試してみましょう。以下のようにして試すことができます。
## 前提条件
[git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) と [Docker](https://www.docker.com/products/docker-desktop) がインストール済みであることを確認してください。
リポジトリを、以下のようにローカルディレクトリに Clone/git pull します。
```
$ git clone https://github.com/Vekkby/objectsriptkernel.git
```
このディレクトリでターミナルを開き、以下を実行します。
```
$ docker-compose up -d --build
```
## 操作方法
ブラウザから以下を使用してノートブックサーバーにアクセスできます。
```
localhost:8888
```
'work' ディレクトリ内に 'hello.ipynb' というサンプルノートブックがあります。 
### 投票をお願いします
このアプリは、IRIS Native API コンテストに参加しています。 こちら からこのアプリに投票してください。
お知らせ
Toshihiko Minamoto · 2024年11月20日
ほとんどの方が @Daniel.Tamajon の CachéQuality プロジェクトをご存知かと思います。 まだご存知でない方のために説明すると、InterSystems 製品用に記述されたコードの静的構文アナライザーです。 コード内の様々な種類の問題や潜在的なバグがお客様のプロダクション環境で見つかる前に、それらを検出して解決するのに役立てられます。 つまり、CachéQuality を使用することで、より優れた製品を提供できるようになります。 ObjectScript コードのチェックに使用されるルールの完全なリストは、こちらをご覧ください。
これは Studio ですでに提供されているものですが、 VSCode でも使用できるようになりました。
インストール
VSCode の拡張機能セクションで、「cachequality」を検索し、SonarLint for CachéQuality をインストールします。より優れたエクスペリエンスを得るには、先に VSCode for ObjectScript をインストールしてください。 いずれの拡張機能も、「objectscript」の検索で見つかります。
この拡張機能をインストールした時点から、構成を行わずに使用し始めることができ、クラスを開くと、検出されるすべての問題が下線表示されるようになります。
下線表示にマウスポインターを合わせると、詳細を表示できます。
また、直接 VSCode で問題の詳細を開くこともできます。
VSCode には問題ビューが備わっており、Cmd+Shift+M ホットキーで有効にできます。このビューには、開いているソースファイルで検出された問題のリストが表示されます。 CacheQuality が検出した問題のリストを確認し、実際の場所に移動できます。
修正された行は保存後に再チェックされます
ただし、制限があります。 ルールのリストと、ルールに関連する他のいくつかのパラメーターを制御できません。 拡張機能は、デフォルト値による事前構成済みで提供されます。 さらに制御が必要な場合は、コネクテッドモードを使用できます。
コネクテッドモード
この拡張機能には、コネクテッドというモードも備わっています。 このモードでは、SonarQube サーバーをインストール済みの CacheQuality プラグインに接続できます。
SonarQube を使って、ルールのリストをカスタマイズできます。 ルールのパラメーターを無効化または有効化し、変更できます。 たとえば、`To method has too many lines` ルールはデフォルトで 50 行以降にトリガーされますが、この数値を変更することができます。
SonarQube Server がある場合は、まず、プロジェクト全体を解析する必要があります。 これには様々な方法があり、詳細は SonarQube ドキュメントに記載されています。 また、VSCode からプロジェクトにアクセスするにはトークンが必要です。
VSCode 設定の変更
"sonarlint.connectedMode.project": {
"projectKey": "Samples",
"serverId": "local"
},
"sonarlint.connectedMode.servers": [
{
"serverId": "local",
"serverUrl": "http://localhost:9000",
"token": "65b19eb2ef04cd81a033c89820acf65d1f349c4f"
}
]
projectKey は SonarQube に定義されているもので、サーバーセクションに定義されたリストの serverId である必要があります。
設定を保存し、SonarQube 側に変更を保存したら、バインディングを更新して VSCode を最新状態にします。
設定が正しく更新されたら、以下の通知が表示されます。
プロジェクトに使用されているルールのリストをカスタマイズする場合は、まず、ビルトインの品質プロファイル Caché Quality をコピーする必要があります。
そして、プロジェクトに新しいプロファイルを有効化します。
検出された一部の問題は SonarQube で解決でき、VSCode 拡張機能はそれを認識するため、再度表示されることはありません。
たとえば、このような問題があります。
呼び出されないメソッドの問題を誤検出として解決し、変数の問題を修正しないようにしましょう。
ファイルを開き直すと、新しい情報でもう一度解析されます。 %OnNew メソッドには未解決の問題が残っていません。
問題とフィードバック
CacheQuality プロジェクト自体はオープンソースではありませんが、VSCode の拡張機能はオープンソースです。 問題やフィードバックはそちらにお送りください。
記事
Megumi Kakechi · 2025年7月22日
これは InterSystems FAQ サイトの記事です。「定期的にプロセスを監視し、あるイベントが発生したときにのみ処理を実行したい」ような場合に使用できる、便利な機能をご紹介します。
もちろん、Forループを行う常駐プロセスを作成してその中で Hang XX しながらIf文にてイベントを検知したり、タスクスケジュールでルーチンを定期実行してその中でIf文にてイベントを検知して処理することも可能です。
今回ご紹介する、%SYSTEM.Event クラスを使用することで、よりシンプルに処理を作成することが可能となります。
【こんな時に便利】・テーブルやグローバルに、あるデータが全て格納されたら処理を行いたい・あるエラーを検知したときにのみ、^SystemCheck情報を取得したい・処理が必要なものがデータベースに入ったら順番に処理を行いたい(pythonだとQueueモジュールのような感じ)
【使用方法】
準備(任意のプロセス)
do $SYSTEM.Event.Create("test")これで、testというイベントがシステムワイドで作成されます。
パターンA=単純な常駐プロセス
(1) 待機プロセス側do $SYSTEM.Event.Wait("test")このコマンドの瞬間、このプロセスは待ち状態になります。 (2) 起こす側do $SYSTEM.Event.Signal("test")これで、指定イベントで待機しているプロセスの待ち状態が解除されます。
パターンB=メッセージ付き常駐プロセス
(1) 待機プロセス側set msg=$SYSTEM.Event.WaitMsg("test")このコマンドの瞬間、このプロセスはウェイクアップイベントを待機しながらスリープ状態に入ります。、他プロセスからのメッセージを待ちます。 (2) 起こす側do $SYSTEM.Event.Signal("test",msg)指定イベントで待機しているプロセスにメッセージ(msg)を送り待ち状態を解除します。リクエストを処理する常駐プロセスにて、SYSTEM.Event.Wait() または$SYSTEM.Event.WaitMsg() でリクエストを待ちます。リクエストを行うプロセスは、$SYSTEM.Event.Signal() で常駐プロセスに通知します。 以下は、パターンAでリクエストを処理する常駐プロセスのサンプルです。10秒(timeout)ごとに監視し、$SYSTEM.Event.Signal() があれば、^SystemCheck 情報を収集します。^Zevent("STOP") グローバルがセットされたら、監視を終了します。
set timeout=10
set requestnum=0
set status=$SYSTEM.Event.Create("test")
while '$g(^Zevent("STOP")) {
set st=$SYSTEM.Event.Wait("test",timeout)
if st=1 {
// timeout内に通知(Signal)があれば、 SystemCheck情報収集
set Status =$$INT^SystemCheck()
write "Request done",! // write 出力は debug 用(本番では消してOK)
}
if st=0 write "Timeout",! // write 出力は debug 用
}
Quit
※sample.macで %SYSネームスペースに保存するとします
【実行例】
端末A 常駐プロセス%SYS>do ^sample
端末B リクエストプロセス%SYS>do $SYSTEM.Event.Signal("test")※例えば、messages.logファイルを読み込み、「○○エラー」のような該当するエラーメッセージがあればこのコマンドを実行します。 このコマンドを実行することで、^SystemCheck情報が収集されます。
停止%SYS>set ^Zevent("STOP")=1※<IRISインストールディレクトリ>\mgr 以下に <CustomerName>yyyy….html // ^SystemCheck情報のファイルが出力されているようであれば、情報収集を行ったことになります。set ^Zevent("STOP")=1 を実行して、常駐プロセスを終了します。このコマンドを実行しない限り、常駐プロセスは繰り返し監視をし続けます。
以下は、パターンBでリクエストを処理する常駐プロセスのサンプルです。
set timeout=10
set requestnum=0
set status=$SYSTEM.Event.Create("test")
while '$g(^Zevent("STOP")) {
set msg=$SYSTEM.Event.WaitMsg("test",timeout)
if $li(msg,1)=1 set ^Zevent("REQUEST",$i(requestnum),$ZDT($H))=$li(msg,2) write "Request done",!
if $li(msg,1)=0 write "Timeout",!
}
Quit
※sample2.macで WORKネームスペースに保存した場合【実行例2】
端末A 常駐プロセスWORK>Do ^sample2端末B、端末C リクエストプロセスWORK>do $SYSTEM.Event.Signal("test","Request From "_$J_" "_$ZDT($H))※複数のプロセスが通知した場合、通知はキューイングされる仕組みとなっています。 常駐プロセスは、通知を受けると待ちが解除されるので、その後処理を行い、また待ちに入る処理を行います。停止 WORK>set ^Zevent("STOP")=1結果例
WORK>zw ^Zevent
^Zevent("REQUEST",1,"08/13/2021 11:35:13")="Request From 16584 08/13/2021 11:34:31"
^Zevent("REQUEST",2,"08/13/2021 11:35:13")="Request From 16584 08/13/2021 11:34:41"
^Zevent("REQUEST",3,"08/13/2021 11:35:13")="Request From 22296 08/13/2021 11:34:54"
^Zevent("REQUEST",4,"08/13/2021 11:35:13")="Request From 22296 08/13/2021 11:34:56"
^Zevent("REQUEST",5,"08/13/2021 11:35:13")="Request From 16584 08/13/2021 11:34:59"
^Zevent("REQUEST",6,"08/13/2021 11:35:13")="Request From 22296 08/13/2021 11:35:02"
^Zevent("REQUEST",7,"08/13/2021 11:35:13")="Request From 16584 08/13/2021 11:35:07"
^Zevent("REQUEST",8,"08/13/2021 11:35:13")="Request From 22296 08/13/2021 11:35:09"
^Zevent("STOP")=1
【ご参考】Simple $system.Event examples(英語)
記事
Tomoko Furuzono · 2020年9月15日
Cachéデータベースのオブジェクトおよびリレーショナルデータモデルは、標準、ビットマップ 、ビットスライスの3種類のインデックスをサポートします。 これら3つのネイティブタイプに加えて、開発者は独自のカスタムタイプのインデックスを宣言し、バージョン2013.1以降の任意のクラスで使用できます。 たとえば、iFindテキストインデックスは、そのメカニズムを使用しています。
カスタムインデックスタイプは、挿入、更新、削除を実行するための%Library.FunctionalIndexインターフェースのメソッドを実装するクラスです。 新しいインデックスを宣言するときに、そのようなクラスをインデックスタイプとして指定できます。
例:
Property A As %String;Property B As %String;Index someind On (A,B) As CustomPackage.CustomIndex;
CustomPackage.CustomIndex クラスは、カスタムインデックスを実装するまさにそのクラスです。
たとえば、ハッカソン中に私たちのチーム(Andrey Rechitsky 、 Aleksander Pogrebnikov、そして私)が開発した空間データのクワッドツリーベースのインデックスの小さなプロトタイプを分析してみましょう。 (ハッカソンは、InterSystems Russia Innovation School Training(インターシステムズ・ロシア・イノベーション・スクール・トレーニング)で毎年開催されます。ハッカソンの主な閃きとなったTimur Safin氏に感謝いたします。)
この記事では、クワッドツリーとそれらの操作方法については説明しません。 代わりに、既存のクワッドツリーアルゴリズム実装のための%Library.FunctionalIndexインターフェースを実装する新しいクラスを作成する方法を検討してみましょう。 私たちのチームでは、このタスクはAndreyに割り当てられました。 Andeyは、次の2つの方法で SpatialIndex.Indexerクラスを作成しました。
Insert(x, y, id)
Delete(x, y, id)
SpatialIndex.Indexerの新しいインスタンスを作成するとき、インデックスデータを格納するためのグローバルノード名を定義する必要がありました。
InsertIndex、 UpdateIndex 、DeleteIndex 、 PurgeIndex メソッドを使って SpatialIndex.Indexのクラスを作成するだけで済みました。 最初の3つのメソッドは、変更する文字列のIdを受け入れ、インデックス付きの値は、対応するクラス内のインデックス宣言で定義されているのとまったく同じ順序です。 この例では、入力引数はpArg(1) — A and pArg(2) — Bです。
Spoiler
Class SpatialIndex.Index Extends %Library.FunctionalIndex [ System = 3 ]{ClassMethod InsertIndex(pID As %CacheString, pArg... As %Binary) [ CodeMode = generator, ServerOnly = 1 ] { if %mode'="method" { set IndexGlobal = ..IndexLocation(%class,%property) $$$GENERATE($C(9)_"set indexer = ##class(SpatialIndex.Indexer).%New($Name("_IndexGlobal_"))") $$$GENERATE($C(9)_"do indexer.Insert(pArg(1),pArg(2),pID)") } }ClassMethod UpdateIndex(pID As %CacheString, pArg... As %Binary) [ CodeMode = generator, ServerOnly = 1 ] { if %mode'="method" { set IndexGlobal = ..IndexLocation(%class,%property) $$$GENERATE($C(9)_"set indexer = ##class(SpatialIndex.Indexer).%New($Name("_IndexGlobal_"))") $$$GENERATE($C(9)_"do indexer.Delete(pArg(3),pArg(4),pID)") $$$GENERATE($C(9)_"do indexer.Insert(pArg(1),pArg(2),pID)") } }ClassMethod DeleteIndex(pID As %CacheString, pArg... As %Binary) [ CodeMode = generator, ServerOnly = 1 ] { if %mode'="method" { set IndexGlobal = ..IndexLocation(%class,%property) $$$GENERATE($C(9)_"set indexer = ##class(SpatialIndex.Indexer).%New($Name("_IndexGlobal_"))") $$$GENERATE($C(9)_"do indexer.Delete(pArg(1),pArg(2),pID)") } }ClassMethod PurgeIndex() [ CodeMode = generator, ServerOnly = 1 ] { if %mode'="method" { set IndexGlobal = ..IndexLocation(%class,%property) $$$GENERATE($C(9)_"kill " _ IndexGlobal) } }ClassMethod IndexLocation(className As %String, indexName As %String) As %String { set storage = ##class(%Dictionary.ClassDefinition).%OpenId(className).Storages.GetAt(1).IndexLocation quit $Name(@storage@(indexName)) }}
IndexLocation は、インデックス値が保存されているグローバル内のノードの名前を返す補足メソッドです。
ここで、 SpatialIndex.Indexタイプのインデックスが使われているテストクラスを分析しましょう。
Class SpatialIndex.Test Extends %Persistent{Property Name As %String(MAXLEN = 300);Property Latitude As %String;Property Longitude As %String; Index coord On (Latitude, Longitude) As SpatialIndex.Index;}
SpatialIndex.Testクラスがコンパイルされている場合、システムは、SpatialIndex.Indexの各インデックスのINTコードで次のメソッドを生成します。
zcoordInsertIndex(pID,pArg...) public {set indexer = ##class(SpatialIndex.Indexer).%New($Name(^SpatialIndex.TestI("coord")))do indexer.Insert(pArg(1),pArg(2),pID) }zcoordPurgeIndex() public {kill ^SpatialIndex.TestI("coord") }zcoordSegmentInsert(pIndexBuffer,pID,pArg...) public {do ..coordInsertIndex(pID, pArg...) }zcoordUpdateIndex(pID,pArg...) public {set indexer = ##class(SpatialIndex.Indexer).%New($Name(^SpatialIndex.TestI("coord")))do indexer.Delete(pArg(3),pArg(4),pID)do indexer.Insert(pArg(1),pArg(2),pID) }
%SaveData 、%DeleteData 、%SQLInsert 、%SQLUpdate および%SQLDelete メソッドはインデックス内のメソッドを呼び出します。 たとえば、次のコードは%SaveDataメソッドの一部です。
if insert { ... do ..coordInsertIndex(id,i%Latitude,i%Longitude,"") ... } else { ... do ..coordUpdateIndex(id,i%Latitude,i%Longitude,zzc27v3,zzc27v2,"") ... }
実際の例は常に理論よりも優れているため、次のリポジトリからファイルをダウンロードできます: https://github.com/intersystems-ru/spatialindex/tree/no-web-interface 。 これは、Web UIのないブランチへのリンクです。 このコードを使用するには、以下を行います。
クラスをインポートする
RuCut.zipを解凍する
次の呼び出しを使用してデータをインポートする:
do $system.OBJ.LoadDir("c:\temp\spatialindex","ck")do ##class(SpatialIndex.Test).load("c:\temp\rucut.txt")
rucut.txtファイルには、ロシアの10万の都市や町に関するデータとその名前と座標が含まれています。 Loadメソッドは各ファイル文字列を読み取り、それをSpatialIndex.Testクラスの個別のインスタンスとして保存します。 Loadメソッドが実行されると、グローバル^ SpatialIndex.TestI( 「coord」)には、緯度と経度の座標を持つクワッドツリーが含まれます。
では、クエリを実行しましょう!
インデックスの構築は、一番おもしろい部分ではありません。 さまざまなクエリでインデックスを使用します。 Cachéには、非標準インデックスの標準構文があります。
SELECT *FROM SpatialIndex.TestWHERE %ID %FIND search_index(coord, 'window', 'minx=56,miny=56,maxx=57,maxy=57')
%ID %FIND search_index は構文の固定部分です。 次に、インデックス名coordがあります。引用符は必要ありません。 他のすべてのパラメーター( 'window'、 'minx = 56、miny = 56、maxx = 57、maxy = 57')は Findメソッドに渡されます。これもインデックスタイプのクラスで定義する必要があります(この例では、 SpatialIndex.Indexです )。
ClassMethod Find(queryType As %Binary, queryParams As %String) As %Library.Binary [ CodeMode = generator, ServerOnly = 1, SqlProc ]{ if %mode'="method" { set IndexGlobal = ..IndexLocation(%class,%property) set IndexGlobalQ = $$$QUOTE(IndexGlobal) $$$GENERATE($C(9)_"set result = ##class(SpatialIndex.SQLResult).%New()") $$$GENERATE($C(9)_"do result.PrepareFind($Name("_IndexGlobal_"), queryType, queryParams)") $$$GENERATE($C(9)_"quit result") }}
このコードサンプルでは、パラメーターは2つしかありません。 queryType と queryParams ですが、必要な数のパラメータを自由に追加できます。
SpatialIndex.Indexが使われているクラスをコンパイルする場合、 Find メソッドは、 z <IndexName> Findと呼ばれる補足メソッドを生成しますが 、これはSQLクエリの実行に使用されます。
zcoordFind(queryType,queryParams) public { s:'$isobject($g(%sqlcontext)) %sqlcontext=##class(%Library.ProcedureContext).%New()set result = ##class(SpatialIndex.SQLResult).%New()do result.PrepareFind($Name(^SpatialIndex.TestI("coord")), queryType, queryParams)quit result }
Findメソッドは、%SQL.AbstractFindインターフェースを実装するクラスのインスタンスを返す必要があります。 このインターフェイスのメソッド、 NextChunk および PreviousChunk は、それぞれ64,000ビットのチャンクでビット文字列を返します。 特定のIDのレコードが選択基準を満たし、対応するビット(chunk_number * 64000 + position_number_within_chunk)が1に設定されます。
Spoiler
Class SpatialIndex.SQLResult Extends %SQL.AbstractFind{Property ResultBits [ MultiDimensional, Private ];Method %OnNew() As %Status [ Private, ServerOnly = 1 ] { kill i%ResultBits kill qHandle quit $$$OK }Method PrepareFind(indexGlobal As %String, queryType As %String, queryParams As %Binary) As %Status { if queryType = "window" { for i = 1:1:4 { set item = $Piece(queryParams, ",", i) set IndexGlobal = ..IndexLocation(%class,%property) $$$GENERATE($C(9)_"kill " _ IndexGlobal) set param = $Piece(item, "=", 1) set value = $Piece(item, "=" ,2) set arg(param) = value } set qHandle("indexGlobal") = indexGlobal do ##class(SpatialIndex.QueryExecutor).InternalFindWindow(.qHandle,arg("minx"),arg("miny"),arg("maxx"),arg("maxy")) set id = "" for { set id = $O(qHandle("data", id),1,idd) quit:id="" set tChunk = (idd\64000)+1, tPos=(idd#64000)+1 set $BIT(i%ResultBits(tChunk),tPos) = 1 } } quit $$$OK } Method ContainsItem(pItem As %String) As %Boolean{ set tChunk = (pItem\64000)+1, tPos=(pItem#64000)+1 quit $bit($get(i%ResultBits(tChunk)),tPos)}Method GetChunk(pChunk As %Integer) As %Binary { quit $get(i%ResultBits(pChunk)) }Method NextChunk(ByRef pChunk As %Integer = "") As %Binary { set pChunk = $order(i%ResultBits(pChunk),1,tBits) quit:pChunk="" "" quit tBits }Method PreviousChunk(ByRef pChunk As %Integer = "") As %Binary { set pChunk = $order(i%ResultBits(pChunk),-1,tBits) quit:pChunk="" "" quit tBits }}
Class SpatialIndex.SQLResult Extends %SQL.AbstractFind{Property ResultBits [ MultiDimensional, Private ];Method %OnNew() As %Status [ Private, ServerOnly = 1 ]{ kill i%ResultBits kill qHandle quit $$$OK}Method PrepareFind(indexGlobal As %String, queryType As %String, queryParams As %Binary) As %Status{ if queryType = "window" { for i = 1:1:4 { set item = $Piece(queryParams, ",", i) set IndexGlobal = ..IndexLocation(%class,%property) $$$GENERATE($C(9)_"kill " _ IndexGlobal) set param = $Piece(item, "=", 1) set value = $Piece(item, "=" ,2) set arg(param) = value } set qHandle("indexGlobal") = indexGlobal do ##class(SpatialIndex.QueryExecutor).InternalFindWindow(.qHandle,arg("minx"),arg("miny"),arg("maxx"),arg("maxy")) set id = "" for { set id = $O(qHandle("data", id),1,idd) quit:id="" set tChunk = (idd\64000)+1, tPos=(idd#64000)+1 set $BIT(i%ResultBits(tChunk),tPos) = 1 } } quit $$$OK }Method ContainsItem(pItem As %String) As %Boolean{ set tChunk = (pItem\64000)+1, tPos=(pItem#64000)+1 quit $bit($get(i%ResultBits(tChunk)),tPos)}Method GetChunk(pChunk As %Integer) As %Binary{ quit $get(i%ResultBits(pChunk))}Method NextChunk(ByRef pChunk As %Integer = "") As %Binary{ set pChunk = $order(i%ResultBits(pChunk),1,tBits) quit:pChunk="" "" quit tBits}Method PreviousChunk(ByRef pChunk As %Integer = "") As %Binary{ set pChunk = $order(i%ResultBits(pChunk),-1,tBits) quit:pChunk="" "" quit tBits}}
上記のコードサンプルに示すように、 SpatialIndex.QueryExecutorクラスの InternalFindWindow メソッドは、指定された長方形内にあるポイントを検索します。 次に、一致する行のIDがFORループのビットセットに書き込まれます。
私たちのハッカソンプロジェクトでは、Andreyは楕円の検索機能も実装しました。
SELECT *FROM SpatialIndex.TestWHERE %ID %FIND search_index(coord,'radius','x=55,y=55,radiusX=2,radiusY=2')and name %StartsWith 'Z'
%FINDに関する補足情報
%FIND 述語には追加パラメーター SIZEがあり、これは、SQLエンジンが一致する行の数を推定するのに役立ちます。 SQLエンジンは、このパラメーターに基づいて%FIND述語で指定されたインデックスを使用するかどうかを決定します。
たとえば、次のインデックスをSpatialIndex.Testクラスに追加してみましょう。
Index ByName on Name;
次に、クラスを再コンパイルして、このインデックスを作成します。
write ##class(SpatialIndex.Test).%BuildIndices($LB("ByName"))
最後に、TuneTableを実行します。
do $system.SQL.TuneTable("SpatialIndex.Test", 1)
クエリプランは次のとおりです。
SELECT *FROM SpatialIndex.TestWHERE name %startswith 'za'and %ID %FIND search_index(coord,'radius','x=55,y=55,radiusX=2,radiusY=2') size ((10))
coordインデックスは数行を返す可能性が高いため、SQLエンジンは Nameプロパティのインデックスを使用しません。
次のクエリには別のプランがあります。
SELECT *FROM SpatialIndex.TestWHERE name %startswith 'za'and %ID %FIND search_index(coord,'radius','x=55,y=55,radiusX=2,radiusY=2') size ((1000))
SQLエンジンは、両方のインデックスを使用してこのクエリを実行します。
そして最後の例として、coordインデックスはおそらく約10万行を返すため、ほとんど使用できないので、 Nameフィールドのインデックスのみを使用するリクエストを作成しましょう。
SELECT *FROM SpatialIndex.TestWHERE name %startswith 'za'and %ID %FIND search_index(coord,'radius','x=55,y=55,radiusX=2,radiusY=2') size ((100000))
この記事を読んでくださった方、または少なくとも最後までスクロールしてくださったすべての方に感謝いたします。
参考:
%FIND
search_index
%SQL.AbstractFind
%Library.FunctionalIndex
記事
Hiroshi Sato · 2020年8月18日
IRISでは.Net Bindingは非推奨機能となりました。
.Net Bindingを使ったアプリケーションは、IRISで提供されている.Net Native APIを利用して書き換えることができます。
ここでは、実際に書き換えをおこなったサンプルコードを示しながら、具体的な方法を説明していきます。
CacheDirect(VisM)エミュレーター
OpenExchangeに登録しているVisMエミュレーターは、元々Cachéの.Net Bindingを使用して作成されました。
それをIRISの標準機能で動作可能にするために、.Net Native APIを使用して書き換えをおこないました。
以下にどのように書き換えを行ったかを順を追って説明します。
参照の変更
まず以前の参照を削除します。
Visual Studioのソリューションエクスプローラーの所で参照をクリックします。
表示されるInterSystems.Data.CacheClientを削除します。(右クリックして削除を選ぶ)
次にプロジェクトメニューから参照の追加をクリックして、以下の2つのファイルを選択します。(プロジェクトの.Net Frameworkバージョンに合わせて、それに対応するファイルを選択する以下の例は、v4.5を選択)
c:\InterSystems\IRIS\dev\dotnet\bin\v4.5InterSystems.Data.IRISClient.dll
using句の変更
先頭のusing句の変更が必要になります。
using InterSystems.Data.CacheClient;using InterSystems.Data.CacheTypes;
上記を以下の様に書き換えます。
using InterSystems.Data.IRISClient;using InterSystems.Data.IRISClient.ADO;
connection情報
connectionオブジェクトをCachéからIRISに変更する必要があります。
CacheConnection conn;
public IRISConnection conn = new IRISConnection();
Proxyクラスの削除
.Net Native APIではプロキシークラスは必要なくなるので、その参照を削除します。
(プロジェクトからもUser.CacheDirect.csを削除します。)
public User.CacheDirect cd;
代わりにIRISオブジェクトを宣言します。
public IRISObject cd;
続いてプロキシークラスが保持していたプロパティ機能を実装するために、以下の宣言を追加します。(すべてのプロパティに対してプライベート変数とそれにパブリックアクセスするためのアクセッサメソッド)
private string p0; private string p1; private string p2; private string p3; private string p4; private string p5; private string p6; private string p7; private string p8; private string p9; private string plist; private string pdelim; private string value; private string code; private long execflag; private string errorname; private long error; public string P0 { set { this.p0 = value; } get { return this.p0; } } public string P1 { set { this.p1 = value; } get { return this.p1; } } public string P2 { set { this.p2 = value; } get { return this.p2; } } public string P3 { set { this.p3 = value; } get { return this.p3; } } public string P4 { set { this.p4 = value; } get { return this.p4; } } public string P5 { set { this.p5 = value; } get { return this.p5; } } public string P6 { set { this.p6 = value; } get { return this.p6; } } public string P7 { set { this.p7 = value; } get { return this.p7; } } public string P8 { set { this.p8 = value; } get { return this.p8; } } public string P9 { set { this.p9 = value; } get { return this.p9; } } public string PLIST { set { this.plist = value; } get { return this.plist; } } public string PDELIM { set { this.pdelim = value; } get { return this.pdelim; } } public string Value { set { this.value = value; } get { return this.value; } } public string Code { set { this.code = value; } get { return this.code; } } public long ExecFlag { set { this.execflag = value; if (value == 1) { this.Execute(this.code); } } get { return this.execflag; } } public string ErrorName { get { return this.errorname; } } public string Error { get { return this.error.ToString(); } }
サーバー接続処理
コンストラクターの処理の所で、サーバー接続処理をIRIS用に変更します。
conn = new CacheConnection(); conn.ConnectionString = constr; conn.Open(); cd = new User.CacheDirect(conn);
IRISでは、コネクションオブジェクトを作成した後、プロキシークラスのインスタンスを生成する代わりにIRISクラスのインスタンスを生成し、サーバーのCacheDirect.Emulatorクラスの%Newクラスメソッドを呼び出して、IRISObjectクラスのインスタンスを生成しています。
(.Net Binding版のクラスUser.CacheDirectから名前も変更)このインスタンスが従来のプロキシークラスのインスタンスと同様の機能を提供します。
conn.ConnectionString = constr; conn.Open(); IRIS iris = IRIS.CreateIRIS(conn); cd = (IRISObject)iris.ClassMethodObject("CacheDirect.Emulator", "%New");
プロキシークラスでの実装と異なり、.Net Native APIではプロパティに値を設定するにはIRISObjectクラスのSetメソッドを使って、明示的に値を設定する必要があります。
public long Execute(string command) { long status; cd.Set("P0", p0); cd.Set("P1", p1); cd.Set("P2", p2); cd.Set("P3", p3); cd.Set("P4", p4); cd.Set("P5", p5); cd.Set("P6", p6); cd.Set("P7", p7); cd.Set("P8", p8); cd.Set("P9", p9); cd.Set("PLIST", plist); cd.Set("PDELIM", pdelim)
サーバーのインスタンスメソッド(Execute)を呼び出すためには、IRISObjectクラスのInvokeメソッドを呼び出します。
status = (long)cd.Invoke("Execute", command);
サーバー側のExecuteメソッド実行後に変更された可能性のあるプロパティの値(P0-P9,PLIST,Valueなど)をクライアントのプロパティに反映させるためにIRISOBjectクラスのGetメソッドを呼び出します。
ここでは、サーバー側のプロパティのタイプに関わらず、戻り値によってタイプが動的に変化する可能性があるために戻り値の型をチェックして適切に処理する必要があります。
if (cd.Get("P0") is string) { p0 = (string)cd.Get("P0"); } else { if (cd.Get("P0") is null) { } else { p0 = cd.Get("P0").ToString(); } }
ErrorNameとErrorもサーバー側のプロパティからGetメソッドを使用して取得します。
errorname =(string) cd.Get("ErrorName"); error = (long)cd.Get("Error");
PLISTの処理用に追加したメソッドも同様にサーバー側のPLISTプロパティをGetメソッドで取得します。PLISTプロパティに値を設定するためにSetメソッドを使用します。
string[] PLISTArray = cd.Get("PLIST").ToString().Split(cd.Get("PDELIM").ToString().ToCharArray()); cd.Set("PLIST", string.Join(cd.Get("PDELIM").ToString(), PLISTArray));
記事
Toshihiko Minamoto · 2021年7月1日
クラスのコンパイル時に、定義済みのプロパティ、クエリ、またはインデックスごとに対応する複数のメソッドが自動的に生成されます。 これらのメソッドは非常に便利です。 この記事では、その一部について説明します。
プロパティ
「Property」というプロパティを定義したとしましょう。 次のメソッドが自動的に利用できるようになります(太字のPropertyは変化する部分でプロパティ名になります)。
ClassMethod PropertyGetStored(id)
このメソッドは、データ型プロパティの場合は論理値を返し、オブジェクトプロパティの場合はIDを返します。 これはクラスのデータグローバルへの、ラップされたグローバル参照であり、特異なプロパティ値を取得する上でもっとも手早い方法です。 このメソッドは、保存されたプロパティでのみ利用できます。
Method PropertyGet()
プロパティgetterである。 再定義可能です。
Method PropertySet(val) As %Status
プロパティsetterである。 再定義可能です。
オブジェクトプロパティ
オブジェクトプロパティの場合、IDとOIDアクセスに関連するいくつかの追加メソッドを利用できるようになります。
Method PropertySetObjectId(id)
このメソッドはIDでプロパティ値を設定するため、オブジェクトを開いてプロパティ値として設定する必要はありません。
Method PropertyGetObjectId()
このメソッドは、プロパティ値のIDを返します。
Method PropertySetObject(oid)
このメソッドは、OIDでプロパティ値を設定します。
Method PropertyGetObject()
このメソッドは、プロパティ値のOIDを返します。
データ型プロパティ
データ型プロパティの場合、異なる形式間で変換するためのほかのメソッドがいくつか利用できるようになります。
ClassMethod PropertyDisplayToLogical(val)
ClassMethod PropertyLogicalToDisplay(val)
ClassMethod PropertyOdbcToLogical(val)
ClassMethod PropertyLogicalToOdbc(val)
ClassMethod PropertyXSDToLogical(val)
ClassMethod PropertyLogicalToXSD(val)
ClassMethod PropertyIsValid(val) As %Status
valが有効なプロパティ値であるかどうかをチェックします。
ClassMethod PropertyNormalize(val)
正規化された論理値を返します。
**注意事項**
* 関係はプロパティであり、これらのメソッドで取得/設定できます。
入力値(val)は、形式変換メソッドを除き、必ず論理値です。
インデックス
「Index」というインデックスの場合、次のメソッドを自動的に利用できるようになります。
ClassMethod IndexExists(val) As %Boolean
このvalを持つオブジェクトが存在するかどうかに応じて、1または0を返します。valはインデックス付きのプロパティの論理値です。
一意のインデックス
一意のインデックスの場合、追加メソッドを利用できるようになります。
ClassMethod IndexExists(val, Output id) As %Boolean
このvalを持つオブジェクトが存在するかどうかに応じて、1または0を返します。valはインデックス付きのプロパティの論理値です。 また、オブジェクトIDがある場合は、第2引数として返します。
ClassMethod IndexDelete(val, concurrency = -1) As %Status
インデックスの値がvalのエントリを削除します。
ClassMethod IndexOpen(val, concurrency, sc As %Status)
インデックスの値がvalの既存のオブジェクトを返します。
**注意事項:**
a)インデックスは複数のプロパティに基づいている可能性があるため、メソッドシグネチャは、入力として複数の値を持つように変更されます。例として、次のインデックスを見てみましょう。
Index MyIndex On (Prop1, Prop2);
この場合、IndexExistsメソッドには次のシグネチャがあります。
ClassMethod IndexExists(val1, val2) As %Boolean
val1はProp1値に対応し、val2はProp2値に対応します。 その他のメソッドも同じロジックに従います。
b)Cachéは、IDフィールド(RowID)にインデックスを作成するIDKEYインデックスを生成します。 ユーザーが再定義することが可能で、複数のプロパティを含むこともできます。 たとえば、クラスにプロパティが定義されているかをチェックするには、次を実行します。
Write ##class(%Dictionary.PropertyDefinition).IDKEYExists(class, property)
c)すべてのインデックスメソッドは、論理値をチェックします。
d) ドキュメント
クエリ
「Query」というクエリの場合(単純なSQLクエリまたはカスタムクラスクエリのどちらでも構いません。これに関する[記事](https://community.intersystems.com/post/class-queries-intersystems-cache%CC%81)をご覧ください)、Funcメソッドが生成されます。
ClassMethod QueryFunc(Arg1, Arg2) As %SQL.StatementResult
このメソッドは、クエリの反復処理に使用される%SQL.StatementResultを返します。 たとえば、SamplesネームスペースのSample.Personクラスには、1つのパラメーターを受け入れるByNameクエリがあり、 次のコードを使って、オブジェクトコンテキストから呼び出すことができます。
Set ResultSet=##class(Sample.Person).ByNameFunc("A")
While ResultSet.%Next() { Write ResultSet.Name,! }
また、[GitHubには、これらのメソッドを実演するデモクラス](https://github.com/eduard93/Utils/blob/master/Utils/GeneratedMethods.cls.xml)があります。
記事
Toshihiko Minamoto · 2022年3月8日
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("")
do stream.WriteLine("")
do stream.WriteLine(" ")
do stream.WriteLine(" ")
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("")
do stream.WriteLine("")
do stream.WriteLine("")
do stream.WriteLine("")
do stream.WriteLine("")
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("
")
do stream.WriteLine("")
do stream.WriteLine("")
do stream.WriteLine("")
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("")
do stream.WriteLine("")
do stream.WriteLine("")
do stream.WriteLine("")
do stream.WriteLine("")
do stream.WriteLine("")
do stream.WriteLine("")
do stream.%Save()
}catch{
set status=$$$NO
}
kill stream
return status
}
**styles.xml**
すべてのフォーマットがこのファイルに含まれます。現時点では、静的スタイルが追加されています(より動的なworkbook固有のスタイルに変換する予定です)。
Excelスタイル
ID
スタイル
Excelフォーマット
1
デフォルト
テキスト
2
#;[Red]-#
数値
3
#.##;[Red]-#.##
数値
4
yyyy/mm/dd
日付
5
hh:mm
日付
6
ヘッダーと中央揃え
テキスト
7
ヘッダー2左寄せ
テキスト
8
良い(緑ハイライト)
全般
9
悪い(赤ハイライト)
全般
10
どちらでもない(オレンジハイライト)
全般
11
yyyy/mm/dd hh:mm
日付
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("")
do stream.WriteLine("")
do stream.WriteLine("")
do stream.WriteLine("")
do stream.WriteLine("")
do stream.WriteLine("")
do stream.WriteLine("")
do stream.WriteLine("")
do stream.WriteLine("")
do stream.WriteLine("")
do stream.WriteLine("")
do stream.WriteLine("")
do stream.WriteLine("")
do stream.WriteLine("")
do stream.WriteLine("")
do stream.WriteLine("")
do stream.WriteLine("")
do stream.WriteLine("")
do stream.WriteLine("")
do stream.WriteLine("")
do stream.WriteLine("")
do stream.WriteLine("")
do stream.WriteLine("")
do stream.WriteLine("")
do stream.WriteLine("")
do stream.WriteLine("")
do stream.WriteLine("")
do stream.WriteLine("")
do stream.WriteLine("")
do stream.WriteLine("")
do stream.WriteLine("")
do stream.WriteLine("")
do stream.WriteLine("")
do stream.WriteLine("")
do stream.WriteLine("")
do stream.WriteLine("")
do stream.WriteLine("")
do stream.WriteLine("")
do stream.WriteLine("")
do stream.WriteLine("")
do stream.WriteLine("")
do stream.WriteLine(" ")
do stream.WriteLine("")
do stream.WriteLine("")
do stream.WriteLine("")
do stream.WriteLine("")
do stream.%Save()
}catch{
set status=$$$NO
}
kill stream
return status
}
**worksheet.xml**
このファイルに日付が含まれます。 シートの最初の行は列のタイトルです。 次の行には、最初の列にのみデータが含まれます。
デフォルトで列が自動調整されない場合は、ここで各列の列幅を定義します。
サンプルworksheet.xml
Name
Amount
Jhon Smith
1000.74
Tracy A
6001.74
サンプルExcel
.png)
_worksheet内の数式は、関数 タグ_を使って表現できます。
B2*0.08
B2+C2
そして最後にそれらを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](/sites/default/files/inline/files/xlsx.writer.xml.zip)にあります。
記事
Mihoko Iijima · 2023年6月6日
これは InterSystems FAQ サイトの記事です。
ターミナルでルーチンやクラスのコンパイルを行う際、コンパイル結果が画面に表示されるのでエラーが発生した場合でも確認しやすいですが、一括コンパイルの場合は、大量のコンパイル結果の中にエラー情報が含まれてしまうためエラー情報だけを取得したい場合には少し工夫が必要です。
以下、ルーチン/クラスの一括コンパイル時の結果からエラー情報を取得する方法をご紹介します。
ルーチンの場合
ネームスペースにあるルーチンをターミナルで一括コンパイルするには、%Library.Routine クラスの CompileAll() メソッドを使用します。
以下実行例は、USERネームスペースにあるルーチンを一括コンパイルした結果です。TestRoutine1でコンパイルエラーが発生しています。
USER>do ##class(%Routine).CompileAll()
Compiling in namespace USER at 11:50:47
Routine1.MAC TestRoutine1.MAC
TestRoutine1.MAC - Errors compiling routine
TestRoutine1.INT(3) : Error 8: <NOLINE> : ' do sub3()'
2 routines compiled with 1 errors at 11:50:47 in 00:00:00.030
USER>
大量にルーチンがある場合、出力結果が流れて画面から消えてしまうため、カレントデバイスに出力されている内容をファイル保存し、保存したファイルの中からエラー情報を取得するようにします。
1) コンパイル結果をファイルに保存する
CompileAll() メソッドの第2引数にファイル名をフルパスで指定します。
このメソッドは、第2引数に指定したデバイスがオープンされている場合、そのデバイスにログを書き込みます。
そのため、一旦ファイルを新規書き込みモードでオープンします(OPENコマンドを使用します)。
// ログファイルのフルパスを変数に設定します
set log="C:\temp\result.log"
//ファイルを新規書き込みモードでオープンします
open log:"NWS"
//第2引数にログ出力するファイル名を指定し、一括コンパイルを実行します。
do ##class(%Routine).CompileAll(,log)
//ファイルをクローズします。
close log
2) 1)で作成したファイルからエラー情報を取得する。
ファイルをオープンし、エラー発生時の文字列「Errors compiling routine」が含まれている場合にルーチン名を取り出して変数に設定する例をご紹介します。
//文字列ファイル操作用のインスタンスを生成
set file=##class(%Stream.FileCharacter).%New()
//ファイルとのリンク付け
do file.LinkToFile("c:\temp\result.log")
//ファイルの終わりを検出するまで読み取りながら情報抽出
//ファイルの終わりが検出されるとAtEndプロパティに1が設定される
while file.AtEnd=0 {
set reco=file.ReadLine()
//読み取った行にエラー字の文字列が含まれる場合
if reco["Errors compiling routine" {
//スペース区切りの1番目にルーチン名が含まれているので取得
set rtn=$piece(reco," ",1)
}
//ルーチン名が空だったら次のループへ移動
if $get(rtn)="" {
continue
}
//ローカル変数の添え字にルーチン名をセット
set val(rtn)=""
}
zwrite val
ターミナルで実行する場合
set file=##class(%Stream.FileCharacter).%New()
do file.LinkToFile("c:\temp\result.log")
while file.AtEnd=0 { set reco=file.ReadLine() if reco["Errors compiling routine" { set rtn=$piece(reco," ",1)} if $get(rtn)="" { continue } set val(rtn)=""}
zwrite val
クラスの場合
ネームスペースにあるクラスをターミナルで一括コンパイルするには、%SYSTEM.OBJクラスのCompileAll()メソッドを使用します。
以下実行例は、USERネームスペースにあるクラスを一括コンパイルした結果で、Dummy.ErrorClass1でコンパイルエラーが発生しています。
USER>do $system.OBJ.CompileAll("ck")
04/20/2023 12:17:49 に修飾子 'ck' でコンパイルを開始しました。
エラー #5373: クラス 'Dummy.ErrorClass1:property:XYZ' が使用するクラス '%Library.Strig' は、存在しません
Skip class Dummy.ErrorClass1
, 72 クラスをコンパイル中
クラスのコンパイル中 CookBook.Class1
クラスのコンパイル中 A.b3
クラスのコンパイル中 A.B1
《省略》
ルーチンのコンパイル中 F4.GoldMember.1
クラスのコンパイル中 MyApp.MyService.Test
ルーチンのコンパイル中 MyApp.MyService.Test.1
1.091s のコンパイル中に 1 エラーを検出しました。
第2引数を参照渡しで指定するとエラー情報が配列変数として設定されます。
USER>do $system.OBJ.CompileAll("ck",.log)
USER>zwrite log
log=1
log(1)="エラー #5373: クラス 'Dummy.ErrorClass1:property:XYZ' が使用するクラス '%Library.Strin' は、存在しません"
log(1,"caller")="findalldependencyclasses+149^%occDepend"
log(1,"code")=5373
log(1,"dcode")=5373
log(1,"domain")="%ObjectErrors"
log(1,"namespace")="USER"
log(1,"param")=2
log(1,"param",1)="%Library.Strin"
log(1,"param",2)="Dummy.ErrorClass1:property:XYZ"
log(1,"stack")=$lb("e^findalldependencyclasses+149^%occDepend^2","e^findalldependencyclasses+58^%occDepend^1","e^findalldependencyclasses+8^%occDepend^1","e^IncludeClasses+44^%occCompile^1","e^CompileList+59^%occCompile^1","e^CompileList+23^%apiOBJ^1","e^CompileAll+15^%apiOBJ^1","e^zCompileAll+1^%SYSTEM.OBJ.1^1","d^^^0")
複数エラーが発生した場合は、ログ用に指定した変数直下にエラー個数が設定されます。
エラーメッセージだけを取り出す方法は以下の通りです。
for i=1:1:log { write log(i),! }
マッピングされているクラス・ルーチンのコンパイル方法については、以下開発者コミュニティの記事をご参照ください。
マッピングされたクラス・ルーチンをコンパイルする方法
お知らせ
Mihoko Iijima · 2023年9月4日
開発者の皆さん、こんにちは!
いよいよ9月6日(水)~「第1回 InterSystems Japan 技術文書ライティングコンテスト」が始まります!
このお知らせでは、技術文書ライティングコンテスト📝への記事の投稿方法をご紹介します。
手順は以下2つだけ。とても簡単です。ぜひチャレンジしてみてください!💪
開発者コミュニティにアカウントを作成する
IRISに関連した記事を書いてコンテスト用タグを設定する
1については、記事「アカウント作成方法」に図解がありますのでご参照ください。
2については、以下の「記事の投稿方法」で詳しくご紹介します。
記事の投稿方法
1) 開発者コミュニティにログインし ボタンをクリックします。
2) を選択し、タイトルを記入します。
本文は上記図解にある小さな画面でも記入できますし をクリックすると全画面表示で記事を記入できます。
もう1度 をクリックすると元の表示に戻ります。
エディタの使い方については、後述します。
3) コンテスト用タグの追加
技術文書ライティングコンテストではタグに「コンテスト」を指定いただく必要があります。
💡この指定がないとコンテストの記事としてご応募できません!!必ず設定してください💡
「コン」まで記入すると候補が下図のように登場します。「コンテスト」を選択してください。
この他、記事に関連するタグがあれば追加してください。(複数指定できます)
4) 記事の下書き保存
画面下にボタンがあります。このボタンを押すと「下書き」として非公開状態で保存できます(この時点では作者しか参照できません)。
下書き保存した状態は以下の通りです。(まだ公開されていない状態です)
記事は下書き保存後も、公開後も何度でも編集できます。「編集」のリンクをクリックすると編集モードで画面が開きます。
5) 公開方法
画面下にあるボタンをクリックするだけです。(下書きをしている場合は、一度編集画面に切り替えてから「公開」ボタンをクリックします。)
クリック前に以下、念のためご確認ください。
タグに「コンテスト」が設定されているかどうか
本文の文字数が800文字以上となっているかどうか
これで公開完了です!
公開後も何回でも記事は編集できます。
以下、エディタの使い方をご紹介します。
エディタの使い方
モードの切り替え(MarkdownモードとWYSWYGモードを選択できます)
デフォルトでは、WYSWYGモードです。Markdownモードに切り替えるには画面左のドロップダウンで「Markdown」を選択します。
エディタのアイコンについて
スタイルについて
スタイルは 以下のようにプルダウンで選択できます。
選択肢別のスタイルは以下の通りです。
画像の追加について
画像ファイルをでUploadしても、コピー&ペーストでエディタ内に貼ることもできます。
コードの追加について
をクリックするとコード追加画面が表示されます。画面左上に言語指定があるので、対象言語を指定します。(例はObjectScriptです)
言語は以下選択できます。
OKボタンクリック後、エディタでは以下のように表示されます。
下書き/公開モードになると、以下のように表示されます。
以上です。
記述方法について何かご不明な点ありましたらこの記事の返信欄にぜひご記入ください!
次回のお知らせでは、過去の技術文書ライティングコンテスト(US版)での作品例をご紹介する予定です。 開催のご連絡ありがとうございます!
早速一つ投稿しようと思ったのですが、グループに「IRIS contest」が無いように思われます。
一度ご確認いただいてもよろしいでしょうか。 @Ohata.Yuji さん、こんにちは。
ご連絡ありがとうございます。コミュニティチームで確認しまして応募方法を以下に変更することとしました。
グループ「IRIS contest」は不要
タグの「コンテスト」のみの設定でご応募お願いいたします。(この後、関連記事を修正します)
(記事に関連するタグは、もちろん設定いただいて大丈夫です)
ご連絡いただき助かりました!ありがとうございました。
ご応募お待ちしてます! ご確認いただきありがとうございます。承知いたしました!
記事
Toshihiko Minamoto · 2024年4月1日
大規模言語モデル(OpenAI の GPT-4 など)の発明と一般化によって、最近までは手動での処理が非現実的または不可能ですらあった大量の非構造化データを使用できる革新的なソリューションの波が押し寄せています。 データ検索(検索拡張生成に関する優れた紹介については、Don Woodlock の ML301 コースをご覧ください)、センチメント分析、完全自律型の AI エージェントなど、様々なアプリケーションが存在します。
この記事では、IRIS テーブルに挿入するレコードに自動的にキーワードを割り当てる単純なデータタグ付けアプリケーションの構築を通じて、IRIS の Embedded Python 機能を使って、Python OpenAI ライブラリに直接インターフェース接続する方法をご紹介します。 これらのキーワードをデータの検索と分類だけでなく、データ分析の目的に使用できるる単純なデータタグ付けアプリケーションを構築します。ユースケースの例として、製品の顧客レビューを使用します。
要件
IRIS の実行インスタンス
OpenAPI API キー(こちらで作成できます)
構成済みの開発環境(この記事では VS Code を使用します)
Review クラス
顧客レビューのデータモデルを定義する ObjectScript クラスの作成から始めましょう。 簡潔さを維持するために、顧客の名前、製品名、レビュー本文、および生成するキーワードの 4 つの %String フィールドのみを定義します。 クラスはオブジェクトをディスクに保存できるように、%Persistent を拡張します。
Class DataTagging.Review Extends %Persistent
{
Property Name As %String(MAXLEN = 50) [ Required ];
Property Product As %String(MAXLEN = 50) [ Required ];
Property ReviewBody As %String(MAXLEN = 300) [ Required ];
Property Keywords As %String(MAXLEN = 300) [ SqlComputed, SqlComputeOnChange = ReviewBody ];
}
ReviewBody への挿入または更新時に、Keywords プロパティが自動的に計算されるようにしたいため、これを SqlComputed とします。計算される値についての詳細は、こちらをご覧ください。
KeywordsComputation メソッド
次に、レビュー本文に基づいてキーワードを計算するために使用するメソッドを定義します。 公式の openai Python パッケージを直接操作するために、Embedded Python を使用できます。 ただし、先にそれをインストールしておく必要があります。 以下のシェルコマンドを実行しましょう。
<your-IRIS-installation-path>/bin/irispip install --target <your-IRIS-installation-path>/Mgr/python openai
OpenAI のチャット補完 API を使用して、キーワードを生成できるようになりました。
ClassMethod KeywordsComputation(cols As %Library.PropertyHelper) As %String [ Language = python ]
{
'''
This method is used to compute the value of the Keywords property
by calling the OpenAI API to generate a list of keywords based on the review body.
'''
from openai import OpenAI
client = OpenAI(
# Defaults to os.environ.get("OPENAI_API_KEY")
api_key="<your-api-key>",
)
# Set the prompt; use few-shot learning to give examples of the desired output
user_prompt = "Generate a list of keywords that summarize the content of a customer review of a product. " \
+ "Output a JSON array of strings.\n\n" \
+ "Excellent watch. I got the blue version and love the color. The battery life could've been better though.\n\nKeywords:\n" \
+ "[\"Color\", \"Battery\"]\n\n" \
+ "Ordered the shoes. The delivery was quick and the quality of the material is terrific!.\n\nKeywords:\n" \
+ "[\"Delivery\", \"Quality\", \"Material\"]\n\n" \
+ cols.getfield("ReviewBody") + "\n\nKeywords:"
# Call the OpenAI API to generate the keywords
chat_completion = client.chat.completions.create(
model="gpt-4", # Change this to use a different model
messages=[
{
"role": "user",
"content": user_prompt
}
],
temperature=0.5, # Controls how "creative" the model is
max_tokens=1024, # Controls the maximum number of tokens to generate
)
# Return the array of keywords as a JSON string
return chat_completion.choices[0].message.content
}
プロンプト内では、最初に「generate a list of keywords that summarize the content of a customer review of a product」で、製品の顧客レビューのコンテンツを要約するキーワードのリストを GPT-4 でどのように生成するかについての一般的な命令を指定してから、希望する出力と 2 つのサンプル入力を指定していることに注意してください。 次に cols.getfield("ReviewBody") を挿入し、「Keywords:」の語でプロンプトを終了することで、私が提示した例と同じ形式でキーワードを提供して、文を完成させるように促しています。 これは、Few-Shot プロンプティング手法の単純な例です。
この記事を簡潔にするため、キーワードを JSON 文字列で格納するようにしていますが、本番では、DynamicArray に格納するのがお勧めです。ただ、これについては各開発者にお任せします。
キーワードの生成
それでは、管理ポータルで以下の SQL クエリを使用してテーブルに行を挿入し、データタグ付けアプリケーションをテストしてみましょう。
INSERT INTO DataTagging.Review (Name, Product, ReviewBody)
VALUES ('Ivan', 'BMW 330i', 'Solid car overall. Had some engine problems but got everything fixed under the warranty.')
以下のとおり、4 つのキーワードが自動的に生成されました。 上出来です!
まとめ
まとめると、Python コードを埋め込む InterSystems IRIS の機能によって、非構造化データを処理する際に幅広い可能性を得られます。 OpenAI の力を活用して自動データタグ付けを行うのは、この強力な機能によって達成できることの一例にすぎません。 これにより、ヒューマンエラーを縮小し、全体的な効率をさらに高めることができます。
お知らせ
Shintaro Kaminaka · 2021年7月8日
開発者の皆さん、こんにちは!
HL7v2メッセージをFHIR(Fast Healthcare Interoperability Resources)に変換するニーズがあり、その変換プロセスが複雑で分かりにくいと感じたことはありませんか?インターシステムズは、HL7v2メッセージをFHIR(Fast Healthcare Interoperability Resources)に変換するプロセスを簡単にする、HealthShareメッセージ変換サービス(HealthShare Message Transformation Services)と呼ばれる新しいクラウドベースのSaaSサービスを展開しています。 この新しいサービスの早期アクセス・プレビュー・プログラムを発表できることを嬉しく思います。 必要なのは、無料のAWSアカウントと、HL7v2メッセージをドロップするためのS3バケット、そしてFHIR出力を得るための別のS3バケットだけです。
この機能の簡単なデモをご覧ください。
インターシステムズ社のラーニングサイトでは、AWS の無料アカウントとインターシステムズ社のクラウド・ポータルの無料アカウントにサインアップして、変換サービスの強力な機能を利用するための簡単な[ステップ・バイ・ステップ・ガイド](https://learning.intersystems.com/course/view.php?name=HMTSExerciseS3)を提供しています。 完全なドキュメントは、[インターシステムズ社のドキュメント](http://www.intersystems.com/hmts)でご覧いただけます。
このサービスは、7月下旬に正式に開始される予定です。プレビューが終了しても、最初の100万件の変換を無料で利用することができます。
### インターシステムズ社のこの新しいオファーの詳細は以下の内容です:
HealthShareメッセージ変換サービスの紹介。
医療IT業界では、FHIR® (Fast Healthcare Interoperability Resources)が、ヘルスケアデータを交換するための最新のデータ標準として採用されています。 オンデマンドのHealthShareメッセージ変換サービスを利用することで、医療機関、保険会社、製薬会社は、既存のデータフォーマットをFHIR規格に変換し、データから最大限の価値を引き出すことができます。 インターシステムズは、最新のFHIR規格だけでなく、HL7v2、X12、CDA、C-CDA、DICOMを含むすべての主要なヘルスケア規格を実装しており、ヘルスケアの相互運用性におけるリーダー的存在です。
HealthShareメッセージ変換サービスは、これらの以前の標準からFHIR R4へのメッセージ変換を簡単に行えるように設計されており、初期リリースではHL7 v2メッセージのFHIR R4への変換をサポートしています。 FHIRメッセージは、AWS S3バケットまたはAmazon HealthLake (Preview)に送ることができ、将来的には他のFHIRリポジトリオプションも追加される予定です。
HealthShareメッセージ変換サービスは、HL7v2メッセージをFHIRに変換することを簡単にします。 変換ロジックを気にする必要がないので、メッセージ変換の複雑な作業はインターシステムズに任せて、優れたヘルスケア・アプリケーションの構築に集中することができます。このサービスが提供するのは
- AWS上での簡単なプロビジョニングと起動
- インバウンドS3バケットのHL7v2メッセージのチェック
- HL7コンテンツのバリデーション
- FHIR R4へのメッセージ変換
- 変換されたメッセージを、アウトバウンドの S3 バケット、[InterSystems FHIR Accelerator (Preview) サービス](https://community.intersystems.com/post/new-video-what-fhir-accelerator-service)、または [Amazon HealthLake (Preview)](https://aws.amazon.com/jp/healthlake/) リポジトリにルーティングする
- 変換パイプラインのステータスと統計情報のモニタリング
さらに、このサービスはAWSのインフラ上に構築されているため、ISO 27001:2013およびHIPAAをサポートするHITRUST認証を取得しています。インターシステムズは、このサービスの運用、監視、バックアップを管理しています。
そして何より、このサービスが商業的に開始されると、最初の100万回の変換が無料で提供され、その後は使用した分だけを支払うことになり、変換されたメッセージあたりのコストは非常に低く抑えられます。 このサービスを利用するための長期契約はなく、いつでも解約できます。
皆様からのフィードバックをお待ちしております。 この記事のコメント欄にご意見をお寄せいただくか、 HMTS@InterSystems.com まで直接お問い合わせください。
HL7 ® と FHIR ® は Health Level Seven International の登録商標であり、これらの商標の使用は HL7 による推奨を意味するものではありません。FHIR商標の使用は、HL7によるHealthShare Message Transformation ServicesまたはHealthShare Message Transformation Servicesの推奨を意味するものではありません。