検索

クリアフィルター
記事
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です。 どうぞお楽しみください!  
記事
Mihoko Iijima · 2024年7月4日

Embedded Python利用時にエラーが発生した場合の対応方法のご紹介

これは InterSystems FAQ サイトの記事です。 PythonスクリプトファイルやPythonで記述されたIRIS内メソッドを呼び出す際、エラーが発生した場合の対応方法をご紹介します。 説明使用するコードや資料PDFは公開しています👉 test1.py、FS.Utilsクラス、コードのコピー元、ビデオで解説している資料PDF Embedded Python 自習用ビデオをご用意しています(項目別にYouTubeプレイリストをご用意しています)。 各プレイリストについて詳しくはこちらをご参照ください👉【はじめてのInterSystems IRIS】Embedded Python セルフラーニングビデオシリーズ公開! Python側でエラーが発生した場合、ObjectScriptのシステムエラーとして取り扱うことができます。 具体的に確認してみましょう。 解説と実行例については以下のビデオの 最初~03:57までで解説しています。 例えば、以下メソッドの第2引数に0を指定すると'ZeroDivisionError'エラーが発生します。IRISから呼び出した場合は、このエラーは特殊変数$ZERRORにセットされます(=ObjectScriptでエラーが発生したときと同じ状況になります) ClassMethod errtest1(a As %Integer, b As %Integer) As %Integer [ Language = python ] { result=a/b return result } IRISから実行した場合の結果は以下の通りです(特殊変数$ZERRORにエラー文字列が設定されていることを確認できます)。 コマンド例は以下の通りです。 do ##class(FS.Utils).errtest1(1,0) ターミナル実行例は以下の通りです。 USER>do ##class(FS.Utils).errtest1(1,0) DO ##CLASS(FS.Utils).errtest1(1,0) ^<THROW> *%Exception.PythonException <PYTHON EXCEPTION> 246 <class 'ZeroDivisionError'>: division by zeroUSER>w $ZE<THROW> *%Exception.PythonException <PYTHON EXCEPTION> 246 <class 'ZeroDivisionError'>: division by zeroUSER> 次に、Pythonのコード内で%Statusのエラーが発生した場合はどうなるでしょうか。 下記コードの解説と実行例については、ビデオの 03:37~ 07:43まで解説しています。 《ObjectScript》 ClassMethod statustest() [ Language = python ] { import iris try: a=iris.cls("FS.Person")._New() a.DOB="ThisIsError" st=a._Save() iris.check_status(st) except RuntimeError as ex: print("pythonのエクセプション!") print(str(repr(ex))) raise } コマンド例は以下の通りです。 write ##class(FS.Utils).errtest1(1,0) ターミナル実行例は以下の通りです。 USER>write ##class(FS.Utils).errtest1(1,0)WRITE ##CLASS(FS.Utils).errtest1(1,0)^<THROW> *%Exception.PythonException <PYTHON EXCEPTION> 246 <class 'ZeroDivisionError'>: division by zeroUSER> 《Pythonスクリプト》 def err1(a,b): result=a/b return result コマンド例は以下の通りです。 set sys=##class(%SYS.Python).Import("sys") do sys.path.append("c:\WorkSpace\TryIRIS") set test1=##class(%SYS.Python).Import("test1") write test1.err1(1,0) ターミナル実行例は以下の通りです。 USER>set sys=##class(%SYS.Python).Import("sys")USER>do sys.path.append("c:\WorkSpace\TryIRIS")USER>set test1=##class(%SYS.Python).Import("test1")USER>write test1.err1(1,0)WRITE test1.err1(1,0)^<THROW> *%Exception.PythonException <PYTHON EXCEPTION> 246 <class 'ZeroDivisionError'>: division by zeroUSER> SQL実行時のエラーはどうなるでしょうか。 下記コードの解説と実行例については、ビデオの07:43~09:06 をご参照ください。 《Pythonスクリプト》 def sqlerr(): import iris import irisbuiltins try: sql="select * from Training.Person" rset=iris.sql.exec(sql) for key,val in enumerate(rset): print(val) except irisbuiltins.SQLError as ex: print(str(repr(ex))) print(ex.sqlcode) print(ex.message) print(ex.statement) raise 一連のコマンド例は以下の通りです。 //ローカル変数に何も設定されていない事を確認します write //システムエラーが発生した場合に$ZE特殊変数に情報が設定されます write $ZE // Pythonシェル起動 do ##class(%SYS.Python).Shell() ## 以下Pythonシェルで実行 import sys sys.path+=["c:\WorkSpace\TryIRIS"] import test1 test1.sqlerr() quit() // 以下IRISターミナルで実行 write $ZE // %objlasterrorが存在すると、エラーメッセージが表示されます。 do $system.OBJ.DisplayError() ターミナル実行例は以下の通りです。 USER>writeUSER>write $ZEUSER>:py //またはdo ##class(%SYS.Python).Shell()Python 3.10.12 (main, Nov 20 2023, 15:14:05) [GCC 11.4.0] on linuxType quit() or Ctrl-D to exit this shell.>>> import sys>>> sys.path+=["c:\WorkSpace\TryIRIS"]>>> import test1>>> test1.sqlerr()SQLError(" テーブル 'TRAINING.PERSON' が見つかりません")-30 テーブル 'TRAINING.PERSON' が見つかりませんselect * from Training.PersonTraceback (most recent call last): File "<input>", line 1, in <module> File "c:\WorkSpace\TryIRIS\test1.py", line 36, in sqlerr rset=iris.sql.exec(sql)irisbuiltins.SQLError: テーブル 'TRAINING.PERSON' が見つかりません>>> quit()USER>write $ZE<SYNTAX>errdone+2^%qaqqt 《2024/7/8確認》ビデオで触れていませんがこの時点で、%Statusのエラーが生成され %objlasterror変数に直近のエラー情報がセットされています。(Pythonシェルで受け取ったエラーと同様の情報を確認できます。) USER>write%objlasterror="0 ÞâB Æ0ü0Ö0ë0 'TRAINING.PERSON' L0d0K00~0[00USER e^exec+3^%SYS.Python.SQL.1^1e^^%SYS.Python.1^0w^Shell+47^%SYS.Python.1^1-e^%DispatchClassMethod+2^%SYSTEM.Python.1^1d^^^0"USER>do $system.OBJ.DisplayError()エラー #5521: SQLエラー: SQLCODE=-30 %msg= テーブル 'TRAINING.PERSON' が見つかりませんUSER> %Statusのエラーの表示方法ついて詳しくは、ObjectScriptクックブックの「%Statusのエラーが戻ってきたら」 Python側でエラー処理を書かない場合の説明についてはビデオ 09:06~ ご参照ください。 続いて、Embedded Python利用時にエラーが発生した場合のエラー処理例をご紹介します。 戻り値でエラーだったことを報告する例(Pythonスクリプトの例)の解説は、ビデオの最初~1:48までをご参照ください。 戻り値でエラーだったことを報告する例(language=pythonの例)は、以下ビデオの1:48~3:16で解説しています。 コード例は以下の通りです。 《Pythonスクリプト》 def err2(a,b): try: if b==1: modori="1で割っても答えは同じです" return modori print(f"割り算の答えは={a/b}") modori="OK" return modori except ZeroDivisionError as ex: modori=str(repr(ex)) print(modori) return modori 《IRISからのコマンド実行例》 set sys=##class(%SYS.Python).Import("sys") do sys.path.append("C:\WorkSpace\TryIRIS") set errtest=##class(%SYS.Python).Import("test1") set ret=errtest.err2(2,2) write ret set ret=errtest.err2(2,1) write ret set ret=errtest.err2(2,0) write ret ターミナル実行例は以下の通りです。 USER>set sys=##class(%SYS.Python).Import("sys")USER>do sys.path.append("C:\WorkSpace\TryIRIS")USER>set errtest=##class(%SYS.Python).Import("test1")USER>set ret=errtest.err2(2,2) write ret割り算の答えは=1.0OKUSER>set ret=errtest.err2(2,1)USER>write ret1で割っても答えは同じですUSER>set ret=errtest.err2(2,0)ZeroDivisionError('division by zero')USER>write retZeroDivisionError('division by zero')USER> 《ObjectScriptの例》 ClassMethod errtest2(a As %Integer, b As %Integer) As %Integer [ Language = python ] { try: if b==1: modori="1で割っても答えは同じです" return modori print(f"割り算の答えは={a/b}") modori="OK" return modori except ZeroDivisionError as ex: modori=str(repr(ex)) print(modori) return modori } コマンド実行例は以下の通りです。 set modori=##class(FS.Utils).errtest2(2,2) write modori set modori=##class(FS.Utils).errtest2(2,1) write modori set modori=##class(FS.Utils).errtest2(2,0) write modori ターミナル実行例は以下の通りです。 USER>set modori=##class(FS.Utils).errtest2(2,2)割り算の答えは=1.0USER>write modoriOKUSER>set modori=##class(FS.Utils).errtest2(2,1)USER>write modori1で割っても答えは同じですUSER>set modori=##class(FS.Utils).errtest2(2,0)ZeroDivisionError('division by zero')USER>write modoriZeroDivisionError('division by zero')USER> 戻り値でエラーだったことを報告する例(%Statusを戻す場合)は、ビデオの3:38~5:22で解説しています。 コード例は以下の通りです。 《ObjectScriptの例》 ClassMethod errtest3(a As %Integer, b As %Integer) As %Status [ Language = python ] { import iris try: print(a/b) ret=1 except Exception as ex: moji="エラーが発生しました!"+str(repr(ex)) ret=iris.system.Status.Error(5001,moji) return ret } コマンド実行例は以下の通りです。 set status=##class(FS.Utils).errtest3(1,0) write $system.Status.GetErrorText(status) set status=##class(FS.Utils).errtest3(1,1) write status ターミナル実行例は以下の通りです。 USER>set status=##class(FS.Utils).errtest3(1,0)USER>write $system.Status.GetErrorText(status)エラー #5001: エラーが発生しました!ZeroDivisionError('division by zero')USER>set status=##class(FS.Utils).errtest3(1,1)1.0USER>write status1USER> try: except: を使う + raise でそのままIRISに例外を戻す例と解説は以下ビデオ5:22~最後までご覧ください。 《Pythonスクリプトの例》 def err3(a,b): try: ret=a/b print(ret) except: raise 《ObjectScriptコード例》 ClassMethod errtest4() As %Status { #dim ex As %Exception.AbstractException try { set sys=##class(%SYS.Python).Import("sys") do sys.path.append("C:\WorkSpace\TryIRIS") set errtest=##class(%SYS.Python).Import("test1") do errtest.err3(1,0) } catch ex { write "エラーが発生しました:",ex.DisplayString(),! //例外から%Statusに変換 set st=ex.AsStatus() //例外からSQLCODEとメッセージを取得 set SQLCODE=ex.AsSQLCODE() set SQLMessage=ex.AsSQLMessage() } } コマンド実行例は以下の通りです。 do ##class(FS.Utils).errtest4() ターミナル実行例は以下の通りです。 USER>do ##class(FS.Utils).errtest4()エラーが発生しました:<PYTHON EXCEPTION> 246 <class 'ZeroDivisionError'>: division by zeroUSER>
記事
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>
お知らせ
Toshihiko Minamoto · 2023年6月20日

