#教程

0 关注者 · 25 帖子

逐步解释技术特性或解决方案的帖子。

文章 Lilian Huang · 十一月 6, 2025 7m read

Interoperability on Python (IoP) 是一个概念验证项目,旨在展示与 Python 优先方式相结合时 InterSystems IRIS Interoperability Framework 的强大功能。IoP 利用Embedded Python(嵌入式 Python,InterSystems IRIS 的一个功能)使开发者能够用 Python 编写互操作性组件,从而可以与强大的 IRIS 平台无缝集成。本指南专为初学者编写,全面介绍了 IoP、其设置以及创建第一个互操作性组件的操作步骤。 阅读完本文,您将能够清楚地了解如何使用 IoP 构建可扩缩、基于 Python 的互操作性解决方案。

0
0 13
文章 Claire Zheng · 九月 23, 2025 2m read

大家好! 我最近才加入 InterSystems,但发现尽管我们推出了完全免费且出色的社区版,但大家并不是十分清楚如何获取。 因此我决定编写一份指南,详细介绍获取 InterSystems IRIS 社区版的所有不同方式:

以容器形式获取 InterSystems IRIS 社区版

对于刚刚接触 InterSystems IRIS 开发的伙伴,推荐使用社区版的容器化实例,在我看来,这是最简单直接的方式。 InterSystems IRIS 社区版可以在 DockerHub 上获取;如果您有 InterSystems SSO 帐户,还可以在 InterSystems 容器注册表中获取。

在这两种情况下,您都需要使用 docker CLI 拉取所需镜像:

docker pull intersystems/iris-community:latest-em
// or
docker pull containers.intersystems.com/intersystems/iris-community:latest-em

接下来,您需要启动容器:要从容器外部与 IRIS 进行交互(例如使用管理门户),您需要发布一些端口。 以下命令将运行 IRIS 社区版容器,并发布超级服务器和 Web 服务器端口;请注意,此时不能运行其他依赖 1972 或 52773 端口的程序!

docker run --name iris -d --publish 1972:1972 --publish 52773:52773 intersystems/iris-community:latest-em

在云端获取 InterSystems IRIS 社区版

您可能想要完全避免本地安装的一系列操作,如果是这种情况,您可以借助 InterSystems IRIS 社区版的云部署实现环境搭建和运行。 所有主流云提供商均受支持;有关详细信息,请参阅我们的云合作伙伴页面。 本例将重点介绍如何在 AWS 上部署。

先在 AWS Marketplace 中找到 InterSystems IRIS 社区版:

您可以点击“查看购买选项”,并登录您的帐户查看订阅页面:

 

向下滚动,点击“订阅”,填写必填信息,然后点击“部署”,将 IRIS 社区版部署为云节点! 有关详细信息,请参阅我们关于将 InterSystems IRIS 部署到云端的文档。

以安装套件形式获取 InterSystems IRIS 社区版

如果您倾向于将 InterSystems IRIS 直接安装到自己的机器上使用,且不想处理容器,可从 InterSystems 评估服务下载适合您系统的安装套件。要下载安装套件,您需要有 InterSystems SSO 登录帐户;但好消息是,如果您有开发者社区帐户,就说明您已有 InterSystems SSO 帐户! 如果您没有此帐户,可以点击“注册新帐户”并完成后续步骤:

登录后,您应会看到以下页面;点击“下载社区版”开始下载安装套件:

 

系统将提示您填写关于所需 InterSystems IRIS 的具体版本和安装平台的信息:

 

对于大部分用例,您需要选择“InterSystems IRIS 社区版”以及您的平台可用的最新版本。 同意《服务条款》后,您便可下载安装套件! 按照平台特定文档中的安装说明进行操作,一切就搞定啦!

0
0 29
文章 Michael Lei · 九月 27, 2024 9m read

在这一系列文章中,我想向大家介绍并探讨使用 InterSystems 技术和 GitLab 进行软件开发可以采用的几种方式。 我将介绍以下主题:

  • Git 101
  • Git 流程(开发流程)
  • GitLab 安装
  • GitLab 工作流
  • 持续交付
  • GitLab 安装和配置
  • GitLab CI/CD
  • 为何使用容器?
  • 容器基础架构
  • 使用容器的 CD
  • 使用 ICM 的 CD

在本文中,我们将使用 InterSystems Cloud Manager 构建持续交付。 ICM 是一个面向基于 InterSystems IRIS 的应用程序的云配置和部署解决方案。 它允许您定义所需部署配置,ICM 会自动提供这些配置。 有关详情,请参阅 ICM 概述

工作流

在我们的持续交付配置中,我们将完成以下任务:

  • 将代码推送到 GitLab 仓库
  • 构建 docker 镜像
  • 将镜像发布到 docker 注册表
  • 在测试服务器上测试镜像
  • 如果测试通过,则在生产服务器上部署

也可以用示意图形式表示此流程:

如您所见,基本操作是相同的,不过我们将使用 ICM 而不是手动方式来管理 Docker 容器。

ICM 配置

在我们开始升级容器之前,需要对它们进行配置。 因此,我们需要定义 defaults.jsondefinitions.json 来描述我们的架构。 我将为 LIVE 服务器提供这 2 个文件,TEST 服务器的 definitions 是相同的,defaults 只在 TagSystemMode 值上有所不同。

defaults.json:

{
    "Provider": "GCP",
    "Label": "gsdemo2",
    "Tag": "LIVE","SystemMode": "LIVE",
    "DataVolumeSize": "10",
    "SSHUser": "sample",
    "SSHPublicKey": "/icmdata/ssh/insecure.pub",
    "SSHPrivateKey": "/icmdata/ssh/insecure",
    "DockerImage": "eduard93/icmdemo:master",
    "DockerUsername": "eduard93",
    "DockerPassword": "...",
    "TLSKeyDir": "/icmdata/tls",
    "Credentials": "/icmdata/gcp.json",
    "Project": "elebedyu-test",
    "MachineType": "n1-standard-1",
    "Region": "us-east1",
    "Zone": "us-east1-b",
    "Image": "rhel-cloud/rhel-7-v20170719",
    "ISCPassword": "SYS",
    "Mirror": "false"
}

definitions.json

[
    {
    "Role": "DM",
    "Count": "1",
    "ISCLicense": "/icmdata/iris.key"
    }
]

在 ICM 容器内部,/icmdata 文件夹从主机挂载,并且:

  • TEST 服务器的定义放置在 /icmdata/test 文件夹中
  • LIVE 服务器的定义放置在 /icmdata/live 文件夹中

获取所有必需的密钥后:

keygenSSH.sh /icmdata/ssh
keygenTLS.sh /icmdata/tls

并将必需的文件放置在 /icmdata 中:

  • iris.key
  • gcp.json(用于部署到 Google Cloud Platform)

调用 ICM 来配置您的实例:

cd /icmdata/test
icm provision
icm run
cd /icmdata/live
icm provision
icm run

这将为每个 TEST 和 LIVE 服务器配置一个独立的 InterSystems IRIS 实例。

有关更详细的指南,请参阅 ICM 概述

构建

首先,我们需要构建镜像。

我们的代码通常存储在仓库中,CD 配置位于 gitlab-ci.yml 中,但为了提高安全性,我们会在构建服务器上存储几个服务器特定的文件。

iris.key

许可证密钥。 或者,它可以在容器构建过程中下载,而不是存储在服务器上。 将密钥存储在仓库中非常不安全。

pwd.txt

包含默认密码的文件。 将这类文件存储在仓库中也非常不安全。 此外,如果您在单独的服务器上托管生产环境,它可能有不同的默认密码。

load_ci_icm.script

初始脚本,它执行以下任务:

  • 加载安装程序
  • 安装程序执行应用程序初始化
  • 加载代码
set dir = ##class(%File).NormalizeDirectory($system.Util.GetEnviron("CI_PROJECT_DIR"))
do ##class(%SYSTEM.OBJ).Load(dir _ "Installer/Global.cls","cdk")
do ##class(Installer.Global).init()
halt

请注意,第一行有意留空。

与之前的示例相比,有几个不同之处。 首先,我们没有启用操作系统身份验证,因为 ICM 会与容器交互而不是直接与 GitLab 交互。 其次,我使用安装程序清单来初始化我们的应用程序,以展示不同的初始化方式。 有关安装程序的更多信息,请参阅这篇文章。 最后,我们将在 Docker Hub 中以私有仓库的形式发布我们的镜像。

 

