検索

記事
· 2025年2月28日 13m read

Proposal for ObjectScript naming conventions and coding guidelines

Introduction

What's in a name? That which we call a rose
By any other name would smell as sweet

William Shakespeare, "Romeo & Juliet"

In this article, we will describe a set of naming conventions for ObjectScript code.

Code naming conventions exist for several important reasons:

  1. Readability: Consistent names improve code clarity and comprehension. Following naming conventions makes it easier to identify and remember components.
  2. Maintainability: Properly named code elements ease the maintenance and updating of code and configuration items, reducing confusion and errors.
  3. Collaboration: in a team setting, having a common naming convention ensures that everyone is on the same page. It promotes smoother collaboration and reduces the likelihood of miscommunication.
  4. Debugging: clear and consistent names can help in quickly identifying and fixing issues. Descriptive names can indicate the purpose and scope of a code element, aiding in the debugging process.
  5. Documentation: following naming conventions can simplify the process of creating and maintaining documentation. Names that reflect their function or purpose make it easier to explain and understand the code. 

As IRIS developers and administrators, we are naming various types of objects, some of which have numerous instances. For example, interoperability production with dozens of configurations items and data transformation classes is not at all uncommon in real-world scenarios such as healthcare patient and appointment data routing.

The aim of this article is to compile bits from various sources of information in a consistent proposal for robust naming conventions. As the many names defined are used in ObjectScript code, it also describes some coding guidelines.

 

Compiler items

In this part, we’ll describe guidelines for naming:

  • packages
  • classes, includes and routines
  • class members: parameters, properties, methods and XData blocks
  • local variables and method parameters 

Packages

 Package names use only lower-case letters and decimal digits. The first character must be a lower-case letter.

 The root package has at least one component, and it identifies the source entity, organization or group.

 Examples:

  • dc
  • myorg
  • acme 

Packages under root denote the purpose of the classes. They may have subpackages denoting a finer purpose or domain, and use lower case letters, except for application domains that are spelled in upper case letters. 

The following root subpackage names are reserved 

Compiler item purpose

Package

CSP page

csp

Data models and transfer objects (classes _not_ extending %Persistent)

model

Data type

type

Include resources: macros, constants, …

inc

Interoperability component - business adapter

interop.ba

Interoperability component - business service

interop.bs

Interoperability component - business process

interop.bp

Interoperability component - business operation

interop.bo

Interoperability component - message

interop.msg

Interoperability component - data transform

interop.dt

Persistent entity (classes extending %Persistent)

entity

REST API

api

Routines

rou

Scheduled tasks

tas

Service

service

SOAP Web Service

ws

Utility class

lib

Classes and other elements

Class names use upper camel case, must start with an uppercase letter and use letters and decimal digits. Avoid starting with "%".

Routine names should be all uppercase. Avoid starting with "%".

Item purpose

 Class name pattern

Examples of fully qualified class names

CSP page

<name>

myorg.csp.app.LogonForm

Data models and transfer objects (classes _not_ extending %Persistent)

<name>

myhospital.model.Patient

Data types

<name>

acme.type.FusionReactorType

myhospital.type.patient.MRN

Include resources: macros, constants, …

<name>

myorg.inc.Errors

Interoperability component - inbound business adapter

<name>InboundAdapter

myorg.interop.ba.hl7.UNCPathFileInboundAdapter

Interoperability component - outbound business adapter

<name>OutboundAdapter

acme.interop.ba.CustomTCPOutboundAdapter

Interoperability component - duplex business adapter

<name>DuplexAdapter

myorg.interop.ba.CustomTCPDuplexAdapter

Interoperability component - business service

<name>Service

myhosp.interop.bs.FileService

Interoperability component - business process

<name>Process

myhosp.interop.bp.AppointmentCancelProcess

Interoperability component - business operation

<name>Operation

myhosp.interop.bo.WISH.PatientOperation

Interoperability component - request message (extends Ens.Request)

<verb>[<resource>]Request

myorg.interop.msg.patient.GetRequest

myorg.interop.msg.patient.GetEncountersRequest
myorg.interop.msg.patient.Response

Interoperability component - response message (extends Ens.Response)

[<name>]Response

myorg.interop.msg.patient.Response