外部テーブルのEarly Access Programにご参加ください

先週の InterSystems Global Summit にて、今年の初めにリリースしました2023.1のエクスペリメンタル機能として、新たな 外部テーブル を発表しました。現在、 外部テーブルの Early Access Program にご参加いただきご評価いただくことで、この機能がお客さまのニーズに合っているか、次に向けてどの機能を優先するべきか、お知らせいただきたいと考えています。 外部テーブルって何なの?この素晴らしい概要ビデオを見る時間やポップコーンがない場合に備えて、外部テーブルは、ファイルやリモートデータベースなど、物理的に別の場所に保存されているデータをIRIS SQLとしてアクセスするのに役立つ機能です。外部テーブルは、通常のIRISテーブルとしてSQLに表示され、他の通常テーブルや外部テーブルとのJOINなど、あらゆるSQLステートメントで使用することができます。クエリを実行する際、外部テーブルから何を検索する必要があるのかを理解し、そのサーバーがリレーショナルデータベースの場合は、ネットワーク経由で取得するデータを最小限に抑えるようなクエリを出力しています。 なぜデータをロードするだけじゃないの?それはいい質問ですね!データをロードした後、IRISでクエリを実行すると、ソース・システムですでに変更されている可能性があります。コストのかかる同期メカニズムを構築するのではなく、外部テーブルとして投影することで、クエリ時に常に最新のデータを取得することができます。その他の使用例としては、ファイルベースの大きなデータで、IRISデータベースのストレージを消費してしまうような場合、または一般的に一度しか読まないようなデータなどがあります。ところで、外部テーブルの構文と機能は、2021.2 で紹介した LOAD DATA コマンドで知っているものと完全に一致しています。 既にリンクテーブルがあるのでは?その通りです。その機能はかなり以前からあり、私たちの顧客ではよく採用されています。しかし、JDBCとODBCの実装が異なる時期に行われたため、機能や制限が微妙に異なり、メンテナンスや機能拡張が複雑になっていました。当初の実装以降、IRISのSQL内部でいくつかの改良(Table Valued Functionsを含む)が行われ、外部とIRISをより明確に分離した、より実用的なアプローチが可能になりました。このため、新しい機能として Foreign Tables を実装し、公式の ANSI SQL 標準と一致するようにしました。 現在、外部テーブルをLinked Tablesの後継とするつもりです。現在、Foreign Tablesを使用している方は、その機能のどれがあなたのユースケースにとって重要かをぜひ教えてください。そうすれば、外部テーブルの次の開発フェーズに優先順位をつけ、迅速に多くのユーザーに参加してもらい、ラボ以外からフィードバックが得られるでしょう。 どこで申し込むの?intersystems.com/early-access-programs にお申込みいただければ、チュートリアルとデモのリポジトリから開始できます。定期的にご連絡を差し上げ、ご意見を伺い、新しい機能を追加していく予定です。 皆様からのご連絡をお待ちしております!
記事
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年8月23日

データベースファイルのサイズを圧縮する方法

これは InterSystems FAQ サイトの記事です。 ※データベースファイルとは、IRIS.DAT、および、CACHE.DATのことを指します。 Caché/Ensemble 2018.1.4、IRIS 2019.1.2/2020.1.0 以降のバージョンより、データベースファイル のサイズの縮小に、データベースの「圧縮」と「未使用領域の削除」機能をあわせて使用できます。 ※注意※Caché/Ensemble 2018.1.4、IRIS 2019.1.2/2020.1.0 より前のバージョンでは、データベースの「圧縮」機能は使用できません。使用された場合、データベース破損が引き起こされる可能性があります(「未使用領域の削除」機能は利用できます)。 詳細情報は以下、弊社ウェブサイト内のページをご確認ください。 製品ニュースとアラート>警告: データベース圧縮またはデフラグ後のデータベース整合性の問題 データベースサイズ縮小手順は以下の通りです。 管理ポータル: [システムオペレーション] > [データベース] を開き、操作したいデータベース名をクリックします。 (または、画面左上の[空き容量]ボタンをクリックし、データベースの空き容量を表示します。) ① 「圧縮」ボタンにより、データベースファイルの使用していないブロックを後ろに集めます <圧縮のイメージ> ●が使用中ブロック、〇が未使用ブロックだとします。 ●●○●●○○●○○ ←のようにブロックが並んでいるデータベースファイル(*.DAT)を「圧縮」すると ●●●●●○○○○○ ←このようになります (左が前方) ②「未使用領域削除」ボタンにより、後方に集めた未使用ブロックを削除してデータベースファイルのサイズを縮小します。 <未使用領域削除のイメージ> ●が使用中ブロック、〇が未使用ブロックだとします。 ●●●●●○○○○○ ←のようにブロックが並んでいるデータベースファイルに「未使用領域削除」を実行すると ●●●●●  ←このようになります 圧縮について詳細は、ドキュメント(データベースの圧縮(IRIS))をご参照ください(2018.1以前のドキュメント:データベースの圧縮)。 管理ポータルの操作と同様の処理を、^DATABASE ユーティリティを使用して実行することもできます。 %SYS ネームスペースに移動してから実行します。 USER>zn "%SYS" %SYS>do ^DATABASE 1) Create a database 2) Edit a database 3) List databases 4) Delete a database 5) Mount a database 6) Dismount a database 7) Compact globals in a database 8) Show free space for a database 9) Show details for a database 10) Recreate a database 11) Manage database encryption 12) Return unused space for a database // 「未使用領域削除」 13) Compact free space in a database // 「圧縮」 14) Defragment a database         // 「DB の断片化解消」 15) Show background database tasks Option? ※「圧縮」 = 13) Compact freespace in a database 「未使用領域削除」= 12) Return unused space for a database ^DATABASE ユーティリティの 14) Defragment globals in a database(「DBの断片化解消」)について補足します。 「DBの断片化解消」では、指定したデータベースファイル内のグローバル毎にブロックが再編成され、データベースファイル内で連続領域に再配置します。 「圧縮」をより強力にしたものですが、この再配置を行う為には、データベースファイル末尾に十分な空きブロックが必要です(一般的に見て、デフラグを定期的に実行する必要はありません)。 <「DBの断片化解消」のイメージ> ^a ♦  ^b ♠ ^c♥ 空き○ 断片化解消前♦♦○♠♠○♠♠♦♦♠♥♥♥○♦♦♥♥♦○○○○○○ 断片化解消後♦♦♦♦♦♦♦♠♠♠♠♠♥♥♥♥♥○○○○○○○○○ ※注意※「断片化解消」機能についても、2015.1.3、2015.2.2 より前のバージョンでは使用できません。使用された場合、「圧縮」機能と同様に、データベース破損が引き起こされる可能性があります。 詳細は、ドキュメント(データベースのグローバルのデフラグ(IRIS))をご参照ください(2018.1以前のドキュメント:データベースのグローバルのデフラグ)。
お知らせ
Mihoko Iijima · 2021年6月8日

バージョンサポートポリシーについて

これは InterSystems FAQ サイトの記事です。 現時点のサポートバージョンについては、以下のドキュメントをご参照ください。 ミニマム・サポートバージョンについて このリストに記述されていないバージョンに関しては、該当システムの保守契約があるという前提で、 メール、お電話等の通常のお問い合わせは受け付けます。 出来うる限りの対応を努力しますが、そのバージョンでの対応が出来ず、最新バージョンへのバージョンアップによる問題解決をご提案させていただく場合がございます。 インターシステムズは、以下の理由により、出来うる限り、最新バージョンへバージョンアップしていただくことをお勧めしております。 開発部門では、定期的に内部のコードレビュを実施しており、その結果、発見された機能不全等を適宜、最新バージョンに取り込んでいます。 過去にお客様先で発見された障害への対応は、順次新バージョンに取り込んでいきます。障害の性質によっては、非常に大規模な改造となり、旧バージョンへの取り込み(バックポート)が不可能なものもございます。 古いバージョンに対し、新バージョンに追加されたバグフィックスを取り込むこと(バックポート)は、非常に困難なケースが多く、新たな副作用を生む可能性が高くなります。また、バージョンが古くなればなるほどバックポートは困難になっていく可能性が高まります。 開発をより迅速に行うための便利な機能等、新機能の恩恵を受けることができます。 新しいバージョンは以前のバージョンよりも性能が同等または高くなければならない、というインターシステムズ製品開発部門のポリシーにより、バージョンが新しくなるにつれ、アプリケーションの性能向上が期待できます。 日々品質向上に向け努力を続けております。結果として最新バージョンの品質は以前のバージョンに比べて高いことが期待できます。 新しいハードウェア対応、それに伴うオペレーティングシステムや様々なソフトウェアの新バージョンへの対応は、最新のバージョンが優先されます。 セキュリティの脆弱性等への対応は最新バージョンが優先されます。 セキュリティの脆弱性等への対応は、大規模な広範囲にわたる変更の可能性が高く、古いバージョンへの対応が大変困難となります。 サポート対象バージョンに関しては、所定のリグレッションテストを実施後出荷しますが、応急パッチ等の非定型な対応では、リグレッションテストを実施しませんので、ソフトウェアの信頼性に大きな差があります。 お使いの IT 環境は、技術の進歩、セキュリティ意識の高まり等様々な要因で変化していく可能性が高い中、古いバージョンのソフトウェアを使い続けることには想定以上のリスクがあります。 関連記事・FAQトピック セキュリティ脆弱性に関する対応について バグフィックス対応について 古いバージョンの購入はできますか?
記事
Megumi Kakechi · 2023年4月3日