Installer/Global.cls

我们的安装程序清单如下所示:

<Manifest>
    <Log Text="Creating namespace ${Namespace}" Level="0"/>
    <Namespace Name="${Namespace}" Create="yes" Code="${Namespace}" Ensemble="" Data="IRISTEMP">
        <Configuration>
            <Database Name="${Namespace}" Dir="${MGRDIR}/${Namespace}" Create="yes" MountRequired="true" Resource="%DB_${Namespace}" PublicPermissions="RW" MountAtStartup="true"/>
        </Configuration>

        <Import File="${Dir}MyApp" Recurse="1" Flags="cdk" IgnoreErrors="1" />
    </Namespace>

    <Log Text="Mapping to USER" Level="0"/>
    <Namespace Name="USER" Create="no" Code="USER" Data="USER" Ensemble="0">
        <Configuration>
            <Log Text="Mapping MyApp package to USER namespace" Level="0"/>
            <ClassMapping From="${Namespace}" Package="MyApp"/>
        </Configuration>

        <CSPApplication  Url="/"      Directory="${Dir}client" AuthenticationMethods="64" IsNamespaceDefault="false" Grant="%ALL"  />
        <CSPApplication  Url="/myApp" Directory="${Dir}"       AuthenticationMethods="64" IsNamespaceDefault="false" Grant="%ALL"  />
    </Namespace>
</Manifest>

它实现了以下更改:

  1. 创建应用程序命名空间。
  2. 创建应用程序代码数据库(数据将存储在 USER 数据库中)。
  3. 将代码加载到应用程序代码数据库中。
  4. 将 MyApp 软件包映射到 USER 命名空间。
  5. 创建 2 个 Web 应用程序:分别用于 HTML 和 REST。

gitlab-ci.yml

现在,继续持续交付配置:

build image:
  stage: build
  tags:
    - master
  script:
    - cp -r /InterSystems/mount ci
    - cd ci
    - echo 'SuperUser' | cat - pwd.txt load_ci_icm.script > temp.txt
    - mv temp.txt load_ci.script
    - cd ..
    - docker build --build-arg CI_PROJECT_DIR=$CI_PROJECT_DIR -t eduard93/icmdemo:$CI_COMMIT_REF_NAME .

这里会执行哪些操作?

首先,由于 docker build 只能访问基础构建目录(在我们的示例中为仓库根目录)的子目录,我们需要将我们的“秘密”目录(其中包含 iris.keypwd.txtload_ci_icm.script)复制到克隆的仓库中。

接下来,首次终端访问需要用户名/密码,因此我们会将这些信息添加到 load_ci.script 中(这也是 load_ci.script 开头一行留空的原因)。

最后,我们会构建 docker 镜像并适当地为其添加标签:eduard93/icmdemo:$CI_COMMIT_REF_NAME

其中,$CI_COMMIT_REF_NAME 是当前分支的名称。 请注意,镜像标签的第一部分应与 GitLab 中的项目名称相同,这样才能在 GitLab 的“注册表”标签页中看到它(“注册表”标签页中提供了关于添加标签的说明)。

Dockerfile

构建 docker 镜像是通过 Dockerfile 完成的,具体如下:

FROM intersystems/iris:2018.1.1-released

ENV SRC_DIR=/tmp/src
ENV CI_DIR=$SRC_DIR/ci
ENV CI_PROJECT_DIR=$SRC_DIR

COPY ./ $SRC_DIR

RUN cp $CI_DIR/iris.key $ISC_PACKAGE_INSTALLDIR/mgr/ \
 && cp $CI_DIR/GitLab.xml $ISC_PACKAGE_INSTALLDIR/mgr/ \
 && $ISC_PACKAGE_INSTALLDIR/dev/Cloud/ICM/changePassword.sh $CI_DIR/pwd.txt \
 && iris start $ISC_PACKAGE_INSTANCENAME \
 && irissession $ISC_PACKAGE_INSTANCENAME -U%SYS < $CI_DIR/load_ci.script \
 && iris stop $ISC_PACKAGE_INSTANCENAME quietly

我们从基本的 iris 容器开始。

首先,我们将仓库(和“秘密”目录)复制到容器中。

接下来,我们将许可证密钥复制到 mgr 目录中。

然后,我们将密码更改为 pwd.txt 中的值。 请注意,此操作会删除 pwd.txt。

之后,实例启动并执行 load_ci.script。

最后,iris 实例停止。

请注意,我使用的是 GitLab Shell 执行器,而不是 Docker 执行器。 当您需要从镜像内部提取某些内容时,将使用 Docker 执行器,例如在 Java 容器中构建 Android 应用程序并且只需要一个 apk 时。 在我们的示例中,我们需要整个容器,因此需要使用 Shell 执行器。 因此,我们通过 GitLab Shell 执行器运行 Docker 命令。

发布

现在,我们将镜像发布到 Docker Hub

publish image:
  stage: publish
  tags:
    - master
  script:
    - docker login -u eduard93 -p ${DOCKERPASSWORD}
    - docker push eduard93/icmdemo:$CI_COMMIT_REF_NAME

注意 ${DOCKERPASSWORD} 变量,它是 GitLab 的秘密变量。 我们可以在“GitLab > 项目 > 设置 > CI/CD > 变量”中添加它们:

作业日志中也不包含密码值:

Running with gitlab-runner 10.6.0 (a3543a27)
  on icm 82634fd1
Using Shell executor...
Running on docker...
Fetching changes...
Removing ci/
HEAD is now at 8e24591 Add deploy to LIVE
Checking out 8e245910 as master...Skipping Git submodules setup$ docker login -u eduard93 -p ${DOCKERPASSWORD}
WARNING! Using --password via the CLI is insecure. Use --password-stdin.
Login Succeeded
$ docker push eduard93/icmdemo:$CI_COMMIT_REF_NAME
The push refers to repository [docker.io/eduard93/icmdemo]
master: digest: sha256:d1612811c11154e77c84f0c08a564a3edeb7ddbbd9b7acb80754fda97f95d101 size: 2620
Job succeeded

在 Docker Hub 中,我们可以看到新镜像:

 

运行

我们有了镜像,接下来在我们的测试服务器上运行它。 脚本如下。

run image:
  stage: run
  environment:
    name: $CI_COMMIT_REF_NAME
  tags:
    - master
  script:
    - docker exec icm sh -c "cd /icmdata/test && icm upgrade -image eduard93/icmdemo:$CI_COMMIT_REF_NAME"

使用 ICM,我们只需要运行一个命令 (icm upgrade) 即可升级现有部署。 我们通过运行“docker exec icm sh -c”来调用它,这会在 icm 容器内执行指定的命令。首先,我们进入 /icmdata/test,这里定义了我们为 TEST 服务器准备的 ICM 部署定义。 之后,我们调用 icm upgrade 将当前存在的容器替换为新容器。

测试

我们来运行一些测试。

test image:
  stage: test
  tags:
    - master
  script:
    - docker exec icm sh -c "cd /icmdata/test && icm session -namespace USER -command 'do \$classmethod(\"%UnitTest.Manager\",\"RunTest\",\"MyApp/Tests\",\"/nodelete\")' | tee /dev/stderr | grep 'All PASSED' && exit 0 || exit 1"

同样,我们在 icm 容器内执行一条命令。 icm 会话在一个已部署的节点上执行命令。 该命令运行单元测试。 之后,它将所有输出传输到屏幕,同时也传输给 grep 以查找单元测试结果,并成功退出进程或以出错方式退出。

部署

生产服务器上的部署与测试服务器上的部署完全相同,只是为 LIVE 部署定义使用了另一个目录。 如果测试失败,此阶段将不会被执行。

deploy image:
  stage: deploy
  environment:
    name: $CI_COMMIT_REF_NAME
  tags:
    - master
  script:
    - docker exec icm sh -c "cd /icmdata/live && icm upgrade -image eduard93/icmdemo:$CI_COMMIT_REF_NAME"

结论

