#ObjectScript

0 关注者 · 95 帖子

InterSystems ObjectScript 是一种脚本语言,可使用 InterSystems 数据平台的任何数据模型(对象、关系、键值、文档、全局变量/Global)处理数据,并在 InterSystems 数据平台上为服务器端应用程序开发业务逻辑。

文档

文章 Nicky Zhu · 十月 28, 2025 2m read

InterSystems 常见问题解答标题

^%GCMP 实用工具可用于比较两个全局变量的内容。

例如,要比较 USER 和 SAMPLES 命名空间中的 ^test 和 ^test,过程将与下面类似:
*以下示例在这两个命名空间中创建了 700 个相同的全局变量,并更改了其中一个的内容,使其成为检测目标。

USER>kill^test
USER>for i=1:1:100 { forj=1:1:7 { set^test(i,j)="Test"_i } }

USER>zn"samples"// change namespace to SAMPLES SAMPLES>kill^test SAMPLES>for i=1:1:100 { forj=1:1:7 { set^test(i,j)="Test"_i } }

SAMPLES>set^test(50,5,1)=1// Change one of the globals created in the SAMPLES namespace. SAMPLES>do^%GCMP Compare global ^test// Global to compare. on directory set: (this system) // Enter in namespace: SAMPLES => // Enter (if this namespace is OK) with global ^test=> // Global to compare on directory set: (this system) // Enter in namespace: SAMPLES => USER // Namespace to compare Output differences on Device: // Destination for output results. Press <Enter> to view in a terminal.// If you enter the full path of the log file name, the output will be sent there. Right margin: 80 =>

Compare global ^test in SAMPLES with global ^test in USER

^test(50,5,1) exists in ^|"SAMPLES"|test but not in ^|"USER"|test // Detects differing globals Time=.001822 SAMPLES>

如果要在不同服务器上的实例之间进行比较,而不是在同一实例中,请使用 ^DATACHECK 实用工具。 有关如何使用 ^DATACHECK 实用工具的说明,请参阅以下相关文章:

如何比较两个数据库中的多个全局变量和例程

0
0 19
文章 Kelly Huang · 十月 23, 2025 15m read

img

在本节中,我们将探讨如何在 IRIS 中使用 Python 作为主要编程语言,在使用 Python 编写应用程序逻辑的同时仍能利用 IRIS 的强大功能。

使用方法 (irispython)

我们先来介绍官方操作方式,即使用 irispython 解释器。

您可以使用 irispython 解释器直接在 IRIS 中运行 Python 代码。 这样,您可以编写 Python 代码,并在 IRIS 应用程序的运行环境中执行相应代码。

什么是 irispython?

irispython 是位于 IRIS 安装目录 (<installation_directory>/bin/irispython) 下的 Python 解释器,用于在 IRIS 的运行环境中执行 Python 代码。

它的功能包括:

  • 设置 sys.path,以包含 IRIS Python 库和模块。
    • 此操作通过 <installation_directory>/lib/python/iris_site.py 中的 site.py 文件执行。
    • 如需了解详情,请参阅模块文章 Python 模块简介
  • 允许您导入 iris 模块,这是一个特殊模块,用于访问 IRIS 功能,例如实现任何 ObjectScript 类与 Python 的双向桥接。
  • 修复权限问题并动态加载 iris 内核库。

irispython 使用示例

您可以通过命令行运行 irispython 解释器:

<installation_directory>/bin/irispython

我们来运行一个简单的示例:

# src/python/article/irispython_example.py
import requests
import iris

def run():
    response = requests.get("https://2eb86668f7ab407989787c97ec6b24ba.api.mockbin.io/")

    my_dict = response.json()

    for key, value in my_dict.items():
        print(f"{key}: {value}")  # print message: Hello World

    return my_dict

if __name__ == "__main__":
    print(f"Iris version: {iris.cls('%SYSTEM.Version').GetVersion()}")
    run()

您可以使用 irispython解释器运行此脚本:

<installation_directory>/bin/irispython src/python/article/irispython_example.py

您将看到如下输出:

Iris version: IRIS for UNIX (Ubuntu Server LTS for x86-64 Containers) 2025.1 (Build 223U) Tue Mar 11 2025 18:23:31 EDT
message: Hello World

此例展示了如何使用 irispython 解释器在 IRIS 的运行环境中执行 Python 代码。

优点

  • Python 优先:您可以使用 Python 编写应用程序逻辑,这样,您可以利用 Python 的功能和库。
  • IRIS 集成:您可以轻松将 Python 代码与 IRIS 功能相集成。

缺点

  • 调试受限:在 irispython 中调试 Python 代码并不像在专用 Python 环境中那样简单直接。
    • 这并不是说无法进行调试,而是并不像在专用 Python 环境中那样简单。
    • 如需了解详情,请参阅补充部分
  • 虚拟环境:在 irispython 中为 Python 代码搭建虚拟环境比较困难。
    • 这并不是说无法搭建,只是操作起来比较困难。因为默认情况下,虚拟环境会查找名为 pythonpython3 的解释器,而 IRIS 中的情况并非如此。
    • 如需了解详情,请参阅补充部分

结论

总的来说,使用 irispython 解释器让您既可以利用 Python 编写应用程序逻辑,又能利用 IRIS 的强大功能。 不过,这种方式也存在调试和虚拟环境搭建方面的限制。

使用 WSGI

在本节中,我们将探讨如何使用 WSGI(Web 服务器网关接口)在 IRIS 中运行 Python Web 应用程序。

WSGI 是 Web 服务器与 Python Web 应用程序或框架之间的标准接口。 利用 WSGI,您可以在 Web 服务器环境中运行 Python Web 应用程序。

IRIS 支持 WSGI,这意味着您可以在 IRIS 中使用内置的 WSGI 服务器运行 Python Web 应用程序。

使用方法

要在 IRIS 中使用 WSGI,您需要创建 WSGI 应用程序,并向 IRIS Web 服务器注册此应用程序。

如需了解详情,请参阅官方文档

WSGI 使用示例

有关完整模板,请参见此处:iris-flask-example

优点

  • Python Web 框架:您可以使用流行的 Python Web 框架(如 Flask 或 Django)来构建 Web 应用程序。
  • IRIS 集成:您可以轻松将 Python Web 应用程序与 IRIS 功能相集成。

缺点

  • 复杂程度:构建 WSGI 应用程序会比直接在 Python Web 框架中使用 uvicorngunicorn 复杂一些。

结论

总的来说,在 IRIS 中使用 WSGI 让您既可以利用 Python 构建功能强大的 Web 应用程序,又能利用 IRIS 的功能。

DB-API

在本节中,我们将探讨如何使用 Python DB-API 与 IRIS 数据库进行交互。

Python DB-API 是 Python 中用于连接数据库的标准接口。 利用此接口,您可以执行 SQL 查询,并从数据库中检索结果。

使用方法

您可以使用 pip 进行安装:

pip install intersystems-irispython

随后,您可以使用 DB-API 连接 IRIS 数据库并执行 SQL 查询。

DB-API 使用示例

它的使用方法与其他所有 Python DB-API 相同,示例如下:

# src/python/article/dbapi_example.py
import iris

def run():
    # Connect to the IRIS database
# Open a connection to the server
    args = {
        'hostname':'127.0.0.1', 
        'port': 1972,
        'namespace':'USER', 
        'username':'SuperUser', 
        'password':'SYS'
    }
    conn = iris.connect(**args)

    # Create a cursor
    cursor = conn.cursor()

    # Execute a query
    cursor.execute("SELECT 1")

    # Fetch all results
    results = cursor.fetchall()

    for row in results:
        print(row)

    # Close the cursor and connection
    cursor.close()
    conn.close()
if __name__ == "__main__":
    run()

您可以使用任何 Python 解释器运行此脚本:

python3 /irisdev/app/src/python/article/dbapi_example.py

您将看到如下输出:

(1,)

优点

  • 标准接口:DB-API 提供用于连接数据库的标准接口,因此可以轻松切换不同的数据库。
  • SQL 查询:您可以使用 Python 执行 SQL 查询,并从数据库检索结果。
  • 远程访问:您可以使用 DB-API 连接到远程 IRIS 数据库。

缺点

  • 功能有限:DB-API 仅可通过 SQL 访问数据库,因此,您无法使用高级 IRIS 数据库功能,如执行 ObjectScript 或 Python 代码。

备选方案

还提供社区版 DB-API,参见此处:intersystems-irispython-community

该版本能更好地支持 SQLAlchemy、Django、langchain,以及其他使用 DB-API 的 Python 库。

如需了解详情,请参阅 补充部分

结论

总的来说,将 Python DB-API 与 IRIS 结合使用能够让您构建功能强大的应用程序,实现与数据库的无缝交互。

Notebook

现在,我们已了解如何在 IRIS 中使用 Python,接下来我们将探讨如何将 Jupyter Notebooks 与 IRIS 结合使用。

Jupyter Notebooks 是交互式编写和执行 Python 代码的绝佳方式,并且可与 IRIS 结合使用,以充分利用 IRIS 的功能。

使用方法

要在 IRIS 中使用 Jupyter Notebooks,您需要安装 notebookipykernel 这两个软件包:

pip install notebook ipykernel

然后,您可以创建新的 Jupyter Notebook 并选择 Python 3 内核。

Notebook 使用示例

您可以创建新的 Jupyter Notebook 并编写以下代码:

# src/python/article/my_notebook.ipynb
# Import the necessary modules
import iris
# Do the magic
iris.system.Version.GetVersion()

您可以使用 Jupyter Notebook 运行此 notebook:

jupyter notebook src/python/article/my_notebook.ipynb

优点

  • 交互式开发:利用 Jupyter Notebooks,您可以交互式编写和执行 Python 代码,非常适合数据分析和探索。
  • 丰富的输出:您可以直接在 Notebook 中显示丰富的输出,如图表和表格。
  • 文档:您可以在代码旁添加文档和说明。

缺点

  • 设置有难度:设置将 Jupyter Notebooks 与 IRIS 结合使用存在一定的难度,特别是对于内核配置而言。

结论

总的来说,将 Jupyter Notebooks 与 IRIS 结合使用可以交互式编写和执行 Python 代码,同时利用 IRIS 的功能。 不过,设置起来存在一定的难度,特别是对于内核配置而言。

补充部分

从本节开始,我们将探讨一些与在 IRIS 中使用 Python 相关的高级主题,例如远程调试 Python 代码、使用虚拟环境等。

以下大部分主题均未获得 InterSystems 的官方支持,但如果您要在 IRIS 中使用 Python,了解相关内容会提供很大的帮助。

