検索

クリアフィルター
記事
Megumi Kakechi · 2021年2月15日

ルーチン・クラスメソッド内の特定処理でデータベースの特権を変更する方法

これは InterSystems FAQ サイトの記事です。 「特権ルーチンアプリケーション」を使用し、コード中に $system.Security.AddRoles()メソッドを使用してロールを付与する仕組みを利用します。 ※ロールベースで必要な特権を付与するため、予め特定の特権を持ったロールを作成する必要があります。 詳細は、以下ドキュメントをご参照ください。特権ルーチン・アプリケーション【IRIS】特権ルーチン・アプリケーションについて 例えば、特定ルーチン(またはクラスメソッド)実行時のみデータベースの更新を許可するための設定は、以下のとおりです。 (接続するデータベースに対してはREAD許可だけを持ち、あるルーチン実行時のみデータベースに対するREAD/WRITE許可を持つように設定します。) 1)  データベース:Aのリソース定義を確認する  データベース:Aのリソースに %DB_%DEFAULT が設定されている場合は、独自リソースを作成します。  (データベースに割り当てられたリソースの確認は、管理ポータル > [システム管理] > [構成] > [システム構成] > [ローカルデータベース] > リソース で確認できます)  リソース作成は、管理ポータル > [システム管理] > [セキュリティ] > [リソース] > 新規リソース作成ボタン押下 データベースリソースは命名規則があり、 %DB_データベース名 を指定します。  例の場合は、 %DB_A を登録します。このとき、パブリック許可は全てチェックを外します。  リソース作成が完了したら、管理ポータル > [システム管理] > [構成] > [システム構成] > [ローカルデータベース] から設定対象のデータベース設定を開き、リソース名を変更し、保存します。 2) READ許可のみを付与するロールを作成する  管理ポータル > [システム管理] > [セキュリティ] > [ロール] > 新規ロール作成ボタン押下  ロール名を記入し、保存します。(ロール名を R1 とします。)  作成したロールに特権(リソースに対する許可の組み合わせ)を登録するため、追加ボタンを押下し、利用可能なリソース一覧から、1)で確認したリソース名を選択します。  選択後、リソースの許可をRのみに変更し、ロールを保存します。 3) アクセスするユーザのログインロールを確認する。  例では、ターミナルでのルーチン(またはクラスメソッド)実行で確認を行うため、ターミナルログイン時にユーザ認証を行うよう、適切なサービス(Windowsなら%Service_Console)の認証方法に「パスワード」を追加します。  任意ユーザに対して、%Developerロールと 2) で作成したロールを付与します。 4) 特権ルーチンアプリケーションの作成  実行時にREAD/WRITE許可を追加するための「特権ルーチンアプリケーション」を作成します。  管理ポータル > [システム管理] > [セキュリティ] > [アプリケーション] > [特権ルーチンアプリケーション] > 作成用ボタン押下  特権ルーチンアプリケーション名を指定し保存します。(特権ルーチンアプリケーション名を TR1 とします。)  ルーチンタブを開き、ルーチン(またはクラスメソッド)があるデータベースの指定と、名前の指定を行います。  続いて、アプリケーション・ロールタブを開き、データベース:A のDBロール(%DB_Aロール)を付与します。  ※ DBロールはDBリソース作成時、自動的に作成されるロールで、DBリソースに対するREAD/WRITE許可を持つロールです。 5) コード内でロールを付与する。  4)で作成した特権ルーチン名を使用して、以下メソッドを追記します。   set st=$system.Security.AddRoles("TR1")  【実行例】 st() public { try { new $roles write "現在の値:",^ABC,! set st=$system.Security.AddRoles("TR1") // ロールの追加 write $roles,! set ^ABC="変更" write "現在の値(更新後):",^ABC,! } catch ex { zwrite ex }} 《ターミナル実行例》 A>set ^ABC="かきくけこ" // ログイン時、READ許可のみのためエラー発生SET ^ABC="かきくけこ"^^ABC,c:\intersystems\cache\mgr\a\A>write ^ABCテストA>do st^Test() // ルーチン実行期間のみ更新可現在の値:テスト%Developer,%DB_A,R1現在の値(更新後):変更
記事
Mihoko Iijima · 2024年8月9日

Text to IRIS SQL with LangChain:Pythonプログラミングコンテスト受賞作品紹介!

開発者の皆さん、こんにちは! この記事は、2024年7月に開催された「[InterSystems Pythonプログラミングコンテスト2024](https://jp.community.intersystems.com/node/572191)」でエキスパート投票、コミュニティ投票の両方で1位を獲得された [@Henry Pereira](https://jp.community.intersystems.com/user/henry-pereira) さん [@José Pereira](https://jp.community.intersystems.com/user/jos%C3%A9-pereira) さん [@Henrique Dias](https://jp.community.intersystems.com/user/henrique-dias-2) さんが開発された [sqlzilla](https://openexchange.intersystems.com/package/sqlzilla) について、アプリを動かしてみた感想と、中の構造について [@José Pereira](https://jp.community.intersystems.com/user/jos%C3%A9-pereira) さんが投稿された「[Text to IRIS with LangChain](https://community.intersystems.com/node/571946)」の翻訳をご紹介します。 [第2回 InterSystems Japan 技術文書ライティングコンテスト 開催!](https://jp.community.intersystems.com/node/571626) では、生成AIに関連する記事を投稿いただくと、ボーナスポイントを4点獲得できます📢 [@José Pereira](https://jp.community.intersystems.com/user/jos%C3%A9-pereira) さんの記事を💡ヒント💡に皆様の操作体験談、アイデアなどを共有いただければと思います。 開発されたアプリSQLzilla についての概要ですが、[Open Exchange](https://openexchange.intersystems.com/) の [sqlzilla](https://openexchange.intersystems.com/package/sqlzilla) のREADMEに以下のように紹介されています。 **「SQLzilla は、Python と AI のパワーを活用して、自然言語の SQL クエリ生成を通じてデータ アクセスを簡素化し、複雑なデータ クエリとプログラミング経験の少ないユーザーとの間のギャップを埋めます。」** 「SQLクエリ生成」とありますので、アプリには Aviationスキーマ以下3つのテーブルとサンプルデータが用意されています。 - Aviation.Aircraft - Aviation.Crew - Aviation.Event 3つのテーブルには、米国国家運輸安全委員会に報告された航空事故の選択されたサブセットのデータが含まれています。 > メモ: [サンプルリポジトリ](https://github.com/intersystems/Samples-Aviation) で提供されるデータセットは、[http://www.ntsb.gov](http://www.ntsb.gov) から入手できる完全な NTSB データセットの小さなサブセットであるためデモ目的のみで提供されており、正確であることを意図または保証するものではありません。(提供元:[National Transportation Safety Board](http://www.ntsb.gov/)) 例えば、Aviation.Aircraftには、AircraftCategoryカラムがあり、航空機の種別が登録されています。 ``` [SQL]IRISAPP>>SELECT AircraftCategory FROM Aviation.Aircraft GROUP BY AircraftCategory 1. SELECT AircraftCategory FROM Aviation.Aircraft GROUP BY AircraftCategory | AircraftCategory | | -- | | AIRPLANE | | HELICOPTER | | GYROCRAFT | | GLIDER | | BALLOON | | POWERED PARACHUTE | | WEIGHT SHIFT | ``` また、DepartureCity には、出発都市名が登録されています。 ``` SELECT top 10 DepartureCity FROM Aviation.Aircraft WHERE AircraftCategory='AIRPLANE' GROUP BY DepartureCity | DepartureCity | | -- | | WILBUR | | IRONWOOD | | STANIEL CAY | | OAK ISLAND | | CLEVELAND | | DECATUR | | MARSHALLTOWN | | MARANA | | TONOPAH | | MURRIETA/TEMECU | ``` また、Aviation.Eventテーブルには発生した事故の情報が含まれていて、InjuriesHighestには負傷者数の状況を文字で表現した情報が含まれています。 ``` SELECT InjuriesHighest FROM Aviation.Event GROUP BY InjuriesHighest | InjuriesHighest | | -- | | NONE | | FATAL | | SERIOUS | | MINOR | ``` さて、これらのテーブルを使って具体的にどのようなことをしてくれるアプリなのか?ですが(説明文より以下抜粋) **「SQLzilla は、ユーザーがデータベースを操作する方法を変革する革新的なプロジェクトです。InterSystems IRIS と統合することで、SQL に詳しくないユーザーでもさまざまなテーブルからデータを簡単に抽出して分析できるツールを作成しました。」** つまり、 SQLに詳しくないユーザでも、自分の欲しい情報に対して質問するとSQLを組み立てて返してきてくれる便利アプリということになります。 以下、アプリケーションを動作させてみたときの図です。(日本語で質問してもしっかりSQLを組み立ててくれています!) ![image](/sites/default/files/inline/images/sqlzilla_1.png) 入力した質問によって生成されたSQLは以下の通りです。 - ヘリコプターによる事故の件数を教えてください。 ``` SELECT COUNT(*) FROM Aviation.Aircraft WHERE AircraftCategory = 'Helicopter' ``` - 2002年に発生したヘリコプターによる事故の件数を教えてください。 ``` SELECT COUNT(*) FROM Aviation.Event WHERE YEAR(EventDate) = 2002 AND Type = 'Helicopter Accident' ``` ![image](/sites/default/files/inline/images/sqlzilla2.png) - ヘリコプターによる事故でInjuriesHighestがFATALの事故数を年別で表示してください。 ``` SELECT YEAR(e.EventDate) AS IncidentYear, COUNT(*) AS FatalIncidentCount FROM Aviation.Event e JOIN Aviation.Aircraft a ON e.EventId = a.EventId WHERE a.AircraftCategory = 'Helicopter' AND e.InjuriesHighest = 'FATAL' GROUP BY YEAR(e.EventDate) ``` >アプリ詳細は、ぜひ [Open Exchange](https://openexchange.intersystems.com/) の [sqlzilla](https://openexchange.intersystems.com/package/sqlzilla) をご参照ください。 素晴らしいです!! ※ このサンプルを動作させるためには、OpenAIのAPIキーが必要となりますので、お試しになる場合は事前にご準備ください。 [Open Exchange](https://openexchange.intersystems.com/) の [sqlzilla](https://openexchange.intersystems.com/package/sqlzilla) のREADMEにも記載がありますが、コンテナを使う場合は以下3コマンドでサンプルを動作させることができます。 まずはソースコードをclone して ``` git clone https://github.com/musketeers-br/sqlzilla.git ``` コンテナをビルドし ``` docker-compose build --no-cache --progress=plain ``` コンテナを開始するだけ ``` docker-compose up -d ``` 後は、[アプリ画面](http://localhost:8501/)を起動するだけ! コンテナ以外でも操作する方法が提供されています。詳しくは、 [sqlzilla](https://openexchange.intersystems.com/package/sqlzilla) のREADMEご参照ください。(ぜひREADMEの一番下までご覧ください!) --------- ## 記事の紹介 アプリの中でどのようにSQLを生成させているか、については、 [@José Pereira](https://jp.community.intersystems.com/user/jos%C3%A9-pereira) さんが投稿された「[Text to IRIS with LangChain](https://community.intersystems.com/node/571946)」の翻訳記事でご紹介します。 --------- LangChainフレームワーク、IRIS Vector Search、LLMを使って、ユーザープロンプトからIRIS互換のSQLを生成する方法についての実験をご紹介します。 この記事は [このノートブック](https://github.com/musketeers-br/sqlzilla/blob/master/jupyter/SQLFromText-article.ipynb) を元にしています。 OpenExchange の [このアプリケーション](https://openexchange.intersystems.com/package/sqlzilla) を使えば、すぐに使える環境で実行できます。 ## セットアップ 最初に必要なライブラリをインストールします。 ```python !pip install --upgrade --quiet langchain langchain-openai langchain-iris pandas ``` 次に、必要なモジュールをインポートし、環境をセットアップします。 ```python import os import datetime import hashlib from copy import deepcopy from sqlalchemy import create_engine import getpass import pandas as pd from langchain_core.prompts import PromptTemplate, ChatPromptTemplate from langchain_core.example_selectors import SemanticSimilarityExampleSelector from langchain_openai import OpenAIEmbeddings, ChatOpenAI from langchain.docstore.document import Document from langchain_community.document_loaders import DataFrameLoader from langchain.text_splitter import CharacterTextSplitter from langchain_core.output_parsers import StrOutputParser from langchain.globals import set_llm_cache from langchain.cache import SQLiteCache from langchain_iris import IRISVector ``` SQLiteCacheを使ってLLMコールをキャッシュします。 ```python # Cache for LLM calls set_llm_cache(SQLiteCache(database_path=".langchain.db")) ``` IRISデータベースへ接続するためのパラメータをセットします。 ```python # IRIS database connection parameters os.environ["ISC_LOCAL_SQL_HOSTNAME"] = "localhost" os.environ["ISC_LOCAL_SQL_PORT"] = "1972" os.environ["ISC_LOCAL_SQL_NAMESPACE"] = "IRISAPP" os.environ["ISC_LOCAL_SQL_USER"] = "_system" os.environ["ISC_LOCAL_SQL_PWD"] = "SYS" ``` OpenAI APIキーが環境変数に設定されていない場合は、ユーザ入力が求められます。 ```python if not "OPENAI_API_KEY" in os.environ: os.environ["OPENAI_API_KEY"] = getpass.getpass() ``` IRISデータベースの接続文字列を作成します。 ```python # IRIS database connection string args = { 'hostname': os.getenv("ISC_LOCAL_SQL_HOSTNAME"), 'port': os.getenv("ISC_LOCAL_SQL_PORT"), 'namespace': os.getenv("ISC_LOCAL_SQL_NAMESPACE"), 'username': os.getenv("ISC_LOCAL_SQL_USER"), 'password': os.getenv("ISC_LOCAL_SQL_PWD") } iris_conn_str = f"iris://{args['username']}:{args['password']}@{args['hostname']}:{args['port']}/{args['namespace']}" ``` IRISデータベースとの接続を確立します。 ```python # Connection to IRIS database engine = create_engine(iris_conn_str) cnx = engine.connect().connection ``` システムプロンプトのコンテキスト情報を保持するdictionaryを用意します。 ```python # Dict for context information for system prompt context = {} context["top_k"] = 3 ``` ## プロンプトの作成 ユーザー入力をIRISデータベースと互換性のあるSQLクエリに変換するために、言語モデル用の効果的なプロンプトを作成する必要があります。 SQLクエリを生成するための基本的な指示を提供する初期プロンプトから始めます。 このテンプレートは[LangChain's default prompts for MSSQL](https://github.com/langchain-ai/langchain/blob/b00c0fc558c278e3299a81ddcda9c61cdeff3043/libs/langchain/langchain/chains/sql_database/prompt.py#L106) から派生し、IRISデータベース用にカスタマイズされています。 ```python # Basic prompt template with IRIS database SQL instructions iris_sql_template = """ You are an InterSystems IRIS expert. Given an input question, first create a syntactically correct InterSystems IRIS query to run and return the answer to the input question. Unless the user specifies in the question a specific number of examples to obtain, query for at most {top_k} results using the TOP clause as per InterSystems IRIS. You can order the results to return the most informative data in the database. Never query for all columns from a table. You must query only the columns that are needed to answer the question. Wrap each column name in single quotes ('') to denote them as delimited identifiers. Pay attention to use only the column names you can see in the tables below. Be careful to not query for columns that do not exist. Also, pay attention to which column is in which table. Pay attention to use CAST(CURRENT_DATE as date) function to get the current date, if the question involves "today". Use double quotes to delimit columns identifiers. Return just plain SQL; don't apply any kind of formatting. """ ``` > 次のような文章をテンプレートに設定しています: あなたは InterSystems IRIS のエキスパートです。 入力された質問に対して、まず、構文的に正しい InterSystems IRIS クエリを作成し、そのクエリを実行し て、入力された質問に対する回答を返します。 ユーザが質問で特定の数の例を取得するように指定しない限り、InterSystems IRIS に従って、TOP 節を使用して最大 {top_k} の結果をクエリします。 テーブルのすべての列に対してクエリを実行してはなりません。 質問に答えるために必要なカラムのみを問い合わせなければなりません。 各カラム名をシングルクォート('')で囲み、区切り識別子にします。 以下の表で確認できるカラム名のみを使用するように注意してください。 存在しないカラムを問い合わせないように注意してください。 また、どのカラムがどのテーブルにあるかに注意すること。 質問内容が "今日 "を含む場合は、CAST(CURRENT_DATE as date)関数を使用して現在の日付を取得することに注意すること。 カラムの識別子を区切るには二重引用符を使用すること。 単なるSQLを返すこと。いかなるフォーマットも適用しないこと。 この基本プロンプトは、言語モデル(LLM)がIRISデータベースに対する特定のガイダンスを持つSQLエキスパートとして機能するように設定しています。 次に、ハルシネーション(幻覚)を避けるために、データベーススキーマに関する情報を補助プロンプトとして提供します。 ```python # SQL template extension for including tables context information tables_prompt_template = """ Only use the following tables: {table_info} """ ``` LLMの回答の精度を高めるために、私たちはfew-shot プロンプトと呼ばれるテクニックを使いました。 これはLLMにいくつかの例を提示するものです。 ```python # SQL template extension for including few shots prompt_sql_few_shots_template = """ Below are a number of examples of questions and their corresponding SQL queries. {examples_value} """ ``` 私たちは few-shot の例のためにテンプレートを以下のように定義しています。 ```python # Few shots prompt template example_prompt_template = "User input: {input}\nSQL query: {query}" example_prompt = PromptTemplate.from_template(example_prompt_template) ``` 私たちは、 few-shot テンプレートを使ってユーザー・プロンプトを作っています。 ```python # User prompt template user_prompt = "\n" + example_prompt.invoke({"input": "{input}", "query": ""}).to_string() ``` 最後に、すべてのプロンプトを組み合わせて最終的なプロンプトを作成します。 ```python # Complete prompt template prompt = ( ChatPromptTemplate.from_messages([("system", iris_sql_template)]) + ChatPromptTemplate.from_messages([("system", tables_prompt_template)]) + ChatPromptTemplate.from_messages([("system", prompt_sql_few_shots_template)]) + ChatPromptTemplate.from_messages([("human", user_prompt)]) ) prompt ``` このプロンプトは、変数 `examples_value`, `input`, `table_info`, and `top_k` を想定しています。 プロンプトの構成は以下の通りです。 ```python ChatPromptTemplate( input_variables=['examples_value', 'input', 'table_info', 'top_k'], messages=[ SystemMessagePromptTemplate( prompt=PromptTemplate( input_variables=['top_k'], template=iris_sql_template ) ), SystemMessagePromptTemplate( prompt=PromptTemplate( input_variables=['table_info'], template=tables_prompt_template ) ), SystemMessagePromptTemplate( prompt=PromptTemplate( input_variables=['examples_value'], template=prompt_sql_few_shots_template ) ), HumanMessagePromptTemplate( prompt=PromptTemplate( input_variables=['input'], template=user_prompt ) ) ] ) ``` プロンプトがどのようにLLMに送られるかを視覚化するために、必要な変数にプレースホルダーの値を使うことができます。 ```python prompt_value = prompt.invoke({ "top_k": "", "table_info": "", "examples_value": "", "input": "" }) print(prompt_value.to_string()) ``` ``` System: You are an InterSystems IRIS expert. Given an input question, first create a syntactically correct InterSystems IRIS query to run and return the answer to the input question. Unless the user specifies in the question a specific number of examples to obtain, query for at most results using the TOP clause as per InterSystems IRIS. You can order the results to return the most informative data in the database. Never query for all columns from a table. You must query only the columns that are needed to answer the question. Wrap each column name in single quotes ('') to denote them as delimited identifiers. Pay attention to use only the column names you can see in the tables below. Be careful to not query for columns that do not exist. Also, pay attention to which column is in which table. Pay attention to use CAST(CURRENT_DATE as date) function to get the current date, if the question involves "today". Use double quotes to delimit columns identifiers. Return just plain SQL; don't apply any kind of formatting. System: Only use the following tables: System: Below are a number of examples of questions and their corresponding SQL queries. Human: User input: SQL query: ``` これで、必要な変数を与えることで、このプロンプトをLLMに送る準備ができました。 準備ができたら次のステップに進みましょう。 ## テーブル情報の提供 正確なSQLクエリを作成するためには、言語モデル(LLM)にデータベース・テーブルに関する詳細な情報を提供する必要があります。 この情報がないと、LLMは一見もっともらしく見えますが、ハルシネーション(幻覚)により正しくないクエリを生成する可能性があります。 そこで、最初のステップとして、IRISデータベースからテーブル定義を取得する関数を作成します。 ### テーブル定義情報を取得する関数 以下の関数はINFORMATION_SCHEMAに問い合わせ、指定されたスキーマのテーブル定義を取得します。 特定のテーブルが指定された場合は、そのテーブルの定義を取得します。そうでない場合は、スキーマ内の全てのテーブルの定義を取得します。 ```python def get_table_definitions_array(cnx, schema, table=None): cursor = cnx.cursor() # Base query to get columns information query = """ SELECT TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME, DATA_TYPE, IS_NULLABLE, COLUMN_DEFAULT, PRIMARY_KEY, null EXTRA FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = %s """ # Parameters for the query params = [schema] # Adding optional filters if table: query += " AND TABLE_NAME = %s" params.append(table) # Execute the query cursor.execute(query, params) # Fetch the results rows = cursor.fetchall() # Process the results to generate the table definition(s) table_definitions = {} for row in rows: table_schema, table_name, column_name, column_type, is_nullable, column_default, column_key, extra = row if table_name not in table_definitions: table_definitions[table_name] = [] table_definitions[table_name].append({ "column_name": column_name, "column_type": column_type, "is_nullable": is_nullable, "column_default": column_default, "column_key": column_key, "extra": extra }) primary_keys = {} # Build the output string result = [] for table_name, columns in table_definitions.items(): table_def = f"CREATE TABLE {schema}.{table_name} (\n" column_definitions = [] for column in columns: column_def = f" {column['column_name']} {column['column_type']}" if column['is_nullable'] == "NO": column_def += " NOT NULL" if column['column_default'] is not None: column_def += f" DEFAULT {column['column_default']}" if column['extra']: column_def += f" {column['extra']}" column_definitions.append(column_def) if table_name in primary_keys: pk_def = f" PRIMARY KEY ({', '.join(primary_keys[table_name])})" column_definitions.append(pk_def) table_def += ",\n".join(column_definitions) table_def += "\n);" result.append(table_def) return result ``` ### スキーマのテーブル定義を取得する この例では、Aviationスキーマを使用しています。Aviationスキーマ情報一式については [こちら ](https://openexchange.intersystems.com/package/Samples-Aviation)から入手できます。 ```python # Retrieve table definitions for the Aviation schema tables = get_table_definitions_array(cnx, "Aviation") print(tables) ``` この関数は、Aviation スキーマ内のすべてのテーブルの CREATE TABLE ステートメントを返します。 ```python [ 'CREATE TABLE Aviation.Aircraft (\n Event bigint NOT NULL,\n ID varchar NOT NULL,\n AccidentExplosion varchar,\n AccidentFire varchar,\n AirFrameHours varchar,\n AirFrameHoursSince varchar,\n AirFrameHoursSinceLastInspection varchar,\n AircraftCategory varchar,\n AircraftCertMaxGrossWeight integer,\n AircraftHomeBuilt varchar,\n AircraftKey integer NOT NULL,\n AircraftManufacturer varchar,\n AircraftModel varchar,\n AircraftRegistrationClass varchar,\n AircraftSerialNo varchar,\n AircraftSeries varchar,\n Damage varchar,\n DepartureAirportId varchar,\n DepartureCity varchar,\n DepartureCountry varchar,\n DepartureSameAsEvent varchar,\n DepartureState varchar,\n DepartureTime integer,\n DepartureTimeZone varchar,\n DestinationAirportId varchar,\n DestinationCity varchar,\n DestinationCountry varchar,\n DestinationSameAsLocal varchar,\n DestinationState varchar,\n EngineCount integer,\n EvacuationOccurred varchar,\n EventId varchar NOT NULL,\n FlightMedical varchar,\n FlightMedicalType varchar,\n FlightPhase integer,\n FlightPlan varchar,\n FlightPlanActivated varchar,\n FlightSiteSeeing varchar,\n FlightType varchar,\n GearType varchar,\n LastInspectionDate timestamp,\n LastInspectionType varchar,\n Missing varchar,\n OperationDomestic varchar,\n OperationScheduled varchar,\n OperationType varchar,\n OperatorCertificate varchar,\n OperatorCertificateNum varchar,\n OperatorCode varchar,\n OperatorCountry varchar,\n OperatorIndividual varchar,\n OperatorName varchar,\n OperatorState varchar,\n Owner varchar,\n OwnerCertified varchar,\n OwnerCountry varchar,\n OwnerState varchar,\n RegistrationNumber varchar,\n ReportedToICAO varchar,\n SeatsCabinCrew integer,\n SeatsFlightCrew integer,\n SeatsPassengers integer,\n SeatsTotal integer,\n SecondPilot varchar,\n childsub bigint NOT NULL DEFAULT $i(^Aviation.EventC("Aircraft"))\n);', 'CREATE TABLE Aviation.Crew (\n Aircraft varchar NOT NULL,\n ID varchar NOT NULL,\n Age integer,\n AircraftKey integer NOT NULL,\n Category varchar,\n CrewNumber integer NOT NULL,\n EventId varchar NOT NULL,\n Injury varchar,\n MedicalCertification varchar,\n MedicalCertificationDate timestamp,\n MedicalCertificationValid varchar,\n Seat varchar,\n SeatbeltUsed varchar,\n Sex varchar,\n ShoulderHarnessUsed varchar,\n ToxicologyTestPerformed varchar,\n childsub bigint NOT NULL DEFAULT $i(^Aviation.AircraftC("Crew"))\n);', 'CREATE TABLE Aviation.Event (\n ID bigint NOT NULL DEFAULT $i(^Aviation.EventD),\n AirportDirection integer,\n AirportDistance varchar,\n AirportElevation integer,\n AirportLocation varchar,\n AirportName varchar,\n Altimeter varchar,\n EventDate timestamp,\n EventId varchar NOT NULL,\n EventTime integer,\n FAADistrictOffice varchar,\n InjuriesGroundFatal integer,\n InjuriesGroundMinor integer,\n InjuriesGroundSerious integer,\n InjuriesHighest varchar,\n InjuriesTotal integer,\n InjuriesTotalFatal integer,\n InjuriesTotalMinor integer,\n InjuriesTotalNone integer,\n InjuriesTotalSerious integer,\n InvestigatingAgency varchar,\n LightConditions varchar,\n LocationCity varchar,\n LocationCoordsLatitude double,\n LocationCoordsLongitude double,\n LocationCountry varchar,\n LocationSiteZipCode varchar,\n LocationState varchar,\n MidAir varchar,\n NTSBId varchar,\n NarrativeCause varchar,\n NarrativeFull varchar,\n NarrativeSummary varchar,\n OnGroundCollision varchar,\n SkyConditionCeiling varchar,\n SkyConditionCeilingHeight integer,\n SkyConditionNonCeiling varchar,\n SkyConditionNonCeilingHeight integer,\n TimeZone varchar,\n Type varchar,\n Visibility varchar,\n WeatherAirTemperature integer,\n WeatherPrecipitation varchar,\n WindDirection integer,\n WindDirectionIndicator varchar,\n WindGust integer,\n WindGustIndicator varchar,\n WindVelocity integer,\n WindVelocityIndicator varchar\n);' ] ``` これらのテーブル定義ができたので、次のステップに進むことができます。 これにより、LLMがSQLクエリを生成する際に、データベース・スキーマに関する正確で包括的な情報が得られるようになります。 ## 最も関連性の高いテーブルを選ぶ データベース、特に大規模なデータベースを扱う場合、プロンプト内のすべてのテーブルのデータ定義言語(DDL)を送信することは非現実的です。 このアプローチは小規模なデータベースでは有効かもしれませんが、現実のデータベースには数百から数千のテーブルが含まれていることが多く、すべてのテーブルを処理するのは非効率的です。 さらに、SQLクエリを効率的に生成するために、言語モデルがデータベース内のすべてのテーブルを認識する必要があるとは考えにくいです。 この課題に対処するため、セマンティック検索機能を活用し、ユーザーのクエリに基づいて最も関連性の高いテーブルのみを選択することができます。 ### アプローチ IRIS Vector Searchでセマンティック検索を使用することでこれを実現します。 この方法は、SQL要素の識別子(テーブル、フィールド、キーなど)に意味のある名前がある場合に最も効果的です。識別子が任意のコードである場合は、代わりにデータ dictionaryの使用を検討してください。 ### 手順 1. テーブル情報の取得 まず、テーブル定義をpandas DataFrameに取り出します。 ```python # Retrieve table definitions into a pandas DataFrame table_def = get_table_definitions_array(cnx=cnx, schema='Aviation') table_df = pd.DataFrame(data=table_def, columns=["col_def"]) table_df["id"] = table_df.index + 1 table_df ``` DataFrame(table_df)は以下のようになります。 | | col_def | id | |---|--------------------------------------------------|----| | 0 | CREATE TABLE Aviation.Aircraft (\n Event bigi... | 1 | | 1 | CREATE TABLE Aviation.Crew (\n Aircraft varch... | 2 | | 2 | CREATE TABLE Aviation.Event (\n ID bigint NOT... | 3 | | | | | 2. 定義をDocumentsに分割する 次に、テーブル定義をLangchain Documentsに分割します。 このステップは、大きなテキストの塊を扱い、テキスト埋め込みを抽出するために非常に重要です。 ```python loader = DataFrameLoader(table_df, page_content_column="col_def") documents = loader.load() text_splitter = CharacterTextSplitter(chunk_size=400, chunk_overlap=20, separator="\n") tables_docs = text_splitter.split_documents(documents) tables_docs ``` その結果、tables_docsリストには、次のようにメタデータ付きの分割ドキュメントが含まれます。 ```python [Document(metadata={'id': 1}, page_content='CREATE TABLE Aviation.Aircraft (\n Event bigint NOT NULL,\n ID varchar NOT NULL,\n ...'), Document(metadata={'id': 2}, page_content='CREATE TABLE Aviation.Crew (\n Aircraft varchar NOT NULL,\n ID varchar NOT NULL,\n ...'), Document(metadata={'id': 3}, page_content='CREATE TABLE Aviation.Event (\n ID bigint NOT NULL DEFAULT $i(^Aviation.EventD),\n ...')] ``` 3. 埋め込み(Embedding)の抽出とIRISへの保存 次に,langchain-irisのIRISVectorクラスを使って埋め込みベクトルを抽出し、それをIRISに格納します。 ```python tables_vector_store = IRISVector.from_documents( embedding=OpenAIEmbeddings(), documents=tables_docs, connection_string=iris_conn_str, collection_name="sql_tables", pre_delete_collection=True ) ``` 注意: `pre_delete_collection` フラグは、各テスト実行で新鮮なコレクションを確保するために、デモ用に `True` に設定されています。 本番環境では、このフラグは通常 `False` に設定します。 4. 関連文書の検索 テーブルに埋め込み(Embedding)が保存されたことで、ユーザー入力に基づいて関連するテーブルを検索できるようになりました。 ```python input_query = "List the first 2 manufacturers" relevant_tables_docs = tables_vector_store.similarity_search(input_query, k=3) relevant_tables_docs ``` 例えば、manufacturers を検索すると、次のような結果が返ってきます。 ```python [Document(metadata={'id': 1}, page_content='GearType varchar,\n LastInspectionDate timestamp,\n ...'), Document(metadata={'id': 1}, page_content='AircraftModel varchar,\n AircraftRegistrationClass varchar,\n ...'), Document(metadata={'id': 3}, page_content='LocationSiteZipCode varchar,\n LocationState varchar,\n ...')] ``` メタデータから、テーブルID 1 (Aviation.Aircraft)だけが関連性があり、クエリと一致していることがわかります。 5. エッジケースへの対応 このアプローチは一般的に効果的ではありますが、常に完璧とは限りません。 たとえば、事故現場のクエリを実行すると、関連性の低いテーブルが返されることもあります。 ```python input_query = "List the top 10 most crash sites" relevant_tables_docs = tables_vector_store.similarity_search(input_query, k=3) relevant_tables_docs ``` 結果は以下の通りです。 ```python [Document(metadata={'id': 3}, page_content='LocationSiteZipCode varchar,\n LocationState varchar,\n ...'), Document(metadata={'id': 3}, page_content='InjuriesGroundSerious integer,\n InjuriesHighest varchar,\n ...'), Document(metadata={'id': 1}, page_content='CREATE TABLE Aviation.Aircraft (\n Event bigint NOT NULL,\n ID varchar NOT NULL,\n ...')] ``` 正しいAviation.Eventテーブルを2回取得したにもかかわらず、Aviation.Aircraftテーブルも表示されることがあります。これは、この例の範囲を超えているため、将来の実装に委ねられます。 6. 関連テーブルを取得する関数を定義する このプロセスを自動化するため、ユーザー入力に基づいて関連するテーブルをフィルタリングして返す関数を定義します。 ```python def get_relevant_tables(user_input, tables_vector_store, table_df): relevant_tables_docs = tables_vector_store.similarity_search(user_input) relevant_tables_docs_indices = [x.metadata["id"] for x in relevant_tables_docs] indices = table_df["id"].isin(relevant_tables_docs_indices) relevant_tables_array = [x for x in table_df[indices]["col_def"]] return relevant_tables_array ``` この機能は、LLMに送信する関連テーブルのみを効率的に検索し、プロンプトの長さを短縮し、クエリ全体のパフォーマンスを向上させるのに役立ちます。 ## 最も適切な例を選ぶ(Few-Shotプロンプティング) 言語モデル(LLM)を扱うとき、適切な例を提供することは、正確で文脈的に適切な応答を保証するのに役立ちます。 これらの例は "Few-Shot" 例と呼ばれ、LLMが処理すべきクエリの構造とコンテキストを理解するためのガイドとなります。今回のケースでは、IRISのSQL構文とデータベースで使用可能なテーブルを幅広くカバーする多様なSQLクエリを `examples_value` 変数に入力する必要があります。これは、LLMが正しくないクエリーや無関係なクエリーを生成するのを防ぐのに役立ちます。 ### クエリ例の定義 以下は、様々なSQL操作を説明するために作られたクエリ例のリストです。 ```python examples = [ {"input": "List all aircrafts.", "query": "SELECT * FROM Aviation.Aircraft"}, {"input": "Find all incidents for the aircraft with ID 'N12345'.", "query": "SELECT * FROM Aviation.Event WHERE EventId IN (SELECT EventId FROM Aviation.Aircraft WHERE ID = 'N12345')"}, {"input": "List all incidents in the 'Commercial' operation type.", "query": "SELECT * FROM Aviation.Event WHERE EventId IN (SELECT EventId FROM Aviation.Aircraft WHERE OperationType = 'Commercial')"}, {"input": "Find the total number of incidents.", "query": "SELECT COUNT(*) FROM Aviation.Event"}, {"input": "List all incidents that occurred in 'Canada'.", "query": "SELECT * FROM Aviation.Event WHERE LocationCountry = 'Canada'"}, {"input": "How many incidents are associated with the aircraft with AircraftKey 5?", "query": "SELECT COUNT(*) FROM Aviation.Aircraft WHERE AircraftKey = 5"}, {"input": "Find the total number of distinct aircrafts involved in incidents.", "query": "SELECT COUNT(DISTINCT AircraftKey) FROM Aviation.Aircraft"}, {"input": "List all incidents that occurred after 5 PM.", "query": "SELECT * FROM Aviation.Event WHERE EventTime > 1700"}, {"input": "Who are the top 5 operators by the number of incidents?", "query": "SELECT TOP 5 OperatorName, COUNT(*) AS IncidentCount FROM Aviation.Aircraft GROUP BY OperatorName ORDER BY IncidentCount DESC"}, {"input": "Which incidents occurred in the year 2020?", "query": "SELECT * FROM Aviation.Event WHERE YEAR(EventDate) = '2020'"}, {"input": "What was the month with most events in the year 2020?", "query": "SELECT TOP 1 MONTH(EventDate) EventMonth, COUNT(*) EventCount FROM Aviation.Event WHERE YEAR(EventDate) = '2020' GROUP BY MONTH(EventDate) ORDER BY EventCount DESC"}, {"input": "How many crew members were involved in incidents?", "query": "SELECT COUNT(*) FROM Aviation.Crew"}, {"input": "List all incidents with detailed aircraft information for incidents that occurred in the year 2012.", "query": "SELECT e.EventId, e.EventDate, a.AircraftManufacturer, a.AircraftModel, a.AircraftCategory FROM Aviation.Event e JOIN Aviation.Aircraft a ON e.EventId = a.EventId WHERE Year(e.EventDate) = 2012"}, {"input": "Find all incidents where there were more than 5 injuries and include the aircraft manufacturer and model.", "query": "SELECT e.EventId, e.InjuriesTotal, a.AircraftManufacturer, a.AircraftModel FROM Aviation.Event e JOIN Aviation.Aircraft a ON e.EventId = a.EventId WHERE e.InjuriesTotal > 5"}, {"input": "List all crew members involved in incidents with serious injuries, along with the incident date and location.", "query": "SELECT c.CrewNumber AS 'Crew Number', c.Age, c.Sex AS Gender, e.EventDate AS 'Event Date', e.LocationCity AS 'Location City', e.LocationState AS 'Location State' FROM Aviation.Crew c JOIN Aviation.Event e ON c.EventId = e.EventId WHERE c.Injury = 'Serious'"} ] ``` ### 関連する事例の選択 例のリストが増え続けることを考えると、LLMにすべての例を提供することは現実的ではありません。代わりに、IRIS Vector Searchと[SemanticSimilarityExampleSelectorクラス](https://api.python.langchain.com/en/latest/example_selectors/langchain_core.example_selectors.semantic_similarity.SemanticSimilarityExampleSelector.html)を使用して、ユーザーのプロンプトに基づいて最も関連性の高い例を特定します。 ### Example Selector を定義する ```python example_selector = SemanticSimilarityExampleSelector.from_examples( examples, OpenAIEmbeddings(), IRISVector, k=5, input_keys=["input"], connection_string=iris_conn_str, collection_name="sql_samples", pre_delete_collection=True ) ``` 注意: `pre_delete_collection` フラグは、各テスト実行で新鮮なコレクションを確保するためのデモンストレーション目的で使用されています。本番環境では、不要な削除を避けるためにこのフラグを `False` に設定する必要があります。 ### Selectorに問い合わせる 与えられた入力に最も関連する例を見つけるには、次のようにSelector を使用します。 ```python input_query = "Find all events in 2010 informing the Event Id and date, location city and state, aircraft manufacturer and model." relevant_examples = example_selector.select_examples({"input": input_query}) ``` 結果は以下のようになります。 ```python [{'input': 'List all incidents with detailed aircraft information for incidents that occurred in the year 2012.', 'query': 'SELECT e.EventId, e.EventDate, a.AircraftManufacturer, a.AircraftModel, a.AircraftCategory FROM Aviation.Event e JOIN Aviation.Aircraft a ON e.EventId = a.EventId WHERE Year(e.EventDate) = 2012'}, {'input': "Find all incidents for the aircraft with ID 'N12345'.", 'query': "SELECT * FROM Aviation.Event WHERE EventId IN (SELECT EventId FROM Aviation.Aircraft WHERE ID = 'N12345')"}, {'input': 'Find all incidents where there were more than 5 injuries and include the aircraft manufacturer and model.', 'query': 'SELECT e.EventId, e.InjuriesTotal, a.AircraftManufacturer, a.AircraftModel FROM Aviation.Event e JOIN Aviation.Aircraft a ON e.EventId = a.EventId WHERE e.InjuriesTotal > 5'}, {'input': 'List all aircrafts.', 'query': 'SELECT * FROM Aviation.Aircraft'}, {'input': 'Find the total number of distinct aircrafts involved in incidents.', 'query': 'SELECT COUNT(DISTINCT AircraftKey) FROM Aviation.Aircraft'}] ``` 数量に関連した例が特に必要な場合は、それに応じてSelectorに問い合わせることができます。 ```python input_query = "What is the number of incidents involving Boeing aircraft." quantity_examples = example_selector.select_examples({"input": input_query}) ``` 出力は以下の通りです。 ```python [{'input': 'How many incidents are associated with the aircraft with AircraftKey 5?', 'query': 'SELECT COUNT(*) FROM Aviation.Aircraft WHERE AircraftKey = 5'}, {'input': 'Find the total number of distinct aircrafts involved in incidents.', 'query': 'SELECT COUNT(DISTINCT AircraftKey) FROM Aviation.Aircraft'}, {'input': 'How many crew members were involved in incidents?', 'query': 'SELECT COUNT(*) FROM Aviation.Crew'}, {'input': 'Find all incidents where there were more than 5 injuries and include the aircraft manufacturer and model.', 'query': 'SELECT e.EventId, e.InjuriesTotal, a.AircraftManufacturer, a.AircraftModel FROM Aviation.Event e JOIN Aviation.Aircraft a ON e.EventId = a.EventId WHERE e.InjuriesTotal > 5'}, {'input': 'List all incidents with detailed aircraft information for incidents that occurred in the year 2012.', 'query': 'SELECT e.EventId, e.EventDate, a.AircraftManufacturer, a.AircraftModel, a.AircraftCategory FROM Aviation.Event e JOIN Aviation.Aircraft a ON e.EventId = a.EventId WHERE Year(e.EventDate) = 2012'}] ``` この出力には、特にカウントと量を扱った例が含まれています。 ### 今後の検討事項 SemanticSimilarityExampleSelectorは強力ですが、選択された例がすべて完璧であるとは限らないことに注意することが重要です。 将来的な改良には、フィルターやしきい値を追加して関連性の低い結果を除外し、最も適切な例だけがLLMに提供されるようにすることが含まれるかもしれません。 ## 精度テスト プロンプトとSQLクエリ生成のパフォーマンスを評価するために、一連のテストを設定し実行する必要があります。その目的は、LLMがユーザーの入力に基づいてSQLクエリを生成する際に、例題に基づいたfew shotを使用する場合と使用しない場合の、その精度を評価することである。 ### SQLクエリを生成する関数 まず、LLMを使って、提供されたコンテキスト、プロンプト、ユーザー入力、その他のパラメーターに基づいてSQLクエリーを生成する関数を定義します。 ```python def get_sql_from_text(context, prompt, user_input, use_few_shots, tables_vector_store, table_df, example_selector=None, example_prompt=None): relevant_tables = get_relevant_tables(user_input, tables_vector_store, table_df) context["table_info"] = "\n\n".join(relevant_tables) examples = example_selector.select_examples({"input": user_input}) if example_selector else [] context["examples_value"] = "\n\n".join([ example_prompt.invoke(x).to_string() for x in examples ]) model = ChatOpenAI(model="gpt-3.5-turbo", temperature=0) output_parser = StrOutputParser() chain_model = prompt | model | output_parser response = chain_model.invoke({ "top_k": context["top_k"], "table_info": context["table_info"], "examples_value": context["examples_value"], "input": user_input }) return response ``` ### プロンプトの実行 例文がある場合とない場合のプロンプトをテストします。 ```python # Prompt execution **with** few shots input = "Find all events in 2010 informing the Event Id and date, location city and state, aircraft manufacturer and model." response_with_few_shots = get_sql_from_text( context, prompt, user_input=input, use_few_shots=True, tables_vector_store=tables_vector_store, table_df=table_df, example_selector=example_selector, example_prompt=example_prompt, ) print(response_with_few_shots) ``` ```sql SELECT e.EventId, e.EventDate, e.LocationCity, e.LocationState, a.AircraftManufacturer, a.AircraftModel FROM Aviation.Event e JOIN Aviation.Aircraft a ON e.EventId = a.EventId WHERE Year(e.EventDate) = 2010 ``` ```python # Prompt execution **without** few shots input = "Find all events in 2010 informing the Event Id and date, location city and state, aircraft manufacturer and model." response_with_no_few_shots = get_sql_from_text( context, prompt, user_input=input, use_few_shots=False, tables_vector_store=tables_vector_store, table_df=table_df, ) print(response_with_no_few_shots) ``` ```sql SELECT TOP 3 "EventId", "EventDate", "LocationCity", "LocationState", "AircraftManufacturer", "AircraftModel" FROM Aviation.Event e JOIN Aviation.Aircraft a ON e.ID = a.Event WHERE e.EventDate >= '2010-01-01' AND e.EventDate < '2011-01-01' Utility Functions for Testing ``` 生成されたSQLクエリをテストするために、いくつかのユーティリティ関数を定義します。 ```python def execute_sql_query(cnx, query): try: cursor = cnx.cursor() cursor.execute(query) rows = cursor.fetchall() return rows except: print('Error running query:') print(query) print('-'*80) return None def sql_result_equals(cnx, query, expected): rows = execute_sql_query(cnx, query) result = [set(row._asdict().values()) for row in rows or []] if result != expected and rows is not None: print('Result not as expected for query:') print(query) print('-'*80) return result == expected ``` ```python # SQL test for prompt **with** few shots print("SQL is OK" if not execute_sql_query(cnx, response_with_few_shots) is None else "SQL is not OK") ``` ``` SQL is OK ``` ```python # SQL test for prompt **without** few shots print("SQL is OK" if not execute_sql_query(cnx, response_with_no_few_shots) is None else "SQL is not OK") ``` ``` error on running query: SELECT TOP 3 "EventId", "EventDate", "LocationCity", "LocationState", "AircraftManufacturer", "AircraftModel" FROM Aviation.Event e JOIN Aviation.Aircraft a ON e.ID = a.Event WHERE e.EventDate >= '2010-01-01' AND e.EventDate < '2011-01-01' -------------------------------------------------------------------------------- SQL is not OK ``` ### テストの定義と実行 一連のテストケースを定義し、実行します。 ```python tests = [{ "input": "What were the top 3 years with the most recorded events?", "expected": [{128, 2003}, {122, 2007}, {117, 2005}] },{ "input": "How many incidents involving Boeing aircraft.", "expected": [{5}] },{ "input": "How many incidents that resulted in fatalities.", "expected": [{237}] },{ "input": "List event Id and date and, crew number, age and gender for incidents that occurred in 2013.", "expected": [{1, datetime.datetime(2013, 3, 4, 11, 6), '20130305X71252', 59, 'M'}, {1, datetime.datetime(2013, 1, 1, 15, 0), '20130101X94035', 32, 'M'}, {2, datetime.datetime(2013, 1, 1, 15, 0), '20130101X94035', 35, 'M'}, {1, datetime.datetime(2013, 1, 12, 15, 0), '20130113X42535', 25, 'M'}, {2, datetime.datetime(2013, 1, 12, 15, 0), '20130113X42535', 34, 'M'}, {1, datetime.datetime(2013, 2, 1, 15, 0), '20130203X53401', 29, 'M'}, {1, datetime.datetime(2013, 2, 15, 15, 0), '20130218X70747', 27, 'M'}, {1, datetime.datetime(2013, 3, 2, 15, 0), '20130303X21011', 49, 'M'}, {1, datetime.datetime(2013, 3, 23, 13, 52), '20130326X85150', 'M', None}] },{ "input": "Find the total number of incidents that occurred in the United States.", "expected": [{1178}] },{ "input": "List all incidents latitude and longitude coordinates with more than 5 injuries that occurred in 2010.", "expected": [{-78.76833333333333, 43.25277777777778}] },{ "input": "Find all incidents in 2010 informing the Event Id and date, location city and state, aircraft manufacturer and model.", "expected": [ {datetime.datetime(2010, 5, 20, 13, 43), '20100520X60222', 'CIRRUS DESIGN CORP', 'Farmingdale', 'New York', 'SR22'}, {datetime.datetime(2010, 4, 11, 15, 0), '20100411X73253', 'CZECH AIRCRAFT WORKS SPOL SRO', 'Millbrook', 'New York', 'SPORTCRUISER'}, {'108', datetime.datetime(2010, 1, 9, 12, 55), '20100111X41106', 'Bayport', 'New York', 'STINSON'}, {datetime.datetime(2010, 8, 1, 14, 20), '20100801X85218', 'A185F', 'CESSNA', 'New York', 'Newfane'} ] }] ``` ### 精度評価 テストを実施し、精度を計算します。 ```python def execute_tests(cnx, context, prompt, use_few_shots, tables_vector_store, table_df, example_selector, example_prompt): tests_generated_sql = [(x, get_sql_from_text( context, prompt, user_input=x['input'], use_few_shots=use_few_shots, tables_vector_store=tables_vector_store, table_df=table_df, example_selector=example_selector if use_few_shots else None, example_prompt=example_prompt if use_few_shots else None, )) for x in deepcopy(tests)] tests_sql_executions = [(x[0], sql_result_equals(cnx, x[1], x[0]['expected'])) for x in tests_generated_sql] accuracy = sum(1 for i in tests_sql_executions if i[1] == True) / len(tests_sql_executions) print(f'Accuracy: {accuracy}') print('-'*80) ``` ### 結果 ```python # Accuracy tests for prompts executed **without** few shots use_few_shots = False execute_tests( cnx, context, prompt, use_few_shots, tables_vector_store, table_df, example_selector, example_prompt ) ``` ``` error on running query: SELECT "EventDate", COUNT("EventId") as "TotalEvents" FROM Aviation.Event GROUP BY "EventDate" ORDER BY "TotalEvents" DESC TOP 3; -------------------------------------------------------------------------------- error on running query: SELECT "EventId", "EventDate", "C"."CrewNumber", "C"."Age", "C"."Sex" FROM "Aviation.Event" AS "E" JOIN "Aviation.Crew" AS "C" ON "E"."ID" = "C"."EventId" WHERE "E"."EventDate" >= '2013-01-01' AND "E"."EventDate" < '2014-01-01' -------------------------------------------------------------------------------- result not expected for query: SELECT TOP 3 "e"."EventId", "e"."EventDate", "e"."LocationCity", "e"."LocationState", "a"."AircraftManufacturer", "a"."AircraftModel" FROM "Aviation"."Event" AS "e" JOIN "Aviation"."Aircraft" AS "a" ON "e"."ID" = "a"."Event" WHERE "e"."EventDate" >= '2010-01-01' AND "e"."EventDate" < '2011-01-01' -------------------------------------------------------------------------------- accuracy: 0.5714285714285714 -------------------------------------------------------------------------------- ``` ```python # Accuracy tests for prompts executed **with** few shots use_few_shots = True execute_tests( cnx, context, prompt, use_few_shots, tables_vector_store, table_df, example_selector, example_prompt ) ``` ``` error on running query: SELECT e.EventId, e.EventDate, e.LocationCity, e.LocationState, a.AircraftManufacturer, a.AircraftModel FROM Aviation.Event e JOIN Aviation.Aircraft a ON e.EventId = a.EventId WHERE Year(e.EventDate) = 2010 TOP 3 -------------------------------------------------------------------------------- accuracy: 0.8571428571428571 -------------------------------------------------------------------------------- ``` ### 結論 例(few shots)を使って生成されたSQLクエリの精度は、例なしで生成されたもの(85%対57%)に比べて約49%高くなりました。 ## ご参考 - https://python.langchain.com/v0.1/docs/expression_language/get_started/ - https://python.langchain.com/v0.1/docs/use_cases/sql/prompting/ - https://python.langchain.com/v0.1/docs/modules/model_io/prompts/composition/ [このノートブック]: https://github.com/musketeers-br/sqlzilla/blob/master/jupyter/SQLFromText-article.ipynb どうもありがとうございます @Mihoko.Iijima
記事
Shintaro Kaminaka · 2020年5月1日

仕様ファースト(APIファースト)アプローチによるREST APIの開発 

この記事では、REST API開発への仕様ファーストアプローチについて説明します。 従来のコードファーストREST API開発は次のようになります。 コードを書く RESTを有効にする ドキュメント化(REST APIとして) 仕様ファーストのアプローチでは同じ手順を行いますが、順序が逆になります。 ドキュメントを兼ねた仕様書を作成し、そこからRESTアプリの定型文を生成して、最後にビジネスロジックを書きます。 これは、次の理由でメリットがあります。 REST APIを使用したいと思っている外部開発者またはフロントエンド開発者向けの関連性のある有用なドキュメントが常に入手できます。 OAS(Swagger)で作成された仕様をさまざまなツールにインポートして、編集、クライアント生成、API管理、ユニットテスト、その他の多くのタスクの自動化または簡略化を行うことができます。 改善されたAPIアーキテクチャ。コードファーストアプローチではAPIはメソッドごとに開発されるため、開発者はAPIアーキテクチャ全体を簡単に見落としてしまう可能性があります。これに対し、仕様ファーストの開発者は通常、APIの消費者の立場としてAPIと対話するように強制されます。これは、よりクリーンなAPIアーキテクチャの設計に役立ちます。 開発の迅速化。すべての定型的なコードが自動的に生成されるため、コードを記述する必要はありません。後は、ビジネスロジックの開発をするだけです。 より高速なフィードバックループ。消費者はAPIをすぐに見ることができ、仕様を修正するだけで簡単に提案を行うことができます。 仕様ファーストのアプローチでAPIを開発しましょう! 計画 Swaggerで仕様を開発する Docker ローカルで オンラインで IRISに仕様を読み込む API管理REST API ^%REST クラス IRISに読み込んだ仕様を確認する 実装 さらなる開発 考慮事項 特別なパラメータ CORS IAMに仕様を読み込む 仕様を作成する 最初のステップは、当然のことながら仕様を作成することです。 InterSystems IRISはOpen API仕様(OAS)をサポートしています。 OpenAPI仕様 (以前のSwagger仕様)は、REST APIのAPI記述形式です。 OpenAPIファイルを使用すると、以下を含むAPI全体を記述することができます。 利用可能なエンドポイント(/users)および各エンドポイントでの操作(GET /users, POST /users) 各操作の操作パラメータ入出力 認証方法 連絡先情報、ライセンス、使用条件およびその他の情報。 API仕様はYAMLまたはJSONで記述できます。 この形式は学びやすく、人間と機械の両方が読み取れるようになっています。 完全OpenAPI仕様はGitHubにあります。OpenAPI 3.0仕様   - Swagger docs.から Swaggerを使用してAPIを記述します。 Swaggerを使用する方法はいくつかあります。 オンライン Docker:docker run -d -p 8080:8080 swaggerapi/swagger-editor ローカルインストール Swaggerをインストールまたは実行すると、次のウィンドウがWebブラウザーに表示されます。 左側では、API仕様を編集し、右側では、レンダリングされたAPIドキュメント/テストツールをすぐに確認できます。 最初のAPI 仕様をその中にロードします(YAML)。 これは、1つのGETリクエストを使用したシンプルなAPIで、指定された範囲の乱数を返します。   数学API仕様 swagger: "2.0" info: description: "Math" version: "1.0.0" title: "Math REST API" host: "localhost:52773" basePath: "/math" schemes: - http paths: /random/{min}/{max}: get: x-ISC_CORS: true summary: "Get random integer" description: "Get random integer between min and max" operationId: "getRandom" produces: - "application/json" parameters: - name: "min" in: "path" description: "Minimal Integer" required: true type: "integer" format: "int32" - name: "max" in: "path" description: "Maximal Integer" required: true type: "integer" format: "int32" responses: 200: description: "OK" 構成は次のとおりです。 APIおよび使用されているOASバージョンに関する基本情報。 swagger: "2.0" info: description: "Math" version: "1.0.0" title: "Math REST API" サーバーホスト、プロトコル(http、https)、およびWebアプリケーション名は次のとおりです。 host: "localhost:52773" basePath: "/math" schemes: - http 次に、パス(完全なURLは http://localhost:52773/math/random/:min/:max)とHTTPリクエストメソッド(get、post、put、delete)を指定します。 paths: /random/{min}/{max}: get: その後、リクエストに関する情報を指定します。 x-ISC_CORS: true summary: "Get random integer" description: "Get random integer between min and max" operationId: "getRandom" produces: - "application/json" parameters: - name: "min" in: "path" description: "Minimal Integer" required: true type: "integer" format: "int32" - name: "max" in: "path" description: "Maximal Integer" required: true type: "integer" format: "int32" responses: 200: description: "OK" この部分では、リクエストを定義します。 CORSに対してこのパスを有効にします(詳細は後述します) 概要と説明を提供します。 operationId は、仕様内の参照を許可します。これはまた、実装クラスで生成されたメソッド名でもあります。 produces - 応答フォーマット(text、xml、 jsonなど) parametersは、入力パラメータを指定します(URLまたは本文で)が、この場合は、2つのパラメータ、すなわち乱数ジェネレータの範囲を指定します。 responsesは、サーバーから可能な応答をリストします。 ご覧のとおり、このフォーマットはそれほど難しいものではありません。利用可能な機能は他にもたくさんありますが、仕様は次のとおりです。 最後に、定義をJSONとしてエクスポートします。 ファイルに移動→ 変換してJSON形式で保存します。 仕様は次のようになります。   数学API仕様 { "swagger": "2.0", "info": { "description": "Math", "version": "1.0.0", "title": "Math REST API" }, "host": "localhost:52773", "basePath": "/math", "schemes": [ "http" ], "paths": { "/random/{min}/{max}": { "get": { "x-ISC_CORS": true, "summary": "Get random integer", "description": "Get random integer between min and max", "operationId": "getRandom", "produces": [ "application/json" ], "parameters": [ { "name": "min", "in": "path", "description": "Minimal Integer", "required": true, "type": "integer", "format": "int32" }, { "name": "max", "in": "path", "description": "Maximal Integer", "required": true, "type": "integer", "format": "int32" } ], "responses": { "200": { "description": "OK" } } } } } } IRISに仕様を読み込む 仕様が完成したので、InterSystems IRISでこのREST APIの定型的なコードを生成できます。 この段階に進むには、次の3つが必要です。 RESTアプリケーション名:生成されたコードのためのパッケージ(たとえば math) JSON形式のOAS仕様:前のステップで作成しました WEBアプリケーション名:REST APIにアクセスするための基本パス( /mathの場合) コード生成に仕様を使用するには3つの方法があります。それらは基本的に同じ方法ですが、同じ機能にアクセスするためのさまざまな方法を提供します ^%RESTルーチン (インタラクティブターミナルセッションで Do ^%RESTを実行する)を呼び出す、 ドキュメント。 %RESTクラス(Set sc = ##class(%REST.API).CreateApplication(applicationName, spec)、非インタラクティブ)を呼び出す、 ドキュメント。 API管理のREST APIを使用する、ドキュメント。 ドキュメントには必要なステップが適切に記述されていると思うので、1つだけ選択してください。 2つの注意事項を追加します。 (1) と (2) の場合、動的オブジェクトにファイル名またはURLを渡すことができます。 (2) と (3)の場合、Webアプリケーションを作成するため、次のように追加の呼び出しをする必要があります。 set sc = ##class(%SYS.REST).DeployApplication(restApp, webApp, authenticationType) なので、今回のケースでは以下のようになります。 set sc = ##class(%SYS.REST).DeployApplication("math", "/math") authenticationType引数の値を%sySecurityインクルードファイルから取得、関連エントリは$$$Authe*なので、認証されていない アクセスパスの場合は $$$AutheUnauthenticated。 省略した場合、パラメーターはデフォルトでパスワード認証になります。   IRISに読み込んだ仕様を確認する アプリを正常に作成した場合、次の3つのクラスをもつ新しいmathパッケージが作成されているはずです。 Spec - 仕様をそのまま保存します Disp - RESTサービスが呼び出されたときに直接呼び出されます。 REST処理をラップし、実装メソッドを呼び出します。 Impl - RESTサービスの実際の内部実装を保持します。 このクラスのみを編集する必要があります。 クラスの詳細に関するドキュメント 。 実装 最初は、実装クラスmath.implには/random/{min}/{max}操作に対応する1つのメソッドのみが含まれます。 /// Get random integer between min and max<br/> /// The method arguments hold values for:<br/> /// min, Minimal Integer<br/> /// max, Maximal Integer<br/> ClassMethod getRandom(min As %Integer, max As %Integer) As %DynamicObject { //(Place business logic here) //Do ..%SetStatusCode(<HTTP_status_code>) //Do ..%SetHeader(<name>,<value>) //Quit (Place response here) ; response may be a string, stream or dynamic object } 小さな実装から始めましょう。 ClassMethod getRandom(min As %Integer, max As %Integer) As %DynamicObject { quit {"value":($random(max-min)+min)} } そして最後に、ブラウザでこのページを開いてREST APIを呼び出すことができます。 http://localhost:52773/math/random/1/100 出力は次のようになります。 { "value": 45 } また、Swaggerエディターで試してみるボタンとリクエストパラメータを入力しても、同じリクエストが送信されます。 おめでとうございます! 仕様ファーストアプローチで作成された最初のREST APIが公開されました。   さらなる開発 もちろん、APIは静的ではないので、新しいパスなどを追加する必要があります。 仕様ファースト開発では、先ず仕様を変更し、次にRESTアプリケーションを更新し(アプリケーションの作成と同じ呼び出し)、最後にコードを記述します。 仕様の更新は安全です。パスが仕様から削除されても、コードは影響を受けません。実装クラスでは、メソッドは削除されません。   考慮事項 追加の注意事項! 特別なパラメータ InterSystems は、swagger仕様に特別なパラメータを追加しました。 名前 データ型 デフォルト 場所 説明 x-ISC_DispatchParent クラス名 %CSP.REST 情報 ディスパッチクラスのスーパークラス。 x-ISC_CORS ブーリアン 偽 操作 このエンドポイント/メソッドの組み合わせに対するCORSリクエストをサポートする必要があることを示すフラグ。 x-ISC_RequiredResource アレイ   操作 RESTサービスのこのエンドポイントへのアクセスに必要な定義済みリソースとそのアクセスモード(resource:mode)のカンマ区切りリスト。 例:["%Development:USE"] x-ISC_ServiceMethod 文字列   操作 この操作を処理するためにバックエンドで呼び出されたクラスメソッドの名前。デフォルトはoperationIdで、これは通常適切です。   CORS CORSサポートを有効にする方法は3つあります。 1. x-ISC_CORSを真としてルートごとに指定する方法。 これは、私たちがMath REST APIで実行した方法です。 2. APIごとに追加する方法 Parameter HandleCorsRequest = 1; そして、クラスを再コンパイルします。 また、仕様の更新後も存続します。 3. (推奨)APIごとにカスタムディスパッチャースーパークラス (%CSP.RESTを拡張する) を実装し、CORS処理ロジックをそこに記述します。 このスーパークラスを使用するには、 x-ISC_DispatchParentを仕様に追加します。 IAMに仕様を読み込む 最後に、IAMに仕様を追加して、他の開発者向けに公開できるようにします。 IAMをまだ始めていない場合は、この記事を読んでみてください。 この記事はIAMを介したREST APIの提供についても説明しているため、ここでの説明は省きます。 InterSystems IRISインスタンスではなくIAMを指すようにするため、仕様のホストと ベースパスパラメータを変更した方が良いかも知れません。 IAM管理者ポータルを開き、 関連するワークスペースで[仕様]タブに移動します。 [仕様を追加]ボタンをクリックして、新しいAPIの名前を入力します(この例ではmath )。 IAMで新しいSpecを作成した後、編集 をクリックして仕様コードを貼り付けます(JSONまたはYAML。IAMではどちらでも構いません)。 ファイルのアップデートをクリックするのを忘れないでください APIが開発者向けに公開されました。 開発者ポータルを開いて、右上端にあるドキュメントをクリックします。 3つのデフォルトAPIに加えて、新しい Math REST APIが利用可能になっているはずです。 それを開きます。 開発者は、新しいAPIのドキュメントを参照して、同じ場所で試すことができます。   まとめ   InterSystems IRISは、REST APIの開発プロセスを簡素化し、仕様ファーストのアプローチにより、REST APIのライフサイクル管理をさらに迅速かつ容易にします。 このアプローチでは、クライアント生成、ユニットテスト、API管理など、さまざまな関連タスクに多様なツールを使用できます。   リンク OpenAPI 3.0 仕様 RESTサービスの作成 IAMで始める ICMドキュメント また、前のパート「 InterSystems API管理を使用してAPIの負荷を分散する」も確認してください 。
記事
Toshihiko Minamoto · 2023年4月18日

「妊娠トラッカ」アプリケーションを支えるIS技術

今回は、「IRIS for Health Contest」に応募するために、どのような技術を使ってアプリケーションを開発していたのか、その詳細を紹介したいと思います。 - OpenAPI仕様からのREST API生成 - APIとWebページを保護する役割ベースのアクセス(RBAC) - InterSystems FHIR サーバー 内容 - [アプリケーション概要](#application-overview) - [OpenAPI仕様からのREST API生成](#rest-api-generation-from-openapi-specification) - [APIとWebページを保護する役割ベースのアクセス(RBAC)](#role-based-access-control--rbac--to-protect-api-and-web-pages) * [REST APIの安全性確保](#securing-r-e-s-t-a-p-is) * [Webページの安全性確保](#securing-web-pages) * [リソースと役割の作成](#creating-resources-and-roles) - [InterSystems FHIR サーバー](#intersystems-fhir-server) ## アプリケーション概要 まず、それらの技術に支えられたアプリケーションを簡単に紹介します。 このアプリケーションは、妊娠中の女性が簡単に症状を報告できるように設計されています。このアプリケーションはレスポンシブであるため、モバイルデバイスを使用して症状を簡単に報告することができます。このような症状は、[FHIR Observation リソース](https://build.fhir.org/observation.html) の[InterSystems FHIR サーバー](https://docs.intersystems.com/services/csp/docbookj/DocBook.UI.Page.cls?KEY=PAGE_fas)を使って記録されます。. 患者と医師は、通常のリレーショナル・テーブルを使用し、[患者](https://build.fhir.org/patient.html) と [医師](https://www.hl7.org/fhir/practitioner.html) のFHIRリソースのIDを参照して連携しています。そのため、医師は患者がどのような症状を訴えているのかを確認することもでき、万が一の事態に迅速に対応することができます。 アプリケーションは、IRIS [リソース](https://docs.intersystems.com/irisforhealthlatest/csp/docbookj/DocBook.UI.Page.cls?KEY=GAUTHZ_RSRCS) と [役割](https://docs.intersystems.com/irislatest/csp/docbookj/DocBook.UI.Page.cls?KEY=AFL_RBAC)を使用して患者/医師を識別し、アクセス権限を制御します。 FHIRリソースは、アプリケーションのフロントエンドで利用可能な[REST API](https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=GREST_intro)によってアクセスされます。IRIS Interoperability Credentialsに格納されたAPI KEYを使用して、FHIRサーバーへのHTTPS接続が確立されます。 アプリケーションのWebリソースは、[IRIS Web Gateway](https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=GCGI_intro)で扱われます。 ![一般的なアプリケーションのアーキテクチャ](https://github.com/jrpereirajr/pregnancy-symp-tracker-fhir-app/raw/master/img/W3U0DcpPRr.png) ## OpenAPI仕様からのREST API生成 IRISプラットフォームでは、手動または[OpenAPI仕様](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md)を介してRESTインターフェースを定義することができます。 OpenAPIを使用することは、デザインファーストのアプローチ、容易な変更追跡、容易な文書化、強力なモデル、設計、モッキング、テストなどのための多くのツールなど、多くの利点があります。 そこで、IRIS REST Servicesを使って、OpenAPI仕様からコードを生成する方法に焦点を当てます。 まず、OpenAPIを使ってAPIを設計する必要があります。今回は、VS Codeの拡張機能である[OpenAPI (Swagger) Editor](https://marketplace.visualstudio.com/items?itemName=42Crunch.vscode-openapi)を使用しました。これは、エンドポイントやその他のOpenAPIリソースをVS Codeで直接作成するのに役立ちます。 ![VS Code用OpenAPI拡張子](https://github.com/jrpereirajr/pregnancy-symp-tracker-fhir-app/raw/master/img/j9GY3tSHq5.png) OpenAPI仕様でAPIを設計したら、それをJSONファイルに保存する必要があります。ここでは、[このファイル](https://github.com/jrpereirajr/pregnancy-symp-tracker-fhir-app/blob/master/src/openapi/pregnancy-symptoms-tracker.json)にAPIを保存しています。 これで、IRIS REST Services を使用して API 用のコードを生成する準備が整いました。これには3つのオプションがあります。 - REST service[/api/mgmnt](https://docs.intersystems.com/irislatest/csp/docbookj/DocBook.UI.Page.cls?KEY=GREST_apimgmnt)を使用 - [`^%REST`ルーチン](https://docs.intersystems.com/irislatest/csp/docbookj/DocBook.UI.Page.cls?KEY=GREST_routine)を使用 - [`%REST.API` class](https://docs.intersystems.com/irislatest/csp/docbookj/DocBook.UI.Page.cls?KEY=GREST_objectscriptapi)を使用 この記事では、最後の1つである `%REST.API` クラスを使用することにします。それでは、IRIS端末を開いて、以下のコードを実行してください。 ``` Set applicationName = "dc.apps.pregsymptracker.restapi" Set swagger = "/irisrun/repo/src/openapi/pregnancy-symptoms-tracker.json" ZW ##class(%REST.API).CreateApplication(applicationName, swagger, , .newApplication, .internalError) ZW newApplication ZW internalError ``` OpenApi仕様のJSONファイルの場所は、`swagger`パラメータで設定されます。 `applicationName` パラメータは、IRIS REST Servicesが生成されたコードを格納するパッケージ名です。 ![REST APIクラスを生成する](https://github.com/jrpereirajr/pregnancy-symp-tracker-fhir-app/raw/master/img/75zkIcliUr.png) 3つのクラスが生成されます。 - `spec.cls`: OpenAPI仕様のための単なるコンテナです。このクラスは編集しないでください。 - `impl.cls`: メソッドの実装を含むメインクラスです。このクラスは、APIロジックを開発するために、自分で編集することを意図しています。ヒント: OpenAPIのメソッドの名前は、必ずIRIS拡張属性 `operationId` を使って、[ ここで ](https://github.com/jrpereirajr/pregnancy-symp-tracker-fhir-app/blob/master/src/openapi/pregnancy-symptoms-tracker.json#L21)のように定義します。この属性を使用しない場合、IRIS はランダムな名前のメソッドを作成します。 - `disp.cls`: ディスパッチクラスは、IRIS で REST API を公開するために Web アプリケーションをバインドするクラスです。ヒント: このクラスを表示するには、生成されたアイテムを表示していることを確認します。このクラスを編集することもできますが、あまりお勧めできませんが、IRIS に任せてください。 ![REST API ディスパッチクラス](https://github.com/jrpereirajr/pregnancy-symp-tracker-fhir-app/raw/master/img/Q1iBkK3GHK.png) 最後の2つのパラメータ、 `newApplication` と `internalError` は出力パラメータで、それぞれ API が作成または更新されたかどうか、そして OpenAPI のパースやクラスの生成時に発生した可能性のあるエラーを返すものです。この情報をチェックするために書き出すだけです。 OpenAPI の仕様を更新した場合、コードを更新するために `CreateApplication` メソッドを再度実行する必要があります。 `impl` クラスに実装した以前のロジックコードはそのまま残し、IRIS REST Service が修正を行った箇所にはコメントを追加します。 ## APIとWebページを保護する役割ベースのアクセス(RBAC) 前述したように、このアプリケーションには、患者と医師の2種類のユーザーが存在します。そこで、この2種類のユーザー間でアプリケーションのリソースに対するアクセスルールを設計するために、リソースと役割を使用しました。 ユーザーは役割を与えられ、役割にはリソースへの権限があり、リソースは例えばREST APIのようなシステムリソースにアクセスするために必要であるべきです。 ### REST APIの安全性確保 IRIS REST Serviceでは、OpenAPIのIRIS拡張子である `x-ISC_RequiredResource` 属性によって、サービスにアクセスするために必要な権限を指定することができました。この属性は、API全体、または特定のエンドポイントに対して、次のように指定することができます: ```javascript "paths": { "/symptom": { "post": { "operationId": "PostSymptom", "x-ISC_RequiredResource": ["AppSymptoms:write"], "description": "患者さんが自分の症状を報告するために使用する", … "/doctor/patients": { "get": { "operationId": "GetDoctorPatientsList", "x-ISC_RequiredResource": ["AppAccessDoctorPatients:read"], "description": "現在ログインしている医師の患者を取得する", … ``` OpenAPI仕様でAPIクラスを生成した後 - [ 前に説明したように ](#rest_api_generation_from_openapi_specification), IRIS REST Serviceが`x-ISC_RequiredResource`制約を`disp`クラスでどのように実装しているかを見ることができます: ```objecscript ClassMethod PostSymptom() As %Status { Try { Set authorized=0 Do { If '$system.Security.Check("AppSymptoms","write") Quit Set authorized=1 } While 0 If 'authorized Do ##class(%REST.Impl).%ReportRESTError(..#HTTP403FORBIDDEN,$$$ERROR($$$RESTResource)) Quit … } Catch (ex) { Do ##class(%REST.Impl).%ReportRESTError(..#HTTP500INTERNALSERVERERROR,ex.AsStatus(),$parameter("dc.apps.pregsymptracker.restapi.impl","ExposeServerExceptions")) } Quit $$$OK } ``` RBACを使ったAPIの保護方法については、[このページ](https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=GREST_SECURING)をご覧ください。 ### Webページの安全性確保 このアプリケーションでは、Web アプリケーションを実装するために CSP ページを使用しました。この技術は、現在の SPA に比べて古いと考えられていますが、それでもまだ利点があります。 例えば、ユーザーがページにアクセスするために、特定の役割を持つことを要求することができます。つまり、REST API のエンドポイントを保護することをまとめると、先に述べたように、アプリケーションに追加のセキュリティ・レイヤーを定義することができるのです。 ユーザがシステムにログインするとき、そのユーザに役割が割り当てられている場合、IRISはそのユーザに役割を割り当てます。このような役割は、CSP コンテキストで `$ROLE` コンテキスト変数を通してアクセスすることができ、ユーザに割り当てられた特定の役割を要求するために使用することができます。 ``` <script language="cache" method="OnPreHTTP" arguments="" returntype="%Boolean"> Do ##class(dc.apps.pregsymptracker.util.Util).AssertRole("AppPatient") Return 1 ``` ``` <script language="cache" method="OnPreHTTP" arguments="" returntype="%Boolean"> Do ##class(dc.apps.pregsymptracker.util.Util).AssertRole("AppDoctor") Return 1 ``` ```objecscript ClassMethod AssertRole(pRole As %String) { If ('$Find($ROLES, pRole)){ Set %response.Redirect = "NoPrivilegesPage.csp" } } ``` もし、現在のユーザーが `patient.csp` ページを評価するときに `AppPatient` 役割を持っていない場合、IRIS Web サーバはそのユーザーを `NoPrivilegesPage.csp` ページにリダイレクトし、ユーザーにセキュリティ問題を通知するメッセージを表示します。doctor.cps`ページも同様ですが、今度は `AppDoctor` 役割が必要です。 この例では、`AppPatient`と`AppDoctor`の2つの役割を持つことができます。つまり、そのユーザーは患者であると同時に医師でもあり、両方のページにアクセスすることができるのです。 ### リソースと役割の作成 IRISポータルで[リソース](http://localhost:64756/csp/sys/sec/%25CSP.UI.Portal.Resources.zen), [役割](http://localhost:64756/csp/sys/sec/%25CSP.UI.Portal.Roles.zen) を作成し、[ユーザー](http://localhost:64756/csp/sys/sec/%25CSP.UI.Portal.Users.zen) に割り当てることができます - これは簡単なことです。しかし、ここではプログラムでそれらを作成する方法を紹介したいと思います: ```objecscript ClassMethod CreateResources() { Do ..Log("アプリケーションリソースを作成する...") Set ns = $NAMESPACE Try { ZN "%SYS" Do $CLASSMETHOD("Security.Resources", "Delete", "AppSymptoms") Do $CLASSMETHOD("Security.Resources", "Delete", "AppAccessDoctorPatients") $$$TOE(st, $CLASSMETHOD("Security.Resources", "Create", "AppSymptoms", "患者の症状", "RWU", "")) $$$TOE(st, $CLASSMETHOD("Security.Resources", "Create", "AppAccessDoctorPatients", "患者のアクセス権", "RWU", "")) } Catch(e) { ZN ns Throw e } ZN ns } ClassMethod CreateRoles() { Do ..Log("アプリケーション役割を作成する...") Set ns = $NAMESPACE Try { ZN "%SYS" Do $CLASSMETHOD("Security.Roles", "Delete", "AppPatient") Do $CLASSMETHOD("Security.Roles", "Delete", "AppDoctor") $$$TOE(st, $CLASSMETHOD("Security.Roles", "Create", "AppPatient", "アプリケーション での患者の役割", "AppSymptoms:RWU", "")) $$$TOE(st, $CLASSMETHOD("Security.Roles", "Create", "AppDoctor", "アプリケーション での医師の役割", "AppSymptoms:RWU,AppAccessDoctorPatients:RWU", "")) } Catch(e) { ZN ns Throw e } ZN ns } ClassMethod CreateUsers() { Do ##class(dc.apps.pregsymptracker.util.Setup).Log("サンプルユーザーを作成する...") //ある患者 &SQL(drop user MarySmith) &SQL(create user MarySmith identified by 'marysmith') &SQL(grant %DB_IRISAPP, %DB_IRISAPPSECONDARY, AppPatient to MarySmith) &SQL(grant select on schema dc_apps_pregsymptracker_data to MarySmith) //他患者 &SQL(drop user SuzieMartinez) &SQL(create user SuzieMartinez identified by 'suziemartinez') &SQL(grant %DB_IRISAPP, %DB_IRISAPPSECONDARY, AppPatient to SuzieMartinez) &SQL(grant select on schema dc_apps_pregsymptracker_data to SuzieMartinez) //ある医師 &SQL(drop user PeterMorgan) &SQL(create user PeterMorgan identified by 'petermorgan') &SQL(grant %DB_IRISAPP, %DB_IRISAPPSECONDARY, AppDoctor to PeterMorgan) &SQL(grant select on schema dc_apps_pregsymptracker_data to PeterMorgan) // 患者である医師 &SQL(drop user AnneJackson) &SQL(create user AnneJackson identified by 'annejackson') &SQL(grant %DB_IRISAPP, %DB_IRISAPPSECONDARY, AppDoctor, AppPatient to AnneJackson) &SQL(grant select on schema dc_apps_pregsymptracker_data to AnneJackson) } ``` ## InterSystems FHIR サーバー [InterSystems FHIR サーバー](https://docs.intersystems.com/services/csp/docbook/DocBook.UI.Page.cls?KEY=PAGE_fas) は、IRIS for Health と同じように、FHIR リソースへのアクセスをクラウド上で提供するサービスです。 FHIR ServerではOAuth2が可能で、[SMART on FHIR JavaScript Library](http://docs.smarthealthit.org/client-js/) などのライブラリを使ってアプリケーションから直接FHIRリソースにアクセスできますが、このアプリケーションでは、FHIR Serverをメインデータリポジトリとして使用しながら、IRISにローカルに保存されているメタデータで制御するハイブリッドアプローチを選択しました。 そこで、バックエンドがFHIR ServerでFHIRトランザクションを実行するために使用するFHIRクライアントを作成しました。このクライアントは、サーバーが生成したAPI KEYを使用して、FHIR ServerへのHTTPSコールを実行するために、`%Net.HttpRequest`を使用して実装されています。 これはHTTPクライアントのコードで、 `` を使って基本的なHTTP動詞を実装しています。 ```objecscript Class dc.apps.pregsymptracker.restapi.HTTPClient Extends %RegisteredObject { Property Request As %Net.HttpRequest; Property Server As %String; Property Port As %String; Property UseHTTPS As %Boolean; Property SSLConfig As %String; Property APIKeyCred As %String; Method CreateRequest() { Set ..Request = ##class(%Net.HttpRequest).%New() Set ..Request.Server = ..Server Set ..Request.Port = ..Port Set ..Request.Https = ..UseHTTPS If (..UseHTTPS) { Do ..Request.SSLConfigurationSet(..SSLConfig) } } Method SetHeaders(headers As %DynamicObject) { Set headersIt = headers.%GetIterator() While (headersIt.%GetNext(.headerName, .headerValue)) { Do ..Request.SetHeader(headerName, headerValue) } } Method GetApiKeyFromEnsCredentials() As %String { Set apiKeyCred = ..APIKeyCred $$$TOE(st, ##class(Ens.Config.Credentials).GetCredentialsObj(.apiKeyCredObj, "", "Ens.Config.Credentials", apiKeyCred)) Return apiKeyCredObj.Password } Method HTTPGet(pPath As %String) As %Net.HttpResponse { Do ..CreateRequest() $$$TOE(st, ..Request.Get(pPath)) Set response = ..Request.HttpResponse Return response } Method HTTPPost(pPath As %String, pBody As %DynamicObject) As %Net.HttpResponse { Do ..CreateRequest() Do ..Request.EntityBody.Clear() Do ..Request.EntityBody.Write(pBody.%ToJSON()) $$$TOE(st, ..Request.Post(pPath)) Set response = ..Request.HttpResponse Return response } Method HTTPPut(pPath As %String, pBody As %DynamicObject) As %Net.HttpResponse { Do ..CreateRequest() Do ..Request.EntityBody.Clear() Do ..Request.EntityBody.Write(pBody.%ToJSON()) $$$TOE(st, ..Request.Put(pPath)) Set response = ..Request.HttpResponse Return response } Method HTTPDelete(pPath As %String) As %Net.HttpResponse { Do ..CreateRequest() $$$TOE(st, ..Request.Delete(pPath)) Set response = ..Request.HttpResponse Return response } } ``` そしてこれがFHIRクライアントのコードで、HTTPクライアントを拡張し、CreateRequestメソッドをオーバーライドしてHTTPコールにFHIRサーバーのAPIキーを自動的に付加しています。 ```objecscript Class dc.apps.pregsymptracker.restapi.FHIRaaSClient Extends dc.apps.pregsymptracker.restapi.HTTPClient { Method CreateRequest() { Do ##super() Do ..SetHeaders({ "x-api-key" : (..GetApiKeyFromEnsCredentials()) }) } } ```
記事
Mihoko Iijima · 2021年4月15日

アプリケーションでロックタイムアウトエラーが発生する理由

これは InterSystems FAQ サイトの記事です。 このエラーが発生する原因としては、アプリケーションの中で既に他のプロセスからロック対象リソースがロックされていて、何らかの理由でそのロックが解放されていないケースが考えられます。 他のプロセスがロックしている兆候がない場合は、ロックテーブルの空き領域が不足しているケースが想定されます。その場合は、メッセージログ(コンソールログ)に LOCK TABLE FULL のメッセージが出力されます。 トランザクション処理を行なっている場合には、ロック解放の延期が影響しているケースも考えられます。トランザクションとロック解放の延期については、以下のドキュメントをご参照下さい。 ドランザクション処理について【IRIS】 ドランザクション処理について また、トランザクション中に、同一テーブルに対する大量レコードのSQL 文による更新がある場合、ロックしきい値(既定値は1000)に到達してロックエスカレーションが発生し、その結果として、テーブルロック状態になっている可能性もあります。 このように、ロックタイムアウトエラーの原因は幾つか考えられますので、まずは、管理ポータル(バージョン2010.2以前では、[システム管理ポータル])の、ロックメニューにて、現在のロックの状態をご確認下さい。 【バージョン2011.1以降】管理ポータル: [システムオペレーション] > [ロック] 【バージョン2010.2以前】システム管理ポータル: [運用] > [ロック] 関連する記事:SQLのUPDATEやDELETEで、ある程度の数のレコードを一括更新するとSQLエラー -110 が発生します。
記事
Hiroshi Sato · 2024年9月12日

適正なロックテーブルサイズの算出方法

これは InterSystems FAQ サイトの記事です。 ロックテーブルの1エントリは管理領域の固定512 bytesとロック文字列情報などの可変領域から構成されます。 可変領域はロック対象のグローバルノード名に関連する情報に必要な長さ(bytes)になります。 1つのLockコマンドにつき、上記で示した長さのデータが必要です。 そしてその可変領域に必要なデータ長は、ロック対象のグローバルノード名(^xxx(xxx,xxx)) の長さに見合う16,32,64,128,256,…bytesのバケットの長さになります。 例えばロック対象のグローバルノード名が^xxx(123,"data")とすると、 ^xxx(123,"data")にデータのロケーション等のデータが付加されたものがその可変領域となり、32byteまたは64bytes(データロケーションが相応に長い場合)のバケットを使用しますので、 このロックで使用するデータサイズが、64byteのバケットを使用すると仮定すると、 512(固定領域)+ 64 (可変領域)= 576 bytes となります。 これを基本とし、システムのピーク時に保持することが想定されるロック件数を掛け合わせることで必要なロックテーブルのサイズはある程度は想定することができますが、グローバルノード名に関連する情報が可変であるために正確な見積もりは、一般的には困難です。 現実的には、予めおおよその想定使用量より大きめのサイズを仮設定し、運用状況を定期的に観察して、ロックサイズの最大値をモニターしながら調整していくことを推奨します。 ロックサイズの最大値をモニターする方法は、カスタマーサポートまでお問い合わせください。
記事
Tomoko Furuzono · 2021年6月15日

IISでRESTを動かす場合の設定方法

これは、InterSystems FAQサイトの記事です。 IISおよびWebゲートウェイの設定方法は以下のとおりです。 (1) IIS構成で以下の設定を行いますアプリケーション /rest を作成し、ハンドラーマッピングで * = CSPms のみ定義します。 IIS構成 > Default Website > アプリケーション /rest > "ハンドラーマッピング" をダブルクリック 要求パス: * モジュール: CSPms 名前: CSPGW (任意の名前) 要求の制限 > [要求のマップ先が次の場合のみハンドラーを呼び出す] のチェックはオフにします。※こちらの設定はデフォルトでは「チェックあり」になっていますのでご注意ください。 この構成により、IIS は /rest アプリケーションをWebゲートウェイに転送します。 (2) Webゲートウェイの構成で以下の設定を行いますWeb Gateway のアプリケーションアクセス から /rest を追加します。→既存アプリケーションをクリックしてコピーし、アプリケーションパスを /rest に設定&IRISサーバを指定します。 この構成により、Webゲートウェイは /rest アプリケーションをIRISサーバに転送します。 (3) IRISサーバの構成で以下の設定を行います管理ポータルを開き、 システム構成 > セキュリティ > アプリケーション > ウェブ・アプリケーションより /rest アプリケーションを追加します。RESTのクラスをディスパッチクラスに指定します。 この構成により、IRIS は /rest アプリケーションを対象ネームスペースに転送し、対象ディスパッチクラスを呼び出します。 (4) IIS経由で以下のように呼び出します。http://localhost/rest/test サンプルコード: Class User.MyREST Extends %CSP.REST{XData UrlMap [ XMLNamespace = "http://www.intersystems.com/urlmap" ]{<Routes><Route Url="/test" Method="GET" Call="test"/>Routes>} ClassMethod test() As %Status{set %response.ContentType="application/json"set res={"abc":"123"}write res.%ToJSON()quit $$$OK} }
お知らせ
Toshihiko Minamoto · 2022年10月4日

その質問は 「重要な質問」ですか?

開発者の皆さんこんにちは。 開発者コミュニティの新たな機能として開発者による重要な質問をご紹介します。 インターシステムズデータプラットフォームに関連する質問のうち、最もインパクトがあると思われるものに「Key Question」タグを追加します。 毎月、インターシステムズエキスパートがそのような質問を選び、その著者は栄誉ある賞を受賞することになります。 どのような栄誉なのかはつづきをご覧ください。 Key Questionタグとは? InterSystems Data Platforms を使用したソリューションの開発、テスト、デプロイ、管理などに関する質問のうち、最も洞察に満ちた、興味深い、難問、重要などと思われるものに焦点を当てたタグです。 誰が決定するの? インターシステムズエキスパートが開発者コミュニティの質問をレビューし、Key Questionとするかどうかを決定します。 どのような質問をレビューするの? A開発者コミュニティ上のすべての質問は考慮されます。 Key Questions が更新頻度は? 毎月、エキスパートがKey Questionタグをつける質問を選びます。 どのような栄誉が与えられるの? A Key Questionと呼ばれる新たなバッジがグローバルマスターズで与えられます。 開発者コミュニティでのハイライトとして@DC.Administration からKey Questionとして選ばれた質問と共にコメントがスレッドに出されます。 質問で Inquisitive Summer on Developer Community によるポイント2倍をゲットしましょう!もしかしたらあなたの質問がエキスパートにKey Questionとして選ばれるかもしれません。 Key Questionタグを購読して 今後にご期待ください!​​
記事
Mihoko Iijima · 2023年2月16日

DX Jetpack for VS Code(VSCodeを使用するIRIS開発者のエクスペリエンスを向上させるツール):デベロッパーツールコンテスト2023入賞作品のご紹介

開発者の皆さん、こんにちは! InterSystems デベロッパーツールコンテスト2023 の21の応募作品の中から、Experts Nomination 第2位に輝いた @John Murray さんの DX Jetpack for VS Code (VSCodeを使用するIRIS開発者のエクスペリエンスを向上させるツール)についてご紹介します。 開発された @John Murray さんが書かれた記事「Introducing DX Jetpack for VS Code」には、3つの新しいエクステンションについて紹介されていますが、ここでは、gj :: codeSpex と ObjectScript Class View の使用例をご紹介します。 まずは、VSCodeをご用意ください。 Extentionの検索窓で、gj と入力する以下の表示になります。 この中から、をインストールしてみました。 試しにクラス定義を作成してみたところ、行番号右隣に吹き出しのマークが登場し、クリックするとクラス定義で使用しているデータタイプの解説が表示されました。 クラスメソッドの場合は、そのメソッドで使用している引数や戻り値のデータタイプを表示してくれました。 クラスリファレンスをいちいち開いて確認しなくても、エディタ上で確認ができて便利ですね。 続いて、ObjectScript Class Viewをインストールしてみます。Extensionの検索窓に「ObjectScript」と記入すると候補が出てきます。 コードエディタ上で参照したいクラスを右クリックし、を選択すると、選択したクラスの継承関係とそのクラスメンバが画面左に表示されます。 これも、どんなメンバをクラスが保持しているか確認するのに、クラスリファレンスを開かなくても確認できるので便利ですね! VSCode上で自由にインストールできますので、ぜひ皆さんのお手元でもお試しください!
お知らせ
Seisuke Nakahashi · 2024年10月4日

近似最近傍探索(ANN)インデックスによるベクトル検索高速化: 早期アクセスプログラムで入手可能

InterSystems IRIS の新バージョンに、 Hierarchical Navigable Small World (HNSW) インデックス・アルゴリズムに基づく新しい近似最近傍探索 (ANN) インデックスが搭載されました。こちらは、ベクトル検索 早期アクセスプログラム で入手いただけます。これにより、大規模なベクトルデータセットに対して非常に効率の良い近似最近傍探索が可能となり、クエリパフォーマンスとスケーラビリティが大幅に向上しました。 HNSW アルゴリズムは、グラフベース構造を利用して高次元データのベクトル検索を最適化するよう設計されており、大規模なベクトル集合における近似近傍探索を高速化します。HNSW によって、レコメンデーションシステム、自然言語処理、その他の機会学習アプリケーションなどすべてにおいて検索時間が大幅に短縮します。 HNSWの主な利点: • データセットサイズ増加後も、より高速な検索が可能 • 高精度をたもちながら、メモリ使用量を削減 • 既存の IRIS ベクトル検索とのシームレスな統合 最新バージョンのトライアル 最新バージョンは、ベクトル検索 早期アクセスプログラム に参加いただくことで入手いただけます。ご興味のある方は、プログラムページから登録し、新バージョンをダウンロードしてお試しください。ベクトル検索機能を向上するためには、みなさまからのフィードバックがとても大切です! ぜひアプリケーションのパフォーマンス向上を追い求め、コミュニティのみなさんと情報をシェアしてください。お試しの中でご質問やフィードバックがありましたら、@Thomas Dyar までご連絡ください。
記事
Toshihiko Minamoto · 2020年12月7日

Arduino と RFID を使用したユーザー認証

    以前の記事では Arduino を使い始め、最終的には気象観測所のデータを表示できるようになりました。 この記事ではさらに掘り下げ、InterSystems Caché アプリケーションに対して RFID カードと Arduino を介した認証をセットアップします。   認証の委任 Caché には認証コードの書き込みを許可することで、[認証を委任](http://docs.intersystems.com/cache20161/csp/docbook/DocBook.UI.Page.cls?KEY=GCAS_delegated)するための仕組みがあります。 この仕組みを有効にするには、次の手順を実行する必要があります。 ZAUTHENTICATE ルーチンにユーザー認証コードを記述します。 このルーチンにはユーザー名/パスワードの取得、それらの検証と権限の割り当て、パスワード変更、トークン生成の 4 つのエントリポイントがあります。 詳細については、以下をお読みください。 1. Caché で委任認証を有効にします([SMP] → [System Administration] → [Security] → [System Security] → [Authentication/CSP Session Options] を開き、[Allow Delegated authentication] ボックスにチェックを入れて設定を保存します)。 2. 関連するサービスかアプリケーションの委任認証を有効にします(前者の場合は [SMP] → [Menu] → [Manage Services] → [Service] → [Allowed Authentication Methods] → [Delegated] を選択 → [Save]、後者の場合は [SMP] → [Menu] → [Manage Web Applications] → [Application] → [Allowed Authentication Methods] → [Delegated] を選択 → [Save])。 仕組み 委任認証は、委任認証が有効になっているサービスや Web アプリケーションに対してユーザーが認証される際に発生します。 1. ZAUTHENTICATE ルーチンが呼び出されます。 このルーチンのコードはユーザーによって書かれたものであり、[OS](http://docs.intersystems.com/cache20161/csp/docbook/DocBook.UI.Page.cls?KEY=RCOS_fzf-1#RCOS_B78412) への呼び出しを含む任意の Caché ObjectScript コードである可能性があります。 2. 次のステップは、ZAUTHENTICATE の呼び出しが成功したかどうかによって決まります。 ZAUTHENTICATE の呼び出しが成功し、ユーザーが ZAUTHENTICATE で認証されたのが初めてだった場合は「委任されたユーザー」が作成されます。 ZAUTHENTICATE がロールやその他の権限をユーザーに割り当てた場合は、それらがユーザープロパティになります。 ZAUTHENTICATE の呼び出しが成功し、ユーザーが ZAUTHENTICATE で認証されたのが初めてではなかった場合はそのユーザーのレコードが更新されます。 ZAUTHENTICATE の呼び出しが成功しなかった場合、ユーザーはアクセスエラーを受け取ります。 インスタンスとサービスで 2 要素認証が有効になっている場合は、ユーザーの電話番号と事業者の検索が開始されます。 これらの情報が入力されている場合は、2 要素認証が実行されます。 入力されていない場合は、ユーザーは認証されません。 ユーザー情報の出処は? 次のように、アプリケーション/サービスで有効になっている認証方法に応じた 2 つの認証方法があります。 * 委任: ユーザー名/パスワードは ZAUTHENTICATE ルーチン(GetCredentials エントリポイント)から取得され、ZAUTHENTICATE を使用して検証されます([ユーザータイプ](http://docs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KEY=GCAS_users#GCAS_users_types): 委任)。 委任およびパスワード: ユーザー名/パスワードは GetCredentials から取得されますが、標準の Caché ツールを使用してチェックされます(ユーザータイプ: Caché)。 次に、ZAUTHENTICATE ルーチンとそのエントリポイントを見てみましょう。 ZAUTHENTICATE これはメインルーチンであり、次の 4 つのエントリポイントで構成されています。 GetCredentials このエントリポイントはサービスで委任認証が有効になっている場合に呼び出され、ユーザーにユーザー名/パスワードの入力を求める代わりに呼び出されます。 このルーチンのコードは、ユーザー名とパスワードを(何らかの方法で)取得します。 その後、(このエントリポイントの外部で)受信したユーザー名とパスワードはユーザーから通常の方法で入力されたかのように認証されます。 ユーザー名とパスワードは、キーボードからの入力、API、外部デバイスを使用したスキャンなど、任意の方法で取得できます。 この記事では、RFID カードを使用した認証を実装します。 このエントリポイントはステータスを返します。ステータスがエラーの場合は監査ログに記録され、認証試行は拒否されます。 ただし、エラーステータス $SYSTEM.Status.Error($$$GetCredentialsFailed) が返された場合は、例外的に通常のユーザー名/パスワードの入力が続きます。 シグネチャは次のとおりです。 GetCredentials(ServiceName, Namespace, Username, Password, Credentials) Public { } 説明: * ServiceName – 接続が確立されるサービスの名前 * Namespace – ネームスペース(接続時に指定されている場合) * Username – ユーザー名 * Password – パスワード Credentials – 現在使用されていません このエントリポイントの重要な機能について説明します。 委任認証とパスワード認証の両方がサービスやアプリケーションで有効になっている場合、ユーザー名とパスワードは GetCredentials エントリポイントを介して受信されますが、それらの情報は標準のパスワード認証に使用されます(ユーザーが手動で入力した場合と同じ)。また、認証が成功した場合のユーザーは委任ユーザーではなく通常の Cache ユーザーになります。 ZAUTHENTICATE 初回認証が成功すると、ZAUTHENTICATE はロールやその他のユーザープロパティを定義します。 初回認証以外の場合はプロパティが更新されます(例えば、Roles はログインのたびに指定する必要があります)。 そのために、Properties 配列のプロパティを定型化したコードで設定する必要があります。 シグネチャは以下のとおりです。 ZAUTHENTICATE(ServiceName, Namespace, Username, Password, Credentials, Properties) Public { } Properties 配列の説明: * Properties("Comment") — コメント * Properties("FullName") — 氏名 * Properties("NameSpace") — 初期ネームスペース * Properties("Roles") — カンマ区切りのロールのリスト * Properties("Routine") — 初期ルーチン * Properties("Password") — パスワード * Properties("Username") — ユーザー名 * Properties("PhoneNumber") — ユーザーの電話番号 * Properties("PhoneProvider") — 電話会社 Properties("AutheEnabled") — 標準の 2 要素認証を有効化します(この目的のために、$$$AutheTwoFactorSMS に等しい値を設定する必要があります) ChangePassword ユーザーパスワードを変更するためのエントリポイントです。シグネチャは次のとおりです。 ChangePassword(Username, NewPassword, OldPassword, Status) Public { } 説明: * NewPassword — 新しいパスワード * OldPassword — 古いパスワード Status — パスワード変更の結果 SendTwoFactorToken 標準の 2 要素認証で使用されるものです。 リクエストの形式と認証トークンを指定します。 シグネチャは以下のとおりです。 SendTwoFactorToken(Username, ServiceName,Namespace,Application,Credentials,SecurityToken,TwoFactorTimeout,UserPhoneNumber) Public { } 説明: * Application — ユーザーが接続している CSP アプリケーションまたはルーチン * SecurityToken — ユーザーに送信されるトークン * TwoFactorTimeout — トークンの有効期限が切れる時間 UserPhoneNumber — ユーザーの電話番号 例 まずは簡単な例から始めましょう。Windows での Caché ターミナルを担う %Service_Console サービスは、ユーザー名とパスワードの入力をユーザーに要求します。 このサービスに対して委任認証を有効にしましょう。 以下は、ユーザーにユーザー名とパスワードの入力を要求する ZAUTHENTICATE ルーチン(%SYS ネームスペース内)です。 ZAUTHENTICATE(ServiceName, Namespace, Username, Password, Credentials, Properties) PUBLIC { #Include %occStatus Quit $$$OK } GetCredentials(ServiceName, Namespace, Username, Password, Credentials) Public { #Include %occErrors #Include %occStatus Do ##class(%Prompt).GetString("USER:",.Username) Do ##class(%Prompt).GetString("PASS:",.Password) Quit $$$OK } ターミナルの場合、これは通常のユーザー名認証と同じように見えます。 >USER: _SYSTEM >PASS: SYS RFID それでは、RFID による認証を見てみましょう。 考え方は単純で、Caché が暗号化されたユーザー名とパスワードをカードに書き込み、認証中に Caché がカードをスキャンして復号化し、受け取ったユーザー名とパスワードを認証に使用するというものです。 まず、Arduino Uno と [RFID-RC522](http://playground.arduino.cc/Learning/MFRC522) モジュールの回路図をご覧ください。 ![](/sites/default/files/inline/images/tutorial_rfid.jpg) [MF522](https://github.com/miguelbalboa/rfid) ライブラリを使用した [C のコードはここにあります](https://github.com/intersystems-ru/ArduinoSnippets/blob/master/Delegated/Delegated.ino)。 このコードでは、COM ポート経由で次の 2 つのコマンドを受信できます。 * Get – RFID カードのブロック 2 / 4 / 5 / 6 の内容が COM ポートに渡されます Set@bloc2@bloc4@bloc5@bloc6 — ブロック 2 / 4 / 5 / 6 の値が受信したデータに置き換えられます Caché 側には [Arduino.Delegate](https://github.com/intersystems-ru/ArduinoSnippets/blob/master/Arduino/Delegated.cls.xml) クラスがあり、その中に次の 2 つの対応するエントリポイントがあります。 * SetCredentials — ユーザー名とパスワードの入力を取得し、それをシステムに格納されているキーを使用して AES 暗号化で暗号化し、RFID カードに書き込みます。 GetCredentials — カードから暗号化テキストを受信して復号化し、ユーザー名、パスワード、および操作のステータスを返します。 また、GetCredentials を使用して Arduino.Delegated クラスを呼び出す ZAUTHENTICATE ルーチンは以下のとおりです。 ZAUTHENTICATE(ServiceName, Namespace, Username, Password, Credentials, Properties) PUBLIC { #Include %occStatus Quit $$$OK } GetCredentials(ServiceName, Namespace, Username, Password, Credentials) Public { #Include %occErrors #Include %occStatus Quit ##class(Arduino.Delegated).GetCredentials(.Username, .Password) } これで準備完了です! 組み立て後のデバイスは次のようになります。 ![](/sites/default/files/inline/images/8f7b06a6e43640d99569c80c3e0496fb.jpg) 次のようにターミナルでシステム暗号化キーを設定します(%SYS ネームスペースと Arduino.Delegated クラスを使用できる必要があります)。 Do ##class(Arduino.Delegated).InitEncryption(Key, IV) ここで、Key は暗号化キー、IV は初期化ベクトルです。 これらは、ユーザー名とパスワードを暗号化するために使用されます。 コマンドを使用して認証するには、Arduino を Caché に接続し、カードに情報を書き込みます。 Do ##class(Arduino.Delegated).SetCredentials("_SYSTEM", "SYS") 適切なサービスまたは Web アプリケーション(端末やシステム管理ポータルなど)で委任認証とパスワード認証を有効にすると、カードを RFID カードリーダーにかざすことで認証できるようになります。 考えられる機能強化 * [マネージド暗号化キー](http://docs.intersystems.com/cache20161/csp/docbook/DocBook.UI.Page.cls?KEY=GCAS_encrypt#GCAS_encrypt_mgmt)を使用してユーザー名とパスワードを暗号化すると、セキュリティを強化できます。 * 2 要素認証を使用すると、セキュリティを強化できます。具体的には、先にユーザー名とパスワードのペアを取得してから、ユーザーに固有のキーが格納されているカードを読み取ります。 次に、受信したキーをシステムに格納されている特定ユーザーのキーで確認する必要があります。 任意のユーザーデータを格納する方法は、InterSystems のコミュニティで[議論されています](https://community.intersystems.com/post/best-practices-store-user-informationsettings-cach%C3%A9)。 それぞれ 15 文字を超えるユーザー名とパスワードを格納する機能を追加します。 まとめ 柔軟性の高い Caché の認証システムを使えば、任意のユーザー認証ロジックを実装できます。 リンク * [ドキュメント](http://docs.intersystems.com/cache20161/csp/docbook/DocBook.UI.Page.cls?KEY=GCAS_delegated) * [GitHub リポジトリ](https://github.com/intersystems-ru/ArduinoSnippets) * SAMPLES ネームスペースには ZAUTHENTICATE ルーチンの例も含まれています
記事
Toshihiko Minamoto · 2020年11月18日

クラス、テーブル、グローバルとその仕組み

クラス、テーブル、グローバルとその仕組み InterSystems IRIS を技術的知識を持つ人々に説明する際、私はいつもコアとしてマルチモデル DBMSであることから始めます。 個人的には、それが(DBMSとして)メインの長所であると考えています。 また、データが格納されるのは一度だけです。 ユーザーは単に使用するアクセス API を選択するだけです。 - データのサマリをソートしたいですか?SQL を使用してください! - 1 つのレコードを手広く操作したいですか?オブジェクトを使用してください! - あなたが知っているキーに対して、1 つの値にアクセスしたりセットしたいですか? グローバルを使用してください! これは短く簡潔なメッセージで、一見すると素晴らしく聞こえます。しかし、実際には intersystems IRIS を使い始めるたユーザーには クラス、テーブル、グローバルはそれぞれどのように関連しているのだろうか? 互いにどのような存在なのだろうか? データは実際にどのように格納されているのだろうか?といった疑問が生じます。 この記事では、これらの疑問に答えながら実際の動きを説明するつもりです。 ## パート 1. モデルに対する偏見。 データを処理するユーザーは多くの場合、処理対象のモデルに偏見を持っています。 開発者はオブジェクトで考えます。 このようなユーザーにとって、データベースとテーブルは CRUD(Create-Read-Update-Delete、ORM の使用が望ましい)を介して操作する箱のようなものですが、その基礎となる概念モデルはオブジェクトです(これは主に私たちのような多くのオブジェクト指向言語の開発者に当てはまります)。 一方、リレーショナル DBMS に多くの時間を費やしているデータベース管理者は往々にしてデータをテーブルと見なしています。 この場合、オブジェクトはレコードの単なるラッパー扱いです。 また、InterSystems IRIS では永続クラスはデータをグローバルに格納するテーブルでもあるため、いくつかの説明が必要になります。 ## パート 2. 具体例 次のような Point クラスを作成したとします。 ```objectscript Class try.Point Extends %Persistent [DDLAllowed] { Property X; Property Y; } ``` 次のように DDL/SQL を使用して同じクラスを作成することもできます。 ``` CREATE Table try.Point ( X VARCHAR(50), Y VARCHAR(50)) ``` コンパイル後、新しいクラスがグローバルにネイティブに格納されているデータをカラム(またはオブジェクト指向のユーザーの場合はプロパティ)にマッピングするストレージ構造を自動生成します。 ``` Storage Default { %%CLASSNAME X Y ^try.PointD PointDefaultData ^try.PointD ^try.PointI ^try.PointS %Library.CacheStorage } ``` ここでは何が起きているのでしょうか? 下から順番に説明します(**太字**の単語が重要です。残りは無視してください)。 - Type - 生成されたストレージタイプ。この場合は永続オブジェクトのデフォルトストレージです。 - StreamLocation - ストリームを格納するグローバルです。 - IndexLocation - インデックス用のグローバルです。 - IdLocation - ID の自動インクリメントカウンターを格納するグローバルです。 - **DefaultData** - グローバルの値をカラム/プロパティにマッピングするストレージの XML 要素です。 - **DataLocation** - データを格納するグローバルです。 ここでは「DefaultData」が `PointDefaultData` となっていますので、その構造をもう少し詳しく見てみましょう。 基本的に、グローバルノードは次の構造を持っていると言われています。 - 1 - %%CLASSNAME - 2 - X - 3 - Y したがって、グローバルは次のようになると予想されます。 ``` ^try.PointD(id) = %%CLASSNAME, X, Y ``` しかし、グローバルを出力すると空になります。ここではデータを追加していなかったためです。 ``` zw ^try.PointD ``` オブジェクトを 1 つ追加しましょう。 ``` set p = ##class(try.Point).%New() set p.X = 1 set p.Y = 2 write p.%Save() ``` すると、グローバルはこのようになります。 ``` zw ^try.PointD ^try.PointD=1 ^try.PointD(1)=$lb("",1,2) ``` ご覧のように、期待する構造 %%CLASSNAME, X, Y はオブジェクトの X プロパティと Y プロパティに対応する `$lb("",1,2)` とセットになっています(%%CLASSNAME はシステムプロパティですので無視してください)。 次のように SQL を使用してレコードを追加することもできます。 ``` INSERT INTO try.Point (X, Y) VALUES (3,4) ``` すると、グローバルの内容は次のようになります。 ``` zw ^try.PointD ^try.PointD=2 ^try.PointD(1)=$lb("",1,2) ^try.PointD(2)=$lb("",3,4) ``` つまり、オブジェクトまたは SQL を介して追加するデータは、ストレージ定義に従ってグローバルに格納されます(補足:PointDefaultData の X と Y を置き換えることでストレージ定義を手動で変更できます。その場合に新しいデータがどうなるかを確認してください!)。 では、SQL クエリを実行したい場合はどうなるのでしょうか? ``` SELECT * FROM try.Point ``` これは `^try.PointD` グローバルを反復処理し、ストレージ定義(正確にはその `PointDefaultData` 部分)に基づいてカラムにデータを入力する ObjectScript コードに変換されます。 今度は変更を行います。 テーブルからすべてのデータを削除しましょう。 ``` DELETE FROM try.Point ``` すると、この時点でグローバルの内容は次のようになります。 ``` zw ^try.PointD ^try.PointD=2 ``` ここでは ID カウンターのみが残っているため、新しいオブジェクト/レコードの ID は 3 になることに注意してください。 また、クラスとテーブルは引き続き存在します。 しかし、次を実行するとどうなるでしょうか。 ``` DROP TABLE try.Point ``` これはテーブルとクラスを破棄し、グローバルを削除します。 ``` zw ^try.PointD ``` 皆さんがこの具体例に従い、グローバル、クラス、テーブルがどのように統合され、相互に補完しているかをより深く理解できたことを願っています。 手元の仕事に適切な API を使用すれば、開発がより高速かつアジャイルになり、バグが少なくなります。
記事
Toshihiko Minamoto · 2022年12月21日

Django 入門 パート 3

Django の可能性と IRIS の使用方法を引き続き観察しています。 [初めに](https://jp.community.intersystems.com/node/527776)モデルの定義方法と、IRIS に存在しているテーブルへの接続方法を確認し、[次に](https://jp.community.intersystems.com/node/527781)組み込みの Django 管理ポータルを拡張して、モデルに含まれるデータの表示、フィルタ、編集、そしてページネーションの機能を追加しました。 では、実際の動作を確認しましょう。posts-and-tags パッケージで使用したデータで Django に REST API を作成します。 それには、[Django REST Framework](https://www.django-rest-framework.org/) を使用します。 ![Django REST Framework](https://www.django-rest-framework.org/img/logo.png) Django REST Framework は、Web API を構築するための強力で柔軟性を備えたツールキットです。 REST Framework の使用を推奨するのには、以下のような理由があります。 * Web で閲覧可能な API には、開発者のユーザビリティにおいて大きなメリットがあります。 * OAuth1a と OAuth2 の認証ポリシーを含むパッケージ * ORM と非 ORM データソースの両方をサポートするシリアル化 * すべてをカスタマイズ可能。強力な機能が必要なければ、通常の関数ベースのビューを使用できます。 * 詳細なドキュメントと優れたコミュニティサポート * Mozilla、Red Hat、Heroku、Eventbrite など、世界的に有名な企業が使用・信頼 まず、依存関係で requirements.txt を更新する必要があります。 # Django そのもの django>=4.0.0 # Django 用 InterSystems IRIS ドライバー、InterSystems の DB-API ドライバー django-iris=>0.1.13 https://raw.githubusercontent.com/intersystems-community/iris-driver-distribution/main/DB-API/intersystems_irispython-3.2.0-py3-none-any.whl # Django REST Framework とそのオプションの依存関係 djangorestframework>=3.4.4 # 閲覧可能な API の Markdown サポート markdown>=3.0.0 # フィルタサポート django-filter>=1.0.1 そして、これらをインストールします。 python install -r requirements.txt ## API の初稿 _urls.py_ ファイルを以下に更新します。 ここでは、API のルートを **_api/_** に更新し、API リクエストに対し、http://localhost:8000/api/ がルートとして使用されるようにします。 from django.contrib import admin from django.urls import path, include from rest_framework import routers router = routers.DefaultRouter() urlpatterns = [ path('api/', include(router.urls)), path('admin/', admin.site.urls), path('api-auth/', include('rest_framework.urls')), ] Django REST Framework には、settings.py の DEBUG=True によりサーバーが開発モードで実行している場合、API の UI が組み込まれています。 この URL を開きましょう。 ![](/sites/default/files/inline/images/images/image(4289).png) 何も定義されておらず、フレームワークが URL に接続されているだけでも、すべてが機能しています。 認証を必要とするリクエストに対しては、認証がサポートされています。  $ curl http://127.0.0.1:8000/api/ {} プロジェクトの API を定義しましょう。最低限、REST Framework のいくつかの機能を使用します。 * シリアライザー - クエリセットやモデルインスタンスなどの複雑なデータをネイティブ Python データ型に変換し、`JSON` や `XML` などのコンテンツタイプに簡単にレンダリングできるようにします。 シリアライザーは逆シリアル化も提供しているため、着信データを検証してから、解析したデータを複雑な型に変換し直すことも可能です。 * ビューセット - 関連する一連のビューのロジックを 1 つのクラスにまとめることができます。 Post 用のエンドポイントを追加しましょう。 とてもシンプルではありますが、 更新された **_urls.py _** のコンテンツを見てみましょう。 from django.contrib import admin from django.urls import path, include from rest_framework import routers, serializers, viewsets from .models import CommunityPost router = routers.DefaultRouter() class CommunityPostSerializer(serializers.HyperlinkedModelSerializer): class Meta: # class with model model = CommunityPost # list of fields to show, or just '__all__' fields = '__all__' # ViewSets define the view behavior. class CommunityPostViewSet(viewsets.ModelViewSet): queryset = CommunityPost.objects.all() serializer_class = CommunityPostSerializer # connect it with API router.register(r'posts', CommunityPostViewSet) urlpatterns = [ path('api/', include(router.urls)), path('admin/', admin.site.urls), path('api-auth/', include('rest_framework.urls')), ] これで Web UI に表示されるようになりました。 ![](/sites/default/files/inline/images/images/image(4290).png) ここのリンクをクリックすると、そのレスポンスを確認できます。 ![](/sites/default/files/inline/images/images/image(4291).png) 最後までスクロールすると、新しい項目用に生成されたフォームがあります。これは POST リクエストで追加可能です。 すべてのフィールドがプロパティの型に適しています。 ![](/sites/default/files/inline/images/images/image(4292).png) 項目リストで任意の項目の URL をクリックして、これを確認します。 レスポンスのこの項目と、PUT リクエストを使った編集フォームのみです。 ![](/sites/default/files/inline/images/images/image(4294).png) ## 認証 PUT か POST でデータを変更できるようになりました。 認証の要件はまだ有効化されていません。 REST Framework には、使用できる認証の組み合わせが様々用意されているため、匿名アクセスの読み取り専用リソースを一部開放することができます。 そして、変更を行うための認証を行います。 または、アクセスを完全に閉鎖することも可能です。 ここでは、匿名の読み取り専用に構成し、変更には認証を必要とするようにしましょう。 それには、次のコードを **_settings.py_** に追加すれば完了です。 REST_FRAMEWORK = { # Use Django’s standard `django.contrib.auth` permissions, # or allow read-only access for unauthenticated users. 'DEFAULT_PERMISSION_CLASSES': [ 'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly', ], } これを使用すれば、Django 管理用に前に作成したユーザー名とパスワードなどでログインするまで、フォームが表示されなくなります。 ## ページネーション デフォルトではページネーションはありませんが、リストクエリに簡単に追加できます。 **_settings.py_** の **REST_FRAMEWORK** 変数を更新しましょう。 ページネーションクラスとデフォルトのページサイズをセットアップします。 REST_FRAMEWORK = { ... 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination', 'PAGE_SIZE': 10, ... } これにより、生成される JSON がわずかに変わりました。「次へ」や「前へ」といったページリンクや全項目数などの関連する項目が追加されています。 これで、Web UI でページを移動できるようになりました。 ![](/sites/default/files/inline/images/images/ezgif_com-gif-maker_(4).gif) ## フィルタと検索 フィルタ機能と検索機能の追加も非常に単純です。 **_settings.py_** の **REST_FRAMEWORK** 変数を更新しましょう。  REST_FRAMEWORK = { ... 'DEFAULT_FILTER_BACKENDS': [ 'django_filters.rest_framework.DjangoFilterBackend', 'rest_framework.filters.SearchFilter', ], ... ] そして、CommunityPostViewSet をフィルタと検索用のフィールドのリストで更新します。 class CommunityPostViewSet(viewsets.ModelViewSet): queryset = CommunityPost.objects.all() serializer_class = CommunityPostSerializer filterset_fields = ['posttype', 'lang', 'published'] search_fields = ['name',] これで、Web UI で動作するようになりました。 ![](/sites/default/files/inline/images/images/ezgif_com-gif-maker_(5).gif) 最後に、完全に機能する REST API が完成しましたが、今のところ、このリソース専用の API です。 非常に単純ですが、十分にカスタマイズ可能で、他のリソースに接続したり、リンクしたリすることが可能です。
記事
Mihoko Iijima · 2024年7月16日

管理ポータルのウェブ・アプリケーションメニューの設定をプログラムで作成する方法

これは InterSystems FAQ サイトの記事です。 管理ポータル > [システム管理] > [セキュリティ] 以下の設定は、%SYSネームスペースにあるSecurityパッケージ以下クラスが提供するメソッドを利用することでプログラムから作成することができます。 以下シナリオに合わせたセキュリティ設定例をご紹介します。 シナリオ:RESTアプリケーション用設定を作成する 事前準備 シナリオの中で使用するソースを2種類インポートします。 アプリケーション用RESTディスパッチクラスをインポートします。 Class Test.REST Extends %CSP.REST { Parameter CHARSET = "utf-8"; Parameter CONTENTTYPE = "application/json"; Parameter CONVERTINPUTSTREAM = 1; XData UrlMap [ XMLNamespace = "http://www.intersystems.com/urlmap" ] { <Routes> <Route Url="/hello" Method="POST" Call="TestInsert" Cors="true" /> <Route Url="/hello" Method="DELETE" Call="TestDelete" Cors="true" /> </Routes> } /// Description ClassMethod TestInsert() As %Status { #dim %request As %CSP.Request #dim rset As %SQL.StatementResult set status = $$$OK Try { set bodyjson={}.%FromJSON(%request.Content) set ^Test=bodyjson.Message set stmt=##class(%SQL.Statement).%New() $$$ThrowOnError(stmt.%Prepare("insert into Test.Human (Name,Message) VALUES(?,?)")) set rset=stmt.%Execute($username,bodyjson.Message) if rset.%SQLCODE<0 { throw ##class(%Exception.SQL).CreateFromSQLCODE(rset.%SQLCODE,rset.%Message) } set j={} set j.Result="こんにちは!メッセージをUSERネームスペースのグローバル^TestとTest.Humanテーブルに格納しました。" do j.%ToJSON() } Catch ex { Set status=ex.AsStatus() } Return status } ClassMethod TestDelete() As %Status { #dim %request As %CSP.Request #dim rset As %SQL.StatementResult set status = $$$OK Try{ kill ^Test set stmt=##class(%SQL.Statement).%New() $$$ThrowOnError(stmt.%Prepare("Delete from Test.Human")) set rset=stmt.%Execute() if rset.%SQLCODE<0 { throw ##class(%Exception.SQL).CreateFromSQLCODE(rset.%SQLCODE,rset.%Message) } set j={} set j.Result="USERネームスペースの^TestとTest.Humnaのデータを削除しました" do j.%ToJSON() } catch ex { Set status=ex.AsStatus() } Return status } } 続いて、Test.Humanテーブル用定義もインポートします。 Class Test.Human Extends %Persistent { Property Name As %String; Property Message As %String; } それでは設定してみましょう。 1) RESTアプリケーション(/testApp)があり、USERネームスペースで動作するRESTアプリケーションに対して不特定多数のユーザが利用できるようにします=(認証なしアクセスを許可します)。 2) RESTアプリケーション利用時は、USERネームスペースのTestスキーマ以下テーブルに対してINSERT/UPDATE/DELETE/SELECTが行えるように設定します。 まずは 1)について、 認証をしない「認証なし」アクセスを許可した場合、UnknownUserとしてInterSystems製品にログインします。 UnknownUserはインストール時の初期セキュリティの指定により初期設定が異なります。(初期セキュリティについて詳細は、記事「インストール時の初期セキュリティについて」をご参照ください。) 「最小」:InterSystems製品の全ての情報にアクセス可能な %Allロールが付与されます。 「通常」以上を指定した場合:ロールは何も設定されません。 このシナリオでは、UnkownUserに対してロールが付与されていない環境(=インストール時の初期セキュリティを「通常」以上とした場合)に対する設定方法を解説します。 手っ取り早くUnkownUserに%Allロールを付与するのも1つの方法ですが、その場合RESTアプリケーション以外の「認証なし」が許可されたアクセスに対しても%Allロールが適用されてしまうため、セキュアな設定とは言えません。このシナリオでは、RESTアプリケーションパスを通過したときのみ適切なロールを付与させることのできる「アプリケーションロール」を利用してロールを付与していきます。 ここで、RESTアプリケーションを動作させるために最低限必要となるアクセス許可はアプリケーションが動作するデータベースに対するREAD許可です。アプリケーションがデータベースに対して書き込みを行う場合はWRITE許可も必要となります。 InterSystems製品では、データベースを作成した際、一緒にセキュリティ設定で使用するデータベースリソースを作成することができます。データベースリソースを作成するとそのリソースに対するREADとWRITEの許可を持ったデータベースロールが自動的に作成されます。 今回はインストールデフォルトで作成されるUSERデータベースにアプリケーションをインストールして利用することにしています。 USERデータベースに対しては、%DB_USERリソースが用意されていてこのリソースに対してREADとWRITE許可を持つ%DB_USERロールが事前に用意されています。この%DB_USERロールをRESTアプリケーションに付与することにします。 さらに、2)では、 2) RESTアプリケーション利用時は、USERネームスペースのTestスキーマ以下テーブルに対してINSERT/UPDATE/DELETE/SELECTが行えるように設定します。 とあるので、Testスキーマに対する適切なテーブル権限が必要となります。テーブル権限はユーザに直接付与することもロールに付与することもできます。 この設定では、RESTアプリケーションにロールを付与したいので、ロールにテーブルの権限も付与することにします。 それでは、ロール:MyAppRoleを作成します。 アプリケーションはUSERネームスペースにアクセスする前提のため、%DB_USERロールを持つ新ロールを作成します。 %SYSネームスペースで実行します。 set $namespace="%SYS" set status=##class(Security.Roles).Create("MyAppRole","アプリケーション用ロール",,"%DB_USER") Security.RolesクラスのCreate()メソッドに指定する引数は以下の通りです。 第1引数:ロール名第2引数:ロールの説明第3引数:リソースの割り当て(未指定もOK)第4引数:割り当てるロール(複数ある場合はカンマ区切りで指定) このロールを付与された人やアプリケーションはTestスキーマに対してSELECT/DELETE/UPDATE/INSERTができるようにこれらの権限を含む全テーブル権限を追加します。 set status=$system.SQL.Security.GrantPrivilege("*","Test","SCHEMA","MyAppRole") %SYSTEM.SQL.SecurityクラスのGrantPrivilege()メソッドで指定する引数は以下の通りです。 第1引数:以下のアクションをカンマ区切りで指定します。全部対象とする場合は * を指定します。 Alter Select Insert Update Delete References Execute Use 第2引数:対象となるテーブル名やスキーマ名を指定できます。例ではTestスキーマを指定しています。 第3引数:対象となるタイプを指定します。例ではSCHEMAを指定しています。 第4引数:付与するユーザ名またはロール名を指定します。 戻り値を確認します。(1が返れば成功です。) 失敗している場合は、以下の出力をご確認ください。 do $system.OBJ.DisplayError(status) 管理ポータルでは以下のように設定を確認できます。 管理ポータル > [システム管理] > [セキュリティ] > [ロール] > [MyAppRole]の[Assigned To]と[SQL Tables]のタブ 作成したMyAppRoleをアプリケーション利用時に追加するようにREST用のウェブアプリケーションを定義します。 作成には、Security.ApplicationsクラスのCreate()メソッドを利用します。 Create()メソッドの第2引数には設定に必要な情報を配列変数で指定します。サブスクリプトの指定については以下の通りです。 DispatchClass:RESTディスパッチクラス名を指定します。 NameSpace:RESTディスパッチクラスがあるネームスペースを指定します。 Enable:アプリケーションを有効とする場合は1を指定します。 AutheEnabled:認証なしは64を設定します。詳細はAutheEnabledプロパティの説明をご参照ください。(認証なしは Bit 6のAutheUnauthenticated の値を設定します。) MatchRoles:アプリケーション通過時に付与するアプリケーションロールの場合は、:ロール名 を設定します。 %SYSネームスペースで以下実行します。 set webName="/testApp" set webProperties("DispatchClass")="Test.REST" set webProperties("NameSpace")="USER" set webProperties("Enable")=1 set webProperties("AutheEnabled")=64 set webProperties("MatchRoles")=":MyAppRole" set status=##class(Security.Applications).Create(webName, .webProperties) 戻り値を確認します。(1が返れば成功です。) 失敗している場合は、以下の出力をご確認ください。 do $system.OBJ.DisplayError(status) 管理ポータルでは以下のように表示されます。 管理ポータル > [システム管理] > [セキュリティ] > [アプリケーション] > [ウェブ・アプリケーション] > /testApp選択 設定が完了したので最初にPOST要求を試します。 URLには、webサーバ/testApp/hello を指定しBodyに以下のプロパティを持つJSONを指定しPOST要求をテストします。 { "Message":"新しいデータをいれます" } POST要求が成功すると、以下応答として返送します。 { "Result": "こんにちは!メッセージをUSERネームスペースのグローバル^TestとTest.Humanテーブルに格納しました。" } グローバル^Test、またはTest.Humanの中身をご確認ください。 続いて、DELETE要求を実行します。(POSTと同じURLを使用します) DELETE要求が成功すると、以下応答として返送します。 { "Result": "USERネームスペースの^TestとTest.Humnaのデータを削除しました" } グローバル^TestとTest.Humanテーブルのデータが削除されたことを確認してください。 ご参考:RESTアプリケーション(/testApp)にテーブル権限を設定し忘れると以下のエラーが出力されます。 { "errors": [ { "code": 5540, "domain": "%ObjectErrors", "error": "エラー #5540: SQLCODE: -99 メッセージ: User UnknownUser is not privileged for the operation", "id": "SQLCode", "params": [ -99, "User UnknownUser is not privileged for the operation" ] } ], "summary": "エラー #5540: SQLCODE: -99 メッセージ: User UnknownUser is not privileged for the operation" }
記事
Mihoko Iijima · 2021年3月9日

ダイナミックオブジェクトの JSON 変換で MAXSTRING エラーが出る場合の対処方法

これは InterSystems FAQ サイトの記事です。 ダイナミックオブジェクトから JSON 文字列を生成するときに使用する %ToJSON() の引数にストリームオブジェクトを指定することでエラーを回避できます。 コード例は以下の通りです。 USER>set temp=##class(%Stream.TmpCharacter).%New() USER>set jsonobj={} USER>set jsonobj.pro1=["a","b","c","d"] USER>set jsonobj.pro2=["あ","い","う","え"] USER>do jsonobj.%ToJSON(temp) USER>write temp.Size 51 USER>write temp.Read() {"pro1":["a","b","c","d"],"pro2":["あ","い","う","え"]} 詳細はドキュメントもご参照下さい。 【IRIS】大きいダイナミック・エンティティからストリームへのシリアル化 大きいダイナミック・エンティティからストリームへのシリアル化 こんにちは、mihokoさん %Stream.TmpCharacterクラスを使用した場合、IRISTEMPデータベースにデータが書き込まれると理解してよろしいでしょうか? また、作成したストリームデータは明示的に削除する必要はありますか? こんにちは、Amanoさん ご質問ありがとうございます! 確認してみたところ、ご質問いただいたように、IRISTEMP に書くには書くのですが、大きなサイズになったときにプロセスプライベートグローバル(=PPG。こちらも IRISTEMP に格納される変数)へセットしていました。 ちなみに、上に書いているサンプルだと、PPG は使用していませんでした。 削除についてですが、ストリームを格納した変数を消去すると使用していた PPG も消えますので、「ストリームをセットした変数を kill する」を入れるのが確実だと思います(メソッド終了でローカル変数も消えますが、明示的に書いた方がわかりやすいかな、と思いました)。 確認するまで私も知りませんでしたので、ご質問いただきありがとうございました! mihokoさん %Stream.TmpCharacterクラスは便利そうですね。 回答ありがとうございました。