ICM 为您提供了一种简单直观的方式来配置云基础架构并在它上面部署服务,帮助您立即进入云,无需大规模开发或重新工具化。 基础架构即代码 (IaC) 和容器化部署的优势使您可以轻松地在公共云平台(如 Google、Amazon 和 Azure)或者在您的私有 VMware vSphere 云上部署基于 InterSystems IRIS 的应用程序。 定义您的需求,发出几条命令,ICM 会完成其余工作。
即使您已经在使用云基础架构、容器或两者,ICM 也能通过自动执行大量原本需要手动完成的步骤,极大地减少配置和部署应用程序所需的时间和精力。
0
0 87
文章 Michael Lei · 八月 17, 2024 3m read

各位社区成员,大家好,

利用面向您的组织中的各个角色提供的全套 InterSystems 学习资源(在线或面授形式),您可以全面挖掘 InterSystems IRIS 的潜力,并帮助您的团队完成入门流程。 开发者、系统管理员、数据分析师和集成商可以快速上手。

学习服务提供的资源

在深入学习角色特定的资源之前,我们先来总体了解一下现有的学习资源:

  • 💻 在线学习:在 learning.intersystems.com 上免费注册,以访问自定进度的课程、视频和练习。 您还可以完成基于任务的学习路径或基于角色的计划,以促进您的职业发展。
  • 👩‍🏫 课堂培训:查看直播、面授或虚拟课堂培训的时间安排,或为您的团队申请不公开课程。 访问 classroom.intersystems.com 了解详细信息。
  • 📘 InterSystems IRIS 文档:全面的参考资料、指南和操作说明文章。 探索文档
  • 💬 开发者社区:与其他开发者互动、发布问题、阅读文章并及时了解最新公告。 请先阅读本帖,其中介绍了在开发者社区中学习的技巧
  • 📧 支持:如需获取技术支持,请发送电子邮件至 support@intersystems.com

针对各个角色提供的入门资源

开发者

系统管理员

数据分析师

集成商

项目经理

通过这些学习资源,您的团队将掌握相应知识,从而最大限度地利用 InterSystems IRIS 的功能,推动组织的发展并取得成功。 如需获取其他帮助,请随时在此发布问题或咨询您的专属销售工程师。

认证机会

certification badge 在您和您的团队成员接受足够时长的培训并获得相应经验后,你们可以申请认证来证明在 InterSystems 技术方面具备相应的技能和专业知识:

0
0 472
文章 Lilian Huang · 三月 14, 2024 6m read

人工智能(AI)最近受到广泛关注,因为它可以改变我们生活的许多领域。更好的计算机能力和更多数据帮助人工智能完成了许多惊人的事情,例如改进医学测试和制造自动驾驶汽车。人工智能还可以帮助企业做出更好的决策,提高工作效率,这也是人工智能越来越流行和广泛应用的原因。如何将 OpenAI API 调用集成到现有的 IRIS 互操作性应用程序中?

 

0
0 208
文章 Veerarajan Karunanithi · 二月 28, 2024 4m read

什么是非结构化数据?
非结构化数据是指缺乏预定义数据模型或组织的信息。与数据库中具有清晰结构(例如表和字段)的结构化数据相比,非结构化数据缺乏固定的模式。此类数据包括文本、图像、视频、音频文件、社交媒体帖子、电子邮件等。

为什么来自非结构化数据的见解很重要?
根据 IDC(国际数据公司)的报告,预计到 2025 年,全球 80% 的数据将是非结构化的,这将成为 95% 企业的重大担忧。 福布斯文章

人工智能世界如何解决这个问题?
在人工智能领域,生成式人工智能在为非结构化数据提供解决方案方面发挥着至关重要的作用。它擅长从文本/图像/视频中提取有价值的信息、文本摘要和处理文档等任务。

Intersystems 非结构化数据解决方案
Intersystems IRIS 提供了一种称为“SQL 文本搜索”的特殊解决方案,用于搜索非结构化数据。此功能有助于对多种语言的非结构化文本数据进行语义上下文搜索。

使用 SQL 文本搜索有什么优点?

快速搜索: InterSystems IRIS SQL 搜索利用优化的索引生成快速导航大量数据,避免对数据本身进行顺序搜索。

单词感知搜索:与基本字符串搜索不同,SQL 搜索依赖于文本中的语义结构,以单词为基本单位。这种方法最大限度地减少了嵌入字符串或跨越两个单词的字符串引起的误报。

1
0 187
文章 Jingwei Wang · 五月 5, 2023 3m read

WIN SQL是大多数用户使用的普通编辑器。但是我们不能使用winsql下载大量数据。所以我写了一个教程如何连接一个新的基于 Java 的编辑器,叫做 Squirrel SQL,它可以很容易地下载或导出 excel 或任何其他格式的数据。我还包括一个 Java JCBC 连接程序来连接 IRIS 数据库,尤其是镜像/故障转移服务器。

基于 SQL Java 的编辑器导出大量数据和用于 IRIS 连接的 Java JDBC 程序

基于 SQL Java 的编辑器导出大量数据

WinSql 是通常用于从 Iris 数据库中提取数据的编辑器,但是,如果没有许可的 winsql,则无法导出大量数据。

解决方案是使用基于 java 的编辑器,称为 Squirrel SQL。这是一个基于 java 的编辑器,您可以在从 IRIS 数据库执行 fetch 从编辑器中导出大量数据。这是用 Java 构建的开源 SQL 客户端,它使用 JDBC 连接到 IRIS 数据库。

Squirrel SQL 的特点

  • Java 19 兼容性
  • 多个插入符/光标编辑
  • 全局首选项和新会话属性搜索
  • Saved Sessions 的多项改进(用于保存和恢复 Session 的所有 SQL 编辑器的特性)
  • 可配置的鼠标右键菜单
  • 重新设计的添加/编辑 JDBC 驱动程序对话框

安装 Squirrel SQL 的步骤

0
0 651
文章 Lilian Huang · 四月 28, 2023 8m read

     

嗨社区,
在本文中,我将演示 InterSystems Embedded Python 的用法,我们将涵盖以下主题:

  • 1-嵌入式Python概述
  • 2-嵌入式Python的使用
    • 2.1- 从 ObjectScript 使用 Python 库
    • 2.2- 从 Python 调用 InterSystems API
    • 2.3- 一起使用 ObjectScript 和 Python
  • 3-使用python内置函数
  • 4-Python 模块/库
  • 5 个嵌入式 Python 用例
  • 6-总结

我们从概述开始
 

1-嵌入式Python概述

嵌入式 Python 是 InterSystems IRIS 数据平台的一项功能,它允许 Python 开发人员完全直接地访问 InterSystems IRIS 中的数据和功能。

InterSystems IRIS 带有一种名为 ObjectScript 的强大内置编程语言,可在数据平台内部进行解释、编译和运行。

0
0 275
文章 water huang · 三月 27, 2023 9m read

一、背景

1.1 我遇到了几个项目,他们的接口服务器崩溃了。 项目上希望尽快恢复服务器。他们的服务器在局域网上运行,他们不能使用git,服务器中有多个命名空间运行不同的服务,而且通常只有一台平台服务器。

1.2 如果消息中有字符流类型的属性,消息搜索页面不支持使用字符流属性进行过滤,因此很难找到想要的消息。

1.3 其他同事可能会更新服务器上的代码,代码中可能有些错误。

2.挑战

2.1 如何快速恢复?

2.2 如何支持字符流属性过滤消息?

2.3 如何在编译类时自动备份?

3.解决方案

1.编译时自动导出为备份文件

首先,我们定义一个名为“SYS.base”的类,它只有一个名为“ CLSBAKPATH”的参数,并设置它的值

Class SYS.Base Extends%RegisteredObject 
{
   Parameter CLSBAKPATH = "D:\IRIS\CLSBAK";
}

然后,定义一个名为“SYS.Projection”的类,它继承了 base和%Projection.AbstractProjection,添加“Projection Reference As SYS.Projection”,重写类方法“CreateProjection”;

代码如下:

1
0 209
文章 姚 鑫 · 三月 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 · 三月 21, 2023 3m read

InterSystems IRIS 是一个高性能、可靠且可扩展的数据平台,用于为医疗保健、金融服务和其他行业构建和部署关键任务应用程序。它提供了广泛的功能,包括数据管理、集成、分析等。