現在の一般メモリヒープ(gmheap)と取得可能なロックテーブル(locksiz)最大サイズについて

これは InterSystems FAQ サイトの記事です。 データ取込み処理の性能・エラー(Lock Table Full)対策として、一般メモリヒープ(gmheap)や ロックテーブルサイズ(locksiz)のパラメータチューニングを行う場合があると思います。 実際に、現在どのくらいの一般メモリヒープが確保できているのかは、ターミナルと管理ポータルで確認することができます。 ★ターミナルの場合 // 一般メモリヒープサマリ USER>w $system.Config.SharedMemoryHeap.GetUsageSummary() 4992226,6029312,59441152 一般メモリヒープサマリは、使用量,アロケート量,構成量(bytes) で戻り値が表示されます。 使用量は、アロケートされたロックテーブルやプロセステーブルなどで実際に使用されている量になります。アロケート量は、gmheapの領域でロックテーブルやプロセステーブルなどでアロケートされている量になります。構成量は、gmheap(KB) +IRISシステム追加領域 で、これが現在の最大利用可能な量(実際の一般メモリヒープの領域の値)になります。 上で述べたように、構成量は構成パラメータの gmheap の単体の値と一致していません。これは、IRISが自動で 構成パラメータ gmheap に内部で使用するメモリ領域分を付加して、一般メモリヒープの領域を構成しているためになります。詳細は以下のドキュメントをご覧ください。 gmheapについて 以下のコマンドでは、ロックテーブル使用量を取得できます。使用可能量, ユーザ使用可能量, 使用量(bytes) で戻り値が表示されます。詳細はこちらの記事をご覧ください。 %SYS>w ##class(SYS.Lock).GetLockSpaceInfo() 16772624,16764624,4592 ★管理ポータルの場合  システムオペレーション > システム使用 > 共有メモリヒープ使用状況 より確認できます。  一般メモリヒープ全体については、 "Total SMH Pages Used"の項目の"割り当てられたSMH/ST"が、アロケート量(bytes) を示します。    ロックテーブルについては "Lock Table"の項目の"SMH/ST使用中"が、ロックテーブルの使用量(byte) を示します。 ユーザ使用可能量は、locksiz 値からこの値の差分により求める必要があります。   gmheap を変更する場合、IRISインスタンスの再起動を伴います。現在の gmheap 内で設定可能な、locksiz の最大値を求めるには以下のように行います。locksiz のみであれば、再起動なしに変更が可能です。 %SYS>write ##class(SYS.Lock).GetMaxLockTableSize() 16777216 GetMaxLockTableSize() で取得できる値よりも大きな locksiz を指定したい場合は、差分を gmheap に追加して設定する必要があります。 その場合は、IRISインスタンスの再起動後に新しい設定値が反映されます。 【注目】2023.1以降のバージョンでは、共有メモリヒープ(gmheap)や ロックに使用する共有メモリ(locksiz) の既定値が「0」に変更されました。リリースノートは、こちら※2022.2は、gmheap=37,568(KB)、locksiz=65,536 (Byte) でした。 構成されたグローバルバッファサイズ(データベースキャッシュサイズ)に基づき、最も効果的な設定値が適用されます。ユーザによって、グローバルバッファサイズが設定されていない場合は、使用可能なシステムメモリから構成されます。以前のバージョンと同様に、ユーザは引き続き特定の値でこれらの既定値をオーバーライドすることが可能です。 2023.1以降のバージョンより、共有メモリヒープ(gmheap)や ロックに使用する共有メモリ(locksiz) の既定値が「0」に変更されましたので、記事後半、【注目】以降に追記しました。
記事
Mihoko Iijima · 2024年7月11日

管理ポータルのユーザ設定をプログラムで作成する方法

これは InterSystems FAQ サイトの記事です。 管理ポータル > [システム管理] > [セキュリティ] 以下の設定は、%SYSネームスペースにあるSecurityパッケージ以下クラスが提供するメソッドを利用することでプログラムから作成することができます。 ユーザ設定については、Security.UsersクラスのCreate()メソッドを使えば作成できますが、ユーザを作成するだけでは適切な権限が付与されずに目的のデータにアクセスできない状況もあります。 例)testAユーザ作成 %SYS>set st=##class(Security.Users).Create("testA",,"testA","これはテストユーザです","USER") %SYS>write st 1 上記メソッドで作成した結果は以下の通りです。(ロール付与無し、テーブルに対する権限の割り当てもなしの状態) 以降の解説では、以下のシナリオをもとにした設定を行っていきます。 シナリオ:アプリケーション開発者用ロールとユーザを作成する 1) アプリケーション開発者のtestAは、USERネームスペースにログインするアプリケーション開発者です(=%DevelopmentリソースのUse許可を与えます)。 2) このユーザはUSERネームスペース内で自由にテーブルの作成・参照・更新が行えるよう適切な特権を持つように定義します。 1) のアプリケーション開発者testAの作成は、以下の通りです。 未作成の場合は以下実行します。(現時点ではロールは何も付与していません。) 第1引数:ユーザ名 第2引数:ロール(複数ある場合はカンマ区切りで指定) 第3引数:パスワード 第4引数:ユーザのフルネーム 第5引数:開始ネームスペース(ログイン後にアクセスするネームスペース) %SYS>set st=##class(Security.Users).Create("testA",,"testA","これはテストユーザです","USER") %SYS>write st 1 2)で触れられているテーブルの特権はユーザに直接付与することもできますが、ロールにも付与できます。 アプリケーション開発者であるtestA 以外にも開発者が増えたとき簡単に同じ設定をユーザに追加できるよう、%DevelopmentリソースのUse許可、%DB_USERロール、テーブルに対する操作が行える特権を追加したロール:MyAppDeveloper を作成します。 %SYSネームスペースで実行します。 set st=##class(Security.Roles).Create("MyAppDeveloper","アプリケーション開発者用ロール","%Development:U","%DB_USER") Security.RolesクラスのCreate()メソッドに指定する引数は以下の通りです。 第1引数:ロール名第2引数:ロールの説明第3引数:リソースの割り当て(未指定もOK)第4引数:割り当てるロール(複数ある場合はカンマ区切りで指定) 管理ポータルでは以下のように表示されます。 管理ポータル > [システム管理] > [セキュリティ] > [ロール] > [MyAppDeveloper]ロールの「Assigned To」タブ選択 このロールにUSERネームスペースの全てのSQL管理者特権を付与します。 set p=##class(Security.SQLAdminPrivilegeSet).%New() set p.AlterTable=1 set p.AlterView=1 set p.BuildIndex=1 set p.CancelQuery=1 set p.CreateFunction=1 set p.CreateMethod=1 set p.CreateProcedure=1 set p.CreateTable=1 set p.CreateTrigger=1 set p.CreateView=1 set p.DropFunction=1 set p.DropProcedure=1 set p.DropQuery=1 set p.DropTable=1 set p.DropTrigger=1 set p.DropView=1 set p.Namespace="USER" set p.Grantee="MyAppDeveloper" set st=p.%Save() ロールMyAppDeveloperの[SQL Admin Privileges] 最後に、アプリケーション開発者testAに作成したロール:MyAppDeveloperを付与します。(AddRoles()メソッドを使用します。) %SYS>set st=##class(Security.Users).AddRoles("testA","MyAppDeveloper") %SYS>write st 1 これで設定完了です。 確認のため、管理ポータルをログアウトし、testAユーザでログインします(パスワードはtestAです) USERネームスペースにアクセスすることを確認し、管理ポータル > [システムエクスプローラ] > [SQL] で任意のSQL文を実行します。(CREATE TABLEなどが実行できることを確認します)
記事
Shintaro Kaminaka · 2020年7月30日

RESTForms - 永続クラスにREST APIをアドオンする

