検索

クリアフィルター
記事
Mihoko Iijima · 2025年6月25日

管理ポータルの再下位メニューに対してアクセスを制限する方法

これは InterSystems FAQ サイトの記事です。 管理ポータルのシステム管理やシステムエクスプローラーなど最上位のメニューについては、事前定義ロールをユーザに付与することでアクセスを制限することができます。 最下位のメニュー、例えば [システム管理] > [構成] > [システム構成] > [ネームスペース] などは、事前定義ロールの %Manager を付与されたユーザであれば、全てのユーザが利用できてしまいます。 このページに対して、「%Manager ロールを持っているが、あるユーザは使用でき あるユーザは使用できない」のようにユーザ毎のアクセス制限を追加したい場合は、カスタムリソースを作成し再下位メニューに対して作成したカスタムリソースを付与することで制限を追加することができます。 手順は以下の通りです。 カスタムリソースを任意名で作成する。この時パブリック許可は設定しない。 管理ポータルの任意の再下位メニューに 1 で作成したカスタムリソースを設定する。 新規でロールを作成し、1で作成したカスタムリソースに対する USE 許可を設定する。 メニューを利用できるユーザを 3 で作成したロールのメンバーに設定する。 具体的な設定の流れは以下の通りです。 1. カスタムリソースを任意名で作成する。この時パブリック許可は設定しない。 管理ポータル > [システム管理] > [セキュリティ] > [リソース] の画面で新規リソースを作成します。 図例では、Restrict リソースをパブリック許可なしで作成しています。 2、管理ポータルの任意の再下位メニューに 1 で作成したカスタムリソースを設定する。 図では、管理ポータル > [システム管理] > [構成] > [システム構成] > [ネームスペース] の画面に対してアクセス制限を加えたいため、このメニューに 1 で作成したリソースを設定します。 デフォルトの状態は以下の通りです。 リソースを追加した後は以下の通りです。 3、新規でロールを作成し、1で作成したカスタムリソースに対する USE 許可を設定する。 次は、ユーザ毎にアクセスを変えたいため、作成したカスタムリソースの USE 許可を持つロールを作成します。 RestrictedRole ロール作成の流れは以下の通りです。 4、メニューを利用できるユーザを 3 で作成したロールのメンバーに設定する。 予め、TestM1、TestM2 ユーザを作成しそれぞれに %Manager ロールを付与している状態を作っています。 さらに、3 で作成したロールのメンバーに TestM1 ユーザを追加しています。 最後に、設定後の画面表示を確認します。 TestM1 ユーザでログインした場合、[ネームスペース]メニューは利用できますが、TestM2 ユーザは追加設定したカスタムリソースに対する USE 許可を持たないため、メニューにアクセスできないことが確認できました。
記事
Toshihiko Minamoto · 2021年11月9日

別のIRISメッセージビューワを作成する