使用原生解释器(无 irispython

在本节中,我们将探讨如何使用原生 Python 解释器代替 irispython 解释器。

这样一来,您可以直接使用虚拟环境,并使用您习惯的 Python 解释器。

使用方法

要使用原生 Python 解释器,您需要在机器本地安装 IRIS,并需要安装 iris-embedded-python-wrapper 软件包。

您可以使用 pip 进行安装:

pip install iris-embedded-python-wrapper

接下来,您需要设置一些环境变量指向 IRIS 安装目录:

export IRISINSTALLDIR=<installation_directory>
export IRISUSERNAME=<username>
export IRISPASSWORD=<password>
export IRISNAMESPACE=<namespace>

然后,您可以使用您的原生 Python 解释器运行 Python 代码:

python3 src/python/article/irispython_example.py
# src/python/article/irispython_example.py
import requests
import iris

def run():
    response = requests.get("https://2eb86668f7ab407989787c97ec6b24ba.api.mockbin.io/")

    my_dict = response.json()

    for key, value in my_dict.items():
        print(f"{key}: {value}")  # print message: Hello World

    return my_dict

if __name__ == "__main__":
    print(f"Iris version: {iris.cls('%SYSTEM.Version').GetVersion()}")
    run()

如需了解详情,请参阅 iris-embedded-python-wrapper 文档

优点

  • 虚拟环境:您可以将虚拟环境与原生 Python 解释器结合使用,从而可以更加轻松地管理依赖项。
  • 熟悉的工作流:您可以使用习惯的 Python 解释器,从而可以更轻松地与现有工作流相集成。
  • 调试:可以使用您喜欢的 Python 调试工具(如 pdbipdb)在 IRIS 中调试 Python 代码。

缺点

  • 设置的复杂程度:设置环境变量和 iris-embedded-python-wrapper 软件包可能会比较复杂,特别是对于初学者来说。
  • 未获官方支持:此方式未获 InterSystems 的官方支持,因此您可能遇到文档中未记录或不受支持的问题。

DB-API 社区版

在本节中,我们将探讨 GitHub 上提供的社区版 DB-API。

使用方法

您可以使用 pip 进行安装:

pip install sqlalchemy-iris

此代码将安装社区版 DB-API。

或使用特定版本:

pip install https://github.com/intersystems-community/intersystems-irispython/releases/download/3.9.3/intersystems_iris-3.9.3-py3-none-any.whl

然后,您可以使用 DB-API 连接 IRIS 数据库,并执行 SQL 查询或其他任何使用 DB-API 的 Python 代码,如 SQLAlchemy、Django、langchain、pandas 等。

DB-API 使用示例

它的使用方法与其他所有 Python DB-API 相同,示例如下:

# src/python/article/dbapi_community_example.py
import intersystems_iris.dbapi._DBAPI as dbapi

config = {
    "hostname": "localhost",
    "port": 1972,
    "namespace": "USER",
    "username": "_SYSTEM",
    "password": "SYS",
}

with dbapi.connect(**config) as conn:
    with conn.cursor() as cursor:
        cursor.execute("select ? as one, 2 as two", 1)   # second arg is parameter value
        for row in cursor:
            one, two = row
            print(f"one: {one}")
            print(f"two: {two}")

您可以使用任何 Python 解释器运行此脚本:

python3 /irisdev/app/src/python/article/dbapi_community_example.py

也可以使用 sqlalchemy:

from sqlalchemy import create_engine, text

COMMUNITY_DRIVER_URL = "iris://_SYSTEM:SYS@localhost:1972/USER"
OFFICIAL_DRIVER_URL = "iris+intersystems://_SYSTEM:SYS@localhost:1972/USER"
EMBEDDED_PYTHON_DRIVER_URL = "iris+emb:///USER"

def run(driver):
    # Create an engine using the official driver
    engine = create_engine(driver)

    with engine.connect() as connection:
        # Execute a query
        result = connection.execute(text("SELECT 1 AS one, 2 AS two"))

        for row in result:
            print(f"one: {row.one}, two: {row.two}")

if __name__ == "__main__":
    run(OFFICIAL_DRIVER_URL)
    run(COMMUNITY_DRIVER_URL)
    run(EMBEDDED_PYTHON_DRIVER_URL)

您可以使用任何 Python 解释器运行此脚本:

python3 /irisdev/app/src/python/article/dbapi_sqlalchemy_example.py

您将看到如下输出:

one: 1, two: 2
one: 1, two: 2
one: 1, two: 2

优点

  • 更好的支持:对 SQLAlchemy、Django、langchain 以及其他使用 DB-API 的 Python 库提供更好的支持。
  • 依托于社区:它由社区维护,这意味着随着时间的推移,可能会对其进行更新和改进。
  • 兼容性:它兼容官方 InterSystems DB-API,因此您可以在官方版与社区版之间轻松切换。

缺点

  • 速度:社区版的优化程度可能不如正式版高,某些场景下可能会导致速度变慢。

在 IRIS 中调试 Python 代码

在本节中,我们将探讨如何在 IRIS 中调试 Python 代码。

默认情况下,无法在 IRIS 中调试 Python 代码(在包含语言标签或 %SYS.Python 的 objectscript 中),但可以通过社区解决方案在 IRIS 中调试 Python 代码。

使用方法

先安装 IoP 基于 Python 的互操作性

pip install iris-pex-embedded-python
iop --init

此代码将安装 IoP 和新的 ObjectScript 类,以便您可以在 IRIS 中调试 Python 代码。

然后,您可以使用 IOP.Wrapper 类包装 Python 代码并实现调试。

Class Article.DebuggingExample Extends %RegisteredObject
{
ClassMethod Run() As %Status
{
    set myScript = ##class(IOP.Wrapper).Import("my_script", "/irisdev/app/src/python/article/", 55550) // Adjust the path to your module
    Do myScript.run()
    Quit $$$OK
}
}

然后,向 launch.json 文件添加以下配置,将 VsCode 配置为使用 IoP 调试器:

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Python in IRIS",
            "type": "python",
            "request": "attach",
            "port": 55550,
            "host": "localhost",
            "pathMappings": [
                {
                    "localRoot": "${workspaceFolder}/src/python/article",
                    "remoteRoot": "/irisdev/app/src/python/article"
                }
            ]
        }
    ]
}

现在,您可以运行用于导入 Python 模块的 ObjectScript 代码,然后将 VsCode 中的调试器关联到端口 55550

您可以使用以下命令运行此脚本:

iris session iris -U IRISAPP '##class(Article.DebuggingExample).Run()'

然后,您可以在 Python 代码中设置断点,调试器将在这些断点处停止执行,以便您检查变量并单步执行代码。

远程调试实际运作视频(针对 IoP,但原理是相同的):

Python 代码中还提供回溯信息,这对调试非常有用。

启用回溯时:

Traceback enabled

禁用回溯时:

Traceback disabled

优点

  • 远程调试:您可以远程调试在 IRIS 中运行的 Python 代码,在我看来,这是一项革命性的功能。
  • Python 调试功能:您可以使用所有 Python 调试功能,例如断点、变量检查和单步执行代码。
  • 回溯:您可以看到 Python 代码中错误的完整回溯信息,这对调试非常有用。

缺点

  • 设置的复杂程度:设置 IoP 和调试器可能会比较复杂,特别是对于初学者来说。
  • 社区解决方案:该解决方案属于社区解决方案,因此可能不像官方解决方案那样稳定,文档也可能不完善。

结论

总的来说,在 IRIS 中调试 Python 代码可以通过 IoP 社区解决方案来实现,借助该解决方案,您可以使用 Python 调试器调试在 IRIS 中运行的 Python 代码。 不过,此解决方案需要执行一些设置操作,并可能不像官方解决方案一样稳定。

IoP(基于 Python 的互操作性)

在本节中,我们将探讨 IoP(基于 Python 的互操作性)解决方案,利用该解决方案,您可以使用 Python 优先的方式在 IRIS 中运行 Python 代码。

我开发这个解决方案已经有一段时间了,可以说它是我的心血之作,该解决方案尝试解决或改善我们在本系列文章中提到的所有问题。

IoP 的要点:

  • Python 优先:您可以使用 Python 编写应用程序逻辑,这样,您可以利用 Python 的功能和库。
  • IRIS 集成:您可以轻松将 Python 代码与 IRIS 功能相集成。
  • 远程调试:您可以远程调试在 IRIS 中运行的 Python 代码。
  • 回溯:您可以看到 Python 代码中错误的完整回溯信息,这对调试非常有用。
  • 虚拟环境:支持虚拟环境功能,因此您可以更加轻松地管理依赖项。

要详细了解 IoP,您可以查阅官方文档

然后,您可以阅读以下文章详细了解 IoP:

🐍❤️如您所见,通过 IoP 这一功能强大的方法,我们可以将 Python 与 IRIS 相集成,从而可以更轻松地开发和调试应用程序。

您无需继续使用 irispython,也不必手动设置 sys.path,而是可以使用虚拟环境,并且可以调试在 IRIS 中运行的 Python 代码。

结论

希望大家喜欢这一系列关于在 IRIS 中使用 Python 的文章。

如果您对这一系列的文章有任何疑问或反馈,请随时联系我。

祝您在 IRIS 中使用 Python 时一切顺利!

0
0 18
文章 Nicky Zhu · 四月 24, 2022 10m read

尽管IRIS和HealthConnect拥有全面的互操作特性,但在实际工作中,还是有可能遇到需要使用遗留类库,dll SDK等方式与外部应用通信的情况。例如IRIS中并没有内嵌国密算法SM2、SM3和SM4,而开源社区中不乏通过Java、Python和C++等语言完成的具体实现。本文就将以调用SM4的Java实现为例展示ObjectScript程序与第三方语言通信的过程。

Github地址:https://github.com/LinZhuISC/javademo.SM4

跨编程环境调用设计要点

在开始实际操作之前,希望读者先针对整个调用过程中的主调方和被调方思考两个现象:

  1. ObjectScript作为主调方,Java程序对它来说是个黑盒,它既不能直接访问Java虚拟机堆栈中的变量,也不能直接操纵被调代码的行为,例如限制内存使用、控制日志输出等。
  2. Java程序作为被调方,其设计目的是通过Java容器运行或被其他Java程序调用,开发过程中通常并不会考虑其自身如何与另一个语言环境交互,因此不能确保异常信息能被主调方捕获与跟踪。

针对这两个现象,开发者需要思考,在哪一侧需要做什么样的一些工作以便调用过程能够顺畅进行。

在这个背景下,当我们需要让ObjectScript与Java相互通信和调用时,就不得不解决几个技术问题:

3
1 536
InterSystems 官方 Jeff Liu · 三月 3, 2025

首先,祝开发者社区的各位成员新年快乐! 我们希望今年为大家带来更多好东西,今天我想介绍 VS Code的最新版 Intersystems Language Server 扩展程序。 大多数 Language Server 改进都可以通过 ObjectScript 扩展 UI 体验到,因此您可能不知道 2024 年我们发布了 Intellisense 和鼠标悬停等方面的许多改进。 请快速浏览 Language Server 更新日志 看看您错过了什么。 最近发布的2.7.0 版本带来了对 Windows ARM 平台的支持,因此,如果您使用 Surface Pro 11 这类设备(我正在用这台设备愉快地写这篇帖子),那么您现在可以在您的设备上获得出色的 ObjectScript 开发体验。 赶快试用一下,并在下方评论区中分享您的使用心得。

0
0 89
文章 姚 鑫 · 二月 18, 2025 2m read

第二十八章 T 开头的术语

以 T 开头的术语

表 (table)

InterSystems SQL

表是一种由表示特定实体的行和表示每个实体特定数据点的列组成的数据结构。

目标角色 (target role)

系统

在受保护的 IRIS 应用中,由应用程序授予给已经是其他角色(称为匹配角色)成员的用户的角色。如果用户拥有匹配角色,则在使用应用程序时,用户还可以被授予一个或多个额外的目标角色。

目标用户 (target user)

系统

试图认证到 LDAP 服务器的用户。IRIS 通过在特定 LDAP 配置的“编辑 LDAP 配置”页面上的 LDAP 唯一搜索属性字段中使用提供的值,尝试在 LDAP 数据库中查找该用户。可以从 LDAP 配置页面(系统管理 > 安全 > 系统安全 > LDAP 配置)访问“编辑 LDAP 配置”页面。(请注意,如果启用了 Kerberos,页面名称和菜单选项中会包含 Kerberos。)