myhospital.interop.msg.invoicing.BillInsuranceResponse

Interoperability component - other message (extends Ens.MessageBody)

<name>

myhospital.interop.msg.patient.PatientUpdatedEvent

myorg.interop.msg.document.Container

Interoperability component - data transform

[<source-applicaton>]<source-format>To[<target-application>]<target-format>

myorg.interop.dt.ULTRAGENDASIUToAppointmentUpdate

Persistent entities (classes extending %Persistent)

<name>

myorg.entity.Document

REST APIs - generated classes (lowercase letters)

impl
spec

disp

myhospital.api.terminology.impl
myhospital.api.terminology.spec

myhospital.api.terminology.disp

Routines

<name>

myhosp.rou.PHUTL001

Scheduled tasks

<name>Task

myhospital.task.CancelAppointmentsTask

Service

<name>Service

service

SOAP Web Services

<name>WS

acme.ws.accounting.SupplierWS

Utility classes

<name>

myorg.lib.xml.Utils

Class members

Class members names use  upper camel case, must start with an uppercase letter (avoid using '%'), and use letters and decimal digits.

Member

Convention

Example

Parameter

Upper camel case or all uppercase

MRNCODESYSTEM
DocTypeCategory

Properties

Upper camel case

BirthDate
DeathDate

Method

Upper camel case. Favor a <verb><object> pattern

FetchPatient
ProcessMessage
UpdatePractitionerRoles

Xdata

Upper camel case

HL7Mappings

Local variables & method parameters

Local variable names and method parameters use lower camel case and start with a lower-case letter.

Some examples: request, response, patientId, mrn

Instance (i%..., r%...) and process (%...) variables follow the same convention.

 

Coding guidelines

Block syntax

A block statement, or compound statement, lets you group any number of statements (including 0) into one statement.

ObjectScript currently supports two syntaxes for blocks:

  • Curly brace block syntax
  • Dot block syntax

Curly brace block syntax

It is similar to that in C, Java, C#, … making the following short example look very familiar to most programmers:

if a=0 {
   write "foo",!
   write "bar",!
}

Dot block syntax 

This is the original MUMPS block syntax. It is supported for backward compatibility with (very) old code. Its use is strongly discouraged, as it can get quite confusing, especially when combined with the short version of commands and the lack of reserved words, as in the following (intentionally a little mischievous) example: 

j=1:1:d d
 . r i
 . i '$test b
 . i i'="" d
 .. s d=$p(l," ",1) 41)
 .. s w=$p(l," ",2)
 .. w d,?10,$e(^title(d),1,80),!

Post-conditionals

This is an implementation in ObjectScript of the concept of guarded command, as defined by Dijkstra (1975).

It is a conditionally executed statement, where a boolean expression "guards" the execution of the statement. 

<command>:<condition> <command arguments>

 It is functionally equilavent to

if <condition> <command> <command arguments>

Although the concept is well defined, the syntax is not common, so when should it be used instead of an if statement?

  • execution flow control: quit, continue, throw
  • default value assignment: set

some examples:

quit on error, continue on condition

quit:$$$ISERR(sc)

#Dim a as %Integer
while a > 0 {

continue:a=5

}

throw on condition

#Dim obj as %RegisteredObject
throw:'$isobject(obj) ##class(%Exception.General).%New("object not found")
 

Assign default value

#Dim obj as Foo

set:'$isobject(obj) obj = ##class(Foo).%New()

Use return instead of quit for return values

In new code, use return instead of quit, as quit can be used both for exiting current execution context and return a value.

'quit' has two different meanings :

  • when use with no argument, it exits current execution context (e.g. loop)
  • when use with an argument, it exits current routine/method and returns value

 

'return' is an addition to ObjectScript meant to improve code readability, as it implements only the second meaning.

Command arguments

The use of a comma-separated list of command arguments should be avoided, as for some commands, it gets confusing.

For example,

if a=0,b=1 {
...
}

 

It is much less readable (to the 'modern' reader) than

if (a=0)&&(b=1) {
...
}
 

Ternary operator - expressional 'if' 

The $select function can be used as ternary if operator:

$select(<boolean expression>:<true value>,1:<false value>)

 

 Switch/case 

Either

  • $case() when switch intent is to select a value
  • if elseif elseif … when switch intent is to select behaviour