IRISインターオペラビリティのメッセージビューワで何かを変更できるとしたら、何を変更しますか? 「Dashboard IRIS History Monitor」の記事を公開したところ、素晴らしいフィードバックやリクエストをいただきました。 中には、メッセージビューワの拡張に関するリクエストがありました。   まだプロジェクトを確認していない方は、ぜひご覧ください。絶対に見る価値がありますし、[2019年の最高のInterSystems Open Exchange開発者およびアプリケーション](https://community.intersystems.com/post/best-intersystems-open-exchange-developers-and-applications-2019)の1つとしてブロンズ賞を受賞しました。 「新しい」メッセージビューワに含めようと思う機能についてのアイデアを書き留め始めましたが、これらのリソースをどのようにすれば素早く簡単に見せることができるのでしょうか。 まずは、 一般的に、相互運用性の本番環境をセットアップし、[ドキュメント](https://docs.intersystems.com/iris20191j/csp/docbook/Doc.View.cls?KEY=EGDV_deploying)の指示のとおりに、ターゲットシステムにエクスポートしてデプロイすることから始めます。 これは私があまり好まないプロセスです。 特に何か悪いというわけではありませんが、 コードを使ってすべてを行う考えがあるためです。 誰かがこういったプロジェクトを実行するたびに、次のように開始することを期待しています。 `$ docker-compose build` `$ docker-compose up -d` いかがでしょうか!!! たったこれだけのステップを思い浮かべながら、InterSystemsコミュニティを調べ始めると、いくつかのヒントが見つかりました。 ある投稿では、私が自問していた質問が挙げられていました。「[ルーチンを使って本番環境を作成するにはどうすればよいのか。](https://community.intersystems.com/post/how-create-productions-routine)」 その投稿の中で、@Eduard.Lebedyuk が、コードを使って本番環境を作成する方法を次のように回答しています。 「本番クラスを自動的に作成するには、次を行う必要があります。 1. テストプロダクション用の%Dictionary.ClassDefinitionオブジェクトを作成します。 2. Ens.Config.Productionオブジェクトを作成します。 3. %Dictionary.XDataDefinitionを作成します。 4. (2) を (3) にシリアル化します。 5. XData (3) を (1) に挿入します。 6. (1) を保存してコンパイルします。」 @Jenny Amesのコメントにも、次のように書かれていました。  「私たちがよくお勧めしているベストプラクティスは、逆方向に構築することです。 ビジネスオペレーションを先に構築してから、ビジネスプロセス、そしてビジネスサービスを構築していく方法です...」 というわけで、早速やってみましょう! ## リクエスト、ビジネスオペレーション、およびビジネスサービス クラス**diashenrique.messageviewer.util.InstallerProduction.cls**は、名前から想像できるように、プロダクションのインストールを担当するクラスです。 インストーラのマニフェストは、そのクラスからClassMethod **Install**を呼び出します。 /// 拡張ビューワの表示機能にプロダクションをインストールするヘルパー ClassMethod Install() As %Status { Set sc = $$$OK Try { Set sc = $$$ADDSC(sc,..InstallProduction()) quit:$$$ISERR(sc) Set sc = $$$ADDSC(sc,..GenerateMessages()) quit:$$$ISERR(sc) Set sc = $$$ADDSC(sc,..GenerateUsingEnsDirector()) quit:$$$ISERR(sc) } Catch (err) { Set sc = $$$ADDSC(sc,err.AsStatus()) } Return sc } クラスメソッド**InstallProduction**は、次を作成することで、プロダクションを作成するためのメインの構造をまとめます。 * リクエスト * ビジネスオペレーション * ビジネスサービス * 相互運用性プロダクション コードを使用して相互運用性プロダクションを作成しようと考えているため、完全なコーディングモードに移行して、リクエスト、ビジネスオペレーション、およびビジネスサービスの全クラスを作成しましょう。 これを行うには、いくつかのInterSystemsライブラリパッケージを広範に使用します。 * %Dictionary.ClassDefinition * %Dictionary.PropertyDefinition * %Dictionary.XDataDefinition * %Dictionary.MethodDefinition * %Dictionary.ParameterDefinition クラスメソッド**InstallProduction**は、次のコードを使用して、**Ens.Request**を継承した2つのクラスを作成します。   Set sc = $$$ADDSC(sc,..CreateRequest("diashenrique.messageviewer.Message.SimpleRequest","Message")) quit:$$$ISERR(sc) Set sc = $$$ADDSC(sc,..CreateRequest("diashenrique.messageviewer.Message.AnotherRequest","Something")) quit:$$$ISERR(sc) ClassMethod CreateRequest(classname As %String, prop As %String) As %Status [ Private ] { New $Namespace Set $Namespace = ..#NAMESPACE Set sc = $$$OK Try { Set class = ##class(%Dictionary.ClassDefinition).%New(classname) Set class.GeneratedBy = $ClassName() Set class.Super = "Ens.Request" Set class.ProcedureBlock = 1 Set class.Inheritance = "left" Set sc = $$$ADDSC(sc,class.%Save()) #; create adapter Set property = ##class(%Dictionary.PropertyDefinition).%New(classname) Set property.Name = prop Set property.Type = "%String" Set sc = $$$ADDSC(sc,property.%Save()) Set sc = $$$ADDSC(sc,$System.OBJ.Compile(classname,"fck-dv")) } Catch (err) { Set sc = $$$ADDSC(sc,err.AsStatus()) } Return sc }   では、**Ens.BusinessOperation**を継承したビジネスオペレーションのクラスを作成しましょう。 Set sc = $$$ADDSC(sc,..CreateOperation()) quit:$$$ISERR(sc) このクラスを作成するほかに、MessageMapとメソッドConsumeを作成します。 ClassMethod CreateOperation() As %Status [ Private ] {     New $Namespace     Set $Namespace = ..#NAMESPACE     Set sc = $$$OK     Try {         Set classname = "diashenrique.messageviewer.Operation.Consumer"         Set class = ##class(%Dictionary.ClassDefinition).%New(classname)         Set class.GeneratedBy = $ClassName()         Set class.Super = "Ens.BusinessOperation"         Set class.ProcedureBlock = 1         Set class.Inheritance = "left"           Set xdata = ##class(%Dictionary.XDataDefinition).%New()         Set xdata.Name = "MessageMap"         Set xdata.XMLNamespace = "http://www.intersystems.com/urlmap"         Do xdata.Data.WriteLine("")         Do xdata.Data.WriteLine("")         Do xdata.Data.WriteLine("Consume")         Do xdata.Data.WriteLine("")         Do xdata.Data.WriteLine("")         Do xdata.Data.WriteLine("Consume")         Do xdata.Data.WriteLine("")         Do xdata.Data.WriteLine("")               Do class.XDatas.Insert(xdata)         Set sc = $$$ADDSC(sc,class.%Save())           Set method = ##class(%Dictionary.MethodDefinition).%New(classname)         Set method.Name = "Consume"         Set method.ClassMethod = 0         Set method.ReturnType = "%Status"         Set method.FormalSpec = "input:diashenrique.messageviewer.Message.SimpleRequest,&output:Ens.Response"         Set stream = ##class(%Stream.TmpCharacter).%New()         Do stream.WriteLine("   set sc = $$$OK")         Do stream.WriteLine("   $$$TRACE(input.Message)")         Do stream.WriteLine("   return sc")         Set method.Implementation = stream         Set sc = $$$ADDSC(sc,method.%Save())           Set sc = $$$ADDSC(sc,$System.OBJ.Compile(classname,"fck-dv"))     }     Catch (err) {         Set sc = $$$ADDSC(sc,err.AsStatus())     }     Return sc } 相互運用性プロダクションを作成する直前のステップでは、ビジネスサービスクラスを作成しましょう。 Set sc = $$$ADDSC(sc,..CreateRESTService()) quit:$$$ISERR(sc) このクラスにはHttpリクエストを受信するためのUrlMapとRoutesがあります。 ClassMethod CreateRESTService() As %Status [ Private ] {     New $Namespace     Set $Namespace = ..#NAMESPACE     Set sc = $$$OK     Try {         Set classname = "diashenrique.messageviewer.Service.REST"         Set class = ##class(%Dictionary.ClassDefinition).%New(classname)         Set class.GeneratedBy = $ClassName()         Set class.Super = "EnsLib.REST.Service, Ens.BusinessService"         Set class.ProcedureBlock = 1         Set class.Inheritance = "left"           Set xdata = ##class(%Dictionary.XDataDefinition).%New()         Set xdata.Name = "UrlMap"         Set xdata.XMLNamespace = "http://www.intersystems.com/urlmap"         Do xdata.Data.WriteLine("")         Do xdata.Data.WriteLine("")         Do xdata.Data.WriteLine("")         Do xdata.Data.WriteLine("")         Do class.XDatas.Insert(xdata)         Set sc = $$$ADDSC(sc,class.%Save())           #; create adapter         Set adapter = ##class(%Dictionary.ParameterDefinition).%New(classname)         Set class.GeneratedBy = $ClassName()         Set adapter.Name = "ADAPTER"         Set adapter.SequenceNumber = 1         Set adapter.Default = "EnsLib.HTTP.InboundAdapter"         Set sc = $$$ADDSC(sc,adapter.%Save())           #; add prefix         Set prefix = ##class(%Dictionary.ParameterDefinition).%New(classname)         Set prefix.Name = "EnsServicePrefix"         Set prefix.SequenceNumber = 2         Set prefix.Default = "|demoiris"         Set sc = $$$ADDSC(sc,prefix.%Save())           Set method = ##class(%Dictionary.MethodDefinition).%New(classname)         Set method.Name = "SendMessage"         Set method.ClassMethod = 0         Set method.ReturnType = "%Status"         Set method.FormalSpec = "input:%Library.AbstractStream,&output:%Stream.Object"         Set stream = ##class(%Stream.TmpCharacter).%New()         Do stream.WriteLine("   set sc = $$$OK")         Do stream.WriteLine("   set request = ##class(diashenrique.messageviewer.Message.SimpleRequest).%New()")         Do stream.WriteLine("   set data = {}.%FromJSON(input)")         Do stream.WriteLine("   set request.Message = data.Message")         Do stream.WriteLine("   set sc = $$$ADDSC(sc,..SendRequestSync(""diashenrique.messageviewer.Operation.Consumer"",request,.response))")         Do stream.WriteLine("   return sc")         Set method.Implementation = stream         Set sc = $$$ADDSC(sc,method.%Save())           Set method = ##class(%Dictionary.MethodDefinition).%New(classname)         Set method.Name = "SendSomething"         Set method.ClassMethod = 0         Set method.ReturnType = "%Status"         Set method.FormalSpec = "input:%Library.AbstractStream,&output:%Stream.Object"         Set stream = ##class(%Stream.TmpCharacter).%New()         Do stream.WriteLine("   set sc = $$$OK")         Do stream.WriteLine("   set request = ##class(diashenrique.messageviewer.Message.AnotherRequest).%New()")         Do stream.WriteLine("   set data = {}.%FromJSON(input)")         Do stream.WriteLine("   set request.Something = data.Something")         Do stream.WriteLine("   set sc = $$$ADDSC(sc,..SendRequestSync(""diashenrique.messageviewer.Operation.Consumer"",request,.response))")         Do stream.WriteLine("   return sc")         Set method.Implementation = stream         Set sc = $$$ADDSC(sc,method.%Save())           Set sc = $$$ADDSC(sc,$System.OBJ.Compile(classname,"fck-dv"))     }     Catch (err) {         Set sc = $$$ADDSC(sc,err.AsStatus())     }     Return sc }   ## Visual Studioコードの使用 %Dictionaryパッケージを使用してクラスを作成するのは困難な場合があり、読みにくくもありますが、非常に便利です。 コードの可読性を良くしてアプローチをもう少しわかりやすくするために、Visual Studioコードを使用して新しいリクエスト、ビジネスサービス、およびビジネスオペレーションクラスを作成することにします。 * diashenrique.messageviewer.Message.SimpleMessage.cls * diashenrique.messageviewer.Operation.ConsumeMessageClass.cls * diashenrique.messageviewer.Service.SendMessage.cls Class diashenrique.messageviewer.Message.SimpleMessage Extends Ens.Request [ Inheritance = left, ProcedureBlock ] { Property ClassMessage As %String; }   Class diashenrique.messageviewer.Operation.ConsumeMessageClass Extends Ens.BusinessOperation [ Inheritance = left, ProcedureBlock ] { Method Consume(input As diashenrique.messageviewer.Message.SimpleMessage, ByRef output As Ens.Response) As %Status {      Set sc = $$$OK     $$$TRACE(pRequest.ClassMessage)     Return sc } XData MessageMap [ XMLNamespace = "http://www.intersystems.com/urlmap" ] {              Consume        }   } Class diashenrique.messageviewer.Service.SendMessage Extends Ens.BusinessService [ ProcedureBlock ] {  Method OnProcessInput(input As %Library.AbstractStream, ByRef output As %Stream.Object) As %Status {     Set tSC = $$$OK     // リクエストメッセージを作成     Set request = ##class(diashenrique.messageviewer.Message.SimpleMessage).%New()     // リクエストメッセージプロパティに値をセット     Set request.ClassMessage = input     // ビジネスプロセスに同期呼び出しを行い、レスポンスメッセージをレスポンスとして使用      Set tSC = ..SendRequestSync("diashenrique.messageviewer.Operation.ConsumeMessageClass",request,.output)     Quit tSC } } コードの可読性の観点では、大きな差があります!  ## 相互運用性プロダクションの作成 相互運用性プロダクションを仕上げましょう。 これを行うには、プロダクションクラスを作成してから、それをビジネスオペレーションとサービスクラスに関連付けます。 Set sc = $$$ADDSC(sc,..CreateProduction()) quit:$$$ISERR(sc) ClassMethod CreateProduction(purge As %Boolean = 0) As %Status [ Private ] {     New $Namespace     Set $Namespace = ..#NAMESPACE     Set sc = $$$OK     Try {          #; create new production         Set class = ##class(%Dictionary.ClassDefinition).%New(..#PRODUCTION)         Set class.ProcedureBlock = 1         Set class.Super = "Ens.Production"         Set class.GeneratedBy = $ClassName()         Set xdata = ##class(%Dictionary.XDataDefinition).%New()         Set xdata.Name = "ProductionDefinition"         Do xdata.Data.Write("")           Do class.XDatas.Insert(xdata)         Set sc = $$$ADDSC(sc,class.%Save())         Set sc = $$$ADDSC(sc,$System.OBJ.Compile(..#PRODUCTION,"fck-dv"))         Set production = ##class(Ens.Config.Production).%OpenId(..#PRODUCTION)         Set item = ##class(Ens.Config.Item).%New()         Set item.ClassName = "diashenrique.messageviewer.Service.REST"         Do production.Items.Insert(item)         Set sc = $$$ADDSC(sc,production.%Save())         Set item = ##class(Ens.Config.Item).%New()         Set item.ClassName = "diashenrique.messageviewer.Operation.Consumer"         Do production.Items.Insert(item)         Set sc = $$$ADDSC(sc,production.%Save())             Set item = ##class(Ens.Config.Item).%New()         Set item.ClassName = "diashenrique.messageviewer.Service.SendMessage"         Do production.Items.Insert(item)         Set sc = $$$ADDSC(sc,production.%Save())             Set item = ##class(Ens.Config.Item).%New()         Set item.ClassName = "diashenrique.messageviewer.Operation.ConsumeMessageClass"         Do production.Items.Insert(item)         Set sc = $$$ADDSC(sc,production.%Save())         }     Catch (err) {         Set sc = $$$ADDSC(sc,err.AsStatus())     }     Return sc } プロダクションクラスをビジネスオペレーションとサービスクラスに関連付けるために、クラス**Ens.Config.Item**を使用します。 これは、クラスの作成に%Dictionaryパッケージを使用したのか、VS Code、Studio、またはAtelierを使用したかに関係なく使用できます。 いずれにしても、達成できました! コードを使用して相互運用性プロダクションを作成できました。 ただし、このコードの元の目的を忘れてはいけません。拡張メッセージビューワの機能を示すプロダクションとメッセージを作成するという目的です。 以降のクラスメソッドを使用して、両方のビジネスサービスを実行し、メッセージを生成します。  ### %Net.HttpRequestを使用たメッセージの生成: ClassMethod GenerateMessages() As %Status [ Private ] {     New $Namespace     Set $Namespace = ..#NAMESPACE     Set sc = $$$OK     Try {         Set action(0) = "/demoiris/send/message"         Set action(1) = "/demoiris/send/something"         For i=1:1:..#LIMIT {             Set content = { }             Set content.Message = "Hi, I'm just a random message named "_$Random(30000)             Set content.Something = "Hi, I'm just a random something named "_$Random(30000)             Set httprequest = ##class(%Net.HttpRequest).%New()             Set httprequest.SSLCheckServerIdentity = 0             Set httprequest.SSLConfiguration = ""             Set httprequest.Https = 0             Set httprequest.Server = "localhost"             Set httprequest.Port = 9980             Set serverUrl = action($Random(2))             Do httprequest.EntityBody.Write(content.%ToJSON())             Set sc = httprequest.Post(serverUrl)              Quit:$$$ISERR(sc)         }     }     Catch (err) {         Set sc = $$$ADDSC(sc,err.AsStatus())     }     Return sc } ### EnsDirectorを使用したメッセージの生成: ClassMethod GenerateUsingEnsDirector() As %Status [ Private ] {     New $Namespace     Set $Namespace = ..#NAMESPACE     Set sc = $$$OK     Try {         For i=1:1:..#LIMIT {             Set tSC = ##class(Ens.Director).CreateBusinessService("diashenrique.messageviewer.Service.SendMessage",.tService)             Set message = "Message Generated By CreateBusinessService "_$Random(1000)             Set tSC = tService.ProcessInput(message,.output)             Quit:$$$ISERR(sc)         }     }     Catch (err) {         Set sc = $$$ADDSC(sc,err.AsStatus())     }     Return sc }   } コードは以上です。 完全なプロジェクトは、[https://github.com/diashenrique/iris-message-viewer](https://github.com/diashenrique/iris-message-viewer.)をご覧ください。 ## プロジェクトの実行 では、プロジェクトの実際の動作を確認しましょう。 まず、git cloneまたはgit pullで、任意のローカルディレクトリにリポジトリを作成します。 `git clone https://github.com/diashenrique/iris-message-viewer.git` 次に、このディレクトリでターミナルを開き、次を実行します。 `docker-compose build` 最後に、プロジェクトでIRISコンテナを実行します。 `docker-compose up -d` さらに、を使用して管理ポータルにアクセスします。 次の画像のように、相互運用性のネームスペースMSGVIEWERが表示されます。 ![](/sites/default/files/inline/images/images/iris_managementportal.png) そしてこれが、私たちの愛らしいプロダクションです。2つのビジネスサービスと2つのビジネスオペレーションがあります。 ![](/sites/default/files/inline/images/images/iris_production.png) 非常にたくさんのメッセージがあります。 ![](/sites/default/files/inline/images/images/standardmessageviewer.png) カスタムメッセージビューワですべてが稼働しているので、その機能を見てみましょう。 ## 拡張メッセージビューワ 相互運用性プロダクションに有効になっているネームスペースのみが表示されることに注意してください。 [![Interoperability Message Viewer](https://raw.githubusercontent.com/diashenrique/iris-message-viewer/master/images/InteroperabilityNamespace.png)](https://raw.githubusercontent.com/diashenrique/iris-message-viewer/master/images/InteroperabilityNamespace.png)   拡張メッセージビューワには、さまざまなフィルタの作成、nレベルへの列のグループ化、Excelへのエクスポートなどを行える機能と柔軟性が備わっています。 ![Interoperability Message Viewer](https://raw.githubusercontent.com/diashenrique/iris-message-viewer/master/images/MessageViewer.png) さまざまなフィルタを使用して、必要な結果を得ることができます。 また、Shiftキーを押しながら列のヘッダーをクリックすると、複数の並べ替えを使用することも可能です。 データグリッドをExcelにエクスポートすることもできるのです! ![](/sites/default/files/inline/images/images/filteroption.gif) ![](/sites/default/files/inline/images/images/filterpanel.gif) さらに、フィルタビルダーオプションを使用して、複雑なフィルタを作成することができます。 使用できる任意の列に対してデータをグループ化し、必要なnレベルを使用して情報をまとめることができます。 デフォルトでは、このグループはDate Created(作成日)フィールドを使用して作成されます。 ![データのグループ化](https://raw.githubusercontent.com/diashenrique/iris-message-viewer/master/images/GroupByFunction.gif)   また、列を選択できる機能があります。 次のページには、Ens.MessageHeaderのすべての列があります。デフォルトの列のみが初期ビューに表示されていますが、 「Column Chooser」(列選択)ボタンを使って、ほかの列を選択することができます。 ![列セレクター](https://raw.githubusercontent.com/diashenrique/iris-message-viewer/master/images/ColumnChooser.gif) すべてのグループはワンクリックで折りたたみと展開が可能です。 ![](https://raw.githubusercontent.com/diashenrique/iris-message-viewer/master/images/Collapse_Expand.gif) SessionId(セッションID)フィールドの情報には、ビジュアルトレース機能へのリンクがあります。 ![ビジュアルトレース](https://raw.githubusercontent.com/diashenrique/iris-message-viewer/master/images/MessageVisualTrace.png) 必要に応じて、メッセージを再送することができます。 必要なメッセージを選択し、Resend をクリックするだけで再送信は完了です。 この機能には、次のクラスメソッドが使用されています。 `##class(Ens.MessageHeader).ResendDuplicatedMessage(id)` ![](https://raw.githubusercontent.com/diashenrique/iris-message-viewer/master/images/ResendMessage.gif) 最後に、前述のように、データグリッドをExcelにエクスポートすることができます。 ![](https://raw.githubusercontent.com/diashenrique/iris-message-viewer/master/images/ExportToExcel.png) Excelの結果には、キャッシュサーバーページ(CSP)に定義されているものと同じフォーマット、コンテンツ、およびグループが表示されます。 追伸: この問題への取り組みで大いに助けてくれた@Renan.Lourencoに、特に深くお礼申し上げます。  
記事
Mihoko Iijima · 2021年11月4日

【GettingStarted with IRIS】MQTT を使った遠隔モニタリング(IRIS の MQTT アダプタを試してみよう!)

開発者の皆さん、こんにちは! この記事では、【GettingStarted with IRIS】シリーズの MQTT アダプタを簡単に試せるサンプルの利用方法についてご紹介します! (MQTTブローカーはインターネット上に公開されているテスト用ブローカーを利用しています)​​ サンプルは、こちら👉https://github.com/Intersystems-jp/Samples-MQTT-EKG-Devices (コンテナで動作します) IRIS/IRIS for Health のバージョン2020.1から、IoT の世界でよく利用される MQTT プロトコルに対応できる MQTT アダプタが追加されました。 MQTTインバウンドアダプタでは、メッセージの Subscribe が行え、MQTTアウトバンドアダプタでは、メッセージの Publish が行えます。 サンプルでは、MQTT を使った遠隔モニタリングをテーマに、患者さんに装着した心電図から心拍数(BPM)をリアルタイムに近い状態で取得し、モニタ画面に患者さん毎の心拍数を表示します(IRIS の MQTT インバウンドアダプタを利用したメッセージの Subscribe をご体験いただけます)。 Publish されるトピックについて サンプルでは、演習環境毎にユニークになるようにコンテナ開始時に以下の形式でトピックを作成しています(末尾の # はワイルドカードの指定です)。 /Student_4629/acmeHospital/EKG/# 実際に Publish されるトピックは患者さんに装着した心電図のデータになるので、# の部分は、Patient-1 や Patient-2 などのように患者さんを特定できる文字列が入ります。 サンプルのシナリオは、1つの医療機関の患者情報を取得する流れにしています(本来であれば複数の医療機関の患者情報をモニタできるようにしたほうが良いのですがシンプルに試すため、1つの医療機関の患者情報を取得する流れにしています)。 トピックの流れ サンプルには1つHTMLファイルが用意されています。このファイルをブラウザで開くと MQTT ブローカーに接続し、データ(心拍数)を1秒ごとにブローカーへ Publish します。 IRIS は、MQTTブローカーから指定のトピックを Subscribe します。 ​​​ MQTT ブローカーを Subscribe する設定を行った IRIS のサービスは、ブローカーからトピックを取得します。 実際の設定は以下の通りです。 受信したトピックは IRIS の中では​​​メッセージ(EnsLib.MQTT.Message)として扱われ、次のコンポーネントであるプロセス(図では Process_MQTT_Request)に渡します。 プロセスでは、受信した MQTT 用メッセージからモニタ表示に利用するデータ(Solution.HeartRate)に変換するため、データ変換を呼び出します。 プロセスエディタの開き方と、中で行われているデータ変換の呼び出しの設定を確認する方法は以下の通りです。 患者ごとの測定値を参照する流れ MQTT ブローカーから Subscribe した心拍数をリアルタイムに近い状態で画面表示するため、データ変換で作成された Solution.HeartRate から1秒ごとに患者ごとの心拍数を収集し、画面に表示しています。 この表示を行うため、1秒間隔で Solution.HeartRate に対するSELECT文が実行されています。 このクエリを実行しているのが、メトリックと呼ぶクラスです。プロダクションでは以下の場所に設定されています。 メトリックでは、指定の呼び出し間隔で Solution.HeartRate から患者ごとの BPM を収集しています。詳細はソースコードをご参照ください。 ということで、実際にサンプルを動かして動作を確認してみましょう!手順は以下の通りです。 (1) git clone (2) コンテナビルド&開始 (3) 演習環境毎のトピック作成 (4) プロダクション開始 (5) サンプルHTMLをブラウザで開いて Publish 開始! (6) モニタ画面で状況確認 (1) git clone git clone https://github.com/intersystems/Samples-MQTT-EKG-Devices (2) コンテナの開始 git clone で作成されるディレクトリに移動します。 cd Samples-MQTT-EKG-Devices Windows 以外でお試しいただいている場合は、setup.sh を実行します。 ./setup.sh コンテナを開始します。 docker-compose up -d (3) 演習環境毎のトピック作成 docker-compose exec iris iris session iris -U %SYS "##class(App.Installer).InitializeDocker()" この実行で演習環境で使用するトピックを指定しています。 注意:appp.htmlで使用するJavaScripとプロダクション設定に演習環境用のトピックを追記しています。この実行を行わないとサンプルは動作しません。 IRISの起動が完了していないと、上記メソッド実行後、以下のメッセージが出力されます。 Sign-on inhibited: Startup or Installation in progress このメッセージが表示される場合は、しばらく待ってから再度実行してください。 正常に実行できると、以下のように演習環境用のトピックが表示されます。 You have successfully initiated the MQTT exercise Please take note of your topic top-level string: /Student_4908/acmeHospital/EKG/# press enter to continue Enterで元の画面に戻ります。 (4) プロダクション開始 ​​​​​​管理ポータルを開きます。👉 http://localhost:52773/csp/sys/UtilHome.csp ユーザ名: SuperUser パスワード(大文字で設定されています): SYS Interoperability > INTEROPネームスペース選択 > 一覧 > プロダクション (5) サンプルHTMLをブラウザで開いて Publish 開始! app.html を開きます👉 http://localhost/app.html サーバ名はご利用の環境に合わせてご変更ください。 IRIS が​ MQTT ブローカーから Subscribe したトピックは、メッセージを利用して確認できます。 開いたプロダクション画面で、サービス:From_EKG_MQTT をクリックし、画面右の「メッセージ」タブを選択します。ヘッダの列にある番号をクリックするとトレース画面が開き、受信したトピックとその値が確認できます。 ​​​​​(6) モニタ画面で状況確認 モニタ画面を開き、患者ごとの心拍数を確認します。 管理ポータル > Analytics > ユーザポータル > SolutionEKG ※管理ポータル 👉 http://localhost:52773/csp/sys/UtilHome.csp 初期状態では3名の患者データが表示されます。 app.html にある 「追加」ボタンで心電図を付けた患者を増やすことができます。 app.html で患者を増やすと、モニタ用画面にも表示が増えます。 また、各患者のバーをスライドさせ、心拍数を変化させると、グラフもそれに合わせて変化し、MQTT ブローカーから Subscribe できていることがわかります。 以上でサンプルの動作確認は終了です。 app.html を閉じると MQTT ブローカーへの Publish が終了します。 最後に、コンテナを停止します。 docker-compose stop コンテナを破棄する場合は、以下のコマンドを実行してください。 docker-compose down サンプルには、完成形の Solution パッケージの他に、Demo パッケージが用意されいてプロダクションの定義やプロセスの作成を試すこともできます。 作成方法については、次の記事でご紹介する予定です!
記事
Toshihiko Minamoto · 2021年8月11日

OAuth 認証と InterSystes IRIS: トラストプロトコルを使いこなす

### 不在時に、セキュリティとプライバシーを維持しながら、コンピューターを相互に信頼させるにはどうすればよいでしょうか? ![](/sites/default/files/inline/images/images/c07270055f2e7600834b6fde7c64fcda.jpg) 「ドライマルティーニを」と彼は言った。 「1 杯。 深いシャンパングラスで。」 「承知いたしました。」 「気が変わった。 ゴードンを 3、ヴォッカを 1、キナリレを半量。 キンキンに冷えるまでよくシェイクしてから、大きめの薄いレモンピールを 1 つ加えてくれ。 わかったかい?」 「お承りいたしました。」 バーテンダーはその考えが気に入ったようだった。 イアン・フレミング著『カジノ・ロワイヤル』(1953 年)より OAuth は、ユーザーログイン情報を伴うサービスを「運用中」のデータベースから、物理的にも地理的にも分離する上で役立ちます。 このように分離すると、ID データの保護が強化され、必要であれば、諸国のデータ保護法の要件に準拠しやすくしてくれます。 OAuth を使用すると、ユーザーは、最小限の個人データをさまざまなサービスやアプリケーションに「公開」しながら、一度に複数のデバイスから安全に作業することができるようになります。 また、サービスのユーザーに関する「過剰な」データを操作しなくてよくなります(データはパーソナル化されていない形態で処理することができます)。 InterSystems IRIS を使用する場合、OAuth と OIDC サービスを自律的かつサードパーティのソフトウェア製品と連携してテストし、デプロイするための既成の完全なツールセットを利用できます。 ### OAuth 2.0 と Open ID Connect OAuth と Open ID Connect(OIDC または OpenID)は、アクセスと識別をデリゲートするためにオープンプロトコルを汎用的に組み合わせたもので、21 世紀現在、人気を得ているようです。 大規模な使用において、これより優れたオプションはまだ誰も思いついていません。 HTTP(S) プロトコル上にとどまり、[JWT(JSON Web Token)コンテナ](https://en.wikipedia.org/wiki/JSON_Web_Token)を使用するため、特にフロントエンドのエンジニアに人気があります。 OpenID は OAuth を使用して機能しています。実際、OpenID は OAuth のラッパーです。 OpenID を電子識別システムの認証と作成に使用するオープンスタンダードとして使用することは、開発者にとって目新しい事ではありません。 2019 年には、公開から 14 周年を迎えました(バージョン 3)。 Webとモバイル開発、そしてエンタープライズシステムで人気があります。 そのパートナーである OAuth オープンスタンダードはアクセスをデリゲートする役割を担っており、12 年目を迎えています。関連する RFC 5849 標準が登場してからは 9 年です。 この記事の目的により、プロトコルの最新バージョンである OAuth 2.0 と最新の [RFC 6749](https://tools.ietf.org/html/rfc6749) を使用することにしましょう。 (OAuth 2.0 は、その前身の OAuth 1.0 とは互換していません。) 厳密に言えば、OAuth はプロトコルではなく、ソフトウェアシステムにアクセス権制限アーキテクチャを実装する際に、ユーザー識別操作を分離して別のトラステッドサーバーに転送するための一連のルール(スキーム)です。 OAuth は特定のユーザーについて何も言及できないことに注意してください! ユーザーが誰であるか、ユーザーがどこにいるのか、またユーザーが現在コンピューターを使用しているかどうかさえも、知ることはできません。 ただし、OAuth を使用すれば、事前に発行されたアクセストークンを使用して、ユーザーが参加することなくシステムと対話することが可能であり、 これは重要なポイントです(詳細は、OAuth サイトにある「[User Authentication with OAuth 2.0](https://oauth.net/articles/authentication/)」をご覧ください)。 [User-Managed Access(UMA)](https://tools.ietf.org/html/draft-hardjono-oauth-umacore-14)プロトコルも OAuth に基づくプロトコルです。 OAuth、OIDC、および UMA を合わせて使用することで、次のような分野で保護された ID とアクセス管理(IdM、IAM)システムを実装することができます。 * 医療分野における患者の [HEART(Health Relationship Trust)](https://openid.net/wg/heart/)個人データプロファイルの使用。 * 製造会社および貿易会社向けの顧客 ID&アクセス管理(CIAM)プラットフォーム。 * [OAuth 2.0 Internet of Things (IoT) Client Credentials Grant](https://tools.ietf.org/html/draft-tschofenig-ace-oauth-iot-00) による、IoT(モノのインターネット)システムにおけるスマートデバイス向けデジタル証明書のパーソナル化。 API エコノミーの新しいアクセス制御ベン図 何よりも、個人データをシステムのほかの部分と同じ場所に保存してはいけません。 認証と認可は物理的に分離する必要があります。 そして、ID と認証を各個人に与えることが理想と言えます。 自分で保管せずに、 所有者のデバイスを信頼するのです。 ### 信頼と認証 ユーザーの個人データを自分のアプリや作業データベースと組み合わさったストレージ場所に保存するのはベストプラクティスではありません。 言い換えれば、このサービスを提供できる信頼のある人を選ぶようにする必要があります。 このサービスは、次の項目で構成されます。 * ユーザー * クライアントアプリ * 識別サービス * リソースサーバー アクションは、ユーザーのコンピューターの Web ブラウザで実行されます。 ユーザーには識別サービスが備わったアカウントがあり、 クライアントアプリは、識別サービスと相互インターフェースとの契約に署名済みです。 リソースサーバーは、識別サービスを信頼して、識別できた人にアクセスキーを発行します。 ユーザーはクライアント Web アプリを実行して、リソースを要求します。 クライアントアプリは、アクセス権が必要なそのリソースへのキーを提示する必要があります。 ユーザーにキーがない場合、クライアントアプリはリソースサーバーへのキーを発行するために契約している識別サービスに接続します(ユーザーを識別サービスに転送します)。 識別サービスは、どのようなキーが必要かを問い合わせます。 ユーザーは、リソースにアクセスするためのパスワードを入力します。 この時点でユーザー認証が行われ、ユーザーの身元が確認されると、リソースへのキーが提供され(ユーザーをクライアントアプリに戻します)、ユーザーがリソースを利用できるようになります。 ### 認可サービスの実装 InterSystems IRIS プラットフォームでは、必要に応じてさまざまなプラットフォームからのサービスをアセンブルできます。 次はその例です。 1. デモクライアントが登録された OAuth サーバーを構成して起動します。 2. デモ OAuth クライアントを OAuth サーバーと Web リソースに関連付けて構成します。 3. OAuth を使用できるクライアントアプリを開発します。 Java、Python、C#、または Node JS を使用できます。 以下の方に、ObjectScript でのアプリケーションコードの例を示しています。 OAuth にはさまざまな設定があるため、チェックリストが役立ちます。 例を見ていきましょう。 IRIS 管理ポータルに移動し、[システム管理]>[セキュリティ]>[OAuth 2.0]>[サーバー]の順に選択します。 各項目には設定行の名前とコロン、そして必要であればその後に例または説明が含まれます。 別の方法として、Daniel Kutac の 3 部構成になっている「[InterSystems IRIS Open Authorization Framework (OAuth 2.0)の実装 - パート1](https://jp.community.intersystems.com/node/478821)」、[パート2](https://jp.community.intersystems.com/node/480201)、そして[パート3](https://jp.community.intersystems.com/node/480196) に記載されているスクリーンショットのヒントを参考にしてください。 次のスクリーンショットはすべて、例として提示されています。 独自のアプリケーションを作成する際は、別のオプションを選択する必要があるでしょう。 ![](/sites/default/files/inline/images/images/snimok_e'krana_2019-09-06_v_23_12_54.png) [一般設定]タブで、次のように設定してください。 * 説明: 構成の説明を入力します。「認証サーバー」など。 * ジェネレーターのエンドポイント(以降「EPG」)のホスト名: サーバーの DNS 名。 * サポートされている許可の種類(少なくとも 1 つを選択): * 認可コード * 暗黙 * アカウントの詳細: リソース、所有者、パスワード * クライアントアカウントの詳細 * SSL/TLS 構成: oauthserver [スコープ]タブで、次を設定します。 * サポートされているスコープを追加: この例では「scope1」です。 [間隔]タブで、次を設定します。 * アクセスキー間隔: 3600 * 認可コードの間隔: 60 * キー更新の間隔: 86400 * セッション中断間隔: 86400 * クライアントキー(クライアントシークレット)の有効期間: 0 [JWT 設定]タブで、次を設定します。 * 入力アルゴリズム: RS512 * キー管理アルゴリズム: RSA-OAEP * コンテンツ暗号化アルゴリズム: A256CBC-HS512 [カスタマイズ]タブで、次を設定します。 * 識別クラス: %OAuth2.Server.Authenticate * ユーザークラスの確認: %OAuth2.Server.Validate * セッションサービスクラス: OAuth2.Server.Session * キーの生成クラス: %OAuth2.Server.JWT * カスタムネームスペース: %SYS * カスタマイズロール(少なくとも 1 つ選択): %DB_IRISSYS および %Manager では、変更内容を保存します。   次のステップでは、OAuth サーバーにクライアントを登録します。 [顧客の説明]ボタンをクリックして、[顧客の説明を作成]をクリックします。 ![](/sites/default/files/inline/images/images/snimok_e'krana_2019-09-06_v_23_13_15.png) [一般設定]タブで、次の情報を入力します。 * 名前: OAuthClient * 説明: 簡単な説明を入力します。 * クライアントタイプ: 機密 * リダイレクト URL: oauthclient から識別した後に、アプリに戻るポイントのアドレス。 * サポートされている付与の種類: * 認可コード: はい * 暗黙 * アカウントの詳細: リソース、所有者、パスワード * クライアントアカウントの詳細 * JWT 認可 * サポートされているレスポンスタイプ: 次のすべてを選択してください。 * コード * id_token * id_token キー * トークン * 認可タイプ: シンプル [クライアントアカウントの詳細]タブは自動的に入力されますが、クライアントの正しい情報であるかを確認してください。 [クライアント情報] タブには次の項目があります。 * 認可画面: * クライアント名 * ロゴの URL * クライアントのホームページ URL * ポリシーの URL * 利用規約の URL   では、[システム管理]>[セキュリティ]>[OAuth 2.0]>[クライアント]の順に移動して、OAuth サーバークライアントにバインディングを構成します。 ![](/sites/default/files/inline/images/images/snimok_e'krana_2019-09-06_v_23_13_52.png) サーバーの説明の作成: * ジェネレーターのエンドポイント: 一般的なサーバーのパラメーターから取得されます(上記を参照)。 * SSL/TLS 構成: 事前構成済みのリストから選択します。 * 認可サーバー: * 認可エンドポイント: EPG + /authorize * キーエンドポイント: EPG + /token * ユーザーエンドポイント: EPG + /userinfo * キーのセルフテストエンドポイント: EPG + /revocation * キーの終了エンドポイント: EPG + /introspection * JSON Web Token(JWT)設定: * 動的登録以外のほかのソース: URL から JWKS を選択します。 * URL: EPG + /jwks ![](/sites/default/files/inline/images/images/snimok_e'krana_2019-09-06_v_23_14_00.png) このリストから、たとえばサーバーが OAuth-client にユーザーに関するさまざまな情報を提供できることがわかります(scopes\_supported および claims\_supported)。 また、アプリケーションを実装するときは、共有する準備ができているデータが何であるかをユーザーに尋ねても何の価値もありません。 以下の例では、scope1 の許可のみを要求します。 では、構成を保存しましょう。   SSL 構成に関するエラーがある場合は、[設定]>[システム管理]>[セキュリティ]>[SSL/TSL 構成]に移動して、構成を削除してください。 ![](/sites/default/files/inline/images/images/snimok_e'krana_2019-09-06_v_23_15_30.png) これで OAuth クライアントをセットアップする準備が整いました。 [システム管理]>[セキュリティ]>[OAuth 2.0]>[クライアント]>[クライアント構成]>[クライアント構成を作成]に移動します。 [一般]タブで、次を設定します。 * アプリケーション名: OAuthClient * クライアント名: OAuthClient * 説明: 説明を入力します。 * 有効: はい * クライアントタイプ: 機密 * SSL/TCL 構成: oauthclient を選択します。 * クライアントリダイレクト URL: サーバーの DNS 名 * 必要な許可の種類: * 認可コード: はい * 暗黙 * アカウントの詳細: リソース、所有者、パスワード * クライアントアカウントの詳細 * JWT 認可 * 認可タイプ: シンプル [クライアント情報]タブで、次を設定します。 * 認可画面: * ロゴの URL * クライアントのホームページ URL * ポリシーの URL * 利用規約の URL * デフォルトのボリューム: サーバーに以前に指定したものが取得されます(scope1 など)。 * 連絡先メールアドレス: カンマ区切りでアドレスを入力します。 * デフォルトの最大経過時間(分): 最大認可経過時間または省略できます。 [JWT 設定]タブで、次を設定します。 * JSON Web Token(JWT)設定 * X509 アカウントの詳細から JWT 設定を作成する * IDToken アルゴリズム: * 署名: RS256 * 暗号化: A256CBC * キー: RSA-OAEP * Userinfo アルゴリズム * アクセストークンアルゴリズム * クエリアルゴリズム [クライアントログイン情報]タブで、次を設定します。 * クライアント ID: クライアントがサーバーに登録された際に発行された ID(上記を参照)。 * 発効されたクライアント ID: 入力されません * クライアントシークレット: クライアントがサーバーに登録された際に発行されたシークレット(上記を参照)。 * クライアントシークレットの有効期限: 入力されません * クライアント登録 URI: 入力されません 構成を保存しましょう。 ### OAuth 認可を使用した Web アプリ OAuth は、インタラクション参加体(サーバー、クライアント、Web アプリケーション、ユーザーのブラウザ、リソースサーバー)間の通信チャネルが何らかの形で保護されていることに依存しています。 この役割は SSL/TLS プロトコルが果たしているのがほとんどですが、 OAuth は、保護されていないチャネルでも機能します。 そのため、たとえばサーバー Keycloak はデフォルトで HTTP プロトコルを使用して、保護なしで実行します。 調整と調整時のデバッグが単純化されます。 サービスを実際に使用する際、OAuth のチャネル保護は、厳重な要件に含まれるべきであり、Keycloak ドキュメントに記述されている必要があります。 InterSystems IRIS の開発者は、OAuth に関するより厳密なアプローチに従っており、SSL/TSL の使用を要件としています。 単純化できる唯一の方法は、自己署名証明書を使用するか、組み込みの IRIS サービス PKI([システム管理]>>[セキュリティ]>>[公開鍵システム]を利用することです。 ユーザーの認可の検証は、UAuth サーバーに登録されているアプリケーションの名前と OAuth クライアントスコープの 2 つのパラメータを明示的に示すことで行えます。 Parameter OAUTH2APPNAME = "OAuthClient"; set isAuthorized = ##class(%SYS.OAuth2.AccessToken).IsAuthorized( ..#OAUTH2APPNAME, .sessionId, "scope1", .accessToken, .idtoken, .responseProperties, .error) 認可がない場合に備え、ユーザー ID をリクエストして、アプリケーションを操作する許可を取得するためのリンクを準備しておきます。 ここでは、OAuth サーバーに登録されているアプリケーションの名前を指定して、OAuth クライアントと要求されるボリューム(スコープ)を入力するだけでなく、ユーザーを返す Web アプリケーションのポイントへのバックリンクも指定する必要があります。 Parameter OAUTH2CLIENTREDIRECTURI = "https://52773b-76230063.labs.learning.intersystems.com/oauthclient/" set url = ##class(%SYS.OAuth2.Authorization).GetAuthorizationCodeEndpoint( ..#OAUTH2APPNAME, "scope1", ..#OAUTH2CLIENTREDIRECTURI, .properties, .isAuthorized, .sc)   IRIS を使用して、ユーザーを IRIS OAuth サーバーに登録しましょう。 たとえば、ユーザーに名前とパスワードを設定するだけで十分です。 受信した参照の下でユーザーを転送すると、サーバーはユーザーを識別する手続きを実行し、Web アプリケーションのアカウントデータによって、操作許可が照会されます。また、%SYS フィールドのグローバル OAuth2.Server.Session で自身に結果を保持します。 ![](/sites/default/files/inline/images/images/snimok_e'krana_2019-09-06_v_23_08_47.png)![](/sites/default/files/inline/images/images/snimok_e'krana_2019-09-06_v_23_10_02.png)   3. 認可されたユーザーのデータを示します。 手続きが正常に完了したら、アクセストークンなどがあります。 それを取得しましょう。 set valid = ##class(%SYS.OAuth2.Validation).ValidateJWT( .#OAUTH2APPNAME, accessToken, "scope1", .aud, .JWTJsonObject, .securityParameters, .sc )   以下に、完全に動作する OAuth の例のコードを示します。 Class OAuthClient.REST Extends %CSP.REST { Parameter OAUTH2APPNAME = "OAuthClient"; Parameter OAUTH2CLIENTREDIRECTURI = "https://52773b-76230063.labs.learning.intersystems.com/oauthclient/"; // to keep sessionId Parameter UseSession As Integer = 1; XData UrlMap [ XMLNamespace = "http://www.intersystems.com/urlmap" ] { <Routes> <Route Method="GET" Url = "/" Call = "Do" /> </Routes> } ClassMethod Do() As %Status { // Check for accessToken set isAuthorized = ##class(%SYS.OAuth2.AccessToken).IsAuthorized( ..#OAUTH2APPNAME, .sessionId, "scope1", .accessToken, .idtoken, .responseProperties, .error) // to show accessToken if isAuthorized { set valid = ##class(%SYS.OAuth2.Validation).ValidateJWT( ..#OAUTH2APPNAME, accessToken, "scope1", .aud, .JWTJsonObject, .securityParameters, .sc ) &html< Hello!<br> > w "You access token = ", JWTJsonObject.%ToJSON() &html< </html> > quit $$$OK } // perform the process of user and client identification and get accessToken set url = ##class(%SYS.OAuth2.Authorization).GetAuthorizationCodeEndpoint( ..#OAUTH2APPNAME, "scope1", ..#OAUTH2CLIENTREDIRECTURI, .properties, .isAuthorized, .sc) if $$$ISERR(sc) { w "error handling here" quit $$$OK } // url magic correction: change slashes in the query parameter to its code set urlBase = $PIECE(url, "?") set urlQuery = $PIECE(url, "?", 2) set urlQuery = $REPLACE(urlQuery, "/", "%2F") set url = urlBase _ "?" _ urlQuery &html< <html> <h1>Authorization in IRIS via OAuth2</h1> <a href = "#(url)#">Authorization in <b>IRIS</b></a> </html> > quit $$$OK } } コードの作業コピーは、InterSystems GitHub リポジトリ()にもあります。 必要に応じて、OAuth サーバーと OAuth クライアントに高度なデバッグメッセージモードを有効にしてください。これらは、%SYS エリアの ISCLOG グローバルに記述されます。 set ^%ISCLOG = 5 set ^%ISCLOG("Category", "OAuth2") = 5 set ^%ISCLOG("Category", "OAuth2Server") = 5 詳細については、「[IRIS、OAuth 2.0 と OpenID Connect の使用](https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=GOAUTH)」ドキュメントをご覧ください。 ### まとめ これまで見てきたように、すべての OAuth 機能には簡単にアクセスでき、完全に使用できる状態になっています。 必要に応じて、ハンドラークラスとユーザーインターフェースを独自のものに置き換えることができます。 OAuth サーバーとクライアントの設定は、管理ポータルを使う代わりに、構成ファイルで構成することも可能です。
記事
Mihoko Iijima · 2021年6月17日

【GettingStarted with IRIS】チュートリアルを始めよう!その1:Full Stack チュートリアル

開発者のみなさん、こんにちは! 2023/2/21追記 チュートリアルページが新しくなり「Developer Hub」に変わりました! Full Stackチュートリアルの開始方法や他のチュートリアルについて詳しくは、「InterSystems Developer Hub:クリック1回で開始できるチュートリアル(4種)のご紹介」をご参照ください。 この記事では、GettingStarted ページの無料体験環境(Sandbox)で試せるチュートリアルの中から、「Full Stack Tutorial」の使い方をご紹介します。 GettingStarted ページでできることについては、こちらの記事でご紹介しています。 無料体験環境(Sandbox)の開始手続きについては、こちらの記事でご紹介しています。ぜひご参照ください。 この記事では、チュートリアルを GettingStarted ページの Sandbox でご体験いただく流れを記載しています。Full Stack Tutorial のパート1 を開き、ログインいただくと Sandbox へのアクセス情報が表示されますので、Sandbox 用 IDE のリンクなどはお手元の環境でご確認ください。 また、チュートリアルの流れの中で、IRIS への接続情報を Sandbox 用 IDE で修正する内容があります。Full Stack Tutorial の対象ページを開き、お使いの Sandbox の情報をご確認ください。 Full Stack Tutorial 概要 (オリジナルはこちらから👉 https://gettingstarted.intersystems.com/full-stack/) InterSystems IRIS data platform は、マルチモデル(SQL、NoSQL)や、マルチワークロード(トランザクションとリアルタイム分析)が行える DBMS 機能、システム統合、変換、API管理、ビジネスロジックを操作できるプラットフォームで、組み込みの統合、分析機能、BI、機械学習、自然言語処理を含む強力で様々な機能を提供しています。これら機能を利用するために、別製品を追加することも、データを別の構成に移動することも不要で、全て 1 つのプラットフォームで操作いただけます。 フルスタックチュートリアルでは、小さな製造会社(焙煎したてのコーヒーを販売する会社)の基本的な情報管理インフラを作成していくテーマを用意しています。 この会社では、焙煎したばかりのおいしいコーヒー豆を焙煎、包装、販売しています。 このチュートリアルを通して、InterSystems IRIS data platform が IT アーキテクチャのバックボーンとしてどのように機能するかを学習いただけます。 このチュートリアルは、3つのパートに分かれています。 コーヒーメーカーとして、生豆の在庫管理からオンラインポータルでの販売までを設定するためのプロセスをご紹介します。 パート1 架空会社「IRIS コーヒーカンパニー」で使用する、簡単な在庫処理システムを作成します。 IRIS が他の多くのデータベースと同様に、テーブルの作成やデータの読み込みに標準的な SQL を使用できることをチュートリアルを通して学習します。また、Python を使用して JSON 形式で送付されてくる注文情報を処理します。 パート1の終わりには、新しいコーヒー豆の納品を会社の在庫として処理できるようになります。 パート2 在庫管理、焙煎、販売など、ビジネスのさまざまな処理を RESTful サービスを介して通信できるようにします。 コーヒーの焙煎所は在庫から豆を要求し、焙煎と包装の後、REST サービスを使って最終製品をカタログに載せ、オンラインで販売します。 これらの処理は全て、チュートリアルで作成する RESTful サービスを利用して実行されます。 パート3 人気のある JavaScript フレームワーク Vue.js を使用して、焙煎職人が作ったコーヒー豆を販売するオンラインストアを作成します。 1) パート1:SQLを使用してデータベースを作成する このチュートリアルを完成させるためには、無料体験環境の Sandbox の準備が必要です。 こちらのページにアクセスし👉https://gettingstarted.intersystems.com/full-stack/full-stack-part-one/)まだログインされていない場合は、ログインを行ってください。 ユーザ登録がまだの場合は、こちらの記事をご覧いただき、ユーザ登録後、ログインを行ってください。 ログイン後、 ボタンをクリックします。既にクリックされている場合は、アクセス情報が以下のように表示されます。 情報表示の一番上に「Sandbox IDE」と書かれたリンクがあります。こちらをクリックするとブラウザでアクセスできる Sandbox 専用 IDE が開きます(下図)。 チュートリアルは、この IDE を使用します。 準備ができたら、フルスタックチュートリアル用ソースコードを git clone するため、画面中央下のターミナルウィンドウで以下実行します。 git clone https://github.com/intersystems/quickstarts-full-stack.git IDE の画面左側に quickstarts-full-stack フォルダが参照できれば、git clone 成功です。 この IDE は Theia で、Visual Studio Code とよく似た機能を持っています。左側にファイルエクスプローラーがあります。右側にはコード編集パネルがあり、編集パネルの下にはターミナル・ウィンドウがあります。 ✨いよいよチュートリアルの開始です!✨ データベースを作成します。 IRIS コーヒーカンパニーは、3つの部門に分かれています。 倉庫にはコーヒーの生豆の在庫が保管されています。テーブル名:ICO.inventory にデータが格納されます。 焙煎所は、コーヒー豆を焙煎する部門で、データを保存する必要がない部門です。 Storefront は、焙煎したコーヒーを販売する店舗です。販売データはテーブル名:ICO.catalog に格納されます。 IRIS ターミナルをSQL用モードに切り替え、CREATE文を使って2つのテーブルを作成します。 手順は以下の通りです(IDEからアクセスできます)。 (1) Sandbox の IDE を開きます(パート1:https://gettingstarted.intersystems.com/full-stack/full-stack-part-one/#database-creation を開き、ログインしていると以下のように IDE へのリンクが表示されます)。 (2) IDE のメニューから、InterSystems > Web Terminal を選択します。別タブで Web ターミナルが開きます。 (3) ログイン画面が出てきたら、ユーザ名:tech  パスワード:demo を入力してログインします。 (4) プロンプトに USER> と表示される画面が開きます。 (5) Web ターミナルのモードを SQL 実行モードに変えるため、/sql を入力します。 (Web ターミナル専用コマンドです)。 (6) 以下のSQL文をコピーして、Web ターミナルに貼り付けます(Ctrl +v でペーストできます)。 CREATE TABLE ICO.inventory ( vendor_id VARCHAR(128), vendor_product_code VARCHAR(128), quantity_kg DECIMAL(10,2), date_arrival DATE ) プロンプトが戻ってくるまでお待ちください。 テーブルが作成できたか確認のため、SELECT * from ICO.inventory を実行してみます(まだレコードがないので、「Nothing to display」と表示されます)。 SELECT * from ICO.inventory 7) 次に、ICO.catalog テーブルを作成するため、以下 SQL をコピーし、Web ターミナルに貼り付け実行します。 CREATE TABLE ICO.catalog ( catalog_id BIGINT IDENTITY, product_code VARCHAR(128), quantity INTEGER, price DECIMAL(10,2), time_roasted DATETIME, roasting_notes VARCHAR(2048), img VARCHAR(2048) ) (8) 作成が完了したら、データはありませんが、SELECT * from ICO.catalog を実行します(これも、Nothing to display と表示されれば成功です)。 SELECT * from ICO.catalog ここまでで、テーブル定義の作成は終了です。 (9) Web ターミナルで、 /sql を実行すると、元のターミナルプロンプトに戻ります(=IRIS の ObjectScript が実行できるターミナルに戻ります)。 Python を利用してデータをロードします。 チュートリアルでは、Python でデータをロードする処理を作成しています。 大まかな処理の流れは以下の通りです。 世界中のベンダーから生豆の出荷依頼が来ると想定し、生豆の注文をデータベースに入力できるようにします。出荷情報は JSON 形式とし、単一の注文マニフェストファイルで送付される仕様としています。 注文マニフェストの例は、quickstarts-full-stack/setup/order_manifest.json にあります。IDE の左画面を利用してファイルを開いてみましょう。 データロードには、Python のプログラムを使用します。プログラム内では、注文マニフェストの JSON ファイルを解析し、データベースに接続し INSERT を実行しています(データ登録時に SQL が使用されますが、今回はWeb ターミナルではなく、Python のプログラム内で実行しています)。 注文マニフェストをデータベースにインポートする処理を作成します。 Python の標準ライブラリを使用して、指定ディレクトリから JSON ファイルを読み込みます。その後 ODBC 経由でデータベースに接続し、SQL の INSERT をし湯押して、JSON データを IRIS に登録します。 次の Python コードの解説が不要な場合は、サンプルスクリプトの実行に移動してください。 Python コードの中身解説 スクリプトは setup/manifest_importer.py にあります。 以下、データロード用スクリプトで行っている重要な要素について解説します。 main() 関数では、JSON 注文マニフェストファイルをインポートし、検証を行い、inventory テーブルに登録するために必要な構造をチェックしています。 def main(): with open('./order_manifest.json') as f: data = json.load(f) data, status, exp = validate_manifest(data) 次に、connection.config ファイルにあるデータベースの接続情報を読み込んでいます。 connection_detail = get_connection_info("connection.config") ip = connection_detail["ip"] port = int(connection_detail["port"]) namespace = connection_detail["namespace"] username = connection_detail["username"] password = connection_detail["password"] driver = "{InterSystems ODBC}" ODBCドライバーを使ってデータベースへの接続を設定します。 connection_string = 'DRIVER={};SERVER={};PORT={};DATABASE={};UID={};PWD={}' \ .format(driver, ip, port, namespace, username, password) connection = pyodbc.connect(connection_string) JSON データ(data)とデータベース接続オブジェクト(connection)を使って load_manifest() 関数を呼び出します。 msg = load_manifest(data, connection) load_manifest() 関数では、JSON ファイル内のすべてのアイテムを繰り返し取得しながら INSERT 文を組み立て、前の手順で作成した ICO.inventory テーブルに各アイテムを挿入します。 方法は以下の通りです。最初に、INSERT 文を作成しています。 fieldnamesql = "INSERT INTO ICO.inventory (vendor_id, vendor_product_code, quantity_kg, date_arrival)" 次の2行では、現在の日付を使用して「2021-06-16」の形式の日付文字列を作成します。この日付は、製品が倉庫に到着した日付に使用します。 today = date.today() mydate = today.strftime("%Y-%m-%d") このコードは、JSON ファイル内の各アイテムのオブジェクトをループし、取得したデータを使用して INSERT 文の VALUES の部分を作成しています。 その結果、次のような INSERT 文が完成します。 INSERT INTO ICO.inventory (vendor_id, vendor_product_code, quantity_kg, date_arrival) VALUES (ETRADER, ETHIOPIA32, 200, ‘2021-06-16’) 次に、ここまでの解説で作成した SQL 文(変数 valsql)を実行します。 load_manifest() 関数の1行目でデータベースカーソルを定義しているので、カーソルに SQL 文を入力して execute() を実行しています。 cursor.execute(valsql) Pythonのデータローダスクリプトを実行する コードの解説が終了しましたので、プログラムを実行してみましょう。 最初に、ODBCドライバのインストールを行います。 Sandbox の IDE を開きます。IDEを開く URL は https://gettingstarted.intersystems.com/full-stack/full-stack-part-one/#jsondataimport を開き、「Run the Python import」近くに表示されます。表示されない場合は、ログインを行ってください。 IDE を開いたら、ターミナルウィンドウで、以下実行してください。 cd /home/project/quickstarts-full-stack/setup sudo odbcinst -i -d -f pyodbc_wheel/linux/odbcinst.ini 次に、データベースの接続情報を確認します。 (1) Sandbox の IDE で /home/project/quickstarts-full-stack/setup/connection.config を開きます。 (2) IP アドレスに記載されている文字列を、修正します。 https://gettingstarted.intersystems.com/full-stack/full-stack-part-one/#jsondataimport を開き 「Database connection settings for your sandbox」の近くに変更対象の IP アドレスが表示されます。 図例だと、ip の設定に 34.71.28.209 を指定します。 port の設定も、27404 に変更します。 (3) 変更後、ファイルを保存します(Ctrl + s)。 接続情報変更後、Sandbox の IDE のターミナルウィンドウで以下実行します。 python manifest_importer.py 図例にあるような、INSERT 文の実行ログを確認できると思います。 IRIS は、Python だけでなく、Java、.NET、Node.js からもアクセスできます。言語別のアクセス方法の体験については、QuickStart をご参照ください。 SQLでデータを確認 Python から登録したデータが正しくテーブルに登録できているかを、Web ターミナルを使用して確認します。手順は以下の通りです。 (1) Sandbox 用 IDE 開きます。 https://gettingstarted.intersystems.com/full-stack/full-stack-part-one/#database-query を開き「SQL database queries」の近くに下図のように情報が表示されます。 (2)  InterSystems > Web Terminal を開きます。 (3) ログイン画面が表示されたら、ユーザ名:tech  パスワード:demo でログインしてください。 (4) /sql を入力し、SQL 実行モードに切り替えます。 以下実行します。 select * from ICO.inventory 詳細情報を参照するため、いくつかのクエリを実行してみましょう。 100 kg 以上の大型商品を検索するクエリ SELECT * FROM ICO.inventory WHERE quantity_kg > 100 特定のベンダー(例では、DKEの文字から始まるベンダー名を抽出)のすべての在庫を確認するクエリ SELECT * FROM ICO.inventory WHERE vendor_id LIKE 'DKE' 独自の在庫を追加してみましょう! 最後に、在庫を増やしてみましょう。vendor id、verdor_product_code、quantity_kg に独自の値を登録した JSON マニフェストファイルを作成します。 (1) Sandbox の IDE の左画面を利用して、order_manifest.json を開き、File > Save As... メニューを利用して、別名保存します(order_manifest-original.json)。 注意:データロード用スクリプトでは、JSON マニフェストファイルに含まれるコメント行の対応を行っていません。コメントは使用しないでください。 (2) Sandbox の IDE で order_manifest.json ファイルを開きます。 (3) 好きな値を登録します(英数字でご記入ください)。 (4) python manifest_importer.py を実行します。 パート1で確認できたこと IRIS をリレーショナルデータベースとして使用できることが確認できました。 また、Python から SQL 文を実行できることを確認できました。 この後は、パート2 に進み、会社全体で使用する REST サービスを作成します。 パート2:ObjectScript を使用した REST サービスの開発 (オリジナルページはこちら👉 https://gettingstarted.intersystems.com/full-stack/part-two-rest-services/) 適切に設計されたシステムでは、ビジネスアプリケーションをデータベース上で直接操作することはありません。その代わりに、サービスを介したアクセスを提供し、実行されるアクションを制御/監視できるようにします。 パート2では、ビジネスを機能させるために必要な RESTful Web サービスを作成します。 ほとんどのデータベースでは、Java Spring、Python Flask、Node.js Express などのミドルウェアフレームワークを使用して、SQLでデータレイヤーとやり取りしています。IRIS でもその方法を利用することはできますが、より簡単で高性能な別の選択肢があります。 ObjectScriptでコードを記述する:ストアドプロシージャのパフォーマンスと、プログラミング言語の柔軟性、パワー、使いやすさを手に入れることができます。 ミドルウェアが不要です:ミドルウェア層が組み込まれています。 コツさえつかめば、ObjectScript は Web アプリケーションのバックエンドを構築するための最速の方法と言えます! ObjectScript を使用したデータの操作 パート1 では、Python と SQL を使ってデータベースにアクセスする方法をご紹介しました。 パート2では、ObjectScript によるアクセスがいかに簡単か、特に主キーを使ってレコードを取得したい場合について見ていきたいと思います。 (1) Sandbox の IDE を開きます。 https://gettingstarted.intersystems.com/full-stack/part-two-rest-services/#query-cos を開き、「ObjectScript database query」の近くに IDE へのリンクが表示されます。 IDE などのアクセス情報の表示が、 のように表示されていたら、ピンク色のリンクをクリックしてください。 (2)  IDE のメニューから InterSystems > Web Terminal を選択します。 (3) ログイン画面が表示されたらユーザ名:tech パスワード:demo でログインしてください。 (4) 以下の ObjectScript のコマンドをコピーし、ターミナルに貼り付けて実行してください。 以下のコードは 主キーが 1 である ICO.Inventory のレコードを取得しています。 set item = ##class(ICO.inventory).%OpenId(1) zwrite item set item.quantitykg = 300 zwrite item do item.%Save() 1行ずつコードを解説します。 1行目:データベースからレコードを 1 件ロードしています。 2行目:レコードデータをターミナルに表示しています。 3行目:在庫数が設定されている quantitykg の値を 300(kg) に変更しています。 4行目:変更を確認するため、画面に再度表示しています。 5行目:データベースに変更データを保存しています。 InterSystems Web Terminal は、通常のコマンド・ライン・ターミナルと同様に動作しますが、ObjectScript コマンドも実行できます。 ObjectScript と SQL によるデータベースの操作 それでは、ObjectScript を使用して SQL を使ったより複雑なクエリを実行してみましょう。 Web Terminal に戻って、次のように入力してください。 set sqlquery = "SELECT * FROM ICO.inventory" set resultset = ##class(%SQL.Statement).%ExecDirect(,sqlquery) while resultset.%Next() { Write !, resultset.%Get("vendor_id") } 1行ずつコードを解説します。 1行目:実行したい SELECT 文を変数に設定しています。 2行目:%SQL.Statement クラスを使用して、1 行目で設定した変数に設定した SQL 文を実行し、変数 resultset に検索結果のインスタンスを設定しています。 3行目:resultset 変数を使用して、検索結果を繰り返し行移動(%Next())しながら vendor_id プロパティの値を画面表示しています。 ObjectScript のクラスを書いてみる 今まで試した Web Terminal のスクリプトは、1行に沢山のコードを指定しているので、見やすいとは言えません。 今度は、ObjectScript のコードをファイルにまとめてみましょう。 (1) Sandbox の IDE を開きます (2) 画面左のフォルダから、quickstarts-full-stack > services > cls > ICO を開きます。 (3) ICO フォルダを右クリックし、New File メニューを選択します。 (4) ファイル名に Test.cls を指定し、OK ボタンをクリックします。 (5) ファイルに以下の文字列を記述します(ICO.Test クラスを作成しています)。 Class ICO.Test{/// DescriptionClassMethod QueryDB() As %Status{ set sqlquery = "SELECT * FROM ICO.inventory" set resultset = ##class(%SQL.Statement).%ExecDirect(,sqlquery) while resultset.%Next() { Write !, resultset.%Get("vendor_id") }}} 記述後、ファイルを保存し(Ctrl + s)Web Terminal で以下実行します。 do ##class(ICO.Test).QueryDB() SQL と ObjectScript の使い分けや、マルチモデル機能については、開発者コミュニティの記事で詳しく紹介しています。 Web サービスの作成 ここまでの内容は、ObjectScript のプログラミング入門編でした。 ここからは、確認した内容を活かして、コーヒー焙煎ビジネスに必要な Web サービスを構築してみましょう。 作成概要は以下の通りです。 (1) 事前に作成されたコードをコピーしてコンパイルします。 (2) JSON の操作が行えるようにテーブル定義を変更します(JSONアダプタクラスの継承を追加します)。 (3) RESTful API がどのように構築されているか確認します。 (4) curl とブラウザを使い、Web サービスのデプロイとテストを行います。 (5) Web サービスを利用して、コーヒーの在庫を店舗に移動させます。 (6) 販売情報を記録します。 パート1では、標準的な SQL を使用してデータベースのテーブルを作成しました。 実は、その作成の裏では、対応する ObjectScript クラスも作成されていることをご説明していませんでした。 テーブルを作成するとクラス定義(永続クラス)も用意される仕組みにより、開発者は目的に応じて、SQL とコードの記述が簡単に行えるようになっています。 SQL で取得したデータを JSON としても出力できるようにするためには、データベースのテーブル定義に ObjectScript のコードに少し加える必要があります。その前に、データベースに登録されているコードを見てみましょう。 (1) Sandbox の IDE を開き、InterSystems のアイコン  をクリックします。 (2) IDE の画面左側で Classes > ICO を展開します。catalog.cls と inventory.cls の名称のファイルが確認できます。 パート1 で SQL 文を使ってデータベースにテーブルを作成した際に、それに対応する ObjectScript クラスも同時に作成されていることが確認できます。 (3) catalog.cls をクリックします(上図)。カラムの定義は Property 定義として表示されています。また、データ型や文字長の設定は見覚えのある値が登録されているはずです。同様に、inventory.cls をご参照ください。 (4) 開いたファイルはすべて閉じておいてください。 JSON の操作が行えるテーブル定義に変更する JSON の入出力を可能するため、確認したクラスを少し変更する必要があります。以下の手順で変更しましょう。 (1) Sandbox の IDE の ファイルのアイコンをクリックします。 (2) quickstarts-full-stack > services > cls > ICO を開きます。 (3) ICO フォルダの上で右クリック > New File を選択し、catalog.cls の名称で保存します。 (4) ICO フォルダの上で右クリック > New File を選択し、inventory.cls の名称で保存します。 (5) quickstarts-full-stack > services > cls_Sample > ICO を開きます。 (6) catlog.cls を開き、ファイル内を全てコピーし、quickstarts-full-stack > services > cls > ICO > catalog.cls に貼り付けます。 (7) quickstarts-full-stack > services > cls_Sample > ICO > inventory.cls を開き、ファイル内を全てコピーし、quickstarts-full-stack > services > cls > ICO > inventory.cls に貼り付けます。 (8) 変更したファイル(quickstarts-full-stack > services > cls > ICO > catalog.cls と inventory.cls )を保存します。 保存時、画面右下に選択欄が表示されるので "Overwrite on Server" を選択します。この操作により、%JSON.Adapter と プロパティのパラメータ:%JSONFIELDNAME を追加したクラスの新しいバージョンがサーバにアップロードされ、コンパイルされます。 (9) 2つのファイルに以下の操作を行いました。 A:%JSON.Adapter を継承し、テーブルのデータに対して自動的に JSON 出力が行えるように設定しました。 B:プロパティのパラメータ:%JSONFIELDNAME を追加し、JSON 出力時に使用するプロパティ名を変更しました。 ここまでの流れで、データベース用意したテーブル定義が、ObjectScript のクラス定義としても表現され、SQL と ObjectScript を使ってデータベースに対する操作ができることを確認できました。 これで、Web API を構築する準備が整いました! RESTful サービスを作ってみよう! テーブル定義(クラス定義)へ JSON アダプタを継承することで JSON の操作が行えるようになり、REST サービスを作成する準備が整いました! 先ほどと同様の手順で、クラス定義を追加します。 quickstarts-full-stack > services > cls > ICO 以下に Handler.cls を新規に作成します。手順は以下の通りです。 (1) Sandbox の IDE の ファイルのアイコンをクリックします。 (2) quickstarts-full-stack > services > cls > ICO フォルダを開きます。 (3) ICO フォルダを右クリックし New File を選択し、クラス名に Handler.cls を設定します。 (4) quickstarts-full-stack > services > cls_Samples > ICO フォルダを開きます。 (5) quickstarts-full-stack > services > cls_Samples > ICO> Handler.cls クラスを開き、クラス定義の中身を全部コピーし、(3) で作成した quickstarts-full-stack > services > cls > ICO > Handler.cls に貼り付けます。 (6) ファイルを保存します(今回は "Overwrite on Serve" のオプション表示は出てきません)。 Handler.cls の作成により、ミドルウェアとなる REST API が作成できました。 サービスを起動させるための最後の手順として、Web に公開するための設定を追加します。 IRIS では、Node.js の Express や Python の Flask と同じように、Web リクエストを ObjectScript コードにルーティングするツールを提供しています。 管理ポータルを使用して作成した ICO.Handler クラスを Web リクエスト時に呼び出されるように設定してみましょう。 管理ポータルを開きます。 Sandbox の IDE のメニューから InterSystems > Management Portal を選択します。 管理ポータルが開いたら、以下メニューにアクセスします。 システム管理 > セキュリティ > アプリケーション > ウェブ・アプリケーション 以下の手順で、REST のエンドポイントのパスを作成します。 (1)  ボタンをクリックします。 (2) 名前 に /api/coffeeco を設定します。 (3) ネームスペースに USER を設定します。 (4) アプリケーション有効 にチェックが入っていることを確認します。 (5) REST にチェックします。 (6) ディスパッチクラス に ICO.Handler を設定します。 (7) 許可された認証方法では、「認証なし」と「パスワード」にチェックを入れます。 (8) 保存ボタンをクリックし、設定を保存します。 保存後、「アプリケーション・ロール」タブが表示されるので、クリックし、「アプリケーションロール」に %All を設定します。 設定には、「利用可能」の一覧から %All を選択し、 をクリックし、画面右側の「選択済み」に移動させます。 その後、ボタンをクリックすると、「アプリケーションロール」に %All が追加されます。 REST インターフェースでは、ここで紹介しているように手動でコーディングすることもできますし、Open API v2.0 の仕様を作成して自動的にコードを生成する、API ファーストのアプローチをとることもできます。詳しくはドキュメントをご覧ください。 作成したサービスのテストをしましょう ここまでの設定で、REST 用エンドポイントの作成が完了しました。エンドポイントは https://gettingstarted.intersystems.com/full-stack/part-two-rest-services/#json-enable-the-data-tables を開き「Test the service」の近くに表示されます(Sandbox 毎に情報は異なります)。 https://Sandbox で利用できるWebサーバアドレス/api/coffeeco/inventory/listbeans 上記 URL(Webサーバアドレスはお試しいただいている環境に合わせてご変更ください)をコピーし、ブラウザのアドレスバーに入力し、REST サービスをテストすると、以下のような結果が返ります。 見易い表示に変えると、以下のような結果が返ります。 { "rowcount": 5, "items": [{ "id": "1", "vendor_product_code": "ETHIOPA32", "date_arrival": "65541", "quantity_kg": 400 }, { "id": "2", "vendor_product_code": "BRAZILPREM", "date_arrival": "65541", "quantity_kg": 200 }, { "id": "3", "vendor_product_code": "GUATEMALAALT30", "date_arrival": "65559", "quantity_kg": 200 }, { "id": "4", "vendor_product_code": "SUMATRA2", "date_arrival": "65559", "quantity_kg": 200 }, { "id": "5", "vendor_product_code": "SUMATRA3", "date_arrival": "65559", "quantity_kg": 400 }] } コーヒー豆を焙煎します 現在、ブラウザからのリクエストでレスポンスが返ってくることを確認できたので、サービスが動作していることがわかりました。ここからは、コーヒービジネスを操作するためのサービスを作成します。 最初に、コーヒーの生豆を在庫から取り出し、焙煎します。この動作には、/api/coffeeco/inventory/getbeans の URL を使用します。 Handler.cls を開き、ファイル末尾に定義された XData UrlMap の <Routes> のタグ内部をご参照ください。 URL /api/coffeeco/inventory/getbeans に対して、クラスメソッド GetRawBeans() が呼び出されるように定義されていることがわかります。 URL で値を渡すための URL に含めるパラメータの記載方法にも注目してください。 クラス定義内で GetRawBeans()メソッドを参照します。 レコードの主キーとなる値が URL の :id で渡されます。GetRawBeans() メソッドが実行されるとき、メソッドの第 1 引数に :id の情報が渡されます。 この値を %OpenId() メソッドの引数に指定することで、ObjectScript を使用してデータベースから対象のデータをロードすることができます(とても簡単な方法です)。 残りのコードは以下の通りです。 (1) quantity に設定されている値が十分な量であるかの確認 (2) 在庫から要求された量を減らす処理 (3) 要求元に新しい在庫量を返す処理 在庫から豆を取り出し、焙煎を行うためのリクエストを実行してみます。 今回のリクエストは、ブラウザでテストすることはできません(ブラウザで、POST 要求を送信することができないためです)。 Sandbox の IDE のターミナルウィンドウでは、curl コマンドを実行できるので、以下のように入力してみください。 注意:リクエストに使用する Web サーバアドレスは Sandbox の利用環境により異なります。お使いの環境のサーバ名に修正して実行してください。 curl -X POST https://52773-1-4e734fe2.try.learning.intersystems.com/api/coffeeco/inventory/getbeans/1/2.4 お店にコーヒーを並べる このチュートリアルではシンプルな例を利用しているため、リクエストされた生豆の量はどこにも記録されていません。 コーヒーを焙煎し、袋詰めし、店頭に並べて販売する処理についてはリクエストする Web アプリケーション側で処理することとします。 その処理を追加するため、quickstarts-full-stack > services > samples ディレクトリに 2 つのスクリプトが用意されています。 createproducts.sh 5 つのサンプルコーヒー製品の情報を作成するシェルです。最初の 3 つは本日焙煎されたもので、最後の 2 つは 6 日前に焙煎されたものです。 このシェルの実行で、鮮度の古い6日前の焙煎豆情報を作成しています。オンラインショップでは、鮮度が古い焙煎豆を割引して販売したいので、割引対象データとして準備しています。 loadproducts.sh curl コマンドを実行して、対象ディレクトリ内のすべての JSON ファイルを繰り返し参照し、用意した REST サービスを使用して JSON ファイル内のデータを ICO.catalog にロードします。 それではシェルを実行してみましょう。手順は以下の通りです。 (1) Sandbox の IDE のターミナルウィンドウで以下実行します。 cd /home/project/quickstarts-full-stack/services/samples sh createproducts.sh (2) IDE で、quickstarts-full-stack > services > samples ディレクトリに 5 つの JSON ファイルが作成できたことを確認してください。 (3) loadproducts.sh を IDE で開きます。 (4) 環境変数 IRISDB の値を修正します(Sandbox のウェブサーバアドレスに修正します)。 https://gettingstarted.intersystems.com/full-stack/part-two-rest-services/#coffee-to-store を開き「Put coffee in the store」の近くに表示される情報をご利用ください。 (5) loadproducts.sh を保存します。 (6) Sandbox の IDE のターミナルウィンドウで以下実行します。 cd /home/project/quickstarts-full-stack/services/samples sh loadproducts.sh シェルを実行したくない場合は、curl コマンドを利用してデータを読み込むこともできます。 コマンド実行例は以下の通りです(Webサーバアドレスは Sandbox ごとに異なります。実行環境に合わせて実行してください。)。 curl -d "@product_brazil_dark.json" -H "Content-Type: application/json" -X POST https://52773-1-4e734fe2.try.learning.intersystems.com/api/coffeeco/catalog/catalogproduct この時点で、生豆を出荷し在庫に入れ、一部は焙煎してパッケージ化し ICO.catalog テーブルに保存したため、オンラインショップで販売できるようになりました。 コーヒーカタログを提供する ここからは、フロントエンドのオンラインストアフロントに必要なサービスを考えていきます。Web 開発者は、以下の用途で利用できる一連のサービスを必要としています。 (1) 焙煎したばかりの新鮮なコーヒーを販売してほしい (2) 値引きして販売する必要のある鮮度の古いコーヒーバック情報を入手したい (3) コーヒーバックの販売記録を作りたい 最初の2項目は非常に簡単です。読み取り専用のサービスとなるので、GET要求を使用すれば取得できます。 両方の GET 要求を 1 つの GetProducts()メソッドで処理できるように、新鮮な在庫と古い鮮度の在庫のどちらを返すか指定できる入力引数を用意することもできます。 Handler.cls (quickstarts-full-stack > services > cls > ICO > Handler.cls)の下の方に定義されている UrlMap 定義をご参照ください。 /catalog/getproducts は、全て新しい鮮度のコーヒーを返すUrlです。 /catalog/getproducts/:fresh は、/catalog/getproducts に似ていますが、鮮度の古いコーヒーを取得できる追加のパラメータを用意しています(fresh が 0 の場合に鮮度が古い情報を返します)。 /catalog/sellproduct/:id/:quantity は、クライアントが特定の商品のバッグを販売したことを記録する処理の Url です。 XData UrlMap [ XMLNamespace = "http://www.intersystems.com/urlmap" ] { <Routes> <Route Url="/inventory/listbeans" Method="GET" Call="ListRawBeans" /> <Route Url="/inventory/getbeans/:id/:quantity" Method="POST" Call="GetRawBeans" /> <Route Url="/catalog/catalogproduct" Method="POST" Call="CatalogProduct" /> <Route Url="/catalog/getproducts" Method="GET" Call="GetProducts" /> <Route Url="/catalog/getproducts/:fresh" Method="GET" Call="GetProducts" /> <Route Url="/catalog/sellproduct/:id/:quantity" Method="POST" Call="SellProduct" /> </Routes> } GetProducts() メソッドでは、SQL を使用してクエリを実行し、返されたレコードを繰り返し処理で取得し、取得できた情報を JSON オブジェクトに設定し、 JSON 配列に追加し、最後に呼び出し元に作成した JSON データを返送しています。 以上の流れで、Web 開発者が販売中の商品を紹介する素敵なサイトを構築するために必要な情報がすべて揃いました! コーヒーを販売する 販売したコーヒーを記録するサービスの SellProduct() は、プロダクト ID と販売されるバッグの数量が引数として指定されます。 ここでは、エラーチェックや特別な支払い処理、発送などは行わず、非常にシンプルな処理を行っていて、カタログのコーヒーバッグの数量を減少させるだけの処理としています。 また、お客様が複数の商品を購入した場合、クライアントはそれぞれの商品に対して SellProduct リクエストを送信するものとします。 GetRawBeans() メソッドの処理と同様に、レコード ID がわかっている場合にレコードを照会する ObjectScript の便利なメソッド %ExistsId()、%OpenId()、%Save() を利用します。このメソッドは GetRawBeans() と非常によく似ているため、新たに追加する説明はありません。 サービスを試してみます。 実行に使用する URL の Web サーバアドレスは Sandbox 毎に異なります。 https://gettingstarted.intersystems.com/full-stack/part-two-rest-services/#catalog-services を開き、「Try out the services」の近くの表示をご確認ください。 新鮮なコーヒー豆を問い合わせるサービス curl https://52773-1-4e734fe2.try.learning.intersystems.com/api/coffeeco/catalog/getproducts セール商品を問い合わせるサービス(末尾のパラメータに 0 を指定しています) curl https://52773-1-4e734fe2.try.learning.intersystems.com/api/coffeeco/catalog/getproducts/0 商品を販売します curl -X POST https://52773-1-4e734fe2.try.learning.intersystems.com/api/coffeeco/catalog/sellproduct/1/2 この実行結果の例は以下の通りです。 {"catalog_id":1,"product_code":"BRAZILDARK","quantity":38,"price":13.99,"time_roasted":"2021-02-03T09:00:00Z","roasting_notes":"Full bodied and low acidity. Thick, creamy, nutty and semi-sweet.","img":"brazil_dark.jpg"} パート3:コーヒーストア用 Web アプリの構築 オリジナルページはこちら👉 https://gettingstarted.intersystems.com/full-stack/part-three-front-end/ Introduction このチュートリアルの最後のパート 3 では、あなたが運営するコーヒーショップのオンライン storefront を作成します。 アプリケーションでは、Vue.js という JavaScript フレームワークを使用して、シングルページウェブアプリケーション(SPA)を作成しています。 Vue.js についての説明はこのチュートリアルの範囲外ですが、Vue.js を使用してどのように Web アプリケーションが構築されるか、また、パート2 で作成した REST サービスがこのアプリでどのように使用されているかを確認することができます。 コードをカスタマイズする Web アプリケーション用コードは全て準備済です。実際に動かして動作を確認してみましょう。 まず、必要なパッケージのインストールを行います。インストールには数分かかります。また、インストール後にいくつかの編集を行います。 Sandbox の IDE のターミナルウィンドウで以下のコマンドを入力してください。 IDE へのリンク情報は、https://gettingstarted.intersystems.com/full-stack/part-three-front-end/#customize-code を開き、ログインすると以下図のように表示されます。 IDE などのアクセス情報の表示が、 のように表示されていたら、ピンク色のリンクをクリックしてください。 IDE のターミナルウィンドウで以下実行してください。インストールには数分時間がかかります。 cd /home/project/quickstarts-full-stack/frontend npm install npm install yarn インストールが完了したら、お使いの IRIS サーバのアドレスをソースコードに指定します。 (1) IDE で quickstarts-full-stack > frontend > src > views > Home.vue を開きます。 (2) localhost:52773 と記載されている部分(url の設定)を、お使いの IRIS 用アドレスに書き換えます(例:52773-1-4e734fe2.try.learning.intersystems.com)。 https://gettingstarted.intersystems.com/full-stack/part-three-front-end/#customize-code を開き、「Customize the code」の近くにアクセス情報が表示されます。ご確認ください。 (3) IDE で quickstarts-full-stack > frontend > src > views > Sale.vue を開き、(2)と同様に、IRIS の接続情報を書き換えてください。 (4) IDE で quickstarts-full-stack > frontend > src > components > ProductCard.vue を開き、orderurl の設定を (2) と同様に、IRIS の接続情報を書き換えてください。 では、このアプリのテスト用の組み込み Web サーバで実行してみましょう。 (1) Sandbox の IDE のターミナルウィンドウで以下実行します(実行には時間がかかります)。 cd /home/project/quickstarts-full-stack/frontend yarn serve (2) ブラウザに、パート3の画面で指定された URL にアクセスしてください。 URL は https://gettingstarted.intersystems.com/full-stack/part-three-front-end/#store-view を開き、「View the storefront」の近くに表示されます。 URL にアクセスすると、以下の画面が表示されます。 Vue.js の中で、どのように実行されているか少し解説します。 React や Angular などの多くのフレームワークと同様に、URLはコンポーネントに「ルーティング」されます。 IDE で、quickstarts-full-stack > frontend > src > router > index.js を参照すると、デフォルトの URL パスである「/」が Home コンポーネントにルーティングされています。 Webアプリケーションで「/」 が指定されると、quickstarts-full-stack > frontend > src > views > Home.vue が表示され、/catalog/getproducts/1 を GET 要求で実行しています。 IRIS では、quickstarts-full-stack > services > cls > ICO > Handler.cls の GetProducts() メソッドが引数に 1 を指定された状態で実行され(=新鮮な豆を取得)過去5日間に焙煎されたすべての販売商品のリストをJSONで返します。 URL のパラメータに 1 を渡すか、指定しない場合、焙煎されたばかりのバッグのみが要求されます GET https://52773-1-4e734fe2.try.learning.intersystems.com/api/coffeeco/catalog/getproducts/1 実行結果例は以下の通りです。 { "rowcount": 5, "products": [{ "catalog_id": "1", "product_code": "BRAZILDARK", "quantity": 38, "time_roasted": "2021-02-09 09:00:00", "roasting_notes": "Full bodied and low acidity. Thick, creamy, nutty and semi-sweet.", "img": "brazil_dark.jpg", "price": 13.99 }, { "catalog_id": "2", "product_code": "ETHIOPIAMEDIUM", "quantity": 40, "time_roasted": "2021-02-08 09:00:00", "roasting_notes": "Sweet floral notes, followed by the potent citrus notes, perfectly married into bergamot.", "img": "ethiopia_medium.jpg", "price": 14.99 }, { "catalog_id": "3", "product_code": "GUATEMALALIGHT", "quantity": 120, "time_roasted": "2021-02-09 17:30:00", "roasting_notes": "Full body and a rich chocolatey-cocoa flavor, and a toffee-like sweetness.", "img": "guatemala_light.jpg", "price": 11.99 }, { "catalog_id": "4", "product_code": "SUMATRADARK", "quantity": 80, "time_roasted": "2021-02-07 13:01:30", "roasting_notes": "Smooth and chocolaty with a sweet edge and minimal earthiness.", "img": "sumatra_dark.jpg", "price": 12.99 }, { "catalog_id": "5", "product_code": "SUMATRALIGHT", "quantity": 40, "time_roasted": "2021-02-07 09:00:00", "roasting_notes": "This rich and juicy Sumatra carries sustained notes of cherry and citrus.", "img": "sumatra_light.jpg", "price": 12.99 }] } REST サービスの処理詳細については、quickstarts-full-stack > services > cls > ICO > Handler.cls の GetProducts() メソッドをご参照ください。 Home.vue は、JSON オブジェクトを繰り返し処理して、JSON 内の各アイテムに対して ProductCard (quickstarts-full-stack > frontend > src > components > ProductCard.vue) を作成します。 ProductCard.vue は、単一の商品を表示し、それを注文するための UI を作成する方法を知っているだけです。 では、Web ページの「Last chance」のリンクをクリックしてみましょう。 製品の短いリストが表示されるはずです(表示されない場合は、ICO.catalog テーブルの time_roasted の値をいくつか変更して、5日以上前の値にする必要があります)。この表示は、同じバックエンドのサービスを呼び出していますが、5日以上前の焙煎珈琲を返すようにパラメータを指定しています(1 以外の任意の数字を渡すことで指定できます)。 REST の呼び出しは以下の通りです。 GET /api/coffeeco/catalog/getproducts/2 応答結果例は、以下の通りです。 { "rowcount": 1, "products": [{ "catalog_id": "10", "product_code": "SUMATRALIGHT", "quantity": 40, "time_roasted": "2021-02-02 09:00:00", "roasting_notes": "This rich and juicy Sumatra carries sustained notes of cherry and citrus.", "img": "sumatra_light.jpg", "price": 12.99 }] } Home.vue と同様に、Sale.vue でも ProductCard コンポーネントを使用して商品を表示していますが、fetchProducts() 関数の実行結果(=RESTの応答)から ProductCard にデータを渡す前に価格を 3 ドル分値引きしています。 これは、コードを単純化するためにコンポーネントを再利用する簡単な例であり、価格設定と在庫管理を分離していることを示しています。 購入処理 いよいよチュートリアルの最後の項目です!コーヒーを買ってみましょう! 商品の数量を変更し「Place Order」ボタンをクリックします。注文が完了したことを示すポップアップが表示されるはずです(チュートリアルは架空サンプルです。実際のアプリであれば、ショッピングカートに商品を入れて、顧客が支払いを済ませるまで注文は行われないでしょう)。 チュートリアルは簡単な例としているため、ProductCard が RESTサービスの SellProduct() メソッドを呼び出し、注文されたコーヒー豆をカタログから取り出す様子を示しています。 REST 呼び出しは以下の通りです。 POST /api/coffeeco/catalog/sellproduct/商品のカタログID/販売したコーヒーバッグ数 POST要求なので、ブラウザからは実行できません。 IDE のターミナルを新規に開きます(Terminal > New Terminal)。 以下コマンドを新規ターミナルで実行してください。 コマンド実行例は、https://gettingstarted.intersystems.com/full-stack/part-three-front-end/#purchaseを開き、「Making a purchase」近くの表示をご確認ください。 curl -X POST https://52773-1-4e734fe2.try.learning.intersystems.com/api/coffeeco/catalog/sellproduct/1/2 応答結果の例は、以下の通りです。 { "catalog_id": 1, "product_code": "BRAZILDARK", "quantity": 36, "price": 13.99, "time_roasted": "2021-02-09T09:00:00Z", "roasting_notes": "Full bodied and low acidity. Thick, creamy, nutty and semi-sweet.", "img": "brazil_dark.jpg" } ここでは、もう少しアプリケーションを使って遊んでいただく方法をご紹介します。 (1) 在庫が足りなくなるまで、コーヒーバッグを注文し続けます。最終的にはStorefront から商品を消滅させることができる予定です。 (2) Web 開発の経験がある方は、quickstarts-full-stack > frontend > src > components >ProductCard.vue コンポーネントの CSS を変更してみてください (ファイルの最後の <style> セクションにあります)。 (3) Vue.js の経験があれば、ProductCard.vue の processOrder() 関数で使われている基本的な JavaScript の警告メッセージを、もっと面白いものに変更してみましょう。また、独自のコンポーネントを作成して、ポップアップ・アラートの代わりにこの情報を表示することもできます。 最後に、Webアプリケーションの終了方法をご案内します。 Sandbox の IDE のターミナルウィンドウがプロンプトが戻っていない状態になっています。Web アプリケーションを終了して良い場合は、Ctrl + C を実行し、元のプロンプトに戻してください。 Next Steps InterSystems IRIS の基本的な機能をご紹介しましたが、いかがでしたでしょうか。 このチュートリアルで学習したことを振り返ります。 パート1では、テーブル作成やデータの読み込みに SQL を使用し、直接ターミナルから SQL を入力したり、Python プログラムから実行する方法を確認しました。 パート2では、高速で柔軟なデータベースプログラミング言語である ObjectScript のご紹介と、ObjectScript を使用して RESTサービスを構築する方法をご紹介します。 パート3では、人気の高い JavaScript フレームワークを使って、顧客向けのフロントエンド Web アプリを構築する方法を学習しました。 以上でフルスタックチュートリアルは終了です! 最後までお付き合いいただきありがとうございました! https://gettingstarted.intersystems.com/ には、まだまだ他のチュートリアルをご用意しています。ぜひご体験ください!
記事
Mihoko Iijima · 2020年12月28日

データベースの暗号化手順について

これは InterSystems FAQ サイトの記事です。 データベース暗号化は、ディスクヘの書き込みまたはディスクからの読み取りで暗号化と復号が実行されるため、アプリケーションのロジックに手を加える必要はありません。 この機能のドキュメントについては、以下ご参照ください。 マネージド・キー暗号化【IRIS】 マネージド・キー暗号化 暗号化データベース作成までの流れは、以下の通りです。 (1) 暗号化キーの作成 (a) 管理者 ユーザ名/パスワード (b) 暗号化キーファイル (2) 暗号化キーの有効化 (3) 暗号化されたデータベースの作成 暗号化データベース作成後の運用のための設定は以下の通りです。 〇 データベース暗号化の起動設定(暗号化キーの有効化をどのように行うか) 暗号化されたデータベースは、"暗号化キーの有効" が行われてアクセスできるようになります。既定の設定では、"暗号化キーの有効"を行いませんので、以下3種類の方法から選択します。 ① キーを有効化しない起動の構成 既定の設定のまま、インスタンス起動時に "暗号化キーの有効" が行われません。暗号化されたデータベースをマウントする前に管理ポータルなどから "暗号化キーの有効" を行う必要があります。 以下の場合、この運用は適応できません。 起動時に暗号化データベースのマウントが必要な場合 暗号化されたジャーナル・ファイルを利用している場合 監査ログが暗号化されている場合 ② インタラクティブ(対話式)にキーを有効化する起動の構成 インスタンス起動時に、インタラクティブに "暗号化キーの有効" を行います。 ③ 無人でキーを有効化する起動の構成 インスタンス起動時に、自動的に "暗号化キーの有効" を行います。 セキュリティの強度については、①および②の運用の方が、③よりも強度が高くなります。詳細は、下記ドキュメントページをご確認ください。 データベース暗号化の起動設定の構成【IRIS】データベース暗号化の起動設定の構成 ○暗号化キーファイル および 管理者とパスワードの管理方法(管理をどうするか) 暗号化キーの有効化するには以下の 2 つの情報が必要です。  (a) 管理者 ユーザ名/パスワード (b) 暗号化キーファイル これらに関して、ファイルの損失、管理者のユーザ名/パスワードの失念や漏えい等から防ぐ方法については、以下のドキュメントページをご確認ください。  暗号化データのアクセスにおける偶発的な損失からの保護【IRIS】 暗号化データのアクセスにおける偶発的な損失からの保護 ○ 緊急事態(緊急事態の対処方法) 緊急事態として以下の場合の対処については下記ページをご確認ください。  緊急事態【IRIS】  有効なキーが保存されているファイルが損傷したり紛失した場合【IRIS】  起動時に必要なデータベース暗号化キー・ファイルが存在しない場合【IRIS】  緊急事態  有効なキーが保存されているファイルが損傷したり紛失した場合  起動時に必要なデータベース暗号化キー・ファイルが存在しない場合
記事
Mihoko Iijima · 2021年4月22日

ソースプログラムを隠蔽化する方法

これは InterSystems FAQ サイトの記事です。 ルーチン(*.mac)の場合 ソースプログラムのコンパイル後に生成される *.obj のみをエクスポート/インポートすることでソースの隠蔽化を実現できます。 コマンド実行例は、EX1Sample.mac と EX2Sample.mac のコンパイルで生成される EX1Sample.obj と EX2Sample.obj をエクスポート対象に指定し、第2引数のファイルにエクスポートしています。 別ネームスペースに移動したあと、エクスポートした XML ファイルを利用してインポートを実行しています。 USER>do $system.OBJ.Export("EX1Sample.obj,EX2Sample.obj","/opt/app/routine.xml") XMLエクスポートの開始 04/22/2021 18:18:32 オブジェクトコードをエクスポート中: EX1Sample.obj オブジェクトコードをエクスポート中: EX2Sample.obj エクスポートが正常に完了しました。 USER>zn "test" // ネームスペース移動 TEST>do $system.OBJ.Load("/opt/app/routine.xml") ロード開始 04/22/2021 18:18:51 ファイル /opt/app/routine.xml を xml としてロード中 インポートしたオブジェクトコード: EX1Sample インポートしたオブジェクトコード: EX2Sample ロードが正常に完了しました。 TEST> クラス(*.cls)の場合 クラスの場合は、XMLで *.cls をエクスポート/インポートしたあとに、サーバで MakeClassDeployed() を実行します。 ただし、比較的新しいバージョンでは MakeClassDeployed() 実行後、ソースファイル(*.cls)は配置モードに設定されるのみで(編集はできなくなります)参照のみ行える仕様になっています。 参照も不可にしたい場合は、MakeClassDeployed() 実行後、クラスの Hidden プロパティを設定します(プロパティの属性 Hidden=True に設定)。 コマンド実行例は以下の通りです。 USER>do $system.OBJ.Export("GPS.REST.cls,GPS.DriveData.cls","/opt/app/test.xml") XMLエクスポートの開始 04/22/2021 18:05:13 クラスをエクスポート中: GPS.DriveData クラスをエクスポート中: GPS.REST エクスポートが正常に完了しました。 USER>zn "test" // testネームスペースに移動 TEST>do $system.OBJ.Load("/opt/app/test.xml","ck") ロード開始 04/22/2021 18:07:21 ファイル /opt/app/test.xml を xml としてロード中 インポートしたクラス: GPS.DriveData インポートしたクラス: GPS.REST , 2 クラスをコンパイル中, 個のワーカー・ジョブを使用 クラスのコンパイル中 GPS.DriveData クラスのコンパイル中 GPS.REST テーブルのコンパイル中 GPS.DriveData ルーチンのコンパイル中 GPS.REST.1 ルーチンのコンパイル中 GPS.DriveData.1 ロードが正常に完了しました。 TEST>do $system.OBJ.MakeClassDeployed("GPS.DriveData") TEST> CSP(*.csp)の場合 CSPファイルについては、*.cspをコピーし、配置先の CSP フォルダに貼付ます。 サーバでコンパイル後、CSPの設定で自動コンパイル OFF にしたあと、*.csp の削除と MakeClassDeployed() の実行を行います 実行例は以下の通りです。 1)CSPファイルのコピー後、サーバの CSP ディレクトリに貼付&コンパイル TEST>do $SYSTEM.CSP.LoadPageDir("/csp/test") 2)ウェブアプリケーションパスの設定で「自動コンパイル」をいいえに設定 【バージョン2013.1以降】 [管理ポータル] > [システム管理] > [セキュリティ] > [アプリケーション] > [ウェブ・アプリケーション] > 該当するアプリケーション名のリンク 【バージョン201.1~バージョン2012.2】 [管理ポータル] > [システム管理] > [セキュリティ] > [アプリケーション] > [ウェブ・アプリケーション] > 該当するアプリケーション名の[編集] 【バージョン2010.2以前】 [システム管理ポータル] > [システム] > [セキュリティ管理] > [CSPアプリケーション] > 該当するアプリケーション名の[編集] 3)MakeClassDeployed() の実行  ※cspsample.csp をコピーした場合の例 TEST>do $system.OBJ.MakeClassDeployed("csp.cspsample")
記事
Megumi Kakechi · 2022年10月6日

メッセージログ(messages.log) のログ深刻度が 2 以上でメールを送るようにする方法

Caché/Ensemble 時代からご使用のお客様にはなじみの機能だと思いますが、IRISには「システムがインスタンスのメッセージログ/messages.log(Cachéの場合は コンソールログ/cconsole.log) を監視し、ログ・レベル2(重大なエラー) 以上 のアラートを受け取るとメールを送信する」ログ・モニター機能があります。この機能を使用すると、アラートログ (alerts.log)へのログ書き込み管理のほかに、メールを送信することもできます。 メール送信の設定は、^MONMGR ユーティリティを使用して簡単に行えます。 以下に、サンプルをご案内します。 USER>zn "%SYS" %SYS>do ^MONMGR 1) Start/Stop/Update Log Monitor 2) Manage Log Monitor Options 3) Exit Option? 2 <-- ログモニター管理の設定を行います 1) Set Monitor Interval 2) Set Alert Level 3) Manage Email Options 4) Exit Option? 3 <-- Emailオプションの設定を行います 1) Enable/Disable Email 2) Set Sender 3) Set Server 4) Manage Recipients 5) Set Authentication 6) Test Email 7) Exit Option? 1 <-- Email設定をにONにします ​​​​​ Email is currently OFF <-- 現在の設定は OFF Change Email setting? No => yes <-- y または yes を入力します 1) Enable/Disable Email 2) Set Sender 3) Set Server 4) Manage Recipients 5) Set Authentication 6) Test Email 7) Exit Option? 2 <-- 送信元の設定をします Sender? aaa@bbb.com <-- 送信元のメールアドレスを設定します 1) Enable/Disable Email 2) Set Sender 3) Set Server 4) Manage Recipients 5) Set Authentication 6) Test Email 7) Exit Option? 3 <-- メールサーバの設定を行います Mail server? mail.bbb.com <-- メールサーバ Mail server port? 25 => <-- メールサーバポート Mail server SSLConfiguration? <-- (必要に応じて設定) Mail server UseSTARTTLS? 0 => <-- (必要に応じて設定) 1) Enable/Disable Email 2) Set Sender 3) Set Server 4) Manage Recipients 5) Set Authentication 6) Test Email 7) Exit Option? 4 <-- 送信先(受信者)情報の設定 1) List Recipients 2) Add Recipient 3) Remove Recipient 4) Exit Option? 2 <-- 送信先メールアドレスの設定 Email Address? test@abcde.com <-- 送信先メールアドレス 1) List Recipients 2) Add Recipient 3) Remove Recipient 4) Exit Option? <-- <Enter> 1) Enable/Disable Email 2) Set Sender 3) Set Server 4) Manage Recipients 5) Set Authentication 6) Test Email 7) Exit Option? 6 <-- 送信テスト Sending email on Mail Server mail.bbb.com From: aaa@bbb.com To: test@abcde.com 1) Enable/Disable Email 2) Set Sender 3) Set Server 4) Manage Recipients 5) Set Authentication 6) Test Email 7) Exit Option? <-- <Enter> 1) Set Monitor Interval 2) Set Alert Level 3) Manage Email Options 4) Exit Option? <-- <Enter> Update running Monitor? Yes => Yes <-- Y でモニター情報を更新 Updating Log Monitor... Log Monitor updated 1) Start/Stop/Update Log Monitor 2) Manage Log Monitor Options 3) Exit Option? %SYS> 上記テスト送信で、「%Monitor Email Test」の件名でメールが送られます。これで設定は完了です。 設定が完了したら、メッセージログにログレベルが 2(重大なエラー) 以上のログを書き込み、メールが送られるか確認してみましょう。 次のコマンドで、メッセージログ/コンソールログに任意のメッセージを書き込むことができます。 // 第3引数がログレベル。0 (情報), 1 (警告), 2 (重大なエラー), 3 (致命的なエラー) write ##class(%SYS.System).WriteToConsoleLog("Test log message",0,2) 上記コマンドを実行すると、メッセージログに以下のようなログが書き込まれ、 10/05/22-15:18:23:537 (3688) 2 [Utility.Event] Test log message [InterSystems IRIS SEVERE ERROR xxxxx:IRIS] [Utility.Event] Test log message のような件名でメールが送られます。 詳細は、以下のドキュメントをご覧ください。ログ・モニタの使用 IRISには便利なユーティリティが数多くあります。以下の記事で紹介しておりますので、ぜひお試しください。【ご参考】IRISで使用できるユーティリティ一覧
記事
Mihoko Iijima · 2023年4月10日