この記事では、RESTFormsプロジェクト(モダンなWebアプリケーション用の汎用REST APIバックエンド)を紹介します。 プロジェクトの背後にあるアイデアは単純です。私はいくつかのREST APIを書いた後、REST APIが一般的に次の2つの部分で構成されていることに気付きました。 * 永続クラスの操作 * カスタムビジネスロジック また、独自のカスタムビジネスロジックを書く必要はありますが、RESTFormsには永続クラスの操作に関連するすべての機能を提供しています。 **使用例** * Cachéにすでにデータモデルがあり、REST API形式で情報の一部(またはすべて)を公開したい * 新しいCachéアプリケーションを開発しており、REST APIを提供したい **クライアントサイド** このプロジェクトはWebアプリケーションのバックエンドとして開発されているため、JSだけで事足ります。 形式の変換は必要ありません。 **補足:CRUD** オブジェクトまたはコレクションに対し、次の4つの操作を実行できます。 * Create(作成) * Read(読み込み) * Update(更新) * Delete(削除) **機能** RESTFormsを使用して以下を実行できます。 * 公開されたクラスに対するCRUD - クラスのメタデータを取得し、クラスのプロパティを作成 / 更新 / 削除できます。 * オブジェクトに対するCRUD - オブジェクトを取得 / 作成 / 更新 / 削除できます。 * オブジェクトコレクションに対するRead(SQL経由) - SQLインジェクションから保護します。 * 自己検出 – 最初に使用可能なクラスのリストを取得し、その後でクラスのメタデータを取得し、そのメタデータを基にしてオブジェクトに対するCRUDを実行できます。 **パス** 以下の表には、主なパスとRESTFormsを使用して実行できる操作を掲載しています。 URL 説明 info 利用可能なすべてのクラスを一覧表示します info/all すべてのクラスのメタデータを取得します info/:class クラスのメタデータ field/:class プロパティをクラスに追加します field/:class クラスのプロパティを変更します field/:class/:property クラスのプロパティを削除します object/:class/:id オブジェクトを取得します object/:class/:id/:property オブジェクトの1つのプロパティを取得します object/:class オブジェクトを作成します object/:class/:id 動的オブジェクトからオブジェクトを更新します object/:class オブジェクトからオブジェクトを更新します object/:class/:id オブジェクトを削除します objects/:class/:query (SQL)クエリでクラスのオブジェクトを取得します objects/:class/custom/:query (SQL)カスタムクエリでクラスのオブジェクトを取得します **RESTFormsを使い始めるには?** 1. GitHubからプロジェクトをインポートします(お勧めの方法は独自リポジトリにサブモジュールとして追加する方法ですが、単にリリースをダウンロードしても良いです)。 2. RESTFormsを介して公開したい各クラスについて以下を実施します。 * アダプタクラスから継承する * 権限を指定します(一部のクラスを読み取り専用として公開する場合などに実施)。 * オブジェクトの表示値として使用されるプロパティを指定します。 * 表示したいプロパティの表示名を指定します。 **セットアップ** 1. [リリースページ](https://github.com/intersystems-ru/RESTForms/releases/tag/v1.0)で最新リリースである20161.xml( Caché 2016.1用)または201162.xml(Caché 2016.2以降用)をダウンロードして任意のネームスペースにインポートします。 2. 新しいWebアプリケーション /forms をDispatchクラス Form.REST.Main を使用して作成します。 3. http://localhost:57772/forms/test?Debug をブラウザで開き、インストールを検証します({"Status": "OK"} が出力され、場合によってはパスワードの入力が求められます)。 4. テストデータが必要な場合は、次を呼び出します: ```do ##class(Form.Util.Init).populateTestForms()``` **例** 最初に、利用可能なクラスを知る必要があります。 この情報を取得するには、次を呼び出します。 http://localhost:57772/forms/form/info 次のような応答が返されます。 [ { "name":"Company", "class":"Form.Test.Company" }, { "name":"Person", "class":"Form.Test.Person" }, { "name":"Simple form", "class":"Form.Test.Simple" } ] 現在3つのサンプルクラス(RESTFormで提供)があります。Person(Form.Test.Personクラス)のメタデータを見てみましょう。 この情報を取得するには、次を呼び出します。 http://localhost:57772/forms/form/info/Form.Test.Person 次のように、クラスのメタデータが応答として返されます。 { "name":"Person", "class":"Form.Test.Person", "displayProperty":"name", "objpermissions":"CRUD", "fields":[ { "name":"name", "type":"%Library.String", "collection":"", "displayName":"Name", "required":0, "category":"datatype" }, { "name":"dob", "type":"%Library.Date", "collection":"", "displayName":"Date of Birth", "required":0, "category":"datatype" }, { "name":"ts", "type":"%Library.TimeStamp", "collection":"", "displayName":"Timestamp", "required":0, "category":"datatype" }, { "name":"num", "type":"%Library.Numeric", "collection":"", "displayName":"Number", "required":0, "category":"datatype" }, { "name":"аge", "type":"%Library.Integer", "collection":"", "displayName":"Age", "required":0, "category":"datatype" }, { "name":"relative", "type":"Form.Test.Person", "collection":"", "displayName":"Relative", "required":0, "category":"form" }, { "name":"Home", "type":"Form.Test.Address", "collection":"", "displayName":"House", "required":0, "category":"serial" }, { "name":"company", "type":"Form.Test.Company", "collection":"", "displayName":"Company", "required":0, "category":"form" } ] } これらの情報は次のような意味を持ちます。 クラスのメタデータ: * name - クラスの表示名。 * class - 基本となる永続クラス。 * displayProperty - オブジェクトを表示するときに使用するオブジェクトのプロパティ。 * objpermissions - ユーザーがオブジェクトを使用して実行できる操作。 この例では、ユーザーは新しいオブジェクトを作成し、既存のオブジェクトを変更し、既存のオブジェクトを削除し、次を取得できます。 プロパティのメタデータ: * name - プロパティ名 - クラスの定義と同じです。 type - プロパティのクラス。 * コレクション - リスト/配列のコレクションです。 * displayName - 表示プロパティ名。 * required - このプロパティが必須であるかどうか。 * category - プロパティのタイプクラスのカテゴリ。 RESTForms対応のすべてのクラスが「form」として表示されることを除き、通常のCachéクラスのカテゴリに従います。 クラス定義では次のようになります。 /// テストフォーム: Person Class Form.Test.Person Extends (%Persistent, Form.Adaptor, %Populate) { /// フォーム名。グローバルキーではないため、何でもかまいません。 /// クラスをフォームとして持たないようにするには(ここのように)空の文字列に設定します。 Parameter FORMNAME = "Person"; /// デフォルトの権限 /// このフォームのオブジェクトは、作成、読み取り、更新、削除できます。 /// すべてのユーザーの権限を変更するには、このパラメーターを再定義します。 /// このクラスのcheckPermissionメソッドを再定義します(Form.Securityを参照してください)。 /// ユーザーやロールなどに基づいて独自のセキュリティを追加します。 Parameter OBJPERMISSIONS As %String = "CRUD"; /// オブジェクトの基本情報に使用されるプロパティ /// デフォルトでは、getObjectDisplayNameメソッドはここから値を取得します。 Parameter DISPLAYPROPERTY As %String = "name"; /// このパラメーターの値をSQLでORDER BY句の値として使用します。 Parameter FORMORDERBY As %String = "dob"; /// Personの名前。 Property name As %String(COLLATION = "TRUNCATE(250)", DISPLAYNAME = "Name", MAXLEN = 2000); /// Personの生年月日。 Property dob As %Date(DISPLAYNAME = "Date of Birth", POPSPEC = "Date()"); Property ts As %TimeStamp(DISPLAYNAME = "Timestamp") [ InitialExpression = {$ZDATETIME($ZTIMESTAMP, 3, 1, 3)} ]; Property num As %Numeric(DISPLAYNAME = "Number") [ InitialExpression = "2.15" ]; /// Personの年齢。<br> /// これは、 <property>DOB</property> から派生した値を持つ計算されたフィールドです。 Property аge As %Integer(DISPLAYNAME = "Age") [ Calculated, SqlComputeCode = { set {*}=##class(Form.Test.Person).currentAge({dob})}, SqlComputed, SqlComputeOnChange = dob ]; /// このクラスメソッドは、誕生日 <var>date</var> が与えられた場合に現在の年齢を計算します。 ClassMethod currentAge(date As %Date = "") As %Integer [ CodeMode = expression ] { $Select(date="":"",1:($ZD($H,8)-$ZD(date,8)\10000)) } /// Personの配偶者。 /// これは別の永続オブジェクトへの参照です。 Property relative As Form.Test.Person(DISPLAYNAME = "Relative"); /// Personの自宅住所。 埋め込みオブジェクトを使用します。 Property Home As Form.Test.Address(DISPLAYNAME = "House"); /// このPersonが働いている会社。 Relationship company As Form.Test.Company(DISPLAYNAME = "Company") [ Cardinality = one, Inverse = employees ]; } **クラスでRESTFormsを有効にする** そして、このクラスでRESTFormsを有効にするため、通常の永続クラスから始めて次のことを行いました。 1. Form.Adaptor から拡張しました。 2. 値を含むパラメーター FORMNAME(クラス名)を追加しました。 3. OBJPERMISSIONS パラメーター(すべての権限のCRUD)を追加しました。 4. DISPLAYPROPERTY パラメーター(オブジェクト名の表示に使用されるプロパティ名)を追加しました。 5. FORMORDERBY パラメーター(RESTFormsを使用するクエリでソートするデフォルトのプロパティ)を追加しました。 6. メタデータで確認したいプロパティごとに DISPLAYNAME プロパティのパラメーターを追加しました。 以上です。 コンパイル後、RESTFormsを含むクラスを使用できるようになります。 いくつかのテストデータを生成しましたので(インストールのステップ4を参照)、IDが1のPersonを取得してみましょう。 オブジェクトを取得するには、次を呼び出します。 http://localhost:57772/forms/form/object/Form.Test.Person/1 その応答は以下のとおりです(生成されるデータは異なる場合があります)。 { "_class":"Form.Test.Person", "_id":1, "name":"Klingman,Rhonda H.", "dob":"1996-10-18", "ts":"2016-09-20T10:51:31.375Z", "num":2.15, "аge":20, "relative":null, "Home":{ "_class":"Form.Test.Address", "House":430, "Street":"5337 Second Place", "City":"Jackson" }, "company":{ "_class":"Form.Test.Company", "_id":60, "name":"XenaSys.com", "employees":[ null ] } } オブジェクト(具体的にはnumプロパティ)を変更するには、次を呼び出します。 PUT http://localhost:57772/forms/form/object/Form.Test.Person このボディを使用します。 { "_class":"Form.Test.Person", "_id":1, "num":3.15 } 速度を上げるには、_class、_id、および変更対象のプロパティのみをリクエストのボディに含める必要があります。 では、新しいオブジェクトを作成しましょう。 以下を呼び出します。 POST http://localhost:57772/forms/form/object/Form.Test.Person このボディを使用します。 { "_class":"Form.Test.Person", "name":"Test person", "dob":"2000-01-18", "ts":"2016-09-20T10:51:31.375Z", "num":2.15, "company":{ "_class":"Form.Test.Company", "_id":1 } } オブジェクトの作成が成功した場合、RESTFormsは以下のようにIDを返します。 {"Id": "101"} 成功しなかった場合、エラーがJSON形式で返されます。 すべての永続オブジェクトのプロパティは、 _class および _id プロパティによってのみ参照する必要があります。 そして最後に、新しいオブジェクトを削除しましょう。 以下を呼び出します。 DELETE http://localhost:57772/forms/form/object/Form.Test.Person/101 これがForm.Test.Personクラスに対する完全なCRUDです。 **デモ** [現在デモ環境はお試しいただくことができません。] こちらでRESTFormsをオンラインで試すことができます(ユーザー名:Demo、パスワード:Demo)。 また、RESTFormsUIアプリケーション(RESTFormsデータエディタ)もあります。こちらをご確認ください(ユーザー名:Demo、パスワード:Demo)。 クラスリストのスクリーンショットを以下に掲載しています。 ![](/sites/default/files/inline/images/risunok1.png) **まとめ** RESTFormsは永続クラスに関する、REST APIから要求されるほとんどの機能を提供します。 **次の内容** この記事では、RESTFormsの機能について説明しました。 次回の記事では、いくつかの高度な機能(クライアントからSQLインジェクションのリスクを冒さずにデータの一部を安全に取得できるクエリなど)についてお話ししたいと思います。 [この記事のパート2でクエリに関する情報をお読みください](https://community.intersystems.com/post/restforms-rest-api-your-classes-part-2-queries)。 RESTFormsUI(RESTFormsデータエディタ)もあります。 **リンク** * [RESTForms GitHubリポジトリ](https://github.com/intersystems-ru/RESTForms/) * [RESTForms UI GitHubリポジトリ](https://github.com/intersystems-ru/RESTFormsUI/) * [パート2:クエリ](https://community.intersystems.com/post/restforms-rest-api-your-classes-part-2-queries)
記事
Takao Otokita · 2024年5月12日

IRIS BI開発者向けチュートリアルを試してみる(9)

はじめに IRIS BIチュートリアル試してみたシリーズの9回目です。今回は、チュートリアル全6ページのうちの5ページ目、「サブジェクト領域の作成」について試していきます。「サブジェクト領域」という新しい概念が出てきますが、どんなものかを触りながら理解していきます。では、早速はじめていきましょう。 サブジェクト領域の作成 最初に、サブジェクト領域とは何かを理解するところから始めましょう。チュートリアルでは以下のような説明があります。”サブジェクト領域は、オプションによる項目名のオーバーライドを持つサブキューブです。サブジェクト領域を定義すると、セキュリティ上の理由およびその他の理由により、より小さなデータ・セットに焦点を当てることができます。”うーん、分かったような、分からないような。。実例を基に説明してみます。6回目の記事で郵便番号と市区町村の階層を作成しました。郵便番号→市区町村という階層構造になっていましたよね。以下の図が、郵便番号と市区町村の関連を表したものです。 現状、チュートリアルで使用しているキューブには上記5つの郵便番号を持つデータが格納されています。これを、特定の郵便番号のデータだけを扱えるサブセットに分割したい要件があったとします。例えば配達員の担当地域ごとに分割するとか。そのような場合に用いられるのがサブジェクト領域です。チュートリアルでは、以下の2つのサブジェクト領域を作成します。 サブジェクト領域名 コンテンツ Patient Set A 郵便番号 32006、32007、または 36711 に居住する患者 Patient Set B 郵便番号 34577、または 38928 に居住する患者 では、実際に作成していきます。作業はアーキテクト画面から行いますので、アーキテクト画面を開きます。画面上部の [新規] ボタンをクリックし、新規作成のダイアログを表示します。表示されたダイアログに、以下のように設定します。 定義タイプ:サブジェクト領域 サブジェクト領域の名前:Patient Set A ベースキューブ:Tutorial サブジェクト領域のクラス名:Tutorial.SubjectA ベースキューブは [参照] ボタンから選択することもできます。 [OK] で終了すると、以下のような画面が表示されます。キューブ作成と同様に、ここに設定を施していくことになります。ではここに、郵便番号のフィルタ条件を追加していきます。それに必要な情報を取得するために、別ウィンドウでアナライザ画面を立ち上げ、Tutorialキューブを開きます。HomeD ディメンジョン配下の ZIP Code レベルを [フィルタ] にドラッグ&ドロップします。こんな画面になります。ピボット・プレビュー領域のすぐ上に表示された ZIP Code の虫眼鏡アイコンをクリックします。Patient Set Aの条件である、郵便番号=32006、32007、36711をチェックし、✓をクリックして終了します。フィルタが適用され、件数が変わりました。※注意:サンプルデータはランダムに作成されるため、こちらの画面表示とみなさまの実行結果は一致しないことがあります。この状態で [クエリ表示] アイコンをクリックします。ピボット・ビルダ領域の [列] の上あたりにあるアイコンです。 MDXクエリが表示されます。 MDXクエリって何??という方もいらっしゃるかもしれません。これはOLAPデータベースに対する標準クエリ言語で、RDBにおけるSQLのようなものです。このクエリ構文から、%FILTER より後ろにある条件の部分をコピーしておきます。コピーしたら、ダイアログは [OK] で閉じます。 %OR({[HOMED].[H1].[ZIP CODE].&[32006],[HOMED].[H1].[ZIP CODE].&[32007],[HOMED].[H1].[ZIP CODE].&[36711]}) では、再びアーキテクト画面に戻り、Patient Set Aの行をクリックして選択し、詳細ペインの [フィルタ] にコピーしたフィルタ式を貼り付けます。 貼り付けたら [保存] ボタンをクリックして、サブジェクト領域を保存します。以下のダイアログで [OK] をクリックします。 保存が完了したら、続いてコンパイルします。 これで Patient Set A については作成できました。同様にPatient Set Bも作成します。先ほどとの相違点のみ記載します。作成のダイアログで、名前を Patient Set B 、クラス名を Tutorial.SubjectB とします。 設定するフィルタ条件は、郵便番号=34577、38928 の2つになります。 %OR({[HOMED].[H1].[ZIP Code].&[34577],[HOMED].[H1].[ZIP Code].&[38928]}) 保存、コンパイルまで完了したら、これで2つのサブジェクト領域は完成です。思ったよりもシンプルな作業ですね。 サブジェクト領域の検証 では、作成したサブジェクト領域が意図した通りに動作するかを確認します。アナライザ画面で キューブの変更 アイコンをクリックします。作成した2つのサブジェクト領域が表示されますね。Patient Set A を選択して開きます。 [行] に ZIP Code をドラッグ&ドロップします。フィルタ設定した3つの郵便番号のみが表示されます。 同様に Patient Set B についても確認します。こちらもフィルタ設定した2つの郵便番号のみが表示されます。 一般的なフィルタ式 もう少しMDXクエリについて学んでみたいと思います。アナライザ画面で Tutorial キューブを開きます。何も条件を設定しない状態で、先ほどの [クエリ表示] アイコンをクリックします。 Tutorial キューブが選択されていて、フィルタ条件などは一切無い状態です。ではここに、ColorD ディメンジョン配下 Favorite Color レベルから Orange メンバを [フィルタ] にドラッグ&ドロップします。カウントが変わりましたね。この状態で [クエリ表示] アイコンをクリックします。 フィルタ条件の構文、 %FILTER [ColorD].[H1].[Favorite Color].&[Orange] が追加されました。では、いま設定したフィルタ条件を × で削除し、今度は Favorite Color レベルを [フィルタ] にセットします。その後にフィルタ・ボックスから Orange と Purple の2つを選択します。 この状態でクエリ表示をしてみます。Orange だけの場合と異なり、 %OR で Orange と Purple の条件が囲まれています。 では次に、Favorite Color フィルタを Orange だけに設定し、AllerD ディメンジョン配下の Allergies レベルから mold メンバを [フィルタ] に追加します。以下のような設定になります。この状態で [クエリ表示] アイコンをクリックします。 今度は2つの条件の組み合わせ(AND条件)になりますので、NONEMPTYCROSSJOIN という関数でフィルタ指定した2つのメンバが囲まれています。ここでは、フィルタ条件を中心にMDXクエリについて確認しました。MDXについて詳しいことが知りたい場合は、オンラインドキュメントの以下のページを参考にしてください。 InterSystems MDX の使用法 InterSystems MDX リファレンス おわりに 今回はサブジェクト領域の作成を行いながら、その後ろ側で使用されているMDXクエリについて簡単に学びました。部門ごとに参照可能なデータを制限したい、などデータを特定の条件でフィルタして公開したいというユースケースはよくあるかと思います。そのような場合に別々のキューブを作らずに対応できるサブジェクト領域は有用であると考えます。次回からは、いよいよ最終ページ「ピボット・テーブルおよびダッシュボードの作成とパッケージ化」について取り上げます。お楽しみに!
記事
Toshihiko Minamoto · 2024年6月26日

ベクトル化の(ほぼ)サポート

近頃、LLM や AI などに関する話題で非常ににぎわっています。 ベクトルデータベースもそれなりに関わっており、IRIS 以外では、世界中で多様なサポートがすでに実現されています。 なぜベクトルなのでしょうか? 類似検索: ベクトルでは、データベース内で最も類似する項目やドキュメントを検索するなど、効率的な類似検索が可能です。 従来のリレーショナルデータベースは完全一致検索向けに設計されているため、画像やテキストの類似検索といったタスクには向いていません。 柔軟性: ベクトル表現には汎用性があり、テキスト(Word2Vec、BERT などの埋め込み経由)や画像(ディープラーニングモデル経由)などの様々なデータタイプから導き出すことができます。 クロスモーダル検索: ベクトルでは、様々なデータモダリティでの検索が可能です。 たとえば、画像のベクトル表現を基に、マルチモーダルデータベースで類似する画像や関連するテキストを検索できます。 理由は他にも多数あります。 そこで、この Python コンテストでは、このサポートを実装してみることにしました。 残念ながら時間内に完成させることはできませんでしたが、その理由を以下で説明します。 完全にするには、やらなければならない主な項目がいくつかあります。 ベクトル化データを SQL で受け入れて格納する。以下は単純な例です(この例の「3」は次元数であり、フィールドごとに固定されており、そのフィールドのすべてのベクトルにこの正確な次元が必要です)。 create table items(embedding vector(3)); insert into items (embedding) values ('[1,2,3]'); insert into items (embedding) values ('[4,5,6]'); 類似関数。類似には様々なアルゴリズムがあり、インデックスを使用しない少量のデータでの単純な検索に適しています。 -- ユークリッド距離 select embedding, vector.l2_distance(embedding, '[9,8,7]') distance from items order by distance; -- コサイン類似度 select embedding, vector.cosine_distance(embedding, '[9,8,7]') distance from items order by distance; -- 内積 select embedding, -vector.inner_product(embedding, '[9,8,7]') distance from items order by distance; カスタムインデックス。大量のデータでのより高速な検索に役立ちます。インデックスには異なるアルゴリズム、上記の異なる距離関数、およびその他のオプションを使用できます。 HNSW 転置ファイルインデックス 検索は作成されたインデックスを使用し、そのアルゴリズムによってリクエストされた情報が検索されます。 ベクトルの挿入 ベクトルは、整数や浮動小数点数のほかに、符号付きや符号なしの数値の配列であることが期待されています。 IRIS では、それを単に $listbuild として格納できます。これには最適な表現があり、すでにサポートされているため、ODBC から論理への変換のみを実装する必要があります。 すると、ODBC/JDBC などの外部ドライバーを使って、または IRIS 内では ObjectScript を使って、プレーンテキストとして値を挿入できます。 プレーンな SQL を使用 insert into items (embedding) values ('[1,2,3]'); ObjectScript を使用 set rs = ##class(%SQL.Statement).%ExecDirect(, "insert into test.items (embedding) values ('[1,2,3]')") set rs = ##class(%SQL.Statement).%ExecDirect(, "insert into test.items (embedding) values (?)", $listbuild(2,3,4)) または埋め込み SQL を使用 &sql(insert into test.items (embedding) values ('[1,2,3]')) set val = $listbuild(2,3,4) &sql(insert into test.items (embedding) values (:val)) 必ず $lb() として格納され、ODBC でテキスト形式で戻されます。 予期しない動作 DBeaver を使ってテストした際に、接続後の最初の行は正しく挿入されても、他の行は検証や変換が行われずにそのまま挿入されているのが判明しました。 その後、JDBC はデフォルトで高速挿入を使用しており、その場合、挿入されたデータを直接 globals に格納することがわかったため、その機能を手動でオフにする必要がありました。 DBeaver では、FeatureOption フィールドの optfastSelect を選択してください。 計算 ベクトルは主に、2 つのベクトルの間の距離の計算をサポートするために必要です。 コンテストにおいては、Embedded Python を使用する必要がありました。問題はここからです。Embedded Python で $lb をどのように操作するのか。%SYS.Class には ToList メソッドがありますが、Python パッケージは iris にビルトインされていないため、ObjectScript のやり方が必要となります。 ClassMethod l2DistancePy(v1 As dc.vector.type, v2 As dc.vector.type) As %Decimal(SCALE=10) [ Language = python, SqlName = l2_distance_py, SqlProc ] { import iris import math vector_type = iris.cls('dc.vector.type') v1 = iris.cls('%SYS.Python').ToList(vector_type.Normalize(v1)) v2 = iris.cls('%SYS.Python').ToList(vector_type.Normalize(v2)) return math.sqrt(sum([(val1 - val2) ** 2 for val1, val2 in zip(v1, v2)])) } まったく正しいようには見えません。 $lb は Python でその場でリストとして、または少なくともビルトイン関数の o_list と from_list として解釈されるのが好ましいと思います。 もう 1 つの問題は、この関数を様々な方法でテストしようとしたときにありました。 Embedded Python で記述された SQL を 使用する SQL を Embedded Python から使用すると、クラッシュします。 そこで、ObjectScript の関数も追加する必要がありました。 ModuleNotFoundError: No module named 'dc' SQL Function VECTOR.NORM_PY failed with error: SQLCODE=-400,%msg=ERROR #5002: ObjectScript error: <OBJECT DISPATCH>%0AmBm3l0tudf^%sqlcq.USER.cls37.1 *python object not found 距離を計算するために現在実装されている関数(Python と ObjectScript の両方) ユークリッド距離 [SQL]_system@localhost:USER> select embedding, vector.l2_distance_py(embedding, '[9,8,7]') distance from items order by distance; +-----------+----------------------+ | embedding | distance | +-----------+----------------------+ | [4,5,6] | 5.91607978309961613 | | [1,2,3] | 10.77032961426900748 | +-----------+----------------------+ 2 rows in set Time: 0.011s [SQL]_system@localhost:USER> select embedding, vector.l2_distance(embedding, '[9,8,7]') distance from items order by distance; +-----------+----------------------+ | embedding | distance | +-----------+----------------------+ | [4,5,6] | 5.916079783099616045 | | [1,2,3] | 10.77032961426900807 | +-----------+----------------------+ 2 rows in set Time: 0.012s コサイン類似度 [SQL]_system@localhost:USER> select embedding, vector.cosine_distance(embedding, '[9,8,7]') distance from items order by distance; +-----------+---------------------+ | embedding | distance | +-----------+---------------------+ | [4,5,6] | .034536677566264152 | | [1,2,3] | .11734101007866331 | +-----------+---------------------+ 2 rows in set Time: 0.034s [SQL]_system@localhost:USER> select embedding, vector.cosine_distance_py(embedding, '[9,8,7]') distance from items order by distance; +-----------+-----------------------+ | embedding | distance | +-----------+-----------------------+ | [4,5,6] | .03453667756626421781 | | [1,2,3] | .1173410100786632659 | +-----------+-----------------------+ 2 rows in set Time: 0.025s 内積 [SQL]_system@localhost:USER> select embedding, vector.inner_product_py(embedding, '[9,8,7]') distance from items order by distance; +-----------+----------+ | embedding | distance | +-----------+----------+ | [1,2,3] | 46 | | [4,5,6] | 118 | +-----------+----------+ 2 rows in set Time: 0.035s [SQL]_system@localhost:USER> select embedding, vector.inner_product(embedding, '[9,8,7]') distance from items order by distance; +-----------+----------+ | embedding | distance | +-----------+----------+ | [1,2,3] | 46 | | [4,5,6] | 118 | +-----------+----------+ 2 rows in set Time: 0.032s 数学関数(加減乗除)を追加で実装しました。 InterSystems は独自の集計関数の作成をサポートしているため、 すべてのベクトルを合計したり平均を求めることが可能かもしれません。 しかし、残念ながら、InterSystems では同一の名前の使用をサポートしていないため、関数に独自の名前(およびスキーマ)を使用する必要があります。 ただし、集計関数では数値以外の結果はサポートされていません。 2 つのベクトルの合計を返す単純な vector_add 関数 集計として使用すると、0 を示し、期待されるベクトルも同様に表示されます。 インデックスの作成 残念ながら、実装中に直面した障害により、この部分は完成させられませんでした。 IRIS のベクトルが $lb に格納されている場合のビルトイン $lb から Python リストへの変換とその逆変換が不足しており、インデックス作成のすべてのロジックが Python で書かれていることが期待されるため、$lb からデータを取得して globals にも設定することが重要です。 globals のサポートの欠如 IRIS の $Order は方向をサポートしているため、逆方向でも使用可能ですが、Python Embedded での順序の実装にはこれが存在しないため、すべてのキーを読み取って順序を逆にするか、最後をどこかに格納する必要があります。 上記の Python から呼び出される Python の SQL 関数がうまくいかないため、疑問を感じている インデックス作成中、ベクトル間の距離がグラフに格納されることが期待されていたのに、global で浮動小数点数に関するバグが発生した この作業中に見つかった Embedded Python 関連の課題を 11 件作成しました。ほとんどの時間は問題を解決するための回避策を見つけるのに費されました。 いくつかの問題は、@Guillaume.Rongier7183 の iris-dollar-list というプロジェクトのお陰でなんとか解決できました。 インストール いずれにせよ、これは引き続き提供中であり、IPM でインストールし、機能が制限されていても使用できます。 zpm "install vector" または開発モードでは docker-compose を使用できます。 git clone https://github.com/caretdev/iris-vector.git cd iris-vector docker-compose up -d
記事
Toshihiko Minamoto · 2021年10月19日

%Statusを使ったデバッグのヒント

## はじめに ObjectScriptで複雑な問題を解決している場合、おそらく%Status値を使用したコードがたくさんあることでしょう。 オブジェクトの観点(%Save、%OpenIdなど)から永続クラスを操作したことがある場合は、ほぼ確実にその状況に遭遇したことがあるでしょう。 %StatusはInterSystemsのプラットフォームでローカライズ可能なエラーメッセージのラッパーを提供します。 OKステータス($$$OK)は1に等しいだけであるのに対し、不良ステータス($$$ERROR(errorcode,arguments...))は0、スペース、エラーに関する構造化情報を含む$ListBuildリストとして表されます。 [$System.Status(クラスリファレンスを参照)](https://docs.intersystems.com/irislatest/csp/documatic/%25CSP.Documatic.cls?PAGE=CLASS&LIBRARY=%25SYS&CLASSNAME=%25SYSTEM.Status)は、%Status値を操作するための便利なAPIをいくつか提供しています。クラスリファレンスを役立てられるので、ここでは繰り返しません。 このトピックに関する有用な記事/質問もほかにいくつかあります(最後のリンクをご覧ください)。 この記事では、コーディングのベストプラクティスではなく、いくつかのデバッグのコツや手法に焦点を当てています(ベストプラクティスについては、最後のリンクをご覧ください)。 ## お題のコード例 注意: このようなコードは絶対に書かないようにしてください! 常にステータスをチェックし、それを返すか、例外としてスロー($$$ThrowStatus(someErrorStatus) など)すれば、デバッグがはるかに簡単になります。 Class DC.Demo.MaskedErrorStatus Extends %Persistent { Property Answer As %TinyInt; ClassMethod Run() As %Status {     Set instance = ..%New()     Set instance.Answer = 9000     Do instance.%Save()          Set instance = ..%OpenId(1,,.sc)     Set instance.Answer = 42     Do instance.%Save()          Quit $$$OK } } ターミナルから実行すると、例外がスローされます。何かが明らかにうまく行っていません。 USER>d ##class(DC.Demo.MaskedErrorStatus).Run()    Set instance.Answer = 42  ^ <INVALID OREF>zRun+5^DC.Demo.MaskedErrorStatus.1 ## %Statusのデバッグのコツ#1: $System.OBJ.DisplayError() 常に $System.OBJ.DisplayError() を実行して、作成された最後のエラーステータスを出力できます。 これが機能するのは、($System.Status.Error 経由で)エラーステータスが作成されるたびに、変数 %objlasterror にそのステータスが設定されるためです。 また、同様に%objlasterrorをzwriteすることもできます。 上記の場合は、次のようになります。 USER 2d1>d $system.OBJ.DisplayError() ERROR #5809: Object to Load not found, class 'DC.Demo.MaskedErrorStatus', ID '1' ## %Statusのデバッグのコツ#2: スタックトレース %Statusごとに、エラーが作成された場所のスタックトレースがあります。 ステータスをzwriteすることで、そのトレースを閲覧することができます。 USER 2d1>zw %objlasterror %objlasterror="0"_$lb($lb(5809,"DC.Demo.MaskedErrorStatus","1",,,,,,,$lb(,"USER",$lb("e^%LoadData+18^DC.Demo.MaskedErrorStatus.1^1","e^%Open+16^%Library.Persistent.1^1","e^%OpenId+1^%Library.Persistent.1^1","e^zRun+4^DC.Demo.MaskedErrorStatus.1^1","d^^^0"))))/* ERROR #5809: Object to Load not found, class 'DC.Demo.MaskedErrorStatus', ID '1' */ ステータスごとに、より分かりやすいテキスト($System.OBJ.DisplayError()または$System.Status.GetErrorText(someStatus) )でスタックトレースを確認したいですか? これは、^%oddENV("callererrorinfo",$namespace)=1 または 2 に設定することで実現できます。 以下に、その効果を示します。 USER>set ^%oddENV("callererrorinfo",$namespace)=1 USER>d $system.OBJ.DisplayError() ERROR #5809: Object to Load not found, class 'DC.Demo.MaskedErrorStatus', ID '1' [%LoadData+18^DC.Demo.MaskedErrorStatus.1:USER] USER>set ^%oddENV("callererrorinfo",$namespace)=2 USER>d $system.OBJ.DisplayError() ERROR #5809: Object to Load not found, class 'DC.Demo.MaskedErrorStatus', ID '1' [e^%LoadData+18^DC.Demo.MaskedErrorStatus.1^1 e^%Open+16^%Library.Persistent.1^1 e^%OpenId+1^%Library.Persistent.1^1 e^zRun+4^DC.Demo.MaskedErrorStatus.1^1 d^^^0:USER] USER>k ^%oddENV("callererrorinfo",$namespace) USER>d $system.OBJ.DisplayError() ERROR #5809: Object to Load not found, class 'DC.Demo.MaskedErrorStatus', ID '1' これは実際には、開発環境のみで使用するのが適切であることに注意してください。ユーザーにはコードの内部を見られたくないはずです。 (ユーザーに直接%Status値を表示することを避けて、よりユーザーフレンドリーなアプリケーション固有のエラーメッセージを表示するのが最善ですが、これについては、別のトピックとしましょう。) ## %Statusのデバッグのコツ#3: 極上の zbreak この辺からトリッキーになってきます。このコードスニペットの場合では、根本的な原因は、前述のコードスニペットの%Save() から%Statusが確認されていないことにあります。 何がうまく行かなかったのかを見つけるのが非常に難しい、はるかに複雑な例を想像するのは簡単です。特に、プラットフォームコードのずっと後の方で発生しているエラーであれば、尚更です。 これに対処するためにインタラクティブデバッガに飛びつく以外で私が好んで使用してる方法は、ターミナルで非常に極上の[zbreak](https://cedocs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KEY=TCOS_ZBreak)コマンドを使用することです。 USER>zbreak *%objlasterror:"N":"$d(%objlasterror)#2":"set ^mtemptl($i(^mtemptl))=%objlasterror" これはどういう意味でしょうか? zbreak <%objlasterrorが変更される時点>:<デバッガでは何もしない>:<%objlasterrorが定義されており値がある限り(定義済みから未定義にならない場合など)>:<整数がサブスクリプトのジャーナルされていないグローバル(mtempから始めることで、トランザクション中に%Statusが作成され、ログを見るより先にロールバックされてしまうの防ぐため。また、コミットされたコードやデータベースで誰かがこれを見つけた時に私に連絡できるように、グローバルの一部に私のイニシャルを使っています)で、次のサブスクリプトのグローバルにエラーステータスをセットする処理を実行> zbreak に関する補足: 現在定義されているブレークポイント/ウォッチポイントは、引数を指定せずに 'zbreak' を実行することで確認できます。また、ブレークポイントの使用が終わったら、break "off" などを実行して、これらのブレークポイントをオフにできますし、する必要があります。以下に例を示します。 USER>zbreak BREAK: No breakpoints %objlasterror F:E S:0 C:"$d(%objlasterror)#2" E:"set ^mtemptl($i(^mtemptl))=%objlasterror" USER>break "off" USER>zbreak BREAK: No breakpoints No watchpoints では、問題のあるメソッドがウォッチポイントを設定して実行されるとどうなるでしょうか? USER>zbreak *%objlasterror:"N":"$d(%objlasterror)#2":"set ^mtemptl($i(^mtemptl))=%objlasterror" USER>d ##class(DC.Demo.MaskedErrorStatus).Run() Set instance.Answer = 42 ^ <INVALID OREF>zRun+5^DC.Demo.MaskedErrorStatus.1 USER 2d1>zw ^mtemptl ^mtemptl=6 ^mtemptl(1)="0 "_$lb($lb(7203,9000,127,,,,,,,$lb(,"USER",$lb("e^zAnswerIsValid+1^DC.Demo.MaskedErrorStatus.1^1","e^%ValidateObject+3^DC.Demo.MaskedErrorStatus.1^4","e^%SerializeObject+3^%Library.Persistent.1^1","e^%Save+4^%Library.Persistent.1^2","d^zRun+3^DC.Demo.MaskedErrorStatus.1^1","d^^^0"))))/* ERROR #7203: Datatype value '9000' greater than MAXVAL allowed of 127 */ ^mtemptl(2)="0 "_$lb($lb(7203,9000,127,,,,,,,$lb(,"USER",$lb("e^zAnswerIsValid+1^DC.Demo.MaskedErrorStatus.1^1","e^%ValidateObject+3^DC.Demo.MaskedErrorStatus.1^4","e^%SerializeObject+3^%Library.Persistent.1^1","e^%Save+4^%Library.Persistent.1^2","d^zRun+3^DC.Demo.MaskedErrorStatus.1^1","d^^^0")),"0 "_$lb($lb(5802,"DC.Demo.MaskedErrorStatus:Answer",9000,,,,,,,$lb(,"USER",$lb("e^EmbedErr+1^%occSystem^1"))))))/* ERROR #7203: Datatype value '9000' greater than MAXVAL allowed of 127- > ERROR #5802: Datatype validation failed on property 'DC.Demo.MaskedErrorStatus:Answer', with value equal to "9000" */ ^mtemptl(3)="0 "_$lb($lb(7203,9000,127,,,,,,,$lb("zAnswerIsValid+1^DC.Demo.MaskedErrorStatus.1","USER",$lb("e^zAnswerIsValid+1^DC.Demo.MaskedErrorStatus.1^1","e^%ValidateObject+3^DC.Demo.MaskedErrorStatus.1^4","e^%SerializeObject+3^%Library.Persistent.1^1","e^%Save+4^%Library.Persistent.1^2","d^zRun+3^DC.Demo.MaskedErrorStatus.1^1","d^^^0"))))/* ERROR #7203: Datatype value '9000' greater than MAXVAL allowed of 127 */ ^mtemptl(4)="0 "_$lb($lb(5802,"DC.Demo.MaskedErrorStatus:Answer",9000,,,,,,,$lb("EmbedErr+1^%occSystem","USER",$lb("e^EmbedErr+1^%occSystem^1"))))/* ERROR #5802: Datatype validation failed on property 'DC.Demo.MaskedErrorStatus:Answer', with value equal to "9000" */ ^mtemptl(5)="0 "_$lb($lb(7203,9000,127,,,,,,,$lb("zAnswerIsValid+1^DC.Demo.MaskedErrorStatus.1","USER",$lb("e^zAnswerIsValid+1^DC.Demo.MaskedErrorStatus.1^1","e^%ValidateObject+3^DC.Demo.MaskedErrorStatus.1^4","e^%SerializeObject+3^%Library.Persistent.1^1","e^%Save+4^%Library.Persistent.1^2","d^zRun+3^DC.Demo.MaskedErrorStatus.1^1","d^^^0")),"0 "_$lb($lb(5802,"DC.Demo.MaskedErrorStatus:Answer",9000,,,,,,,$lb("EmbedErr+1^%occSystem","USER",$lb("e^EmbedErr+1^%occSystem^1"))))))/* ERROR #7203: Datatype value '9000' greater than MAXVAL allowed of 127- > ERROR #5802: Datatype validation failed on property 'DC.Demo.MaskedErrorStatus:Answer', with value equal to "9000" */ ^mtemptl(6)="0 "_$lb($lb(5809,"DC.Demo.MaskedErrorStatus","1",,,,,,,$lb(,"USER",$lb("e^%LoadData+18^DC.Demo.MaskedErrorStatus.1^1","e^%Open+16^%Library.Persistent.1^1","e^%OpenId+1^%Library.Persistent.1^1","e^zRun+4^DC.Demo.MaskedErrorStatus.1^1","d^^^0"))))/* ERROR #5809: Object to Load not found, class 'DC.Demo.MaskedErrorStatus', ID '1' */ 多少ノイズがありますが、重要な問題がすぐに現れます。 /\* ERROR #7203: Datatype value '9000' greater than MAXVAL allowed of 127 \*/ %TinyInt を使用しないことを想定すべきでした! (また、より重要なことは、呼び出すメソッドが返す%Status値を必ず確認する必要があります。) ## 関連資料 [私が気に入っている、エラー処理とレポートのコーディングパターン](https://community.intersystems.com/post/try-catch-block-i-usually-use-intersystems-objectscript#comment-7751) [Caché ObjectScriptメソッドの%Statusとその他の戻り値](https://community.intersystems.com/post/status-vs-other-return-values-cach%C3%A9-objectscript-methods) [%objlasterrorについて](https://community.intersystems.com/post/about-objlasterror) [$$$envCallerErrorInfoGetの設定方法](https://community.intersystems.com/post/how-set-envcallererrorinfoget-windows-get-location-information-within-exception#comment-95586) [ObjectScriptによるエラー処理スニペット](https://community.intersystems.com/post/objectscript-error-handling-snippets) [ZBREAKコマンド](https://cedocs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KEY=TCOS_ZBreak)
記事
Megumi Kakechi · 2020年9月16日

HTMLからRESTを使って画像ファイルをアップロードする方法

これはInterSystems FAQ サイトの記事です。 HTMLからRESTを使って画像ファイルをアップロードする方法をご紹介します。 1.はじめに、以下のようなhtmlとクラスを作成してください。 *UploadTest.html <html lang="ja"> <head> <title>Upload</title> </head> <body> <input id="up" type="file" /> <button id="btn">Upload</button> <div></div> <script type="text/javascript"> const sendfile = function(e) { let up = document.getElementById("up"); let file = up.files[0]; let fd = new FormData(); fd.append("imgfile", file); let xmlhttp = new XMLHttpRequest(); xmlhttp.onreadystatechange = function () { var result = document.querySelector('div'); xmlhttp.onload = function () { result.innerHTML = xmlhttp.responseText; }; }; xmlhttp.open("POST", "http://127.0.0.1:52773/csp/user/isjtest/uploadimg", true); xmlhttp.send(fd); } let btn = document.getElementById("btn"); btn.addEventListener("click", sendfile); </script> </body> </html> *User.MyREST.cls (IRIS/CachéサーバのUSERネームスペースに作成してください) ※こちらのサンプルでは、C:\tempフォルダにファイルをUploadしています。 適宜フォルダを作成して頂くか、任意のフォルダパスに変更して再コンパイルしてください。 Class User.MyREST Extends %CSP.REST { Parameter CONVERTINPUTSTREAM = 1; Parameter HandleCorsRequest = 1; XData UrlMap { <Routes> <Route Url="/uploadimg" Method="POST" Call="readMimeData" /> </Routes> } ClassMethod readMimeData() As %Status { set upload=$g(%request.MimeData("imgfile", 1)) set fname=%request.MimeData("imgfile",1).FileName set file=##class(%File).%New("c:\temp\"_fname) do file.Open("NWUK\BIN\") do file.CopyFrom(upload) set st = file.%Save() if st { write fname_" アップロード完了!!" } else { write fname_" アップロード失敗" } do file.Close() quit $$$OK } } 2. ウェブ・アプリケーション /csp/user/isjtest の定義を作成します。  管理ポータル:[システム管理]>[セキュリティ]>[アプリケーション]>  [ウェブ・アプリケーション]>[新しいウェブ・アプリケーションを作成]  RESTのディスパッチ・クラスに、1.で作成したUser.MyRESTクラスを指定します。(下記画像参照) 3. 必要に応じて UploadTest.html を編集し(※1)、Webサーバのドキュメントルート(※2)に配置します。  ※1. xmlhttp.openには、環境にあったIPアドレス・ポートを指定してお試しください。 xmlhttp.open("POST", "http://<サーバIP>:<IRIS/Cachéポート>/csp/user/isjtest/uploadimg", true);   ※2. 例:C:\inetpub\wwwroot 4. クライアントブラウザより以下を実行し、任意のファイルをUploadします。  http://localhost/UploadTest.html 5. 指定したフォルダにファイルがUploadされたことをご確認ください。 この投稿を参考にさせて頂き、初めてRESTでIRISへの接続をするプログラムを作成してみました。何となく思った通りに動作しそうな雰囲気です。アプリケーション開発環境での組み込みができていませんので、まだ何が起こるか不安がよぎっていますが、現時点では満足しています。本日は力尽きました... 私の場合、すでに構築済みの手法(フレームワーク、と呼べるほど大層なものではなりません)があり、その手法に合わせるために、敢えてJSONを利用していません。そのため、多くのRESTのサンプルコードがJSON前提ということもあり参考になるものが見当たらず、苦しみました。(自業自得、ですが..) ところで、上記のサンプルコード(UploadTest.html のJavaScript部分)についてです。 var result = document.querySelector('div');  のコードがありますが、「div」に対応するオブジェクト(タグ)が無いように思われます。その為?サーバーからのレスポンスが画面上に表示されないようです。ご確認頂ければ幸いです。 RESTでIRISへの接続をお試しいただきありがとうございます。また、サンプルコードへのご指摘ありがとうございます。確かに、タグが抜けておりました。大変失礼いたしました。サンプル(UploadTest.html)に足りないタグを追加しました。お手数をおかけしますが、ご確認をよろしくお願いいたします。 <button id="btn">Upload</button> <div></div> ←このタグを追加 ご確認とコードの改修、有難う御座いました。 重箱の隅をつつくような内容でしたがご対応頂き感謝いたします。
記事
Toshihiko Minamoto · 2024年1月11日

DeepSee で有効な階層を設計

DeepSee で階層を設計する場合、子メンバーに 1 つの親しか指定できません。 子が 2 つの親に対応する場合には、信頼性のない結果が得られることになります。 類似する 2 つのメンバーが存在する場合、そのキーがそれぞれ一意になるように変更する必要があります。 これが起きる場合とそれを回避する方法について、2 つの例を見ながら説明します。 例 1 (アメリカには)Boston と言う都市がある州がたくさんあります。 私のサンプルデータでは、Boston, MA(マサチューセッツ州ボストン)と Boston, NY(ニューヨーク州ボストン)のレコードがあります。 次元は次のように定義されています。 私の場合、City(都市)と State(州)は単純な文字列です。 キューブにビルドすると、"MA" と "NY" の2 つの州メンバー、"Boston" と "Boston" の 2 つの都市メンバーが得られます。 Boston が 1 つではなく 2 つあるのはなぜでしょうか。 メンバーには 2 つの親メンバーを指定できないため、親ごとに異なるメンバーを作成する必要があります。 残念ながら、1 つのキーが 2 つの異なるメンバーを持っているため、この時点で「不適切な階層」状態になっています。 これを修正するには、キーを一意にしなければなりません。 "City" プロパティをレベルのソースプロパティとして直接使用する代わりに、ソース式を使うとこのメンバーを一意にすることができます。 こうすることで問題は解決されますが、望ましくない副作用が発生する可能性があります。 この式では、ピボットテーブルで以下のような結果になります。 これは表示フォーマットとして適切な場合も、適切でない場合もあります。 この時点ではキーとメンバー名が同じであるため、もう少し手を加えれば "Boston" だけが表示されるようにできますが、その後ろに一意のキーがあります。 詳細は、ドキュメントをご覧ください。 まとめると、異なるメンバーには一意のキーが必要です。 特定のキーを持つ子メンバーに、同じキーを持つ既存の子メンバーとは異なる親メンバーがある場合、そのキーは再利用されますが、新しいメンバーが生成されます。 このため、階層が無効になります。 例 2 無効な階層は日付階層によく見られます。 自然と以下の階層を作成する傾向にあります:YearMonthWeekDay *** ここでは、具体的に DeepSee における Year、MonthYear、WeekYear、および DayMonthYear 抽出関数について言及しています。 知っての通り、Week は 2 つの月、さらには 2 つの年に含まれる場合があります。 ここでの他のすべてのレベル(Year、Month、Day)はどれも親に適合し、2 つの親に含まれることはありません。 このように Week メンバーで定義された階層がある場合、DeepSee エンジンがツリーをトラバースする方法(Jan 3 2020 から December 2019 の Week 52 2019 まで。 ただし Jan 3 2020 は December 2019 の子ではないため、エンジンはこれらの結果を削除する可能性があります)が原因で予期しない結果となる可能性があります。 一般的な解決策は、Week メンバーのみの階層を新たに作成することです。 こうすることで、元の階層の整合性を保持しながら、クエリ内で Week を使用することができます。 DeepSeeButtons on Open Exchange には、生成レポート内にこれらの条件をチェックして、階層が無効であるかを通知するセクションがあります。 *** InterSystems IRIS 2020.3 以降では、DeepSeeButtons は製品に含まれています。 追加情報は、ドキュメント をご覧ください***