Command keywords

Command keywords are not case-sensitive, and most commands come in two variants, fully named and shorthand.

  • Favor the use of full command keywords, except for the most common ones like 'set' and 'do'
  • Use all lowercase for command keywords
  • Avoid using legacy goto <label> command for flow control

 

Function names

As commands, function names are not case-sensitive and most functions come in two variants, fully named and shorthand.

  • Favor the use of full function names instead of shorthand, e.g. use $extract() instead of $e
  • Use all lowercase for intrinsic function names
  • Use upper camel case for extrinsic function names

Method parameters and return values

  • group optional parameters at the end
  • if the method is a function that returns a data type or OREF, and returns a %Status, the %Status is returned as the last parameter
 

Interoperability production items

Interoperability productions can easily count a sizeable of business hosts. A consistent naming scheme helps a lot with readability across the various actors reading the names: developers, administrators and support staff.

Business services

Propose

Name pattern

Examples

Receive messages from an application

<format>From<application>

SIUFromULTRAGENDA

Receive deferred responses

<application>Response

DOCSHIFTERResponse

REST or SOAP API

<name>Service

TerminologyService

Business processes

Purpose

Name pattern

Examples

Process requests

Orchestration

<name>Process

AppointmentCancelProcess

DocumentProcess

Route messages

<format>Router

ADTRouter
SIURouter

Business operations

Purpose

Name pattern

Examples

Send messages to an application or application component. Optionally use suffixes to denote application subcomponents or environments

<format>To<application>[_<component>]

SIUToWISH
ADTToSOFTALMO_PROD
ADTToSOFTALMO_TEST

HL7ToArchive

Query external system and return responses

<name>Operation

ULTRAGENDAAPIOperation

Duplex operation

<name>Duplex

 

Business duplexes

Classes extending Ens.BusinessDuplex are use

<name>Duplex

 

 

Some examples

Class method