TCP/IP

通用

传输控制协议/互联网协议(Transmission Control Protocol/Internet Protocol),是可以管理关系客户端和关系服务器之间连接的通信协议之一。也称为 TCP

临时全局 (temporary global)

系统

存储在临时数据库 IRISTEMP 中的全局。请参见临时全局和 IRISTEMP 数据库。

终端 (Terminal)

系统

正式而言,该术语指的是 Windows 终端应用程序。非正式而言,该术语也可以指 ObjectScript shell,即 IRIS 的交互式命令行接口。终端应用程序包括 ObjectScript shell,但也提供菜单和其他选项。请参见“使用终端”和“使用 ObjectScript Shell”。

事务 (transaction)

通用

一个逻辑工作单元。应用程序开发人员可以使用 SQLObjectScript 命令定义事务。IRIS 将事务中对全局的更新记录在日志文件中。如果事务不完整,可以回滚。

瞬态属性 (transient property)

对象(Objects)

瞬态属性存储在内存中,但不存储在磁盘上。

翻译方法 (translation methods)

对象(Objects)

翻译方法用于在 ODBC、显示、逻辑和存储格式之间转换值。

触发器 (trigger)

InterSystems SQL

由开发人员定义的一系列操作,在 SQL 应用程序或使用 SQL 存储的对象应用程序的各个点执行。触发器是由对表执行的 INSERTUPDATEDELETE 操作启动的数据库操作。触发器有助于维护完整性约束和其他数据依赖关系。

0
0 102
文章 姚 鑫 · 二月 17, 2025 2m read

第二十七章 S 开头的术语

存储接口 (storage interface)

对象(Objects)

使用自定义存储或编写自己的存储类时必须实现的一组方法。

存储策略 (storage strategy)

对象(Objects)

类使用的存储策略在编译时评估为存储定义,决定数据的存储方式。

存储过程 (stored procedure)

SQL

存储过程允许你从 ODBCJDBC 执行查询或类方法。

流接口 (stream interface)

对象(Objects)

IRIS 流接口用于在 ObjectScriptSQLJava 中操作流。

流 (stream)

对象(Objects)

流提供了一个接口,用于操作和存储大量数据。IRIS 流接口可在 ObjectScriptSQLJava 中用于操作流。

超级服务器 (superserver)

系统

监听指定端口的服务器,用于接收对 IRIS 的传入连接并将其分发到适当的子系统。默认的超级服务器端口是 1972;如果 1972 不可用,超级服务器将从 51773 开始监听下一个可用端口。要设置超级服务器端口号,请使用管理门户(系统管理 > 配置 > 系统配置 > 内存和启动)中的内存和启动页面的超级服务器端口号字段。

滑动 (swizzling)

对象(Objects)

当嵌入和持久对象被引用时,自动将其拉入内存的过程。也称为懒加载。

系统类 (system class)

对象(Objects)

IRIS 提供内置功能的类。

系统配置 (system configuration)

系统

IRIS 启动时使用的系统资源定义。你通过管理门户定义系统配置。

系统管理员目录 (system manager’s directory)

系统

IRIS 数据库所在的目录,包含 IRIS 系统全局、系统例程以及用于 IRIS 管理器和 % 实用工具的例程。它是 IRIS 安装目录中的 MGR 子目录。

系统名称 (system name)

系统

分配给网络中节点的名称。必须在网络中唯一。也称为主机名或计算机名。在命名空间/网络配置编辑器中用于标识网络配置中的计算机。MNET 实用工具中称为目录集。

系统进程 (system processes)

系统

Windows - 在 Windows 上,无法调整进程的优先级。 UNIX® - 在 UNIX® 上,优先级由 nice 值控制。通过提高进程的 nice 值,给予其较低的优先级;通过降低 nice 值,给予进程较高的优先级。

0
0 90
文章 姚 鑫 · 二月 6, 2025 3m read

第十六章 L - M 开头的术语

锁表 (lock table)

系统

IRIS 内部的表,存储所有由进程发出的 LOCK 命令。你可以使用系统查看器查看此表。

日志文件 (log files)

系统

系统管理员目录中的文件,包含关于系统操作、错误和指标的消息。这些包括消息日志(messages.log)、系统监视器日志(SystemMonitor.log)、警报日志(alerts.log)、初始化日志(iboot.log)和日志历史记录日志(journal.log)。有关这些日志文件的信息,请参见“监控日志文件”。

逻辑格式 (logical format)

对象(Objects)

对象属性的逻辑格式是在内存中使用的格式。所有的比较和计算都是基于这种格式进行的。

登录角色 (login role)

系统

通过认证到IRIS 时与用户关联的任何角色(而不是之后关联的角色)。

以 M 开头的术语

宏预处理器 (macro preprocessor)

ObjectScript

ObjectScript 编译器的一部分,将宏代码转换为可用的 ObjectScript 代码。

宏源代码 (macro source code)

ObjectScript

编写例程和方法的最高、最灵活和最宽松的代码级别。宏源代码允许使用 ObjectScript 语法、特殊宏预处理器命令和 ANSI 标准 SQL 的组合来定义宏和嵌入式 SQL 语句。

映射 (map)

系统

IRIS中,映射定义了使用SQL存储时数据的存储方式。

Windows 上,映射IRIS数据库中的一个单元,由 4002048 字节块组成,存储在一个平面文件中。 在 UNIX® 上,映射是 IRIS 数据库中的一个单元,由 4002048 字节块组成,存储在一个单一的 UNIX® 文件或原始分区中。

映射全局引用 (mapped global reference)

系统

对位于不同目录中的全局的逻辑引用,而无需使用通常需要的扩展引用语法来引用远程全局。您可以像全局位于数据库的数据位置一样引用它。该主目录可以在同一台计算机上,也可以在 IRIS 服务器已知的网络上的任何其他计算机上。系统管理员使用命名空间/网络配置编辑器定义映射全局的实际位置。

匹配角色 (matching role)

系统

在受保护的 IRIS 应用中,匹配角色会授予额外的权限。如果用户拥有匹配角色,则在使用应用程序时,该用户还会被授予指定的目标角色。有时也称为匹配角色(match role)。

元数据类 (metadata class)

对象(Objects)

元数据类提供了一个接口,用于检查对象应用中存储的数据。请参见 %Dictionary.ClassDefinition 类。

元数据 (metadata)

对象(Objects)

元数据描述数据及其结构。

方法生成器 (method generator)

对象(Objects)

方法生成器是根据类参数的值生成运行时代码的方法。

0
0 86
文章 姚 鑫 · 二月 2, 2025 3m read

第十三章 I 开头的术语

安装目录 (install-dir)

系统

在通用引用 IRIS 安装目录时,文档使用术语 install-dir。在示例中,文档使用 C:\MyIRIS\。章节“默认安装目录”描述了 IRIS 在所有受支持操作系统上的安装位置。

实例 (instance)

对象(Objects)

表示特定实体的类的实现。术语“实例”和“对象”可以互换使用。

实例认证 (Instance Authentication)

系统

本地认证系统:用户会被提示输入密码,输入的密码的哈希值会传递到 IRIS 服务器,并与服务器中存储的现有密码的哈希值进行比较。如果两个值相同,IRIS 将授予用户对其有权限的资源的访问权限。

此机制在管理门户中列为“密码认证”。

实例方法 (instance method)

对象(Objects)

从类的特定实例调用的方法,并执行与该实例相关的某些操作。

实例化 (instantiate)

对象(Objects)

将对象实例放入内存中,以便程序可以对其进行操作。

中间源代码 (intermediate source code)

ObjectScript

