0 フォロワー · 496 投稿

  

InterSystems CachéはマルチモデルのDBMSおよびアプリケーションサーバーです

詳細はこちらをご覧ください

ドキュメント

記事 Tomoko Furuzono · 11月 24, 2020 4m read

最近、InterSystems 内で PHP から Caché ベースの Web サービスに接続が必要になる事例がいくつかありました。 これらの最初の事例とは、実はこの開発者コミュニティそのものであり、他の InterSystems サイト/アプリケーションとのシングルサインオンに Web サービスを使用しています。 次の例は、パスワード認証を使用して PHP から Caché ベースの Web サービス(具体的には SAMPLES ネームスペースの Web サービス)に接続する方法を示しています。


(注意: この例は、/csp/samples に対してパスワード認証が有効になっていることを前提としています。) 

0
0 347
記事 Tomoko Furuzono · 11月 24, 2020 2m read

これは、InterSystems FAQサイトの記事です。
[管理ポータル] > [システムオペレーション] > [ライセンス使用量] ページで表示される各項目の意味は以下のとおりです。
 ① 現在使用中のライセンス数:現時点のライセンスユニット使用数です。

② 最大ライセンス使用:インスタンスが起動した後、現在に至るまでで最もライセンス使用の大きかった時点の
            "現在使用中のライセンス数"です。

③ 許可されたライセンス数(Cache.Key/iris.keyの値):該当システムで許可されている最大ライセンスユニット数です。

④ 現在の接続:現時点のクライアントからの接続数です。

⑤ 最大接続:インスタンスが起動した後、現在に至るまでで最も接続数の大きかった時点の"現在の接続"です。
(A) ローカル:表示しているサーバのインスタンスで消費しているライセンスの情報です。

(B) リモート:マルチサーバライセンスを使用して複数インスタンスでライセンス共有している場合の、共有している全インスタンスで消費しているライセンスの合計値の情報です。
 

※ライセンス共有を行うには、ライセンスサーバの設定が必要です。詳細は下記トピックをご参考になさってください。
複数インスタンスでライセンスを共有する場合に必要な設定

0
0 634
記事 Hiroshi Sato · 11月 23, 2020 2m read

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

 1. エクスポートAPI

a. ルーチンを個別に指定してエクスポートする場合は、$system.OBJ.Export() を使用します。

例: 

do $system.OBJ.Export("TEST1.mac,TEST2.mac","c:\temp\routines.xml",,.errors)

指定する形式は ルーチン名.拡張子 で、拡張子は mac,bas,int,inc,obj を指定します。

エクスポート中のエラーは errors に格納されます。

$system.OBJ.Export() の詳細はクラスリファレンス %SYSTEM.OBJ を参照してください。

b. ワイルドカードを使用してエクスポートする場合にも、$system.OBJ.Export() を使用します。
例: 