/// <p>Purges all message bodies associated with sessionId and if purgeHeaders is set, purge headers too.</p>
/// <p><b>purged</b> returns the total count of items successfully purged, and the count by class name in the first subscript.</p>
/// <p>Stops and returns error status if any error occurs during purge.</p>
ClassMethod PurgeSessionMessageBodies(sessionId As %Integer, Output purged As %Integer, purgeHeaders As %Boolean = 0, noLock As %Boolean = 1) As %Status
{
  #Dim sc as %Status
  #Dim ex as %Exception.AbstractException
  #Dim stmt as %SQL.Statement
  #Dim rs as %SQL.StatementResult
  s sc = $$$OK
  try {    
    s stmt = ##class(%SQL.Statement).%New()
    s rs = stmt.%ExecDirect(,"select"_$select(noLock:" %NOLOCK",1:"")_" ID as HeaderId,MessageBodyClassName as BodyClass,MessageBodyId as BodyId from Ens.MessageHeader where SessionId=?",sessionId)
    while rs.%Next() {
      if ($length(rs.BodyClass) > 1) && $$$ISOK($classmethod(rs.BodyClass,"%DeleteId",rs.BodyId)) {
        d $increment(purged)
        d $increment(purged(rs.BodyClass))
      }
      if purgeHeaders {
        $$$TOE(sc,##class(Ens.MessageHeader).%DeleteId(rs.HeaderId))
        d $increment(purged)
        d $increment(purged("Ens.MessageHeader"))     
      }
    }
  }
  catch (ex) {
    s sc = ex.AsStatus()
  }
  return sc
}

Outbound adapter

/// HL7 file outbound adapter, using <class>ks.lib.file.ba.UNCOutboundAdapter</class>
/// This adapter also adds expression parsing to <method>CreateFilename</method> : see <method>ks.lib.hl7.Utils.ParseExpressions</method>
Class ks.interop.hl7.ba.FileOutboundAdapter Extends ks.interop.file.ba.UNCOutboundAdapter
{
// keeping parameter names as in superclass for clarity
Method CreateFilename(ByRef pFileName As %String, ByRef pSpec As %String, ByRef pIsVMS As %Boolean, ByRef pDirectory As %String, ByRef pLocal As %Boolean) As %String
{
	#Dim sc as %Status
	#Dim ex as %Exception.AbstractException
	s sc = $$$OK
	try {
  	  if $isobject(..BusinessHost.%RequestHeader) &&
	     $classmethod(..BusinessHost.%RequestHeader.MessageBodyClassName,"%Extends","EnsLib.HL7.Message") {
	    s msg = ##class(EnsLib.HL7.Message).%OpenId(..BusinessHost.%RequestHeader.MessageBodyId)
	    if $isobject(msg) {		  
	  	  s pSpec = ##class(ks.lib.hl7.Utils).ParseExpressions(msg,pSpec,.sc)
		  $$$TRACE("spec after HL7 expressions parsing : "_pSpec)
	    }
	  } 
	}
	catch (ex) {
	  // do nothing, fall back to ##super
	}	
	return ##super(.pFileName,.pSpec,.pIsVMS,.pDirectory,.pLocal)
}

}
/// A string datatype definition which extends <class>%Library.String</class> with additional regex pattern validation. <br />
Class ks.lib.type.RegExString Extends %String
{

/// Set PATTERN to empty and final, as it is not relevant on
/// this type, but is inherited from <class>%Library.String</class>
Parameter PATTERN [ Final ];
/// Set VALUELIST to empty and final, as it is not relevant on
/// this type, but is inherited from <class>%Library.String</class>
Parameter VALUELIST [ Final ];
/// Set DISPLAYLIST to empty and final, as it is not relevant on
/// this type, but is inherited from <class>%Library.String</class>
Parameter DISPLAYLIST [ Final ];
/// Set a valid regex pattern for value validation
Parameter REGEX As STRING;
/// The XMLPATTERN to regex by default. Can be overridden.
Parameter XMLPATTERN = {..#REGEX};
ClassMethod IsValid(%val As %Library.RawString) As %Status [ ServerOnly = 0 ]
{
    #Dim sc as %Status = $$$OK
    #Dim ex as %Exception.AbstractException
    try {
         $$$TOE(sc,##class(%String).IsValid(%val))
         if (..#REGEX '= "") {
            if '$match(%val, ..#REGEX) {
                s sc = $$$ERROR($$$DTPattern, %val, ..#REGEX)
            }
         }       
    }
    catch (ex) {
      s sc = ex.AsStatus()
    }
    q sc
}

}
Class myhosp.interop.dt.ADTNToFHIR Extends Ens.DataTransform
{

Parameter TARGETFHIRVERSION = "R4";
ClassMethod Transform(source As EnsLib.HL7.Message, ByRef target As Ens.StreamContainer, aux) As %Status
{
    #Dim sc as %Status = $$$OK
    #Dim ex as %Exception.AbstractException
    #Dim sda as %Stream.TmpCharacter
    #Dim fhir as HS.FHIR.DTL.Util.API.Transform.SDA3ToFHIR
    #Dim schema as HS.FHIRServer.Schema
    #Dim stream as %Stream.Object
    #Dim patientId as myhosp.type.WISH.MRN
    #Dim encounterId as myhosp.type.WISH.NADM
    #Dim exportType as %String
    try {
        s schema = ##class(HS.FHIRServer.Schema).LoadSchema(..#TARGETFHIRVERSION)
        if '$isobject(schema) throw ##class(%Exception.General).%New("FHIR Schema "_..#TARGETFHIRVERSION_" not found")

        s patientId = source.GetValueAt("PID:3.1")
        s encounterId = source.GetValueAt("PV1:19.1")

        $$$TOE(sc,##class(HS.Gateway.HL7.HL7ToSDA3).GetSDA(source,.sda,0))        
        s fhir = ##class(HS.FHIR.DTL.Util.API.Transform.SDA3ToFHIR).TransformStream(sda,"HS.SDA3.Container",..#TARGETFHIRVERSION,patientId,encounterId)  
        s stream = ##class(%Stream.GlobalCharacter).%New() 
        s stream.%Location = "^MyHosp.FHIR.Stream"
        s exportType = $select($data(aux):$select($isobject(aux):$select(aux.RuleActionUserData="":aux.RuleUserData,1:aux.RuleActionUserData),1:aux),1:"")
        if exportType="JSON" {
          s str = fhir.bundle.%ToJSON(stream)
        } else {
          d ##class(HS.FHIRServer.Util.JSONToXML).JSONToXML(fhir.bundle, .stream, schema)
        }
        s target = ##class(Ens.StreamContainer).%New(stream)
    } catch (ex) {
      s sc = ex.AsStatus()
    }
    return sc
}

}
15 Comments
ディスカッション (15)7
続けるにはログインするか新規登録を行ってください
記事
· 2025年2月28日 1m read

Évitez d'utiliser un package nommé « code » avec des méthodes Python intégrées utilisant [Language = python]

Bonjour,

Comme il m'a fallu un certain temps pour comprendre d'où venait le problème, je voudrais partager cette expérience, afin que vous ne tombiez pas dans le même piège.

Je viens de remarquer que si vous nommez votre package "code" (tout en minuscules), dans une classe utilisant du python intégré en utilisant [Language = python], vous aurez l'erreur suivante :

 <THROW> *%Exception.PythonException <PYTHON EXCEPTION> 246 <class 'ModuleNotFoundError'>: No module named 'code.basics'; 'code' is not a package

Class code.basics Extends %RegisteredObject
{

ClassMethod Welcome() As %Status [ Language = python ]
{
print('Welcome!')
return True
}
}
IRISAPP>w ##class(code.basics).Welcome()

W ##CLASS(code.basics).Welcome()
^
<THROW> *%Exception.PythonException <PYTHON EXCEPTION> 246 <class 'ModuleNotFoundError'>: No module named 'code.basics'; 'code' is not a package

Cela fonctionne bien avec "Code" :

Class Code.basics Extends %RegisteredObject
{

ClassMethod Welcome() As %Status [ Language = python ]
{
print('Welcome!')
return True
}
}
IRISAPP>w ##class(Code.basics).Welcome()
Welcome!
1
ディスカッション (0)1
続けるにはログインするか新規登録を行ってください
記事
· 2025年2月28日 1m read

Avoid to use package named "code" with embedded python methods using [Language = python]

Hello,

as it took me some time to figure out what's wrong, I would like to share this experience, so that you do not fall into the same trap.

I've just noticed that if you name your package "code" (all lowercase), in a class using some embedded python using [Language = python], you'll face the <THROW> *%Exception.PythonException <PYTHON EXCEPTION> 246 <class 'ModuleNotFoundError'>: No module named 'code.basics'; 'code' is not a package

Class code.basics Extends %RegisteredObject
{

ClassMethod Welcome() As %Status [ Language = python ]
{
print('Welcome!')
return True
}
}
IRISAPP>w ##class(code.basics).Welcome()

W ##CLASS(code.basics).Welcome()
^
<THROW> *%Exception.PythonException <PYTHON EXCEPTION> 246 <class 'ModuleNotFoundError'>: No module named 'code.basics'; 'code' is not a package

It works well with "Code" :

Class Code.basics Extends %RegisteredObject
{

ClassMethod Welcome() As %Status [ Language = python ]
{
print('Welcome!')
return True
}
}
IRISAPP>w ##class(Code.basics).Welcome()
Welcome!
1
2 Comments
ディスカッション (2)3
続けるにはログインするか新規登録を行ってください
記事
· 2025年2月28日 7m read

Using Dynamic & Embedded SQL with InterSystems IRIS

Hi Community, 

In this article, we will explore the concepts of Dynamic SQL and Embedded SQL within the context of InterSystems IRIS, provide practical examples, and examine their differences to help you understand how to leverage them in your applications.

InterSystems SQL provides a full set of standard relational features, including the ability to define table schema, execute queries, and define and execute stored procedures. You can execute InterSystems SQL interactively from the Management Portal or programmatically using a SQL shell interface. Embedded SQL enables you to embed SQL statements in your ObjectScript code, while Dynamic SQL enables you to execute dynamic SQL statements from ObjectScript at runtime. While static SQL queries offer predictable performance, dynamic and embedded SQL offer flexibility and integration, respectively.

Dynamic SQL

Dynamic SQL refers to SQL statements that are constructed and executed at runtime, as opposed to static SQL, which is predefined and embedded directly in the application code. Dynamic SQL is particularly useful when the structure of a query is not known in advance or needs to be dynamically adjusted based on user input or application logic.

In InterSystems IRIS, Dynamic SQL is implemented through the %SQL.Statement class, which provides methods for preparing and executing SQL statements dynamically.

Key Benefits of Dynamic SQL

  1. Flexibility: Dynamic SQL allows you to build queries programmatically, making it ideal for applications with complex or changing requirements.
  2. Adaptability: You can modify queries based on runtime conditions, such as user input or application state.
  3. Ad-Hoc Queries: If the application needs to generate custom queries based on user input, Dynamic SQL allows the construction of these queries at runtime.
  4. Complex Joins and Conditions: In scenarios where the number of joins or conditions can change based on data, Dynamic SQL enables the construction of complex queries.


Practical Examples

1- Dynamic Table Creation: Building Database Schemas on the Fly

This example demonstrates how to dynamically create a table at runtime using InterSystems Dynamic SQL, enabling flexible and adaptive database schema management.

ClassMethod CreateDynamicTable(tableName As %String, columns As %String) As %Status
{
    // Construct sql text
    Set sql = "CREATE TABLE " _ tableName _ " (" _ columns _ ")"
    //Create an instance of %SQL.Statement
    Set statement = ##class(%SQL.Statement).%New()
    //Prepare the query
    Set status = statement.%Prepare(sql)
    If $$$ISERR(status) {
        Quit status
    }
    //Execute the query
    Set result = statement.%Execute()
    //Check for errors
    If result.%SQLCODE = 0 {
        Write "Table created successfully!", !
    } Else {
        Write "Error: ", result.%SQLCODE, " ", result.%SQLMSG, !
    }
    Quit $$$OK
}

Invoke Method

USER>do ##class(dc.DESql).CreateDynamicTable("Books","BookID NUMBER NOT NULL,Title VARCHAR(100),Author VARCHAR(300),PublicationYear  NUMBER NULL, AvailableFlag  BIT")

Output


2- Dynamic Table Search: Querying Data with User-Defined Filters

This example illustrates how to perform a dynamic table search based on user-defined criteria, enabling flexible and adaptable querying.

ClassMethod DynamicSearchPerson(name As %String = "", age As %Integer = "") As %Status
{
    // Create an instance of %SQL.Statement
    set stmt = ##class(%SQL.Statement).%New()

    // Base query
    set query = "SELECT ID, Name, Age, DOB FROM Sample.Person"
    // Add conditions based on input parameters
    if name '= "" {
        set query = query _ " WHERE Name %STARTSWITH ?"
    }
    if (age '= "") && (name '= "") {
        set query = query _ " AND Age = ?"
    }
    if (age '= "") && (name = "") {
        set query = query _ " WHERE Age = ?"
    }
    
    // Prepare the query
    set status = stmt.%Prepare(query)
    if $$$ISERR(status) {
        do $System.Status.DisplayError(status)
        quit status
    }
   
    // Execute the query with parameters
    if (age '= "") && (name '= "") {
        set rset = stmt.%Execute(name, age)
    }
    if (age '= "") && (name = "") {
        set rset = stmt.%Execute(age)
    }
    if (age = "") && (name '= "") {
        set rset = stmt.%Execute(name)
    }

    // Display results
    while rset.%Next() {
        write "ID: ", rset.ID, " Name: ", rset.Name, " Age: ", rset.Age,  !
    }

    quit $$$OK
}

Invoke Method

do ##class(dc.DESql).DynamicSearchPerson("Y",67)

    Output


        3- Dynamic Pivot Tables: Transforming Data for Analytical Insights

        This example showcases how to dynamically generate a pivot table using InterSystems Dynamic SQL, transforming raw data into a structured summary.

        ClassMethod GeneratePivotTable(tableName As %String, rowDimension As %String, columnDimension As %String, valueColumn As %String) As %Status
        {
            // Simplified example; real pivot tables can be complex
            Set sql = "SELECT " _ rowDimension _ ", " _ columnDimension _ ", SUM(" _ valueColumn _ ") FROM " _ tableName _ " GROUP BY " _ rowDimension _ ", " _ columnDimension
            //Create an instance of %SQL.Statement
            Set statement = ##class(%SQL.Statement).%New()
            // Prepare the query
            Set status = statement.%Prepare(sql)
           
            If $$$ISERR(status) {
                Quit status
            }
            // Execute the query
            Set result = statement.%Execute()
            // Check for errors
            If result.%SQLCODE = 0 {
                While result.%Next() {
                    do result.%Display()
                }
            } Else {
                Write "Error: ", result.%SQLCODE, " ", result.%SQLMSG, !
            }
            Quit $$$OK
        }

        Invoke Method

        Do ##class(dc.DESql).GeneratePivotTable("Sales", "Region", "ProductCategory", "Revenue")

        Output

        4- Schema Exploration: Unlocking Database Metadata with Dynamic SQL

        This example demonstrates how to explore and retrieve metadata about database schemas dynamically, providing insights into table structures and column definitions.

        ClassMethod ExploreTableSchema(tableName As %String) As %Status
        {
            // Create a new SQL statement object
            set stmt = ##class(%SQL.Statement).%New()
            
            // Construct the query dynamically
            set sql = "SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA||'.'||TABLE_NAME = ?"
            // Prepare the query
            set status = stmt.%Prepare(sql)
            if $$$ISERR(status) {
                do $System.Status.DisplayError(status)
                quit status
            }
           
            // Execute the query
            set result = stmt.%Execute(tableName)
            
            // Display schema information
            write !, "Schema for Table: ", tableName
            write !, "-------------------------"
            write !, "Column Name",?15, "Data Type", ?30, "Nullable ",?45,"Column#"
            write !, "-------------------------"
            while result.%Next() {
                
                write !, result.%Get("COLUMN_NAME"),?15, result.%Get("DATA_TYPE"), ?30,  result.%Get("IS_NULLABLE"), ?45,result.%Get("ORDINAL_POSITION") 
                
            }
            
            quit $$$OK
        }
        

        Invoke Method

        Do ##class(dc.DESql).ExploreTableSchema("Sample.Person")

        Output

         

        Embedded SQL

        Embedded SQL is a method of including SQL statements directly within your programming language (in this case, ObjectScript or another InterSystems-compatible language). Embedded SQL is not compiled when the routine that contains it is compiled. Instead, compilation of Embedded SQL occurs upon the first execution of the SQL code (runtime). It is quite powerful when used in conjunction with the object access capability of InterSystems IRIS.

        You can embed SQL statements within the ObjectScript code used by the InterSystems IRIS® data platform. These Embedded SQL statements are converted to optimized, executable code at runtime. Embedded SQL is particularly useful for performing database operations such as querying, inserting, updating, and deleting records.

        There are two kinds of Embedded SQL:

        • A simple Embedded SQL query can only return values from a single row. Simple Embedded SQL can also be used for single-row insert, update, and delete, and for other SQL operations.
        • A cursor-based Embedded SQL query can iterate through a query result set, returning values from multiple rows. Cursor-based Embedded SQL can also be used for multiple-row update and delete SQL operations.

        Key Benefits of Embedded SQL

        1. Seamless Integration: Embedded SQL allows you to write SQL statements directly within ObjectScript code, eliminating the need for external calls or complex interfaces.
        2. Performance: By embedding SQL within ObjectScript, you can optimize database interactions and reduce overhead.
        3. Simplicity: Embedded SQL simplifies the process of working with databases, as it eliminates the need for separate SQL scripts or external tools.
        4. Error Handling: Embedded SQL allows for better error handling since the SQL code is part of the application logic.

        Practical Examples

        1-Record Creation: Inserting Data with Embedded SQL

        This example demonstrates how to insert a new record into a table using Embedded SQL, ensuring seamless data integration.

        ClassMethod AddBook(bookID As %Integer, title As %String, author As %String, year As %Integer, available As %Boolean) As %Status
        {
            // Embedded SQL to insert a new book
            &sql(
                INSERT INTO SQLUser.Books (BookID, Title, Author, PublicationYear, AvailableFlag)
                VALUES (:bookID, :title, :author, :year, :available)
            )
        
            // Check for errors
            if SQLCODE '= 0 {
                write "Error inserting book: ", %msg, !
                quit $$$ERROR($$$GeneralError, "Insert failed")
            }
        
            write "Book added successfully!", !
            quit $$$OK
        }
        

        Invoke Method

        Do ##class(dc.DESql).AddBook(1,"To Kill a Mockingbird","Harper Lee", 1960,1)

        Output

         

        2-Data Retrieval: Fetching and Displaying Records with Embedded SQL

        This example retrieves a list of books from a database using Embedded SQL, showcasing how to fetch and display data efficiently.

        ClassMethod ListBooks()
        {
            // Embedded SQL to query books
            &sql(
                DECLARE BookCursor CURSOR FOR
                SELECT BookID, Title, Author, PublicationYear, AvailableFlag
                FROM SQLUser.Books
                WHERE AvailableFlag = 1
            )
        
            // Open the cursor
            &sql(OPEN BookCursor)
        
            // Fetch and display results
            for {
                &sql(FETCH BookCursor INTO :bookID, :title, :author, :year, :available)
                quit:(SQLCODE '= 0)
        
                write "Book ID: ", bookID, !
                write "Title: ", title, !
                write "Author: ", author, !
                write "Publication Year: ", year, !
                write "Available: ", available, !
                write "-----------------------------", !
            }
        
            // Close the cursor
            &sql(CLOSE BookCursor)
        }

        Invoke Method

        Do ##class(dc.DESql).ListBooks()

        Output

        3- Transaction Management: Ensuring Data Integrity with Embedded SQL

        This example demonstrates how to manage database transactions using Embedded SQL, ensuring data integrity during fund transfers.

        ClassMethod TransferFunds(fromAccount As %Integer, toAccount As %Integer, amount As %Decimal) As %Status
        {
            // Start a transaction
            TSTART
            // Deduct amount from the source account
            &sql(UPDATE Accounts
                 SET Balance = Balance - :amount
                 WHERE AccountID = :fromAccount)
            
            if SQLCODE '= 0 {
                TROLLBACK
                quit $$$ERROR($$$GeneralError, "Failed to deduct amount from source account.")
            }
            
            // Add amount to the destination account
            &sql(UPDATE Accounts
                 SET Balance = Balance + :amount
                 WHERE AccountID = :toAccount)
            
            if SQLCODE '= 0 {
                TROLLBACK
                quit $$$ERROR($$$GeneralError, "Failed to add amount to destination account.")
            }
            
            // Commit the transaction
            TCOMMIT
            write !, "Funds transferred successfully."
            quit $$$OK
        }

        Invoke Method

        do ##class(MyApp.FundManager).TransferFunds(101, 102, 500.00)

        Output

        4- Validate Username Availability

        This example checks if a username is available for use by querying the database to ensure it does not already exist.

        ClassMethod ValidateUserName(username As %String) As %Boolean
        {
            // Embedded SQL to check if the username exists
            &sql(SELECT COUNT(*) INTO :count
                 FROM SQLUser.Users
                 WHERE Name = :username)
            //Check for errors
            if SQLCODE = 0 {
                if count > 0 {
                    write !, "Username already exists."
                    quit 0
                } else {
                    write !, "Username is available."
                    quit 1
                }
            } else {
                write !, "Error validating username: ", %msg
                quit 0
            }
        }

        Invoke Method

        Do ##class(dc.DESql).ValidateUserName("Admin")

        Output

         


        Comparison Between Dynamic SQL & Embedded SQL

        Conclusion

        Dynamic SQL and Embedded SQL are powerful tools in InterSystems IRIS that cater to different use cases. Dynamic SQL provides flexibility for runtime query construction, while Embedded SQL offers performance benefits for static queries. By understanding their strengths and combining them effectively, you can build robust and efficient applications on the InterSystems IRIS platform.

        Thanks

        8 Comments
        ディスカッション (8)3
        続けるにはログインするか新規登録を行ってください
        ダイジェスト
        · 2025年2月28日

        Share Your Feedback – InterSystems Developer Ecosystem Survey 2025

        Dear Community Member,

        We’re thrilled to have you as part of our InterSystems Developer Ecosystem, and we’d love to hear your thoughts! To help us improve and grow, please take a few moments to share your feedback in our InterSystems Developer Ecosystem Annual Survey 2025:

        👉 Take the Survey 👈

        The survey will take approximately 10 minutes to complete. Your feedback will directly influence our improvements in 2025 and beyond.

        Thank you for your continued support and participation!

        Sincerely yours,
        InterSystems Developer Relations Team