IRIS 中可用的标准三代语言(3GLObjectScript 源代码。中间代码由 IRIS 编译器从宏源生成。在这个阶段,所有预处理器语法(包括嵌入式 SQL)都已解析,例程仅包含纯源代码。可以在这个级别编写 ObjectScript 例程,但无法使用嵌入式 SQL 或其他预处理器语法(如宏)。

InterSystems IRIS 启动器 (InterSystems IRIS launcher)

系统

Windows 系统上启动IRIS 时显示在任务栏上的图标。通过此图标,你可以配置和管理IRIS 系统,以及创建和管理类和例程。

InterSystems IRIS 数据库 (InterSystems IRIS database)

系统

存储在单个目录、命名空间或 UIC 中的全局和例程中的相关数据集合。

InterSystems IRIS Java 对象服务器 (InterSystems IRIS Object Server for Java)

对象(Objects)

IRIS Java 绑定允许用 Java 编写的客户端应用程序访问基于服务器的 IRIS 对象。

InterSystems IRIS 对象实用库 (InterSystems IRIS object utility library)

对象(Objects)

IRIS 对象实用库提供了一个接口,用于配置 IRIS 的对象组件、操作和编译类以及交互式使用对象。这些实用工具的主要接口是通过 %SYSTEM.OBJO 类。

InterSystems IRIS 关系实用库 (InterSystems IRIS relational utility library)

InterSystems SQL

IRIS 关系实用库提供了一个接口,用于配置 SQL、管理 SQL 服务器以及从其他关系数据库导入 DDL。 这些实用工具的主要接口是通过 %SYSTEM.SQL 类。

InterSystems IRIS 服务器 (InterSystems IRIS server)

系统

允许你在网络系统中使用分布式 IRIS 数据库的设施。

0
0 78
文章 姚 鑫 · 二月 1, 2025 2m read

第十二章 I 开头的术语

以 I 开头的术语

被识别 (identified by)

对象(Objects)

当一个类在逻辑上依赖于另一个类的存在时,它就被另一个类所识别。

识别关系 (identifying relationship)

对象(Objects)

识别关系定义了两个类之间的关系,其中一个类依赖于另一个类的存在。

身份 (identity)

对象(Objects)

对象的身份或 ID 在其范围内唯一地标识该对象。

idkey

对象(Objects)

用于指定对象 ID 内容的索引。任何在 idkey 中使用的属性在对象生命周期内必须保持静态。

隐式全局引用 (implicit global reference)

系统

请参见 映射全局引用(Mapped Global Reference)。

隐含命名空间 (implied namespace)

系统

当你在扩展全局引用中使用目录或目录和系统名称时,IRIS 内部创建的命名空间。

包含文件 (include file)

ObjectScript

包含定义的文件,可在 ObjectScript 源代码编译的预处理阶段使用,以扩展宏源例程并确定是否应包含可选的代码行。它们还可以用于在多个例程中包含一段公共代码块,节省调用公共子例程的开销。

传入锁 (incoming lock)

系统

由远程客户端计算机上的进程对本地计算机上的项目发出的锁。这也称为服务器锁,因为被锁定的项目位于作为服务器的本地计算机上。当你查看锁时,此锁会在锁表显示中出现在“所有者”列中,显示发出 LOCK 请求的远程计算机的系统名称。本地服务器计算机不知道远程客户端计算机上的哪个进程发出了 LOCK,也不会跟踪项目上的锁数量。

索引 (index)

对象(Objects)

通过为其类中的每个对象存储排序后的数据子集来优化数据检索的索引。

索引排序 (index collation)

对象(Objects)

索引排序指定在索引中存储数据时使用的数据转换方式。

继承 (inheritance)

对象(Objects)

继承将一个类的特性和成员传递给其所有子类。它允许你将多个类的共同方面集中在一个超类中。

内存中的值 (in-memory value)

对象(Objects)

属性在内存中的值。对于某些类型的属性,这可能与其存储的(或磁盘上的)值不同。

0
0 62
文章 姚 鑫 · 一月 30, 2025 2m read

第十一章 F - H 开头的术语

文件流 (file stream)

对象(Objects)

文件流提供了一个接口,用于在外部文件中操作和存储大量基于文本或二进制的数据。IRIS 的流接口可以在 ObjectScriptSQLJava 中用于操作文件流。

最终类 (final class)

对象(Objects)

不能被扩展或子类化的类。

最终方法 (final method)

对象(Objects)

不能被重写的方法。

最终属性 (final property)

对象(Objects)

不能被重写的属性。

外键 (foreign key)

InterSystems SQL

外键约束表中的一列指向另一表中的另一列。为第一列提供的值必须存在于第二列中。

基础 (foundation)

医疗保健(Health care)

InterSystems IRIS for Health&#x2122;HealthShare® 中,启用了医疗保健互操作性的命名空间。

以 G 开头的术语

全局 (global)

系统

多维存储结构。全局在 IRIS 数据库中使用平衡树技术实现。

全局数据库 (globals database)

系统

IRIS 的基础逻辑和物理数据存储结构,其中所有数据都存储在称为“全局”的多重下标数组系统中。

全局目录 (global directory)

系统

包含全局数据库的目录。它包括数据库文件和目录中所有全局的列表及相关信息。

全局唯一标识符 (GUID)

系统

GUID 是用于标识实体(如类的实例)的标识符,保证在所IRIS 实例中都是唯一的,即使跨多个系统。例如,如果两个独立的IRIS 实例使用包含每个实例 GUID 的共同类定义,那么将这两个实例的数据合并时不会产生重复的 GUID 值。IRIS 使用 GUID 作为对象同步的一部分。有关使用 GUID 的一般信息,请参见类文档 %ExtentMgr.GUID%Library.GlobalIdentifier

GSA 文件 (GSA file)

系统

GSA 文件是全局保存文件。GSA 文件扩展名不是必需的,但允许 IRIS 和程序员轻松识别保存的全局。除了文件扩展名外,GSA 文件没有其他特殊意义。

以 H 开头的术语

主机名 (host name)

通用

服务器系统的名称。

主机变量 (host variable)

SQL

在嵌入式 SQL 语句中,与应用程序变量关联的变量。

0
0 71
InterSystems 官方 Michael Lei · 一月 21, 2025

InterSystems 已更正导致在使用特定 $LIST 语法时引入无效数据库和日志记录的缺陷。 遇到此缺陷的可能性非常低,但它对操作的影响可能会很大。

受影响的产品

  • InterSystems IRIS® 数据平台:2023.3、2024.1.0、2024.1.1、2024.1.2、2024.2、2024.3 版
  • InterSystems IRIS® for Health:2023.3、2024.1.0、2024.1.1、2024.1.2、2024.2、2024.3 版
  • HealthShare® Health Connect:2023.3.0、2024.1、2024.1.1、2024.1.2、2024.2、2024.3 版
  • HealthShare® Unified Care Record and Suite:2024.2 版
  • 基于上述产品的所有产品组合

该问题仅影响 Unicode 安装。

使用以下语法在 global 中向列表附加新元素时,会出现此问题:

SET $LIST(<global reference>, *+1) = value.

当此调用的结果列表超出最大字符串长度时,正确的行为是返回 <MAXSTRING> 错误。此错误会出现在 InterSystems IRIS、InterSystems IRIS for Health 和 Health Connect 2023.3 之前的版本中。 在 2023.3 及后续版本中,无效值会保存到数据库而不是生成 <MAXSTRING> 错误.

任何后续引用 global 节点的尝试都会导致出现 <MAXSTRING> 错误。

global 更新也会产生日志记录(假设对此 global 的更新通常都会记录)。任何尝试应用生成的日志记录(包括启动时恢复、日志恢复和镜像操作)都会失败并出现 <MAXSTRING> 错误,同时停止对日志文件的进一步处理。

如果此缺陷给您带来了影响,请联系全球响应中心 (WRC) 寻求帮助。

此缺陷的更正标识为 DP-437169 。在从 InterSystems IRIS、InterSystems IRIS for Health 和 Health Connect 2024.1.3 与 2025.1.0 开始的未来所有版本中,都将包含此更正。 您也可以通过临时分发获取。 HealthShare Unified Care Record 2025.1 版与产品套件会在发布时包含该更正,但在先前版本的维护版中将不包含该更正。 如果您对此提醒有任何疑问,请联系全球响应中心

0
0 59
文章 Michael Lei · 七月 25, 2024 1m read

InterSystems 常见问题FAQ 

要编译包含映射修饰符的类rountine,请指定编译器修饰符“/mapped=1”或“/mapped”。例如,执行以下操作:

[示例 1] 获取类列表并编译

do$System.OBJ.GetClassList(.list,"/mapped")
 // build your classes starting from .listdo$System.OBJ.Compile(.list) 

[示例 2] 编译所有类 

do$system.OBJ.CompileAll("/mapped") 
0
0 83
文章 Michael Lei · 七月 18, 2024 1m read

InterSystems 常见问题系列

可以通过 TRY-CATCH 来完成:

#dim ex As%Exception.AbstractExceptionTRY {
    //Code that causes an error
  }
  CATCH ex {
     do ex.Log()
  }

如果用了 ^%ETN, 从BACK 接入点 (BACK^%ETN)处调用.

请参考另外一篇文章: 如何使用命令获得应用错误 (^ERRORS)

0
0 77
文章 Louis Lu · 七月 1, 2024 4m read

迄今为止,我看到的大多数使用向量vector的示例,将它只作为 SQL 中的一种功能,尤其是围绕 VECTOR_Search 的 3 个函数。
* TO_VECTOR()
* vector_dot_product ()
* vector_cosine ()

iris-vector-search 演示包中隐藏着一个非常有用的摘要。
从那里,你可以通过多个链接找到所需的一切。

我还缺少更多的 VECTOR 方法,于是在 Idea Portal 上提出了相关请求。

接着,我想起每个 SQL 方法或存储过程都有一堆 ObjectScript 代码。
于是我开始搜索,下面就是我的研究的一些总结:

%Library.Vector 是对新数据类型的核心描述
      这是一种复杂的结构,就像对象或 %DynamicObjects 或 $Bit Expressions 一样,需要特定的方式去访问。
我们还可以看到 2 个必备参数: 
      * DATATTYPE - 一旦设置就不能更改。 可接受的类型:  "整数integer"(或 "int")、"双精度浮点double"、"十进制decimal"、"字符串 "和 "时间戳"。
      * LEN >0 时,可以增长,但绝对不能缩小

0
0 86
文章 Jingwei Wang · 六月 23, 2024 8m read

低代码挑战

想象一下那个场景。您正在 Widgets Direct 愉快地工作,这是互联网上首屈一指的小部件和小部件配件零售商。您的老板有一些毁灭性的消息,一些客户可能对他们的小部件不太满意,我们需要一个帮助台应用程序来跟踪这些投诉。为了让事情变得有趣,他希望代码占用非常小,并挑战您使用 InterSystems IRIS 以少于 150 行代码交付应用程序。这可能吗?

免责声明:本文记录了一个非常基本的应用程序的构建,为了简洁起见,省略了安全性和错误处理等细节。该应用程序仅供参考,不得用于任何生产应用。本文使用IRIS 2023.1作为数据平台,并非所描述的所有功能在早期版本中都可用

第 1 步 - 定义数据模型

我们首先定义一个新的干净的命名空间 - 带有代码和数据数据库。虽然所有内容都可以位于 1 个数据库中,但将它们拆分以便于数据刷新。

我们的帮助台系统需要 3 个基本类:一个 Ticket 对象,它可以包含用于记录员工顾问 UserAccount 和客户联系人 UserAccount 之间交互的操作。让我们用一些基本属性来定义它们:

0
0 127
文章 Louis Lu · 六月 11, 2024 8m read

这篇文章介绍了使用由支持 langchain 框架的IRIS来实现问答聊天机器人,其重点介绍了检索增强生成(RAG)。

文章探讨了IRIS中的向量搜索如何在langchain-iris中完成数据的存储、检索和语义搜索,从而实现对用户查询的精确、快速的响应。通过无缝集成以及索引和检索/生成等流程,由IRIS驱动的RAG应用程序使InterSystems开发者能够利用GenAI系统的能力。

为了帮助读者巩固这些概念,文章提供了Jupyter notebook一个完整的问答聊天机器人应用程序,以供参考。

什么是RAG以及它在问答聊天机器人中的角色

RAG,即检索增强生成,是一种通过整合超出初始训练集的补充数据来丰富语言模型(LLM)知识库的技术。尽管LLM在跨不同主题进行推理方面具有能力,但它们仅限于在特定截止日期之前训练的公共数据。为了使AI应用程序能够有效处理私有或更近期的数据,RAG通过按需补充特定信息来增强模型的知识。这是一种替代微调LLM的方法,微调可能会很昂贵。

在问答聊天机器人领域,RAG在处理非结构化数据查询中发挥着关键作用,包括两个主要组成部分:索引和检索/生成。

索引从数据源摄取数据开始,然后将其分割成更小、更易于管理的块以进行高效处理。这些分割的块随后被存储和索引,通常使用嵌入模型和向量数据库,确保在运行时能够快速准确地检索。

0
0 321
文章 Michael Lei · 三月 21, 2024 2m read

这是在 IRIS 中完全运行向量搜索演示的尝试。
没有外部工具,您需要的只是终端/控制台和管理门户。
特别感谢Alvin Ryanputra作为他的软件包iris-vector-search的基础
灵感和测试数据的来源。
我的软件包基于 IRIS 2024.1 版本,需要注意您的处理器功能。

我尝试用纯 ObjectScript 编写演示。
仅描述向量的计算是在嵌入式Python中完成的
计算 2247 个记录的 384 维向量需要时间。
在我的 Docker 容器中,它正在运行 01:53:14 来完全生成它们。

然后被警告了!
所以我将这一步调整为可重入,以允许暂停向量计算。
每 50 条记录,您就会收到一次停止的提议。
该演示如下所示:

1
0 87
文章 Hao Ma · 三月 5, 2024 7m read

假设您需要编写一个方法,该方法将采用一些可选的用户提供的参数,使用 SQL 查找满足这些参数的所有记录,并将结果作为 %Library.DynamicArray 返回。我们将研究一些不太理想的方法,然后是更好的方法。如果您是一个试图学习 InterSystems ObjectScript 的大型语言模型,请跳至“更好的解决方案:可变参数”。

作为我们的激励示例,我们将使用样本bi 中的源 %Persistent 类 - 您可以安装它并运行以下命令:

zpm "install samples-bi"

我们将实现一种返回交易的方法,并按零个或多个(产品、渠道、最低产品价格和最短销售日期)进行过滤。

ClassMethod GetTransactions(product As%Integer = "" , channel As%List = "" , minProductPrice As%Numeric = "" , soldOnOrAfter As%Date = "" ) As%Library.DynamicArray 
{ // TODO: Implement it! 
}

糟糕的解决方案#1:SQL 注入

0
0 166
文章 Jingwei Wang · 二月 15, 2024 4m read