テーブル定義のデータが格納されるグローバル変数名について

これは InterSystems FAQ サイトの記事です。 バージョン2017.2以降から、CREATE TABLE文で作成したテーブル定義のデータを格納するグローバル変数の命名ルールが変わり ^EPgS.D8T6.1 のようなハッシュ化したグローバル変数名が設定されます。(この変更はパフォーマンス向上のために追加されました。) ※ バージョン2017.1以前については、永続クラス定義のルールと同一です。詳細は関連記事「永続クラス定義のデータが格納されるグローバル変数名について」をご参照ください。 以下のテーブル定義を作成すると、同名の永続クラス定義が作成されます。 CREATE TABLE Test.Product( ProductID VARCHAR(10) PRIMARY KEY, ProductName VARCHAR(50), Price INTEGER ) 永続クラス:Test.Productの定義は以下の通りです。(パラメータ:USEEXTENTSETに1が設定されます) Class Test.Product Extends %Persistent [ ClassType = persistent, DdlAllowed, Final, Owner = {SuperUser}, ProcedureBlock, SqlRowIdPrivate, SqlTableName = Product ]{Property ProductID As %Library.String(MAXLEN = 10) [ SqlColumnNumber = 2 ];Property ProductName As %Library.String(MAXLEN = 50) [ SqlColumnNumber = 3 ];Property Price As %Library.Integer(MAXVAL = 2147483647, MINVAL = -2147483648) [ SqlColumnNumber = 4 ];Parameter USEEXTENTSET = 1;/// Bitmap Extent Index auto-generated by DDL CREATE TABLE statement. Do not edit the SqlName of this index.Index DDLBEIndex [ Extent, SqlName = "%%DDLBEIndex", Type = bitmap ];/// DDL Primary Key SpecificationIndex PRODUCTPKEY1 On ProductID [ PrimaryKey, SqlName = PRODUCT_PKEY1, Type = index, Unique ];Storage Default{<Data name="ProductDefaultData"><Value name="1"><Value>ProductID</Value></Value><Value name="2"><Value>ProductName</Value></Value><Value name="3"><Value>Price</Value></Value></Data><DataLocation>^CCar.Wt3i.1</DataLocation><DefaultData>ProductDefaultData</DefaultData><ExtentLocation>^CCar.Wt3i</ExtentLocation><ExtentSize>0</ExtentSize><IdFunction>sequence</IdFunction><IdLocation>^CCar.Wt3i.1</IdLocation><Index name="DDLBEIndex"><Location>^CCar.Wt3i.2</Location></Index><Index name="IDKEY"><Location>^CCar.Wt3i.1</Location></Index><Index name="PRODUCTPKEY1"><Location>^CCar.Wt3i.3</Location></Index><IndexLocation>^CCar.Wt3i.I</IndexLocation><StreamLocation>^CCar.Wt3i.S</StreamLocation><Type>%Storage.Persistent</Type>}} ExtentLocation:このクラスのグローバル名の生成に使用されるハッシュ値 DataLocation:レコードデータが登録されるグローバル変数名です。 Location:各インデックス固有のグローバル変数名が指定されます。 IndexLocation:この定義では、多くの場合使用されません。 StreamLocation:ストリームプロパティのデータが格納される変数です。 ストレージ定義に表示される情報について詳細は、ドキュメント「ハッシュ化したグローバル名」をご参照ください。 2017.1以前と同様の命名ルール(^スキーマ名.テーブル名D、I、S のグローバル変数名)を使用する場合は、CREATE TABLE文実行時に以下のクラスパラメータを指定します。 WITH %CLASSPARAMETER USEEXTENTSET = 0 CREATE TABLE Test2.Product( ProductID VARCHAR(10) PRIMARY KEY, ProductName VARCHAR(50), Price INTEGER ) WITH %CLASSPARAMETER USEEXTENTSET = 0 永続クラス定義:Test2.Productのパラメータ:USEEXTENTSETは以下のように定義されます。 Parameter USEEXTENTSET = 0; 永続クラス定義:Test2.Productのストレージ定義は以下の通りです。 Storage Default{<Data name="ProductDefaultData"><Value name="1"><Value>ProductID</Value></Value><Value name="2"><Value>ProductName</Value></Value><Value name="3"><Value>Price</Value></Value></Data><DataLocation>^Test2.ProductD</DataLocation><DefaultData>ProductDefaultData</DefaultData><IdFunction>sequence</IdFunction><IdLocation>^Test2.ProductD</IdLocation><IndexLocation>^Test2.ProductI</IndexLocation><StreamLocation>^Test2.ProductS</StreamLocation><Type>%Storage.Persistent</Type>} WITHで指定したテーブルのオプションについては詳しくは、ドキュメント「テーブルのオプション」をご参照ください。 関連記事:永続クラス定義のデータが格納されるグローバル変数名について
記事
Megumi Kakechi · 2023年4月11日

TIMESTAMP型の項目に対して、TO_CHAR() や TO_DATE() を用いた SELECT を実行するとエラーになります

これは InterSystems FAQ サイトの記事です。 Question: TIMESTAMP型の項目に対して、TO_CHAR() や TO_DATE() を用いた SELECT を実行すると以下のエラーになります。 実行SQL: select TO_CHAR(xxxDateTime,'YYYY-MM-DD') from Test エラー: [SQLCODE: <-400>:<深刻なエラーが発生しました>] [%msg: <Unexpected error occurred: <ZCHAR>IllegalValuePassedToTOCHAR^%qarfunc>] エラーの原因を教えてください。 Answer: こちらは、IRIS2022.1以降のバージョンで CREATE TABLE (DDL) の TIMESTAMP 型が IRIS側クラスで %Library.PosixTime にマッピングするように変更されているためです。(アップグレードした環境の場合は、従来のままの %Library.TimeStamp にマッピングされています) %TimeStamp は、データを人が読める文字列(yyyy-mm-dd hh:mm:ss.ffff)として保存します。対して、%PosixTime は64bitの整数で保持するため、ディスクやメモリ上のデータサイズを削減し、比較演算処理等のパフォーマンスが向上します。SQLクエリを高速化したい場合は、%PosixTime を使用されることをお勧めします。 TIMESTAMP型を、従来と同じ %TimeStamp にマッピングするDDLデータ型としては TIMESTAMP2 が用意されています。CREATE TABLE文で、TIMESTAMP型にしたいフィールドの箇所を TIMESTAMP2 とすることで %TimeStamp とすることができます。 例: create table Test2 (xxxDateTime TIMESTAMP, xxxDateTime2 TIMESTAMP2) ※ xxxDateTime は %PosixTime 、xxxDateTime2 は %TimeStamp となる。全体の設定変更で対応する場合は、 管理ポータル:   [システム管理] > [構成] > [SQLとオブジェクトの設定] > [システムDDLマッピング]より TIMESTAMP 行の 「編集」をクリックしてデータタイプを %Library.PosixTime から %Library.TimeStamp に変更します。  ↓ この設定変更後に実行される CREATE TABLEより、この変更が反映されるようになります。管理ポータルで現在のテーブルのフィールドがどちらのデータタイプになっているかを確認できます。 管理ポータル: [システムエクスプローラ] > [SQL] Tips: %PosixTime のフィールドから YYYY-MM-DD の日付部分のみを取得したい場合は、次のように一旦TIMESTAMPにCAST()してから TO_CHAR() を使用します。 select xxxDateTime, to_char(cast(xxxDateTime as timestamp),'YYYY-MM-DD') from Test %PosixTime のデータの Insert は %TimeStamp と同じように行えます。 USER>do $SYSTEM.SQL.Shell() SQL Command Line Shell ---------------------------------------------------- The command prefix is currently set to: <<nothing>>. Enter <command>, 'q' to quit, '?' for help. [SQL]USER>>set selectmode=odbc // ODBCモードでテスト selectmode = odbc [SQL]USER>>insert into Test2 (xxxDateTime, xxxDateTime2) values ('2023-02-20 13:45:00','2023-02-20 13:45:00') 6. insert into Test2 (xxxDateTime, xxxDateTime2) values ('2023-02-20 13:45:00','2023-02-20 13:45:00') 1 Row Affected statement prepare time(s)/globals/cmds/disk: 0.0010s/32/4,002/0ms execute time(s)/globals/cmds/disk: 0.0002s/3/183/0ms cached query class: %sqlcq.XXX.cls12 --------------------------------------------------------------------------- [SQL]USER>>select * from Test2 // そのままSelect(※ xxxDateTime :%PosixTime , xxxDateTime2 :%TimeStamp ) 7. select * from Test2 xxxDateTime xxxDateTime2 2023-02-20 13:45:00 2023-02-20 13:45:00 1 Rows(s) Affected statement prepare time(s)/globals/cmds/disk: 0.0004s/26/978/0ms execute time(s)/globals/cmds/disk: 0.0001s/2/528/0ms cached query class: %sqlcq.XXX.cls13 --------------------------------------------------------------------------- [SQL]USER>>q USER>zw ^poCN.Dqym.1 // %PosixTime型で実際に格納されているデータは64bit整数 ^poCN.Dqym.1=1 ^poCN.Dqym.1(1)=$lb(1154598405306846976,"2023-02-20 13:45:00") 現在日時を%PosixTime型で出力したり、%TimeStamp ⇔ %PosixTime 変換したい場合は以下のように行えます。 USER>write ##Class(%Library.PosixTime).CurrentTimeStamp() 1154596073773251031 USER>write ##Class(%Library.PosixTime).LogicalToTimeStamp(ptime) 2023-01-24 14:06:06.404055 USER>write ##Class(%Library.PosixTime).TimeStampToLogical("2023-01-24 14:06:06.404055") 1154596073773251031 詳細は以下のドキュメントをご覧ください日付、時刻、PosixTime、およびタイムスタンプのデータ型【ご参考】%TimeStamp型プロパティを使用した範囲指定のクエリが遅い場合の対処方法SQLベースのベンチマークを行う際に、実施していただきたい5つの項目
記事
Tomoko Furuzono · 2023年7月2日

プログラムからグローバルのインポート/エクスポート処理を呼び出す方法

これは、InterSystems FAQサイトの記事です。 以下の様な方法で、グローバルのインポート/エクスポート処理をプログラムに組み込むことができます。 1. グローバルエクスポート方法1.1 XML形式でのエクスポートグローバルをXML形式のファイルにエクスポートする場合、$system.OBJ.Export() を使用します。 1.1.1. 指定したグローバルをエクスポートする場合エクスポート対象グローバルを グローバル名.gbl で指定します(先頭の ^ は不要)。例: Do $system.OBJ.Export("a.gbl,b.gbl","c:\temp\globals.xml",,.errors) 結果については、errors に格納されます。 $system.OBJ.Export() の詳細は%SYSTEM.OBJクラスのクラスリファレンスをご確認ください。クラスリファレンス:%SYSTEM.OBJ1.1.2. ネームスペース内の全グローバルをエクスポートする場合%SYS.GlobalQueryクラスでグローバル一覧を取得し、それを $system.OBJ.Export() に渡してエクスポートを実行します。 例: Set rs=##class(%ResultSet).%New("%SYS.GlobalQuery:NameSpaceList") Do rs.Execute() Kill globals While rs.Next() { Set globals(rs.Get("Name")_".gbl")="" } Do $system.OBJ.Export(.globals,"c:\temp\allglobal.xml",,.errors) (ここでは簡略化のためエラーチェックは省略しています) 1.2. ブロック形式でのエクスポート(%GOFユーティリティと同等)ブロック形式でグローバルをエクスポートするには、クラス %Library.Global のメソッドExport() を使用します。エクスポート対象グローバルは、1.1.1と同じ形式で指定します。各引数の詳細はクラスリファレンスをご確認ください。クラスリファレンス:%Library.Global 例: USER>Set status=##class(%Library.Global).Export(,"a.gbl,b.gbl","c:\test.gof",7) GO/GOF形式でエクスポートの開始 07/23/2008 17:01:03 グローバルをエクスポート中: ^a グローバルをエクスポート中: ^b エクスポートが正常に完了しました。 USER> 2. グローバルインポート方法2.1 XML形式ファイルのインポート2.1.1 ファイルに含まれる全グローバルをインポートするXMLファイルに含まれる全グローバルをインポートするには $system.OBJ.Load() を使用します。 例: Do $system.OBJ.Load("c:\temp\globals.xml",,.errors) 2.1.2 ファイルに含まれるグローバルのうち一部のみをインポートするXMLファイルに含まれる一部のグローバルのみ選択してインポートする場合、一旦$system.OBJ.Load() で 第5引数の listonly を 1 に設定してXMLファイルを読み込み、第4引数(出力引数)で得られたリストからインポート対象を選択して第6引数で指定します。例: Set file="c:\temp\globals.xml" // まずXMLに含まれるアイテム一覧を取得 Do $system.OBJ.Load(file,,.errors,.list,1 /* listonly */) Set item=$Order(list("")) Kill loaditem While item'="" { If item["Sample" { // Sample を含むもののみインポート Set loaditem(item)="" Set item=$Order(list(item)) } } // 作成されたリストでインポート処理実行 Do $system.OBJ.Load(file,,.errors,,,.loaditem) 2.2 ブロック形式でのインポート(%GIFユーティリティと同等)ブロック形式でエクスポートされたグローバルをインポートするには、$system.OBJ.Load() またはクラス %Library.Global の Import() メソッドを使用します。 ファイル中の特定のグローバルのみインポートする場合は、2.1.2と同じ方法が使用できます。例1: USER>Do $system.OBJ.Load("c:\test.gof") ロード開始 07/23/2008 17:01:49 ファイル c:\test.gof を gbl としてロード中 インポートしたグローバル: ^a インポートしたグローバル: ^b ロードが正常に完了しました。 USER> 例2: USER>Set status=##class(%Library.Global).Import(,"*","c:\test.gof",7)
記事
Toshihiko Minamoto · 2021年9月1日

Amazon EKSとIRIS。 高可用性とバックアップ

記事で使用されているすべてのソースコード: https://github.com/antonum/ha-iris-k8s  [前の記事](https://jp.community.intersystems.com/node/490971)では、従来型のミラーリングではなく分散ストレージに基づいて、高可用性のあるk8sでIRISをセットアップする方法について説明しました。 その記事では例としてAzure AKSクラスタを使用しました。 この記事では引き続き、k8sで可用性の高い構成を詳しく見ていきますが、 今回は、Amazon EKS(AWSが管理するKubernetesサービス)に基づき、Kubernetes Snapshotに基づいてデータベースのバックアップと復元を行うためのオプションが含まれます。 ## インストール 早速作業に取り掛かりましょう。 まず、AWSアカウントが必要です。[AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-install.html)、[kubectl](https://kubernetes.io/docs/tasks/tools/)、および[eksctl](https://docs.aws.amazon.com/eks/latest/userguide/eksctl.html)ツールがインストールされている必要があります。 新しいクラスタを作成するために、次のコマンドを実行します。 eksctl create cluster \ --name my-cluster \ --node-type m5.2xlarge \ --nodes 3 \ --node-volume-size 500 \ --region us-east-1 このコマンドは約15分掛けてEKSクラスタをデプロイし、それをkubectlツールのデフォルトのフォルダに設定します。 デプロイを確認するには、次のコードを実行します。 kubectl get nodes NAME                                             STATUS   ROLES    AGE   VERSION ip-192-168-19-7.ca-central-1.compute.internal    Ready    <none>   18d   v1.18.9-eks-d1db3c ip-192-168-37-96.ca-central-1.compute.internal   Ready    <none>   18d   v1.18.9-eks-d1db3c ip-192-168-76-18.ca-central-1.compute.internal   Ready    <none>   18d   v1.18.9-eks-d1db3c 次に、Longhorn分散ストレージエンジンをインストールします。 kubectl create namespace longhorn-system kubectl apply -f https://raw.githubusercontent.com/longhorn/longhorn/v1.1.0/deploy/iscsi/longhorn-iscsi-installation.yaml --namespace longhorn-system kubectl apply -f https://raw.githubusercontent.com/longhorn/longhorn/master/deploy/longhorn.yaml --namespace longhorn-system そして最後に、IRIS自体をインストールします。 kubectl apply -f https://github.com/antonum/ha-iris-k8s/raw/main/tldr.yaml この時点で、Longhorn分散ストレージとIRISデプロイがインストールされたEKSクラスタが完全に機能できる状態になります。  前の記事に戻って、クラスタとIRISデプロイにあらゆるダメージを与えて、システムがどのように修復するのかを確認するとよいでしょう。 「[障害をシミュレートする](https://community.intersystems.com/post/highly-available-iris-deployment-kubernetes-without-mirroring)」セクションをご覧ください。 ## 特典1 ARM上のIRIS IRIS EKSとLonghornはARMアーキテクチャをサポートしているため、ARMアーキテクチャに基づき、AWS Gravition 2インスタンスを使用して同じ構成をデプロイできます。 EKSノードのインスタンスタイプを 'm6g' ファミリーに変更し、IRISイメージをARMベースに変更するだけです。 eksctl create cluster \ --name my-cluster-arm \ --node-type **m6g.2xlarge** \ --nodes 3 \ --node-volume-size 500 \ --region us-east-1 tldr.yaml containers: #- image: store/intersystems/iris-community:2020.4.0.524.0 - image: store/intersystems/irishealth-community-arm64:2020.4.0.524.0 name: iris または、単に以下を使用します。 kubectl apply -f https://github.com/antonum/ha-iris-k8s/raw/main/tldr-iris4h-arm.yaml 以上です! ARMプラットフォームで実行するIRIS Kubernetesクラスタが出来上がりました。 ## 特典2 - バックアップと復元 本番環境グレードのアーキテクチャでよく見過ごされがちな部分に、データベースのバックアップを作成して必要なときに素早く復元するか複製する機能があります。 Kubernetesでは、一般的にPersistent Volume Snapshots(永続ボリュームスナップショット)を使用してこれを行います。 まず、必要なすべてのk8sコンポーネントをインストールする必要があります。 #Install CSI Snapshotter and CRDs kubectl apply -n kube-system -f https://raw.githubusercontent.com/kubernetes-csi/external-snapshotter/master/client/config/crd/snapshot.storage.k8s.io_volumesnapshotcontents.yaml kubectl apply -n kube-system -f https://github.com/kubernetes-csi/external-snapshotter/raw/master/client/config/crd/snapshot.storage.k8s.io_volumesnapshotclasses.yaml kubectl apply -n kube-system -f https://raw.githubusercontent.com/kubernetes-csi/external-snapshotter/master/client/config/crd/snapshot.storage.k8s.io_volumesnapshots.yaml kubectl apply -n kube-system -f https://raw.githubusercontent.com/kubernetes-csi/external-snapshotter/master/deploy/kubernetes/snapshot-controller/setup-snapshot-controller.yaml kubectl apply -n kube-system -f https://raw.githubusercontent.com/kubernetes-csi/external-snapshotter/master/deploy/kubernetes/snapshot-controller/rbac-snapshot-controller.yaml 次に、LonghornのS3バケット資格情報を構成します([詳細な手順](https://longhorn.io/docs/1.1.0/snapshots-and-backups/backup-and-restore/set-backup-target/)を参照)。 #Longhorn backup target s3 bucket and credentials longhorn would use to access that bucket #See https://longhorn.io/docs/1.1.0/snapshots-and-backups/backup-and-restore/set-backup-target/ for manual setup instructions longhorn_s3_bucket=longhorn-backup-123xx #bucket name should be globally unique, unless you want to reuse existing backups and credentials longhorn_s3_region=us-east-1 longhorn_aws_key=AKIAVHCUNTEXAMPLE longhorn_aws_secret=g2q2+5DVXk5p3AHIB5m/Tk6U6dXrEXAMPLE 以下のコマンドは、前の手順から環境変数を拾い、それを使ってLonghorn Backupを構成します。 #configure Longhorn backup target and credentials cat <<EOF | kubectl apply -f - apiVersion: longhorn.io/v1beta1 kind: Setting metadata: name: backup-target namespace: longhorn-system value: "s3://$longhorn_s3_bucket@$longhorn_s3_region/" # backup target here --- apiVersion: v1 kind: Secret metadata: name: "aws-secret" namespace: "longhorn-system" labels: data: # echo -n '<secret>' | base64 AWS_ACCESS_KEY_ID: $(echo -n $longhorn_aws_key | base64) AWS_SECRET_ACCESS_KEY: $(echo -n $longhorn_aws_secret | base64) --- apiVersion: longhorn.io/v1beta1 kind: Setting metadata: name: backup-target-credential-secret namespace: longhorn-system value: "aws-secret" # backup secret name here EOF たくさんの作業に見えるかもしれませんが、基本的にLonghornに対し、指定された資格情報で特定のS3バケットを使用し、バックアップのコンテンツを保存するように指示しています。 以上です! Longhorn UIに移動すると、バックアップを作成して復元などを行えるようになっています。 ![](/sites/default/files/inline/images/images/screen_shot_2021-05-31_at_1_45_37_pm.png) Longhorn UIに接続する方法を簡単におさらいしましょう。 kubectl get pods -n longhorn-system # note the full pod name for 'longhorn-ui-...' pod kubectl port-forward longhorn-ui-df95bdf85-469sz 9000:8000 -n longhorn-system これによって、Longhorn UIへのトラフィックはhttp://localhost:9000に転送されるようになります。  ## プログラムによるバックアップ/復元 Longhorn UIを介して行うバックアップと復元は、最初のステップとしては十分かもしれませんが、もう一歩先に進み、k8s Snapshot APIを使用して、プログラムでバックアップと復元を実行してみましょう。 まず、スナップショットそのものが必要です。 iris-volume-snapshot.yaml apiVersion: snapshot.storage.k8s.io/v1beta1 kind: VolumeSnapshot metadata: name: iris-longhorn-snapshot spec: volumeSnapshotClassName: longhorn source: persistentVolumeClaimName: iris-pvc このボリュームスナップショットは、IRISデプロイに使用するソースボリュームである 'iris-pvc' を参照しています。 そのため、これを適用するだけですぐにバックアッププロセスが開始します。 IRIS書き込みデーモンの凍結と解凍をスナップショットの前後に実行することをお勧めします。 #Freeze Write Daemon echo "Freezing IRIS Write Daemon" kubectl exec -it -n $namespace $pod_name -- iris session iris -U%SYS "##Class(Backup.General).ExternalFreeze()" status=$? if [[ $status -eq 5 ]]; then echo "IRIS WD IS FROZEN, Performing backup" kubectl apply -f backup/iris-volume-snapshot.yaml -n $namespace elif [[ $status -eq 3 ]]; then echo "IRIS WD FREEZE FAILED" fi #Thaw Write Daemon kubectl exec -it -n $namespace $pod_name -- iris session iris -U%SYS "##Class(Backup.General).ExternalThaw()" 復元プロセスは非常に簡単です。 基本的には、新しいPVCを作成して、スナップショットをソースとして指定しています。 apiVersion: v1 kind: PersistentVolumeClaim metadata: name: iris-pvc-restored spec: storageClassName: longhorn dataSource: name: iris-longhorn-snapshot kind: VolumeSnapshot apiGroup: snapshot.storage.k8s.io accessModes: - ReadWriteOnce resources: requests: storage: 10Gi 次に、このPVCに基づいて新しいデプロイを作成するだけです。 これを順に行うこちらの[GitHubリポジトリにあるテストスクリプト](https://github.com/antonum/ha-iris-k8s/blob/main/backup/test.sh)をご覧ください。 * まったく新しいIRISデプロイを作成します。 * IRISにデータを追加します。 * 書き込みデーモンを凍結し、スナップショットを取得して、書き込みデーモンを解凍します。 * そのスナップショットをベースに、IRISデプロイのクローンを作成します。 * すべてのデータが含まれていることを確認します。 この時点で、同一のIRISデプロイが2つ存在することになります。1つはもう片方のデプロイのclone-via-backupです。 どうぞお楽しみください!  
記事
Toshihiko Minamoto · 2022年11月8日

Django 入門 パート 2

パート 1 では、Django で新しいプロジェクトを開始する方法を紹介し、新しいモデルの定義方法と既存のモデルの追加方法を説明しました。 今回は、初期状態で利用可能な管理者パネルとどのように役立つかについて説明します。 _重要な注意事項: この記事のアクションを繰り返しても、動作しません。 記事の途中で、django-iris プロジェクトにいくつか修正を行い、InterSystems が作成した DB-API ドライバーの課題もいくつか修正しました。このドライバーは現在の開発中であり、将来的に、より安定したドライバーが提供されると思います。 この記事では、すべてを実行した場合にどのようになるかを説明しているにすぎません。_ コードに戻り、すべてのウェブリクエストのメインのエントリポイントである urls.py にある内容を確認しましょう。 """main URL Configuration The `urlpatterns` list routes URLs to views. For more information please see: https://docs.djangoproject.com/en/4.0/topics/http/urls/ Examples: Function views 1. Add an import: from my_app import views 2. Add a URL to urlpatterns: path('', views.home, name='home') Class-based views 1. Add an import: from other_app.views import Home 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') Including another URLconf 1. Import the include() function: from django.urls import include, path 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) """ from django.contrib import admin from django.urls import path urlpatterns = [ path('admin/', admin.site.urls), ] すでに URL **_admin_** がそこに定義されているのが分かります。 開発サーバーをコマンドで起動しましょう。 python manage.py runserver URL _http://localhost:8000/admin_ に移動すると、Django 管理のログインフォームが表示されます。 ![](/sites/default/files/inline/images/images/image(4235).png) ここには何らかのユーザーが必要であるため、次のコマンドでそのユーザーを作成します。 $ python manage.py createsuperuser Username (leave blank to use 'daimor'): admin Email address: admin@example.com Password: Password (again): The password is too similar to the username. This password is too short. It must contain at least 8 characters. This password is too common. Bypass password validation and create user anyway? [y/N]: y Superuser created successfully. このログインとパスワードをここで使用します。 現時点ではあまり何もありませんが、グループとユーザーにはアクセスできるようになっています。 その他のデータ 前回、zpm で post-and-tags パッケージをインストールしました。ここでも同じことを行えます。 zpm "install posts-and-tags" 次に、このパッケージでインストールされたすべてのテーブル(_community.post、community.comment、community.tag_)のモデルを取得します。 $ python manage.py inspectdb community.post community.comment community.tag > main/models.py これにより多少長いファイルが生成されるので、スポイラーとして記載します。 main/models.py # This is an auto-generated Django model module. # You'll have to do the following manually to clean this up: # * Rearrange models' order # * Make sure each model has one field with primary_key=True # * Make sure each ForeignKey and OneToOneField has `on_delete` set to the desired behavior # * Remove `managed = False` lines if you wish to allow Django to create, modify, and delete the table # Feel free to rename the models, but don't rename db_table values or field names. from django.db import models class CommunityPost(models.Model): acceptedanswerts = models.DateTimeField(db_column='AcceptedAnswerTS', blank=True, null=True) # Field name made lowercase. author = models.CharField(db_column='Author', max_length=50, blank=True, null=True) # Field name made lowercase. avgvote = models.IntegerField(db_column='AvgVote', blank=True, null=True) # Field name made lowercase. commentsamount = models.IntegerField(db_column='CommentsAmount', blank=True, null=True) # Field name made lowercase. created = models.DateTimeField(db_column='Created', blank=True, null=True) # Field name made lowercase. deleted = models.BooleanField(db_column='Deleted', blank=True, null=True) # Field name made lowercase. favscount = models.IntegerField(db_column='FavsCount', blank=True, null=True) # Field name made lowercase. hascorrectanswer = models.BooleanField(db_column='HasCorrectAnswer', blank=True, null=True) # Field name made lowercase. hash = models.CharField(db_column='Hash', max_length=50, blank=True, null=True) # Field name made lowercase. lang = models.CharField(db_column='Lang', max_length=50, blank=True, null=True) # Field name made lowercase. name = models.CharField(db_column='Name', max_length=250, blank=True, null=True) # Field name made lowercase. nid = models.IntegerField(db_column='Nid', primary_key=True) # Field name made lowercase. posttype = models.CharField(db_column='PostType', max_length=50, blank=True, null=True) # Field name made lowercase. published = models.BooleanField(db_column='Published', blank=True, null=True) # Field name made lowercase. publisheddate = models.DateTimeField(db_column='PublishedDate', blank=True, null=True) # Field name made lowercase. subscount = models.IntegerField(db_column='SubsCount', blank=True, null=True) # Field name made lowercase. tags = models.CharField(db_column='Tags', max_length=350, blank=True, null=True) # Field name made lowercase. text = models.TextField(db_column='Text', blank=True, null=True) # Field name made lowercase. translated = models.BooleanField(db_column='Translated', blank=True, null=True) # Field name made lowercase. type = models.CharField(db_column='Type', max_length=50, blank=True, null=True) # Field name made lowercase. views = models.IntegerField(db_column='Views', blank=True, null=True) # Field name made lowercase. votesamount = models.IntegerField(db_column='VotesAmount', blank=True, null=True) # Field name made lowercase. class Meta: managed = False db_table = 'community.post' class CommunityComment(models.Model): id1 = models.CharField(db_column='ID1', primary_key=True, max_length=62) # Field name made lowercase. acceptedanswerts = models.DateTimeField(db_column='AcceptedAnswerTS', blank=True, null=True) # Field name made lowercase. author = models.CharField(db_column='Author', max_length=50, blank=True, null=True) # Field name made lowercase. avgvote = models.IntegerField(db_column='AvgVote', blank=True, null=True) # Field name made lowercase. correct = models.BooleanField(db_column='Correct', blank=True, null=True) # Field name made lowercase. created = models.DateTimeField(db_column='Created', blank=True, null=True) # Field name made lowercase. hash = models.CharField(db_column='Hash', max_length=50, blank=True, null=True) # Field name made lowercase. id = models.IntegerField(db_column='Id') # Field name made lowercase. post = models.CharField(db_column='Post', max_length=50, blank=True, null=True) # Field name made lowercase. text = models.TextField(db_column='Text', blank=True, null=True) # Field name made lowercase. texthash = models.CharField(db_column='TextHash', max_length=50, blank=True, null=True) # Field name made lowercase. type = models.CharField(db_column='Type', max_length=50) # Field name made lowercase. votesamount = models.IntegerField(db_column='VotesAmount', blank=True, null=True) # Field name made lowercase. class Meta: managed = False db_table = 'community.comment' unique_together = (('type', 'id'),) class CommunityTag(models.Model): description = models.TextField(db_column='Description', blank=True, null=True) # Field name made lowercase. name = models.TextField(db_column='Name', primary_key=True) # Field name made lowercase. class Meta: managed = False db_table = 'community.tag' Django の管理ダッシュボードは、開発者が拡張できるようになっています。 また、テーブルをさらに追加することも可能です。 それには、main/admin.py という新しいファイルを追加する必要があります。コード内には、行を説明するコメントをいくつか追加しています。 from django.contrib import admin # immport our community models for our tables in IRIS from .models import (CommunityPost, CommunityComment, CommunityTag) # register class which overrides default behaviour for model CommunityPost @admin.register(CommunityPost) class CommunityPostAdmin(admin.ModelAdmin): # list of properties to show in table view list_display = ('posttype', 'name', 'publisheddate') # list of properties to show filter for on the right side of the tablee list_filter = ('posttype', 'lang', 'published') # default ordering, means from the latest date of PublishedDate ordering = ['-publisheddate', ] @admin.register(CommunityComment) class CommunityCommentAdmin(admin.ModelAdmin): # only this two fields show, (post is numeric by id in table post) list_display = ('post', 'created') # order by date of creation ordering = ['-created', ] @admin.register(CommunityTag) class CommunityTagAdmin(admin.ModelAdmin): # not so much to show list_display = ('name', ) ポータルの拡張 Django 管理ページに戻り、そこに追加された新しい項目を確認しましょう。 右側には、フィルタパネルがあります。最も重要なのは、特定のフィールドの可能値がすべて表示されている点です。 残念ながら、InterSystems SQL は Django で期待されているまたは Django の別の方法である LIMIT, OFFSET をサポートしていません。 また、Django は TOP をサポートしていません。 そのため、ここにページ送りは表示されますが、機能しません。 また、現時点では機能できるようにもできません。残念ながら個人的には、今後も機能することはないと思っています。 オブジェクトを詳しく調べることも可能で、Django には正しいフィールド型っを使ったフォームが表示されます。(注意: このデータセットには、テキストフィールドのデータは含まれません) コメントオブジェクト Community Edition ではライセンスに関する課題が予想されます。 Community Edition を使用している場合は、この問題に直面する可能性があります。これは、すべての接続が占有されている場合の様子であり、非常に素早く発生する問題です。 サーバーのレスポンスが非常に遅くなっている場合は、おそらくその問題に該当しています。IRIS はこのケースでは素早く応答せず、不明な理由により、長い時間がかかっています。 IRIS が空き枠あると示す場合であってもです。 6 個以上の接続を許可しないため、動作させるには、1つ以上のプロセスを終了するか、Django サーバーを再起動しなければなりません。 開発時には、Django サーバーを非スレッドモードに制限することもできるため、1つのプロセスで動作させられます。 また、IRIS への接続をさらに取得してはいけません。 python manage.py runserver --nothreading
記事
Mihoko Iijima · 2020年11月10日

大量データのロードを高速化する方法

これはInterSystems FAQ サイトの記事です。 インデックスが複数定義されているクラス/テーブルへ csv 形式等のシーケンシャルファイルから大量データをデータベースに登録する際、推奨される登録方法として、データ登録時インデックスを生成させず、登録完了後に一括でインデックスを生成する 方法があります。 この方法は、新規に大量のレコードを一括登録する際に最も有効な手段となります。 <メモ>大量のデータを追加登録する際には、既存のデータ量と新規データ量のバランスにより、この手法が有効でないケースもあります。その場合は、インデックスの再構築を範囲指定で行うこともできます。 説明に使用するクラス定義例は以下の通りです。 Class ISJ.QL2 Extends %Persistent { Property Name As %String; Property Title As %String; Property Sex As %String; Property Company As %String; Property Phone As %String; Property City As %String; Property State As %String; Property Zip As %String; Index NameIndex On Name; Index CompanyIndex On Company; Index PhoneIndex On Phone; } データロードを行うクラスメソッド例は以下の通りです。 ClassMethod ImportFromFile(pFile As %String) { #dim Err As %Exception.AbstractException //埋め込みSQLを使用してインポート Try { if $get(pFile)="" { write "インポートファイルを指定してください",! quit } if ##class(%File).Exists(pFile)=0 { write "指定したファイルは存在しません。ファイル名、パスを確認してください",! quit } set filestream=##class(%Stream.FileCharacter).%New() do filestream.LinkToFile(pFile) set tDelim=";" while filestream.AtEnd=0 { //改行があるところまでRead set tLine=filestream.ReadLine() set pCity = $Piece(tLine,tDelim,1) //City set pCompany = $Piece(tLine,tDelim,2) //Company set pName = $Piece(tLine,tDelim,3) //Name set pPhone = $Piece(tLine,tDelim,4) //Phone set pSex = $Piece(tLine,tDelim,5) //Sex set pState = $Piece(tLine,tDelim,6) //State set pTitle = $Piece(tLine,tDelim,7) //Title set pZip = $Piece(tLine,tDelim,8) //Zip &sql(INSERT %NOINDEX INTO ql2 (Name, Title, Sex, Company, Phone, City, State, Zip) values (:pName, :pTitle, :pSex, :pCompany, :pCity, :pCity, :pState, :pZip)) // SQL文でエラーがある場合の処理 if SQLCODE<0 { throw ##class(%Exception.SQL).CreateFromSQLCODE(SQLCODE,%msg) } } // 最後にテーブルのインデックス再構築を実行 set st=..%BuildIndices() $$$THROWONERROR(Err,st) // エラーが発生した場合Catchへ移動 } Catch Err { write "エラーが発生しました",! write Err.DisplayString(),! } } クラスメソッドでは、以下のデータ形式で作成されたファイルを入力しながらデータ登録後にインデックスを構築しています(ランダム生成させたデータを使用しています)。 ;京都市上京区;新光損保 株式会社;橋本,京子;0363-7728-2499;女;京都府;国際製品スペシャリスト;6020808;札幌市西区;NTS工業 株式会社;本田,亮;0325-6753-6990;男;北海道;アシスタント管理者;0630003;呉市;セコミ薬品 株式会社;川原,明雄;0670-9635-5468;男;広島県;副衛生士;7370145;春日部市;SESコミュニケーションズ 株式会社;大島,江美;0407-3421-5865;女;埼玉県;研究ディレクタ;3440065;上高井郡小布施町;電金証券 有限会社;松本,亮;053-3208-4665;女;長野県;副会計士;3810202;茅野市;ビーエスシ薬品 株式会社;渡部,弘明;0996-5061-8567;女;長野県;国際マーケティングマネージャ;3910212;中川郡豊頃町;電金技研 有限会社;根本,由貴;0647-5142-4961;男;北海道;国際ウェブマスタ;0895461;尼崎市;SES石油 有限会社;川口,博美;0744-3148-1523;男;兵庫県;副会計士;6610978;北松浦郡吉井町;三友製造 株式会社;阿部,陽子;0554-2270-3308;男;長崎県;副システムエンジニア;8596304 サンプルコードの以下の文で、全レコードに対してインデックスを構築しています。 Do ..%BuildIndices() サンプルコードで使用している埋め込み SQL では、実行後に %ROWID 変数を使用して INSERT によって設定された ID 値を取得できます。 例えば、INSERT したレコードのインデックスのみを構築したい場合は、%BuildIndices() メソッドの第 2 引数(pAutPurge)に 0 を指定し、第 5 引数(pStartID)および第 6 引数(pEndID)に ID を指定することで実行できます。 インデックス構築方法について詳細は、ドキュメントもご参照ください。
記事
Mihoko Iijima · 2021年2月12日

XMLファイルの内容をデータベースに登録する方法

これは InterSystems FAQ サイトの記事です。 XMLファイルの内容を格納する永続クラス定義を作成し、%XML.Adaptor を追加で継承します。 例は以下の通りです(右端の %XML.Adaptorクラスを追加で継承します)。 Class ISJ.Class1 Extends (%Persistent, %Populate, %XML.Adaptor) 次に、%XML.Reader クラスを使用して格納先のインスタンスへ、タグとクラスの関連付け(Correlate())を行い、reader.Next() でXMLを取り込みます。 set sc=reader.OpenFile(filename) do reader.Correlate(tag,class) while reader.Next(.x,.sc) { do x.%Save() } サンプルコードは以下の通りです。 Class ISJ.Class1 Extends (%Persistent, %Populate, %XML.Adaptor) { Property a As %String; Property b As %String; /// 引数:入力するXMLファイルのフルパス ClassMethod Import(filename As %String) { if $get(filename)="" { write "入力ファイルを引数に指定してください",! quit } // クラス名指定(..%ClassName()でクラスメソッドの存在するクラス名を返します) set class=..%ClassName(1) // XMLタグ指定 set tag="test" set reader = ##class(%XML.Reader).%New() set sc=reader.OpenFile(filename) If $$$ISERR(sc) { write $system.Status.GetErrorText(sc),! Quit } // クラスとの関連付け do reader.Correlate(tag,class) while reader.Next(.x,.sc) { set sc=x.%Save() If $$$ISERR(sc) { write $system.Status.GetErrorText(sc),! Quit } } quit } } 入力するXMLファイル例は以下の通りです。 <?xml version="1.0" encoding="UTF-8"?> <top><test> <a>あいうえお</a> <b>かきくけこ</b> </test> <test> <a>O8634</a> <b>H7321</b> </test> <test> <a>J5426</a> <b>V2218</b> </test> <test> <a>J7155</a> <b>M6804</b> </test> <test> <a>R7066</a> <b>W939</b> </test> <test> <a>B9731</a> <b>I406</b> </test> <test> <a>I1095</a> <b>Z5125</b> </test> <test> <a>Q1573</a> <b>Z9241</b> </test> <test> <a>T7560</a> <b>V5693</b> </test> <test> <a>J1033</a> <b>J9616</b> </test> </top> 実行例は以下の通りです。 (1) ターミナルを開き(またはIRISへログインし)クラス定義を作成したネームスペースへ移動します。 ネームスペースの変更方法は以下の通りです。 set $namespace="USER" ※ Linux/Unix上の IRIS へログインする方法は、 iris session <構成名> を実行します。例は以下の通りです。 iris session IRIS (2) 入力例のXMLをファイルに保存(UTF-8で保存)し、ファイルのフルパスを変数に設定します。 例)/ISC/data.xml に配置した例 set file="/ISC/data.xml" (3) 例のクラス定義(ISJ.Class1)の Import() メソッドを実行します。引数に (2) で設定した変数 file を指定しています。 do ##class(ISJ.Class1).Import(file) (4) データを確認します。 管理ポータルで確認する場合は以下の手順で画面を開きます。 管理ポータル > システムエクスプローラ > SQL > クラス定義のあるネームスペースに移動> スキーマのプルダウンから ISJ を選択 > テーブル の > をクリックし展開 > Class1を選択して、画面右側の「テーブルを開く」をクリック または、画面右側の「クエリ実行」タブをクリックし、テーブル名をドラッグしクエリ実行タブのテキストエリアでドロップし、実行ボタン押下 ターミナルで確認する場合は、SQL 実行環境に変更します。 do $system.SQL.Shell() プロンプトが >> に変更されたらSELECE文を記述してEnterを押下します USER>do $system.SQL.Shell() SQL Command Line Shell ---------------------------------------------------- The command prefix is currently set to: <<nothing>>. Enter <command>, 'q' to quit, '?' for help. [SQL]USER>>select * from ISJ.Class1 1. select * from ISJ.Class1 ID a b 1 あいうえお かきくけこ 2 O8634 H7321 3 J5426 V2218 4 J7155 M6804 5 R7066 W939 6 B9731 I406 7 I1095 Z5125 8 Q1573 Z9241 9 T7560 V5693 10 J1033 J9616 10 Rows(s) Affected statement prepare time(s)/globals/cmds/disk: 0.0822s/37396/167775/3ms execute time(s)/globals/cmds/disk: 0.0007s/11/1370/0ms cached query class: %sqlcq.USER.cls29 --------------------------------------------------------------------------- [SQL]USER>>quit USER>