搜索​​​​

清除过滤器
文章
Qiao Peng · 十月 17, 2023

FHIR生态

2023年6月底,世卫组织(WHO)和HL7签署了合作协议,利用HL7 FHIR提供互操作性,来支撑WHO的SMART指南(SMART Guideline)愿景 - 使用数智化的方式推动并加速一致化的健康干预措施建议,让世界上每个人都能立即从临床、公卫和数据使用建议中充分受益。 作为WHO的《2020-2025 年全球数字卫生战略》的一部分,SMART 指南使用 FHIR 、HL7的临床质量语言 (CQL) 和ICD标准以表达 WHO 的各种健康和临床指南,实现数据互操作、决策支持与指标、术语的一致性。这些标准被进一步利用来为各国及其合作伙伴开发一个由软件库、服务和工具组成的支持生态系统,并作为数字公共产品服务全球卫生健康事业。 为什么世卫组织会采用FHIR作为卫生信息互操作的标准在全球推广其一致化的健康干预措施建议?因为FHIR不仅标准成熟适用,而且还具有一个极具生命力的生态。 一个有生命力的标准会吸引生态的构建,而完善的生态将促进标准的成熟和演进。HL7 FHIR作为新一代的卫生信息互操作标准,其生态已经初具规模并蓬勃发展。 HL7 FHIR的知识产权类型 HL7 FHIR的知识产权是CC0,也就是知识共享。任何机构、组织和个人都可以无需向HL7申请而免费使用、扩展FHIR的标准。其知识产权类型配合FHIR标准的丰满程度,极大地鼓励和促进了基于FHIR的生态建设,应该也是WHO采用FHIR的原因之一。 FHIR的标准发布和标准的推广 标准应该是方便可及的 - 不仅有用户可阅读、可理解的文字说明,更需要要可以直接下载让计算机可用、可理解的电子结构化标准。 HL7 FHIR官网详细说明了每个版本、每个FHIR资源的结构与关系、使用范围、用例和示例。在下载页面提供了各种版本的标准、值集、profile和工具的免费下载。 对于用户的扩展、再约束和实施指南,有专门的实施指南注册和发布网站。这里可以免费注册自己的实施指南、也可以访问、查阅和下载别人的实施指南,从而让基于FHIR标准的自定义扩展可以无障碍地被分享、使用、理解,甚至进一步扩展。 下图是发布在注册网站的按用例类型统计的FHIR实施指南: 这众多方向的实施指南也是FHIR横跨交叉领域建立起成熟生态的体现。FHIR有什么快速建立生态的秘诀? 成熟的卫生信息标准要能应对各种行业互操作挑战,FHIR有一个四层机制用于制定标准并用各种互操作挑战来测试、验证和推进FHIR落地: 工作组(workgroups):FHIR有40多个工作组,专注不同的领域的需求,并制定和改进相关FHIR资源和用例标准。例如FHIR基础架构、基因组学、电子健康档案、财务管理、设备... 加速器计划(accelerators):为了推进在主要互操作领域的成熟和落地,FHIR建立加速器计划让每个领域的各个利益相关方参与进来,通过研究各方的需求、凝聚各方的智慧来推动FHIR。如今已经有8个不同领域的加速器计划: 例如Vulcan是专注连接临床研究、转化研究和医疗保健的加速器,它的成员不仅有HL7这样的标准开发组织,还有学会 - 例如约翰霍普金斯医学院,行业协会 - 例如全球医疗数据科学社区PHUSE,政府机构 - 例如FDA,技术厂商 - 例如InterSystems,药厂 - 例如GSK,甚至意见领袖。 课题(projects):FHIR通过课题,研究具体的需求、实现具体的目标,让FHIR扎实、可用。例如Vulcan加速器有以下课题: 课题 目标 Schedule of Activities (SoA) 活动安排 用FHIR表示电子表格中的活动时间表。 使得研究中的每项活动的描述、时间和标识都能保持一致 Real World Data (RWD) 真实世界数据 以标准化的格式从EHR中提取数据,以支持临床研究,特别是向监管机构提交数据 Phenotypic Data 表型数据 为基因组研究和基因组医学提供更多高质量的标准化表型信息 Electronic Product Information (ePI) 电子产品信息 为产品信息(各论)定义一个共同的结构,支持患者对产品数据的跨边界交换 Adverse Events (AE) 不良事件 支持对不良事件的报告和格式进行标准化。 提高相关FHIR资源的成熟度 FHIR to OMOP FHIR与OMOP映射 支持开发FHIR到OMOP的数据传输,以便更好地分析临床数据,用于研究 连接测试马拉松(connectathons):这是一个针对技术厂商的FHIR互操作系列化的一致性认证。每年3次的连接测试马拉松会确定众多的具体互操作用例,厂商选择并参与这些用例,用FHIR进行跨厂商的互操作测试。它不仅是技术厂商验证自己的FHIR互操作一致性的试验场,更是通过测试和反馈来发现标准的问题、确定标准适用性的大型沟通会。 FHIR confluence上公布有历次的连接测试马拉松的用例说明、实施指南、学习资料等详尽的资料。 除了这些手段,HL7还有FHIR认证,建立FHIR标准的智力资源池、确保FHIR在全球的正确采纳。 FHIR标准的适应性 FHIR的适应性核心在于其标准的设计 - 通过profile,在资源模型层面已经考虑到如何让用户进行不破坏标准的扩展和再约束;在标准成熟上,设计了成熟度模型,让标准基于实际使用和反馈逐步成熟。 Profile可以让用户裁剪、扩展FHIR标准,以适用于自己的术语体系和用例场景,实现基于统一标准的千人千面。 在标准的理解与反馈上,FHIR官方沟通提供了开放的交流和反馈的渠道。 FHIR生态的工具 成熟的生态工具是FHIR的一大亮点。这些工具是整个生态贡献的,好的工具得到广泛认同和采纳,既促进了标准的理解与使用、也避免了低水平的重复建设。 1. 标准学习工具: 理解和学习是标准推行的第一要务。除了汗牛充栋的学习材料和视频,FHIR还有不错的学习网站,例如Clinfhir ,最初设计是方便医生理解如何用FHIR构建和解决自己的用例的,但实际上也被广大卫生信息从业者用于理解FHIR标准。 2. 测试数据生成工具: 想学习标准?没有什么比直观的数据更能说明问题了。FHIR生态下有名的Synthea是一个基于马塞诸塞州的患者真实数据经过统计、混淆后的FHIR测试数据生成工具,可以按用户要求生成指定数量的、符合真实数据分布的FHIR资源,会为每个生成的虚拟患者生成一个FHIR boundle文件,并生成对应的医院、医生等FHIR资源。大家可以免费下载Synthea使用它产生测试数据。 另外,国内也广泛使用的MIMIC - 麻省理工贝斯以色列迪康医学中心的有5万多患者真实完整的高质量重症医疗数据集,如今也有了FHIR版本。 3. FHIR服务器: 还没有FHIR服务器,怎么测试FHIR? FHIR生态下有大量的免费沙箱,用户可以选择它们进行标准的学习和测试。例如官网提供的沙箱和各个厂商提供的沙箱。通过各种API工具,例如postman,学习者无需注册即可以了解FHIR标准的方方面面,甚至将自己的测试数据加载进去并测试自己的解决方案。 4. 标准扩展和再约束构建工具: 如何方便、直观地构建自己的术语、扩展和再约束(Profile)和用例?FHIR生态下有众多公司提供的免费工具可用 - 随君取用。例如术语扩展可以用Snapper和FSH、进行小规模profile开发可以用可视化的Forge或Trifolia-on-FHIR、进行大规模的profile和实施指南开发可以用FSH。 5. 标准验证工具: 需要基于profile对FHIR资源进行校验?资源更多了,不仅有FHIR官网提供的FHIR资源校验网页,还有各种开发语言版本的校验工具代码: JAVA C#/DotNet FHIR生态下百花齐放的各种应用架构、应用方向 更令人眼前一亮的是FHIR生态下各种应用架构、应用方向和众多其它生态对FHIR的采纳。 应用开发架构: FHIR提供了标准卫生信息模型和相应的API,为行业应用的快速开发提供了坚实的基础。FHIR生态下最有名的SMART on FHIR,实现即插即用和可复用的应用开发架构。在国际卫生信息互操作标准发展简史中有简要介绍。 SMART on FHIR市场已经有大量的应用可以直接下载部署。 决策支持架构: 决策支持已经是卫生信息数字化转型的核心需求之一。卫生信息化已经建设了各种基于知识库和基于机器学习的决策支持系统,涵盖了临床、业务管理、费用、组学与科研、公卫、健康管理等全部业务,但仍面临众多挑战。 任何知识库系统和决策支持系统面临的一个关键挑战是决策支持的可移植性!如果决策支持厂商都按自己的数据、术语和服务标准构建解决方案,用户在使用多个决策支持产品时,将面临大量数据转换和映射及服务集成带来的非常高的实施成本和潜在决策错误风险。 FHIR通过Clinical Reasoning模块和CDS Hooks分别提供了本地决策支持架构和外部决策支持架构,通过标准化降低成本和风险、提高决策效率和范围。这里是对CDS Hooks的介绍。 其它标准对FHIR的采纳: 相较于之前流行的互操作标准,FHIR在标准化、灵活性、可用性 三方面取得了很好的平衡。FHIR资源模型比大多数的行业通用数据模型(CDM)都简化,方便使用。曾经各自为战的众多标准都发现FHIR无处不在,且FHIR资源和API可以作为自己的数据和访问数据的基石,而融入FHIR生态可以更方便获得数据、获得更多的推广、发挥更大的价值,因此一系列的XX on FHIR项目应运而生 - 或者直接采纳FHIR、或者与FHIR相兼容。除了上面提到的SMART on FHIR,这里简单汇总一下主要的已完成和进行中的on FHIR项目和标准。 1. IHE IHE(Integrating the Healthcare Enterprise)是国际上比较流行且成功的卫生信息交换服务规范。它一直采用流行和稳定的互操作基础标准来开发自己的服务规范,最初使用DICOM + HL7 V2消息,后来用到HL7 V3 和CDA。IHE发现新的FHIR互操作标准有助于应对新的用例、并更好解决老的用例,认为FHIR会成为最流行的互操作基础标准,因此已经发布了很多基于FHIR的IHE服务,尤其是那些和移动业务相关的服务,例如移动患者人口统计查询 (PDQm)。 2. OMOP on FHIR OMOP(Observational Medical Outcomes Partnership)是包括国内在内全球科研人员进行真实世界研究的重要工具,它开发了通用数据模型CDM和分析工具库。 HL7国际和OHDSI宣布合作提供单一的通用数据模型,用于共享临床护理和观察研究信息 - 这就是OMOP on FHIR项目。 OMOP-on-FHIR 是构建在 OMOP CDM 数据库之上的 FHIR 服务器,它提供中间映射层,实现OMOP CDM和FHIR资源之前的双向转换,从而打通两大生态,使临床医生和研究人员能够从多个来源提取数据并以相同的结构进行分析处理与共享交换而不会降低数据质量,可以同时使用两个生态下丰富的应用与工具,利用各自的生态优势。例如OMOP让FHIR生态可以利用其丰富的预测模型,而FHIR让OMOP的研究分析可以集成到临床工作流程中,推动精准医学的落地。 3. FHIR to CDISC Joint Mapping CDISC 是一个标准开发组织,开发了生物制药行业使用的诸多数据标准,常用于提交临床试验数据以进行分析和监管审批。 通过与HL7合作,FHIR to CDISC Joint Mapping实施指南定义了FHIR 与三个特定 CDISC 标准之间的映射: 研究数据列表模型实施指南 (SDTMIG) 3.2 临床数据采集标准协调实施指南 (CDASH) 2.1 实验室1.0.1 通过简化 HL7 FHIR和 CDISC 标准之间的数据转换,消除使用临床信息支持科研的障碍。用途包括: 捕获“真实世界证据”(RWE),让那些不是为临床试验目的采集的数据可以用于研究监管 利用FHIR 的 SMART等技术,直接在临床系统内部捕获试验驱动的数据,而不是建立单独的临床试验管理解决方案 在回顾性研究中更容易利用临床数据 创建病例报告表单 (CRF),链接到使用 FHIR 资源和Profile定义的数据元素 使两个标准社区的专家能够理解彼此的术语,并随着两套规范的不断发展更好地协调它们 4. 通用数据模型协调 Common Data Models Harmonization(CDMH) 在卫生信息领域,有众多的通用数据模型(Common Data Models)服务于不同的或相同的业务领域。虽然都是“通用”数据模型,但数据在彼此之间并不通用。 FHIR的细颗粒度统一语义资源模型可以作为众多通用数据模型间的桥梁。通用数据模型协调(CDMH)目标就是借助FHIR打通各个通用数据模型,让它们的数据可以相互转换。 CDMH 项目由美国FDA 领导,与其他联邦政府机构合作。已发布的通用数据模型协调 (CDMH) FHIR 实施指南 (IG) 将重点放在以患者为中心的结果研究 (PCOR) 和其它目的提取的观察数据的映射和转换为 FHIR 格式。该项目重点关注以下四种通用数据模型 (CDM) 到 FHIR 的映射: 以患者为中心的结果研究网络 (PCORNet) 整合生物学和床边 (Informatics for Integrating Biology & the Bedside - i2b2) 临床试验 (ACT) 信息学,也称为 i2b2/ACT。 观察性医疗结果合作伙伴 (OMOP) 美国食品和药物管理局的哨兵(Sentinel) 5. Arden Syntax on FHIR 和HL7的临床质量语言(Clinical Quality Language - CQL)类似,Arden Syntax 是一种结构化、可执行的医学知识表示和处理语言,将医学知识表达为独立的单元 - 医学逻辑模块(Medical Logical Modules),常用于设计CDS系统,构建临床指南规则和临床决策规则。 新版本 Arden Syntax 3.0 版采用FHIR进行扩展,重新定义了基于FHIR的标准化的数据模型和数据访问方式。作为经过审计、基于共识的迭代 HL7 标准开发流程的一部分,3.0版已成功通过投票。 6. HL7 V2 to FHIR HL7 V2在全球依然有很高的采纳度,但其局限性和FHIR的成熟度都在推动从V2到FHIR的迁移。HL7 V2 to FHIR 项目建立实施指南,将HL7 V2的组件映射到FHIR组件:V2的消息、消息段、数据类型和词汇分别映射到 FHIR 的Bundle、FHIR资源、数据类型和编码系统,并对FHIR进行相应扩展以弥补二者间的差距。 7. C-CDA on FHIR C-CDA是最广泛实施的 HL7 CDA 实施指南之一,涵盖了临床护理的文档范围。CDA 和 FHIR 之间的互操作能力是推动临床文档进化的重要渠道。 C-CDA on FHIR 实施指南 (IG) 定义了一系列 FHIR 配置文件,以表示 C-CDA 中的各种文档类型,并弥补二者设计上的差异。C-CDA on FHIR 利用FHIR使文档标准更为精简。 还有更多的on FHIR项目没有介绍到,例如SNOMED on FHIR、PDMP on FHIR... 同时可以预期还会有越来越多的on FHIR项目会不断涌现。 不仅是这些on FHIR 项目,越来越多的机构发现FHIR的价值,将自己原来的数据模型改为FHIR。例如美国互操作核心数据集USCDI(U.S. Core Data for Interoperability) 起初采用通用临床数据集CCDS作为模型, 如今已经完全采纳FHIR,并且成为美国国家FHIR标准US Core的一部分。FHIR也得到了很多国家采纳作为国家级卫生信息互操作的标准。 大规模数据统计与分析: 一个好的标准应该有助于解决完整的行业需求。FHIR作为行业互操作标准已经超越了传统互操作的能力范围,除了互操作的数据模型、消息、文档、服务和API,FHIR服务器加上FHIR资源仓库为大规模的卫生信息持久化和访问提供了方案。 FHIR的完整蓝图目前尚缺一块拼图 - 基于FHIR的大规模数据统计与分析。 1. 大规模数据检索 FHIR API提供检索类型的API,通过查询参数(Search Parameter)对资源进行检索。 例如: 想要获取所有检验项目为loinc 1234-1,且检验结果小于9.2的Observation资源,可以用这样的查询参数: GET http://fhirsvr.com/Observation? code-value-quantity=loinc|1234-1$lt9.2 除了FHIR Core发布的查询参数,用户还可以扩展自己的查询参数,满足检索需求。 FHIR标准里的FHIR Path为FHIR资源模型提供了类似于XPath的资源路径导航和获取语言,可以方便地筛选、过滤层次化的FHIR数据。 但FHIR查询API和FHIR Path都仅适合于单资源类型的检索,对于需要多类型资源联合分析、汇聚、统计等分析需求无能为力。 2. 大规模的数据统计分析 HL7为临床质量指标与决策支持提出了临床质量语言(Clinical Quality Language - CQL) ,CQL如今基于FHIR,使用FHIR资源模型来构建标准化的指标体系,以支持决策和基于指标的管理。 对于科研数据分析,借助上面介绍的OMOP on FHIR和其它项目,用户可以用自己熟悉的科研工具并利用FHIR数据支持自己的科研工作,本质上是将FHIR数据转换并导入自己的科研工具。 对于通用大规模数据统计分析,虽然FHIR提供了API、FHIR资源数据序列化的JSON、XML可以作为文档进行分析,但市面上的统计分析工具和机器学习工具大都支持SQL,SQL也是最流行的数据统计分析语言。 FHIR的深层次化模型是立体的、对象化的,而SQL是扁平的、表格化的。这个差异让FHIR对主流分析工具和机器学习工具不友好。这对基于FHIR原生的大规模数据分析利用造成了障碍,是FHIR最需要完善的那一块。 FHIR和生态已经创立了很多项目,努力补上这一环。 SQL on FHIR SQL on FHIR项目的思路是为SQL用户提供FHIR的SQL表示层。SQL表示层提供一个机制:让用户根据自己的需要基于FHIR Path定义视图。这里的视图不是SQL视图,而是一个SQL模型的逻辑表达,由一个新的FHIR工件ViewDefinition定义。各个技术厂商负责物理实现它并展现为SQL表。 例如下面的视图定义: { "resourceType": "http://hl7.org/fhir/uv/sql-on-fhir/StructureDefinition/ViewDefinition", "select": [ { "column": [ { "path": "getResourceKey()", "alias": "id" }, { "path": "gender" } ] }, { "column": [ { "path": "given.join(' ')", "alias": "given_name", "description": "A single given name field with all names joined together." }, { "path": "family", "alias": "family_name" } ], "forEach": "name.where(use = 'official').first()" } ], "name": "patient_demographics", "status": "draft", "resource": "Patient" } 它定义一张这样的SQL表: 考虑到FHIR资源模型的复杂,SQL on FHIR目前尚待成熟。当前是版本2,尚未发布,且有很多限制,例如不能在视图里定义跨资源的字段。 技术厂商的FHIR资源SQL实现 除了SQL on FHIR项目,很多技术厂商也在借助自身技术上的优势为FHIR提供SQL访问层。 例如InterSystems IRIS是一个多模型数据平台技术,它可以同时支持对FHIR资源逻辑模型使用对象建模、对FHIR序列化的JSON/XML使用文档建模,并将这些模型投射为SQL模型。InterSystems IRIS正是借助于这个特性,提供一个名为FHIR SQL构建器(FHIR SQL Builder)的工具,用户通过图形化方式拖拽建立需要的SQL模型,而无需拷贝和转换数据。 FHIR生态正展现出蓬勃的生命力,如今已经是百花齐放。FHIR展现的统一行业语义能力和强大的生态,不仅帮助WHO发布数字公共产品服务,也可以赋能卫生信息数字化转型。
文章
姚 鑫 · 五月 24, 2021