do $system.OBJ.Export("*.mac",c:\temp\allmacroutines.xml")

※バージョン2008.1より前では、$system.OBJ.ExportPattern() を使用します。

2. インポート方法

a. ファイルに含まれる全ルーチンをインポートする

ファイルに含まれる全ルーチンをインポートするには $system.OBJ.Load() を使用します。

例: 

0
0 636
記事 Hiroshi Sato · 11月 23, 2020 1m read

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

変数等命名規約に関して、InterSystems自体が定めている規則の様なものはありません。

しかし、チームで開発を行う際には、何らかの命名規約を作ることをお勧めします。

一般的には、変数名から使用目的が容易に想像できる様な名前付けが好ましいです。

名前の長短で、実行性能には有意な差はないと考えて結構ですので、一目見て内容が理解しやすく、他の名前と明確な区別ができるよう、ある程度の長さを持った命名を心がけることをお勧めします。

また、先頭のアルファベットを大文字にする、単語の先頭は大文字にするというのが一般的です。
しかし、昨今は、Javaで良く使われる、先頭は小文字で初めて、途中の単語の始まりを大文字にするという命名規約を使う開発者も増えてきました。

さらに変数のタイプ(一時変数、パラメータなど)がわかるように、先頭の文字で表現する方法もよく使われます。
(例: tSC,pLengthなど)

0
0 165
記事 Tomohiro Iwamoto · 11月 23, 2020 21m read

この記事では、OData API 標準に基づいて開発された RESTful API サービスを利用するための IRIS クライアントの開発について説明します。

HTTP リクエストを作成し、JSON ペイロードの読み取りと書き込みを行い、それらを組み合わせて OData 用の汎用クライアントアダプタを構築する方法を確認するため、多数の組み込み IRIS ライブラリを説明します。 また、JSON を永続オブジェクトに逆シリアル化するための新しい JSON アダプタについても説明します。

RESTful API の操作

REST は World Wide Web の標準化に関する作業から作成された一連の設計原則です。 これらの原則はあらゆるクライアントサーバー通信に適用でき、HTTP API が RESTful であることを説明するためによく使用されます。

REST はステートレスなリクエスト、キャッシュ処理、統一した API 設計など、さまざまな原則を網羅しています。 ただし、詳細な実装については網羅していません。また、これらのギャップを埋めるための一般的な API 仕様は存在しません。

この曖昧さは、RESTful API に幾分かの理解、ツール、より厳密なエコシステムを中心によく構築されるライブラリが不足している原因となっています。 特に、開発者は RESTful API の検出と文書化のために独自のソリューションを構築する必要があります。

OData

OData は、一貫性のある RESTful API を構築するための OASIS の仕様です。 OASIS コミュニティには、Microsoft / Citrix / IBM / Red Hat / SAP などの有名なソフトウェア会社が参加しています。 2007 年に OData 1.0 が最初に導入され、最新バージョンの 4.1 が今年リリースされました。

OData の仕様は、メタデータ、一貫性のある操作の実装、クエリ、例外処理などを対象としています。 また、アクションや関数などの追加機能も対象としています。

TripPinWS OData API の説明

この記事では、Odata.org が例示している TripPinWS API を使用します。

他の RESTful API と同様に、一般的にはサービスのベース URL が必要です。 OData でこのベース URL にアクセスすると、API エンティティのリストも返されます。

https://services.odata.org:443/V4/(S(jndgbgy2tbu1vjtzyoei2w3e))/TripPinServiceRW

この API には Photos、People、Airlines、Airports、Me のエンティティと、GetNearestAirport という関数が含まれていることがわかります。

応答には、TripPinWS メタデータドキュメントへのリンクも含まれています。

https://services.odata.org/V4/(S(djd3m5kuh00oyluof2chahw0))/TripPinServiceRW/$metadata

このメタデータは XML ドキュメントとして実装されており、独自の XSD ドキュメントが含まれています。 これにより、IRIS XML スキーマウィザードから生成されるコードを使用してメタデータドキュメントを消費する可能性が広がります。

メタデータドキュメントは一見かなり複雑に見えるかもしれませんが、エンティティのスキーマ定義を構成するために使用されるタイプのプロパティを表しているだけです。

次の URL を使用すると、API から People のリストを取得できます。

https://services.odata.org/V4/(S(4hkhufsw5kohujphemn45ahu))/TripPinServiceRW/People

この URL は 8 人のリストを返します。この 8 という数値は、厳密な結果ごとのエンティティ数の上限値です。 実際には、これよりもはるかに大きな上限値を使用することになるでしょう。 ただし、OData が @odata.nextLink などの追加のハイパーテキストリンクを含んでいる例も示されています。そのリンクを使用すると、People を検索した結果の次のページを取得できます。

また、次のようにして上位 1 件の結果のみを選択するなど、クエリ文字列値を使用して結果リストを絞り込むこともできます。

https://services.odata.org/V4/(S(4hkhufsw5kohujphemn45ahu))/TripPinServiceRW/People?$top=1

FirstName でリクエストを絞り込むこともできます。

https://services.odata.org/V4/(S(4hkhufsw5kohujphemn45ahu))/TripPinServiceRW/People?$filter=FirstName eq 'Russell'

この例では eq 演算子を使用し、「Russell」に等しいすべての FirstName を抽出しました。 ここでは対象の文字列を一重引用符で囲むことが重要です。 OData では、さまざまな演算子を組み合わせて表現力の高い検索クエリを作成することができます。

IRIS %Net パッケージ

IRIS には、包括的な標準ライブラリが含まれています。 私たちは FTP / メール / LDAP / HTTP などのプロトコルをサポートする %Net パッケージを使用することになります。

TripPinWS サービスを使用するには、HTTPS を使用する必要があります。そのためには、IRIS 管理ポータルに HTTPS 構成を登録する必要があります。 複雑な証明書をインストールする必要はないため、次のように数ステップで作業を完了することができます。

  • IRIS 管理ポータルを開きます。
  • [ システム 管理] > [セキュリティ] > [SSL/TLS 構成] をクリックします。
  • [新規構成の作成] ボタンをクリックします。
  • 「odata_org」という構成名を入力し、[保存] をクリックします。
  • ここでは任意の名前を選択できますが、記事の残りの部分では odata_org を使用します。

これで、HttpRequest クラスを使用して全員のリストを取得できるようになりました。 Get() が動作すると、OK の場合に 1 が返ってきます。 その後、次のように応答オブジェクトにアクセスして結果を端末に出力できます。

DC>set req=##class(%Net.HttpRequest).%New()
DC>set req.SSLConfiguration="odata_org"
DC>set sc=req.Get("https://services.odata.org:443/V4/(S(jndgbgy2tbu1vjtzyoei2w3e))/TripPinServiceRW/People")
DC>w sc
1
DC>do req.HttpResponse.OutputToDevice()

先に進む前に、基本的な HttpRequest を自由に試してみてください。 Airlines や Airports を取得してみたり、不正な URL を入力した場合にどのようなエラーが返ってくるかを調べたりしてください。

汎用 OData クライアントの開発

HttpRequest クラスを抽象化し、さまざまな OData クエリオプションの実装を簡単にする汎用 OData クライアントを作成しましょう。

このクライアントを DcLib.OData.Client と名付け、%RegisteredObject を拡張して作成します。 また、特定の OData サービスの名前を定義するために使用できるいくつかのサブクラスと、HttpRequest オブジェクトなどのランタイムオブジェクトと値をカプセル化するいくつかのプロパティを定義します。

OData クライアントのインスタンス化を簡単にするため、%OnNew() メソッド(クラスのコンストラクタメソッド)もオーバーライドし、それを使用して実行時のプロパティを設定します。

Class DcLib.OData.Client Extends %RegisteredObject
{
Parameter BaseURL;
Parameter SSLConfiguration;
Parameter EntityName;
Property HttpRequest As %Net.HttpRequest;
Property BaseURL As %String;
Property EntityName As %String;
Property Debug As %Boolean [ InitialExpression = 0 ];
Method %OnNew(pBaseURL As %String = "", pSSLConfiguration As %String = "") As %Status [ Private, ServerOnly = 1 ]
{
   set ..HttpRequest=##class(%Net.HttpRequest).%New()
   set ..BaseURL=$select(pBaseURL'="":pBaseURL,1:..#BaseURL)
   set ..EntityName=..#EntityName
   set sslConfiguration=$select(pSSLConfiguration'="":pSSLConfiguration,1:..#SSLConfiguration)
   if sslConfiguration'="" set ..HttpRequest.SSLConfiguration=sslConfiguration
   quit $$$OK
}
}

このように DcLib.OData.Client を拡張し、BaseURL と SSL 構成パラメーターを一箇所で設定することにより、TripPinWS サービスに固有のクライアントクラスを定義できるようになります。

Class TripPinWS.Client Extends DcLib.OData.Client
{
Parameter BaseURL = "https://services.odata.org:443/V4/(S(jndgbgy2tbu1vjtzyoei2w3e))/TripPinServiceRW";
Parameter SSLConfiguration = "odata_org";
}

この基本クライアントを使用すれば、サービスで使用したいエンティティタイプごとにクラスを作成できます。 この新しいクライアントクラスを拡張すれば、EntityName パラメーターでエンティティ名を定義するだけで済みます。

Class TripPinWS.People Extends TripPinWS.Client
{
Parameter EntityName = "People";
}

次に、エンティティのクエリを簡単にするため、基本の DcLib.OData.Client クラスにさらにいくつかのメソッドを追加する必要があります。

Method Select(pSelect As %String) As DcLib.OData.Client
{
   do ..HttpRequest.SetParam("$select",pSelect)
   return $this
}
Method Filter(pFilter As %String) As DcLib.OData.Client
{
   do ..HttpRequest.SetParam("$filter",pFilter)
   return $this
}
Method Search(pSearch As %String) As DcLib.OData.Client
{
   do ..HttpRequest.SetParam("$search",pSearch)
   return $this
}
Method OrderBy(pOrderBy As %String) As DcLib.OData.Client
{
   do ..HttpRequest.SetParam("$orderby",pOrderBy)
   return $this
}
Method Top(pTop As %String) As DcLib.OData.Client
{
   do ..HttpRequest.SetParam("$top",pTop)
   return $this
}
Method Skip(pSkip As %String) As DcLib.OData.Client
{
   do ..HttpRequest.SetParam("$skip",pSkip)
   return $this
}
Method Fetch(pEntityId As %String = "") As DcLib.OData.ClientResponse
{
   if pEntityId="" return ##class(DcLib.OData.ClientResponse).%New($$$ERROR($$$GeneralError,"Entity ID must be provided"),"")
   set pEntityId="('"_pEntityId_"')"
   if $extract(..BaseURL,*)'="/" set ..BaseURL=..BaseURL_"/"
   set sc=..HttpRequest.Get(..BaseURL_..EntityName_pEntityId,..Debug)
   set response=##class(DcLib.OData.ClientResponse).%New(sc,..HttpRequest.HttpResponse,"one")
   quit response
}
Method FetchCount() As DcLib.OData.ClientResponse
{
   if $extract(..BaseURL,*)'="/" set ..BaseURL=..BaseURL_"/"
   set sc=..HttpRequest.Get(..BaseURL_..EntityName_"/$count")
   set response=##class(DcLib.OData.ClientResponse).%New(sc,..HttpRequest.HttpResponse,"count")
   quit response
}
Method FetchAll() As DcLib.OData.ClientResponse
{
   #dim response As DcLib.OData.ClientResponse
   if $extract(..BaseURL,*)'="/" set ..BaseURL=..BaseURL_"/"
   set sc=..HttpRequest.Get(..BaseURL_..EntityName,..Debug)
   set response=##class(DcLib.OData.ClientResponse).%New(sc,..HttpRequest.HttpResponse,"many")
   if response.IsError() return response
   //応答に nextLink が含まれる場合は、さらにデータを取得し続ける必要があります。
   while response.Payload.%IsDefined("@odata.nextLink") {
       //前の値を配列に退避し、新しい値をその配列にプッシュしてから
       //新しい応答にそれを設定し直し、新しい値のイテレータを作成します。
       set previousValueArray=response.Payload.value
       set sc=..HttpRequest.Get(response.Payload."@odata.nextLink",..Debug)
       set response=##class(DcLib.OData.ClientResponse).%New(sc,..HttpRequest.HttpResponse)
       if response.IsError() return response
       while response.Value.%GetNext(.key,.value) {
           do previousValueArray.%Push(value)    
       }
       set response.Payload.value=previousValueArray
       set response.Value=response.Payload.value.%GetIterator()
   }
   return response
}

ここでは 9 つの新しいメソッドを追加しました。 最初の 6 つはクエリオプションを定義するためのインスタンスメソッドであり、最後の 3 つは 1 つのエンティティ、すべてのエンティティ、またはすべてのエンティティのカウントを取得するためのメソッドです。

最初の 6 つのメソッドは、基本的に HTTP リクエストオブジェクトにパラメーターを設定するためのラッパーです。 コードを実装しやすくするため、これらの各メソッドはこのオブジェクトのインスタンスを返し、メソッドをチェーン化できるようにしています。

メインの Fetch() メソッドについて説明する前に、Filter() メソッドの動作を見てみましょう。

set people=##class(TripPinWS.People).%New().Filter("UserName eq 'ronaldmundy'").FetchAll()
while people.Value.%GetNext(.key,.person) {
 write !,person.FirstName," ",person.LastName    
}

このメソッドを使用すると、次のような結果が返ってきます。

Ronald Mundy

このサンプルコードでは、TripPinWS Peopleオブジェクトのインスタンスを作成しています。 これにより、ベース URL と基本クラスの証明書の構成が設定されます。 その後、その Filter メソッドを呼び出してフィルタークエリを定義してから FetchAll() を呼び出すと HTTP リクエストを呼び出すことができます。

ここで直接アクセスできるのは生の JSON データではなく、動的オブジェクトとしての People の結果であることに注意してください。 これは、例外処理を簡単にする ClientResponse オブジェクトも実装するためです。 また、返される結果のタイプに応じて動的オブジェクトを生成します。

まず、FetchAll() メソッドについて説明します。 この段階で、実装クラスが基本クラス構成で OData URL を定義しており、ヘルパーメソッドが追加のパラメーターを設定しています。FetchAll() メソッドは URL を組み立てて GET リクエストを発行する必要があります。 元のコマンドラインの例と同様に HttpRequest クラスで Get() メソッドを呼び出し、その結果から ClientResponse を作成します。

API が一度に 8 件しか結果を返さないため、このメソッドは複雑になっています。 コード内でこの制限に対処し、前の結果の nextLink 値を使用して最後のページに到達するまで次の結果ページを取得し続ける必要があります。 追加の各ページを取得する際に前の結果配列を保管してから、新しい結果をそれぞれその配列にプッシュしています。

Fetch() / FetchAll() / FetchCount() メソッドは、DcLib.OData.ClientResponse というクラスのインスタンスを返します。 例外を処理し、有効な JSON 応答を自動的に逆シリアル化するためにこのクラスを作成しましょう。

Class DcLib.OData.ClientResponse Extends %RegisteredObject
{
Property InternalStatus As %Status [ Private ];
Property HttpResponse As %Net.HttpResponse;
Property Payload As %Library.DynamicObject;
Property Value;
Method %OnNew(pRequestStatus As %Status, pHttpResponse As %Net.HttpResponse, pValueMode As %String = "") As %Status [ Private, ServerOnly = 1 ]
{
   //直接の HTTP エラーをチェック
   set ..InternalStatus = pRequestStatus
   set ..HttpResponse = pHttpResponse
   if $$$ISERR(pRequestStatus) {
       if $SYSTEM.Status.GetOneErrorText(pRequestStatus)["" set ..InternalStatus=$$$ERROR($$$GeneralError,"Could not get a response from HTTP server, server could be uncontactable or server details are incorrect")
       return $$$OK
   }
   
   //モードが count の場合、応答は JSON ではなく単なる数値になります。
   //数値であることを確認し、true ならばすべて ok を返しますが、それ以外の場合は
   //JSON で表現されるエラーを検出するためにフォールスルーします。
   if pValueMode="count" {
       set value=pHttpResponse.Data.Read(32000)
       if value?1.N {
           set ..Value=value
           return $$$OK
       }
   }
   
   //JSON ペイロードをシリアル化し、シリアル化エラーをキャッチします。
   try {
       set ..Payload={}.%FromJSON(pHttpResponse.Data)    
   } catch err {
       //先に HTTP ステータスコードのエラーをチェックします。
       if $e(pHttpResponse.StatusCode,1)'="2" {
           set ..InternalStatus = $$$ERROR($$$GeneralError,"Unexpected HTTP Status Code "_pHttpResponse.StatusCode)
           if pHttpResponse.Data.Size>0 return $$$OK
       }
       set ..InternalStatus=err.AsStatus()
       return $$$OK
   }
   
   //OData エラーのペイロードをチェックします。
   if ..Payload.%IsDefined("error") {
       do ..HttpResponse.Data.Rewind()
       set error=..HttpResponse.Data.Read(32000)
       set ..InternalStatus=$$$ERROR($$$GeneralError,..Payload.error.message)    
       return $$$OK
   }
   
   //すべて ok なら、必要なモード(many, one, count)に一致するように応答値を設定します。
   if pValueMode="one" {
       set ..Value=..Payload
   } else {
       set iterator=..Payload.value.%GetIterator()
       set ..Value=iterator
   }
   
   return $$$OK
}
Method IsOK()
{
   return $$$ISOK(..InternalStatus)
}
Method IsError()
{
   return $$$ISERR(..InternalStatus)
}
Method GetStatus()
{
   return ..InternalStatus
}
Method GetStatusText()
{
   return $SYSTEM.Status.GetOneStatusText(..InternalStatus)
}
Method ThrowException()
{
   Throw ##class(%Exception.General).%New("OData Fetch Exception","999",,$SYSTEM.Status.GetOneStatusText(..InternalStatus))
}
Method OutputToDevice()
{
   do ..HttpResponse.OutputToDevice()
}
}

ClientResponse オブジェクトのインスタンスが与えられた場合、最初にテストを実行してエラーがあったかどうかを確認することができます。 エラーは複数のレベルで発生する可能性があるため、単一の使いやすいソリューションでエラーを返すのが望ましいです。

set response=##class(TripPinWS.People).%New().Filter("UserName eq 'ronaldmundy'").FetchAll()
if response.IsError() write !,response.GetStatusText() quit

IsOK() メソッドと IsError() メソッドはオブジェクトのエラーをチェックします。 エラーが発生した場合は GetStatus() または GetStatusText() を呼び出してエラーにアクセスするか、ThrowException() を使用してエラーを例外ハンドラに渡すことができます。

エラーが発生していない場合、ClientResponse は生のペイロードオブジェクトを応答ペイロードのプロパティに代入します。

set ..Payload={}.%FromJSON(pHttpResponse.Data)

次に、応答の Value プロパティを単一のインスタンスとして、または多数の結果を探索するための配列イテレータとして、ペイロード内のメインデータ配列に設定します。

私はこれらすべてのコードを GitHub https://github.com/SeanConnelly/IrisOData/blob/master/README.md 上の単一のプロジェクトに格納しています。そこで全体を見直せば、より深く理解することができるでしょう。 次の例はすべて、ソースの GitHub プロジェクトに含まれています。

OData クライアントの使用

基本 Client クラスに関しては、With() メソッドも理解しておく必要があります。 すべてのエンティティのインスタンスを作成する代わりに、単一のクライアントクラスだけで With() メソッドを使用することができます。 With() メソッドは、指定されたエンティティ名で新しいクライアントを定義します。

ClassMethod With(pEntityName As %String) As DcLib.OData.Client
{
   set client=..%New()
   set client.EntityName=pEntityName
   return client
}

このメソッドを使用すれば、次のように基本 Client クラスですべての People を取得できます。

/// 基本クライアントクラスと .With("People") を使用してすべての "People" を取得します
ClassMethod TestGenericFetchAllUsingWithPeople()
{
   #dim response As DcLib.OData.ClientResponse
   set response=##class(TripPinWS.Client).With("People").FetchAll()
   
   if response.IsError() write !,response.GetStatusText() quit
   
   while response.Value.%GetNext(.key,.person) {
       write !,person.FirstName," ",person.LastName    
   }
}

または、次のようにクラスごとのエンティティを使用します。

/// People クラスを使用してすべての "People" を取得します
ClassMethod TestFetchAllPeople()
{
   #dim people As DcLib.OData.ClientResponse
   set people=##class(TripPinWS.People).%New().FetchAll()
   
   if people.IsError() write !,people.GetStatusText() quit
   
   while people.Value.%GetNext(.key,.person) {
       write !,person.FirstName," ",person.LastName    
   }
}

ご覧のとおり、これらの方法は非常に似通っています。 どちらの方法を選択すべきかは、具体的なエンティティについて自動補完がどれほど重要であるか、および具体的なエンティティクラスにエンティティ固有のメソッドを追加するかどうかによって異なります。

DC>do ##class(TripPinWS.Tests).TestFetchAllPeople()
Russell Whyte
Scott Ketchum
Ronald Mundy
… およびその他の人

次に、Airlines についても同じ処理を実装しましょう。

/// すべての "Airlines" を取得します
ClassMethod TestFetchAllAirlines()
{
   #dim airlines As DcLib.OData.ClientResponse
   set airlines=##class(TripPinWS.Airlines).%New().FetchAll()
   
   if airlines.IsError() write !,airlines.GetStatusText() quit
   
   while airlines.Value.%GetNext(.key,.airline) {
       write !,airline.AirlineCode," ",airline.Name    
   }
}

そして、コマンドラインから次の結果を得ることができます。

DC>do ##class(TripPinWS.Tests).TestFetchAllAirlines()
AA American Airlines
FM Shanghai Airline
… およびその他の航空会社

次は Airports の実装です。

/// すべての "Airports" を取得します
ClassMethod TestFetchAllAirports()
{
   #dim airports As DcLib.OData.ClientResponse
   set airports=##class(TripPinWS.Airports).%New().FetchAll()
   
   if airports.IsError() write !,airports.GetStatusText() quit
   
   while airports.Value.%GetNext(.key,.airport) {
       write !,airport.IataCode," ",airport.Name    
   }
}

そして、コマンドラインから次の結果を得ることができます。

DC>do ##class(TripPinWS.Tests).TestFetchAllAirports()
SFO San Francisco International Airport
LAX Los Angeles International Airport
SHA Shanghai Hongqiao International Airport
… およびその他の空港

これまでは FetchAll() メソッドを使用してきました。 次のように Fetch() メソッドを使用し、エンティティの主キーを使用して単一のエンティティを取得することもできます。

/// 人の識別子を使用して単一の "People" エンティティを取得します
ClassMethod TestFetchPersonWithID()
{
   #dim response As DcLib.OData.ClientResponse
   set response=##class(TripPinWS.People).%New().Fetch("russellwhyte")
   
   if response.IsError() write !,response.GetStatusText() quit
   
   //新しいフォーマッターを使用して出力を美しく整形してみましょう(最新バージョンの IRIS のみ)
   set jsonFormatter = ##class(%JSON.Formatter).%New()
   do jsonFormatter.Format(response.Value)
}

この例では動的配列またはオブジェクトを取得し、整形したJSONに出力できる新しい JSON フォーマッタークラスを使用しています。

DC>do ##class(TripPinWS.Tests).TestFetchPersonWithID()
{
 "@odata.context":"http://services.odata.org/V4/(S(jndgbgy2tbu1vjtzyoei2w3e))/TripPinServiceRW/$metadata#People/$entity",
 "@odata.id":"http://services.odata.org/V4/(S(jndgbgy2tbu1vjtzyoei2w3e))/TripPinServiceRW/People('russellwhyte')",
 "@odata.etag":"W/\"08D720E1BB3333CF\"",
 "@odata.editLink":"http://services.odata.org/V4/(S(jndgbgy2tbu1vjtzyoei2w3e))/TripPinServiceRW/People('russellwhyte')",
 "UserName":"russellwhyte",
 "FirstName":"Russell",
 "LastName":"Whyte",
 "Emails":[
   "Russell@example.com",
   "Russell@contoso.com"
 ],
 "AddressInfo":[
   {
     "Address":"187 Suffolk Ln.",
     "City":{
       "CountryRegion":"United States",
       "Name":"Boise",
       "Region":"ID"
     }
   }
 ],
 "Gender":"Male",
 "Concurrency":637014026176639951
}

OData の永続化

最後のいくつかの例では、新しい JSON アダプタクラスを使用して OData JSON を永続オブジェクトに逆シリアル化する方法を説明します。 ここでは Person、Address、City の 3 つのクラスを作成しますが、いずれも Person のデータ構造を OData メタデータに反映します。 また、@odata.context のような追加の OData プロパティが逆シリアル化エラーをスローしないよう、1 に設定された %JSONIGNOREINVALIDFIELD を使用します。

Class TripPinWS.Model.Person Extends (%Persistent, %JSON.Adaptor)
{
Parameter %JSONIGNOREINVALIDFIELD = 1;
Property UserName As %String;
Property FirstName As %String;
Property LastName As %String;
Property Emails As list Of %String;
Property Gender As %String;
Property Concurrency As %Integer;
Relationship AddressInfo As Address [ Cardinality = many, Inverse = Person ];
Index UserNameIndex On UserName [ IdKey, PrimaryKey, Unique ];
}
Class TripPinWS.Model.Address Extends (%Persistent, %JSON.Adaptor)
{
Property Address As %String;
Property City As TripPinWS.Model.City;
Relationship Person As Person [ Cardinality = one, Inverse = AddressInfo ];
}
Class TripPinWS.Model.City Extends (%Persistent, %JSON.Adaptor)
{
Property CountryRegion As %String;
Property Name As %String;
Property Region As %String;
}

次に、OData サービスから Russel Whyte を取得し、Person モデルの新しいインスタンスを作成した後に応答値を使用して %JSONImport() メソッドを呼び出します。 これにより、Address と City の詳細とともに Person オブジェクトにデータが入力されます。

ClassMethod TestPersonModel()
{
   #dim response As DcLib.OData.ClientResponse
   set response=##class(TripPinWS.People).%New().Fetch("russellwhyte")
   
   if response.IsError() write !,response.GetStatusText() quit
   
   set person=##class(TripPinWS.Model.Person).%New()
   
   set sc=person.%JSONImport(response.Value)
   if $$$ISERR(sc) write !!,$SYSTEM.Status.GetOneErrorText(sc) return
   
   set sc=person.%Save()
   if $$$ISERR(sc) write !!,$SYSTEM.Status.GetOneErrorText(sc) return
}

次に、次のように SQL コマンドを実行してデータが永続化されていることを確認できます。

SELECT ID, Concurrency, Emails, FirstName, Gender, LastName, UserName
FROM TripPinWS_Model.Person
ID                          Concurrency                      Emails                                                                                    FirstName    Gender    LastName    UserName
russellwhyte    637012191599722031    Russell@example.com Russell@contoso.com    Russell            Male         Whyte            russellwhyte

最終的な考え

上記のように、組み込みの %NET クラスを使用して RESTful な OData サービスを利用するのは簡単です。 少しばかりの追加ヘルパーコードを使用すれば、OData クエリの構築を単純化し、エラーレポートを統合し、JSON を動的オブジェクトに自動的に逆シリアル化できます。

そして、必要に応じてベース URL と HTTPS 構成を指定するだけで、新しい OData クライアントを作成できます。 さらに、この単一のクラスと .With('エンティティ') メソッドを使用してサービス上の任意のエンティティを利用するか、関心のあるエンティティの名前付きサブクラスを作成することができます。

また、新しい JSON アダプタを使用して JSON 応答を永続クラスに直接逆シリアル化できることも説明しました。 現実的には最初にこのデータを非正規化することを検討し、JSON アダプタクラスがカスタムマッピングで機能することを確認する必要があります。

最後になりますが、OData の操作は非常に簡単です。 私が特注の実装でよく経験する場合よりもはるかに少ないコード量でサービス実装の一貫性を維持することができました。 私は RESTful 設計の自由さを楽しんでいますが、次のサーバーサイドソリューションでは標準を実装することを検討したいと思います。

0
0 301
記事 Mihoko Iijima · 11月 20, 2020 2m read

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

バックアップには、以下4種類の方法があります。

  1. 外部バックアップ
  2. オンラインバックアップ(コンカレントバックアップ)
  3. コールドバックアップ
  4. レガシー並行外部バックアップ

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

バックアップの方法について【IRIS】
バックアップの方法について

1、2、4は、インスタンスを停止せずにバックアップが行えます(末尾の関連情報もご参照ください)。3 は、インスタンスを停止した後でバックアップを行う方法です。

またシステム構成や障害バターンを基にバックアップを設計、計画する上での注意点、自動化のサンプルやバックアップを実施する上で役立つ各種関連技術についてはInterSystems Symposia 2014 発表資料 をご参照ください(※1)。

※1:InterSystems Symposia 2014でご紹介した内容で、Cachéと記載がありますがバックアップ方法についてはIRISも同様です。

【関連情報】(コミュニティ/FAQトピックをリンクしています)

0
0 612
記事 Mihoko Iijima · 11月 20, 2020 3m read

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

(2025/2/17更新)担当される役割や学習目的に合わせた最適なラーニングパス(学習経路)を確認できるページ :InterSystems ラーニングパス をご利用ください(ページの使い方については、記事「学習目的に合わせた最適なラーニングパス(学習経路)を確認できるページ」をご参照ください)。

また、現在ご覧いただいている開発者コミュニティの中では、初心者(beginner)タグ があり、下記セルフラーニング用資料/ビデオをご用意しています。ぜひご参照ください。

セルフラーニングビデオ以外にも、過去に開催したウェビナーアーカイブビデオも公開しています👉「開発者向けウェビナー:アーカイブビデオ一覧

ぜひご参照ください。

0
1 616
記事 Toshihiko Minamoto · 11月 18, 2020 3m read

FTP ファイルを Intersystems Caché からダウンロードするメソッドを以下に示します。ご質問がある場合はメッセージをお寄せください。

ClassMethod FTPDownload(myFTP = "", myUserName = "", myPassword = "", sFileLocation = "", dLocation = "", noOfdownloadFile = 1, sourceFileDel = )
{
 /*---------------------------------------------------------------------------------------------------------------------------
 要件に従ってファイルをダウンロードします  : FTP

メソッド : 再利用可能 

作成者 : Sanjib Raj Pandey、30/03/2018 に作成

 downLoadFile = ファイル数またはすべてのファイル  ...... ダウンロードしたいファイルを指定します、デフォルト値は 1 です。
   = 1,3,7,100 ファイルなどの値を指定します。
   = すべてのファイルをダウンロードするには "*" を指定します。
 
  SourceFileDel = ダウンロード後にソースフォルダーのファイルを削除したい場合は..... 
                  この値を 1 に設定します  -- ;   デフォルト値 :  0 
                  1= True (ダウンロード後にソースフォルダーを削除する)、 0 = False(コピーのみ)
 
  sFileLocation = ソースファイルの場所(フォルダー)
  dLocation = 宛先フォルダー
例 :
  以下の内容は ..... すべてのファイルをソースから宛先フォルダーに移動します。
  w ##class(CW.COMMON).FTPDownload("IP アドレス","ユーザー名","パスワード","ソースフォルダー","宛先フォルダー,"*",1)
 
以下の内容は...... 200 ファイルをソースから宛先フォルダーにコピーします。
w ##class(CW.COMMON).FTPDownload("IP アドレス","ユーザー名","パスワード","ソースフォルダー","宛先フォルダー,200,0)

  -------------------------------------------------------------------------------------------------------------------------
 */
 // Try .. Catch のようなエラー制御をセットアップできます。 
 
 Set (count,fileNo,key,messge,fileStream,myFileName,myFile,fSave,eMessage,eSubject)=""
 Set fIp= myFTP 
 Set fUserName= myUserName 
 set fPassword=myPassword 
 set sFileLocaion=sFileLocation 
 set dLocation=dLocation 
 Set downloadFile=noOfdownloadFile 
 Set sourceFileDel=sourceFileDel
 
 If $Length(fIp)=0||($L(fUserName)=0)||($L(fPassword)=0) || ($L(downloadFile)=0) "資格情報が無効であるか、ダウンロードファイルが 0 です!IP、ユーザー名、パスワード、FTP または宛先の場所を確認してください!"      
 Set myFtp=##class(%Net.FtpSession).%New()
 Set eMessage="FTP 接続に失敗しました。"_fIp_" またはユーザー名、パスワードをチェックしてください!"
 Set eSubject ="FTP 警告メッセージ。"  
 Set myFtp.Timeout = 60
 If 'myFtp.Connect(fIp,fUserName,fPassword) Quit  $$EVEMAIL^CW.COMMON(eSubject,eMessage)
 Do myFtp.SetDirectory(sFileLocaion)
 If 'myFtp.NameList(" ",.x) Quit "ファイルが見つかりません "
 Set fileStream = ##class(%Stream.FileBinary).%New()
 Set message ="コピー"
 Set myFileName=""
 Set fileNo=0
 Set Key=""
 If (downloadFile = "*")
 {
   While (x.GetNext(.Key))'=""
 {
       Do StartCopy
 }
Do myFtp.Logout()
Quit fileNo_" ファイルが正常に"_message_"されました!"   
 }
 
 If (downloadFile >0)
 {
       Set count=1
       While ((count <= downloadFile) && (count<=x.Count()))
  {
   do StartCopy
  Set count=count +1
  }
  Do myFtp.Logout()
  Quit fileNo_" ファイルが正常に"_message_"されました!" 
 }
  
StartCopy
  Set myFileName= x.GetNext(.fileNo)
   Do myFtp.Binary()
  Do myFtp.Retrieve(myFileName,.fileStream)
  Set myFile= ##class(%Library.FileBinaryStream).%New()
   Set myFile.Filename=dLocation_myFileName
  Do myFile.CopyFrom(fileStream)
  Set fSave=myFile.%Save()
   IF ((sourceFileDel=1) && (fSave = 1))
  {
     Do myFtp.Delete(myFileName)
      Set message="移動"
}

0
0 357
記事 Hiroshi Sato · 11月 16, 2020 2m read

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

Config.Configurationクラス、SYS.Databaseクラスのメソッドを使用して、ネームスペース・データベースの作成及び登録をターミナルから実行することができます。

以下はデータベースファル/CacheDB/AAA/cache.datを作成し、構成ファイル(cache.cpf)にデータベース AAA、及び、ネームスペースAAAの登録を行う一連の実行例です。
*実行は、%SYSネームスペースで行って下さい。*

Set Directory="/CacheDB/AAA/"
Set x=$ZF(-100, "/shell", "mkdir", Directory)
Set db=##Class(SYS.Database).%New()
Set db.Directory=Directory
Set status=db.%Save()
Set DBName="AAA"
Set status=##class(Config.Configuration).AddDatabase(DBName,Directory)
Set NSName=DBName
Set status=##class(Config.Configuration).AddNamespace(NSName,DBName)
0
0 359
記事 Hiroshi Sato · 11月 16, 2020 1m read

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

InterSystems ObjectScript言語では引数の異なる同名のメソッドを定義することはできません。

InterSystems ObjectScript言語は一般的に動的言語と呼ばれるプログラミング言語に分類されます。

ObjectScriptではメソッド実行時にどの引数を使用するかどうかは自由に制御可能ですので、動的プログラミング言語ではないJava等の言語と異なり、コンパイルの段階で厳密に引数の数でメソッドを区別する必要がありません。

従って ObjectScript言語は、オーバーロードと一般に呼ばれる言語仕様を含んでいません。

Java等で記述されたプログラムを移植する際にオーバーロード相当の機能を実現するには、

ClassMethod test(args... as %String)

のように引数の後ろに ... を付加します。

これにより、可変長引数を渡すことが可能です。

このメソッドに複数の引数が渡された場合は、args(1)=第一引数 args(2)=第二引数というように順次設定されます。

これを使用して、メソッドコード中で渡された引数の個数を取得して処理を分岐させることができます。
※ただし、引数のデータタイプを判別することはできません。

0
0 139
記事 Mihoko Iijima · 11月 12, 2020 3m read

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

システムルーチン ^DBSIZE を利用するとバックアップファイルサイズを見積もることができます(メモ1もご参照ください)。

^DBSIZE は、データベース・バックアップ・リストに選択されたデータベースを対象に、フルバックアップ/累積バックアップ/差分バックアップそれぞれのファイルサイズを見積もります。

なお、データベース・バックアップ・リストは、管理ポータルの [システム管理] > [構成] > [データベースバックアップ] > [データベース・バックアップ・リスト] から作成します。

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

^DBSIZEによるバックアップ・サイズの見積もり【IRIS】

^DBSIZEによるバックアップ・サイズの見積もり

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

0
0 356
記事 Toshihiko Minamoto · 11月 11, 2020 22m read

こんにちは! この記事は「Prometheus で InterSystems Caché を監視する」の続きになります。 ここでは ^mgstat ツールの動作結果を視覚化する方法を見ていきます。 このツールを使用すると、Caché のパフォーマンス統計、具体的なグローバルとルーチンの呼び出し数(ローカルおよびECP 経由)、書き込みデーモンのキュー長、ディスクに保存されるブロックと読み取られるブロックの数、ECP トラフィックの量などを取得できます。 ^mgstat は(対話的に、またはジョブによって)単独で起動したり、別のパフォーマンス測定ツールである ^pButtons と並行して起動したりできます。

ここでは 2 つのパートに分けて説明したいと思います。最初のパートでは ^mgstat によって収集された統計を図示し、2 番目のパートではこの統計を正確に収集する方法を集中して取り上げます。 手短に言えば、ここでは $zu関数 を使用しています。 ただし、SYS.Stats パッケージのクラス経由で収集できる大部分のパラメーターに対応したオブジェクトインターフェースがあります。 ^mgstat に表示されるのは、収集できるパラメーターのほんの一部です。 その後、Grafana ダッシュボードですべてのパラメーターを表示してみましょう。 今回は ^mgstat によって提供されるパラメーターのみを使用します。 また、Docker コンテナについても説明のために少しだけ取り上げます。

Docker のインストール

最初のパートでは tarball から Prometheus と Grafana をインストールする方法を説明しています。 ここでは Docker の機能を使用して監視サーバーを起動する方法を説明します。 以下はデモ用のホストマシンです。

# uname -r
4.8.16-200.fc24.x86_64
# cat /etc/fedora-release
Fedora release 24 (Twenty Four)

さらに 2 台の仮想マシン(192.168.42.131 と 192.168.42.132)が VMWare Workstation Pro 12.0 環境で使用され、どちらも Caché がインストールされています。 これらのマシンが監視対象になります。 バージョンは次のとおりです。

# uname -r
3.10.0-327.el7.x86_64
# cat /etc/redhat-release
Red Hat Enterprise Linux Server release 7.2 (Maipo)

USER>write $zversion
Cache for UNIX (Red Hat Enterprise Linux for x86-64) 2016.2 (Build 721U) Wed Aug 17 2016 20:19:48 EDT

ホストマシンに Docker をインストールして起動しましょう。

# dnf install -y docker
# systemctl start docker
# systemctl status docker
● docker.service — Docker Application Container Engine
Loaded: loaded (/usr/lib/systemd/system/docker.service; disabled; vendor preset: disabled)
Active: active (running) since Wed 2017-06-21 15:08:28 EEST; 3 days ago
...

Docker コンテナで Prometheus を起動する

最新の Prometheus イメージをロードしましょう。

# docker pull docker.io/prom/prometheus

Docker ファイルを参照すると、イメージが /etc/prometheus/prometheus.yml ファイルから構成を読み取り、収集された統計が /prometheusフォルダーに保存されていることがわかります。


CMD [ "-config.file=/etc/prometheus/prometheus.yml", \
"-storage.local.path=/prometheus", \
...

Docker コンテナで Prometheus を起動する際に、ホストマシンから構成ファイルとメトリックデータベースをロードするようにしましょう。 こうすることで、コンテナの再起動を「乗り切る」ことができます。 次に、ホストマシン上に Prometheus 用のフォルダーを作成しましょう。

# mkdir -p /opt/prometheus/data /opt/prometheus/etc

そして、次のような Prometheus の構成ファイルを作成しましょう。

cat /opt/prometheus/etc/prometheus.yml
global:
  scrape_interval: 10s
scrape_configs:
  - job_name: 'isc_cache'
    metrics_path: '/mgstat/5' # Tail 5 (sec) it's a diff time for ^mgstat. Should be less than scrape interval.
    static_configs:
    - targets: ['192.168.42.131:57772','192.168.42.132:57772']
    basic_auth:
      username: 'PromUser'
      password: 'Secret'

これで、Prometheus を含むコンテナを起動できます。

# docker run -d --name prometheus \
--hostname prometheus -p 9090:9090 \
-v /opt/prometheus/etc/prometheus.yml:/etc/prometheus/prometheus.yml \
-v /opt/prometheus/data/:/prometheus \
docker.io/prom/prometheus

正常に起動したかどうかを確認してください。

# docker ps --format "{{.ID}}: {{.Command}} {{.Status}} {{.Names}}"
d3a1db5dec1a: "/bin/prometheus -con" Up 5 minutes prometheus

Docker コンテナで Grafana を起動する

まずは最新のイメージをダウンロードしましょう。

# docker pull docker.io/grafana/grafana

次に、Grafana データベース(デフォルトでは SQLite)をホストマシンに保存するように指定して起動します。 また、Prometheus を含むコンテナへのリンクを作成し、Grafana を含むコンテナからそのコンテナにリンクできるようにします。

# mkdir -p /opt/grafana/db
# docker run -d --name grafana \
--hostname grafana -p 3000:3000 \
--link prometheus \
-v /opt/grafana/db:/var/lib/grafana \
docker.io/grafana/grafana
# docker ps --format "{{.ID}}: {{.Command}} {{.Status}} {{.Names}}"
fe6941ce3d15: "/run.sh" Up 3 seconds grafana
d3a1db5dec1a: "/bin/prometheus -con" Up 14 minutes prometheus

Docker-compose を使用する

両方のコンテナは別々に起動されます。 Docker-compose を使用すると、まとめて複数のコンテナを起動できるので便利です。 このツールをインストールし、現在の 2 つのコンテナを一時停止しましょう。その後、Docker-compose 経由で再起動するように再構成し、これらのコンテナをもう一度起動します。

これを cli で書くと次のようになります。

dnf install -y docker-compose
docker stop $(docker ps -a -q)
docker rm $(docker ps -a -q)
mkdir /opt/docker-compose
cat /opt/docker-compose/docker-compose.yml
version: '2'
services:
  prometheus: 
    image: docker.io/prom/prometheus
    container_name: prometheus
    hostname: prometheus
    ports: 
      - 9090:9090
    volumes:
      - /opt/prometheus/etc/prometheus.yml:/etc/prometheus/prometheus.yml
      - /opt/prometheus/data/:/prometheus
  grafana: 
    image: docker.io/grafana/grafana
    container_name: grafana
    hostname: grafana
    ports: 
      - 3000:3000
    volumes:
      - /opt/grafana/db:/var/lib/grafana
docker-compose -f /opt/docker-compose/docker-compose.yml up -d
# # 両方のコンテナを次のコマンドを使用して無効化および削除できます。 
# # docker-compose -f /opt/docker-compose/docker-compose.yml down
docker ps --format "{{.ID}}: {{.Command}} {{.Status}} {{.Names}}"
620e3cb4a5c3: "/run.sh" Up 11 seconds grafana
e63416e6c247: "/bin/prometheus -con" Up 12 seconds prometheus

インストール後の手順

Grafana を初めて起動した後は、Web インターフェイスの管理者パスワードを変更し(デフォルトでは、ログインとパスワードの組み合わせは admin/admin です)、Prometheus をデータソースとして追加する必要があります。 この手順は Web インターフェイスから実施できますが、Grafana SQLite データベース(デフォルトの位置は /opt/grafana/db/grafana.db)を直接編集するか、REST リクエストを使用することによっても実施できます。

さらにもう 1 つの方法をご紹介します。次をご覧ください。

curl -XPUT "admin:admin@localhost:3000/api/user/password" \
-H "Content-Type:application/json" \
-d '{"oldPassword":"admin","newPassword":"TopSecret","confirmNew":"TopSecret"}'

パスワードが正常に変更された場合は次の応答が返ってきます。
{"message":"User password changed"}
次のような応答が返ってくる場合があります:
curl: (56) Recv failure: Connection reset by peer
これは、Grafana サーバーがまだ起動しておらず、前述のコマンドを再度実行する前に少し待機する必要があることを意味しています。 例えば、次のように待機できます。
until curl -sf admin:admin@localhost:3000 > /dev/null; do sleep 1; echo "Grafana is not started yet";done; echo "Grafana is started"
パスワードを正常に変更したら、Prometheus のデータソースを追加してください。
curl -XPOST "admin:TopSecret@localhost:3000/api/datasources" \
-H "Content-Type:application/json" \
-d '{"name":"Prometheus","type":"prometheus","url":"http://prometheus:9090","access":"proxy"}'

データソースが正常に追加されると、次の応答が返ってきます。
{"id":1,"message":"Datasource added","name":"Prometheus"}

^mgstat に相当するものを作成する

^mgstat は対話モードで出力をファイルとターミナルに保存します。 ここではファイルへの出力は取り上げません。 このため、Studio を使用して USER スペースに ^mgstat をオブジェクト指向で実装した my.Metrics というクラスを作成してコンパイルします。

/// このクラスは ^mgstat ルーチンをオブジェクト指向で実装しています。
/// 前回とは異なり、Caché のバージョンチェックはスキップされます。
/// seizes を監視したい場合はパラメーター ISSEIZEGATHERED を 1 に設定する必要があります。
/// ^mgstat ルーチンとは異なり、Seizes メトリックは(パーセンテージではなく)差分として表示されます。
/// 一部の $zutil 関数についてはよく分かりませんが、^mgstat で使用されているので残しておきます。
Class my.Metrics Extends %RegisteredObject
{

/// メトリックの接頭辞
Parameter PREFIX = "isc_cache_mgstat_";

/// Prometheus のメトリックは改行で区切る必要があります。
Parameter NL As COSEXPRESSION = "$c(10)";

/// 不明なパラメーターです -) ^mgstat.int と同じものを使用しています。
Parameter MAXVALUE = 1000000000;

/// 2**64 - 10 です。 なぜマイナス 10 なのでしょうか? 分かりません -) ^mgstat.int と同じものを使用しています。
Parameter MAXVALGLO = 18446744073709551610;

/// 監視対象にするリソースです。 このリストは変更できます。
Parameter SEIZENAMES = "Global,ObjClass,Per-BDB";

/// デフォルト値は $zutil(69,74) です。 "1" を設定すると seize 統計の収集を開始できます。
Parameter ISSEIZEGATHERED = 0;

Parameter MAXECPCONN As COSEXPRESSION = "$system.ECP.MaxClientConnections()";

/// グローバルバッファタイプの数(8K、16K など)
Parameter NUMBUFF As COSEXPRESSION = "$zutil(190, 2)";

/// メモリオフセット(用途不明 )
Parameter WDWCHECK As COSEXPRESSION = "$zutil(40, 2, 146)";

/// 書き込みデーモンフェーズ用のメモリオフセット
Parameter WDPHASEOFFSET As COSEXPRESSION = "$zutil(40, 2, 145)";

/// ジャーナル用のオフセット
Parameter JOURNALBASE As COSEXPRESSION = "$zutil(40, 2, 94)";

ClassMethod getSamples(delay As %Integer = 2) As %Status
{
    set sc = $$$OK
    try {
        set sc = ..gather(.oldValues)
        hang delay
        set sc = ..gather(.newValues)
        set sc = ..diff(delay, .oldValues, .newValues, .displayValues)
        set sc = ..output(.displayValues)
    } catch e {
        write "Error: "_e.Name_"_"_e.Location, ..#NL
    }
    quit sc
}

ClassMethod gather(Output values) As %Status
{
    set sc = $$$OK
    
    // グローバルの統計を取得
    set sc = ..getGlobalStat(.values)
    
    // 書き込みデーモンの統計を取得
    set sc = ..getWDStat(.values)
    
    // ジャーナルの書き込みを取得
    set values("journal_writes") = ..getJournalWrites()
    
    // seize の統計を取得
    set sc = ..getSeizeStat(.values)
    
    // ECP の統計を取得
    set sc = ..getECPStat(.values)
    
    quit sc
}

ClassMethod diff(delay As %Integer = 2, ByRef oldValues, ByRef newValues, Output displayValues) As %Status
{
    set sc = $$$OK
    
    // グローバルのメトリックを処理
    set sc = ..loopGlobal("global", .oldValues, .newValues, delay, 1, .displayValues)
    
    set displayValues("read_ratio") = $select(
        displayValues("physical_reads") = 0: 0,
        1: $number(displayValues("logical_block_requests") / displayValues("physical_reads"),2)
    )
    set displayValues("global_remote_ratio") = $select(
        displayValues("remote_global_refs") = 0: 0,
        1: $number(displayValues("global_refs") / displayValues("remote_global_refs"),2)
    )
    
    // 書き込みデーモンのメトリックを処理(秒単位ではない)
    set sc = ..loopGlobal("wd", .oldValues, .newValues, delay, 0, .displayValues)
    
    // ジャーナルの書き込みを処理
    set displayValues("journal_writes") = ..getDiff(oldValues("journal_writes"), newValues("journal_writes"), delay)
    
    // seize メトリックの処理
    set sc = ..loopGlobal("seize", .oldValues, .newValues, delay, 1, .displayValues)
    
    // ECP クライアントメトリックの処理
    set sc = ..loopGlobal("ecp", .oldValues, .newValues, delay, 1, .displayValues)
    set displayValues("act_ecp") = newValues("act_ecp")
    
    quit sc
}

ClassMethod getDiff(oldValue As %Integer, newValue As %Integer, delay As %Integer = 2) As %Integer
{
    if (newValue < oldValue) {
        set diff = (..#MAXVALGLO - oldValue + newValue) \ delay
        if (diff > ..#MAXVALUE) set diff = newValue \ delay
    } else {
        set diff = (newValue - oldValue) \ delay
    }
    quit diff
}

ClassMethod loopGlobal(subscript As %String, ByRef oldValues, ByRef newValues, delay As %Integer = 2, perSecond As %Boolean = 1, Output displayValues) As %Status
{
    set sc = $$$OK
    
    set i = ""
    for {
        set i = $order(newValues(subscript, i)) 
        quit:(i = "")
        if (perSecond = 1) {
            set displayValues(i) = ..getDiff(oldValues(subscript, i), newValues(subscript, i), delay)
        } else {
            set displayValues(i) = newValues(subscript, i)
        }
    }
    
    quit sc
}

ClassMethod output(ByRef displayValues) As %Status
{
    set sc = $$$OK
    set i = ""
    for {
        set i = $order(displayValues(i))
        quit:(i = "")
        write ..#PREFIX_i," ", displayValues(i),..#NL
    }
    write ..#NL
    quit sc
}

ClassMethod getGlobalStat(ByRef values) As %Status
{
    set sc = $$$OK
    
    set gloStatDesc = "routine_refs,remote_routine_refs,routine_loads_and_saves,"_
        "remote_routine_loads_and_saves,global_refs,remote_global_refs,"_
        "logical_block_requests,physical_reads,physical_writes,"_
        "global_updates,remote_global_updates,routine_commands,"_
        "wij_writes,routine_cache_misses,object_cache_hit,"_
        "object_cache_miss,object_cache_load,object_references_newed,"_
        "object_references_del,process_private_global_refs,process_private_global_updates"
        
    set gloStat = $zutil(190, 6, 1)
    
    for i = 1:1:$length(gloStat, ",") {
        set values("global", $piece(gloStatDesc, ",", i)) = $piece(gloStat, ",", i)
    }

    quit sc
}

ClassMethod getWDStat(ByRef values) As %Status
{
    set sc = $$$OK
    
    set tempWdQueue = 0 
    for b = 1:1:..#NUMBUFF { 
        set tempWdQueue = tempWdQueue + $piece($zutil(190, 2, b), ",", 10) 
    }
    
    set wdInfo = $zutil(190, 13)
    set wdPass = $piece(wdInfo, ",")
    set wdQueueSize = $piece(wdInfo, ",", 2)
    set tempWdQueue = tempWdQueue - wdQueueSize 
    if (tempWdQueue < 0) set tempWdQueue = 0
    
    set misc = $zutil(190, 4)
    set ijuLock = $piece(misc, ",", 4)
    set ijuCount = $piece(misc, ",", 5)
    
    set wdPhase = 0 
    if (($view(..#WDWCHECK, -2, 4)) && (..#WDPHASEOFFSET)) {
        set wdPhase = $view(..#WDPHASEOFFSET, -2, 4)
    }
    
    set wdStatDesc = "write_daemon_queue_size,write_daemon_temp_queue,"_
        "write_daemon_pass,write_daemon_phase,iju_lock,iju_count"
    
    set wdStat = wdQueueSize_","_tempWdQueue_","_wdPass_","_wdPhase_","_ijuLock_","_ijuCount
    
    for i = 1:1:$length(wdStat, ",") {
        set values("wd", $piece(wdStatDesc, ",", i)) = $piece(wdStat, ",", i)
    }
    
    quit sc
}

ClassMethod getJournalWrites() As %String
{
    quit $view(..#JOURNALBASE, -2, 4)
}

ClassMethod getSeizeStat(ByRef values) As %Status
{
    set sc = $$$OK
    
    set seizeStat = "", seizeStatDescList = ""
    set selectedNames = ..#SEIZENAMES
    
    set seizeNumbers = ..getSeizeNumbers(selectedNames)  // seize statistics
    set isSeizeGatherEnabled = ..#ISSEIZEGATHERED
    if (seizeNumbers = "") { 
        set SeizeCount = 0 
    } else { 
        set SeizeCount = isSeizeGatherEnabled * $length(seizeNumbers, ",") 
    }
    
    for i = 1:1:SeizeCount { 
        set resource = $piece(seizeNumbers, ",", i)
        set resourceName = ..getSeizeLowerCaseName($piece(selectedNames, ",", i))
        set resourceStat = $zutil(162, 3, resource)
        set seizeStat = seizeStat_$listbuild($piece(resourceStat, ","))
        set seizeStat = seizeStat_$listbuild($piece(resourceStat, ",", 2))
        set seizeStat = seizeStat_$listbuild($piece(resourceStat, ",", 3))
        set seizeStatDescList = seizeStatDescList_$listbuild(
            resourceName_"_seizes", resourceName_"_n_seizes", resourceName_"_a_seizes"
        )
    }
    set seizeStatDesc = $listtostring(seizeStatDescList, ",")
    
    set seizeStat = $listtostring(seizeStat, ",")
    
    if (seizeStat '= "") {
        for k = 1:1:$length(seizeStat, ",") {
            set values("seize", $piece(seizeStatDesc, ",", k)) = $piece(seizeStat, ",", k)
        }
    }
    
    quit sc
}

ClassMethod getSeizeNumbers(selectedNames As %String) As %String
{
    /// USER>write $zu(162,0)
    // Pid,Routine,Lock,Global,Dirset,SatMap,Journal,Stat,GfileTab,Misc,LockDev,ObjClass...
    set allSeizeNames = $zutil(162,0)_"," //すべてのリソース名を返す
    
    set seizeNumbers = ""
    for i = 1:1:$length(selectedNames, ",") {
        set resourceName = $piece(selectedNames,",",i)
        continue:(resourceName = "")||(resourceName = "Unused")
        set resourceNumber = $length($extract(allSeizeNames, 1, $find(allSeizeNames, resourceName)), ",") - 1
        continue:(resourceNumber = 0)
        if (seizeNumbers = "") {
            set seizeNumbers = resourceNumber
        } else {
            set seizeNumbers = seizeNumbers_","_resourceNumber
        }
    }
    quit seizeNumbers
}

ClassMethod getSeizeLowerCaseName(seizeName As %String) As %String
{
    quit $tr($zcvt(seizeName, "l"), "-", "_")
}

ClassMethod getECPStat(ByRef values) As %Status
{
    set sc = $$$OK
    
    set ecpStat = ""
    
    if (..#MAXECPCONN '= 0) {
        set fullECPStat = $piece($system.ECP.GetProperty("ClientStats"), ",", 1, 21)
        set activeEcpConn = $system.ECP.NumClientConnections()
        set addBlocks = $piece(fullECPStat, ",", 2)
        set purgeBuffersByLocal = $piece(fullECPStat, ",", 6)
        set purgeBuffersByRemote = $piece(fullECPStat, ",", 7)
        set bytesSent = $piece(fullECPStat, ",", 19)
        set bytesReceived = $piece(fullECPStat, ",", 20)
    }
    set ecpStatDesc = "add_blocks,purge_buffers_local,"_
        "purge_server_remote,bytes_sent,bytes_received"
    
    set ecpStat = addBlocks_","_purgeBuffersByLocal_","_
        purgeBuffersByRemote_","_bytesSent_","_bytesReceived
        
    if (ecpStat '= "") {
        for l = 1:1:$length(ecpStat, ",") {
            set values("ecp", $piece(ecpStatDesc, ",", l)) = $piece(ecpStat, ",", l)
        }
        set values("act_ecp") = activeEcpConn
    }
    
    quit sc
}

}

REST 経由で my.Metrics を呼び出すため、USER スペースにラッパークラスを作成しましょう。

Class my.Mgstat Extends %CSP.REST
{

XData UrlMap [ XMLNamespace = "http://www.intersystems.com/urlmap" ]
{



}

ClassMethod getMgstat(delay As %Integer = 2) As %Status
{
    // デフォルトでは 2 秒間隔で平均値を取得します
    quit ##class(my.Metrics).getSamples(delay)
}

}

リソース、ユーザー、Web アプリケーションを作成する

メトリックを提供するクラスが完成し、RESTful Web アプリケーションを作成できるようになりました。 最初の記事と同様に、この Web アプリケーションにリソースを割り当て、そのリソースを使用して Prometheus がメトリックを収集するユーザーを作成します。 作成したら、特定のデータベースにユーザー権限を付与しましょう。 最初の記事とは異なり、CACHESYS データベースに書き込むための権限( <UNDEFINED>loop+1^mymgstat *gmethod" エラーを回避するため)を追加し、%Admin_Manage リソースを使用できるようにしました( <PROTECT>gather+10^mymgstat *GetProperty,%SYSTEM.ECP" エラーを回避するため)。 192.168.42.131 と 192.168.42.132 の両方の仮想サーバーでこれらの手順を繰り返しましょう。 ただし、その前に作成した my.Metrics クラスと my.Mgstat クラスのコードを両方のサーバーの USER スペースにアップロードします(コードは GitHub で取得できます)。

具体的にはそれぞれの仮想サーバーで次の手順を実行します。

cd /tmp
wget https://raw.githubusercontent.com/myardyas/prometheus/master/mgstat/cos/udl/Metrics.cls
wget https://raw.githubusercontent.com/myardyas/prometheus/master/mgstat/cos/udl/Mgstat.cls
#
# # サーバーがインターネットに接続されていない場合はプログラムとクラスをローカル環境でコピーし、 scp を使用してください。
#
csession -U user
USER>do $system.OBJ.Load("/tmp/Metrics.cls*/tmp/Mgstat.cls","ck")
USER>zn "%sys"
%SYS>write ##class(Security.Resources).Create("PromResource","Resource for Metrics web page","") 
1
%SYS>write ##class(Security.Roles).Create("PromRole","Role for PromResource","PromResource:U,%Admin_Manage:U,%DB_USER:RW,%DB_CACHESYS:RW")
1
%SYS>write ##class(Security.Users).Create("PromUser","PromRole","Secret")
1
%SYS>set properties("NameSpace") = "USER"
%SYS>set properties("Description") = "RESTfull web-interface for mgstat"
%SYS>set properties("AutheEnabled") = 32 ; 説明を参照してください
%SYS>set properties("Resource") = "PromResource"
%SYS>set properties("DispatchClass") = "my.Mgstat" 
%SYS>write ##class(Security.Applications).Create("/mgstat",.properties)
1

curl を使用してメトリックにアクセスできることを確認する

ファイアウォールで 57772 番ポートを忘れずに開いてください

curl --user PromUser:Secret -XGET http://192.168.42.131:57772/mgstat/5
isc_cache_mgstat_global_refs 347
isc_cache_mgstat_remote_global_refs 0
isc_cache_mgstat_global_remote_ratio 0

curl --user PromUser:Secret -XGET http://192.168.42.132:57772/mgstat/5
isc_cache_mgstat_global_refs 130
isc_cache_mgstat_remote_global_refs 0
isc_cache_mgstat_global_remote_ratio 0
...

Prometheus からメトリックにアクセスできることを確認する

Prometheus は 9090 番ポートをリッスンします。 まずは [Targets] のステータスを確認しましょう。

その後、任意のメトリックを確認してください。

1 つのメトリックを表示する

ここでは 1 つのメトリック(isc_cache_mgstat_global_refs)を例としてグラフに表示します。 まず、ダッシュボードを更新し、そこにグラフを挿入する必要があります。 そのためには Grafana(http://localhost:3000、ログイン/パスワードは admin/TopSecret)に移動し、新しいダッシュボードを追加してください。

グラフを追加します。

[Panel title]、[Edit] の順にクリックし、グラフを編集します。

Prometheus をデータソースとして設定し、isc_cache_mgstat_global_refs メトリックを選択します。 解像度は 1/1 に設定します。

このグラフに名前を付けましょう。

凡例を追加します。

ウィンドウの上部にある [Save] ボタンをクリックし、ダッシュボードの名前を入力します。

最終的には次のように表示されます。

すべてのメトリックを表示する

残りのメトリックも同じように追加しましょう。 2 つのテキストメトリックがあります(Singlestat)。 その結果、次のダッシュボードが表示されます(ここでは上部と下部に分けて掲載しています)。

次の 2 つは明らかに問題があるように思われます。

— 凡例のスクロールバー(サーバーの数が増えるとスクロールバーが長くなります)。

— Singlestat パネルにデータがありません(値が単一であることを意味します)。 私たちには 2 台のサーバーとそれに対応する 2 つの値があります。

テンプレートを使用する

インスタンスにテンプレートを導入し、これらの問題を解決してみましょう。 そのためにはインスタンスの値を格納する変数を作成し、ルールに従って Prometheus へのリクエストを少しだけ編集する必要があります。 つまり、“instance” 変数を作成した後に "isc_cache_mgstat_global_refs" リクエストの代わりに"isc_cache_mgstat_global_refs{instance="[[instance]]"}" を使用する必要があります。

変数を作成します。

Prometheus へのリクエストでは、各メトリックからインスタンスラベルの値を選択しましょう。 画面の下のほうで 2 つのインスタンスの値が識別されていることがわかります。 [Add] ボタンをクリックしてください。

ダッシュボードの上部に、使用可能な値を持つ変数が追加されました。

次に、ダッシュボードの各パネルのリクエストにこの変数を追加しましょう。つまり、"isc_cache_mgstat_global_refs" のようなリクエストを "isc_cache_mgstat_global_refs{instance="[[instance]]"}" に変更します。 最終的なダッシュボードは次のようになります(インスタンス名は意図的に凡例の横に残されています)。

Singlestat パネルが機能するようになりました。

このダッシュボードのテンプレートは GitHub からダウンロードできます。 テンプレートを Grafana にインポートする手順はこの記事のパート 1で説明しています。

最後に、サーバー 192.168.42.132 を 192.168.42.131 の ECP クライアントにして ECP トラフィックを生成するためのグローバルを作成しましょう。 次のように、ECP クライアントの監視が機能していることがわかります。

まとめ

^mgstat の結果を Excel で表示する代わりに、見栄えの良いグラフを使ったダッシュボードをオンラインで使用することができます。 デメリットは、^mgstat の代替バージョンを使用しなければならないことです。 一般的には元になるツールのコードは変更される可能性がありますが、その事は考慮されていません。 ただし、非常に楽な方法で Caché のパフォーマンスを監視することができます。

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

つづく...

追伸

デモ(1 つのインスタンス用)はこちらで利用できます。ログイン/パスワードは必要ありません。

0
0 468
記事 Mihoko Iijima · 11月 10, 2020 4m read

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

インデックスが複数定義されているクラス/テーブルへ csv 形式等のシーケンシャルファイルから大量データをデータベースに登録する際、推奨される登録方法として、データ登録時インデックスを生成させず、登録完了後に一括でインデックスを生成する 方法があります。

この方法は、新規に大量のレコードを一括登録する際に最も有効な手段となります。

<メモ>
大量のデータを追加登録する際には、既存のデータ量と新規データ量のバランスにより、この手法が有効でないケースもあります。その場合は、インデックスの再構築を範囲指定で行うこともできます。

説明に使用するクラス定義例は以下の通りです。

0
0 526
記事 Toshihiko Minamoto · 11月 10, 2020 8m read

最近行われたディスカッションの中で、Caché ObjectScript における for/while loop のパフォーマンンスが話に出ましたので、意見やベストプラクティスをコミュニティの皆さんと共有したいと思います。 これ自体が基本的なトピックではありますが、他の点では合理的と言える方法のパフォーマンスが意味する内容を見逃してしまうことがよくあります。 つまり、$ListNext を使って$ListBuild リストをイテレートするループ、または $Order を使ってローカル配列をイテレートするループが最も高速な選択肢ということです。

興味深い例として、コンマ区切りの文字列をループするコードについて考えます。

そのようなループをできるだけ手短に書くと、次のようになります。

For i=1:1:$Length(string,",") {
    Set piece = $Piece(string,",",i)
    //piece を使って何らかの処理を実行する...
}

とても分かりやすいですね。でも、多くのコーディングスタイルガイドラインは次のようなコードを提案するかもしれません。

Set n = $Length(string,",")
For i=1:1:n {
    Set piece = $Piece(string,",",i)
    //piece を使って何らかの処理を実行する...
}

各イテレーションで終了条件は評価されていないので、この 2 つのコードにパフォーマンス面での違いはありません。 (初めは誤解していましたが、これはパフォーマンスの問題ではなく、単にスタイルの違いだということを Mark が指摘してくれました。)

コンマ区切りの文字列の場合は、このパフォーマンスを高めることが可能です。 文字列が長くなるにつれ、$Piece(string,",",i) は、string を i 個目の piece の終わりまで処理することになるので、どんどん重くなっていきます。 これを改善するには $ListBuild リストを使用します。 例えば、$ListFromString$ListLength、および $List を使うと、以下のようなコードを書けます。

Set list = $ListFromString(string,",")
Set n = $ListLength(list)
For i=1:1:n {
    Set piece = $List(list,i)
    //piece を使って何らかの処理を実行する...
}

この方が、特に piece が長い場合は、$Length/$Piece を使うよりもパフォーマンスが良くなります。 $Length/$Piece を使った方法では、n の各イテレーションで piece の最初の i に渡される文字がスキャンされています。 一方の $ListFromString/$ListLength/$List を使った方法では、n の各イテレーションで $ListBuild 構造の i ポインターを追いかけています。 この方が高いパフォーマンスを得られますが、それでも実行時間は O(n2) のままです。 loop によりリストの内容が変更されないことを想定した場合、$ListNext を使えば O(n) を改善することができます。

Set list = $ListFromString(string,",")
Set pointer = 0
While $ListNext(list,pointer,piece) {
    //piece を使って何らかの処理を実行する...
}

$List のように、毎回、次のポインタはリストの先頭から i個目の piece までを移動するのではなく、変数「pointer」がリスト内の現在位置を把握しています。 したがって、合計 n(n+1)/2 回 ($Listではn 回のイテレーションでそれぞれ i 回) の「次のポインタ」操作は行わずに、単純に操作を n 回 ($ListNextではイテレーションごとに 1 回) 実行するということになります。

最後に、文字列を整数の添え字が付いた配列に変換すると良いかもしれません。一般的に、$Order を使ってローカル配列をイテレートすると、$ListNext を使った場合よりも処理速度が少し、または大幅に改善します (リスト要素の長さによる)。 もちろん、カンマ区切りの文字列の場合は、配列に変換するのに少し手間がかかります。 繰り返しイテレートする場合や、リストを部分的に変更する必要がある場合、または逆方向にイテレートする必要がある場合は、手間をかけてでも行う価値があるでしょう。

以下は、異なる入力サイズごとに示した実行時間のサンプルです (必要な変換をすべて含む)。

これらの数字は以下から得ています。

USER>d ##class(DC.LoopPerformance).Run(10000,20,100)
Iterating 10000 times over all the pieces of a string with 100 ,-delimited pieces of length 20:
Using $Length/$Piece (hardcoded delimiter): .657383 seconds
Using $Length/$Piece: 1.083932 seconds
Using $ListFromString/$ListLength/$List (hardcoded delimiter): .189867 seconds
Using $ListFromString/$ListLength/$List: .189938 seconds
Using $ListFromString/$ListNext (hardcoded delimiter): .089618 seconds
Using $ListFromString/$ListNext: .089242 seconds
Using $Order over an equivalent local array with integer subscripts: .072485 seconds
****************************************************
Using $ListFromString/$ListNext (not including conversion to $ListBuild list): .058329 seconds
Using one-argument $Order over an equivalent local array with integer subscripts: .060327 seconds
Using three-argument $Order over an equivalent local array with integer subscripts: .069508 seconds
 
USER>d ##class(DC.LoopPerformance).Run(2,1000,2000)
Iterating 2 times over all the pieces of a string with 2000 ,-delimited pieces of length 1000:
Using $Length/$Piece (hardcoded delimiter): 3.372927 seconds
Using $Length/$Piece: 11.739316 seconds
Using $ListFromString/$ListLength/$List (hardcoded delimiter): 1.004757 seconds
Using $ListFromString/$ListLength/$List: .997821 seconds
Using $ListFromString/$ListNext (hardcoded delimiter): .010489 seconds
Using $ListFromString/$ListNext: .010268 seconds
Using $Order over an equivalent local array with integer subscripts: .000839 seconds
****************************************************
Using $ListFromString/$ListNext (not including conversion to $ListBuild list): .003053 seconds
Using one-argument $Order over an equivalent local array with integer subscripts: .000938 seconds
Using three-argument $Order over an equivalent local array with integer subscripts: .000677 seconds

コード (DC.LoopPerformance) を Gist で表示する

追加

ディスカッションの際に、他の方法でも良いパフォーマンスが得られることが判明しましたので、お互いを比較しておく価値があるでしょう。 RunLinearOnly メソッドとテストを実施した様々な実装を最新版の Gist でご覧ください。

USER>d ##class(DC.LoopPerformance).RunLinearOnly(100000,20,100)
Iterating 100000 times over all the pieces of a string with 100 ,-delimited pieces of length 20:
Using $ListFromString/$ListNext (While): .781055 seconds
Using $ListFromString/$ListNext (For/Quit): .8438 seconds
Using $ListFromString/$ListNext (While, not including conversion to $ListBuild list): .37448 seconds
Using $Find/$Extract (Do...While): .675877 seconds
Using $Find/$Extract (For/Quit): .746064 seconds
Using one-argument $Order (For): .589697 seconds
Using one-argument $Order (While): .570996 seconds
Using three-argument $Order (For): .688088 seconds
Using three-argument $Order (While): .617205 seconds
 
USER>d ##class(DC.LoopPerformance).RunLinearOnly(200,2000,1000)
Iterating 200 times over all the pieces of a string with 1000 ,-delimited pieces of length 2000:
Using $ListFromString/$ListNext (While): .913844 seconds
Using $ListFromString/$ListNext (For/Quit): .925076 seconds
Using $ListFromString/$ListNext (While, not including conversion to $ListBuild list): .21842 seconds
Using $Find/$Extract (Do...While): .572115 seconds
Using $Find/$Extract (For/Quit): .610531 seconds
Using one-argument $Order (For): .044251 seconds
Using one-argument $Order (While): .04467 seconds
Using three-argument $Order (For): .043631 seconds
Using three-argument $Order (While): .042568 seconds

以下のチャートは、これらのメソッドの While/Do...While をそれぞれ比較したものです。 特に、$ListFromString/$ListNext と $Extract/$Find、および文字列から $ListBuild リストへの変換をせずに使用する場合の $ListNext と整数の添え字を付けたローカル配列で使用する $Order を比較した相対的なパフォーマンスに注目してください。

まとめ

  • コンマ区切りの文字列から始める場合は、$ListFromString/$ListNext を使った方がわずかにより直感的なコードを書けますが、パフォーマンスの面では $Find/$Extract が最良な選択肢となります。
  • データ構造のチョイスを考慮した場合、 $ListBuild リストのトラバーサルは、入力の小さい同等のローカル配列よりもわずかにパフォーマンスが優れているように思える一方で、入力が大きい場合はローカル配列の方がかなり高いパフォーマンスを提供します。 パフォーマンスにそれほど大きな違いがある理由は分かっていません。 (これに関連して、整数の添え字を付けたローカル配列と$ListBuild リストにおけるランダムな挿入と削除のコストを比較する価値はあるでしょう。このような配列では、set $list を使った方が、わざわざ値を移動させるよりも処理が速くなると思います。)
  • 引数なしの同等の for loop と比較すると、While loop または Do...While loop の方が わずかに 高いパフォーマンスを発揮します。
0
1 564
記事 Mihoko Iijima · 11月 6, 2020 2m read

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

SELECT のみを実行できるユーザを作成するには、ユーザかロールに対してテーブルへのアクセス権限を設定することで対応できます。

設定は、管理ポータルか、GRANT 文を実行することで設定できます。GRANT 文については、以下ドキュメントもご参照ください。
InterSystems SQL リファレンス - GRANT【IRIS】
CachéSQLリファレンス - GRANT

管理ポータルでの設定は、ユーザまたはロールの編集画面内「SQLテーブル」タブを使用します。

ユーザに直接設定する場合は以下のメニューを利用します。
  [ホーム] > [システム管理] > [セキュリティ] > [ユーザ] > [ユーザ編集] 

ロールに設定する場合は、以下のメニューを使用します。
 [ホーム] > [システム管理] > [セキュリティ] > [ロール] > 新規ロール作成
 ※ ロール名を指定し、保存するまで詳細設定画面が表示されません。
 ※ 作成したロールをユーザに付与することで、テーブルへのアクセス権限をロールで一元管理できます。 SQLテーブルでの権限設定

手順は以下のとおりです。

0
0 893
記事 Mihoko Iijima · 11月 6, 2020 4m read

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

テーブルチューニングを行った際に、フィールドに値がほとんど登録されていない(Null)場合や、特定の値がほとんどを占める場合、その値を[外れ値] として除外して選択性計算を行います。 また、外れ値が全レコードの何 % を占めているかの値[外れ値の選択性] として記録されます。

InterSystems 製品のクエリオプティマイザは、選択性数値とエクステントサイズを使用してクエリの経路を決定しますが、クラスクエリ、埋め込み SQL に使用しているクエリに外れ値が含まれる場合は、外れ値の選択性が自動的に考慮され、インデックスの使用有無を決定しています。

ダイナミック SQL 、ODBC/JDBC 経由でのクエリについては、外れ値が Null である場合、自動的に外れ値の選択性が考慮されますが、Null 以外の特定の値が外れ値に検出される場合は、明示的に指示を与えるまで考慮しません。

詳細は、ドキュメント(異常値に対する述語条件【IRIS】異常値に対する述語条件【Caché/Ensemble】)をご参照ください。

0
0 552
記事 Toshihiko Minamoto · 11月 5, 2020 6m read

データベースシステムには非常に特殊なバックアップ要件があり、企業のデプロイメントでは、事前の検討と計画が必要です。 データベースシステムの場合、バックアップソリューションの運用上の目標は、アプリケーションが正常にシャットダウンされた時と同じ状態で、データのコピーを作成することにあります。 アプリケーションの整合性バックアップはこれらの要件を満たし、Cachéは、このレベルのバックアップ整合性を達成するために、外部ソリューションとの統合を容易にする一連のAPIを提供しています。

これらのAPIは**ExternalFreezeExternalThaw**です。 _ExternalFreeze_は一時的にディスクへの書き込みを停止し、この期間にCaché はメモリ内の変更をコミットします。 この期間にバックアップ操作を完了させ、_ExternalThaw_の呼び出しを行う必要があります。 この呼び出しによって、書き込みのデーモンがグローバルバッファプール(データべースキャッシュ)で更新されたキャッシュをディスクに書き込むと、通常のCachéデータベースの書き込みデーモン操作が再開します。 このプロセスはCachéのユーザープロセスに対して透過的に行われます。 具体的なAPIクラスメソッドは次のとおりです。

##Class(Backup.General).ExternalFreeze()

##Class(Backup.General).ExternalThaw()

これらのAPIは、スナップショット操作のプリスクリプトとポストスクリプトを実行するAzure Backupの新機能と合わせて、Azure上のCachéのデプロイメントに対する包括的なバックアップソリューションを提供しています。 Azure Backupのプリスクリプトとポストスクリプト機能は現在、Linux VMでのみ利用できます。

前提条件

Azure Backupを使用してVMをバックアップする前に、おおまかに3つのステップを実行する必要があります。

  1. Recovery Servicesコンテナーを作成する
  2. VM Agentの最新バージョンをインストールする
  3. VMからAzureサービスへのネットワークアクセスを確認する

Recovery Servicesコンテナーは、バックアップの目標、ポリシー、および保護する項目を管理します。 Recovery Servicesコンテナーの作成は、Azure PortalまたはPowerShellを使ったスクリプトによって行います。 Azure BackupにはVMで実行する拡張機能が必要であり、Linux VMエージェントによって管理されています。また、最新バージョンのエージェントも必要です。 拡張機能はAzure StorageとRecovery Servicesコンテナーの外向きのHTTPSエンドポイントと対話します。 VMからこれらのサービスへのセキュアアクセスは、Azure Network Security Groupのプロキシとネットワークルールを使用して構成できます。

上記のステップに関する詳細は、「Prepare your environment to back up Resource Manager-deployed virtual machines」を参照してください。

プリスクリプトとポストスクリプトの構成

バックアップ操作の前と後にスクリプトを呼び出す機能は、Azure Backup Extension(Microsoft.Azure.RecoveryServices.VMSnapshotLinux)の最新バージョンに含まれています。 この拡張機能のインストール方法については、機能に関する詳細なドキュメントを確認してください。

デフォルトでは、拡張機能には、Linux VMの次の場所に、サンプルのプリスクリプトとポストスクリプトが含まれます。

/var/lib/waagent/Microsoft.Azure.RecoveryServices.VMSnapshotLinux-1.0.9110.0/main/tempPlugin

そして、スクリプトをそれぞれ次の場所にコピーする必要があります。

/etc/azure/prescript.sh
/etc/azure/postScript.sh

スクリプトテンプレートは、GitHubからもダウンロード可能です。

Cachéでは、ExternalFreeze APIを呼び出すprescript.shスクリプトを実装でき、postScript.shにはExternalThawを実行するコードが含まれている必要があります。

以下は、Cachéのprescript.shの実装例です。

#!/bin/bash
# variables used for returning the status of the script
success=0
error=1
warning=2
status=$success
log_path="/etc/preScript.log"#path of log file
printf  "Logs:\n" > $log_path# TODO: Replace <CACHE INSTANCE> with the name of the running instance
csession <CACHE INSTANCE> -U%SYS "##Class(Backup.General).ExternalFreeze()" >> $log_path
status=$?if [ $status -eq 5 ]; then
echo "SYSTEM IS FROZEN"
printf  "SYSTEM IS FROZEN\n" >> $log_pathelif [ $status -eq 3 ]; then
echo "SYSTEM FREEZE FAILED"
printf  "SYSTEM FREEZE FAILED\n" >> $log_path
status=$error
csession <CACHE INSTANCE> -U%SYS "##Class(Backup.General).ExternalThaw()"
fi

exit $status

以下は、CachéのpostScript.shの実装例です。

#!/bin/bash
# variables used for returning the status of the script
success=0
error=1
warning=2
status=$success
log_path="/etc/postScript.log"#path of log file
printf  "Logs:\n" > $log_path# TODO: Replace <CACHE INSTANCE> with the name of the running instance
csession <CACHE INSTANCE> -U%SYS "##class(Backup.General).ExternalThaw()"
status=$?
if [ $status req 5]; then
echo "SYSTEM IS UNFROZEN"
printf  "SYSTEM IS UNFROZEN\n" >> $log_pathelif [ $status -eq 3 ]; then
echo "SYSTEM UNFREEZE FAILED"
printf  "SYSTEM UNFREEZE FAILED\n" >> $log_path
status=$error
fi
exit $status

バックアップの実行

Azureポータルで、Recoveryサービスに移動して、最初のバックアップをトリガできます。 初回バックアップまたは後続のバックアップに関係なく、VMスナップショットの時間は数秒であることに注意してください。 最初のバックアップのデータ転送には時間がかかりますが、データ転送は、データベースのフリーズを解除するポストスクリプトの実行後に開始されるため、プリスクリプトとポストスクリプト間の時間に影響を与えることはありません。

データ保護操作が有効であることを確認するには、定期的に非本番環境にバックアップを復元してデータベースの整合性チェックを実行することを強くお勧めします。

バックアップのトリガ方法、およびバックアップスケジュールなどの関連トピックについては、「Back up Azure virtual machines to a Recovery Services vault」を参照してください。  

0
0 355
記事 Toshihiko Minamoto · 11月 3, 2020 123m read

1.本記事の内容

Caché パターンマッチングと同様に、Caché では正規表現を使ってテキストデータのパターンを特定することができますが、後者の場合はより高い表現力を利用できます。 本記事では正規表現を簡単に紹介し、Caché での活用方法について解説します。 本記事の情報は、主に Jeffrey Friedl 氏著作の「Mastering Regular Expressions (詳説 正規表現)」に加え、もちろん Caché のオンラインドキュメンテーションなど、様々なリソースを基に提供しています。 本記事は正規表現のあらゆる可能性や詳細について解説することを意図したものではありません。 更なる詳細にご興味のある方は、チャプター 5 に記載のソースを参照してください。 オフラインで読む場合は、PDF バージョンをダウンロードしていただけます。

パターンを使ったテキストの処理は複雑な作業な作業になることがあります。 正規表現を使用する場合、一般的には、パターンを探すテキスト、パターンそのもの (正規表現)、マッチ (パターンに一致するテキストの部分) など、異なる種類のエンティティを伴います。 こういったエンティティを簡単に区別できるよう、本ドキュメントでは以下のルールを使用しています。

テキストのサンプルは、モノスペース書体で個別に、追加の引用符を使わずに書かれています。

この ″テキスト文字列″ には探している "何か" が含まれています。

区別しにくい場合に限り、本文中にある正規表現はこの例にもあるように、灰色のバックグラウンドで表示されています: ".*?"

マッチした部分は必要に応じて異なる色で強調表示されます。

この "テキスト文字列" には探している "何か" が含まれています。

大きめのコードサンプルは、次の例のようにボックスに分けています。

set t="この ""テキスト文字列"" には探している ""何か"" が含まれています。"
set r="\"".*?\"""
w $locate(t,r,,,tMatch)

2.歴史 (とトリビア) の紹介

1940 当初、神経生理学者により人間の神経系がモデル化され、 その何年後かに、ある数学者が「正規セット」と呼ぶ代数学を用いてこのモデルを説明しました。 そして、この代数学の表記が「正規表現」と名付けられました。

1965 年になり、正規表現は初めてコンピューターの世界で言及されるようになり、 当時 UNIX オペレーティングシステムの一部であったエディター QED に正規表現が導入されるようになりました。 そのエディターの後のバージョンで、すべてのテキスト行で正規表現のマッチを検索し、その結果を出力するコマンドシーケンス g / regular expression / p (global, regular expression, print) が提供されました。 このコマンドシーケンスが、最終的にスタンドアロンの UNIX コマンドラインプログラム「grep」になりました。

今日、正規表現 (RegEx) は、数多くのプログラミング言語において、様々な形で実装されています (セクション 3.3 参照)。

3.正規表現 101

Caché パターンマッチングと同様に、正規表現を使ってテキストデータのパターンを特定することができますが、後者の場合はより高い表現力を利用できます。 以下のセクションでは、正規表現のコンポーネントとその評価をまとめ、使用可能なエンジンをいくつか紹介します。使用方法は、チャプター 4 で詳しく説明します。

3.1.正規表現のコンポーネント

3.1.1.正規表現のメタ文字

以下の文字は正規表現として使用された場合に特別な意味を持ちます。

.  *  +  ?  (  )  [  ]  \  ^ $  |

これらをリテラルとして使用する場合は、バックスラッシュを使ってエスケープする必要があります。 リテラルシーケンスを明示的に指定する場合は、\Q \E を使用します。

3.1.2.リテラル

通常のテキストとエスケープされた文字はリテラルとして扱われます。以下はその一部です。

<td>
  abc
</td>
<td>
  改ページ
</td>
<td>
  改行
</td>
<td>
  行頭復帰
</td>
<td>
  (垂直) タブ
</td>
<td>
  8 進数。Caché (ICU) で使用される正規表現エンジンは、最大で \0377 (10 進法では 255) までの 8 進数に対応しています。 他のエンジンから正規表現を移行させる場合は、8 進数がどう処理されるかを事前に確認しておいてください。
</td>
<td>
  16 進数。ICU ライブラリに 16 進数を処理する別のオプションが記載されています。ICU ドキュメンテーションを参照してください (リンクはセクション 5.8 にあります)
</td>
abc
\f
\n
\r
\v
\0+ 3 桁の数字 (例: \0101)
\x+ 2 桁の数字 (例: \x41)

3.1.3.アンカー

アンカーは、テキスト / 文字列の位置を一致させる場合に使用します。以下はその例です。

  • \A           文字列の先頭
  • \Z           文字列の末尾
  • ^             テキストまたは行の先頭
  • $             テキストまたは行の末尾
  • \b           単語の境界
  • \B           単語の境界ではない
  • <           単語の先頭
  • >           単語の末尾

一部の RegEx エンジンは、単語を構成するものの正確な定義や、単語の区切り文字と見なされる文字の定義によって、動作が異なります。

3.1.4.量指定子

量指定子を使用すると、先行する要素がマッチとみなされるための出現回数を指定できます。

  • {x}          x 回の出現
  • {x,y}      x 回から y 回の出現
  • *             0 回以上、{0,} と同じ
  • +             1 回以上、{1,} と同じ
  • ?             0 回または 1 回
     

最長一致

量指定子は「最長一致」、つまり、できるだけ多くの文字を一致させようとします。 以下のようなテキスト文字列があり、引用符内のテキストを見つける必要があるとしましょう。

This is "a text" with "four quotes".

セレクタは最長一致なため、正規表現 ".*" を使うと、一致するテキストの数が多くなり過ぎてしまいます。

This is "a text" with "four quotes".

この例の正規表現 .* は、引用符のペアの間にある文字をできるだけ多く検出しようとします。 しかし、ドットセレクタ ( . ) により引用符も検出されてしまうため、期待している結果が得られません。

一部の正規表現エンジン (Caché で使用されるものを含む) では、クエスチョンマーク (?) を追加することにより、量指定子の最長一致となることを抑制できます。 そうすることで、正規表現 ".*?" は、引用符で囲まれたテキストの 2 つの部分に一致し、期待通りの結果が得られます。

This is "a text" with "four quotes".

3.1.5.文字クラス (文字の範囲)

文字の範囲や文字のセットは、角括弧を使って [a-zA-Z0-9] または [abcd] のように指定します。正規表現では、これを「文字クラス」と呼んでいます。 範囲の中で一致するのは 1 文字だけです。つまり、範囲定義内にある文字の順番は関係ありません。[dbac][abcd] では同じ文字が一致します。

特定の文字の範囲を省く場合は、範囲定義の前 (角括弧内) に ^ を挿入します: [^ abc] と指定すると、a、b、c 以外のすべてが一致します。

一部の正規表現エンジンでは、事前定義された文字クラス (POSIX) を使用できます。以下はその一部です。

  • [:alnum:]    [a-zA-z0-9]

  • [:alpha:]    [a-zA-Z]

  • [:blank:]    [ \t]

3.1.6.グループ

括弧を使用すると、正規表現の一部をグループ化することができます。 これは、セレクタのグループに量指定子を適用したり、同じ正規表現 (後方参照) および正規表現を呼び出す Caché オブジェクトスクリプトコード (キャプチャバッファ) の両方からグループを参照する場合に便利です。 グループはネストできます。

下の正規表現は、順に 3 桁の数字、ダッシュ、大文字と数字の 3 つのペア、ダッシュ、先頭と同じ 3 桁の数字で構成される文字列に一致します。

([0-9]{3})-([A-Z][[0-9]){3}-\1

この例は、後方参照 (以下を参照) を使用して、構造だけでなく中身にも一致させる方法を示しています。後方参照 (紫) は、先頭の 3 桁の数字を末尾でも検索するようエンジンに指示しています (黄) 。 また、より複雑な構造 (緑色) に量指定子を適用する方法も示しています。

上記の正規表現は、以下の文字列に一致します。

123-D1E2F3-123

以下には一致しません。

123-D1E2F3-456             (末尾の 3 桁の数字が先頭の 3 桁と異なる)

123-1DE2F3-123             (中央部分が3 つの大文字、数字のペアではない)

123-D1E2-123                   (中央部分が2つの大文字、数字のペアしかない)

グループは、いわゆるキャプチャバッファの作成にも使用されます (セクション4.5.1を参照)。 これはとても強力な機能で、情報の一致と抽出を同時に実行できます!

3.1.7. 論理和指定子

論理和指定子を指定するには、skyfall|done のように縦線の文字を使います。 そうすることで、セクション 3.1.5 で説明した文字クラスを使う場合のように、より複雑な式を一致させることができます。

3.1.8.後方参照

後方参照を使用すると、以前に定義されたグループ (括弧内のセレクター) を参照できます。 下の正規表現の例は、同じ文字が 3 回繰り返す場合に一致します。

([a-zA-Z])\1\1

後方参照は「\x」で指定され、「x」は何番目の括弧で囲まれた式を参照するのかを意味します。

3.1.9.優先順位

  1. () よりも [] を優先
  2. シーケンスよりも , + および ? を優先。ab(ab) ではなく、a(b) と同等である
  3. 論理和指定子よりもシーケンスを優先。ab|c は、a(b|c) でなく、(ab)|c と同等である。

3.2.理論

通常、正規表現の評価は、下に紹介する 2 つの手段のどちらかで実施されます (ここでは簡単に説明していますので、詳しい内容はチャプター 5 に記載の文献を参照してください)。

  1. テキストを基にした判定 (DFA – Deterministic Finite Automaton「決定性有限オートマトン」)
    エンジンは入力されたテキスト文字を 1 文字ずつ確認し、それまで確認した文字を一致させようとする。 入力されたテキストの末尾に到達すると、成功とします。
     
  2. Regexを基にした判定(NFA – Non-deterministic Finite Automaton「非決定性有限オートマトン」)
    エンジンは、正規表現のトークンを 1 つずつ確認し、それをテキストに適用しようとします。 最後のトークンに到達(かつ一致)すれば、成功とします。
     

手段 1 は決定的な方法です。実行時間は入力されるテキストの長さによります。 正規表現に使われるセレクタの順序が実行時間に影響を与えることはありません。

手段 2 は非決定的な方法です。エンジンは、一致するかエラーが発生するまで、正規表現に使われているセレクタのすべての組み合わせを確認します。 従ってこの方法は_一致しない_場合は特に時間がかかります (すべての可能な組み合わせを確認する必要があるため)。 セレクタの順番は、実行時間に影響を_与えます_。 ただし、この方法はバックトラックおよびキャプチャバッファを使用できます。

3.3.エンジン

正規表現エンジンは、プログラミング言語やオペレーティングシステムに組み込まれているものから、ほぼどこでも使用可能なライブラリまで、様々なものが存在します。 以下は評価手段別に分けた正規表現エンジンの例です。

  • DFA:      grep、awk、lex
  • NFA:      Perl、Tcl、Python、Emacs、sed、vi、ICU

下のテーブルは、様々なプログラミング言語やライブラリで使用可能な正規表現の機能を比較したものです。

詳細はこちらをお読みください: https://en.wikipedia.org/wiki/Comparison_of_regular_expression_engines

4.RegEx と Caché

InterSystems Caché では、正規表現に ICU ライブラリが使用されています。その機能の多くは、Caché オンラインドキュメンテーションでご確認いただけます。 (後方参照などを含む) 完全な詳細は、ICU ライブラリのオンラインドキュメンテーションを参照してください。 – ICU へのリンクはセクション 5.8 をご覧ください。 以下のセクションで、その使用方法を簡単に紹介します。

4.4.$match() と $locate()

Caché ObjectScript (COS) では、2 つの関数 $match()$locate() により、ICU ライブラリが提供する多くの Regex 機能を直接使用できます。$match(String, Regex) は、指定された Regex パターンを基に入力文字列を検索します。 マッチが見つかった場合は 1 を、それ以外の場合は 0 を返します。

例:

  • w $match("baaacd","<span style="background-color:#D3D3D3;">.*(a)\1\1.*</span>") は 1 を返します
  • w $match("bbaacd","<span style="background-color:#D3D3D3;">.*(a)\1\1.*</span>") は 0 を返します

$locate(String,Regex,Start,End,Value) は、$match() と同様に、指定された Regex パターンを基に入力文字列を検索します。 ですが、$locate() は扱いやすい上に、より多くの情報を返します。 Start により、入力文字列内でパターンの検索を開始する位置を $locate に指示します。 $locate() は、マッチを見つけると、その最初の文字の位置を返し、End をマッチの次の文字の位置に設定します。 マッチの内容は Value として返されます。

$locate() は、マッチが見つからないと 0 を返し、EndValue が指定されていも、それを変更することはありません。EndValue は参照渡しで渡されるので、繰り返し使用する場合は注意が必要です (ループ内で使用する場合など)。

例:

  • w $locate("abcdexyz","<span style="background-color:#D3D3D3;">.d.</span>",1,e,x) は 3 を返すと同時に、e は 6 に、x は "cde" に設定されます

$locate() は、パターンマッチングを実行すると同時に、最初のマッチの内容を返すことができます。  すべてのマッチの内容を抽出する必要がある場合は、ループの中で $locate() を繰り返し呼び出すか、%Regex.Matcher のメソッドを実行します (次のセクションで解説)。

4.5.%Regex.Matcher

%Regex.Matcher を使用すると、$match() および $locate() と同様に、ICU ライブラリの正規表現機能を使用できます。 しかし、%Regex.Matcher を使用すると、高度な機能も利用でき、複雑なタスクの扱いがとてもシンプルになります。 次のセクションでは、キャプチャバッファについてもう一度確認し、正規表現を使って文字列を置き換える方法やランタイムの動作を制御する方法について説明します。

4.5.1.キャプチャバッファ

グループや後方参照、$locate() のセクションで説明してきましたが、正規表現を使用すると、テキスト内のパターンを検索すると同時に、一致した内容を返すことができます。 これは、抽出したいパターンの部分を括弧 (グルーピング) に入れて行います。 マッチが成功すると、一致したすべてのグループの内容がキャプチャバッファに入ります。 これはマッチした内容を Value パラメーターで返す $locate() とは少し異なるので注意が必要です。$locate() はマッチ全体を返す一方で、キャプチャバッファは、マッチ (グループ) への_部分的_なアクセスを可能にします。

これを使用するには、%Regex.Matcher クラスのオブジェクトを作成し、それに正規表現と入力文字列を渡します。 それから、%Regex.Matcher が提供するメソッドを 1 つ呼び出せば、実際の作業を実行することができます。 

例 1 (シンプルなグループ):

set m=##class(%Regex.Matcher).%New("(a|b).*(de)", "abcdeabcde")
w m.Locate() 1 を返す
w m.Group(1) a を返す
w m.Group(2) de を返す

例 2 (ネストされたグループと後方参照):

set m=##class(%Regex.Matcher).%New("((a|b).*?(de))(\1)", "abcdeabcde")
w m.Match()                   1 を返す
w m.GroupCount                4 を返す
w m.Group(1)                  abcde を返す
w m.Group(2)                  a を返す
w m.Group(3)                  de を返す
w m.Group(4)                  abcde を返す

(ネストされたグループの順番に注目してください。始め括弧がグループの始まりを意味するため、インデックス番号は外側のグループよりも内側のグループの方が高くなっています)

先ほども触れましたが、キャプチャバッファはパターンを一致させると同時に一致した内容を抽出できる、とても強力な機能です。 正規表現がないと、ステップ 1 として (パターンマッチオペレーターを使用するなどして) マッチを探し、ステップ 2 として何らかの条件を基に、マッチした内容を抽出 (または部分的に抽出) しなくてはならなくなります。

パターンを部分的にグループ化する必要がある (例: その部分に量指定子を適用するため) が、マッチした部分の内容をキャプチャバッファに取り入れたくない場合は、以下の例 3 で示すように、グループの前に疑問符とコロンを順に挿入し、そのグループを "非キャプチャリング (non-capturing)" または "内気 (shy)" なグループとして定義することができます。

例 3 (内気 "shy" なグループ):

set m=##class(%Regex.Matcher).%New("((a|b).*?(?:de))(\1)","abcdeabcde")
w m.Match()                        1 を返す
w m.Group(1)                       abcde を返す
w m.Group(2)                       a を返す
w m.Group(3)                       abcde を返す
w m.Group(4)                       <REGULAR を返すEXPRESSION>zGroupGet+3^%Regex.Matcher.1

4.5.2.置換

%Regex.Matcher は、マッチした内容を即座に置き換えることができる、ReplaceAll()ReplaceFirst() というメソッドも提供します。

set m=##class(%Regex.Matcher).%New(".c.","abcdeabcde")
w m.ReplaceAll("xxxx")        axxxxeaxxxxe を返す
w m.ReplaceFirst("xxxx")     axxxxeabcde を返す

置換文字列でグループを参照することもできます。 前の例のパターンにグループを追加した場合、置換文字列に $1 を含めることでその内容を参照することができます。

set m=##class(%Regex.Matcher).%New("<span style="background-color:#D3D3D3;">.</span><strong><span style="background-color:#D3D3D3;">(</span></strong><span style="background-color:#D3D3D3;">c</span><strong><span style="background-color:#D3D3D3;">)</span></strong><span style="background-color:#D3D3D3;">.</span>","abcdeabcde")
w m.ReplaceFirst("xx$1xx")   axxcxxeabcde を返す

マッチした完全な内容を置換文字列に含めるには、$0 を使用します。

w m.ReplaceFirst("xx$0xx")    axxbcdxxeabcde を返す

4.5.3.OperationLimit

セクション 3.2 で、正規表現を評価する 2 つの方法 (DFA と NFA) について解説しました。 Caché で使用される正規表現エンジンは、非決定的有限オートマトン (NFA) です。 したがい、特定の入力文字列において様々な正規表現を評価するのにかかる時間は異なる場合があります。 [1]

%Regex.Matcher オブジェクトのプロパティ OperationLimit を使えば、(_クラスタ_と呼ばれる) 実行単位の数を制限することができます。 クラスターの実行にかかる正確な時間は、環境によって異なります。 通常、クラスターの実行はわずか数ミリ秒で完了します。 しかし、OperationLimitは 0 (制限なし) にデフォルト設定されています。

4.6.実例: Perl から Caché への移行

このセクションでは、Perl から Caché への移行において、正規表現が関連する部分について説明します。 Perl スクリプトは、文字のマッチと抽出の両方に使用される数十個の多少複雑な正規表現で構成されていました。

もし、Caché で正規表現の機能を使用できなかったとしたら、Caché への移行プロジェクトは大掛かりな作業となったことでしょう。 幸い、Caché では正規表現の機能を使用できる上に、Perl スクリプトの正規表現は、ほぼ何の変更も加えずに Caché で使用することができました。

以下のリンクから Perl スクリプトを一部ご覧いただけます。

正規表現を Perl から Caché に移行する上で、唯一必要となった変更は (正規表現に大文字と小文字を区別させる) 修飾子 /iに関するもので、正規表現の末尾から先頭に移動させる必要がありました。

Perl では、キャプチャバッファの中身は特別な変数にコピーされます (上の Perl コードでいう $1$2)。 Perl プロジェクトのほぼ全ての正規表現で、このメカニズムが使用されていました。 これに似た作業を行えるよう、Caché Object Script ではシンプルなラッパーメソッドが作成されました。 %Regex.Matcher を使ってテキスト文字列に対し正規表現を評価し、キャプチャバッファの中身をリストとして返すというものです ($lb())。

以下がその Caché Object Script コードです。

if ..RegexMatch(
       tVCSFullName,
       "(?i)[\\\/]([^\\^\/]+)[\\\/]ProjectDB[\\\/](.+)[\\\/]archives[\\\/]",
       .tCaptureBufferList)
       {
             set tDomainPrefix=$zcvt($lg(tCaptureBufferList,1), "U")
             set tDomain=$zcvt($lg(tCaptureBufferList,2), "U")
       }
…

Classmethod RegexMatch(pString as %String, pRegex as %String, Output pCaptureBuffer="") {

       #Dim tRetVal as %Boolean=0
       set m=##class(%Regex.Matcher).%New(pRegex,pString)
       while m.Locate() {
             set tRetVal=1
             for i=1:1:m.GroupCount {
                    set pCaptureBuffer=pCaptureBuffer_$lb(m.Group(i))
             }
       }
       quit tRetVal
}

5.リファレンス情報

5.7.一般情報

一般情報およびチュートリアル

チュートリアルおよび例

正規表現エンジンの比較

クイックガイド

書籍

5.8.Caché オンラインドキュメンテーション

5.9.ICU

上述のとおり、InterSystems Caché は ICU エンジンを使用します。 包括的なドキュメンテーションはオンラインでご利用いただけます。

5.10.ツール

正規表現を作成するにあたり、開発者をサポートするツールは、無料で使用できるものから、商用ライセンスが付属するものまで数多く存在します。 私の個人的なお気に入りは RegexBuddy (http://www.regexbuddy.com/) です。インタラクティブで視覚的な機能を使用できるので、正規表現の作成とテストを様々な方法で行えます。

0
1 814
記事 Hiroshi Sato · 10月 29, 2020 1m read

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

起動時に、 CTELNETD startup error: bind(sock) failed Telnet23ポートは別ソフトでは使用していません。というエラーが発生する場合の対処法です。

現在、InterSystems製品と以下のソフトの組み合わせで、この現象が発生することがわかっています。

  1. NOD32 (セキュリティソフト)※1
  2. McAfee (セキュリティソフト・V5以前)※2
  3. AntiVirus2004 (セキュリティソフト)
  4. AirH トルネード (パケット圧縮ツール)
  5. Norton インターネットセキュリティ ※3
  6. Norton パーソナルファイアウォール ※3
  7. Sygate Personal Firewall
  8. WinGate
  9. Outpost
  10. ZoneAlarm
  11. McAfee Security Suite のプライバシーサービス  

これらがインストールされていると、InterSystems製品の起動も、各GUIツールも正しく動作しません。


上記ソフトウェアについては、アンインストールをお願いいたします。

※1 IMONで、InterSystems製品の全実行ファイルを監視をしないように指定することで、正常に動作します。

0
0 525
記事 Hiroshi Sato · 10月 29, 2020 1m read

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

以下のようにユーザ名、パスワードを引数に持ち、認証が成功した場合はユーザ名、失敗したときは""(NULL)を返すルーチン(SecTest^SecTest)を作成し、標準の認証システムを書き換えることができます。

SecTest(user,pass)
 // user1のパスワードがuser1の場合、認証OKのログを作成
 if user="user1",pass="user1" {
  set ^sqllog($i(^sqllog))="認証OK;"_$horolog_";"_user
  quit user
 }
 // 認証できなかった場合、認証NGのログを作成
 set ^sqllog($i(^sqllog))="認証NG;"_$horolog_";"_user
 quit ""

このルーチンを$SYSTEM.SQL.SetSQLLoginOverride()関数を使用して置き換えます。

do $SYSTEM.SQL.SetSQLLoginOverride("SecTest^SecTest")

ただし標準の認証ができなくなりますので、パスワードを別に保管、参照する仕組みを記述する必要があります。

0
0 180
記事 Hiroshi Sato · 10月 29, 2020 1m read

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

$IsObject()を使用して判別できます。

調べたい変数をvとすると、

$IsObject(v)=1 // vはOREF
$IsObject(v)=0 // vはOREFではない
$IsObject(v)=-1 // vはOREFだが、有効なオブジェクトを指していない

となります。

vが未定義の場合は、$IsObject(v)はUNDEFINEDエラーとなりますので、ご注意ください。

UNDEFINEDエラーを防止するには、次のように$Getを使用することをお勧めします。 

$IsObject($Get(v))
0
0 334
記事 Toshihiko Minamoto · 10月 28, 2020 12m read

この記事では $Increment 関数と $Sequence 関数を比較します。

まずは、$Increment 関数を聞いたことがないという方のために、その概要を説明いたします。 $Increment は、CachéObjectScript の関数で、引数をアトミックに 1 ずつインクリメントし、結果の値を返します。 $Increment にパラメーターとして渡せるのはグローバル変数ノードとローカル変数ノードのみで、任意の式を渡すことはできません。 $Increment は連続する ID の割り当てに多用されます。 その場合、$Increment のパラメーターにはグローバルノードがよく使用されます。 $Increment を使用するプロセスには確実に任意の ID が割り当てられます。

for i=1:1:10000 {
     set Id = $Increment(^Person) ; 新しい ID
     set surname = ##class(%PopulateUtils).LastName() ; ランダムなラストネーム
     set name = ##class(%PopulateUtils).FirstName()  ; ランダムなファーストネーム
     set ^Person(Id) = $ListBuild(surname, name)
}

$Increment の問題は、多数のプロセスが並行して行を追加していると、各プロセスはそれぞれの順番が回ってくるまで、ID を持つグローバルノードの値をアトミックに変更することができない場合があるという点です。上の例では、^Person がこれに該当します。

この問題を解決するためにデザインされたのが新しい関数$Sequence です。 $Sequence は Caché 2015.1 から導入されています。 $Increment と同様に、$Sequence もそのパラメーターの値をアトミックにインクリメントします。 $Increment と違う点として、$Sequence は前のプロセスから後続のカウンター値を現在のプロセス用にリザーブし、リザーブしておいた範囲の次の値を同じプロセス内の次の呼び出し中に返します。 $Sequence はリザーブしておく値の数を自動的に計算します。 プロセスが $Sequence を呼び出す頻度が増えると、$Sequence がリザーブする値の数も増えていきます。

USER>kill ^myseq

USER>for i=1:1:15 {write "increment:",$Seq(^myseq)," allocated:",^myseq,! }
increment:1 allocated:1
increment:2 allocated:2
increment:3 allocated:4
increment:4 allocated:4
increment:5 allocated:8
increment:6 allocated:8
increment:7 allocated:8
increment:8 allocated:8
increment:9 allocated:16
increment:10 allocated:16
increment:11 allocated:16
increment:12 allocated:16
increment:13 allocated:16
increment:14 allocated:16
increment:15 allocated:16

$Sequence(^myseq) が 9 を返した時点で、次の 8 個の値 (16 まで) が既に現在のプロセス用にリザーブされています。 別のプロセスが $Sequence を呼び出すと、10 ではなく、17 という値が割り当てられます。

$Sequence は、複数のグローバルノードを同時にインクリメントするプロセスを考えてデザインされています。 $Sequence は値をリザーブするため、リザーブされたすべての値がプロセスで使用されない場合は、ID の間にギャップが生じる場合があります。 $Sequence を使用する主な目的は、連続する ID を生成することです。 $Increment は $Sequence よりもジェネリクス型の関数と言えます。

それでは、$Increment と $Sequence のパフォーマンスを比較してみましょう。

Class DC.IncSeq.Test 
{

ClassMethod filling()
{
    lock +^P:"S"
    set job = $job
     for i=1:1:200000 {
         set Id = $Increment(^Person)
         set surname = ##class(%PopulateUtils).LastName()
         set name = ##class(%PopulateUtils).FirstName()
         set ^Person(Id) = $ListBuild(job, surname, name)
     }
     lock -^P:"S"
}

ClassMethod run()
{
    kill ^Person
    set z1 = $zhorolog
    for i=1:1:10 {
        job ..filling()
     }
     lock ^P
     set z2 = $zhorolog - z1
     lock
     write "done:",z2,!
}

}

メソッド run は 10 個のプロセスを実行し、それぞれが 200,000 件のレコードを ^Person の Global 変数ノードに挿入しています。 子プロセスの終了を待機するため、 メソッド run は ^P に対して排他ロックを取得しようとします。 子プロセスがジョブを終了し、^P の共有ロックを開放すると、メソッド run は ^P の排他ロックを取得し、実行を続けます。 この直後に、システム変数 $zhorolog の時間を記録し、これらのレコードを挿入するのにかかった時間を計算します。 低速 HDD を搭載したマルチコアノートブックでは 40 秒かかりました(科学実験として、これまで数回実行していますが、今回で 5 回目です ):

USER>do ##class(DC.IncSeq.Test).run()
done:39.198488

この 40 秒間を分析すると興味深いことが分かりました。 ^%SYS.MONLBL を実行すると、ID の取得に合計 100 秒もかかっていることが分かります。10 個のプロセスに 100 秒かかっているということは、各プロセスが新しい ID を取得するのに 10 秒かかっているということです。ファーストネームとラストネームの取得には 1.7 秒、データグローバルにデータを書き込むのには 28.5 秒かかっています。

下に示す %SYS.MONLBL レポートの 1 列目は行番号、2 列目はこの行が実行された回数、3 列目はこの行を実行するのにかかった秒数を表しています。

 ; ** メソッド 'filling' のソースコード**
1            10    .001143    lock +^P:"S"
2            10    .000055    set job = $JOB
3            10    .000118     for i=1:1:200000 {
4       1998499 100.356554         set Id = $Increment(^Person)
5       1993866  10.409804         set surname = ##class(%PopulateUtils).LastName()
6       1990461   6.347832         set name = ##class(%PopulateUtils).FirstName()
7       1999762  285.54603         set ^Person(Id) = $ListBuild(job, surname, name)
8       1999825   3.393706     }
9            10    .000259     lock -^P:"S"
 ; ** メソッド 'filling' のソースコード終わり **
 ;
 ; ** メソッド 'run' のソースコード **
1             1    .005503    kill ^Person
2             1    .000002    set z1 = $zhorolog
3             1    .000002    for i=1:1:10 {
4            10    .201327        job ..filling()
5             0          0     }
6             1  43.472692     lock ^P
7             1     .00003     set z2 = $zhorolog - z1
8             1     .00001     lock
9             1    .000053     write "done:",z2,!
 ; ** メソッド 'run' のソースコード終わり**

プロファイリングが実行されたため、前回よりも 4 秒長くなり、合計で 43.47 秒かかりました。

テストコードの filling メソッドで、1 つだけ入れ替えたい部分があります。 $Increment(^Person) を $Sequence(^Person) に変更して、もう一度テストを実行しましょう。

USER>do ##class(DC.IncSeq.Test).run()
done:5.135189

意外な結果になりましたね。 $Sequence によって ID を取得する時間は削減されましたが、データをグローバルに保管するのにかかっていた 28.5 秒はどうなったのでしょう。 では、^%SYS.MONLBL を確認してみましょう。

 ; ** メソッド 'filling' のソースコード **
1            10    .001181    lock +^P:"S"
2            10    .000026    set job = $JOB
3            10    .000087     for i=1:1:200000 {
4       1802473   1.996279         set Id = $Sequence(^Person)
5       1784910   4.429576         set surname = ##class(%PopulateUtils).LastName()
6       1853508   3.829051         set name = ##class(%PopulateUtils).FirstName()
7       1838752  32.281624         set ^Person(Id) = $ListBuild(job, surname, name)
8       1951569     1.0243     }
9            10    .000219     lock -^P:"S"
 ; ** メソッド 'filling' のソースコード終わり **
 ;
 ; ** メソッド 'run' のソースコード **
1             1    .006514    kill ^Person
2             1    .000002    set z1 = $zhorolog
3             1    .000002    for i=1:1:10 {
4            10    .385055        job ..filling()
5             0          0     }
6             1   6.558119     lock ^P
7             1    .000011     set z2 = $zhorolog - z1
8             1    .000008     lock
9             1    .000025     write "done:",z2,!
 ; ** メソッド 'run' のソースコード終わり **

各プロセスは 10 秒ではなく、わずか 0.2 秒で ID を取得できるようになりました。 よく解らないのは、なぜ各プロセスがたった 3.23 秒でデータを保管できているのか、という点です。 それは、グローバルノードはデータブロックに保管されるほか、通常は各ブロックのサイズが 8192 バイトだからです。 プロセスは、(set ^Person(Id) = … のように) グローバルノードの値を変更する前に、ブロック全体をロックします。 複数のプロセスが同時に同じブロック内のデータを変更しようとすると、それを変更できるのは 1 つのプロセスに限られるので、他のプロセスはそれが終了するのを待たなくてはいけません。

$Increment を使って新しい ID を生成する際に作成されたグローバルを見てみましょう。 連続するレコードに同じプロセス ID が割り当てられることはまずありません (プロセス ID はデータリストの最初の要素として保管していることをお忘れなく)。

1:    ^Person(100000)    =    $lb("12950","Kelvin","Lydia")
2:     ^Person(100001)    =    $lb("12943","Umansky","Agnes")
3:     ^Person(100002)    =    $lb("12945","Frost","Natasha")
4:     ^Person(100003)    =    $lb("12942","Loveluck","Terry")
5:     ^Person(100004)    =    $lb("12951","Russell","Debra")
6:     ^Person(100005)    =    $lb("12947","Wells","Chad")
7:     ^Person(100006)    =    $lb("12946","Geoffrion","Susan")
8:     ^Person(100007)    =    $lb("12945","Lennon","Roberta")
9:     ^Person(100008)    =    $lb("12944","Beatty","Mark")
10:     ^Person(100009)    =    $lb("12946","Kovalev","Nataliya")
11:     ^Person(100010)    =    $lb("12947","Klingman","Olga")
12:     ^Person(100011)    =    $lb("12942","Schultz","Alice")
13:     ^Person(100012)    =    $lb("12949","Young","Filomena")
14:     ^Person(100013)    =    $lb("12947","Klausner","James")
15:     ^Person(100014)    =    $lb("12945","Ximines","Christine")
16:     ^Person(100015)    =    $lb("12948","Quine","Mary")
17:     ^Person(100016)    =    $lb("12948","Rogers","Sally")
18:     ^Person(100017)    =    $lb("12950","Ueckert","Thelma")
19:     ^Person(100018)    =    $lb("12944","Xander","Kim")
20:     ^Person(100019)    =    $lb("12948","Ubertini","Juanita")

並行プロセスが同じブロックにデータを書き込もうとしていたため、データの変更にかかる時間よりも待機していた時間の方が長くなっていました。 $Sequence を使用すると、ID はチャンク生成されるので、異なるプロセスが異なるブロックを使用する可能性が高くなります。

1:     ^Person(100000)    =    $lb("12963","Yezek","Amanda")
// プロセス番号が 12963 のレコードは 351 件
353:     ^Person(100352)    =    $lb("12963","Young","Lola")
354:     ^Person(100353)    =    $lb("12967","Roentgen","Barb")

実際のプロジェクトがこのサンプルのような状態に陥っているという方は、$Increment の代わりに $Sequence を使用することを検討してください。 当然ですが、$Increment をすべて $Sequence に入れ替えてしまう前に、まずはドキュメンテーション を参照してください。

また、このテストの内容をそのまま鵜呑みにはせず、ご自身の目で確かめてください。

Caché 2015.2 からは、$Increment の代わりに $Sequence を使ってテーブルを操作できるようになりました。 そのために $system.Sequence.SetDDLUseSequence というシステム関数が用意されています。Management Portal の SQL 設定からも同じオプションを使用できます。

また、クラス定義には -- IDFunction という新しいストレージパラメーターがあります。「increment」にデフォルト設定されており、ID 生成に $Increment が使用されることを意味します。 (Inspector > Storage > Default > IDFunction と移動して)「sequence」に変更することができます。

おまけ

自分の notebook で他にも簡単なテストを行いました。DB サーバーをホストオペレーションシステムに、そしてアプリケーションサーバーを同じ notebook のゲスト VM にインストールした小さな ECP 構成を使っています。 ^Person はリモートのデータベースにマッピングしています。 いたってベーシックなテストなので、それを基に一般論を出すのは控えておきます。 $Increment と ECP を使用する際には 考慮すべき点 がいくつかあります。 では、結果をご覧ください。

$Increment を使った場合

USER>do ##class(DC.IncSeq.Test).run()
done:163.781288

^%SYS.MONLBL:

 ; ** メソッド 'filling' のソースコード **
1            10    .000503         --     lock +^P:"S"
2            10    .000016    set job = $job
3            10    .000044    for i=1:1:200000 {
4       1843745 1546.57015        set Id = $Increment(^Person)
5       1880231   6.818051        set surname = ##class(%PopulateUtils).LastName()
6       1944594   3.520858        set name = ##class(%PopulateUtils).FirstName()
7       1816896  16.576452        set ^Person(Id) = $ListBuild(job, surname, name)
8       1933736    .895912    }
9            10    .000279    lock -^P:"S"
 ; ** メソッド 'filling' のソースコード終わり **
 ;
 ; ** メソッド 'run' のソースコード **
1             1    .000045    kill ^Person
2             1    .000001    set z1 = $zhorolog
3             1    .000007    for i=1:1:10 {
4            10    .059868        job ..filling()
5             0          0    }
6             1 170.342459    lock ^P
7             1    .000005    set z2 = $zhorolog - z1
8             1    .000013    lock
9             1    .000018    write "done:",z2,!
 ; ** メソッド 'run' のソースコード終わり **

$Sequence を使った場合

USER>do ##class(DC.IncSeq.Test).run()
done:13.826716

^%SYS.MONLBL

 ; ** メソッド 'filling' のソースコード **
1            10    .000434     lock +^P:"S"
2            10    .000014    set job = $job
3            10    .000033    for i=1:1:200000 {
4       1838247  98.491738        set Id = $Sequence(^Person)
5       1712000   3.979588        set surname = ##class(%PopulateUtils).LastName()
6       1809643   3.522974        set name = ##class(%PopulateUtils).FirstName()
7       1787612  16.157567        set ^Person(Id) = $ListBuild(job, surname, name)
8       1862728    .825769    }
9            10    .000255    lock -^P:"S"
 ; ** メソッド 'filling' のソースコード終わり **
 ;
 ; ** メソッド 'run' のソースコード **
1             1    .000046    kill ^Person
2             1    .000002    set z1 = $zhorolog
3             1    .000004    for i=1:1:10 {
4            10    .037271        job ..filling()
5             0          0    }
6             1  14.620781    lock ^P
7             1    .000005    set z2 = $zhorolog - z1
8             1    .000013    lock
9             1    .000016    write "done:",z2,!
 ; ** メソッド 'run' のソースコード終わり **
0
1 337
記事 Toshihiko Minamoto · 10月 27, 2020 14m read

秩序(順序)はだれにとっても必要であるが、皆が同じように秩序(順序)を理解しているわけではない (ファウスト・セルチニャーニ)

免責事項: この記事では、例としてロシア語とキリル文字を使用しますが、英語以外のロケールでCachéを使用するすべての方に関連のある記事です。この記事は主にNLS照合について言及しており、SQL照合とは異なることに注意してください。 SQL照合(SQLUPPER、SQLSTRING、照合なしを意味するEXACT、TRUCATEなど)は、値に明示的に適用される実際の関数であり、その結果はグローバルサブスクリプトに明示的に格納されることがあります。 サブスクリプトに格納されると、これらの値は当然、有効なNLS照合(「SQLおよびNLS照合」)に従うことになります。

Cachéのデータ、メタデータ、クラス、ルーチンはすべてグローバルに格納されます。 グローバルは永続的です。 グローバルノードはサブスクリプト値によって順序付けされ、検索やディスクフェッチのパフォーマンスを向上させるために、挿入された順ではなくソート順でストレージデバイスに格納されます。

USER>set ^a(10)=""
USER>set ^a("фф")=""
USER>set ^a("бб")=""
USER>set ^a(2)=""
USER>zwrite ^a
^a(2)=""
^a(10)=""
^a("бб")=""
^a("фф")=""

ソート中、Cachéは数値と文字列を区別します。2は数値として扱われ、10より前にソートされます。 コマンドZWriteおよび関数$Order$Queryは、これらのサブスクリプトが格納された順でグローバルサブスクリプトを返します。初めに空の文字列(サブスクリプトとして使用できません)、その後に負の数値、ゼロ、正の数値、そして照合で定義された順で文字列を返します(照合)。

Cachéの標準的な照合は(当然ながら)Caché標準と呼ばれており、 文字列はUnicode文字コードに従ってソートされます。

現在のプロセスのローカル配列の照合はロケールによって定義されます(管理ポータル > システム管理 > 構成 > システム構成 > 国言語設定 > Locale Definitions)。 CachéのUnicodeインストールのロシア語ロケールはruswで、ruswのデフォルトの照合はCyrillic3です。 ruswロケールで指定できる照合には、Caché標準、Cyrillic1、Cyrillic3、Cyrillic4、Ukrainian1があります。

ClassMethod ##class(%Collate).SetLocalName()は現在のプロセスのローカル配列に使用する照合を設定します。

USER>write ##class(%Collate).GetLocalName()
Cyrillic3
USER>write ##class(%Collate).SetLocalName("Cache standard")
1
USER>write ##class(%Collate).GetLocalName()
Cache standard
USER>write ##class(%Collate).SetLocalName("Cyrillic3")
1
USER>write ##class(%Collate).GetLocalName()
Cyrillic3

すべての照合には、数値を文字列としてソートする照合があります。 その照合の名前には末尾に「string」が含まれています。

USER>write ##class(%Collate).SetLocalName("Cache standard string")
1
USER>kill test

USER>set test(10) = "", test(2) = "", test("фф") = "", test("бб") = ""

USER>zwrite test
test(10)=""
test(2)=""
test("бб")=""
test("фф")=""

USER>write ##class(%Collate).SetLocalName("Cache standard")
1
USER>kill test

USER>set test(10) = "", test(2) = "", test("фф") = "", test("бб") = ""

USER>zwrite test
test(2)=""
test(10)=""
test("бб")=""
test("фф")=""

Caché標準とCyrillic3

Caché標準は、コードに従って文字をソートします。

 write ##class(%Library.Collate).SetLocalName("Cache standard"),!
 write ##class(%Library.Collate).GetLocalName(),!
 set letters = "абвгдеёжзийклмнопрстуфхцчщщьыъэюя"
 set letters = letters _ $zconvert(letters,"U")
 kill test

 //fill local array “test” with data
 for i=1:1:$Length(letters) {
     set test($Extract(letters,i)) = ""
 }

 //print test subscripts in sorted order
 set l = "", cnt = 0
 for  {
     set l = $Order(test(l))
     quit:l=""
     write l, " ", $Ascii(l),","
     set cnt = cnt + 1
     write:cnt#8=0 !
 }

USER>do ^testcol
1
Cache standard
Ё 1025,А 1040,Б 1041,В 1042,Г 1043,Д 1044,Е 1045,Ж 1046,
З 1047,И 1048,Й 1049,К 1050,Л 1051,М 1052,Н 1053,О 1054,
П 1055,Р 1056,С 1057,Т 1058,У 1059,Ф 1060,Х 1061,Ц 1062,
Ч 1063,Щ 1065,Ъ 1066,Ы 1067,Ь 1068,Э 1069,Ю 1070,Я 1071,
а 1072,б 1073,в 1074,г 1075,д 1076,е 1077,ж 1078,з 1079,
и 1080,й 1081,к 1082,л 1083,м 1084,н 1085,о 1086,п 1087,
р 1088,с 1089,т 1090,у 1091,ф 1092,х 1093,ц 1094,ч 1095,
щ 1097,ъ 1098,ы 1099,ь 1100,э 1101,ю 1102,я 1103,ё 1105,

キリル文字は、ロシア語のアルファベット順に出力されますが、「ё」と「Ё」は例外です。 これらのUnicode文字コードは順不同であり、 「Ё」は「Е」と「Д」の間、「ё」は「е」と「д」の間で照合されます。 そのため、ロシア語ロケールには独自の照合として、ロシア語のアルファベットと同じ順に並ぶ文字を使用するCyrillic3が必要となります。

USER>do ^testcol
1
Cyrillic3
А 1040,Б 1041,В 1042,Г 1043,Д 1044,Е 1045,Ё 1025,Ж 1046,
З 1047,И 1048,Й 1049,К 1050,Л 1051,М 1052,Н 1053,О 1054,
П 1055,Р 1056,С 1057,Т 1058,У 1059,Ф 1060,Х 1061,Ц 1062,
Ч 1063,Щ 1065,Ъ 1066,Ы 1067,Ь 1068,Э 1069,Ю 1070,Я 1071,
а 1072,б 1073,в 1074,г 1075,д 1076,е 1077,ё 1105,ж 1078,
з 1079,и 1080,й 1081,к 1082,л 1083,м 1084,н 1085,о 1086,
п 1087,р 1088,с 1089,т 1090,у 1091,ф 1092,х 1093,ц 1094,
ч 1095,щ 1097,ъ 1098,ы 1099,ь 1100,э 1101,ю 1102,я 1103,

Cachéのobjectscriptには、「後にソート」という特殊なバイナリ演算子「]]」があります。 最初のオペランドを持つサブスクリプトが2番目のオペランドを持つサブスクリプトの後にソートされる場合は1を返し、そうでない場合は0を返す演算子です。

USER>write ##class(%Library.Collate).SetLocalName("Cache standard"),!
1
USER>write "А" ]] "Ё"
1
USER>write ##class(%Library.Collate).SetLocalName("Cyrillic3"),!
1
USER>write "А" ]] "Ё"
0

グローバルと照合

同一のデータベースに含まれるグローバルには、さまざまな照合が使用される場合があります。 各データベースには構成オプションがあり、新しいグローバルにはデフォルト照合が使用されます。 インストール直後、USERを除くすべてのデータベースはCaché標準のデフォルト照合を使用します。 USERデータベースのデフォルト照合はインストールロケールによって決まるため、 ruswの場合はCyrillic3となります。

データベースに対しデフォルト以外の照合を使用してグローバルを作成するには、##class(%GlobalEdit).Createメソッドを使用します。

USER>kill ^a
USER>write ##class(%GlobalEdit).Create(,"a",##class(%Collate).DisplayToLogical("Cache standard"))

管理ポータル(システムエクスプローラ > Globals)のグローバルのリストに、各グローバルの照合列があります。

既存のグローバルの照合を変更することはできないため、 新しい照合でグローバルを作成して、Mergeコマンドを使ってデータをコピーする必要があります。 グローバルの一括変換を行うには##class(SYS.Database).Copy()を使用します。

Cyrillic4、Cyrillic3、およびウムラウト

文字列サブスクリプトを内部形式に変換するには、Cyrillic3照合には、Caché標準照合にかかる時間よりもはるかに長い時間がかかるため、Cyrillic3照合を使用したグローバル(またはローカル)配列の挿入とルックアップの処理が遅くなります。 Caché 2014.1には新しい照合であるCyrillic4が含まれており、Cyrillic3と同じ正しい文字順になりますが、パフォーマンスが改善されます。

for collation="Cache standard","Cyrillic3","Cyrillic4" {
     write ##class(%Library.Collate).SetLocalName(collation),!
     write ##class(%Library.Collate).GetLocalName(),!
     do test(100000)
 }
 quit
test(C)
 set letters = "абвгдеёжзийклмнопрстуфхцчщщьыъэюя"
 set letters = letters _ $zconvert(letters,"U")

 kill test
 write "test insert: "
 //fill local array “test” with data
 set z1=$zh
 for c=1:1:C {
     for i=1:1:$Length(letters) {
         set test($Extract(letters,i)_"плюс длинное русское слово" _ $Extract(letters,i)) = ""
     }
 }
 write $zh-z1,!

 //looping through test subscripts
 write "test $Order: "
 set z1=$zh
 for c=1:1:C {
     set l = ""
     for  {
         set l = $Order(test(l))
         quit:l=""
     }
 }
 write $zh-z1,!

USER>do ^testcol
1
Cache standard
test insert: 1.520673
test $Order: 2.062228
1
Cyrillic3
test insert: 3.541697
test $Order: 5.938042
1
Cyrillic4
test insert: 1.925205
test $Order: 2.834399

Cyrillic4は、まだruswロケールのデフォルトの照合ではありませんが、ruswに基づいて独自のロケールを定義し、Cyrillic4をローカル配列のデフォルトの照合として指定することができます。 または、データベース設定でグローバルの新しいデフォルト照合としてCyrillic4を設定することもできます。

Cyrillic3は、2つの文字列を各文字コードに基づいてソートするのに比べてさらに一般的なアルゴリズムに基づいているため、Caché標準とCyrillic4よりも低速になります。

ドイツ語の場合、ソート時には文字はssとして照合されます。 Cachéは次の通り、そのルールを尊重します。

USER>write ##class(%Collate).GetLocalName()
German3
USER>set test("Straßer")=1
USER>set test("Strasser")=1
USER>set test("Straster")=1
USER>zwrite test
test("Strasser")=1
test("Straßer")=1
test("Straster")=1

サブスクリプトの文字列のソート順に注目してください。 特に、最初の文字列の頭の4文字は「Stras」で、次に「Straß」、そしてもう一度「Stras」となっているところです。 照合が個別の文字のコードに基づいてソートされているだけであれば、この方法で文字列をソートすることは不可能です。

もう1つの例として、フィンランド語の場合、「v」と「w」は同一の文字として照合される必要があります。 ロシア語の照合ルールはより単純であり、各文字にある特定のコードを与え、これらのコードでソートするだけで十分です。 この方法で、照合Cyrillic4 のパフォーマンスはCyrillic3と比べて改善されています。

照合とSQL

配列の照合とSQL照合を混同しないようにしてください。 SQL照合は、比較の前に文字列に適用される変換、またはインデックスグローバルでサブスクリプトとして使用する変換です。 CachéのデフォルトのSQL照合はSQLUPPERで、 すべての文字を大文字に変換し、スペース文字を削除して、文字列の先頭にスペースを1つ追加します。 ほかのSQL照合(EXACT、SQLSTRING、TRUNCATE)については、こちらのドキュメントに説明されています。

同一のデータベース内の異なるグローバルにさまざまな照合が使用されており、ローカル配列にも別の照合が使用されている場合、たやすく混乱が生じます。 SQLは一時データにCACHETEMPデータベースを使用しますが、 CACHETEMPのグローバルのデフォルト照合は、Cachéのインストールロケールの照合とは異なる可能性があります。

主なルールが1つあります。期待される順序で行を返すSQLクエリのORDER BYについては、データと関連するテーブルのインデックスがソートされるグローバルの照合は、CACHETEMPデータベースのデフォルトの照合とローカル配列の照合と同一である必要があります。 詳細については、ドキュメントの「SQLとNLS照合」の段落を参照してください。

では、テストクラスを作成しましょう。

Class Collation.test Extends %Persistent
{

Property Name As %String;

Property Surname As %String;

Index SurInd On Surname;

ClassMethod populate()
{
    do ..%KillExtent()

    set t = ..%New()
    set t.Name = "Павел", t.Surname = "Ёлкин"
    write t.%Save()

    set t = ..%New()
    set t.Name = "Пётр", t.Surname = "Иванов"
    write t.%Save()

    set t = ..%New()
    set t.Name = "Прохор", t.Surname = "Александров"
    write t.%Save()
}

}

クラスにデータを入力します(後で、ドイツ語を使った前の例の単語を使用してください)。

USER>do ##class(Collation.test).populate()

クエリを実行します。

![](https://community.intersystems.com/sites/default/files/inline/images/0-order-by-name-wrong.png)

予想外の結果となりました。 主な疑問は、名前がなぜアルファベット順に並べられないのか (Павел、Пётр、Прохор)というところにあります。 クエリプランを見てみましょう。

![](https://community.intersystems.com/sites/default/files/inline/images/1-order-by-name-plan.png)

このプランのキーワードは、「一時ファイルを作成する」です。 SQLエンジンは、このクエリの実行に一時的な構造を使用することに決定しました。 「ファイル」と呼ばれてはいますが、実際にはプロセスプライベートのグローバルであり、場合によってはローカル配列です。 このグローバルのサブスクリプトは、この特定のケースでは個人名で順序付けする値です。 プロセスプライベートのグローバルは、CACHETEMPデータベースに格納され、CACHETEMPの新しいグローバルのデフォルト照合はCaché標準です。

また、「ё」がなぜ最後ではなく最初に返されているのかという別の合理的な疑問もあります(Caché標準では「ё」はロシア語のすべての文字と「Ё」の前ではなく、後にソートされます)。 一時グローバルのサブスクリプトはNameフィールドの実際の値ではなく、Nameを大文字化した値(SQLUPPERは文字列のデフォルトのSQL照合)であるため、「Ё」がほかの文字の前に返されているのです。

%Exact関数を使用してデフォルトの照合を変更すると、依然として誤りではありますが、少なくとも「ё」がほかの文字の後にソートされるという、期待された結果が得られます。

![](https://community.intersystems.com/sites/default/files/inline/images/2-order-by-exact-name.png)

ここでは、CACHETEMPのデフォルトの照合を変更せずに、Surname列のクエリを確認してみましょう。 この列のインデックスは、^Collation.testIグローバルに格納されます。 そのグローバルの照合はCyrillic3であるため、行の正しい順序を確認できるはずです。

![](https://community.intersystems.com/sites/default/files/inline/images/3-order-by-surname-wrong.png)

またしても、誤りです。「Ё」は 「А」と「И」の間である必要があります。 クエリプランを見てみましょう。

![](https://community.intersystems.com//sites/default/files/inline/images/4-order-by-surname-plan.png)

SQLUPPERがSurInd インデックスの値に適用されているため、Surnameフィールドの元の値を出力できる十分なインデックスデータがありません。 SQLエンジンは、Name列と同様に、テーブル自体の値を使用して一時ファイルの値を並べ替えるように決定しています。

クエリに、Surnameが大文字でも良いことを記述することができます。 行はインデックスグローバル ^Collation.testI から直接取得されるため、順序は正しくなります。

![](https://community.intersystems.com//sites/default/files/inline/images/5-order-by-surname-sqlupper.png)

クエリプランは期待通りです。

![](https://community.intersystems.com//sites/default/files/inline/images/6-order-by-surname-sqlupper-plan.png)

では、ずっと前に行っているはずの、CACHETEMPデータベースのデフォルト照合をCyrillic3(またはCyrillic4)に変更する作業を行いましょう。

一時ファイルを使用するクエリは、正しい順で行を出力します。

![](https://community.intersystems.com//sites/default/files/inline/images/7-order-by-name-ok.png)
![](https://community.intersystems.com//sites/default/files/inline/images/8-order-by-surname-ok.png)

要約

  • ローカルアルファベットのニュアンスを気にしない場合は、Caché標準の照合を使用します。
  • 一部の照合(Cyrillic4)は、ほかの照合(Cyrillic3)よりもパフォーマンスが優れています。
  • CACHETEMPの照合がメインデータベースとローカル配列の照合と同じであることを確認します。
0
0 569
記事 Mihoko Iijima · 10月 25, 2020 2m read

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

%Net.HttpRequest クラスの SSLConfiguration プロパティに SSL/TLS 構成の「クライアント」構成名が指定されているかご確認ください。

%Net.HttpRequest クラスを使用して、https の url にアクセスするためには、以下のドキュメントに記載されている SSL/TLS 構成 の「クライアント」構成を作成して指定した名前を SSLConfiguration プロパティに指定する必要があります。

SSL/TLS構成のクライアント構成方法

管理ポータルの [システム管理] > [セキュリティ] > [SSL/TLS構成] メニューを開き、「構成名」に任意名を設定し、「保存」ボタンをクリックします(そのほかの構成パラメータは、デフォルト値で作成します)。

実行例は以下の通りです(https://www3.nhk.or.jp/news/ にアクセスしています)。

0
0 517
記事 Hiroshi Sato · 10月 19, 2020 3m read

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

2つのステップにて作業します。

クラス定義の移行

クラス定義を別システムへ移行するため、XML形式またはUDL形式(拡張子.cls)のファイルにエクスポートします。

スタジオでのエクスポート手順は以下の通りです。

[ツール] > [エクスポート]

> [追加]ボタンで移行したいクラスを複数選択

> [ローカルファイルにエクスポート]にチェック

> ファイルの種類がXMLであることを確認し、ファイル名を入力し、[OK]

この後、別システム上のスタジオで、エクスポートしたXML、UDLファイルをインポートします。

この手順で、クラス定義は移行できます。

スタジオでのインポート手順は以下の通りです。

[ツール] > [ローカルからインポート]

> 上記手順で出力したXML、UDLファイルを指定します。  

データの移行

次に実際のデータを移行します。
オブジェクトデータは既定では、以下の命名規則のグローバル変数内に格納されています。
データ   :^クラス名D
インデックス:^クラス名I
ストリーム :^クラス名S
例)User.testクラスのデータは以下の3つのグローバルに格納されます。
^User.testD, ^User.testI, ^User.testS

0
0 368
記事 Mihoko Iijima · 10月 15, 2020 5m read

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

DATE 型は InterSystems 製品のデータ型の %Date に、TIME 型は %Time に対応しています。

%Date は内部日付(特殊変数 $Horolog のカンマ区切り1番目)、%Time は内部時刻($Horolog のカンマ区切り2番目)を登録するタイプであるため、サーバ側ロジックでは表示モードを切り替えない限り、内部(論理)形式の値が使用されます。
サーバ側ロジックで内部日付・時刻の表示形式を変更する方法は、操作方法により異なります。

以降の実行例では、Sample.Person テーブルを使用して解説します。
(コマンド実行例は SELECT 文で記載していますが、更新文に対しても同様に記述できます。)

IRIS/IRIS for Health でお試しいただく場合は、ドキュメント(InterSystems IRIS で使用するサンプルのダウンロード)から、
または 関連記事(サンプル(Sample.Person)のクラス定義ダウンロードとサンプルデータの作成について)から、
Sample.Person クラスのインポートとサンプルデータの作成を行ってからお試しください。

Caché/Ensembleでお試しいただく場合は、SAMPLESネームスペースのSample.Personをご利用ください。

0
0 869
記事 Hiroshi Sato · 10月 15, 2020 1m read

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

$ZHorologを使用します。

$ZHorologシステム変数はInterSystems製品を起動してからの経過時間(秒)を保持しています。

2点間の$ZHorolog値の差を取るだけで正確な経過時間を取得する事が出来ます。

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


$ZHorologについて【IRIS】


$ZHorologについて

この他、秒の小数部を含む時間の日付と時間の取得には $NOW, $ZTimeStamp が使用できます。

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

$NOWについて【IRIS】
$NOWについて
$ZTimeStampについて【IRIS】
$ZTimeStampについて

0
0 212
記事 Mihoko Iijima · 10月 15, 2020 6m read

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

この記事では「グローバルを誤って削除してしまった!」という場合の対処方法をご紹介します。

誤って削除してしまった特定のグローバルを復旧するためには、バックアップファイルとジャーナルを使用します。
復旧は、^ZJRNFILTユーティリティによるジャーナルリストアで条件を指定してジャーナルレコードをリストアする方法で行います。
この方法で、ある時点のデータベースのバックアップに対して、削除が含まれるジャーナルレコードについて特定グローバルを削除するまでのものを適用することができます。

^ZJRNFILTユーティリティの詳細については、以下のドキュメントをご参照ください。

^ZJRNFILT を使用したジャーナル・レコードのフィルタ処理について【IRIS】
^ZJRNFILT を使用したジャーナル・レコードのフィルタ処理について

【実施例】

  ・2020/10/14 時点のバックアップが存在している(バックアップは2020/10/15 0:30に実行したとします)
     ジャーナル:2020/10/15 の1日分が存在している(2020/10/14のバックアップ以降のもの)
  ・対象のグローバル:^TEST1

イメージは以下の通りです。

0
0 547
記事 Toshihiko Minamoto · 10月 14, 2020 9m read


こんにちは!

この記事では、IRIS から Caché、Ensemble、HealthShare など、InterSystems の製品で使用されるクラスやその構造を理解するのに役立つツールの概要を簡単にまとめています。

つまり、そのツールはクラスやパッケージ全体を視覚化し、クラス間の相対関係を示し、ディベロッパーやチームリーダーに必要な情報をすべて提供してくれるので、わざわざ Studio に移動してコードを調べる必要が省けます。

InterSystems の製品について情報を集めている方からたくさんのプロジェクトをレビューしている方、または単純に InterSystems Technology ソリューションの新機能に興味がある方まで、ObjectScript Class Explorer の概要をぜひお読みください!

InterSystems 製品のご紹介

以前は Caché として知られた IRIS はマルチレベルの DBMS です。 SQL クエリを使ってアクセスしたり、さまざまなプログラミング言語のインターフェースを使い、保管されているオブジェクトやプロシージャを操作したりできます。 ですが、DBMS に組み込まれているネイティブ言語の ObjectScript (COS) を使ってアプリケーションを開発することが、常に最初の選択肢として選ばれます。

Caché は DBMS レベルのクラスに対応しています。 クラスには主に、Persistent(データベースに保管できる) と Registered(データベースに保管されないが、プログラムやハンドラーとしての役割を果たす)の2 種類があります。 また、Serial(アドレスなどの複雑なデータ型を作成するために Persistent クラスに統合して使えるクラス) 、DataType(ユーザー定義のデータ型を作成する際に使用される) 、Index、View、Stream などの特殊なタイプもいくつかあります。

Class Explorer の概要

Caché Class Explorer は、Caché クラスの構造をダイアグラムとして視覚化し、クラスやすべての関連情報 (メソッドのコード、クエリ、xData ブロック、コメント、ドキュメンテーション、さまざまなクラス要素のキーワードなど) の依存関係を示すツールです。

機能

Caché にはクエリ、xData ブロック、メソッドやプロパティの多数のキーワード (System、ZenMethod、Hidden、ProcedureBlock など)、親子関係、一対多関係、クラス型といった、スタンダードな UML ではサポートされていないが、Caché には重要な一連のエンティティが存在するため、Class Explorer は拡張バージョンの UML 表記を使って視覚化を実行します。

Caché Class Explorer (バージョン1.14.3) では、次のことができます。

  • パッケージの階層、クラスのダイアグラム、パッケージ全体を表示する。
  • 表示されたダイアグラムの外観を編集する。
  • クラスのダイアグラムのその時点でのイメージを保存する。
  • ダイアグラムのその時点での外観を保存し、後で復元する。
  • ダイアグラムやクラスツリーに表示されるキーワードを使って検索を行う。
  • ツールヒントを使ってクラス、そのプロパティ、メソッド、パラメーター、クエリ、xData ブロックの完全な情報を確認する。
  • メソッドのコード、クエリ、または xData ブロックを表示する。
  • グラフィックアイコンを含むダイアグラム要素の表示を有効または無効にする。

この記事の残りの内容をよく理解していただけるよう、Class Explorer で視覚化されたクラスを見てみましょう。 それでは、例の 1 つとして、「SAMPLES」ネームスペースの「Cinema」パッケージを表示してみましょう。

詳細および機能の概要 

左側のサイドバーにパッケージツリーがあります。 パッケージ名にカーソルを合わせると、その右側にボタンが表示されるので、それをクリックしてパッケージ全体を表示します。 パッケージツリーの中からクラスを選択し、リンクされているクラスと一緒にレンダリングします。

Class Explorer で表示できるクラス間の依存関係には種類がいくつかあります。

  1. 継承。 継承先クラスを指す白い矢印で示されます。
  2. アソシエーションまたはクラス同士の関係。クラスのフィールドに別クラスのタイプが含まれている場合、ダイアグラムビルダーはこれをアソシエーション関係として示します。
  3. 親子関係と 一対多関係。データの整合性を維持するためのルール。

各関係にカーソルを合わせると、その関係を生み出すプロパティが強調表示されます。

Class Explorer は現在のパッケージの外に存在するクラス間の依存関係までは表示しません。 表示されるのは現在のパッケージ内のクラスのみです。また、Class Explorer によるクラス検索に制限を設定する場合は、「依存関係レベル」設定を使います。

クラスは長方形として表示され、以下の 6 つのセクションに分割されます。

  1. クラス名:クラス名にカーソルを合わせると、作成日、変更日、コメント、クラスに割り当てられたすべてのキーワードが表示されます。 クラスヘッダーをダブルクリックすると、そのドキュメンテーションが表示されます。
  2. クラスパラメーター: すべての割り当てられたパラメーターがタイプ、キーワード、コメントと共に表示されます。 斜体で表記されるパラメーターやすべてのプロパティは、カーソルを合わせるとツールヒントが表示されます。
  3. クラスプロパティ:クラスプロパティはパラメーターに似ています。
  4. メソッド:メソッドをクリックすれば、ソースコードが表示されます。 COS 構文は強調表示されます。
  5. クエリ:メソッドと同様に、クリックすればソースコードが表示されます。
  6. xData ブロック: 主に XML データで構成されるブロック クリックすると、フォーマットされたソースコードがブロックとして表示されます。

デフォルトで、各クラスは多数のグラフィックアイコンと一緒に表示されます。 各アイコンの意味は、画面の右上隅にある「ヘルプ」ボタンをクリックすれば分かります。 多少厳密な UML 表記をデフォルトで表示する必要がある場合や、クラスセクションを表示する場合は、グラフィックアイコンを設定セクションで無効にすることができます。

非常に大きなダイアグラムを使い慣れていない場合は、ダイアグラムのクイック検索機能を使うことができます。 入力したキーワードの一部を含むクラスが強調表示されます。 次のマッチにジャンプするには、Enter キーを押すか、検索ボタンをもう一度クリックします。

最後に、ダイアグラムの編集をすべて完了し、不要な関係をすべて削除し、各要素をそれぞれの適切な位置に配置して、希望通りの外観が出来上がれば、左下隅にある「ダウンロード」ボタンをクリックして保存します。

ピンボタン をアクティブにすると、クラス (またはパッケージ) の現在のセットのダイアグラムに配置されている要素の位置が保存されます。 例えば、クラス A と B を選択し、そのビューをピンボタンで保存すると、ブラウザーやマシンを再起動した後でも、クラス A と B を選択すれば完全に同じビューが表示されます。 しかし、クラス A だけを選択した場合は、デフォルトのレイアウトで表示されます。

インストール

Caché Class Explorer をインストールするには、最新リリース版 の XML パッケージだけをお好きなネームスペースにインポートしてください。 インポートが完了すると、「hostname / ClassExplorer /」という名前の新しい Web アプリ (最後のスラッシュは必須です) が表示されます。

インストールの詳しい手順

  1. Caché Class Explorer の最新リリースを含むアーカイブをダウンロードします。
  2. 「Cache/CacheClassExplorer-vX.X.X.xml」という名前の XML ファイルを抽出します。
  3. 以下のいずれかの方法でパッケージをお好きなネームスペースにインポートします。
    1. XML ファイルを Studio にドラッグする。
    2. System Management Portal を使用する場合: System Explorer -> Classes -> Import と順に移動し、ローカルファイルへのパスを指定する。
    3. ターミナルコマンドを使用する場合: do ##class(%Installer.Installer).InstallFromCommandLine(“Path/Installer.cls.xml”);
  4. インポートログを読み、問題がなければ「http://hostname/ClassExplorer/」から Web アプリケーションを開くことができます。 問題が発生した場合は、以下を確認してください。
    1. このネームスペースにクラスをインポートする権限があるか。
    2. Web アプリケーションのユーザーは異なるネームスペースへのアクセス権を持っているか。
    3. エラー 404 が表示される場合は、URL の最後に「/」を追加しているか。

他のスクリーンショット

[スクリーンショット 1] DSVRDemo パッケージ。クラス名にカーソルを合わせた状態。

[スクリーンショット 2] DataMining パッケージ。ダイアグラムでキーワード「TreeInput」を検索中。

[スクリーンショット 3] JavaDemo.JavaListSample クラスのメソッドコードを表示したビュー。

[スクリーンショット 4] ClassExplorer.Router クラスの XData ブロックのコンテンツを表示中。

Class Explorer の機能を標準の SAMPLES ネームスペース デモでお試しください。 プロジェクトのビデオレビューは こちら からご覧ください。

フィードバックやご提案、コメントはこちら、もしくは GitHub リポジトリからお寄せください。 どうぞお楽しみください!

0
0 395
記事 Henrique Dias · 10月 8, 2020 2m read

npm-iris とは何ですか? 

N.P.Mは "No Project Mess "の略です。

N.P.M.は、InterSystems IRISとBootstrap 4を使用したプロジェクト&タスク管理アプリです。

No Project Messは、シンプルで直感的なプロジェクトとタスクの管理ソフトウェアで、開発者や中小企業が日々の複雑な問題を軽減できるように作成されています。 
スプレッドシート、カンバン、カレンダー、ガントチャートなど、タスクのためのさまざまなビューを提供しています。

2
0 175