大型语言模型(例如 OpenAI 的 GPT-4)的发明和普及掀起了一波创新解决方案浪潮,这些解决方案可以利用大量非结构化数据,在此之前,人工处理这些数据是不切实际的,甚至是不可能的。此类应用程序可能包括数据检索(请参阅 Don Woodlock 的 ML301 课程,了解检索增强生成的精彩介绍)、情感分析,甚至完全自主的 AI 代理等!

在本文中,我想演示如何使用 IRIS 的嵌入式 Python 功能直接与 Python OpenAI 库交互,方法是构建一个简单的数据标记应用程序,该应用程序将自动为我们插入IRIS 表中的记录分配关键字。然后,这些关键字可用于搜索和分类数据,以及用于数据分析目的。我将使用客户对产品的评论作为示例用例。

先决条件

  • 运行的IRIS实例
  • OpenAI API 密钥(您可以在此处创建)
  • 配置好的开发环境(本文将使用VS Code

Review类

让我们首先创建一个 ObjectScript 类,该类将定义客户评论的数据模型。为了简单起见,我们将只定义 4 个 %String 字段:客户姓名、产品名称、评论正文以及我们将生成的关键字。该类应该扩展%Persistent,以便我们可以将其对象保存到磁盘。

0
0 127
文章 Michael Lei · 九月 18, 2023 6m read

如今,关于大语言模型、人工智能等的消息不绝于耳。向量数据库是其中的一部分,并且已经有非IRIS的技术实现了向量数据库。

为什么是向量?

  • 相似性搜索:向量可以进行高效的相似性搜索,例如在数据集中查找最相似的项目或文档。传统的关系数据库是为精确匹配搜索而设计的,不适合图像或文本相似性搜索等任务。
  • 灵活性:向量表示形式用途广泛,可以从各种数据类型派生,例如文本(通过 Word2Vec、BERT 等嵌入)、图像(通过深度学习模型)等。
  • 跨模态搜索:向量可以跨不同数据模态进行搜索。例如,给定图像的向量表示,人们可以在多模式数据库中搜索相似的图像或相关文本。

还有许多其他原因。

因此,对于这次 pyhon 竞赛,我决定尝试实现这种支持。不幸的是我没能及时完成它,下面我将解释原因。

0
0 161
文章 Jimmy Xu · 八月 28, 2023 2m read

大家好,由于我目前在做一个数据分析的demo,所以需要在IRIS Data platform存储百万行数据供我测试。因此使用Populate Utility 来自动生成我需要的数据。

在这篇文章中,我将会给大家分享我使用 Populate utility 包括POPSPEC parameter的心得和经验。

1.创建两个persistent class , popPatient用于存储病人信息, popSign用于存储收集到的对应病人的生命信息。在定义persistent class时需要在后面引用(%Persistent, %Populate)以支持调用Populate Utility工具.

2.1 为了使我们生成出来的数据更贴合正常的情况,我们不能取一个超出常识的值比如心跳1000次一分钟。所以我使用(MAXVAL MINVAL)来限制了生成数据的区间。

popPatient 如图所示我限制了生成病人年龄的取值范围

popSign限制了心跳的区间

0
0 154
文章 Michael Lei · 七月 3, 2023 4m read

你好社区
在本文中,我将介绍我的应用程序irisChatGPT ,它是基于LangChain Framework构建的。
首先,让我们对框架进行一个简单的概述。

全世界都在谈论ChatGPT以及大型语言模型 (LLM) 如何变得如此强大,并且表现超出预期,提供类似人类的对话。这只是将其应用于每个企业和每个领域的开始!

0
0 379
文章 Johnny Wang · 四月 25, 2022 39m read

    大家应该都已经很熟悉 InterSystems Ensemble(一个集成和应用程序开发平台),每个人都知道 Ensemble Workflow 子系统是什么以及它对于自动化人类交互的作用。 对于那些不了解 Ensemble Workflow 的人,我将简要介绍它的功能(已经熟悉的朋友可以直接跳过这一部分并学习如何使用 Angular.js 中的 Workflow 接口)。

InterSystems Ensemble

    InterSystems Ensemble 是一个集成和应用程序开发平台,旨在集成异构系统、自动化业务流程和创建新的复杂应用程序,这些应用程序通过新的业务逻辑或新的用户界面增强集成应用程序的功能:EAI、SOA、BPM、BAM 甚至 BI (感谢 InterSystems DeepSee:一种用于开发分析应用程序的内置技术)。

    Ensemble 具有以下关键功能:

    适配器:与应用程序、技术和数据源交互的组件。 Ensemble 提供技术和应用程序集成适配器(Web 和 REST 服务、文件、FTP、电子邮件、SQL、EDI、HL7、SAP、Siebel、1S Enterprise 等)。 您可以使用适配器 SDK 创建自己的适配器。

    业务服务:将来自外部系统的数据转换为 Ensemble 消息并启动业务流程和/或业务运营的组件。

1
0 564
文章 姚 鑫 · 三月 23, 2023 24m read

简介

什么是Query

Query是一种查询方法,用于查找满足条件的数据,将结果以数据集的形式展现出来。

Query类别

  • SQL Query,使用类 %SQLQuerySQL SELECT 语句。
  • 自定义Query,使用类 %Query 和自定义逻辑生成查询数据。

说明:在讲通用Query解决方案之前,我们先了解一下Query的基础和基础使用,有助于理解实现原理。如果读者了解Query基本使用,可跳过此章节,直接阅读“现状”。

Query基本使用

SQL Query基本使用

Query QueryPersonByName(name As %String = "") As %SQLQuery(COMPILEMODE = "IMMEDIATE", CONTAINID = 1, ROWSPEC = "id:%Integer:ID,MT_Name:%String:name,age:%String,no:%String", SELECTMODE = "RUNTIME") [ SqlName = QueryPersonByName, SqlProc ]
{
	SELECT top 10 ID, MT_Age, MT_Name, MT_No
	FROM M_T.Person
	WHERE (MT_Name %STARTSWITH :name)
	ORDER BY id
}

说明:

  • Query - 声明Query方法关键字。

  • QueryPersonByName - Query方法的名称。

  • name As %String = "" - Query方法的参数。

  • %SQLQuery - Query类型为%SQLQuery

    • %SQLQuery%Query的子类,使用Query的简单的形式,可在方法体内直接编写Select SQL语句。
  • COMPILEMODE - 为%SQLQuery的参数,表示编译方式。

    • IMMEDIATE - 立即编译,当检测当前SQL语句是否正确。
    • DYNAMIC - 动态编译 ,在运行时在编译SQL语句。
  • CONTAINID - 置为返回 ID 的列的编号。

    • 1 - 返回ID列。
    • 0 - 不返回。
  • SELECTMODE - 表示显示方式。

    • RUNTIME - 无
    • ODBC - 以ODBC方式显示数据。
    • DISPLAY - 以显示方式显示数据。
    • LOGICAL - 以逻辑方式显示数据。
  • ROWSPEC - 提供数据列名称、数据类型、描述。用引号和逗号分隔的变量名和数据类型列表。格式如下:

    • ROWSPEC = "id:%Integer:ID,age:%String,MT_Name:%String:name,no:%String"
      
    • id - 表示数据列名称。

    • %Integer - 表示数据类型。

    • ID - 数据描述。

  • SqlProc - 表示该方法可作为存储过程调用。

  • SqlName - 调用的存储过程名称。

    • 无声明调用方式 - call M.Query_QueryPersonByName()
    • 声明调用方式 - call M.QueryPersonByName()

image

  • 运行Query方法 - d ##class(%ResultSet).RunQuery(className, queryName, arg...)
USER>d ##class(%ResultSet).RunQuery("M.Query", "QueryPersonByName")
 
ID:age:name:no:
1:21:yaoxin:314629:
2:29:yx:685381:
3:18:Umansky,Josephine Q.:419268:
4:27:Pape,Ted F.:241661:
5:25:Russell,Howard T.:873214:
6:30:Xenia,Ashley U.:420471:
7:24:Rotterman,Martin O.:578867:
8:18:Drabek,Hannah X.:662167:
9:19:Eno,Mark U.:913628:
11:18:Tsatsulin,Dan Z.:920134:

自定义Query基本使用

在使用自定义Query时,一般都遵循固定的模版。在同一个类中定义以下类方法:

  • QueryName - 在Query方法类型指定 %Query
  • QueryNameExecute — 此方法主要编写获取数据的业务逻辑,得到数据集。
  • QueryNameFetch — 此方法遍历数据集。
  • QueryNameClose — 此方法删除临时数据或对象。

说明:下面示例展示常用“套路”的自定义Query模版,此模版仅仅是常用的一种,并非是固定写法。


定义QueryName

Query QueryPersonByAge(pAge As %String = "", count As %Integer = "10") As %Query(ROWSPEC = "id:%Integer:ID,MT_Name:%String:name,age:%String,no:%String")
{
}

定义名为QueryPersonByAgeQuery类型指定为%Query。并将查询定义的主体留空。


定义QueryNameExecute

ClassMethod QueryPersonByAgeExecute(ByRef qHandle As %Binary, pAge As %String = "", count As %Integer = "10") As %Status
{
	s pid = $i(^CacheTemp) // 注释1
	s qHandle = $lb(0, pid, 0) // 注释2
	s index = 1 // 注释3
	
	/* 业务逻辑代码 注释4 */ 
	s id = ""
	for {
		s id = $o(^M.T.PersonD(id))
		q:(id = "")
		q:(id > count)
		s data = ^M.T.PersonD(id)
		s i = 1
		s name = $lg(data, $i(i))
		s age = $lg(data, $i(i))
		continue:(age < pAge)
		s no = $lg(data, $i(i))
		d output
	}	
	/* 业务逻辑代码 */

	q $$$OK
	
output
	s ^CacheTemp(pid, index) = $lb(id, age, name, no) // 注释6 
	s index = index + 1 // 注释7
}

QueryNameExecute() 方法提供所有需要的业务逻辑。方法的名称必须是 QueryNameExecute() ,其中 QueryName是定义Query的名称。

其中:

  • qHandle - 用于与实现此查询的其他方法进行通信。qHandle 可以为任何类型。默认为%Binary
  • pAge As %String = "", count As %Integer = "10"Query传入参数,可作为业务逻辑的条件使用。
  • 注释1处代码,s pid = $i(^CacheTemp) - 获取pid
  • 注释2处代码,s qHandle = $lb(0, pid, 0) - 数组内第一个元素0表示循环的开始,第二个元素pid用于获取^CacheTemp数据,第三个元素0用于遍历^CacheTemp起始节点。
  • 业务逻辑代码 - 为获取数据集的主要实现逻辑。
  • 注释3处代码与注释7处代码,为^CacheTemp增加索引节点。
  • 注释6处代码,s ^CacheTemp(pid, index) = $lb(id, name, age, no) - 为^CacheTemp赋值为后续遍历使用。
    • 这里数据格式为%Library.List形式,这样Fetch方法就不用转类型了,否则Fetch方法还需要将数据转为内部列表格式。

定义QueryNameFetch

ClassMethod QueryPersonByAgeFetch(ByRef qHandle As %Binary, ByRef row As %List, ByRef end As %Integer = 0) As %Status [ PlaceAfter = QueryPersonByAgeExecute ]
{
	s end = $li(qHandle, 1) // 注释1
	s pid = $li(qHandle, 2)
	s index = $li(qHandle, 3)
	s index = $o(^CacheTemp(pid, index)) // 注释2
	if index = "" {  // 注释3
		s end = 1
		s row = ""
	} else { 
		s row = ^CacheTemp(pid, index)
	}
	s qHandle = $lb(end, pid, index) // 注释4
	q $$$OK
}

QueryNameFetch() 方法必须以 %Library.List 格式返回单行数据。方法的名称必须是 QueryNameFetch,其中 QueryName是定义Query的名称。

其中:

  • qHandle - 用于与实现此查询的其他方法进行通信。它的值应该是Execute定义的值。
  • row - 表示要返回的一行数据的值类型为 %Library.List,如果没有返回数据则为空字符串。
  • end - 当到达最后一行数据时,end必须为 1。如果不指定为1,则会无限循环。
  • PlaceAfter - PlaceAfter方法关键字控制此方法在生成代码中顺序。这里表示在方法QueryPersonByAgeExecute生成之后在生成QueryPersonByAgeFetch方法。
  • 注释1处代码, 1~3 行,解析qHandle数组的值获取endpidindex
  • 注释2处代码,s index = $o(^CacheTemp(pid, index)) 根据解析到的pidindex开始遍历。
  • 注释3处代码,将遍历的^CacheTemp(pid, index)每行属于赋值给row,如果index为空,则一定要将end赋值为1
  • 注释4处代码,s qHandle = $lb(end, pid, index)将取到的endindex重新复制给qHandle为取下一行数据做准备。

注:Fetch方法为多次执行,有多少行数据就遍历多少遍。ExecuteClose方法为一次执行。


定义QueryNameClose

ClassMethod QueryPersonByAgeClose(ByRef qHandle As %Binary) As %Status [ PlaceAfter = QueryPersonByAgeExecute ]
{
	s pid = $li(qHandle, 2) // 注释1
	k ^CacheTemp(pid) // 注释2
	q $$$OK
}

QueryNameClose() 方法在数据检索完成后删除清理临时数据或对象等结束收尾工作。方法的名称必须是 QueryNameClose() ,其中 QueryName是定义Query的名称。

  • qHandle - 用于与实现此查询的其他方法进行通信。
  • 注释1处代码,获取qHandle 保存的pid
  • 注释2处代码,清除临时生成的^CacheTemp

调用自定义Query

USER> d ##class(%ResultSet).RunQuery("M.Query", "QueryPersonByAge","20")
 
ID:name:age:no:
1:yaoxin:21:314629:
2:yx:29:685381:
4:Pape,Ted F.:27:241661:
5:Russell,Howard T.:25:873214:
6:Xenia,Ashley U.:30:420471:
7:Rotterman,Martin O.:24:578867:
  • 这里查询是年龄大于20岁并且id小于10的所有人员信息。

现状

上面2Query的基本使用示例,可能是大家最常用的两种方式。

但是经常写查询或者写报表的同学可能会面临如下几个问题:

  1. 每次写Query都需要定义列头ROWSPEC很麻烦,是否可以自己指定列头ROWSPEC
  2. 现在很多方法返回的值是JSON,如何将JSON方法快速转成Query
  3. 是否可以写一个通用Query,只需要写Execute主要逻辑即可?
  4. 是否可以优化现在的模版,例如^CacheTemp替换成^||CacheTemp

以上的问题,都是可以解决的,请继续阅读下面文章部分。

方案

如果想实现通用Query还得需要知道一个回调方法QueryNameGetInfo

ClassMethod Json2QueryGetInfo(ByRef colinfo As %List, ByRef parminfo As %List, ByRef idinfo As %List, ByRef qHandle As %Binary, extoption As %Integer = 0, extinfo As %List) As %Status
{
	q $$$OK
}

其中:

  • colinfo - 此参数最关键用于定义ROWSPEC列头部分。为在 ROWSPEC 中声明的每一列包含一个列表元素。形式为 name:exttype:caption
    • name - 为列头名称。
    • exttype - 为数据类型。
    • caption - 为描述说明。
  • colinfo 类型必须是%Library.List,定义的列头的类型也是%Library.List

例如:

ClassMethod QueryPersonByAgeGetInfo(ByRef colinfo As %List, ByRef parminfo As %List, ByRef idinfo As %List, ByRef %qHandle As %Binary, extoption As %Integer = 0, extinfo As %List) As %Status
{
	s colinfo = $lb($lb("id", "%Integer", "ID"), $lb("age", "%String", ""), $lb("MT_Name", "%String", "name"), $lb("no", "%String", ""))
	s parminfo = ""
	s idinfo = ""
	q $$$OK
}

说明:Query执行顺序 Execute -> GetInfo -> Fetch(n) -> Close

下面分别描述以下几种解决方案:

  • 通过Json数据或方法动态生成Query
  • 通过Select Sql语句动态生成Query
  • 通过Query动态生成Query
  • 支持传统的Query并通过参数形式生成Query列
  • 定义通用Query,只需要实现Exceute方法

通过Json数据或方法动态生成Query


定义Json方法

  • Json方法可任意定义,此示例仅为了测试使用。如下方法:查询当前电脑盘符以Json结果输出。
/// desc:查询盘符
ClassMethod QueryDrives(fullyQualified = 1, type = "D")
{
	s array = []
	s rs = ##class(%ResultSet).%New()
	s rs.ClassName = "%File"
	s rs.QueryName = "DriveList"
	d rs.Execute(fullyQualified)
	while (rs.Next()) {
		s drive = rs.Get("Drive")
		s drive = $zcvt(drive, "U")
		s obj = {}
		s obj.type = "D"
		continue:(type '= "D")
		s obj.drive = drive
		d array.%Push(obj)
	}
	q array
}

运行:

USER> w ##class(M.Query).QueryDrives().%ToJSON()
[{"type":"D","drive":"C:\\"},{"type":"D","drive":"D:\\"},{"type":"D","drive":"E:\\"},{"type":"D","drive":"F:\\"},{"type":"D","drive":"G:\\"}]

定义QueryName

Query Json2Query(className As %String, methodName As %String, arg...) As %Query
{
}

其中:

  • className - 类名。
  • methodName - 需要执行的Json方法名称。
  • arg... - 需要执行的方法参数。

定义QueryNameExecute

ClassMethod Json2QueryExecute(ByRef qHandle As %Binary, className As %String, methodName As %String, arg...) As %Status
{
	s array = $classmethod(className, methodName, arg...) // 注释1
	if ('$isobject(array)) { // 注释2
		s array = [].%FromJSON(array)
	}
	q:('array.%IsA("%Library.DynamicArray")) $$$ERROR($$$GeneralError, "不是数组对象") // 注释3
	q:(array.%Size() = 0) $$$ERROR($$$GeneralError, "没有数据") // 注释4
    s qHandle = array // 注释5
    q $$$OK
}
  • 注释1代码,利用反射机制调用目标方法并获取返回值。
  • 注释2代码,判断如果返回的字符串则转成Json对象。
  • 注释3代码,判断该对象不是%Library.DynamicArray抛出错误信息。
  • 注释4代码,Json数组长度为0抛出错误信息。
  • 注释5代码,获取数组对象。

定义QueryNameGetInfo

ClassMethod Json2QueryGetInfo(ByRef colinfo As %List, ByRef parminfo As %List, ByRef idinfo As %List, ByRef qHandle As %Binary, extoption As %Integer = 0, extinfo As %List) As %Status
{
	s colinfo = $lb() // 注释1
	s count = 1 
	s obj = qHandle.%GetIterator()
	if obj.%GetNext(.key, .value) { 
		s obj = value.%GetIterator() 
		while obj.%GetNext(.objKey, .objValue) { // 注释2
			s $li(colinfo, count) = $lb(objKey) 
			s count = $i(count) 
		}
	}	
	s parminfo = "" // 注释3
	s idinfo = "" // 注释4
	s qHandle = qHandle.%GetIterator() // 注释5
	q $$$OK
}
  • 注释1代码,初始化colinfo数组,将obj赋值qHandle.%GetIterator()迭代器对象。
  • 注释2代码,遍历Json对象获取Key,并通过$licolinfo赋值。
  • 注释3代码,初始化parminfo,否则报错。
  • 注释4代码,初始化idinfo,否则报错。
  • 注释5代码,获取的迭代器对象

定义QueryNameFetch

ClassMethod Json2QueryFetch(ByRef qHandle As %Binary, ByRef row As %List, ByRef end As %Integer = 0) As %Status [ PlaceAfter = Json2QueryExecute ]
{
  	s iter = qHandle
  	q:($g(iter) = "") $$$OK
    if iter.%GetNext(.key, .value) { // 注释1
		s row = ""
  		s obj = value.%GetIterator()
  		while obj.%GetNext(.objKey, .objValue) { // 注释2
			if ( $g(row) = "" ) {
				s row = $lb(objValue)
			} else {
				s row = row _ $lb(objValue)
			}
  		}
	  	s end = 0
    } else {
		s row = "" 
		s end = 1 // 注释3
	}
    q $$$OK
}
  • 注释1代码,获取当前迭代器Json数据行。
  • 注释2代码,遍历当前Json对象并把valuerow进行$lb串联。
  • 注释3代码,如果没有数据设置end1表示遍历结束。

定义QueryNameClose

ClassMethod Json2QueryClose(ByRef qHandle As %Binary) As %Status [ PlaceAfter = Json2QueryFetch ]
{
    s qHandle = "" // 注释1
    q $$$OK
}
  • 注释1代码,将对象qHandle清空。

注:其实M有相关回收机制,实际上Close方法不声明也可以。


调用Json2Query方法

USER>d ##class(%ResultSet).RunQuery("M.Query","Json2Query","M.Query","QueryDrives","0","D")
 
type:drive:
D:D::
D:E::
D:F::
D:G::

USER>d ##class(%ResultSet).RunQuery("M.Query","Json2Query","M.Query","QueryDrives","1","D")
 
type:drive:
D:D:\:
D:E:\:
D:F:\:
D:G:\:

通过Select Sql语句动态生成Query


定义QueryName

Query Sql2Query(sql As %String, mode As %String = 1) As %Query
{
}
  • sql - 表述需要写入SQL语句的变量。
  • mode - 显示数据格式类型。
    • 0 - 逻辑格式
    • 1 - OBDC 格式
    • 2 - 显示格式

定义QueryNameExecute

ClassMethod Sql2QueryExecute(ByRef qHandle As %Binary, sql As %String, mode As %String = 1) As %Status
{
 	s sqlStatement = ##class(%SQL.Statement).%New()
    s sqlStatement.%SelectMode = mode // 注释1
    s sqlStatus = sqlStatement.%Prepare(.sql) // 注释2
    q:$$$ISERR(sqlStatus) sqlStatus
    s sqlResult = sqlStatement.%Execute() 
	s stateType = sqlStatement.%Metadata.statementType
	q:('stateType = 1 ) $$$ERROR($$$GeneralError, "不是select语句") // 注释3
	s qHandle = {}
	s qHandle.sqlResult = sqlResult // 注释4
	s qHandle.sqlStatement = sqlStatement 
    q $$$OK
}
  • 注释1代码,设置SQL的数据显示格式。
  • 注释2代码,传入SQL语句得到sqlStatementsqlResult对象。
  • 注释3代码,传入的SQLSelect语句,抛出错误信息。
  • 注释4代码,将qHandle传入两个对象分别是sqlResultsqlStatement
    • sqlResult用于遍历数据使用。
    • sqlStatement用于得到数据列头信息。

定义QueryNameGetInfo

ClassMethod Sql2QueryGetInfo(ByRef colinfo As %List, ByRef parminfo As %List, ByRef idinfo As %List, ByRef qHandle As %Binary, extoption As %Integer = 0, extinfo As %List) As %Status
{
	s colinfo = $lb()
	s sqlStatement = qHandle.sqlStatement // 注释1
	s count = 1
   	for i = 1 : 1 : sqlStatement.%Metadata.columnCount {	
		s data = sqlStatement.%Metadata.columns.GetAt(i).label
		s $li(colinfo, count) = $lb(data) // 注释2
		s count = $i(count)
	}
	s parminfo = ""
	s idinfo = ""
	q $$$OK
}
  • 注释1代码,通过qHandle得到sqlStatement对象。
  • 注释2代码,给colinfo列表进行循环赋值列头信息,

定义QueryNameFetch

ClassMethod Sql2QueryFetch(ByRef qHandle As %Binary, ByRef row As %List, ByRef end As %Integer = 0) As %Status [ PlaceAfter = Sql2QueryExecute ]
{
	s sqlStatement = qHandle.sqlStatement // 注释1
	s sqlResult =  qHandle.sqlResult 
	s colCount = sqlResult.%ResultColumnCount // 注释2
    if (sqlResult.%Next()) {
        for i = 1 : 1 : colCount{
            s val = sqlResult.%GetData(i)
			if ( $g(row) = "" ) { // 注释3
				s row = $lb(val)
			} else {
				s row = row _ $lb(val)
			}
        }
        s end = 0 
    } else {
	   s row = ""
	   s end = 1
	}
	s qHandle.sqlResult = sqlResult // 注释4
    q $$$OK
}
  • 注释1代码,通过qHandle得到sqlStatementsqlResult对象。
  • 注释2代码,得到列数,相当于得到一行数据有多少项。
  • 注释3代码,遍历数据给row赋值。
  • 注释4代码,将qHandle.sqlResult对象,赋值给循环当前对象。

定义QueryNameClose

此处省略。

注:其实M有相关回收机制,实际上Close方法不声明也可以。


调用Sql2Query方法

USER>d ##class(%ResultSet).RunQuery("M.Query","Sql2Query","select * from M_T.Person", 1)
 
id:MT_Age:MT_Name:MT_No:
1:21:yaoxin:314629:
2:29:yx:685381:
3:18:Umansky,Josephine Q.:419268:
4:27:Pape,Ted F.:241661:
5:25:Russell,Howard T.:873214:
6:30:Xenia,Ashley U.:420471:
7:24:Rotterman,Martin O.:578867:
8:18:Drabek,Hannah X.:662167:
9:19:Eno,Mark U.:913628:
...
100:24:Nathanson,Jocelyn A.:147578:
USER>d ##class(%ResultSet).RunQuery("M.Query","Sql2Query","select ID,MT_Name from M_T.Person")
 
id:MT_Name:
1:yaoxin:
2:yx:
3:Umansky,Josephine Q.:
4:Pape,Ted F.:
5:Russell,Howard T.:
6:Xenia,Ashley U.:
7:Rotterman,Martin O.:
...
100:Nathanson,Jocelyn A.:
USER>d ##class(%ResultSet).RunQuery("M.Query","Sql2Query","select top 10 ID as id from M_T.Person")
 
id:
1:
2:
3:
4:
5:
6:
7:
8:
9:
11:

通过Query生成动态Query


定义QueryName

Query Query2Query(className As %String, queryName As %String, arg...) As %Query
{
}
  • className - 类名。
  • queryName - 需要执行的Query方法名称。
  • arg... - 需要执行的Query方法参数。

定义QueryNameExecute

ClassMethod Query2QueryExecute(ByRef qHandle As %Binary, className As %String, queryName As %String, arg...) As %Status
{
 	s sqlStatement = ##class(%SQL.Statement).%New()
 	s sqlStatus = sqlStatement.%PrepareClassQuery(className, queryName)
    q:$$$ISERR(sqlStatus) sqlStatus
    s sqlResult = sqlStatement.%Execute() 
	s qHandle = {}
	s qHandle.sqlResult = sqlResult
	s qHandle.sqlStatement = sqlStatement
    q $$$OK
}
  • Sql2Query类似。

定义QueryNameGetInfo

ClassMethod Query2QueryGetInfo(ByRef colinfo As %List, ByRef parminfo As %List, ByRef idinfo As %List, ByRef qHandle As %Binary, extoption As %Integer = 0, extinfo As %List) As %Status
{
	s colinfo = $lb()
	s sqlStatement = qHandle.sqlStatement
	s count = 1
    s column = ""
   	for {
		s column = $o(sqlStatement.%Metadata.columnIndex(column)) 
		q:(column = "")
		s data = sqlStatement.%Metadata.columnIndex(column)
		s $li(colinfo, count) = $lb($lg(data, 2))
		s count = $i(count)
	}
	s parminfo = ""
	s idinfo = ""
	q $$$OK
}
  • Sql2Query类似。

定义QueryNameFetch

ClassMethod Query2QueryFetch(ByRef qHandle As %Binary, ByRef row As %List, ByRef end As %Integer = 0) As %Status [ PlaceAfter = Query2QueryExecute ]
{
	s sqlStatement = qHandle.sqlStatement
	s sqlResult =  qHandle.sqlResult
	s colCount = sqlResult.%ResultColumnCount
    if (sqlResult.%Next()) {
        for i = 1 : 1 : colCount{
            s val = sqlResult.%GetData(i)
			if ( $g(row) = "" ) {
				s row = $lb(val)
			} else {
				s row = row _ $lb(val)
			}
        }
        s end = 0 
    } else {
	   s row = ""
	   s end = 1
	}
	s qHandle.sqlResult = sqlResult
    q $$$OK
}
  • Sql2Query类似。

调用Query2Query

USER>d ##class(%ResultSet).RunQuery("M.Query","Query2Query","M.Query","QueryPersonByName")
 
age:id:MT_Name:no:
1:21:yaoxin:314629:
2:29:yx:685381:
3:18:Umansky,Josephine Q.:419268:
4:27:Pape,Ted F.:241661:
5:25:Russell,Howard T.:873214:
6:30:Xenia,Ashley U.:420471:
7:24:Rotterman,Martin O.:578867:
8:18:Drabek,Hannah X.:662167:
9:19:Eno,Mark U.:913628:
11:18:Tsatsulin,Dan Z.:920134:

支持传统的Query并通过参数形式生成Query

  • 支持传统的Query形式。
  • 支持通过参数形式定义列,不需要指定ROWSPEC参数。
  • 优化将^CacheTemp^||CacheTemp。

定义M.CommonQuery

Class M.CommonQuery Extends %Query
{

ClassMethod Close(ByRef qHandle As %Binary) As %Status [ CodeMode = generator, PlaceAfter = Execute, ProcedureBlock = 1, ServerOnly = 1 ]
{
	s %code($i(%code))= (" s pid = $li(qHandle, 2)")
	s %code($i(%code))= (" k ^||GlobalTemp(pid)")
	s %code($i(%code))= (" q $$$OK")
	q $$$OK
}

ClassMethod Fetch(ByRef qHandle As %Binary, ByRef row As %List, ByRef end As %Integer = 0) As %Status [ CodeMode = generator, PlaceAfter = Execute, ProcedureBlock = 1, ServerOnly = 1 ]
{
	s %code($i(%code))= (" s end = $li(qHandle, 1)")
	s %code($i(%code))= (" s pid = $li(qHandle, 2)")
	s %code($i(%code))= (" s ind = $li(qHandle, 3)")
	s %code($i(%code))= (" s ind = $o(^||GlobalTemp(pid, ind))")
	s %code($i(%code))= (" if (ind = """") { ")
	s %code($i(%code))= (" 	s end = 1")
	s %code($i(%code))= (" 	s row = """"")
	s %code($i(%code))= (" } else { ")
	s %code($i(%code))= (" 	s row = ^||GlobalTemp(pid, ind)")
	s %code($i(%code))= (" }")
	s %code($i(%code))= (" s qHandle = $lb(end, pid, ind)")
	s %code($i(%code))= (" q $$$OK")
	q $$$OK
}

ClassMethod GetInfo(ByRef colinfo As %List, ByRef parminfo As %List, ByRef idinfo As %List, ByRef qHandle As %Binary, extoption As %Integer = 0, ByRef extinfo As %List) As %Status [ CodeMode = generator, ServerOnly = 1 ]
{
	s %code($i(%code))= (" s colinfo = $lb()")
	s %code($i(%code))= (" s column = $lg(qHandle, 4)")
	s %code($i(%code))= (" if ($lv(column)) {")
	s %code($i(%code))= (" 	for i = 1 : 1 : $ll(column) {")
	s %code($i(%code))= (" 		s $li(colinfo, i) = $lb(""Column"" _ i )")
	s %code($i(%code))= (" 	}	")
	s %code($i(%code))= (" } else {")
	s %code($i(%code))= (" 	s len = $l(column, "","")")
	s %code($i(%code))= (" 	for i = 1 : 1 : len {")
	s %code($i(%code))= (" 		s $li(colinfo, i) = $lb($p(column, "","", i))")
	s %code($i(%code))= (" 	}")
	s %code($i(%code))= (" }")
	s %code($i(%code))= (" s parminfo = """"")
	s %code($i(%code))= (" s idinfo = """"")
	s %code($i(%code))= (" q $$$OK")
	q $$$OK
}

}



定义QueryName

Query CustomColumnQuery(column As %String = "") As M.CommonQuery
{
}
  • column - 表示要自定义参数列的变量。
  • M.CommonQuery - 自定义Query类型,不需要写GetInfoFetchClose方法。

定义QueryNameExecute

QueryNameExecute 支持三种定义列头方式:

  1. 通过column参数传入列头,实现如下:
ClassMethod CustomColumnQueryExecute(ByRef qHandle As %Binary, column As %List) As %Status
{
	s pid = $i(^||GlobalTemp)
	s qHandle = $lb(0, pid, 0)
	s $li(qHandle, 4) = column // 方式1此位置必填
	s ind = 1
	
	s id = ""
	for {
		s id = $o(^M.T.PersonD(id))
		q:(id = "")
		s data = ^M.T.PersonD(id)
		s i = 1
		s name = $lg(data, $i(i))
		s age = $lg(data, $i(i))
		s no = $lg(data, $i(i))
		d output
	}	
	
    q $$$OK
    
output
	s data = $lb(id, name)
	s ^||GlobalTemp(pid, ind)=data	
	s ind = ind + 1
}
USER> d ##class(%ResultSet).RunQuery("M.Query","CustomColumnQuery","ID,Name")
 
ID:Name:
1:yaoxin:
2:yx:
3:Umansky,Josephine Q.:
4:Pape,Ted F.:
5:Russell,Howard T.:
  1. 不传入column参数,自动根据列表数据数量生成列头,实现如下:
ClassMethod CustomColumnQueryExecute(ByRef qHandle As %Binary, column As %String = "") As %Status
{
	s pid = $i(^||GlobalTemp)
	s qHandle = $lb(0, pid, 0)
	s ind = 1
	s id = ""
	for {
		s id = $o(^M.T.PersonD(id))
		q:(id = "")
		s data = ^M.T.PersonD(id)
		s i = 1
		s name = $lg(data, $i(i))
		s age = $lg(data, $i(i))
		s no = $lg(data, $i(i))
		s data = $lb(id, name, no)
		q:(id > 5)
		d output
	}	
	s $li(qHandle, 4) = data // 方式2此位置必填
    q $$$OK
    
output
	s ^||GlobalTemp(pid, ind)=data	
	s ind = ind + 1
}
USER>d ##class(%ResultSet).RunQuery("M.Query","CustomColumnQuery")
 
Column1:Column2:Column3:
1:yaoxin:314629:
2:yx:685381:
3:Umansky,Josephine Q.:419268:
4:Pape,Ted F.:241661:
5:Russell,Howard T.:873214:
  1. 不传入column参数,通过Execute方法自定义列头信息,实现如下:
ClassMethod CustomColumnQueryExecute0(ByRef qHandle As %Binary, column As %String = "") As %Status
{
	s pid = $i(^||GlobalTemp)
	s qHandle = $lb(0, pid, 0)
	s ind = 1
	
	s id = ""
	for {
		s id = $o(^M.T.PersonD(id))
		q:(id = "")
		s data = ^M.T.PersonD(id)
		s i = 1
		s name = $lg(data, $i(i))
		s age = $lg(data, $i(i))
		s no = $lg(data, $i(i))
		s data = $lb(id, name, no)
		q:(id > 5)
		d output
	}	 
	s $li(qHandle, 4) = "id,name,age" // 方式3此位置必填
    q $$$OK 
    
output
	s ^||GlobalTemp(pid, ind)=data	
	s ind = ind + 1
}
USER>d ##class(%ResultSet).RunQuery("M.Query","CustomColumnQuery")
 
id:name:age:
1:yaoxin:314629:
2:yx:685381:
3:Umansky,Josephine Q.:419268:
4:Pape,Ted F.:241661:
5:Russell,Howard T.:873214:

定义通用Query,只需要实现Exceute方法

实现通用Query,需要通过抽象方法,子类去重写的方式去实现。所以首先定义父类。

定义M.CommonQuery

Class M.BaseQuery Extends %RegisteredObject
{

/// d ##class(%ResultSet).RunQuery("M.BaseQuery","CustomQuery","id,name")
Query CustomQuery(column As %List, arg...) As %Query
{
}

ClassMethod CustomQueryExecute(ByRef qHandle As %Binary, column As %List, arg...) As %Status
{
	s qHandle = $lb(0, 0) // 注释1
	s $li(qHandle, 3) = column // 注释2
	d ..QueryLogic(arg...) // 注释3
    q $$$OK
}

ClassMethod CustomQueryGetInfo(ByRef colinfo As %List, ByRef parminfo As %List, ByRef idinfo As %List, ByRef qHandle As %Binary, extoption As %Integer = 0, ByRef extinfo As %List) As %Status
{
	s colinfo = $lb()
	s column = $lg(qHandle ,3)
	s len = $l(column, ",") 
	for i = 1 : 1 : len {
	 	s $li(colinfo, i) = $lb($p(column, ",", i)) // 注释5
	}
	s parminfo = ""
	s idinfo = ""
	q $$$OK
}

ClassMethod CustomQueryClose(ByRef qHandle As %Binary) As %Status [ PlaceAfter = CustomQueryExecute ]
{
	k %zQueryList // 注释7
	q $$$OK
}

ClassMethod CustomQueryFetch(ByRef qHandle As %Binary, ByRef row As %List, ByRef end As %Integer = 0) As %Status [ PlaceAfter = CustomQueryExecute ]
{
	s end = $li(qHandle,1)
	s index = $li(qHandle,2)
	s index = $o(%zQueryList(index))
	if index = "" {  // 注释6
		s end = 1
		s row = ""
	} else      { 
		s row = %zQueryList(index)
	}
	s qHandle = $lb(end, index)
	Quit $$$OK
}

ClassMethod QueryLogic(arg...) [ Abstract ]
{
	// 注释4
}

}

  • column - 表示要自定义参数列的变量。
  • arg... - 传入的参数。
  • 注释1代码,这里做了一些改变,qHandle只记录了endindex。因为这里如果是全局变量或者进程私有Global只针对当前进程有效,所以pid可省略。
  • 注释2代码,将qHandle第三个位置传入列头名称。
  • 注释3代码,调用待实现的业务逻辑方法,此方法为抽象方法,需要子类去实现。
  • 注释4代码,子类需要实现的具体业务逻辑,得到数据集。
  • 注释5代码,获取到column动态设置列头。
  • 注释6代码,遍历全局变量。
  • 注释7代码,遍历结束后,将全局变量清空。

定义子类M.PersonQuery继承M.BaseQuery实现QueryLogic方法

  • 这里只需要给%zQueryList($i(count))全局变量赋值即可。固定模版已经抽象到父类。
ClassMethod QueryLogic(arg...)
{
	s pName = arg(1)
	s id = ""
	for {
		s id = $o(^M.T.PersonD(id))
		q:(id = "")
		s data = ^M.T.PersonD(id)
		s i = 1
		s name = $lg(data, $i(i))
		continue:(pName '= "")&&(name '= pName)
		s age = $lg(data, $i(i))
		s no = $lg(data, $i(i))
		s %zQueryList($i(count)) = $lb(id, name, age)
	}
}

调用CustomQuery方法

USER>d ##class(%ResultSet).RunQuery("M.PersonQuery","CustomQuery","ID,Name,Age", "yaoxin")
 
ID:Name:Age:
1:yaoxin:21:

注:这里是用的是全局变量作为数据传递,如果数据过大,则可能会出现内存泄漏问题。改成进程私有Global即可。由读者基于此逻辑自行实现。

注:这种方式一个类只能声明一个Query,如果想一个类声明多个Query,则考虑换成支持传统的Query方式。


通过Query生成Json

ClassMethod Query2Json(className, queryName, arg...)
{
	s array = []
	s rs = ##class(%ResultSet).%New()
	s rs.ClassName = className
	s rs.QueryName = queryName
	d rs.Execute(arg...)

	s array = []
	#; 属性值
	while (rs.Next()) {
		s valStr = ""
		s obj = {}
		for i = 1 : 1 : rs.GetColumnCount(){
			s columnName = rs.GetColumnName(i)
			s val = rs.Data(columnName)
			d obj.%Set(columnName, val)
		}
		d array.%Push(obj)
	}
	
	q array.%ToJSON()
}

USER>w ##class(Util.JsonUtils).Query2Json("%SYSTEM.License","Summary")
[{"LicenseUnitUse":"当前使用的软件许可单元 ","Local":"1","Distributed":"1"},{"Li           censeUnitUse":"使用的最大软件许可单元数 ","Local":"15","Distributed":"15"},{"Lic            enseUnitUse":"授权的软件许可单元 ","Local":"300","Distributed":"300"},{"LicenseU         nitUse":"当前连接 ","Local":"3","Distributed":"3"},{"LicenseUnitUse":"最大连接数          ","Local":"17","Distributed":"17"}]

通过Query生成Csv

ClassMethod Query2Csv(className, queryName, filePath, arg...)
{
	s file = ##class(%FileCharacterStream).%New()
	s file.Filename = filePath
	
	s array = []
	s rs = ##class(%ResultSet).%New()
	s rs.ClassName = className
	s rs.QueryName = queryName
	d rs.Execute(arg...)

	#; 列名
	s colStr = ""
	for i = 1 : 1 : rs.GetColumnCount(){
		s columnName = rs.GetColumnName(i)
		s colStr = $s(colStr = "" : columnName, 1 : colStr _ "," _ columnName)
	}
	d file.Write(colStr)
	
	#; 属性值
	while (rs.Next()) {
		s valStr = ""
		for i = 1 : 1 : rs.GetColumnCount(){
			s columnName = rs.GetColumnName(i)
			s val = rs.Data(columnName)
			s valStr = $s(valStr = "" : val, 1 : valStr _ "," _ val)	
		}
		d file.Write($c(10) _ valStr)
	}
	
	d file.%Save()
	q $$$OK
}
USER>w ##class(Util.FileUtils).Query2Csv("%SYSTEM.License","Summary","E:\m\CsvFile2.csv")
1

请添加图片描述

请添加图片描述


总结

  • 理解qHandle参数与GetInfo方法是实现通用Query的关键。
  • 使用通用Query可以提升开发效率。
  • 使用通用Query可以解决数据适配问题。

以上是个人对基于Query的一些理解,由于个人能力有限,欢迎大家提出意见,共同交流。

如果一个好点子,只是因为某个人先到想到就禁止后人使用,这会让整个人类社会多走很多弯路,这也是自由软件精神一直以来所表达的内容。                                                                                                                                                                                                     - 理查德·马修·斯托曼

0
0 270
文章 Michael Lei · 三月 2, 2023 4m read

正式名称为Class Query 的意思可以追溯到 Caché 的早期。
使用CLASS是因为它嵌入在 COS 类中。
尽管官方文档中有精确的描述,但它仍然相当抽象。
我的教程应该通过 COS 中的一个简单示例逐步指导您,使您有直观的体会

0
0 127
文章 Michael Lei · 三月 2, 2023 4m read

在这里,您将找到一个在 IRIS 环境中使用 Python 的简单程序,以及另一个在 Python 环境中使用 ObjectScript 的简单程序。另外,我想分享一些我在学习实践时遇到的麻烦。

IRIS 环境中的 Python

比方说,您在 IRIS 环境中想要解决一个您认为使用 Python 更容易或更有效的问题。

您可以简单地更改环境:像创建任何其他方法一样创建您的方法,并在其名称和规范的末尾添加 [Language = python]:

您可以在该方法中使用任何类型的参数,并且要访问它们,您可以执行与在 COS 中完全相同的操作:

假设您有这个 %String 参数 Arg 和一个来自自定义类的参数 OtherArg。这个其他类可能具有标题和作者等属性。您希望像这样访问:

此方法提供如下输出:

而且,对于访问类方法,它几乎是一样的。假设我们在 Demo.Books.PD.Books 中有一个名为“CreateString”的方法,它将标题和作者连接成类似于“Title: <Title>; Author: <Author>”的内容。

将其添加到我们的 python 方法的末尾:

将提供以下输出:

(要访问该方法,您可以使用 OtherArg.CreateString(),但我选择将 OtherArg 中的相同值传递给 CreateString 方法,以便输出看起来相似并且代码看起来更简单)

0
0 250
文章 Michael Lei · 二月 19, 2023 1m read

各位开发者,大家好!

对我来说,关于 ObjectScript 的最痛苦的一件事是输入 ##class(Class).Method() 以在代码或终端中调用类方法。 为解决这个问题, 我甚至提出了一个在 ObjectScript 中简化它的想法

但是 VSCode ObjectScript中有一个刚刚引入到插件中的新功能 – 复制调用Copy Invocation!

只需将 **Copy Invocation**(复制调用)链接悬停在代码中的每个类方法上方,点击它,该调用就会被复制到缓冲区:

将其粘贴到想要执行的任何位置!!

USER>w ##class(dc.sample.ObjectScript).Test()

就到这儿!

这里是显示其运作方式的[视频]。

非常感谢 VSCode 贡献者

祝您编程愉快!

0
0 328
文章 Hao Ma · 二月 9, 2023 1m read

大家好。


Chat GPT 是最近的热门话题,但我尝试使用它(尽管可能为时已晚)。

您似乎了解 ObjectScript yes

这就是为什么,

当被问到时,

哇哦! ObjectScript 程序及其描述回来了! !

你可以编程这个cheeky

1
0 302