第五章 向邮件添加附件

# 第五章 向邮件添加附件 # 向邮件添加附件 可以将附件添加到电子邮件或消息部分(具体地说,是添加到`%Net.MailMessagePart`或`%Net.MailMessage`的实例)。要执行此操作,请使用以下方法: 这些方法中的每一种都会将附件添加到原始邮件(或邮件部分)的`Parts`数组中,并自动将`IsMultiPart`属性设置为1。 ### AttachFile() ```java method AttachFile(Dir As %String, File As %String, isBinary As %Boolean = 1, charset As %String = "", ByRef count As %Integer) as %Status ``` 将给定文件附加到电子邮件。默认情况下,文件以二进制附件的形式发送,但您可以将其指定为文本。如果文件是文本,还可以指定该文件使用的字符集。 具体地说,此方法创建`%Net.MailMessagePart`的实例,并根据需要将文件内容放在`BinaryData`或`TextData`属性中,并根据需要设置`CharSet`属性和`TextData.TranslateTable`属性。该方法通过引用返回一个整数,该整数指示此新消息部分在部件数组中的位置。 此方法还设置消息或消息部分的`Dir`和`FileName`属性。 ### AttachStream() ```java method AttachStream(stream As %Stream.Object, Filename As %String, isBinary As %Boolean = 1, charset As %String = "", ByRef count As %Integer) as %Status ``` 将给定流附加到电子邮件。如果指定了`Filename`,则附件被视为文件附件。否则,它将被视为内联附件。 ### AttachNewMessage() ```java method AttachNewMessage() as %Net.MailMessagePart ``` 创建`%Net.MailMessage`的新实例,将其添加到消息中,并返回新修改的父消息或消息部分。 ``` AttachEmail() ``` 给定一封电子邮件(`%Net.MailMessage`的实例),此方法会将其添加到邮件中。此方法还设置消息或消息部分的`Dir`和`FileName`属性。 注意:此方法将`contentType`设置为`"message/rfc822"`。在这种情况下,不能添加任何其他附件。 示例:`MessageWithAttach()` 以下示例生成一封带有一个硬编码附件的简单电子邮件。它不为邮件提供任何地址;可以在实际发送邮件时提供该信息 ```java /// w ##class(PHA.TEST.HTTP).MessageWithAttachment() ClassMethod MessageWithAttachment() As %Net.MailMessage { Set msg = ##class(%Net.MailMessage).%New() Set msg.Subject="Message with attachment "_$h Set msg.IsBinary=0 Set msg.IsHTML=0 Do msg.TextData.Write("This is the main message body.") //add an attachment Set status=msg.AttachFile("E:\", "HttpDemo.pdf") If $$$ISERR(status) { Do $System.Status.DisplayError(status) Quit $$$NULLOREF } b Quit msg } ``` # 使用SMTP服务器发送电子邮件 如果有权访问SMTP服务器,则可以发送电子邮件。SMTP服务器必须正在运行,并且必须具有使用它所需的权限。要发送电子邮件,请执行以下操作: 1. 创建`%Net.SMTP`实例并根据需要设置其属性,特别是以下属性: - `Smtpserver`是正在使用的`SMTP`服务器的名称。 - 端口是在`SMTP`服务器上使用的端口;默认值为25。 - 时区指定RFC 822指定的服务器时区,例如 `"EST"` 或 `"-0400"` 或 `"LOCAL"`。如果未设置,消息将使用世界时。 此对象描述将使用的`SMTP`服务器。 2. 如果`SMTP`服务器需要身份验证,请指定必要的凭据。为此: a. 创建`%Net.Authenticator`的实例。 b. 设置此对象的用户名和密码属性。 c. 将`%Net.SMTP`实例的验证器属性设置为等于此对象。 d. 如果邮件本身具有授权发件人,请设置`%Net.SMTP`实例的`AuthFrom`属性。 3. 要使用到`SMTP`服务器的`SSL/TLS`连接,请执行以下操作: a. 将`SSLConfiguration`属性设置为要使用的已激活`SSL/TLS`配置的名称。 `SSL/TLS`配置包括一个名为`Configuration Name`的选项,该选项是在此设置中使用的字符串。 b. 将`UseSTARTTLS`属性设置为0或1。 在大多数情况下,使用值0。如果服务器交互在普通`TCP`套接字上开始,然后在与普通套接字相同的端口上切换到`TLS`,则使用值1。 或者,将`SSLCheckServerIdentity`属性设置为1。如果要验证证书中的主机服务器名称,请执行此操作。 4. 创建要发送的电子邮件(如“创建单部分电子邮件”和“创建多部分电子邮件”中所述)。 5. 调用`SMTP`实例的`send()`方法。此方法返回一个状态,应该检查该状态。 6. 如果返回的状态指示错误,请检查`Error`属性,该属性包含错误消息本身。 7. 检查`FailedSend`属性,该属性包含发送操作失败的电子邮件地址列表。 以下各节中的示例使用了两种不同的免费SMTP服务,这些服务在编写本手册时是可用的。选择这些服务并不意味着特别认可。还要注意的是,这些示例并没有显示实际的密码。 `Samples`命名空间中还有其他示例。要查找它们,请在该命名空间中搜索`%Net.SMTP`。 重要提示:`%Net.SMTP`将邮件正文写入临时文件流。默认情况下,该文件被写入命名空间目录,如果该目录需要特殊的写入权限,则不会创建该文件,并且您会得到一个空的消息正文。 可以为这些临时文件定义新路径,并选择不限制写访问的路径(例如,`/tmp`)。为此,请设置全局节点`%SYS("StreamLocation",namespace)`,其中`NAMESPACE`是运行代码的名称空间。例如: ``` Set ^%SYS("StreamLocation","SAMPLES")="/tmp" ``` 如果`%SYS("StreamLocation",namespace)`为`NULL`,则InterSystems IRIS使用`%SYS("TempDir",namespace)`指定的目录。如果未设置`%SYS("TempDir",namespace)`,则IRIS使用 `%SYS("TempDir")`指定的目录 示例1:`HotPOPAsSMTP()`和`SendSimpleMessage()` 此示例由一起使用的两个方法组成。第一个创建`%Net.SMTP`的实例,该实例使用已在`HotPOP SMTP`服务器上设置的测试帐户: ```java /// w ##class(PHA.TEST.HTTP).HotPOPAsSMTP() ClassMethod HotPOPAsSMTP() As %Net.SMTP { Set server=##class(%Net.SMTP).%New() Set server.smtpserver="smtp.hotpop.com" //HotPOP SMTP服务器使用默认端口(25) Set server.port=25 //创建对象以进行身份验证 Set auth=##class(%Net.Authenticator).%New() Set auth.UserName="isctest@hotpop.com" Set auth.Password="123pass" Set server.authenticator=auth Set server.AuthFrom=auth.UserName b Quit server } ``` 下一个方法使用提供的SMTP服务器作为参数发送一条简单、唯一的消息: ```java ClassMethod SendSimpleMessage(server As %Net.SMTP) As %List { Set msg = ##class(%Net.MailMessage).%New() Set From=server.authenticator.UserName Set:From="" From="xxx@xxx.com" Set msg.From = From Do msg.To.Insert("xxx@xxx.com") //Do msg.Cc.Insert("yyy@yyy.com") //Do msg.Bcc.Insert("zzz@zzz.com") Set msg.Subject="Unique subject line here "_$H Set msg.IsBinary=0 Set msg.IsHTML=0 Do msg.TextData.Write("This is the message.") Set status=server.Send(msg) If $$$ISERR(status) { Do $System.Status.DisplayError(status) Write server.Error Quit "" } Quit server.FailedSend } ``` 示例2:`YPOPsAsSMTP()` 此示例创建使用`YPOPS`的`%Net.SMTP`实例的实例,`YPOPS`是一种客户端软件,提供对`Yahoo`电子邮件帐户的`SMTP`和`POP3`访问。它使用已为此目的设置的测试帐户: ```java ClassMethod YPOPsAsSMTP() As %Net.SMTP { Set server=##class(%Net.SMTP).%New() //local host acts as the server Set server.smtpserver="127.0.0.1" //YPOPs uses default port, apparently Set server.port=25 //Create object to carry authentication Set auth=##class(%Net.Authenticator).%New() //YPOPs works with a Yahoo email account Set auth.UserName="isc.test@yahoo.com" Set auth.Password="123pass" Set server.authenticator=auth Set server.AuthFrom=auth.UserName Quit server } ``` 可以将其与上例中所示的`SendSimpleMessage`方法一起使用。 示例3:`SendMessage()` 以下更灵活的方法同时接受`SMTP`服务器和电子邮件。电子邮件应已包含主题行(如果`SMTP`服务器要求),但不必包含地址。然后,此方法将电子邮件发送到一组硬编码的测试目的地: ```java ClassMethod SendMessage(server As %Net.SMTP, msg As %Net.MailMessage) As %Status { Set From=server.authenticator.UserName //make sure From: user is same as used in authentication Set msg.From = From //finish addressing the message Do msg.To.Insert("xxx@xxx.com") //send the message to various test email addresses Do msg.To.Insert("isctest@hotpop.com") Do msg.To.Insert("isc_test@hotmail.com") Do msg.To.Insert("isctest001@gmail.com") Do msg.To.Insert("isc.test@yahoo.com") Set status=server.Send(msg) If $$$ISERR(status) { Do $System.Status.DisplayError(status) Write server.Error Quit $$$ERROR($$$GeneralError,"Failed to send message") } Quit $$$OK } ``` ## `%Net.SMTP`的其他属性 `%Net.SMTP`类还具有一些您可能需要的其他属性,具体取决于使用的SMTP服务器: - `AllowHeaderEncoding`指定`Send()`方法是否对非`ASCII`标头文本进行编码。默认值为1,这意味着非`ASCII`标头文本按照RFC 2047指定的方式进行编码。 - `ContinueAfterBadSend`指定在检测到失败的电子邮件地址后是否继续尝试发送邮件。如果`ContinueAfterBadSend`为1,系统会将失败的电子邮件地址添加到`FailedSend`属性的列表中。默认值为0。 - `ShowBcc`指定是否将密件抄送标头写入电子邮件。这些通常会被SMTP服务器过滤掉。
文章
water huang · 三月 27, 2023

医院信息化建设实战教程:如何在不允许使用Git的情况下自动备份代码/自动执行代码?