IRIS 提供的功能之一是能够将 Python 代码嵌入到 ObjectScript 代码中。这意味着您可以在 IRIS 应用程序中使用 Python 库和函数,让您可以访问大量的工具和资源。在本文中,我们将了解如何在 InterSystems IRIS 中使用嵌入式 Python。

设置嵌入式 Python

在 IRIS 中开始使用嵌入式 Python 之前,您需要设置环境。这涉及安装 Python 解释器和配置 IRIS 以识别它。

第一步是安装 Python。您可以从官方网站 ( https://www.python.org/downloads/ ) 下载最新版本的 Python。安装 Python 后,需要将其添加到系统的 PATH 环境变量中。这允许 IRIS 找到 Python 解释器。

接下来,您需要配置 IRIS 以识别 Python。为此,您需要创建一个 Python 网关。网关是一个在 IRIS 之外运行的进程,充当 IRIS 和 Python 之间的桥梁。

要创建网关,请打开一个终端窗口并导航到 Python 安装目录。然后运行以下命令:

python -m irisnative

0
1 222
文章 Hao Ma · 三月 14, 2023 2m read

介绍

在最近几篇文章中的一些文章中,我谈到了 IRIS 和 Python 之间的类型,很明显,从一侧到另一侧访问对象并不是那么容易。

幸运的是,已经完成了创建SQLAlchemy-iris 的工作(点击链接在 Open Exchange 上查看它),这使得 Python 访问 IRIS 对象的一切变得更加容易,我将展示它的启动器。

谢谢@Dmitry Maslennikov

安装中

要安装,只需打开具有管理员权限的终端并输入

pip install sqlalchemy-iris

如果需要,这还将为您安装先决条件。

用法

现在,在 python 文件上,您可以导入模块、连接到数据库并以任何您想要的方式使用 sqlalchemy。如果你觉得舒服,你可以按照以下步骤操作:

  • 从 sqlalchemy 导入“create_engine”并使用字符串“iris://username:password@IP:port/namespace”创建引擎。当然,您可以导入整个模块,但“create_engine”会创建一个 Engine 实例(sqlalchemy.engine,有关更多信息,请单击此处)具有我在这里展示的所有必要子类。
0
0 329
文章 王喆 👀 · 三月 12, 2023 8m read

SSH框架是Java一个的比较有代表性的框架,是多年前比较流行的一个。有struts+spring+hibernatespringMVC+spring+hibernate两种,当然我大学时候使用的是第二种。倘使我能把IRIS作为一个库,联结到Hibernate那么理论上是不是也代表IRIS也可以使用SSH框架开发呢?

工具及环境

JDK 1.8

       Maven

       Hibernate 5.X.X

       IRISHealth-2022.1.3

       intellij idea

       Windows 10 64

1、创建数据库

       用代码的方式创建几个表

Class BKIP.SOA.MonitorScreen.CacheTable.logError Extends %Persistent

{

/// 服务概况缓存表

Property SucNum As %String(MAXLEN = "");

Property failNum As %String(MAXLEN = "");

Property fdateTime As %String(MAXLEN = "");

}

如图所示:

2、创建一个Spring的项目

       File—New—Project....

next

给项目起名,选择jdk版本

Next

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

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

0
0 127
文章 Hao Ma · 十一月 20, 2022 7m read

发布您自己的软件

首先:要发布您的软件,您要支持这个”命名规范。其中和zmp最相关的是包名和l类名的设计,你要定义成这样:

company.project.subpackage.TheClass.cls

如果您的Package Name定义是: Company.Project, 有大写字母,对不起,是无法用zpm打包的。

这个链接给了最简单的例子,但还不详细,我来总结一下:

发布您的软件前,有几件事情要了解:

  1. zpm的注册中心并不存代码,存的只是一个到您代码的链接。因此,您得找地方放您的代码。当前最常用的是github。

  2. 文件目录的结构

    举例:有一个class定义是 com.tony.Test1.cls, 你的目录应该这么组织,假设您要放在 /myDemo, 那么class应该在/myDemo/src/com/tony/Test1.cls。这是使用VSCode组织代码的默认方式,只有保证这样的目录结构,您才可能用zpm加载代码到iris.

让我来做个简单的例子。

首先,有这样的class:

Class com.tony.Test1
{	Property p1;
}

我的文件目录设置

$ ls -l /external/myDemo/src
total 4
-rw-r--r-- 1 irisowner irisowner 40 Nov 12 10:00 Test1.cls
$

这时候我来使用zpm打包测试

module.xml的生成和加载

第一步,生成module.xml

我们看看最简单的用zpm generate命令生成module的例子:

zpm:USER>generate /external/myDemo/project1

Enter module name: project1
Enter module version: 1.0.0 =>
Enter module description:
Enter module keywords:
Enter module source folder: src =>

Existing Web Applications:
    /csp/user
    /terminal
    /terminalsocket
    Enter a comma separated list of web applications or * for all:
Dependencies:
    Enter module:version or empty string to continue:
zpm:USER>

zpm generate会把/external/myDemo/project1目录下的文件打包,在这个目录下创建一个module.xml文件, 是这样的:

<?xml version="1.0" encoding="UTF-8"?>
<Export generator="Cache" version="25">
  <Document name="project1.ZPM">
    <Module>
      <Name>project1</Name>
      <Version>1.0.0</Version>
      <Packaging>module</Packaging>
      <Resource Name="com.PKG" />
      <SourcesRoot>src</SourcesRoot>
    </Module>
  </Document>
</Export>

先等等解释这个xml, 让我们先执行第2步。

第2步: 把文件load到iris

zpm:USER>load /external/myDemo/
[USER|firstdemo]	Reload START (/external/myDemo/)
[USER|firstdemo]	Reload SUCCESS
[firstdemo]	Module object refreshed.
[USER|firstdemo]	Validate START
[USER|firstdemo]	Validate SUCCESS
[USER|firstdemo]	Compile START
[USER|firstdemo]	Compile SUCCESS
[USER|firstdemo]	Activate START
[USER|firstdemo]	Configure START
[USER|firstdemo]	Configure SUCCESS
[USER|firstdemo]	Activate SUCCESS
zpm:USER>

您去iris里看看, 确认class已经被loaded

第3步: 删除(optional, 但你可能会用到这个命令)

zpm:USER>uninstall firstdemo

[USER|firstdemo]	Clean START
[USER|firstdemo]	Unconfigure START
[USER|firstdemo]	Unconfigure SUCCESS
Deleting class com.tony.Test1
[USER|firstdemo]	Clean SUCCESS
zpm:USER>

这时候您应该可以发现com.tong.Test1类已经从Iris删除了。

对上面的例子总结一下:

  1. 打包是对一个文件夹打包

  2. 使用zpm把软件包加载进iris是先找包里面的module.xml文件。通过module.xml里定义的信息来知道包的名字,版本,打包的内容等等。 这个module.xml是在打包的时候用zpm generate创建的, 但您也可以自己手工创建,比如copy其他包的module.xml改改, 有时候会更快捷,尤其是您对zpm命令不是很熟悉的时候, 对很多打包的需求,比如后面会提到的定义依赖等等,直接改module.xml比zpm generate容易多了。

  3. 让我来说说可能的问题:

    module.xml中<Resource Name="com.PKG"/>定义了包名com.PKG, 加载数据包到iris很成功。但是,如果您还记得,刚刚例子里打包的是class是com.tony.Test1,那么如果你再定义一个新的类,叫com.tiedan.Test1, 用zpm generate一个新module, 名字叫project2(注意第一个打包的module名字是project1), project2的"Resource Name"也还是"com.PKG, 它能正确加载吗?

    不会, 你会被告知:"ERROR! Resource 'com.PKG' is already defined as part of module 'project1'; cannot also be listed in module 'project2'"

​ 解决方法:手工将module.xml里面的Resource Name改成“com.tony.PKG"和"com.tiedan.PKG", 这样两个包都能成功加载了。

还有个小问题,Mac用户可能会看到这样的提示:

zpm:USER>load /external/myDemo/project1

[USER|project1] Reload START (/external/myDemo/project1/) [project1] Reload FAILURE ERROR! Unable to import file '/external/myDemo/project1/src/com/.DS_Store' as this is not a supported type. zpm:USER>

这是说打包的文件夹下面有.DS_Store文件,而zpm不认识。zpm会把里面认识的文件, 比如.cls文件成功加载, 然后告诉你"Roload FAILURE"

好吧, 到这里我们知道怎么打包和把包加载到iris里, 接着看看什么文件可以被打包。

Package可以包含的文件类型

这时候要好好了解module.xml的内容的细节了, 请阅读技术文档的module.xml部分

其中的resource部分,阐明了您可以打包的内容:

第一部分:可以被加载到iris的文件类型。

Use the following suffixes for different types of resources:

.PKG - Package .CLS - Class .INC - Include .MAC - Routine .LOC - LocalizedErrorMessages .GBL - Global .DFI - DeepSee Item

第二部分:jar包

<FileCopy Name="lib/" Target="${libdir}my-lib/"/>               Copies content of lib folder to Target
<FileCopy Name="somefile.jar" Target="${libdir}my-lib/"/>       Copies just desired file to Target

第三部分:UnitTest

module.xml的例子里给出的UnitTest部分是这个样子

<UnitTest Name="/tests/unit_tests/" Package="Test.Unit" Phase="test"/>

说实话,我还没研究怎么使用zpm generate可以做到这一点,能想到的就是手工去修改module.xml文件。

第四部分:Web Application

执行zpm generate的时候, 会列出当前命名空间可以使用的Web Application列表。让我重新执行一下打包的第一步,看看结果是什么样子

zpm:USER>generate /external/myDemo/project1

Enter module name: project11
Enter module version: 1.0.0 =>
Enter module description:
Enter module keywords:
Enter module source folder: src =>

Existing Web Applications:
    /csp/user
    /terminal
    /terminalsocket
    Enter a comma separated list of web applications or * for all: /csp/user
    Enter path to csp files for /csp/user: 
Dependencies:
    Enter module:version or empty string to continue:
zpm:USER>

得出的module.xml里多了如下内容:

<CSPApplication CookiePath="/csp/user/" DefaultTimeout="900" DeployPath="${cspdir}user/" Description="User Namespace applications" GroupById="%ISCMgtPortal" MatchRoles=":{$dbrole}" PasswordAuthEnabled="1" Recurse="1" ServeFiles="2" ServeFilesTimeout="3600" UnauthenticatedEnabled="1" Url="/csp/user" UseSessionCookie="2"/>
      <SourcesRoot>src</SourcesRoot>

其中,打包时提问`Enter path to csp files for /csp/user:。 这里,您需要填入的是当前要load的csp文件。比如:您有一个tony.csp要加载,那么您可以在要打包的目录下创建一个子目录“cspfiles", 把tony.csp放在cspfiles目录里, 回答提问的使用这样

Enter path to csp files for /csp/user:/cspfiles

这里用的是相对路径,但格式是绝对路径的格式,我把它看成一个bug。

module.xml中还有其他很多配置的内容,我在后面会介绍包的依赖的部分。

软件包的Package

用package命令,在iris里将软件打包。打包的结果是得倒一个project1-1.0.0.tgz的文件。 package -v显示verbose信息,您可以清楚的看到project1-1.0.0.tgz和module.xml的存放位置。

zpm:USER>project1 package -v

[USER|project1]	Reload START (/external/myDemo/project1/)
Skipping preload - directory does not exist.
Load of directory started on 11/20/2022 13:15:17 '*'

Loading file /external/myDemo/project1/src/com/tony/Test1.cls as udl
Load finished successfully.

[USER|project1]	Reload SUCCESS
[project1]	Module object refreshed.
[USER|project1]	Validate START
[USER|project1]	Validate SUCCESS
[USER|project1]	Compile START
Compilation started on 11/20/2022 13:15:17 with qualifiers 'd-lck'
Compiling class com.tony.Test1
Compiling routine com.tony.Test1.1
Compilation finished successfully in 0.008s.

[USER|project1]	Compile SUCCESS
[USER|project1]	Activate START
[USER|project1]	Configure START
[USER|project1]	Configure SUCCESS
Studio project created/updated: project1.PRJ
[USER|project1]	Activate SUCCESS
[USER|project1]	Package START
Exporting 'com.tony.Test1.cls' to '/usr/irissys/mgr/Temp/dirKBwoaM/project1-1.0.0/src/com/tony/Test1.cls'
Exported to /usr/irissys/mgr/Temp/dirKBwoaM/project1-1.0.0/module.xml
Module exported to:
	/usr/irissys/mgr/Temp/dirKBwoaM/project1-1.0.0/

Module package generated:
	/usr/irissys/mgr/Temp/dirKBwoaM/project1-1.0.0.tgz
[USER|project1]	Package SUCCESS
zpm:USER>

软件包的Publish

我们并没有权限把软件包直接发布到官方的registry去。您需要去InterSystems的OpenExchange页面,提交您的软件包。如下图填入软件包的信息,Github URL, 注意勾选右下角的"Publish in Package Manager" 。

image

后面, 我会介绍怎么创建自己team的私服, 用zpm publish可以简单的把iris的软件包发布到私服上去,这对一个开发团队共享软件包并方便部署应该是更有吸引力些。

2
0 281
文章 Hao Ma · 十一月 26, 2022 4m read

建立私服(Porxy-Registry)

这张图解释了您的私服是怎么工作的, 整篇文章在这里: Proxy-Registry

搭建私服

您需要有一台自己的的服务器, 在上面安装IRIS, zpm, 然后用zpm去下载另一个软件包“zpm-registry"。象这样

zpm:DEMO>search -r zpm-registry
registry https://pm.community.intersystems.com:
zpm-registry 1.1.11Repository: https://github.com/intersystems-community/zpm-registry/

zpm:DEMO>install zpm-registry
[DEMO|zpm-registry]	Reload START (/usr/irissys/mgr/.modules/DEMO/zpm-registry/1.1.11/)
[DEMO|zpm-registry]	Reload SUCCESS
[zpm-registry]	Module object refreshed.
[DEMO|zpm-registry]	Validate START
[DEMO|zpm-registry]	Validate SUCCESS
[DEMO|zpm-registry]	Compile START
[DEMO|zpm-registry]	Compile SUCCESS
[DEMO|zpm-registry]	Activate START
[DEMO|zpm-registry]	Configure START
[DEMO|zpm-registry]	Configure SUCCESS
[DEMO|zpm-registry]	Activate SUCCESS
zpm:DEMO>

到github页面, https://github.com/intersystems-community/zpm-registry/, 你可以得到更详细的软件信息。

配置私服连接公服, 需要在私服的IRIS的安装目录添加一个yaml文件, 定义uplink:

uplinks:
  pm:
    url: https://pm.community.intersystems.com/
    allow_packages: dsw,zpm*,?u*

几点说明:

  • uplinks的项目可以有多个,pm是intersystems的默认公共registry

  • allow_packages: - a comma-separated list of allowed packages, you can use the exact package name or mask: * - any sequence of characters, ? - any charac

  • registry 提供的rest 接口描述: https://pm.community.intersystems.com/_spec

设置zpm client连接私服

通过repo命令将您的zpm client切换到刚刚配置的私服。repo -n registry里面的 - user, - pass是你搭建的私服的账号密码。您也可以在私服上修改/registryWeb应用不做用户验证。

zpm:DEMO>repo -list
registry
	Source: 		https://pm.community.intersystems.com
	Enabled?		Yes
	Available?		Yes
	Use for Snapshots?	Yes
	Use for Prereleases?	Yes
	Is Read-Only?		No
	Deployment Enabled? 	No

zpm:DEMO>repo -n registry -r -url http://localhost:52773/registry/ -user superuser -pass demo

registry
	Source: 		http://localhost:52773/registry/
	Enabled?		Yes
	Available?		Yes
	Use for Snapshots?	Yes
	Use for Prereleases?	Yes
	Is Read-Only?		No
	Deployment Enabled? 	No
	Username: 		superuser
	Password: 		<set>
zpm:DEMO>

这时的私服时没有连接公服。

发布软件包到私服

发布一个 GitHub 的包

先把软件存在github, 然后用curl命令把包发布在私服的地址。

$ curl -i -X POST -H "Content-Type:application/json" -u superuser:1104 -d '{"repository":"https://github.com/psteiwer/ObjectScript-Math"}' 'http://localhost:52773/registry/package’

使用zpm 客户端先在本地load,然后使用publish 命令

大概像这个样子:

zpm:USER>help load
...
  ■ Examples
    ∙ load C:\module\root\path\
load C:\module\root\path\module-0.0.1.tgz
      Loads the module described in C:\module\root\path\module.xml
    ∙ load -dev -verbose C:\module\root\path\
load -dev -verbose C:\module\root\path\module-0.0.1.tgz
      Loads the module described in C:\module\root\path\module.xml in developer mode and with verbose output.
    ∙ load https://github.com/user/repository.git
load https://github.com/user/repository.git -b branch-name
      Loads the module described in C:\module\root\path\module.xml in developer mode and with verbose output.
zpm:USER> load https://github.com/user/repository.git
zpm: USER> publish <package name>

其他的Feature

在使用中您还会有各种各样的需求, 但我相信看到现在您应该对怎么寻找答案非常清楚了。这里简单的说两个feautre

1. 发布为Delopyed模式

如果您要隐藏自己的代码,只发布编译后的软件, 可以简单的修改Module.xml, 设置“<Resource Name="Sample.Demo.PKG" Deploy="true"/>“, zpm会自动完成。

2. 包的依赖

因为有朋友问,所以说一些包依赖的功能。是的, zpm是设计了包依赖的功能的,在module.xml里添加Dependencies节点, 可以定被依赖的包的列表。 具体的写法请参见这个例子:module.xml example

虽然但是,对ObjectScript程序来说,定义包依赖的机会并不多。我能想到的应用场景就是, 当您的软件中想用社区其他包,比如上面的bitmap-adoption的包的话,您可以包"bitmap-adoption"打包到module.xml里面。

我对当前发布的200多软件包随便挑了一些,还没有发现有哪个使用了包依赖, 象这样:

zpm:USER>list-dependents yaml-utils
zpm:USER>list-dependents terminal-multiline-editor
zpm:USER>list-dependents bitmap-adoption
zpm:USER>list-dependents global-dump-sql
...
0
0 234
文章 Claire Zheng · 十月 18, 2022 4m read

各位开发者社区的同学们,大家好!

您想更好地获得帮助、讨论有趣的功能、发布公告或分享您的知识吗?在这篇文章中,我们将告诉你如何做到这一切。

我们将通过以下几部分来分享“如何做”:

一般发帖步骤

首先,你需要点击开发者社区网站顶部菜单中的“发布新帖”按钮:

之后,您将看到编辑器中显示创建一个问题、一则公告、一篇文章或一个讨论。不同类型的帖子有自己的一组必填字段和可选字段。

0
0 153
文章 Michael Lei · 五月 20, 2022 29m read

本文向你推荐一些使用IRIS创建REST API应用程序的模式。

注:所有源代码在https://github.com/yurimarx/movie

类模式到REST应用

首先,请看我对创建IRIS API应用程序所需类的建议:

  • IRISRESTApplication: CSP.REST 类会作为中央控制者来控制业务服务处理的所有REST请求和响应.
  • BusinessService: 具有业务主题的类实现。它可以使用一个或多个持久化域类来持久化和查询业务主题要求的数据。
  • Persistent Domain: 管理SQL表的持久化类.

环境准备

  • VSCode;
  • Docker Desktop;
  • InterSystems ObjectScript Extension Pack.

示例应用的类图

我将创建一个电影目录应用程序来演示文章中建议的模式:

Note: 感谢IRIS API 模版应用 https://openexchange.intersystems.com/package/iris-rest-api-template . 这是本教程的基础.

搭建样本应用

1. 在你的文件系统中创建一个movie文件夹。在一个新的VSCode窗口中打开这个文件夹。

2. 在movie 文件夹中创建 Dockerfile 文件来在Docker container实例中运行IRIS社区版. 内容:

0
0 253
文章 Nicky Zhu · 九月 22, 2021 7m read
1 安装基础知识:目的
本指南可帮助您成功在桌面或虚拟机上运行一个经授权许可的 InterSystems IRIS®。适用于我们在技术概要
(First Look)操作指南里所提及的技术。我们提供的说明包括64位 Windows 10, Red Hat 企业版Linux7,以及Apple macOS10.13.
注: InterSystems 云管理器(ICM)提供了一种简单、直观的方法来配置云或虚拟基础架构,用户可在该基 础架构上部署所需的 InterSystems IRIS架构以及其他服务。
请勿使用本指南安装生产实例或任何自定义安装配置;如需帮助,请参阅InterSystems Cloud Manager Guide(《InterSystems 云管理器指南》)或Installation Guide(《安装指南》)。
 
2 用前须知
在开始安装 InterSystems IRIS之前,请确保:• 计算机上的完整管理员权限或sudo权限。• 访问InterSystems IRIS的单文件的可执行安装文件(Windows)或工具包(Linux和macOS)。 • 在IRIS可访问的文件夹下存放有效的许可证密钥文件,通常命名为iris.key。
您正在使用的技术概要(First Look)中对安装有特定要求;例如,可能需要“正常”安全性。在遵循以下说
0
0 702
问题 kun an · 六月 9, 2021

请问cahce中所有的数据库访问都是通过cache server完成的吗,比如使用终端访问数据库、studio开发的应用、使用第三方库使用代码都是先访问cache server,然后通过cache server对数据进行存取的吗?使用studio开发的应用程序也是跑在cache server中吗? 如果是的话studio开发的应用程序(比如web程序)如何跟cache server分开部署呢?

4
0 224
文章 Qianzhu Liu · 四月 2, 2021 7m read

着手书写数据应用方案分享系列文章的初衷是,希望从终端用户的视角阐述我们所期待的数据应用方式及其可能为医疗领域带来的获益,为医学信息工作者提供参考。在这个系列中,笔者会以临床常见疾病和流程为例,用真实的数据录入、获取、展现和使用场景说明需求;尤其是如何细致、精准的构建数据源头,以确保现代医学信息技术“有数可用”、“数用必达”。其中肯定有思虑不周全或逻辑不严谨之处,望各位读者按需审阅,取其精华、弃其糟粕。此外,本系列更多在于探讨数据应用的可能性,而非可行性。文中部分图片尚处于设想模拟阶段,并非真实系统图片,请知悉。

0
0 348
文章 Jeff Liu · 一月 27, 2021 13m read

1.关于本文

就像Caché模式匹配一样,正则表达式也可以在Caché中用来识别文本数据中的模式--只是表达能力更强。本文简要介绍了正则表达式,以及在Caché中如何使用它。这里提供的信息基于各种来源,最值得拜读的是Jeffrey Friedl的《掌握正则表达式》一书,当然还有Caché在线文档。本文无意讨论正则表达式的所有可能性和细节。如果你想了解更多,请参考第5章中列出的信息来源。

使用模式进行文本处理有时会变得很复杂。在处理正则表达式时,我们通常有几种实体:我们正在搜索模式的文本、模式本身(正则表达式)和匹配(文本中与模式匹配的部分)。为了便于区分这些实体,本文档中使用了以下约定。

文本样本以单色字体单独列出,不加引号。

This is a "text string" in which we want to find "something".

除非不明确,否则正文中的正则表达式会以灰色背景显示,如本例。\".*?\".

需要时用不同的颜色突出显示匹配。

这是一个"text string",我们要在其中找到"something"

代码样本显示在如下的文本框里

set t=" This is a ""text string"" in which we want to find ""something "

set r="\"".*?\"""

w $locate(t,r,,tMatch)

1
0 448
文章 Qiao Peng · 一月 14, 2021 12m read

你好,开发者!

你们中的许多人在 Open Exchange 和 Github 上发布了 InterSystems ObjectScript 库。

但对于开发者来说,如何简化项目的使用和协作呢?

在本文中,我想介绍一种简单方法,只需将一组标准文件复制到你的仓库中,就可以启动任何 ObjectScript 项目和对其做出贡献。

我们开始吧!

TLDR - 将以下文件从该仓库复制到你的仓库:

Dockerfile

docker-compose.yml

Installer.cls

iris.script

settings.json{#9f423fcac90bf80939d78b509e9c2dd2-d165a4a3719c56158cd42a4899e791c99338ce73}

.dockerignore{#f7c5b4068637e2def526f9bbc7200c4e-c292b730421792d809e51f096c25eb859f53b637}
.gitattributes{#fc723d30b02a4cca7a534518111c1a66-051218936162e5338d54836895e0b651e57973e1}
.gitignore{#a084b794bc0759e7a6b77810e01874f2-e6aff5167df2097c253736b40468e7b21e577eeb}

你已经知道启动你的项目和协作的标准方式。 以下将详细说明这样做的步骤和原因。

**注意:**在本文中,我们将考虑可在 InterSystems IRIS 2019.1 及更新版本上运行的项目。

选择 InterSystems IRIS 项目的启动环境

通常,我们希望开发者尝试项目/库,并确保这是快速安全的练习。

在我看来,快速安全地启动任何新项目的理想方式是 Docker 容器,它可以保证开发者启动、导入、编译和计算任何内容对于主机来说都是安全的,并且不会破坏任何系统或代码。如果出了问题,只需停止并删除容器即可。 如果应用程序占用大量磁盘空间,使用容器将其擦除,空间就回来了。 如果某个应用程序破坏了数据库配置,只需删除配置被破坏的容器。 简单又安全。

Docker 容器提供安全和标准化。

运行通用 InterSystems IRIS Docker 容器的最简单方法是运行 IRIS 社区版映像

  1. 安装 Docker desktop 

  2. 在操作系统终端中运行以下命令:

docker run --rm -p 52773:52773 --init --name my-iris store/intersystems/iris-community:2020.1.0.199.0
  1. 然后在主机浏览器上打开管理门户:

http://localhost:52773/csp/sys/UtilHome.csp

  1. 或者打开终端启动 IRIS:

    docker exec -it my-iris iris session IRIS

  2. 不需要 IRIS 容器时,将其停止:

    docker stop my-iris

好! 我们在一个 docker 容器中运行 IRIS。 但是你希望开发者将你的代码安装到 IRIS 中,可能还要进行一些设置。 这就是我们下面要讨论的。

导入 ObjectScript 文件

最简单的 InterSystems ObjectScript 项目可以包含一组 ObjectScript 文件,例如类、例程、宏和global。 请查看有关命名和建议的文件夹结构的文章。

问题是如何将所有这些代码导入 IRIS 容器?

此时我们可以借助 Dockerfile 来获取通用 IRIS 容器,并将某个仓库中的所有代码导入 IRIS,然后根据需要对 IRIS 进行一些设置。 我们需要在仓库中添加一个 Dockerfile。

我们来看一下 ObjectScript 模板仓库中的 Dockerfile

ARG IMAGE=store/intersystems/irishealth:2019.3.0.308.0-community
ARG IMAGE=store/intersystems/iris-community:2019.3.0.309.0
ARG IMAGE=store/intersystems/iris-community:2019.4.0.379.0
ARG IMAGE=store/intersystems/iris-community:2020.1.0.199.0
FROM $IMAGE

USER root

WORKDIR /opt/irisapp
RUN chown ${ISC_PACKAGE_MGRUSER}:${ISC_PACKAGE_IRISGROUP} /opt/irisapp

USER irisowner

COPY  Installer.cls .
COPY  src src
COPY iris.script /tmp/iris.script # run iris and initial 

RUN iris start IRIS \
    && iris session IRIS < /tmp/iris.script

 

前几个 ARG 行设置 $IMAGE 变量,随后在 FROM 中使用该变量。 这适合在不同的 IRIS 版本中测试/运行代码,只需在 FROM 前面的最后一行中更改 $IMAGE 变量即可切换版本。

这里我们采用:

ARG IMAGE=store/intersystems/iris-community:2020.1.0.199.0

FROM $IMAGE

这意味着我们将使用 IRIS 2020 社区版 build 199。

我们想要导入仓库中的代码,这意味着我们需要将仓库中的文件复制到 docker 容器。 下面几行完成此操作:

USER root

WORKDIR /opt/irisapp
RUN chown ${ISC_PACKAGE_MGRUSER}:${ISC_PACKAGE_IRISGROUP} /opt/irisapp

USER irisowner

COPY  Installer.cls .
COPY  src src

USER root - 这里我们将用户切换为 root,以在 docker 中创建文件夹和复制文件。

WORKDIR  /opt/irisapp - 我们在此行中设置将文件复制到的工作目录。

RUN chown ${ISC_PACKAGE_MGRUSER}:${ISC_PACKAGE_IRISGROUP} /opt/irisapp   -  这里我们为运行 IRIS 的 irisowner 用户和组授予权限。

USER irisowner - 将用户从 root 切换到 irisowner

COPY Installer.cls .  -Installer.cls 复制到工作目录的根目录。 不要漏了那个点儿。

COPY src src - 将仓库的 src 文件夹中的源文件复制到 docker 上的工作目录的 src 文件夹中。

在下一个块中,我们运行初始脚本,其中将调用安装程序和 ObjectScript 代码:

COPY iris.script /tmp/iris.script # run iris and initial 
RUN iris start IRIS \
    && iris session IRIS < /tmp/iris.script

COPY iris.script / - 我们将 iris.script 复制到根目录。 它包含我们要调用以设置容器的 ObjectScript。

RUN iris start IRIS</span>  - 启动 IRIS

&& iris session IRIS < /tmp/iris.script - 启动 IRIS 终端并在其中输入初始 ObjectScript。

很好! 我们有 Dockerfile,它将文件导入 docker。 但还有两个文件:installer.cls 和 iris.script。我们来检查一下。

Installer.cls

Class App.Installer
{

XData setup
{
<Manifest>
  <Default Name="SourceDir" Value="#{$system.Process.CurrentDirectory()}src"/>
  <Default Name="Namespace" Value="IRISAPP"/>
  <Default Name="app" Value="irisapp" />

  <Namespace Name="${Namespace}" Code="${Namespace}" Data="${Namespace}" Create="yes" Ensemble="no">

    <Configuration>
      <Database Name="${Namespace}" Dir="/opt/${app}/data" Create="yes" Resource="%DB_${Namespace}"/>

      <Import File="${SourceDir}" Flags="ck" Recurse="1"/>
    </Configuration>
    <CSPApplication Url="/csp/${app}" Directory="${cspdir}${app}"  ServeFiles="1" Recurse="1" MatchRoles=":%DB_${Namespace}" AuthenticationMethods="32"
       
    />
  </Namespace>

</Manifest>
}

ClassMethod setup(ByRef pVars, pLogLevel As %Integer = 3, pInstaller As %Installer.Installer, pLogger As %Installer.AbstractLogger) As %Status [ CodeMode = objectgenerator, Internal ]
{
  #; Let XGL document generate code for this method. 
  Quit ##class(%Installer.Manifest).%Generate(%compiledclass, %code, "setup")
}

}

坦白说,我们不需要 Installer.cls 就可以导入文件。 这只需要一行就能完成。 但除了导入代码之外,我们经常还需要设置 CSP 应用,引入安全设置,创建数据库和命名空间。

在此 Installer.cls 中,我们创建了名为 IRISAPP 的新数据库和命名空间,并为该命名空间创建了默认的 /csp/irisapp 应用程序。

所有这些都在元素中 <Namespace> 执行:

<Namespace Name="${Namespace}" Code="${Namespace}" Data="${Namespace}" Create="yes" Ensemble="no">

    <Configuration>
      <Database Name="${Namespace}" Dir="/opt/${app}/data" Create="yes" Resource="%DB_${Namespace}"/>

      <Import File="${SourceDir}" Flags="ck" Recurse="1"/>
    </Configuration>
    <CSPApplication Url="/csp/${app}" Directory="${cspdir}${app}"  ServeFiles="1" Recurse="1" MatchRoles=":%DB_${Namespace}" AuthenticationMethods="32"
       
    />
  </Namespace>

我们用 Import 标签导入 SourceDir 中的所有文件:

<Import File="${SourceDir}" Flags="ck" Recurse="1"/>

这里的 SourceDir 是一个变量,设置为当前目录/src 文件夹:

<Default Name="SourceDir" Value="#{$system.Process.CurrentDirectory()}src"/>

有了带这些设置的 Installer.cls,我们可以自信地创建一个干净的新数据库 IRISAPP,我们将从 src 文件夹导入任意 ObjectScript 代码到该数据库中。

iris.script

欢迎在这里提供任何所需的初始 ObjectScript 设置代码来启动 IRIS 容器。

例如, 我们加载并运行 installer.cls,然后让 UserPasswords 永远有效,以避免第一次启动时出现密码更改请求,因为开发不需要这个提示。

; run installer to create namespace
do $SYSTEM.OBJ.Load("/opt/irisapp/Installer.cls", "ck")
set sc = ##class(App.Installer).setup()  zn "%SYS"
Do ##class(Security.Users).UnExpireUserPasswords("*") ; call your initial methods here
halt

docker-compose.yml

为什么需要 docker-compose.yml,不能只用 Dockerfile 构建和运行映像吗? 可以的。 但 docker-compose.yml 能让生活更轻松。

通常,docker-compose.yml 用于启动连接到一个网络的多个 docker 映像。

当启动一个 docker 映像需要处理多个参数时,也可以使用 docker-compose.yml 来简化过程。 你可以使用它向 docker 传递参数,例如端口映射、卷、VSCode 连接参数。

version: '3.6' 
services:
  iris:
    build: 
      context: .
      dockerfile: Dockerfile
    restart: always
    ports: 
      - 51773
      - 52773
      - 53773
    volumes:
      - ~/iris.key:/usr/irissys/mgr/iris.key
      - ./:/irisdev/app

这里我们声明服务 iris,它使用 docker 文件 Dockerfile,并开放 IRIS 的以下端口:51773、52773、53773。 此服务还映射两个卷:将主机主目录中的 iris.key 映射到期望的 IRIS 文件夹,以及将源代码的根文件夹映射到 /irisdev/app 文件夹。

Docker-compose 提供了更短的统一命令来构建和运行映像,无论你在 docker compose 中设置了什么参数。

总之,构建和启动镜像的命令是:

$ docker-compose up -d

要打开 IRIS 终端:

$ docker-compose exec iris iris session iris

Node: 05a09e256d6b, Instance: IRIS

USER>

此外,docker-compose.yml 有助于设置 VSCode ObjectScript 插件的连接。

.vscode/settings.json

与 ObjectScript 加载项连接设置有关的部分如下:

{
    "objectscript.conn" :{
      "ns": "IRISAPP",
      "active": true,
      "docker-compose": {
        "service": "iris",
        "internalPort": 52773
      }
    }     

}

在这里,我们看到的设置与 VSCode ObjectScript 插件的默认设置不同。

我们想要连接到 IRISAPP 命名空间(我们用 Installer.cls 创建的):

"ns": "IRISAPP",

一个 docker-compose 设置指示,docker-compose 文件在服务“iris”内,VSCode 将连接到 52773 映射到的端口:

"docker-compose": {
        "service": "iris",
        "internalPort": 52773
      }

如果我们检查一下 52773 的相关设置,我们会看到没有为 52773 定义映射端口:

ports: 
      - 51773
      - 52773
      - 53773

这意味着将使用主机上的随机可用端口,并且 VSCode 将通过随机端口自动连接到 docker 上的 IRIS。

这是一个非常方便的功能,因为它允许在随机端口上运行任意数量的带 IRIS 的 docker 映像,并让 VSCode 自动连接到它们。

其他文件呢?

我们还有:

.dockerignore  - 该文件可用于过滤你不想复制到所构建的 docker 映像中的主机文件。 通常 .git 和 .DS_Store 是必须有的行。

.gitattributes - git 的属性,用于统一来源中的 ObjectScript 文件的行尾。 如果仓库由 Windows 和 Mac/Ubuntu 所有者协作,此文件非常有用。

.gitignore - 你不希望 git 跟踪其更改历史记录的文件。 通常是一些隐藏的操作系统级文件,例如 .DS_Store。

好了!

如何使你的仓库可被 docker 运行并且对协作友好?

  1. 克隆此仓库

  2. 复制以下所有文件:

Dockerfile

docker-compose.yml

Installer.cls

iris.script

settings.json{#9f423fcac90bf80939d78b509e9c2dd2-d165a4a3719c56158cd42a4899e791c99338ce73}

.dockerignore{#f7c5b4068637e2def526f9bbc7200c4e-c292b730421792d809e51f096c25eb859f53b637}
.gitattributes{#fc723d30b02a4cca7a534518111c1a66-051218936162e5338d54836895e0b651e57973e1}
.gitignore{#a084b794bc0759e7a6b77810e01874f2-e6aff5167df2097c253736b40468e7b21e577eeb}

到你的仓库。

更改 Dockerfile 中的这一行,使目录与仓库中要导入 IRIS 的 ObjectScript 匹配(如果在 /src 文件夹中则不要更改)。

就这样。 每个人(也包括你)都会将你的代码导入到新的 IRISAPP 命名空间的 IRIS 中。

人们如何启动你的项目

在 IRIS 中执行任何 ObjectScript 项目的法则为:

  1. Git clone 项目到本地

  2. 运行项目:

$ docker-compose up -d
$ docker-compose exec iris iris session iris

Node: 05a09e256d6b, Instance: IRIS

USER>zn "IRISAPP"

开发者如何为你的项目做出贡献

  1. 对仓库执行分叉,并将分叉后的仓库 git clone 到本地

  2. 在 VSCode 中打开该文件夹(还需要在 VSCode 中安装 DockerObjectScript 扩展)

  3. 右击 docker-compose.yml->重启 - VSCode ObjectScript 将自动连接并准备好编辑/编译/调试

  4. 向你的仓库提交、推送和拉取请求更改

以下是操作过程的短 gif:

好了! 编码愉快!

0
0 250
文章 Nicky Zhu · 一月 8, 2021 3m read

我打算基于实例中的数据实现业务智能。 怎样才是设置数据库和环境来使用 DeepSee 的最佳方法呢?

本教程通过 3 个 DeepSee 架构示例来解决此问题。 首先,我们从基本架构模型开始,并重点说明其局限性。 对于复杂程度中等的业务智能应用,建议使用下一个模型,对于大多数用例而言,该模型应该足矣。 在本教程的最后,我们将说明如何增强架构的灵活性以管理高级实现。

本教程中的每个示例都介绍了新的数据库和全局映射,并讨论了为何以及何时设置它们。 在构建架构时,则重点说明更灵活的示例提供的好处。

开始前

主服务器和分析服务器

为了使数据高度可用,InterSystems 通常建议使用镜像或映射,并将 DeepSee 实现基于镜像/映射服务器。 承载数据原始副本的机器称为“主服务器”,而承载数据副本和业务智能应用程序的计算机通常称为“分析服务器”(有时称为“报告服务器”)。

拥有主服务器和分析服务器至关重要,主要原因是避免任一台服务器出现性能问题。 请查阅有关推荐架构的文档。

数据和应用程序代码

通常,将源数据和代码存储在同一数据库中仅对小型应用程序有效。 对于更大型的应用程序,建议将源数据和代码存储在两个专用数据库中,这样您就可以与运行 DeepSee 的所有命名空间共享代码,同时保持数据分离。 源数据的数据库应从生产服务器镜像。 该数据库可以为只读,也可为读写。 建议为此数据库持续启用日志功能。

源类和自定义应用程序应存储在生产和分析服务器上的专用数据库中。 请注意,这两个用于源代码的数据库不需要同步,甚至不需要运行相同的 Caché 版本。 只要定期将代码备份到其他地方,通常就不需要日志。

在本教程中,我们采用以下配置。 分析服务器上的 APP 命名空间有 APP-DATA 和 APP-CODE 作为默认数据库。 APP-DATA 数据库可以访问主服务器上的源数据数据库中的数据(源表类及其事实数据)。 APP-CODE 数据库存储 Caché 代码(.cls 和 .INT 文件)以及其他自定义代码。 数据和代码的这种分离是一种典型的架构,这允许用户,例如,有效地部署 DeepSee 代码和自定义应用程序。

在不同的命名空间上运行 DeepSee

使用 DeepSee 的业务智能实现通常在不同的命名空间中运行。 在本文中,我们将说明如何设置单个的 APP 命名空间,但是相同的过程适用于运行业务智能应用程序的所有名称空间。

文档

建议熟悉文档页面执行初始设置。 该页面的内容包括:设置 Web 应用程序,如何将 DeepSee 全局变量放置在单独的数据库中,以及 Deepeep 全局变量的替代映射列表。


在本系列的第二部分中,我们将阐述基本架构模型的实现

0
0 331