一、背景 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”; 代码如下: Class SYS.Projection Extends ( %Projection.AbstractProjection , Base) { Projection Reference As SYS.Projection ; ClassMethod CreateProjection(classname As %String , ByRef parameters As %String , modified As %String , qstruct) As %Status { w ! s changetime=^oddDEF(classname,63) s changetime=$zdt(changetime,3) w "class"_classname_" is modified at "_changetime_" !",! s CLSBAKPATH=..#CLSBAKPATH i CLSBAKPATH[ "/" d .s PL= "/" e d .s PL= "\" s:$e(CLSBAKPATH,*)'=PL CLSBAKPATH=CLSBAKPATH_PL s sc= $SYSTEM.OBJ.Export(classname_".cls",CLSBAKPATH_classname_$tr(changetime,":-","")_ ".xml" ) q $$$OK } } 执行该方法后,被编译的类将直接导出到目标路径“CLSBAKPATH”,它的名称将类似于“类名20230217130218.xml” 最后,如何使用它? 选择一个要自动备份的类,让它继承“SYS.Projection”。当类被编译时,它会被导出。如果你不修改重新编译它,它会覆盖旧的备份文件。 顺便说一句,我认为关键点是 %Projection.AbstractProjection,有了“CreateProjection”,我们可以做更多,如果你想在生产环境中更新一些代码,并且又不想让人再手动去执行一些目标方法代码,这是个好方法。他需要做的是将xml文件导入studio,并编译它,如果勾选“Compile Imported Items”框,那就不再需要单独编译。也就是导入的时候就会自动编译,编译的时候就自动执行,相当于就是导入后自动执行! 到目前为止,它只备份了一个类文件。如果我们想要备份更多怎么办? 方法来了!简单来说,挂任务! 通常用于备份接口代码和数据。 在本教程中,我们在本地驱动器中备份文件。 首先,我们定义一个名为“Common.SYS”的类 然后添加一个名为“ Export ”的类方法,它是主要方法,它将调用其他功能方法。 A导出类,B导出凭证,C导出GLB。 类看起来像: Class Common.SYS Extends %RegisteredObject { /// Exportclass ClassMethod ExportAllClassesIndividual(NS As %String = "" ) { s Drive=..#Drive s currentns= $ZUTIL ( 67 , 6 , $j ) i NS'= "" d .w:'$d(^["%sys"]CONFIG("Namespaces",NS)) "namespace "_NS_" is not existed,please check it!" ,! .q:'$d(^[ "%sys"]CONFIG("Namespaces",NS)) .zn NS .s FilePath=Drive_":/BakFile/"_NS_"/" .i '##class(%File).DirectoryExists(FilePath) d ..d ##class(%File).CreateDirectoryChain(FilePath) .s sc= $system .OBJ.ExportAllClassesIndividual(FilePath,"b",.el,"","CDYZone,Sample,User" ) .i sc'=1 d ..w " namespace "_NS_" get an error when export class! "_$System.Status.GetErrorText(sc),! e d .s alns="" .f s alns=$o(^["%sys"]CONFIG("Namespaces",alns)) q:alns="" d ..q:$e(alns,1)="%" ..q:(alns="ENSDEMO")||(alns="ENSEMBLE")||(alns["-") ..q:'$d(^[alns]CacheMsg("Confirm")) ..zn alns ..s FilePath=Drive_":/BakFile/"_alns_"/" ..i '##class(%File).DirectoryExists(FilePath) d ...d ##class(%File).CreateDirectoryChain(FilePath) ..s sc=$system.OBJ.ExportAllClassesIndividual(FilePath,"b",.el,"","Common,SYS" ) ..i sc'=1 d ...w " namespace "_alns_" get an error when export class! "_ $System.Status.GetErrorText(sc),! zn currentns q $$$OK } /// Ens.Config.Credentials ClassMethod ExportAllCredentials(NS As %String = "" ) { s Drive=..#Drive s currentns= $ZUTIL ( 67 , 6 , $j ) i NS'= "" d . w :' $d (^[ "%sys" ]CONFIG( "Namespaces" ,NS)) "namespace " _NS_ " is not existed,please check it!" ,! . q :' $d (^[ "%sys" ]CONFIG( "Namespaces" ,NS)) . q :' $d (^[NS]Ens.Conf.CredentialsD) . zn NS . s Data=[] . s FilePath=Drive_ ":/BakFile/" _NS_ "/Ens_Config_Credentials" .i ' ##class ( %File ).DirectoryExists(FilePath) d ..d ##class ( %File ).CreateDirectoryChain(FilePath) . s Data=[] . s CID= "" .f s CID= $o ( ^Ens .Conf.CredentialsD(CID)) q :CID= "" d ..s obj= ##class (Ens.Config.Credentials). %OpenId (CID) ..q :' $IsObject (obj) ..s data={} ..s data.SystemName=obj.SystemName ..s data.Username=obj.Username ..s data.Password=obj.Password ..d Data. %Push (data) . d :Data. %Size ()> 0 ..SaveFile (FilePath,Data. %ToJSON ()) e d . ;w currentns,! . s alns= "" .f s alns= $o (^[ "%sys" ]CONFIG( "Namespaces" ,alns)) q :alns= "" d ..q : $e (alns, 1 )= "%" ..q :(alns= "ENSDEMO" )||(alns= "ENSEMBLE" )||(alns[ "-" ) ..q :' $d (^[alns]CacheMsg( "Confirm" )) ..q :' $d (^[alns]Ens.Conf.CredentialsD) ..zn alns ..s FilePath=Drive_ ":/BakFile/" _alns_ "/Ens_Config_Credentials" ..i ' ##class ( %File ).DirectoryExists(FilePath) d . ..d ##class ( %File ).CreateDirectoryChain(FilePath) ..s Data=[] ..s CID= "" ..f s CID= $o ( ^Ens .Conf.CredentialsD(CID)) q :CID= "" d . ..s obj= ##class (Ens.Config.Credentials). %OpenId (CID) . ..q :' $IsObject (obj) . ..s data={} . ..s data.SystemName=obj.SystemName . ..s data.Username=obj.Username . ..s data.Password=obj.Password . ..d Data. %Push (data) ..d :Data. %Size ()> 0 ..SaveFile (FilePath,Data. %ToJSON ()) zn currentns q $$$OK } ClassMethod SaveFile(FilePath, Data) { s file= ##class ( %FileCharacterStream ). %New () s file.Filename=FilePath_ "/Credentials.JSON" d file. Write (Data) s sc=file. %Save () i sc'= 1 d . w FilePath_ " save Credential failed! " _ $System .Status.GetErrorText(sc),! e d . w FilePath_ " save Credential success! " ,! q $$$OK } ClassMethod SaveGLB(NS) { s Drive=..#Drive w :' $d (^[ "%sys" ]CONFIG( "Namespaces" ,NS)) "namespace " _NS_ " is not existed,please check it!" ,! q :' $d (^[ "%sys" ]CONFIG( "Namespaces" ,NS)) 1 s rset = ##class ( %ResultSet ). %New ( "%SYS.GlobalQuery:NameSpaceList" ) q :'rset.QueryIsValid() "invalid Query:" d rset.Execute(NS, "CDY*" ) s Flag= 0 while (rset.Next()){ s HasData=rset.GetDataByName( "HasData" ) i HasData= 1 d . s Name=rset.GetDataByName( "Name" ) . q :Name[ "PushConfigListD" ;some needed globals . s Data(Name)= 1 . s Flag= 1 } w :Flag= 0 NS_ " namespace doesn't have global to be exported" ,! q :Flag= 0 NS_ " namespace doesn't have global to be exported" s FilePath=Drive_ ":/BakFile/" _NS_ "/GLB/" i ' ##class ( %File ).DirectoryExists(FilePath) d . d ##class ( %File ).CreateDirectoryChain(FilePath) q ##class ( %Library.Global ).Export(NS,.Data,FilePath_ "SYS.gof" ) } /// ExportAll CDY* Globals ClassMethod ExportAllCDYGLB(NS As %String = "" ) { s currentns= $ZUTIL ( 67 , 6 , $j ) i NS'= "" d . w :' $d (^[ "%sys" ]CONFIG( "Namespaces" ,NS)) "namespace " _NS_ " is not existed,please check it!" ,! . q :' $d (^[ "%sys" ]CONFIG( "Namespaces" ,NS)) . s sc= ..SaveGLB (NS) .i sc'= 1 d ..w " namespace " _NS_ " get an error when export global! " _ $System .Status.GetErrorText(sc),! e d . s alns= "" .f s alns= $o (^[ "%sys" ]CONFIG( "Namespaces" ,alns)) q :alns= "" d ..q : $e (alns, 1 )= "%" ..q :(alns= "ENSDEMO" )||(alns= "ENSEMBLE" )||(alns[ "-" ) ..q :' $d (^[alns]CacheMsg( "Confirm" )) ..s sc= ..SaveGLB (alns) ..i sc'= 1 d . ..w " namespace" _alns_ " get an error when export global! " _ $System .Status.GetErrorText(sc),! zn currentns q $$$OK } /// location to store backuped file Parameter Drive = "d" ; ClassMethod Export() { s NS= "" ///namespace d ..ExportAllClassesIndividual (NS) ///class file(include package) d ..ExportAllCredentials (NS) ///Credential information d ..ExportAllCDYGLB (NS) ///Global file, or data q $$$OK } } 在 iris 中添加一个自定义任务,你就能保留最新的代码和数据。 优化消息搜索 当我得到使消息搜索页面支持使用字符流类型的属性进行过滤的解决方案时(为了解决这个问题,我问 intersystems 的工程师,她告诉我,使用“EnsPortal.MsgFilter.Assistant”,覆盖类方法“GetSQLCondition”。所以我做了一个类扩展 EnsPortal.MsgFilter.Assistant.it 似乎已经解决了问题。在编译类之后然后 s ^EnsPortal.Settings("MessageViewer","AssistantClass")=class ). 我写的代码如下: Class ENSLIB.MsgFilterAssistant Extends EnsPortal.MsgFilter.Assistant { ClassMethod GetSQLCondition(pOperator As %String , pProp As %String , pValue As %String , pDisplay As %Boolean = 0 ) As %String { if (pValue = "" ) || ((pOperator '= "Like" ) && (pOperator '= "NotLike" )) quit ##super (pOperator, pProp, pValue, pDisplay) if ( "%%" = $extract (pValue, *- 2 , *- 1 )) { set pValue = "'" _ $extract (pValue, 1 , *- 3 ) _ "' ESCAPE '" _ $extract (pValue, *) _ "'" } else { set pValue = "'" _ $replace (pValue, "'" , "''" ) _ "'" } quit "substring(" _ pProp _ ", 1, 3000000) " _ $case (pOperator, "Like" : "LIKE" , "NotLike" : "NOT LIKE" ) _ " " _ pValue } /// w ##class(ENSLIB.MsgFilterAssistant).SetMV() ClassMethod SetMV() { s ^EnsPortal .Settings( "MessageViewer" , "AssistantClass" )= "ENSLIB.MsgFilterAssistant" q $$$OK } } 导入代码后,必须有人执行'##class(ENSLIB.MsgFilterAssistant).SetMV()',然后它才能工作!所以我想如果有一种方法可以在导入或编译后自动运行 SetMV,我寻找解决方案,从论坛到开放交换,最后我想到可以用 Projection,代码更改为: Class ENSLIB.MsgFilterAssistant Extends (EnsPortal.MsgFilter.Assistant, %Projection.AbstractProjection ) { Projection Reference As MsgFilterAssistant ; ClassMethod GetSQLCondition(pOperator As %String , pProp As %String , pValue As %String , pDisplay As %Boolean = 0 ) As %String { if (pValue = "" ) || ((pOperator '= "Like" ) && (pOperator '= "NotLike" )) quit ##super (pOperator, pProp, pValue, pDisplay) if ( "%%" = $extract (pValue, *- 2 , *- 1 )) { set pValue = "'" _ $extract (pValue, 1 , *- 3 ) _ "' ESCAPE '" _ $extract (pValue, *) _ "'" } else { set pValue = "'" _ $replace (pValue, "'" , "''" ) _ "'" } quit "substring(" _ pProp _ ", 1, 3000000) " _ $case (pOperator, "Like" : "LIKE" , "NotLike" : "NOT LIKE" ) _ " " _ pValue } /// w ##class(ENSLIB.MsgFilterAssistant).SetMV() ClassMethod SetMV() { s ^EnsPortal .Settings( "MessageViewer" , "AssistantClass" )= $this q $$$OK } ClassMethod CreateProjection(cls As %String , ByRef params) As %Status { w ! w "开始执行" ,! d ..SetMV () w "执行完成" ,! q $$$OK } } 类编译完成后会自动执行SetMV!是否有更好的实现方式呢? 如果你有更好的方法,请分享! 4.更多 我将继续寻找更好的方法来复制源类和配置数据。 为黄老师点赞!
文章
sun yao · 十月 12, 2022

前端操作自动生成BS、BP、BO

## **概述** 现有Ensemble平台BS(服务)、BP(流程)、BO(操作)需对平台及开发语言有一定的了解才能实现,为简化用户操作,现对现有平台进行二次封装,通过API接口的形式进行前后端分离,通过前端界面操作实现BS(对外提供的服务)、BP、BO(逻辑处理或调用外部的服务)自动生成(通过%Dictionary实现),具体实现如下。 ## **一、开发技术和工具** 版本:Ensemble 2017.2.1 ## **二、涉及公用类** ### 2.1 %Dictionary.ClassDefinition(自定义类) • property **Super** as %CacheString; Specifies one or more superclasses for the class. 定义一个或多个父类,继承父类 • property** ProcedureBlock** as %Boolean [ InitialExpression = 0 ]; Specifies that the class uses procedure block for method code. 设置类是否允许使用程序块,程序块强制实施变量作用域:方法无法看到由其调用方定义的变量,程序块中的任何变量都会自动成为私有变量 • relationship **Parameters** as %Dictionary.ParameterDefinition [ Inverse = parent,Cardinality = children ]; Parameter. 定义类参数,如全局变量、适配器等相关定义 • relationship **Methods** as %Dictionary.MethodDefinition [ Inverse = parent,Cardinality = children ]; Method. 定义类方法 参考链接:http://localhost:57772/csp/documatic/%25CSP.Documatic.cls?APP=1&LIBRARY=%25SYS&CLASSNAME=%25Dictionary.ClassDefinition ### 2.2 %Dictionary.ParameterDefinition(自定义类参数) • property **Name** as %Dictionary.CacheIdentifier [ Required ]; The name of the parameter. 定义参数名 • property **Default** as %CacheString [ SqlFieldName = _Default ]; Specifies a default value for the parameter assuming the Expression keyword is blank. 定义参数默认值,不设置则为空 • property **Description** as %CacheString; Specifies a description of the parameter. 定义参数描述 参考链接:http://localhost:57772/csp/documatic/%25CSP.Documatic.cls?APP=1&LIBRARY=%25SYS&CLASSNAME=%25Dictionary.ParameterDefinition ###2.3 %Dictionary.MethodDefinition(自定义类方法) • property **Name** as %Dictionary.CacheIdentifier [ Required ]; The name of the method. 定义方法名 • property **ClassMethod** as %Boolean [ InitialExpression = 0 ]; Specifies that the method is a class method. Instance methods can only be invoked via an instantiated object while class methods can be directly invoked without an object instance. 指定该方法是类方法。实例方法只能通过实例化的对象调用,而类方法可以在没有对象实例的情况下直接调用。 • property **FormalSpec** as %CacheString; Specifies the list of arguments. Each argument is of the format [&|*][:][=] where & means pass-by-reference and * means output-only. 定义方法入参,每个入参格式为“参数名:参数类型=默认值”,如:code:%String=”” • property **ReturnType** as %Dictionary.CacheClassname; Specifies the data type of the value returned by a call to the method. Setting ReturnType to an empty string specifies that there is no return value. 定义方法返回值,设置为空则无返回值 • property **WebMethod** as %Boolean [ InitialExpression = 0 ]; Specifies that a method can be invoked as a web method using the SOAP protocol. 设置方法是否为web方法,适用于SOAP协议 • property **Implementation** as %Stream.TmpCharacter; The code that is executed when the method is invoked. In the case of an expression method, this is an expression. In the case of a call method, this is the name of a Cache routine to call. 调用方法时执行的代码。对于表达式方法,这是一个表达式。对于调用方法,这是要调用的缓存例程的名称 参考链接:http://localhost:57772/csp/documatic/%25CSP.Documatic.cls?APP=1&LIBRARY=%25SYS&CLASSNAME=%25Dictionary.MethodDefinition ###2.4 Ens.Config.Production • property **Items** as list of Ens.Config.Item(XMLNAME="Item",XMLPROJECTION="ELEMENT"); 定义Production下的BS、BP、BO,根据父类确认属于哪一类 • method **SaveToClass**(pItem As Ens.Config.Item = $$$NULLOREF) as %Status This method saves the production into the XData of the corresponding class 参考链接: http://localhost:57772/csp/documatic/%25CSP.Documatic.cls?APP=1&LIBRARY=ENSLIB&CLASSNAME=Ens.Config.Production ###2.5 Ens.Config.Item(BS服务、BP流程、BO操作) • property **PoolSize** as %Integer(MINVAL=0,XMLPROJECTION="ATTRIBUTE"); Number of jobs to start for this config item. Default value: 0 for Business Processes (i.e. use shared Actor Pool) 1 for FIFO message router Business Processes (i.e. use a dedicated job) 1 for Business Operations 0 for adapterless Business Services 1 for others For TCP based Services with JobPerConnection=1, this value is used to limit the number of connection jobs if its value is greater than 1. A value of 0 or 1 places no limit on the number of connection jobs. 设置缓冲池大小 • property **Name** as %String(MAXLEN=128,XMLPROJECTION="ATTRIBUTE") [ Required ]; The name of this config item. Default is the class name. 设置BS、BP、BO名称 • property **ClassName** as %String(MAXLEN=128,XMLPROJECTION="ATTRIBUTE") [ Required ]; Class name of this config item. 设置BS、BP、BO类名称 • property **Category** as %String(MAXLEN=2500,XMLPROJECTION="ATTRIBUTE"); Optional list of categories this item belongs to, comma-separated. This is only used for display purposes and does not affect the behavior of this item. 设置类别 • property **Comment** as %String(MAXLEN=512,XMLPROJECTION="ATTRIBUTE"); Optional comment text for this component. 设置注释 • property **Enabled** as %Boolean(XMLPROJECTION="ATTRIBUTE") [ InitialExpression = 1 ]; Whether this config item is enabled or not. 设置启用停用标志 参考链接:http://localhost:57772/csp/documatic/%25CSP.Documatic.cls?PAGE=CLASS&LIBRARY=ENSLIB&CLASSNAME=Ens.Config.Item ##**三、实现方法** ###3.1 创建BS模板类 创建模板类,后续类生成方法体通过模板类获取 /// BS的SOAP模板 Class HIP.Platform.Template.BSSOAPTemplate Extends EnsLib.SOAP.Service { Parameter ADAPTER; Parameter NAMESPACE = "http://tempuri.org"; Parameter SERVICENAME = "BSSOAPTemplate"; Method TemplateFun(code As %String, data As %GlobalCharacterStream) As %GlobalCharacterStream [ WebMethod ] { set OutStream=##class(%GlobalCharacterStream).%New() try{ s ..%ConfigName = $classname($this) set sourceCode=$p($classname($this),".",4) //PUB000X set methodCode=##safeexpression(""""_$get(%methodname)_"""") //SendDataFromHis s messageCode = $p(code,"^",1) s requestType= $select($p(code,"^",2)="REST":"REST", 1:"SOAP") set proc = ##class(%SYS.ProcessQuery).%OpenId($j) //当前进程 获取调用服务客户端的IP地址 set sc = ##class(HIP.Service.PublishService).GetAllowedIP(sourceCode) if +sc=1 { s allowedIP = $p(sc,"^",2) if allowedIP '[ proc.ClientIPAddress { SET oref=##class(%Exception.General).%New("","无权限",,"您的IP地址不允许访问,请联系管理员") THROW oref } }else{ return sc } s request = ##class(HIP.Platform.Message.Request).%New() s request.sourceCode=sourceCode //PUB0001 s request.requestType=requestType //REST SOAP s request.inputFlag="0" //-1表示失败,0表示未处理,1表示成功 s request.inputStream = data //JSON流,或者XML流 s request.messageCode=messageCode //BOE0001 Set tSC=..SendRequestSync("HIP.Platform.BP.ProcessCode",request,.pOutput) If $$$ISERR(tSC) Do ..ReturnMethodStatusFault(tSC) d OutStream.CopyFrom(pOutput.outStream) return OutStream }catch err { set OutStream=##class(%GlobalCharacterStream).%New() do OutStream.Write(err.DisplayString()) return OutStream } } Storage Default { %%CLASSNAME ^HIP.PlatforE240.BSSOAPTemplateD BSSOAPTemplateDefaultData ^HIP.PlatforE240.BSSOAPTemplateD ^HIP.PlatforE240.BSSOAPTemplateI ^HIP.PlatforE240.BSSOAPTemplateS %Library.CacheStorage } } ###3.2 自动生成BS,并添加至Production中 通过模板类自动生成WebService方法,并添加到Production的BS中 /// 创建BS服务 PUB00XX服务,提供给第三方调用 /// d ##class(HIP.Util.SOAP).BSCreateSOAPInfo("PUB0001","提供给HIS访问平台") ClassMethod BSCreateSOAPInfo(Code As %String, Desc As %String) As %Status { ///HIP.Platform.BS.PUB0001 s src = "HIP.Platform.BS."_Code_".PublishWebService" s isExist = 0 try { set isExist=##class(%Dictionary.ClassDefinition).%ExistsId(src) if isExist=1 { //类已存在则更新,先删除再插入 set classObj = ##class(%Dictionary.ClassDefinition).%OpenId(src) d classObj.Parameters.Clear() d classObj.Properties.Clear() d classObj.Indices.Clear() d classObj.ForeignKeys.Clear() d classObj.Methods.Clear() }else { //类不存在则新建 set classObj = ##class(%Dictionary.ClassDefinition).%New(src) } //设置父类 s classObj.Super="EnsLib.SOAP.Service" //设置允许使用程序块,则可动态定义变量 s classObj.ProcedureBlock=1 ///Parameter的值 //设置适配器 set ParDef = ##class(%Dictionary.ParameterDefinition).%New() set ParDef.Name="ADAPTER" d classObj.Parameters.Insert(ParDef) set ParDef = ##class(%Dictionary.ParameterDefinition).%New() //设置服务名 set ParDef.Name="SERVICENAME" set ParDef.Default=Code set ParDef.Description=Desc d classObj.Parameters.Insert(ParDef) //设置命名空间 set ParDef = ##class(%Dictionary.ParameterDefinition).%New() set ParDef.Name="NAMESPACE" set ParDef.Default="www.boe.com" d classObj.Parameters.Insert(ParDef) ///函数模板代码,通过模板类获取 s methodTemplate = ##class(%Dictionary.MethodDefinition).%OpenId("HIP.Platform.Template.BSSOAPTemplate||TemplateFun") Set methodObj=##class(%Dictionary.MethodDefinition).%OpenId(src_"||SendData") if methodObj="" Set methodObj=##class(%Dictionary.MethodDefinition).%New(src_".SendData") //设置方法名 set methodObj.Name="SendData" set methodObj.ClassMethod=0 //set methodObj.FormalSpec="code:%String,data:%GlobalCharacterStream,*pOutput:HIP.Platform.Message.Response" //设置方法入参 set methodObj.FormalSpec="code:%String,data:%GlobalCharacterStream" //设置方法返回值 set methodObj.ReturnType="%GlobalCharacterStream" //设置方法为WebService方法 set methodObj.WebMethod=1 //设置方法具体实现代码,通过模板类获取 set methodObj.Implementation=methodTemplate.Implementation d classObj.Methods.Insert(methodObj) set sc=classObj.%Save() if $$$ISERR(sc) { return $system.Status.GetErrorText(sc) }else{ d $system.OBJ.Compile(src,"ck/displaylog=0") } if isExist=0 { //存储到production中 s prodObj = ##class(Ens.Config.Production).%OpenId("HIP.Platform.Production") if $IsObject($G(prodObj)){ Set item = ##class(Ens.Config.Item).%New() Set item.PoolSize = 1 Set item.Name = src Set item.ClassName = src Set:item.Name="" item.Name = item.ClassName Set item.Category = "" Set item.Comment = Desc Set item.Enabled = 1 Set tSC = prodObj.Items.Insert(item) If $$$ISOK(tSC) { // save production (and item) Set tSC = prodObj.%Save() set ^TempSy("tSC")=tSC If ($$$ISOK(tSC)) { // update production class Set tSC = prodObj.SaveToClass() } return tSC } If $$$ISERR(tSC) return $system.Status.GetErrorText(tSC) } } return $$$OK } catch(ex) { return ex.DisplayString() } } ###**四、 结果展示** 运行 d ##class(HIP.Util.SOAP).BSCreateSOAPInfo("PUB0001","提供给HIS访问平台") 后,Studio中自动生成HIP.Platform.BS.PUB0001.PublishWebService.cls 类 如下: ![image](/sites/default/files/inline/images/zi_dong_sheng_cheng_lei__0.png) 打开Portal管理界面,Production配置,可看到该服务已添加至Production中,如下: ![image](/sites/default/files/inline/images/production_1.png) 可直接通过soapUI调用,地址 http://localhost:57772/csp/hip/HIP.Platform.BS.PUB0001.PublishWebService.CLS?WSDL=1 ![image](/sites/default/files/inline/images/soapui.png) InterSystems消息查看 ![image](/sites/default/files/inline/images/xiao_xi_1.png) ![image](/sites/default/files/inline/images/xiao_xi_2.png) ![image](/sites/default/files/inline/images/xiao_xi_3.png) ###**五、 结论与猜想** 同理,BO也可通过该方法实现自动生成,另可通过建立REST服务或WebService服务的方式通过前端调用该方法实现前端自动生成BS、BP、BO,以简化用户操作,但该方法存在问题点,如BP都为公用单个BP,消息并发量大时可能导致BP堵塞问题,可能实现的解决方法为前端先单独调用接口创建BP,后生成BS,再通过配置实现BS到BP的关联,大家感兴趣可自行尝试,以上,谢谢! 点赞 讲解的非常详细,非常有用。 非常有用的知识 加油 感谢分享 膜拜大佬 学习到了👍👍👍 非常有价值,思路清晰,值得学习 真好 博主的思路值得学习! 谢谢分享,学习了
文章
姚 鑫 · 五月 5, 2021

第三章 使用多维存储(全局变量)(一)

# 第三章 使用多维存储(全局变量)(一) 本章描述了使用多维存储(全局变量)可以执行的各种操作。 # 以全局变量存储数据 在全局节点中存储数据很简单:像对待任何其他变量一样对待全局变量。 区别在于对全局变量的操作是自动写入数据库的。 ## 创建全局变量 创建新的全局变量不需要设置工作;只需将数据设置为全局变量即可隐式创建新的全局结构。可以创建全局变量(或全局变量下标)并通过单个操作将数据放入其中,也可以创建全局变量(或下标)并通过将其设置为空字符串将其保留为空。在ObjectScript中,这些操作是使用`SET`命令完成的。 下面的例子定义了一个名为`Color`(如果还不存在)的全局变量,并将值`“Red”`与之关联。 如果已经存在一个名为`Color`的全局变量,那么这些示例将其修改为包含新信息。 在ObjectScript中: ```java SET ^Color = "Red" ``` 注意:在应用程序中使用直接全局访变量问时,应制定并遵守命名约定,以防止应用程序的不同部分相互“遍历”;这类似于为类、方法和其他变量开发命名约定。 ## 在全局变量节点中存储数据 要在全局下标节点中存储值,只需像设置任何其他变量数组一样设置全局节点的值。如果指定的节点以前不存在,则会创建该节点。如果它确实存在,则其内容将替换为新值。 可以通过表达式(称为全局引用)指定全局内的节点。全局引用由脱字符(`^`)、全局名称和(如果需要)一个或多个下标值组成。下标(如果有)用括号“()”括起来,并用逗号分隔。每个下标值本身都是一个表达式:文字值、变量、逻辑表达式,甚至是全局引用。 **设置全局节点的值是一个原子操作:它肯定会成功,不需要使用任何锁来确保并发性。** 以下都是有效的全局引用: 在ObjectScript中: ```java SET ^Data = 2 SET ^Data("Color")="Red" SET ^Data(1,1)=100 /*第二级下标(1,1)设置为值100。第一级下标(^DATA(1))不存储任何值。 */ SET ^Data(^Data)=10 /*全局变量^data的值是下标的名称。 */ SET ^Data(a,b)=50 /*局部变量a和b的值是下标的名称 */ SET ^Data(a+10)=50 ``` 此外,还可以在运行时使用间接方式构造全局引用。 ## 在全局变量节点中存储结构化数据 每个全局节点可以包含最多`32K`个字符的单个字符串。 数据通常以以下方式之一存储在节点中: - 作为最多`32K`个字符的单个字符串(具体地说,`32K - 1`)。 - 作为包含多条数据的字符分隔字符串。 要使用字符分隔符在节点中存储一组字段,只需使用连接操作符(`_`)将这些值连接在一起。下面的ObjectScript示例使用`#`字符作为分隔符: ```java SET ^Data(id)=field(1)_"#"_field(2)_"#"_field(3) ``` 检索数据时,可以使用`$PIECE`函数将字段拆分: ```java SET data = $GET(^Data(id)) FOR i=1:1:3 { SET field(i) = $PIECE(data,"#",i) } QUIT ``` - 作为包含多条数据的`$LIST`编码字符串。 `$LIST`函数使用特殊的长度编码方案,不需要保留分隔符。(这是InterSystems IRIS对象和SQL使用的默认结构。) 要在节点中存储一组字段,请使用`$LISTBUILD`函数构造列表: ```java SET ^Data(id)=$LISTBUILD(field(1),field(2),field(3)) ``` 检索数据时,可以使用`$LIST`或`$LISTGET`函数将字段拆分: ```java SET data = $GET(^Data(id)) FOR i = 1:1:3 { SET field(i)=$LIST(data,i) } QUIT ``` - 作为较大数据集(例如流或`“BLOB”`)的一部分。 **由于单个节点的数据量限制在略低于`32K`,因此可以通过将数据存储在一组连续节点中来实现更大的结构(如流):** ```java SET ^Data("Stream1",1) = "First part of stream...." SET ^Data("Stream1",2) = "Second part of stream...." SET ^Data("Stream1",3) = "Third part of stream...." ``` **获取流的代码(如`%GlobalCharacterStream`类提供的流)循环遍历结构中的连续节点,该结构将数据作为连续字符串提供**。 - 作为一个位串。 如果正在实现位图索引(位字符串中的位对应表中的行的索引),应该将全局索引的节点值设置为位字符串。 请注意IRIS使用压缩算法来编码位串; 因此,位串只能使用IRIS `$BIT`函数来处理。 - 作为一个空节点。 如果感兴趣的数据是由节点本身提供的,那么通常将实际下标设置为空字符串(`""`)。 例如,将名称与`ID`值相关联的索引通常是这样的: ```java SET ^Data("APPLE",1) = "" SET ^Data("ORANGE",2) = "" SET ^Data("BANANA",3) = "" ``` # 删除全局节点 要从数据库中删除一个全局节点、一组子节点或整个全局节点,请使用ObjectScript `kill`或`ZKILL`命令。 `Kill`命令删除特定全局引用处的所有节点(数据及其在数组中的相应条目),包括任何子代节点。也就是说,所有以指定下标开头的节点都将被删除。 例如,ObjectScript语句: ```java KILL ^Data ``` 删除整个`^Data`全局变量。对此全局变量的后续引用将返回``错误。 ObjectScript语句: ```java KILL ^Data(100) ``` 删除`^Data`全局变量中节点`100`的内容。如果有子代节点,如`^data(100,1)`、`^data(100,2)`和`^data(100,1,2,3)`,这些子节点也会被删除。 ObjectScript `ZKILL`命令用于删除指定的全局或全局下标节点。它不会删除子代子节点。 **注意:在杀死一个大型全局变量之后,该全局变量曾经占用的空间可能没有完全释放,因为垃圾收集器守护进程在后台将这些块标记为空闲。因此,在终止大型全局变量之后立即调用`SYS.Database`类的`ReturnUnusedSpace`方法可能不会返回预期大小的空间,因为该全局占用的块可能尚未释放。** **不能对全局变量使用`new`命令。** # 测试全变量局节点的存在 要测试特定全局变量(或其后代)是否包含数据,请使用`$DATA`函数。 `$DATA`返回一个值,该值指示指定的全局变量引用是否存在。可能的返回值包括: 状态值| 含义 ---|--- `0`| 全局变量未定义。 `1`| 全局变量存在并包含数据,但没有子代。请注意,空字符串(`“”`)可用作数据。 `10`| 全局变量有后代(包含指向子节点的向下指针),但本身不包含数据。对此类变量的任何直接引用都将导``错误。例如,如果`$data(^y)`返回`10`,则`SET x=^y`将产生``错误。 `11`| 全局变量既包含数据,又有后代(包含指向子节点的向下指针)。 # 检索全局变量节点的值 要获取存储在特定全局变量节点中的值,只需使用全局引用作为表达式: ```java SET color = ^Data("Color") ; assign to a local variable WRITE ^Data("Color") ; use as a command argument SET x=$LENGTH(^Data("Color")) ; use as a function parameter ``` ## `$GET`函数 还可以使用`$GET`函数获取全局节点的值: ```java SET mydata = $GET(^Data("Color")) ``` 这将检索指定节点的值(如果存在),如果该节点没有值,则返回空字符串(`“”`)。如果节点没有值,可以使用可选的第二个参数`$get`返回指定的默认值。 ## `WRITE`、`ZWRITE`和`ZZDUMP`命令 可以使用各种ObjectScript显示命令显示全局变量或全局变量子节点的内容。`WRITE`命令以字符串形式返回指定全局或子节点的值。`ZWRITE`命令返回全局变量的名称及其值,以及它的每个子代节点及其值。`ZZDUMP`命令以十六进制转储格式返回指定全局或子节点的值。
文章
姚 鑫 · 四月 23, 2021

第五章 优化查询性能(二)

# 第五章 优化查询性能(二) # 使用索引 **索引通过维护常见请求数据的排序子集,提供了一种优化查询的机制。 确定哪些字段应该被索引需要一些思考:太少或错误的索引和关键查询将运行太慢; 太多的索引会降低插入和更新性能(因为必须设置或更新索引值)。** ## 什么索引 要确定添加索引是否会提高查询性能,请从管理门户SQL接口运行查询,并在性能中注意全局引用的数量。 添加索引,然后重新运行查询,注意全局引用的数量。 一个有用的索引应该减少全局引用的数量。 可以通过在`WHERE`子句或`ON`子句条件前使用`%NOINDEX`关键字来防止使用索引。 应该为联接中指定的字段(属性)编制索引。左外部联接从左表开始,然后查看右表;因此,应该为右表中的字段建立索引。在下面的示例中,应该为`T2.f2`编制索引: ```sql FROM Table1 AS T1 LEFT OUTER JOIN Table2 AS T2 ON T1.f1 = T2.f2 ``` 内部联接应该在两个`ON`子句字段上都有索引。 执行“显示计划”,然后找到第一张map。 如果查询计划中的第一个项目是`“Read master map”`,或者查询计划调用的模块的第一个项目是`“Read master map”`,则查询的第一个映射是主映射,而不是索引映射。 因为主映射读取数据本身,而不是数据索引,这总是表明查询计划效率低下。 除非表相对较小,否则应该创建一个索引,以便在重新运行该查询时,查询计划的第一个映射表示“读取索引映射”。 应该索引在`WHERE`子句`equal`条件中指定的字段。 可能希望索引在`WHERE`子句范围条件中指定的字段,以及`GROUP BY`和`ORDER BY`子句中指定的字段。 在某些情况下,基于范围条件的索引可能会使查询变慢。如果绝大多数行满足指定的范围条件,则可能会发生这种情况。例如,如果将`QUERY`子句`WHERE Date < CURRENT_DATE` 用于大多数记录来自以前日期的数据库,则在`DATE`上编制索引实际上可能会降低查询速度。这是因为查询优化器假定范围条件将返回相对较少的行数,并针对此情况进行优化。可以通过在范围条件前面加上`%noindex`来确定是否发生这种情况,然后再次运行查询。 如果使用索引字段执行比较,则比较中指定的字段的排序规则类型应与其在相应索引中的排序规则类型相同。例如,`SELECT`的`WHERE`子句或联接的`ON`子句中的`Name`字段应该与为`Name`字段定义的索引具有相同的排序规则。如果字段排序规则和索引排序规则之间存在不匹配,则索引可能效率较低或可能根本不使用。 ![image](/sites/default/files/inline/images/1_39.png) ![image](/sites/default/files/inline/images/2_21.png) ## 索引配置选项 以下系统范围的配置方法可用于优化查询中索引的使用: - 要将主键用作`IDKey`索引,请设置`$SYSTEM.SQL.Util.SetOption()`方法,如下所示 `SET status=$SYSTEM.SQL.Util.SetOption("DDLPKeyNotIDKey",0,.oldval)`. 默认为`1` - 要将索引用于`SELECT DISTINCT`查询,请设置`$SYSTEM.SQL.Util.SetOption()`方法,如下所示: `SET status=$SYSTEM.SQL.Util.SetOption("FastDistinct",1,.oldval)`. 默认为`1` ## 索引使用情况分析 可以使用以下任一方法按SQL缓存查询分析索引使用情况: - 管理门户索引分析器SQL性能工具。 - `%SYS.PTools.UtilSQLAnalysis`方法`indexUsage()`、`tableScans()`、`tempIndices()`、`joinIndices()`和`outlierIndices()`。、 ## 索引分析 可以使用以下任一方法从管理门户分析SQL查询的索引使用情况: - 选择系统资源管理器,选择工具,选择SQL性能工具,然后选择索引分析器。 - 选择系统资源管理器,选择SQL,然后从工具下拉菜单中选择索引分析器。 索引分析器提供当前命名空间的SQL语句计数显示和五个索引分析报告选项。 ![image](/sites/default/files/inline/images/3_17.png) ![image](/sites/default/files/inline/images/4_13.png) ![image](/sites/default/files/inline/images/5_6.png) ### SQL语句计数 在SQL索引分析器的顶部有一个对命名空间中的所有SQL语句进行计数的选项。按收集SQL语句按钮。SQL索引分析器显示“正在收集SQL语句...”当计票进行时,然后“完成!”当清点完毕后。SQL语句分为三类进行计数:缓存查询计数、类方法计数和类查询计数。这些计数针对整个当前命名空间,不受架构选择选项的影响。 对应的方法是`%SYS.PTools.UtilSQLAnalysis`类中的`getSQLStmts()`。 可以使用清除语句按钮删除当前命名空间中收集的所有语句。该按钮调用`clearSQLStatements()`方法。 ### 报告选项 可以检查当前命名空间中选定架构的缓存查询报告,也可以(通过不选择架构)检查当前命名空间中所有缓存查询的报告。可以在此分析中跳过或包括系统类查询、`INSERT`语句和/或`IDKEY`索引。“架构选择”和“跳过选项”复选框是用户自定义的。 指数分析报告选项包括: - 索引使用:此选项获取当前名称空间中的所有缓存查询,为每个查询生成显示计划,并记录每个查询使用每个索引的次数以及名称空间中所有查询对每个索引的总使用量。这可用于显示未使用的索引,以便可以删除或修改这些索引以使其更有用。结果集从最少使用的索引到最常使用的索引排序。 对应的方法是`%SYS.PTools.UtilSQLAnalysis`类中的`indexUsage()`。要导出此方法生成的分析数据,请使用`exportIUAnalysis()`方法。 - 使用表扫描的查询:此选项标识当前名称空间中执行表扫描的所有查询。如果可能,应避免表扫描。表扫描并不总是可以避免的,但是如果一个表有大量的表扫描,那么应该检查为该表定义的索引。通常,表扫描列表和临时索引列表会重叠;修复其中一个会删除另一个。结果集按从最大块计数到最小块计数的顺序列出表格。提供了显示计划链接以显示对帐单文本和查询计划。 对应的方法是`%SYS.PTools.UtilSQLAnalysis`类中的`tableScans()`。要导出此方法生成的分析数据,请使用`exportTSAnalysis()`方法。 - 带临时索引的查询:此选项标识当前名称空间中构建临时索引以解析SQL的所有查询。有时,使用临时索引会有所帮助并提高性能,例如,基于范围条件构建一个小索引,然后InterSystems IRIS可以使用该索引按顺序读取主映射。有时,临时索引只是不同索引的子集,可能非常有效。其他情况下,临时索引会降低性能,例如,扫描`master may`以在具有条件的特性上构建临时索引。这种情况表明缺少所需的索引;应该向与临时索引匹配的类添加索引。结果集按从最大块计数到最小块计数的顺序列出表格。提供了显示计划链接以显示对帐单文本和查询计划。 对应的方法是`%SYS.PTools.UtilSQLAnalysis`类中的`tempIndices()`。要导出此方法生成的分析数据,请使用`exportTIAnalysis()`方法。 - 缺少联接索引的查询:此选项检查当前名称空间中具有联接的所有查询,并确定是否定义了支持该联接的索引。它将可用于支持联接的索引从0(不存在索引)排序到4(索引完全支持联接)。外部联接需要一个单向索引。内联接需要双向索引。默认情况下,结果集只包含`JoinIndexFlag 21 AND %NOINDEX E.Age < 65 ``` ![image](/sites/default/files/inline/images/6_5.png)
文章
Claire Zheng · 八月 17, 2021

FHIR标准和国际基于FHIR的互联互通实践(7):国际互联互通实践

国际互联互通的需求是在不断增长,这跟咱们国内的情况是非常类似的。这些年美国在互联互通领域的政策跟实践还是不少的,比如大家可能听到过包括“有意义的使用(Meaningful Use)”,“21世纪治愈法案(21st Century Cures Act)”,还有更多的政策上的驱动。这里先介绍一下“有意义的使用(Meaningful Use)”。 “有意义的使用(Meaningful Use)”其实源于2009年奥巴马签署的美国复兴与投资法案。“有意义的使用(Meaningful Use)”是它的俗称,标准的名称应该是“电子健康档案激励计划”。电子健康档案激励计划提出了很多使用标准,这些标准促进了认证电子病历的使用,分成了三个阶段:从2011年开始是第一阶段,第一阶段使用电子健康档案的技术来做数据的获取共享。之后在2014年做了第二个阶段,第二阶段来强调护理协调和患者信息的交流。第三个阶段是从2017年开始,它的目标是做一些更高级应用,例如电子处方临床学的支持。不过在“有意义的使用(Meaningful Use)”的过程中间,遇到了很多问题,因为它专注在电子病历的认证和使用上,很多用户抱怨已经被认证过的电子病历的能力是不足的。 所以在2018年4月份的时候, CMS将“有意义的使用(Meaningful Use)”,从“电子档案激励计划”改成了“促进互操作性计划”。在2018年的时候,“有意义的使用(Meaningful Use)”相当于是终结了。其替代者,也就是“促进互通的计划”,它现在还在执行。 上图是2020年的评分的标准,大家可以看到红框里面标出来的都是主要的评分项,基本上都集中在互联互通的能力之上的。现在美国正在执行的这些互操作性的政策,其实主要是美国的21世纪治愈法案(21st Century Cures Act),这个法案下有很多的子法律条款,提出了更具体、更可操作、更有计划性的互操作路线。 我挑几个给大家做个介绍。 首先第一个就是可信交换框架和共同协议草案,它设定了4个目标。这4个目标都是针对于提高医疗结果为目标的,它在这个目标里定义了未来的分阶段来进行设定目标,未来4年的目标就是建立所谓的“学习健康体系”。“学习健康体系”实现了临床、科研、公卫、患者个人、医保等各个利益方的互操作能力的健康信息体系。要实现这个目标,美国有一些具体的行动。行动之一就是ONC发布了一个“美国互操作核心数据集(U.S. Core Data for Interoperability)”。这个数据集跟咱们卫生数据集有点像,但是它包含的是数据类、数据元以及规范,建议了术语绑定的数据集。 这个数据集其实挺小的。这个数据集目前现在有两个版本,第一版本已经发布了。 上图包括了尚未发布的第二版的核心数据集,总共只有18个种类,这些数据集比我们的数据集要小得多。美国联邦政府现在要求所有的医疗机构都必须以HL7 FHIR R4的标准来共享交换核心数据集里面所有的患者数据。 还有另外一个法案,最新的“互操作能力和患者访问法规(The Interoperability and Patient Access final rule [CMS-9115-F])”。法规要求每个医院和保险公司都要开放针对于患者个人的数据查询的能力,需要使用FHIR的API,使用FHIR 4.0.1的标准来作为数据交换的基础,患者可以自己自由选择第三方的应用来查看、分享自己的数据。 这个法规从今年(2021年)1月1号开始实施的,目标要在三年之内能够完成。新一届的美国政府还要求所有的机构全都要全面的支持FHIR R4的API和FHIR的美国核心 Profile(总共25个Profile)。所以 FHIR在美国处于一个被快速采纳的阶段。 当然还有其他的机构,除了美国政策驱动之外,行业标准开发组织也在积极采用FHIR,例如说IHE。目前IHE总共开发了251个场景用例,现在基于FHIR已经达到了41个。IHE不仅在使用FHIR资源作为内容的格式,它也在使用FHIR的API来替代原来的互操作实现的方式。 FHIR在科研领域也大展身手。目前用于前瞻性临床研究的这些临床数据,其实很大程度上还是人工收集和分析出来的,需要手动查看临床数据图表、收集这些数据,增加了研究时间并且创造了很多出错的机会。通过FHIR标准,能够用FHIR标准化的协议和API使数据分析流程更加现代化。 “火神计划(Project Vulcan)”就是针对于科研发展的计划,这个计划的目标是将临床研究跟临床护理能够连接起来,让整个利益相关方能够聚集在一起,以弥合临床护理和临床研究之间的现有差距,战略性地连接行业合作,最大限度地利用集体资源,并提供集成的工具和资源。 这个项目在2019年9月份启动,目前有三个方向: 第一个就是建立表型数据的交换标准,其目标是使用FHIR在各种环境中使用和交换匿名化的患者病历级信息,这需要对FHIR资源进行进一步开发; 第二个是通过FHIR获取电子病历系统的数据,并直接生成科研所需数据的能力。当前正在和FDA协作处理药品数据; 第三个是采用FHIR资源描述科研活动计划。当前目标是将“临床数据交换标准联盟”的ODM-XML格式的活动计划转换为FHIR标准。 “火神加速器计划(HL7 Accelerator Program: Project Vulcan)”是一个比较典型的、能够体现出美国的标准合作应用模式的计划。 这个模式是将各个利益方都纳入进来。在支持科研和药品研发的计划中间包含了很多利益方,比如说标准开发组织HL7国际,政府代表FDA,科研机构(比如约翰逊霍普金斯医学院),当然还有行业团体(比如临床研究组织协会),很多的技术厂商(包括InterSystems公司)。通过多方参与来加速标准在科研和药品研发中间的落地。 其他国家也有很多启动了FHIR标准的互联互通的计划——例如沙特,沙特采用FHIR来进行排班计划,德国试图建立FHIR文档仓库上的所有能力,英国在使用FHIR来管理儿童的健康预警。 除此之外,有一个“全球数字健康伙伴关系”联盟,这是一个由30个国家和地区,以及世卫组织组成的合作的项目,中国香港特别行政区也在里面。它为全球参与者提供了交流数据共享、电子健康记录、电子处方、患者访问等最佳实践的机会。上图汇总了这些参与方使用互操作标准的情况。可以看到FHIR的标准的采纳度已经达到了17个,很快就会赶上最流行的V2,V2现在是19个。 以上全球对于互操作标准的采纳以及FHIR使用情况的简单介绍,如需了解更多,欢迎留言与我们交流!
文章
姚 鑫 · 七月 9, 2021

第三十二章 XML基础知识概念

# 第三十二章 XML基础知识概念 ## attribute 以下形式的名值对: ```java ID="QD5690" ``` 属性位于元素中,如下所示,一个元素可以有任意数量的属性。 ```xml Cromley,Marcia N. ``` ## CDATA区域 表示不应该验证的文本,如下所示: ```xml ``` 一个`CDATA`(字符数据)区段不能包含字符串`]]`>,因为这个字符串标志着区段的结束。 这也意味着`CDATA`区段不能嵌套。 注意,`CDATA`部分的内容必须符合为XML文档指定的编码,XML文档的其余部分也是如此。 ## comment 不是XML文档主数据的一部分的插入说明。 注释是这样的: ```xml ``` ## content model 对XML元素的可能内容的抽象描述。 可能的内容模型如下: - 空内容模型(不允许有子元素或文本节点) - 简单内容模型(只允许文本节点) - 复杂内容模型(只有子元素) - 混合内容模型(允许子元素和文本节点) 在所有情况下,元素可能有也可能没有属性; 短语内容模型不涉及元素中属性的存在或不存在。 ## default namespace 给定上下文中任何非限定元素所属的名称空间。 添加的默认名称空间没有前缀。 例如: ```xml Isaacs,Rob G. 1981-01-29 ``` 因为这个名称空间声明没有使用前缀,所以``、``和``元素都属于这个名称空间。 注意,下面的XML没有使用默认名称空间,它实际上等同于前面的示例: ```xml Isaacs,Rob G. 1981-01-29 ``` ## DOM 文档对象模型(DOM)是表示XML和相关格式的对象模型。 ## DTD(文档类型定义) 包含在XML文档或外部文件中的一系列文本指令。 它定义了可以在文档中使用的所有有效元素和属性。 dtd本身不使用XML语法。 ## element 一个元素通常由两个标记(一个开始标记和一个结束标记)组成,可能包含文本和其他元素。 元素的内容是这两个标记之间的所有内容,包括文本和任何子元素。 下面是一个完整的XML元素,包含开始标记、文本内容和结束标记: ```xml Cromley,Marcia N. ``` 一个元素可以有任意数量的属性和任意数量的子元素。 空元素可以包含一个开始标记和一个结束标记,也可以只包含一个标记。 下面的例子是等价的: ```xml ``` 在实践中,元素很可能引用数据记录的不同部分,例如 ```xml Barnes,Gerry 1981-04-23 ``` ## entity (在XML文件中)表示一个或多个字符的文本单元。 一个实体有以下结构: ```xml &characters; ``` ## global element 全局元素和局部元素的概念适用于使用名称空间的文档。 全局元素的名称与局部元素的名称放在一个单独的符号空间中。 全局元素是其类型具有全局作用域的元素,即其类型在相应XML模式的顶层定义的元素。 作为``元素的子元素的元素声明被认为是全局声明。 任何其他元素声明都是局部元素,除非它通过ref属性引用全局声明,这实际上使它成为全局元素。 属性可以是全局的,也可以是局部的。 ## local element 不是全局的XML元素。 局部元素不显式属于任何名称空间,除非元素是限定的。 参见限定元素和全局元素。 ## namespace 名称空间是为标识符定义域的惟一字符串,以便基于xml的应用程序不会混淆一种类型的文档和另一种类型的文档。 它通常以URL(统一资源位置)的形式给出一个URI(统一资源指示器),它可能与实际的web地址对应,也可能不对应。 例如,`“http://www.w3.org”`是一个名称空间。 使用以下语法之一包含命名空间声明: ```xml xmlns="your_namespace_here" pre:xmlns="your_namespace_here" ``` 在这两种情况下,名称空间只在插入名称空间声明的上下文中使用。 在后一种情况下,名称空间与给定的前缀(pre)相关联。 当且仅当元素或属性也有此前缀时,元素或属性就属于该名称空间。 例如: ```xml Ravazzolo,Roberta X. 1943-10-24 ``` 命名空间声明使用`s01`前缀。 ``元素也使用了这个前缀,所以这个元素属于这个名称空间。 但是,``和``元素并不显式地属于任何命名空间。 ## 处理指令(PI) 一种指令(在序言中),旨在告诉应用程序如何使用XML文档或如何处理它。 一个例子; 这将样式表与文档关联起来。 ```xml ``` ## prolog XML文档中根元素之前的部分。 序言以XML声明(指示使用的XML版本)开始,然后可能包括DTD声明或模式声明以及处理指令。 (从技术上讲,不需要`DTD`或模式。 此外,从技术上讲,可以将两者放在同一个文件中。) ## root, root element, document element 每个XML文档都要求在最外层只有一个元素。 这称为根元素、根元素或文档元素。 根元素在序言之后。 ## qualified 如果显式地将元素或属性分配给名称空间,则该元素或属性是限定的。 考虑下面的例子,其中``的元素和属性是不限定的: ```xml Frost,Sally O. 1957-03-11 ``` 在这里,名称空间声明使用`s01`前缀。 没有默认的命名空间。 ``元素也使用了这个前缀,因此该元素属于这个名称空间。 ``和``元素或``属性没有前缀,因此它们不显式属于任何名称空间。 相反,考虑以下情况,其中``的元素和属性是限定的: ```xml Frost,Sally O. 1957-03-11 ``` 在本例中,``元素定义了一个默认名称空间,该名称空间应用于子元素和属性。 注意:XML模式属性`elementFormDefault`属性和`attributeFormDefault`属性控制在给定的模式中元素和属性是否被限定。 在InterSystems IRIS XML支持中,使用类参数来指定元素是否限定。 ## schema 一种为一组XML文档指定元信息的文档,可作为DTD的替代。 与DTD一样,可以使用模式来验证特定XML文档的内容。 对于某些应用程序,XML模式提供了与`dtd`相比的几个优势,包括: - XML模式是有效的XML文档,因此更容易开发操作模式的工具。 - XML模式可以指定一组更丰富的特性,并包含值的类型信息。 形式上,模式文档是符合W3 XML模式规范的XML文档(在`https://www.w3.org/XML/Schema`)。 它遵守XML规则,并使用一些额外的语法。 通常,文件的扩展名是`.xsd`。 ## style sheet 用XSLT编写的文档,描述如何将给定的XML文档转换为另一个XML或其他“人类可读”的文档。 ## text node 包含在开始元素和相应结束元素之间的一个或多个字符。 例如: ```xml sample text node ``` ## type 对数据解释的限制。 在XML模式中,每个元素和属性的定义对应于一个类型。 类型可以是简单的,也可以是复杂的。 每个属性都有一个简单类型。 简单类型还表示没有属性和子元素(只有文本节点)的元素。 复杂类型表示其他元素。 下面的模式片段展示了一些类型定义: ```xml ``` ## unqualified 如果没有显式地将元素或属性分配给名称空间,则该元素或属性是非限定的。 ## well-formed XML 遵循XML规则的XML文档或片段,例如有一个结束标记来匹配一个开始标记。 ## XML declaration 指示给定文档中使用的XML版本(以及可选的字符集)的语句。 如果包含,它必须是文档中的第一行。 一个例子: ```xml ``` ## XPath XPath (XML路径语言)是一种基于XML的表达式语言,用于从XML文档中获取数据。 结果可以是标量,也可以是原始文档的XML子树。 ## XSLT XSLT(可扩展样式表语言转换)是一种基于XML的语言,用于描述如何将给定的XML文档转换为另一个XML或其他“人类可读的”文档。
文章
Lilian Huang · 十月 24, 2022

自动部署自适应分析Adaptive Analytics

当我们使用IRIS时,我们通常有能力快速的部署一个现成使用的BI基础模块(数据、分析立方体和IRIS BI仪表盘)。当我们开始使用Adaptive Analytics时,我们通常希望有同样的功能。Adaptive Analytics拥有我们需要的所有工具。文档中包含了对如何使用开放的网络API的描述。用户界面和引擎之间的所有交互也都是通过内部的Web API发生的,并且可以被发射出来。 有必要将这两个过程自动化:在容器中部署Adaptive Analytics和直接部署到服务器系统。为此,最简单的方法是使用bash脚本来处理API。我们唯一需要的第三方应用程序是一个名为jq的JSON文件解析器。你可以使用以下命令来安装它: apt update apt install -y jq 首先,我们需要登录以获得一个API访问令牌。这个令牌也适用于引擎本身的方法。我们必须将访问令牌保存在一个变量中,因为现在我们几乎在每个请求中都需要它。对于一个标准的登录和密码admin/admin,该命令将看起来像这样: TOKEN=$(curl -u admin:admin --location --request GET 'http ://localhost:10500/default/auth') 接下来,我们需要一个活跃的引擎来与API交互。如果没有许可证检查,引擎是不可用的,所以我们要为它提供许可证。Web API没有这个选项,所以我们必须使用引擎命令: curl --location --request PUT 'http://127.0.0.1:10502/license' --header 'Content-Type: application/json' --data-binary "@$license" 许可证变量包含许可证文件的路径。当我们在不同的平台和不同容量的PC上做一些测试时,我们注意到一个奇特的现象。在系统启动脚本完成后,Adaptive Analytics可能会出现这样的情况:服务还没有启动,但初始化脚本已经将控制权交给了我们的脚本。为了确保引擎启动和运行,我们在一个循环中组织发送许可证,直到我们收到许可证已被接受的消息。这样做的代码看起来像这样: RESPONSE="1" TIME=1 echo "Waiting for engine ......." while [ "$RESPONSE" != "200 OK" ] do for license in /root/license/* do RESPONSE =$(curl --location --request PUT 'http://127.0.0.1:10502/license' --header 'Content-Type: application/json' --data-binary "@$license" | jq -r '.status.message') sleep 1s done echo "$TIME seconds waiting" TIME=$(($TIME + 1)) done echo "Engine started" 打印时间变量对于调试启动问题很有用。正如你所看到的,我们在一个特定的文件夹中循环浏览文件,以便不被文件名所束缚。我们将在未来再次使用这种方法。 现在我们可以与web API互动,我们可以将我们的项目上传到Adaptive Analytics。我们使用下面的代码将所有放在指定文件夹中的项目发送到引擎: for cube in /root/cubes/* do sleep 1s curl --location --request POST "http://localhost:10500/api/1.0/org/default/"Authorization: Bearer $TOKEN" --header "Content-Type: application/xml" --data-binary "@$cube" sleep 5s done 如果我们希望我们的项目可以被BI系统使用,我们必须发布它们。幸运的是,在API中也有这样的方法。由于项目在导入时得到一个随机的唯一ID,首先,我们应该把这些ID解析成一个变量: PROJECTS_ID=$(curl --location --request GET "http://localhost:10500/api/1.0/org/default/projects" -- header "Authorization: Bearer $TOKEN" | jq -r '.response[].id') 然后我们需要遍历变量中的所有项目并发布它们。 for project in $PROJECTS_ID do curl --location --request POST "http://localhost:10500/api/1.0/org/default/project/$project/publish" --header "Authorization: Bearer $TOKEN" sleep 1s done 现在我们需要告诉Adaptive Analytics如何连接到IRIS,以及应该如何命名连接,以便我们的项目能够接收它们。在API中有一个方法,但是它有不同的URL地址。事实证明,这是一个有文档记录的引擎方法。所以如果我们想使用它,我们需要访问一个不同的端口。该方法接受JSON文件形式的连接信息,但不可能从Adaptive Analytics以相同的格式导出它。API根据请求返回给我们的JSON文件有一些额外/缺失的字段。以下是用于IRIS连接的JSON文件模板: { "platformType": "iris", "name": "name_you_want", "connectionId": "name_you_want", "overrideConnectionId":true, "extraProperties": { "namespace": "Your_namespase ", "udafMode": "customer_managed" }, "aggregateSchema": "ATSCALE", "readOnly":false, "isImpersonationEnabled": false, "isCanaryAlwaysEnabled": false, "isPartialAggHitEnabled": false, "subgroups": [ { " name": "iris", "hosts": "iris", "port": 1972, "connectorType": "iris", "username": "Your_username", "password":"Your_password", "isKerberosClientEnabled": false , "queryRoles": [ "large_user_query_role", "small_user_query_role", "system_query_role" ], "extraProperties": {}, "connectionGroupsExtraProps": {} } ] } 值得一提的是“udafMode”参数:如果在IRIS中安装了UDAF,我们可以将其设置为“customer_managed”,如果没有安装UDAF,则设置为“none”。要了解更多关于UDAF是什么、为什么需要它以及在安装它时可能会遇到什么缺陷的信息,请查看这篇()文章。 当我们为所有连接准备好这样的文件时,我们应该从文件夹中加载它们: for connection in /root/connections/* do sleep 1s curl --location --request POST "http://localhost:10502/connection-groups/orgId/default" - -header "Authorization: Bearer $TOKEN" --header "Content-Type: application/json" --data-binary "@$connection" sleep 5s done 接下来,我们希望有机会将Logi Report Designer连接到Adaptive Analytics实例。这就是为什么我们需要模拟Adaptive Analytics初始化的第一个步骤之一。因此,我们为hive连接设置端口: sleep 1s curl --location --request POST "http://localhost:10502/organizations/orgId/default" --header "Authorization : Bearer $TOKEN" --header "Content-Type: application/json" --data-raw '{"hiveServer2Port": 11111}' 为了不超过IRIS社区版本中的连接限制,我们必须限制Adaptive Analytics的同时连接数量。根据我们的经验,为了防止在测试场景中超过限制,在5个连接中保留3个是值得的。我们使用下面的代码模拟更改设置: for settings_list in /root/settings/* do curl --location --request PATCH 'http://localhost:10502/settings' --header "Authorization: Bearer $TOKEN" --header "Content-Type: application/json" --data-binary "@$settings_list" done 在文件夹下的JSON文件中,写入如下内容: { "name": "bulkUpdate", "elements": [ { "name": "connection.pool.group.maxConnections", "value": "3" }, { "name": "connection.pool.user.maxConnections", "value": "3 " } ] } 根据我使用Adaptive Analytics的经验,这种限制对性能影响不大。 修改设置后,需要重新启动引擎: curl --location --request POST 'http://localhost:10500/api/1.0/referrerOrg/default/support/service-control/engine/restart' 现在我们有了自动准备Adaptive Analytics工作所需的一切。为方便您,我们已将上述脚本发表在: https://github.com/intersystems-community/dc-analytics/blob/master/atscale-server/entrypoint.sh 在这个存储库中,您还可以找到使用聚合更新计划的脚本。聚合是在IRIS中配置UDAF时由Adaptive Analytics生成的特殊表。这些表存储聚合查询的缓存结果,从而加快了BI系统的工作速度。Adaptive Analytics有一个更新聚合的内部逻辑,但是自己控制这个过程要方便得多。 我们曾经遇到过这样的情况:这些表存储的数据已经过时好几天了。相应地,Adaptive Analytics返回的值与真实值明显不同。为了避免这种情况,我们在更新BI系统中的数据之前设置了每日的聚合更新。 您可以在Adaptive Analytics的web界面中按每个立方体配置更新。然后,您将能够使用脚本将时间表导出和导入到另一个实例,或者使用导出的时间表文件作为备份。如果不想单独配置每个数据集,还可以在应用程序中找到一个脚本,将所有数据集设置为相同的更新计划。 最后一件便于自动化的事情是在开发阶段从Adaptive Analytics备份项目本身。为了设置这种自动化,我们编写了另一个OEX应用程序,它每天一次将所选项目保存到Git存储库中。 最后一件便于自动化的事情是在开发阶段从Adaptive Analytics备份项目本身。为了设置这种自动化,我们编写了另一个OEX应用程序OEX application,它每天一次将所选项目保存到Git存储库中。 初学者指南描述了在新报表文件中创建小部件的过程。如果使用“插入”选项卡或“组件”窗口,也可以将图表或表格插入到现有的报表中。
文章
Michael Lei · 四月 9

Open AI 与 IRIS 集成 - 文件管理

人工智能不仅限于通过带有说明的文本生成图像,或通过简单的指示创建叙事。您还可以制作图片的变体,或为已有图片添加特殊背景。此外,您还可以获得音频转录,无论其语言和说话者的语速如何。让我们来分析一下文件管理是如何工作的。 问题描述 在分析 OpenAI 有关需要将文件作为输入值的方法的信息时,必须使用 multipart/form-data 提供参数。 在 IRIS 中,我们知道如何使用 JSON 内容创建对 POST 方法的调用。但在这种情况下,使用带有 Base64 格式文件内容的参数并不实用。 要在多址/表单数据(multipart/form-data)中包含文件内容,必须使用%Net.MIMEPart.类。 要在我们的调用中包含文件,应创建一个与类对象 %Net.MIMEPart 相关联的 Content-Disposition 标头 set content = ##class(%Net.MIMEPart).%New() set contentDisposition = "form-data; name="_$CHAR(34)_"image"_$CHAR(34) set contentDisposition = contentDisposition_"; filename="_$CHAR(34)_fileName_$CHAR(34) do content.SetHeader("Content-Disposition",contentDisposition) 由于我们使用请求类来保留进程的值,因此我们必须将 Base64 内容转换为流,以构成内容的主体。 我们可以使用StreamUtils实用程序将 Base64 转换为流。 注意:"pImage"变量包含文件内容的 Base64 字符串。 Do ##class(HS.Util.StreamUtils).Base64Encode(pImage, .tStream) Set content.Body = tStream 不过,在 2023 年全球峰会上,我有幸从 InterSystems 专家那里学到了一个更好的技巧。他告诉我,这种执行方法比 StreamUtils 更有效,因为 StreamUtils 最后会循环读取字符串并记录到 Stream 中。这个解决方案就像使用 JSON 并将其转换为 Stream 的 Get 一样简单。 set contentfile = {} set contentfile.file = pImage set content.Body = contentfile.%Get("file",,"stream<base64") 在调用中包含了所需的所有参数后,我们就可以创建一个新的 MIMEPart 类来封装部件了。 Set rootMIME = ##class(%Net.MIMEPart).%New() do rootMIME.Parts.Insert(content) set writer = ##class(%Net.MIMEWriter).%New() set tSC = writer.OutputToStream(tHttpRequest.EntityBody) set tSC = writer.WriteMIMEBody(rootMIME) Set tContentType = "multipart/form-data; boundary="_rootMIME.Boundary set tSC = ..Adapter.SendFormDataArray(.tHttpResponse, "POST", tHttpRequest,,,url) 这就是我们如何将文件内容发送到我们在 OpenAI 中需要的方法。 Image files图像文件 图像方法允许您发送图片并进行变化。由于所有插图都必须是 PNG 格式,因此当我们以 Base64 格式指明文件内容时,文件名会随机生成,并带有 PNG 扩展名。下面是一个如何更改照片的示例。 Original Variation 正如你所看到的,程序以自己的方式解释指令。它认为公司的标志是一个圆圈,所以用另一个圆圈代替了它。它还发现办公室有一扇玻璃门,于是用另一扇玻璃门代替,但暂时用砖墙代替。此外,它还修改了衬衫的颜色,并改变了男子手臂的位置。此外,OpenIA 还允许您通过提供一个蒙版来编辑图像,蒙版上有您想要插入提示内容的区域。利用同一幅图像,我应用了一个去掉图像背景的蒙版。 Original Mask 当我要求它把我传送到牙买加海滩时,得到了如下结果: 现在,下次见到亲朋好友时,您就可以炫耀自己的假期了 😊 Image图像 Endpoint: POST https://api.openai.com/v1/images/variations 它允许你对已有的图像进行修改。由于它不需要提示您要如何修改,因此我们必须相信人工智能的品味,它会如何解释这张图片。此外,我们还可以定义大小和返回结果的方式,无论是通过链接还是 Base64 格式的内容。 输入参数如下: image: 必选 在这里,您要提及要转换的图像文件。 n: 可选. 默认为 1 在此区域,您可以决定生成图像的最大数量。(使用 1 到 10 之间的数字)。 size: 可选. 默认 1024x1024 定义图像大小,其数值必需为 “256x256”, “512x512”, 或者 “1024x1024”. response_format: 可选.默认是“url” 这个参数是关于您希望如何返回生成图像的格式。此处的值应为 "url "或 "b64_json"。 Endpoint: POST https://api.openai.com/v1/images/edits 它可以让你修改现有的图片,根据掩码文件,按照提示创建图片。此外,我们还可以指定尺寸和返回结果的方式,无论是通过链接还是 Base64 格式的内容。输入参数如下: image: 必选 如上. mask: 必选 这部分是关于所应用的蒙版图像文件. n: 可选,默认 1 如上 size: 可选,默认 1024x1024 如上 response_format: 可选. 默认是 “url” 如上 Audio files声音文件 OpenAI 管理的不仅仅是图像。我们还可以使用音频文件来获取所提供录音的转录或翻译。这种方法使用 Whisper 模型,可以区分专有名词、品牌和俚语,从而提供正确的转录和翻译。例如,将 "微型机器 "作为一个品牌来谈论,与将 "微型机器 "作为一个普通名词翻译成西班牙语是不一样的。下面的例子是对 80 年代一个著名广告插播的转录: 因此,指示 Whisper 为我们转录音频的结果如下: { "text": "This is the Micromachine Man presenting the most midget miniature motorcade of micromachines. Each one has dramatic details, terrific trim, precision paint jobs, plus incredible micromachine pocket playsets. There's a police station, fire station, restaurant, service station, and more. Perfect pocket portables to take anyplace. And there are many miniature playsets to play with and each one comes with its own special edition micromachine vehicle and fun fantastic features that miraculously move. Raise the boat lift at the airport, marina, man the gun turret at the army base, clean your car at the car wash, raise the toll bridge. And these playsets fit together to form a micromachine world. Micromachine pocket playsets, so tremendously tiny, so perfectly precise, so dazzlingly detailed, you'll want to pocket them all. Micromachines and micromachine pocket playsets sold separately from Galoob. The smaller they are, the better they are." } 多么神奇! 你觉得呢? 之所以能取得上述成果,是因为 Whisper 模型接受了训练。我们可以从 OpenAI 页面提供的下图中看到一些相关信息。 更多信息可以访问 https://openai.com/research/whisper 请记住,告知程序文件名至关重要,因为服务需要知道它正在处理的文件类型(如 WAV、MP3、OGG 等)。由于我们在调用中只包含 Base64 内容,因此还必须指明文件扩展名,以便用随机文本和建议的扩展名创建文件名。例如,St.OpenAi.Msg.Audio.AudioRequest 消息的 "类型 "属性可显示音频的种类: MP3、OGG、WAV、FLAC 等。 Endpoint: https://api.openai.com/v1/audio/transcriptions 通过这种方法,您可以将音频内容转录为有声语言。 输入参数如下: file: 必要 在这里,您可以指定要转录的音频文件(而不是文件名)。它支持以下格式: FLAC、MP3、MP4、MPEG、MPGA、M4A、OGG、WAV 或 WEBM model: 必要 用于转录的模型。目前只有 "whisper-1 "可用 language: 可选. 默认是音频语言. 如果指定的话,根据 ISO-639-1,可以提高准确率和延时. prompt: 可选. 这是一段可选的文字,用于引导模型的风格或延续上一段音频。此处的信息必须与音频语言一致。. response_format. 可选,默认为 “json”. 在这一部分中,您要明确转录输出的格式。请使用以下选项之一: "json"、"text"、"verbose_json"。 temperature: 可选,默认为 0. 采样温度应介于 0 和 1 之间。 0.8 等较高值会使输出更加随机,而 0.2 等较低值则会使输出更加集中和确定。如果设置为 0,模型将使用对数似然自动提高温度,直到达到特定阈值。 本方法的文档请参考 https://platform.openai.com/docs/api-reference/audio/createTranscription<. Endpoint: https://api.openai.com/v1/audio/translations 此方法可将音频内容翻译成英语。输入参数如下: file: 必要 它是您要翻译的音频文件(而不是文件名)。它支持以下格式: FLAC、MP3、MP4、MPEG、MPGA、M4A、OGG、WAV 或 WEBM model: 必要. 如上. prompt: 可选l. 这是一段可选的文字,用于引导模型的风格或延续上一段音频。此处的信息必须使用英语。 response_format. 可选. 默认是 “json”. 在这里,您可以用以下选项之一决定转录输出的格式: "json"、"text"、"verbose_json"。 temperature: 可选. 默认为 0. 如上 更多文档请查阅 https://platform.openai.com/docs/api-reference/audio/createTranscription. 下一步? 由于 OpenAi 在不断发展,下一次迭代将是将文本转换为音频的方法,以及其他一些新功能。如果您喜欢这篇文章,请记得点个 "赞"。
文章
jieliang liu · 一月 27, 2021

在Caché中使用正则表达式

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) 2.一些历史(和一些小事)。 在20世纪40年代初,神经生理学家开发了人类神经系统的模型。几年后,一位数学家用一种代数来描述这些模型,他称之为"正则集"。这种代数的符号被命名为"正则表达式"。 1965年,正则表达式第一次在计算机的范畴内被提及。随着qed,一个作为UNIX操作系统一部分的编辑器,正则表达式开始传播。该编辑器后来的版本提供了一个命令序列g/正则表达式/p(全局、正则表达式、打印),在所有文本行中搜索匹配的正则表达式并输出结果。这个命令序列最终成为独立的UNIX命令行程序"grep"。 今天,许多编程语言都存在正则表达式(RegEx)的各种实现(见3.3节)。 3.Regex 101 就像Caché模式匹配一样,正则表达式也可以用来识别文本数据中的模式--只是表达能力更强。下面的章节概述了正则表达式的组成部分,它们的评估和一些可用的引擎,然后在第4章中详细介绍如何使用。 3.1.正则表达式的组成部分 3.1.1.Regex元字符 以下字符在正则表达式中具有特殊意义。 . ( ) [ ] \ ^ $ | 如果你需要将它们作为字面数使用,你需要使用反斜杠来转义。你也可以使用 \Q <literal sequence> \E显式指定字面序列。 3.1.2.文字 普通文本和转义字符被视为字面,例如:。 abc abc \f 换页 \n 换行 \r 回车 \v 标签 \0+三位数(如:0101) 八进制数Caché (ICU)中使用的regex引擎支持八进制数,最高可达\0377(十进制系统为255)。当你从另一个引擎迁移正则表达式时,请确保你了解它如何处理八进制数。 \x+两位数(如:x41) 十六进制数Caché库确实提供了更多处理十六进制数的选项,请参考文档(链接可以在5.8节找到)。 3.1.3.锚 使用锚点,你可以匹配文本/字符串中的位置,例如:。 \A 字符串的开始 \Z 字符串的末端 ^ 文本或行的开始 $ 文末或行末 \b 字词边界 \B 不字界 < 词的开头 #> 词尾 和一些RegEx引擎的行为有所不同,例如,对构成单词的确切定义以及哪些字符被视为单词定界符。 3.1.4.量词 使用正则表达式量词,你可以指定前面的元素可能出现的频率来进行匹配。 {x}正好出现x次 {x,y}最小x,最大y的出现次数。 * 0或更多;相当于{0,}。 +1或更多;相当于{1,}。 ? 0或1 量词很"贪婪",它们会尽可能多地抓取字符。假设我们有下面的文本字符串,想找到带引号的文本。 This is "a text" with "four quotes". 由于选择器的贪婪性质,正则表达式"/".*/"会找到太多的文本。 This is "a text" with "four quotes". 在这个例子中,正则表达式.*试图捕捉尽可能多的位于一对引号之间的字符。然而,由于点选择器 ( .) 也匹配引号,我们没有得到我们想要的结果。 通过一些regex引擎(包括Caché使用的引擎),你可以通过添加一个问号来控制量化符的贪婪程度。因此,正则表达式"\".*?\"现在可以匹配引号中的两部分文本--这正是我们要找的。 This is "a text" with "four quotes". 3.1.5.字符类(范围) 方括号用于指定字符的范围或字符集,例如 [a-zA-Z0-9] 或 [abcd] - 在 regex 中,这被称为字符类。一个范围可以匹配单个字符,所以范围定义中的字符顺序无关紧要--[dbac]返回的匹配结果与[abcd]相同。 要排除一个字符范围,只需在字符范围定义前面加上^(在方括号内!)。[^abc] 匹配除了a, b或c以外的任何字符. 一些regex引擎确实提供了预先定义的字符类(POSIX),例如。 [:alnum:] [a-zA-z0-9] [:alpha:] [a-zA-Z] [:blank:] [\t] … 3.1.6.Groups (组) 正则表达式的部分内容可以使用对括号进行分组。这对于将量化符应用于一组选择符,以及从同一regex内(反向引用)和从调用正则表达式的Caché对象脚本代码(捕获缓冲区)中引用分组都很有用。组可以被嵌套。 下面的regex匹配由一个三位数组成的字符串,后面是一个破折号,然后是3对大写字母和一个数字,后面是一个破折号,然后是与第一部分相同的三位数。 ([0-9]{3})-([A-Z][[0-9]){3}-\1 这个例子展示了如何使用反向引用(见下文)不仅匹配结构,而且匹配内容:反向引用(紫色)告诉引擎在结尾处寻找与开头处相同的三位数数字(黄色)。它还演示了如何将量词应用于更复杂的结构(绿色)。 上面的regex将匹配以下字符串。 123-D1E2F3-123 在这些上面是不匹配的。 123-D1E2F3-456(最后三位数与前三位数不同) 123-1DE2F3-123(中间部分不包含三个字母/数字对) 123-D1E2-123(中间部分只包含两个字母/数字对) 组也会填充所谓的捕获缓冲区(见4.5.1节)。这是一个非常强大的功能,它允许同时匹配和提取信息。 3.1.7. Alternations(交替) 使用管道字符来指定alternations,例如skyfall|done。这允许匹配更复杂的表达式,如3.1.5节中描述的字符类。 3.1.8.回溯引用 后面的引用允许您引用以前定义的组(括号中的选择器)。下面的例子显示了一个正则表达式,它匹配三个必须相等的连续字符。 ([a-zA-Z])/1/1 后面的引用由\x指定,而x代表第x个括号中的表达式。 3.1.9.优先规则 []在()之前 +和? 在序列前:ab等于a(b*),而不是(ab)*。 序列在alternation前:ab|c等于(ab)|c,而不是a(b|c) 3.2.一些理论 正则表达式的评估通常采用以下两种方法之一来实现(这里描述是简化的,请参考第5章中提到的文献进行深入讨论)。 文本驱动(DFA - Deterministic Finite Automaton) 引擎逐字逐句地检查输入文本,并尝试匹配它目前所拥有的内容。当它真正到达输入文本的结尾时,它宣布成功。 Regex-driven (NFA - Non-deterministic Finite Automaton) 引擎会逐一检查正则表达式,并尝试将其应用到文本中。当它真正到达(并匹配)最后一个标记时,它宣布成功。 方法1是确定性的,执行时间只取决于输入文本的长度。正则表达式中选择符的顺序不影响执行时间。 方法2是非决定性的,引擎会遍历正则表达式中选择符的所有组合,直到找到匹配或遇到错误。因此,当它没有找到匹配项时,这种方法特别慢(因为它必须遍历所有可能的组合)。选择符的顺序确实对执行时间有影响。但是,这种方法允许回溯和捕获缓冲区。 3.3.Regex引擎 目前有很多不同的regex引擎,有些是编程语言或操作系统的内置部分,有些是几乎可以在任何地方使用的库。以下是一些regex引擎,按评估方法分组。 DFA: grep, awk, lex. NFA:Perl、Tcl、Python、Emacs、sed、vi、ICU。 下表是各种编程语言和库中可用的regex功能的比较。 详情请点击这里:https://en.wikipedia.org/wiki/Comparison_of_regular_expression_engines 4.RegEx和Caché InterSystems Caché使用ICU库来处理正则表达式,Caché在线文档描述了它的许多功能。请参考ICU库的在线文档以了解完整的细节(包括诸如回溯引用等)--ICU的链接可以在5.8节中找到。以下章节旨在为您快速介绍如何使用它。 4.4.$match()和$locate() 在Caché ObjectScript (COS)中,两个函数$match()和$locate()提供了对ICU库提供的大部分regex功能的直接访问。$match(String, Regex) 在输入的字符串中搜索指定的Regex模式。当它找到一个匹配的模式时,它返回1,否则它返回0。 例如: w $match("baaacd",".*(a)\1/1.*") 返回1。 w $match("baacd",".*(a)\1/1.*") 返回0。 $locate(String,Regex,Start,End,Value) 就像$match()一样,以指定的regex模式搜索输入字符串。然而,$locate()给你更多的控制权,它返回更多的信息。在Start中,你可以告诉$locate应该在哪个位置开始搜索输入字符串中的模式。当$locate()找到一个匹配时,它会返回匹配的第一个字符的位置,并将End设置为匹配后的下一个字符位置。匹配的内容会在Value中返回。 如果$locate()没有找到匹配的对象,它将返回0,并且不触及End和Value(如果指定)的内容。End和Value是以引用的形式传递的,所以如果你重复使用它(例如在循环中)要小心。 例如: w $locate("abcdexyz",".d.",1,e,x) 返回3,e设为6,x设为"cde" $locate()执行模式匹配,并且可以同时返回第一个匹配的内容,如果需要提取所有匹配的内容,可以在循环中反复调用$locate(),也可以使用%Regex.Matcher提供的方法。 如果需要提取所有匹配的内容,你可以在一个循环中重复调用$locate(),或者你可以使用%Regex.Matcher提供的方法(见下一节)。 4.5.%Regex.Matcher. %Regex.Matcher提供了ICU库的regex功能,就像$match()和$locate()一样。然而,%Regex.Matcher还提供了一些高级功能,使更复杂的任务变得非常容易使用。下面的章节将重新审视捕获缓冲区,看看用正则表达式替换字符串的可能性以及控制运行时行为的方法。 4.5.1.捕获缓冲区(Buffers) 正如我们在关于组、回溯引用和$locate()的章节中已经看到的,正则表达式允许你同时搜索文本中的模式并返回匹配的内容。它的工作原理是将您想要提取的模式的部分放在一对括号中(组)。匹配成功后,捕获缓冲区包含所有匹配的组的内容。请注意,这与$locate()通过其值参数提供的内容略有不同:$locate()返回整个匹配本身的内容,而捕获缓冲区则让您访问匹配的部分内容(组)。 要使用它,你需要创建一个 %Regex.Matcher 类的对象,并将正则表达式和输入字符串传递给它。然后你可以调用%Regex.Matcher提供的方法来执行实际工作。 例1(简单组): set m=##class(%Regex.Matcher).%New("(a|b).*(de)", "abcdeabcde") w m.Locate()返回1 w m.Group(1) 返回 a w m.Group(2) 返回 de 例2(嵌套组和回溯引用)。 set m=##class(%Regex.Matcher).%New("((a|b).*?(de))(\1)", "abcdeabcde") w m.Match()返回1 w m.GroupCount返回4 w m.Group(1) 返回 abcde。 w m.Group(2) 返回 a w m.Group(3) 返回 de w m.Group(4) 返回 abcde。 (注意嵌套组的顺序--因为开头的括号标志着一个组的开始,所以内部组的索引号比外部组的索引号高) 如前所述,捕获缓冲区是一个非常强大的功能,因为它们允许您同时匹配模式和提取匹配的内容。如果没有正则表达式,您必须在第一步中找到匹配的内容(例如使用模式匹配操作符),并在第二步中根据一些标准提取匹配的内容(或部分内容)。 如果您需要对模式中的部分进行分组(例如对该部分应用量化符),但又不想用匹配部分的内容来填充捕获缓冲区,您可以通过在组前加上问号和冒号的方式将组定义为"非捕获"或"害羞",如下面的例子3。 例3。 set m=##class(%Regex.Matcher).%New("((a|b).*?(?:de))(\1)","abcdeabcde") w m.Match()返回1 w m.Group(1) 返回 abcde。 w m.Group(2) 返回 a w m.Group(3) 返回 abcde。 w m.Group(4) 返回 <REGULAR EXPRESSION>zGroupGet+3^%Regex.Matcher.1。 4.5.2.替换 %Regex.Matcher也提供了立即替换匹配内容的方法。ReplaceAll()和ReplaceFirst()。 set m=##class(%Regex.Matcher).%New(".c.","abcdeabcde") w m.ReplaceAll("xxxx") 返回 axxxxeaxxxxe。 w m.ReplaceFirst("xxxx") 返回 axxxxeabcde。 你也可以在替换字符串中引用组。如果我们在上一个例子的模式中添加一个组,我们可以通过在替换字符串中包含$1来引用它的内容。 set m=##class(%Regex.Matcher).%New(".(c).","abcdeabcde").","abcdeabcde") w m.ReplaceFirst("xx$1xx") 返回 axxcxxeabcde。 使用$0在替换字符串中包含匹配的全部内容。 w m.ReplaceFirst("xx$0xx") 返回 axxbcdxxeabcde。 4.5.3.操作限制(OperationLimit) 在3.2节中,我们了解了评估正则表达式的两种方法(DFA和NFA)。Caché中使用的正则表达式引擎是一个非确定性的有限自动机(NFA)。因此,对给定输入字符串评估各种正则表达式的持续时间可能会有所不同。[1] 您可以使用%Regex.Matcher对象的OperationLimit属性来限制执行单元的数量(所谓的簇)。执行一个簇的确切持续时间取决于你的环境。通常情况下,一个簇的执行持续时间是非常少的几毫秒。默认情况下,OperationLimit被设置为0(无限制)。 4.6.真实世界的例子:从Perl到Caché的迁移。 本节介绍了从Perl迁移到Caché的过程中与正则表达式有关的部分。Perl 脚本实际上由几十个或多或少复杂的正则表达式组成,这些正则表达式被用来匹配和提取内容。 如果Caché中没有regex功能,迁移项目就会变成一项重大工作。然而,Caché中的regex功能是可用的,Perl脚本中的正则表达式几乎可以在Caché中使用,而不需要任何改变。 下面是Perl脚本的一部分。 将正则表达式从 Perl 移到 Caché 的唯一改动是 /i 修饰符(使 regex 不区分大小写)--这必须从 regex 的结尾移到开头。 在Perl中,捕获缓冲区的内容被复制到特殊的变量中(在上面的Perl代码中是$1和$2)。在Perl项目中,几乎所有的正则表达式都使用了这种机制。为了类似于这种机制,我们在Caché对象脚本中写了一个简单的包装方法。它使用 %Regex.Matcher 对文本字符串评估正则表达式,并将捕获缓冲区的内容以列表的形式返回($lb())。 由此产生的Caché对象脚本代码如下。 如果...RegexMatch( tVCSFullName。 "(?i)[\\\/]([^\\^\/]+)[\\\/]ProjectDB[\\\/](.+)[\\\/]archives[\\\/]", .tCaptureBufferList) { set tDomainPrefix=$zcvt($lg(tCaptureBufferList,1), "U") set tDomain=$zcvt($lg(tCaptureBufferList,2), "U") } … Classmethod RegexMatch(pString as %String, pRegex as %String, Output pCaptureBuffer="") { #Dim tRetVal as %Boolean=0。 set m=##class(%Regex.Matcher).%New(pRegex,pString) set m.Locate() { set tRetVal=1 for i=1:1:m.GroupCount { set pCaptureBuffer=pCaptureBuffer_$lb(m.Group(i)) } } quit tRetVal } 5.参考资料 5.7.一般资料 概括信息和教程。 http://www.regular-expressions.info/engine.html 教程和实例。 http://www.sitepoint.com/demystifying-regex-with-practical-examples/ 几种regex引擎的比较。 https://en.wikipedia.org/wiki/Comparison_of_regular_expression_engines 常用表达式cheat sheet。 https://www.cheatography.com/davechild/cheat-sheets/regular-expressions/pdf/ 书。 Jeffrey E. F. Friedl:"掌握正则表达式"(见http://regex.info/book.html) 5.8.Caché在线文件 关于Caché中正则表达式的用法概述。 http://docs.intersystems.com/latest/csp/docbook/ DocBook.UI.Page.cls?KEY=GCOS_regexp。 $match()的文档。 http://docs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KEY=RCOS_fmatch $locate()的文档。 http://docs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KEY=RCOS_flocate %Regex.Matcher的类引用。 http://docs.intersystems.com/latest/csp/documatic/%25CSP.Documatic.cls?APP=1&LIBRARY=%25SYS&CLASSNAME=%25Regex.Matcher 5.9.ICU 如上所述,InterSystems Caché使用ICU引擎。全面的文档可在网上查阅。 http://userguide.icu-project.org/strings/regexp http://userguide.icu-project.org/strings/regexp#TOC-Regular-Expression-Metacharacters http://userguide.icu-project.org/strings/regexp#TOC-Regular-Expression-Operators http://userguide.icu-project.org/strings/regexp#TOC-Replacement-Text http://userguide.icu-project.org/strings/regexp#TOC-Flag-Options 5.10.工具 有许多工具支持开发人员创建正则表达式--其中一些是免费的,另一些则有商业许可。我个人的选择是RegexBuddy(http://www.regexbuddy.com/)--它提供了一套全面的交互式和可视化功能,可以创建和测试不同风味的正则表达式。 thanks for sharing!
文章
jieliang liu · 一月 7, 2021

精华文章---在 Windows 主机上运行的 Hyper-V Ubuntu 虚拟机中配置 Docker 使用环境

这次我想谈一谈不专门针对 InterSystems IRIS 的东西,不过如果你想使用 Docker,并且你工作环境是安装了 Windows 10 专业版或企业版的 PC 或笔记本电脑,那么我认为这个很重要。 你可能知道,容器技术基本上来自于 Linux 世界,如今在 Linux 主机上发挥出最大潜能。 那些平常使用 Windows 的人会看到,Microsoft 和 Docker 在过去的几年做出了重要的努力,让我们可以在 Windows 系统上以非常简单的方式运行基于 Linux 映像的容器... 但是生产系统不支持这种方式,这是个大问题,如果我们要将持久性数据保留在主机系统中的容器之外,这样做非常不可靠... 这主要是由于 Windows 和 Linux 文件系统之间的巨大差异导致的。 最终,_Docker for Windows 自身使用了一个小型 linux 虚拟机 (_MobiLinux_) 来运行容器... 此操作对于 Windows 用户是透明的,而且效果完美,只要你不需要你的数据库比容器存活的时间更长... 好了,我们进入正题,很多时候为了避免出现问题和简化操作,我们需要一个完整的 Linux 系统,而且如果我们的服务器基于 Windows,那么唯一的方法就是通过虚拟机来实现。 至少在 Windows 中的 WSL2 发布之前是这样,但发布后就是另一回事了,不过它要变得足够强大稳定肯定还需要一些时间。 在本文中,我将一步一步告诉你如何在 Windows 服务器中的 Ubuntu 系统上安装一个能使用 Docker 容器进行工作的环境。 我们开始吧... 1. 启用 Hyper-V 如果尚未启用,则转到添加 `Windows 功能`并启用 Hyper-V。 你将需要重启(图片上的文本是西班牙语,但这就是我当前的区域设置。 如果你不懂堂吉诃德的语言,我希望加上说明能帮助你“解密”😉) ![](/sites/default/files/inline/images/images/image(424).png)   2. 在 Hyper-V 上创建一个 Ubuntu 虚拟机 我认为创建虚拟机 (VM) 没有更简单的方法了。 只需打开 `Hyper-V 管理器`的窗口,然后转到选项快速创建...(屏幕的右上角),使用已经提供的任一 Ubuntu 版本来创建你的虚拟机(你可以下载任何其他 Linux 的 iso 文件,创建不同发行版的虚拟机)。 在我的示例中,我选择了最新的 Ubuntu 版本:19.10。 不过,你在这里看到的一切内容也都适用于 18.04。 在 15 或 20 分钟内,具体取决于你下载映像花费的时间,新的虚拟机就创建完毕并准备就绪。 重要: 保持选项默认交换机 不变。这将保证你可以从主机和虚拟机访问互联网。 ![](/sites/default/files/inline/images/images/vm_ubuntu_network_start_defaultswitch_eth0.jpg) 3. 创建本地子网 使用虚拟机经常遇到的问题之一与网络配置有关... 有时有效,有时无效,或者连接 Wi-Fi 时有效,但连接网线就无效,或者是相反情况;或者如果我在 Windows 主机中建立一个 VPN,那么在虚拟机中就无法访问互联网,或者是虚拟机 (Linux) 和主机 (Windows) 之间的通信中断... 总之,非常让人抓狂! 这使得我在使用笔记本电脑进行开发、小型快速演示或展示时无法信任我的环境,而在这些场景下访问互联网很可能不如确保在主机与虚拟机之间进行可靠通信来得重要。 在 Windows 主机和虚拟机之间共享一个临时本地子网,可以解决这个问题。 要让它们互相通信,使用该子网就可以了。 你只需要为主机和虚拟机分配特定 IP 即可。 通过以下步骤可以很容易实现。 只需转到虚拟交换机管理器...,你可以在 `Hyper-V 管理器`中找到: ![](/sites/default/files/inline/images/images/image(425).png) 然后,转到选项新建虚拟交换机(之后就像虚拟机的新网卡一样): ![](/sites/default/files/inline/images/images/image(461).png) 确保将其定义为_内部网络_,选择我们想要的名称,其他选项保持默认 ![](/sites/default/files/inline/images/images/image(427).png) 现在,如果转到 _`Windows 控制面板 --> 网络和共享中心`_,我们会看到那里已经有了我们刚才创建的交换机: ![](/sites/default/files/inline/images/images/image(429).png)   4. 配置主机和虚拟机共享的本地子网 此时,你可以完成新的本地网络的配置。 为此,将光标放在连接 _Mi Nuevo Conmutador LOCAL_ 上,单击并转到属性,再转到 IPv4 协议,以便分配一个固定 IP 地址: ![](/sites/default/files/inline/images/images/image(449).png)   重要:在此处分配的 IP 将是主机 (Windows) 在该本地子网中的 IP。   5. 将新的本地网络链接并配置到虚拟机 现在回到 `Hyper-V 管理器`。 如果虚拟机正在运行,将其停止。 停止后,转到其配置并添加新的内部虚拟交换机: ![](/sites/default/files/inline/images/images/image(431).png) _(注意:在图片上可以看到另一个交换机 Hyper-V Conmutador INTERNO。 它用于我的另一个子网。 此配置中不需要它)_ 单击“添加”后,你只需选择先前创建的交换机: ![](/sites/default/files/inline/images/images/image(432).png) 好了,完成此操作后,依次单击“应用”、“接受”... 一切就绪!你只需启动并再次登录虚拟机即可完成内部连接的配置。 为此,在虚拟机启动后,单击网络图标(右上角),你将看到两个网络:_eth0_ 和 _eth1_。 _eth1_ 目前显示为断开连接: ![](/sites/default/files/inline/images/images/image(450).png) 进入以太网 (eht1) 的配置,并为此本地子网分配一个固定 IP,例如:_155.100.101.1_,子网掩码:_255.255.255.0_ ![](/sites/default/files/inline/images/images/image(452).png) 这样就完成了。 你的虚拟机标识为 IP 155.100.101.1,与主机共享同一子网。 7. 允许从虚拟机访问 Windows 10 你可能会发现 Windows 10 默认不允许其他服务器连接,对于 Windows 系统来说,你刚刚创建的虚拟机正是一个可能存在危险的外部服务器。因此,必须在防火墙中添加规则,才能从这些虚拟机连接到主机。 如何操作? 非常简单,只需在 `Windows 控制面板`中查找 `Windows Defender 防火墙`,转到高级配置,然后创建一条新的*入站规则*: ![](/sites/default/files/inline/images/images/image(451).png) 你可以设置一个端口或者一个或多个端口范围...(也可以设置针对所有端口的规则)... ![](/sites/default/files/inline/images/images/image(453).png) 我们需要的操作是_允许连接_... ![](/sites/default/files/inline/images/images/image(454).png) 用于_所有网络类型_... ![](/sites/default/files/inline/images/images/image(455).png) 为规则指定名称... ![](/sites/default/files/inline/images/images/image(456).png) **这里很重要**,指定名称后要立即再次打开新创建的规则的属性并*限制应用程序范围*,以便只应用于本地子网内的连接... ![](/sites/default/files/inline/images/images/image(457).png) 8. 就绪。 在新的 Ubuntu 虚拟机中安装 Docker 和任何其他应用程序 完成整个安装过程后,新虚拟机即就绪且为最新,并可以访问互联网等等。 你可以安装所需的应用程序... 至少要安装 Docker,这是一开始就有的想法,如果你需要连接公司网络,还可以安装 VPN 客户端,还有 VS Code、Eclipse+Atelier 等等。 具体来说,要在虚拟机中安装 Docker,可以按照以下说明进行操作: 确保 Docker 运行时正在工作,下载一些测试映像等等... 仅此而已。 这样... _**你已完成所有工作!**_,现在你将能够在 Ubuntu 虚拟机中无限制(除了硬件能力限制)运行容器,你可以从 Windows 10 主机、浏览器或应用程序连接到虚拟机,以及反过来从 Ubuntu 虚拟机连接到 Windows 10 主机。 所有使用你在共享本地子网中设置的 IP 地址的操作都将有效,无论是否建立 VPN,是通过 Wi-fi 适配器还是通过以太网电缆接入互联网。 啊... 最后一个建议。 如果要在 Windows 10 和虚拟机之间交换文件,一个非常有用且简单的选项是使用 [WinSCP](https://winscp.net/eng/download.php)。 它是免费的,而且非常好用。 当然,还有其他配置,但这是我使用的配置,已经证明是比较可靠的。 希望你也觉得它有用。 如果我帮助你避免了令人头疼的问题,这篇文章就值了。 编码愉快!     
文章
姚 鑫 · 四月 27, 2021

第七章 解释SQL查询计划

# 第七章 解释SQL查询计划 本章介绍由`ShowPlan`生成的InterSystems SQL查询访问计划中使用的语言和术语。 # 存储在映射中的表 SQL表存储为一组映射。 每个表都有一个包含表中所有数据的主映射; 表还可以有其他的映射,如索引映射和位图。 每个映射可以被描绘成一个多维全局,其中一些字段的数据在一个或多个下标中,其余字段存储在节点值中。 下标控制要访问的数据。 - 对于主映射,`RowID`或`IDKEY`字段通常用作映射下标。 - 对于索引映射,通常将其他字段用作前导下标,将`RowID/IDKEY`字段用作附加的较低级别的下标。 - 对于位图,可以将位图层视为附加的RowID下标级别。但是,位图只能用于为正整数的`RowID`。 # 发展计划 编译SQL查询会生成一组指令来访问和返回查询指定的数据。 这些指令表示为`. int`例程中的ObjectScript代码。 指令及其执行顺序受到SQL编译器中有关查询中涉及的表的结构和内容的数据的影响。 编译器尝试使用表大小和可用索引等信息,以使指令集尽可能高效。 查询访问计划(`ShowPlan`)是对结果指令集的可读翻译。 查询的作者可以使用这个查询访问计划来查看将如何访问数据。 虽然SQL编译器试图最有效地利用查询指定的数据,但有时查询的作者对存储的数据的某些方面的了解要比编译器清楚得多。 在这种情况下,作者可以利用查询计划修改原始查询,为查询编译器提供更多的信息或更多的指导。 # 阅读计划 `“ShowPlan”`的结果是一系列关于访问和显示查询中指定的数据的处理的语句。 下面提供了关于如何解释`ShowPlan`语句的信息。 ## 访问映射 一个查询计划可以访问多个表。 当访问一个表时,计划可以访问单个映射(索引或主映射)、两个映射(索引映射后面跟着主映射),或者,对于多索引计划,可以访问多个映射。 在通过映射访问数据时,计划指示使用的下标。 它还指示实际的下标值是什么:一个给定值、一组给定值、一个值范围,或该下标在表中显示的所有值。 选择哪一个取决于查询中指定的条件。 显然,访问单个或几个下标值要比访问该下标级别上的所有值快得多。 ## 条件和表达式 当查询运行时,将测试查询指定的各种条件。 除了前面提到的某些限制下标的条件外,`ShowPlan`输出没有显式地指示条件的测试。 尽早测试条件总是最好的。 测试各种条件的最佳地点可以从计划细节中推断出来。 类似地,`ShowPlan`不详细描述表达式和子表达式的计算。 除了简单之外,主要原因是在大多数数据库环境中,表和索引访问构成了处理的更重要方面; 检索表数据的成本占总体查询成本的主要地位,因为磁盘访问速度仍然比CPU处理慢几个数量级。 ## 循环 当访问一个表中的数据时,经常需要迭代地检查多个行。 这样的访问是通过一个循环来指示的。 每一次传递要执行的指令称为循环体。 它们可以通过缩进直观地显示出来。 涉及多个表的数据库访问通常需要循环中的循环。 在这种情况下,每个循环级别都通过与前一个级别相比的进一步缩进表示。 ## 临时文件 ### 定义 查询计划还可能指示需要构建和使用中间临时文件(`TEMP-FILE`)。这是本地数组中的“临时”区域。它用于保存临时结果以用于各种目的,如排序。就像映射一样,临时文件有一个或多个下标,可能还有节点数据。 ### 使用 一些临时文件包含处理单个表的数据。在这种情况下,可以将构建临时文件视为对该表中的数据进行预处理。在读取这样的临时文件之后,可以访问源表的主映射,也可以不访问源表的主映射。在其他情况下,临时文件可能包含处理多个表的结果。在其他情况下,临时文件用于存储分组的聚合值、检查DISTINCT等。 ## 模块 临时文件的构建,以及其他处理,可以委托给一个称为模块的独立工作单元。 每个模块都被命名。 当列出单独的模块时,该计划将指明调用每个模块的位置。 当模块执行结束时,处理将在模块调用之后的下一条语句中继续进行。 ## 发送给处理的查询 对于通过ODBC或JDBC网关连接链接的外部表,该计划显示发送到远程SQL gateway connection的查询文本,以从远程表检索所请求的数据。 对于并行查询处理和分片,该计划显示发送到并行处理或在分片上处理的各种查询。 还将显示用于每个查询的计划。 ## 子查询、连接和联合 给定查询中的一些子查询(和视图)也可以单独处理。 它们的计划在单独的子查询部分中指定。 在计划中没有指明子查询部分被调用的精确位置。 这是因为它们经常作为条件或表达式处理的一部分被调用。 对于指定`OUTER JOIN`的查询,如果没有找到匹配的行,该计划可能指示可能生成的`null`行,以满足外部连接语义的要求。 对于`UNION`,该计划可能指示将来自不同`UNION`子查询的结果行组合到一个单独的模块中,在该模块中可以对这些结果行进行进一步处理。 # 计划分析 在分析给定查询的计划时,应用程序开发人员有时可能会觉得不同的计划会更有效率。 应用程序开发人员有多种方法来影响计划。 首先,计划将受到在包含实际应用程序数据的环境中正确运行调优表的影响。 在类源定义中手动定义一些`Tune Table`通常计算的值——例如表`EXTENTSIZE`、字段`SELECTIVITY`和映射`BlockCount`——也可以用于实现所需的计划。 此外,分析计划可能表明对类定义的某些更改可能导致更有效的计划,例如: ## 添加一个索引 在某些情况下(尽管不总是),使用一个临时文件进行预处理可能意味着向原始表添加一个与临时文件具有相同或类似结构的索引将消除构建临时文件的需要。 从查询计划中删除这个处理步骤显然可以使查询运行得更快,但这必须与更新表时维护索引所需的工作量进行平衡。 ## 添加字段到索引数据 当计划显示正在使用的索引,然后是对主映射的访问时,这意味着将查询中使用的主映射字段添加到索引节点数据可能会为该查询生成更快的计划。 同样,这必须与额外的更新时间以及添加到处理使用该索引的其他查询的额外时间进行平衡,因为索引会更大,因此需要更多的读取时间。 ## 添加连接索引 当计划显示以特定顺序连接两个表时(例如,首先检索`t1`,然后使用连接条件`t1.a=t2.b`连接到`t2`),可能相反的表顺序会产生一个更快的计划。例如,如果`t2`有额外的条件,可以显著限制符合条件的行数。 在这种情况下,在`t1`上添加一个t1索引。 a将允许这样一个连接顺序。
文章
Michael Lei · 七月 18, 2022

翻译文章--Angular 14 新特性介绍

Hi 大家好! 我是 Sergei Sarkisian,在InterSystems 做Angular 前端7年。Angular是非常流行的框架,我们的开发人员、客户和合作伙伴经常选择它来开发他们的应用程序。 我会写一系列的文章,涵盖Angular的不同方面:概念、方法、最佳实践、高级主题等等。这个系列的文章将针对那些已经熟悉Angular的人,不会涉及基本概念。由于我正在构建文章的路线图,我想从突出最近的Angular版本中的一些重要功能开始。 ## 严格类型化表单 这可能是近几年来Angular最受欢迎的功能。有了Angular 14,开发者现在可以在Angular Reactive Forms中使用TypeScript的所有严格类型检查功能。 表单控制Formcontrol 类现在是通用的,并接受它所持有的值的类型。 ```typescript /* Before Angular 14 */ const untypedControl = new FormControl(true); untypedControl.setValue(100); // value is set, no errors // Now const strictlyTypedControl = new FormControl(true); strictlyTypedControl.setValue(100); // you will receive the type checking error message here // Also in Angular 14 const strictlyTypedControl = new FormControl(true); strictlyTypedControl.setValue(100); // you will receive the type checking error message here ``` 正如你所见,第一个和最后一个例子几乎是一样的,但有不同的结果。这是因为在Angular 14中,新的FormControl类从开发者提供的初始值中推断出类型。因此,如果提供了`true`的值,Angular就为这个FormControl设置`boolean | null`的类型。`.reset()`方法需要可置空的值,如果没有提供值,就会置空这些值。 一个旧的、没有定义类型的FormControl类被转换为`UntypedFormControl`(对`UntypedFormGroup`、`UntypedFormArray`和`UntypedFormBuilder`来说也是如此),它实际上是`FormControl`的别名。如果你从以前的Angular版本升级,你所有提到的`FormControl`类将被Angular CLI替换为`UntypedFormControl`类。 Untyped* 类通常用以实现特定目标: 1. 保持应用程序的工作方式与从以前的版本过渡之前完全一样(记住,新的FormControl将从初始值推断出类型) 2. 确保所有的`FormControl`的使用都是有意的。所以你需要自己将任何UntypedFormControl改为`FormControl`。 3. 为了给开发者提供更多的灵活性(我们将在下面介绍这个问题) 记住,如果你的初始值是 "null",那么你将需要明确指定FormControl类型。另外,在TypeScript中有一个错误,如果你的初始值是 "false",也需要这样做。 对于表单组,你也可以定义接口,并把这个接口作为表单组的类型传递。在这种情况下,TypeScript将推断出FormGroup中的所有类型。 ```typescript interface LoginForm { email: FormControl; password?: FormControl; } const login = new FormGroup({ email: new FormControl('', {nonNullable: true}), password: new FormControl('', {nonNullable: true}), }); ``` FormBuilder的方法`.group()`现在有了通用属性,可以接受你预定义的接口,就像上面的例子中我们手动创建了FormGroup。 ```typescript interface LoginForm { email: FormControl; password?: FormControl; } const fb = new FormBuilder(); const login = fb.group({ email: '', password: '', }); ``` 由于我们的接口只有原始的nonNullable类型,它可以用新的 "nonNullable "表单生成器属性来简化(它包含 "NonNullable FormBuilder表单生成器 "类实例,也可以直接创建): ```typescript const fb = new FormBuilder(); const login = fb.nonNullable.group({ email: '', password: '', }); ``` ❗ 请注意,如果你使用nonNullable的FormBuilder或者你在FormControl中设置了nonNullable的选项,那么当你调用`.reset()`方法时,它将使用初始FormControl值作为重置值。 另外,非常重要的一点是,`this.form.value`中的所有属性都将被标记为可选属性。像这样: ```typescript const fb = new FormBuilder(); const login = fb.nonNullable.group({ email: '', password: '', }); // login.value // { // email?: string; // password?: string; // } ``` 发生这种情况是因为当你禁用表单组FormGroup内的任何表单控件FromControl时,这个表单控件的值将从`form.value`中删除。 ```typescript const fb = new FormBuilder(); const login = fb.nonNullable.group({ email: '', password: '', }); login.get('email').disable(); console.log(login.value); // { // password: '' // } ``` 要获得整个表单对象,你应该使用`.getRawValue()`方法:: ```typescript const fb = new FormBuilder(); const login = fb.nonNullable.group({ email: '', password: '', }); login.get('email').disable(); console.log(login.getRawValue()); // { // email: '', // password: '' // } ``` 严格类型化表单的优势: 1. 任何返回FormControl / FormGroup值的属性和方法现在都是严格类型的。例如:`value`,`getRawValue()`,`valueChanges`. 2. 任何改变表单控件值的方法现在都是类型安全的:`setValue()`, `patchValue()`, `updateValue()` 3. 表单控件现在是严格类型化的。它也适用于表单组的`.get()`方法。这也将防止你在编译时发生访问不存在的情况. ### 新的 FormRecord 类 新的 "表单组 "类的缺点是它失去了它的动态性质。一旦定义了,你将不能在运行中添加或删除表单控件。 为了解决这个问题,Angular提出了新的类--`FormRecord'。`FormRecord`实际上与`FormGroup`相同,但它是动态的,所有的表单控件都应该有相同的类型。. ```typescript folders: new FormRecord({ home: new FormControl(true, { nonNullable: true }), music: new FormControl(false, { nonNullable: true }) }); // Add new FormContol to the group this.foldersForm.get('folders').addControl('videos', new FormControl(false, { nonNullable: true })); // This will throw compilation error as control has different type this.foldersForm.get('folders').addControl('books', new FormControl('Some string', { nonNullable: true })); ``` 正如你所看到的,这里有另一个限制 - 所有的FormControls必须是相同的类型。如果你真的需要动态和异质的FormGroup,你应该使用`UntypedFormGroup`类来定义你的表单 ## 无模块的 (独立standalone) 组件 这个特性仍然被标记为实验性的,但它是一个有趣的功能。它允许你定义组件、指令和管道,而不把它们包含在任何模块中。 这个概念还没有完全准备好,但我们已经能够在没有ngModules的情况下建立一个应用程序。 要定义一个独立的组件,你需要使用Component组件/Pipe管道/Directive Decorator指令装饰器中新的`standalone'属性: ```typescript @Component({ selector: 'app-table', standalone: true, templateUrl: './table.component.html' }) export class TableComponent { } ``` 在这种情况下,这个组件不能在任何NgModule中声明。但它可以在NgModules和其他独立组件中被导入。 每个独立的组件/管道/指令现在都有机制可以直接在Decorator装饰器中导入它的依赖项: ```typescript @Component({ standalone: true, selector: 'photo-gallery', // an existing module is imported directly into a standalone component // CommonModule imported directly to use standard Angular directives like *ngIf // the standalone component declared above also imported directly imports: [CommonModule, MatButtonModule, TableComponent], template: ` ... Next Page `, }) export class PhotoGalleryComponent { } ``` 正如我上面提到的,你可以在任何现有的ngModule中导入独立的组件。不再需要导入整个共享模块,我们可以只导入我们真正需要的东西。这也是一个开始使用新的独立组件的好策略: ```typescript @NgModule({ declarations: [AppComponent], imports: [BrowserModule, HttpClientModule, TableComponent], // import our standalone TableComponent bootstrap: [AppComponent] }) export class AppModule {} ``` 你可以通过输入Angular CLI创建独立的组件: ```bash ng g component --standalone user ``` ### Bootstrap 无模块的应用 如果你想摆脱你的应用程序中的所有ngModules,你将需要以不同的方式启动你的应用程序。Angular有新的函数,你需要在main.ts文件中调用这个函数: ```typescript bootstrapApplication(AppComponent); ``` 这个函数的第二个参数将允许你定义你在你的应用程序中需要的提供者。由于大多数提供者通常存在于模块中,Angular(目前)需要为它们使用一个新的`importProvidersFrom`提取函数: ```typescript bootstrapApplication(AppComponent, { providers: [importProvidersFrom(HttpClientModule)] }); ``` ### 懒人加载独立组件的路线: Angular有新的懒人-加载路由函数`loadComponent`,它的存在正是为了加载独立的组件: ```typescript { path: 'home', loadComponent: () => import('./home/home.component').then(m => m.HomeComponent) } ``` `loadChildren`现在不仅允许懒人加载ngModule,而且还允许直接从路由文件中加载子路由: ```typescript { path: 'home', loadChildren: () => import('./home/home.routes').then(c => c.HomeRoutes) } ``` ### 关于本文的一些注意事项 - 独立组件的功能仍处于实验阶段。它在未来会变得更好,因为它将移到Vite builder而不是Webpack,更好的工具,更快的构建时间,更强大的应用架构,更容易的测试等等。但现在这些东西都没有了,所以我们没有得到整个包,但至少我们可以开始用新的Angular范式开发我们的应用程序。 - IDE和Angular工具还没有完全准备好静态地分析新的独立实体。因为你需要在每个独立实体中导入所有的依赖关系,万一你漏掉了什么,编译器也会漏掉它,并在运行时让你失败。这一点会随着时间的推移而得到改善,但现在需要开发人员更加关注导入。 - 目前Angular中没有全局导入(例如在Vue中),所以你需要在每个独立实体中完全导入每个依赖。我希望这个问题能在未来的版本中得到解决,因为在我看来,这个功能的主要目标是减少模板,让事情变得更简单。 # 先写到这,谢谢大家!
文章
姚 鑫 · 十一月 11, 2021

第七十三章 SQL命令 SET OPTION

# 第七十三章 SQL命令 SET OPTION 设置执行选项。 # 大纲 ```java SET OPTION option_keyword = value ``` # 描述 `SET OPTION`语句用于设置执行选项,如编译模式、`SQL`配置设置和控制日期、时间和数字约定的区域设置。 每个`set option`语句只能设置一个关键字选项。 `SET OPTION`支持以下选项: - `AUTO_PARALLEL_THRESHOLD - `COMPILEMODE` - `DEFAULT_SCHEMA`` - `EXACT_DISTINCT` - `LOCK_ESCALATION_THRESHOLD` - `LOCK_TIMEOUT` - `PKEY_IS_IDKEY` - `SUPPORT_DELIMITED_IDENTIFIERS` - `Locale Options (date, time, and numeric conventions)` `SET OPTION`可以在动态SQL(包括`SQL Shell`)和嵌入式SQL中使用。 为了`SQL`兼容性,IRIS会解析其他`SET OPTION`参数(这里没有文档),但不执行任何操作。 因为`SET OPTION`的准备和执行速度很快,而且通常只运行一次,所以`IRIS`不会在`ODBC`、`JDBC`或动态SQL中为`SET OPTION`创建缓存查询。 IRIS支持下列选项: ## `AUTO_PARALLEL_THRESHOLD` `AUTO_PARALLEL_THRESHOLD`选项被设置为一个整数`n`,用于确定当启用自动并行处理时是否应该对查询应用并行处理。 由于与并行处理相关的性能成本,因此需要为并行处理的优势确定一个阈值。 `n`越高,SQL查询使用并行处理执行的可能性就越低。 默认为`3200`。 这是一个系统范围的设置。 值n大致对应于所访问的映射中发生并行处理所需的最小元组数量。 当自动并行被禁用时,`AUTO_PARALLEL_THRESHOLD`选项没有作用。 也可以使用`$SYSTEM.SQL.Util.SetOption()`方法`AutoParallelThreshold`选项设置该选项。 ## COMPILEMODE `COMPILEMODE`选项将当前名称空间的编译模式设置为`DEFERRED`、`IMMEDIATE`、`INSTALL`或`NOCHECK`。 默认为`IMMEDIATE`。 从`DEFERRED`编译模式更改为`IMMEDIATE`编译模式会导致`DEFERRED compile Queue`中的任何类立即被编译。 如果所有类编译都成功,IRIS将`SQLCODE`设置为0。 如果有任何错误,`SQLCODE`设置为`-400`。 类编译错误记录在`^mtemp2 ("Deferred Compile Mode","Error")`中。 如果将`SQLCODE`设置为`-400`,则应该查看此全局结构以获得更精确的错误消息。 `INSTALL`编译模式类似于`DEFERRED`编译模式,但它应该只用于表中没有数据的`DDL`安装。 `NOCHECK`编译模式与`IMMEDIATE`编译模式类似,只是在编译时忽略了以下约束:如果一个表被删除, IRIS不检查引用被删除表的其他表中的外键约束。 如果添加了外键约束, IRIS不会检查现有数据以确保它对这个外键有效。 如果添加了`NOT NULL`约束, IRIS不会检查现有数据是否为`NULL`,也不会指定字段的默认值。 如果删除了`UNIQUE`或`Primary Key`约束 IRIS不会检查该表或其他表中的外键是否引用了被删除的键。 也可以使用`$SYSTEM.SQL.Util.SetOption()`方法`CompileMode`选项设置该选项。 ## DEFAULT_SCHEMA `DEFAULT_SCHEMA`选项为所有名称空间设置系统范围的默认模式。 在显式更改之前,此默认值将保持有效。 默认模式名用于为所有未限定的表、视图或存储过程名提供模式名。 可以指定一个文字模式名或指定`_CURRENT_USER`。 如果指定`_CURRENT_USER`作为默认模式名, IRIS会将当前登录进程的用户名作为默认模式名。 ## EXACT_DISTINCT `EXACT_DISTINCT`布尔值选项指定是否在系统范围内使用`DISTINCT`处理`(TRUE)`或`Fast DISTINCT`处理`(FALSE)`。 系统范围的默认值是使用`Fast Distinct`处理。 当`EXACT_DISTINCT=TRUE`时,`GROUP BY`和`DISTINCT`查询生成原始值。 当`EXACT_DISTINCT=FALSE`时,启用快速`Distinct`,通过更好地使用索引(如果有索引),使涉及`Distinct`或`GROUP BY`子句的SQL查询更有效地运行。 但是,这些查询返回的值以与存储在索引中的相同的方式进行排序。 这意味着此类查询的结果可能都是大写的。 这可能对区分大小写的应用程序有影响。 这个选项也可以使用`$SYSTEM.SQL.Util.SetOption()`方法`FastDistinct boolean`选项来设置。 ## `LOCK_ESCALATION_THRESHOLD` `LOCK_ESCALATION_THRESHOLD`选项被设置为一个整数`n`,用于确定何时将行锁定升级为表锁定。 默认值是`1000`。 值`n`是单个事务中单个表的插入、更新或删除次数,当到达时将触发表级锁。 这是针对所有名称空间的系统范围设置。 例如,如果锁阈值为`1000`,并且进程启动一个事务,然后插入`2000`行,那么在插入第`1001`行之后,进程将尝试获取表级锁,而不是继续锁定各个行。 这有助于防止锁表变得太满。 这个选项也可以使用`$SYSTEM.SQL.Util.SetOption()`方法`LockThreshold`选项来设置。 ## LOCK_TIMEOUT `LOCK_TIMEOUT`数值选项允许为当前进程设置默认的锁定超时。 `LOCK_TIMEOUT`值是SQL执行期间试图建立锁时等待的秒数。 当锁定冲突阻止当前进程对`lock`、`INSERT`、`UPDATE`、`DELETE`或`SELECT`操作立即锁定一条记录、表或其他实体时,使用此锁定超时。 `SQL`继续尝试建立锁,直到超时超时,这时将生成`SQLCODE -110`或`-114`错误。 可用的值是正整数和零。 超时设置是每个进程的。 可以使用`$SYSTEM.SQL.Util.GetOption(“ProcessLockTimeout”)`方法确定当前进程的锁定超时设置。 如果没有为当前进程设置锁定超时,则默认为当前系统范围的锁定超时设置。 如果您的`ODBC`连接断开并重新连接,重新连接的进程将使用当前系统范围的锁定超时设置。 系统范围的锁定超时默认为10秒。 ## PKEY_IS_IDKEY `PKEY_IS_IDKEY boolean`选项指定主键是否也是系统范围内的ID键。 取值为`TRUE`、`FALSE`。 如果为`TRUE`,且该字段不包含数据,则将主键创建为`ID`键。 也就是说,表的主键也成为了类定义中的`IDKey`索引。 如果字段不包含数据,则没有定义`IDKey`索引。 如果将主键定义为`IDKey`索引,则数据访问将更加有效,但主键值一旦设置,就永远不能修改。 一旦设置,就不能更改分配给主键的值,也不能将其他键指定为主键。 使用此选项还将更改主键排序规则的默认值; 主键字符串值默认为`EXACT`排序规则。 如果为`FALSE`,则主键和`ID`键被定义为独立的,效率较低。 但是,主键值是可修改的,主键字符串值默认为当前排序规则类型`default`,默认为`SQLUPPER`。 要设置`PKEY_IS_IDKEY`选项,必须具有`%Admin_Manage:USE`权限。 否则,将收到一个`SQLCODE -99`错误(特权违反)。 一旦设置,该选项将在系统范围内对所有进程生效。 该选项的系统范围默认值也可以通过以下方式设置: - `$SYSTEM.SQL.Util.SetOption()`方法配置选项`DDLPKeyNotIDKey`。 要确定当前设置,调用`$SYSTEM.SQL.CurrentSettings()`,它显示通过DDL创建的是主键而不是ID键; 默认值是1。 - 管理门户配置设置。 选择系统管理,配置,SQL和对象设置,SQL。 查看或修改通过DDL创建的表的“将主键定义为ID键”的当前设置。 `PKEY_IS_IDKEY`设置保持有效,直到通过另一个SET OPTION `PKEY_IS_IDKEY`重置或直到 IRIS `Configuration`被重新激活,将该参数重置为IRIS System `Configuration`设置。 ## SUPPORT_DELIMITED_IDENTIFIERS 默认情况下,系统范围内支持分隔标识符。 `SUPPORT_DELIMITED_IDENTIFIERS`布尔选项允许您更改系统范围内对分隔标识符的支持。 取值为`TRUE`、`FALSE`。 如果为`TRUE`,用双引号分隔的字符串被认为是SQL语句中的标识符。 如果为`FALSE`,由双引号分隔的字符串被认为是SQL语句中的字符串字面值。 要设置`SUPPORT_DELIMITED_IDENTIFIERS`选项,必须具有`%Admin_Manage:USE`权限。 否则,将收到一个`SQLCODE -99`错误(特权违反)。 一旦设置,该选项将在系统范围内对所有进程生效。 `SUPPORT_DELIMITED_IDENTIFIERS`设置将保持有效,直到通过另一个设置选项`SUPPORT_DELIMITED_IDENTIFIERS`进行重置,或者直到由`$SYSTEM.SQL.Util.SetOption()方法delimitedifiers`选项在系统范围内进行更改。 ## Locale Options 区域设置选项是关键字选项,用于为当前进程的日期、时间和数字约定设置IRIS区域设置。 可选关键字有`AM、DATE_FORMAT、DATE_MAXIMUM、DATE_MINIMUM、DATE_SEPARATOR、DECIMAL_SEPARATOR、MIDNIGHT、MINUS_SIGN、MONTH_ABBR、MONTH_NAME、NOON、NUMERIC_GROUP_SEPARATOR、NUMERIC_GROUP_SIZE、PM、PLUS_SIGN、TIME_FORMAT、TIME_PRECISION、TIME_SEPARATOR、WEEKDAY_ABBR、WEEKDAY_NAME、YEAR_OPTION`。 所有这些选项都可以设置为文字,并且都采用默认值(美式英语惯例)。 `TIME_PRECISION`选项是可配置的(参见下面)。 如果将这些选项中的任何一个设置为无效值,InterSystems IRIS将发出`SQLCODE -129`错误(`set OPTION`区域设置属性的非法值)。 Date/Time Option Keyword| Description ---|--- `AM` |`String`. 默认 `'AM'` `DATE_FORMAT` |`Integer`. 默认值为`1`。取值范围为`0 ~ 15`。 `DATE_MAXIMUM`| `Integer`. 默认为`2980013(12/31/9999)`。可以设置为更早的日期,但不能设置为更晚的日期。 `DATE_MINIMUM`| `Positive Integer`. 默认为0`(12/31/1840)`。可以设置为较晚的日期,但不能设置为较早的日期。 `DATE_SEPARATOR`| Character. Default is '/' `DECIMAL_SEPARATOR`| Character. Default is '.' `MIDNIGHT`| String. Default is 'MIDNIGHT' `MINUS_SIGN`| Character. Default is '-' `MONTH_ABBR`| String. Default is ' Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec'. (注意,该字符串以空格字符开始,这是默认分隔符.) `MONTH_NAME`| String. Default is ' January February March April May June ... November December'. 注意,该字符串以空格字符开始,这是默认分隔符.) `NOON`| String. Default is 'NOON' `NUMERIC_GROUP_SEPARATOR`| Character. Default is ',' `NUMERIC_GROUP_SIZE` |Integer. Default is 3.PM String. Default is 'PM' `PLUS_SIGN`| Character. Default is '+' `TIME_FORMAT`| Integer. Default is 1. 取值范围为1 ~ 4。 `TIME_PRECISION`| Integer from 0 through 9 (inclusive). Default is 0. 小数秒的位数。 `TIME_SEPARATOR`| Character. Default is ':' `WEEKDAY_ABBR`| String. Default is ' Sun Mon Tue Wed Thu Fri Sat'. (注意,该字符串以空格字符开始,这是默认分隔符.) `WEEKDAY_NAME`| String. Default is ' Sunday Monday Tuesday Wednesday Thursday Friday Saturday'. (注意,该字符串以空格字符开始,这是默认分隔符.) `YEAR_OPTION`| Integer. Default is 0. 取值范围为0 ~ 6。有关表示2位数和4位数年份的这些方法的解释,见ObjectScript $ZDATE函数。 要在系统范围内配置`TIME_PRECISION`,请进入管理门户,选择“系统管理”、“配置”、“SQL”和“对象设置”、“SQL”。 查看和编辑`GETDATE()`、`CURRENT_TIME`和`CURRENT_TIMESTAMP`的默认时间精度的当前设置。 它指定小数秒的精确位数。 默认值是`0`。 允许的值的范围是`0`到`9`位精度。 小数秒中有意义的数字的实际数目与平台有关。