#ObjectScript

0 Seguidores · 104 Postagens

InterSystems ObjectScript é uma linguagem de script que opera com dados, usando qualquer modelo de dados da Plataforma de Dados InterSystems (Objetos, Relacionais, Chave-valor, Documentos, Globais) e, desenvolve a lógica de negócios para aplicações de servidor na Plataforma de Dados InterSystems.

Documentação.

Artigo Henrique Dias · Jun. 10, 2023 9m read

20 anos. 

Agora em 2023 eu completo 20 anos trabalhando com tecnologia, desenvolvendo sistemas, implementando, criando novas soluções e posso afirmar que trabalhar com tecnologia InterSystems fez e faz parte da minha vida. Afinal, foram 18 anos trabalhando todos os dias com essa tecnologia.

Comecei a trabalhar com o Caché 4, lá atrás em 2003, vivenciei as mudanças para o Caché 5, Caché e Ensemble 2008, 2010, 2012, 2014, 2017, 2018 e até finalmente chegarmos no InterSystems IRIS. 

4
0 205
Artigo Miqueias Santos · Jun. 10, 2023 6m read

Olá!

Eu estava querendo dedicar algum tempo para implementar alguma DLL ou algo que eu pudesse usar do Caché e finalmente tive uma pequena ideia, se você está interessado em poder produzir mensagens que são enviadas para o Kafka rapidamente, você é no lugar certo ;-)

Antes de lhe entregar a ficha com o que vamos ver, vou fazer um resumo para que você decida se tem interesse em ler o artigo.

Neste artigo vamos focar "apenas" na parte de produzir mensagens e enviá-las para Kafka:

Como funciona?

Utilizo uma DLL .Net (Netframework 4.5) que fiz (está dentro da pasta dll do repositório)

0
0 275
Artigo Miqueias Santos · Jun. 7, 2023 4m read

Quando você compila rotinas ou classes no terminal, os resultados da compilação são exibidos na tela, portanto, mesmo que ocorra um erro, é fácil verificar.Se você deseja obter apenas informações de erro, precisa planejar um pouco.

O seguinte descreve como obter informações de erro do resultado da compilação em lote de rotinas/classes.

para rotinas

Para rotinas de compilação em massa em um namespace no Terminal, o método CompileAll() da classe %Library.Routine. use

0
0 108
InterSystems Oficial Danusa Calixto · Maio 9, 2023 2m read

Queria avisar sobre uma melhoria na forma como geramos e chamamos o código de método no IRIS 2023.1.

Uma classe no IRIS é composta de dois componentes de tempo de execução principais:

  1. Descritor de classe - Uma lista altamente otimizada de parâmetros de classe, métodos e propriedades que compõem a classe junto com os atributos associados a cada um deles, por exemplo, ambiente público/privado.
  2. Código ObjectScript - Um grupo de rotinas que contém o código ObjectScript a ser executado quando um método é chamado.

Quando você chama um método de uma classe/objeto, o código dispatch procura o método no descritor de classe e verifica se você tem permissão para chamá-lo. Em seguida, configura o contexto de classe correto (atualizando $this no processo) e, por fim, chama o código ObjectScript na rotina de classe associada.

A maneira que o ObjectScript é gerado no IRIS 2023.1 foi otimizada para garantir o dispatch desse código pelo descritor de classe, a aplicação de todas as verificações corretas e a execução do ObjectScript com o contexto de classe correto. Antes dessa mudança, era possível chamar o código ObjectScript manual e diretamente, o que poderia gerar resultados inválidos e a capacidade de executar código ObjectScript que não deveria ser permitido.

Um resultado dessa mudança é que qualquer tentativa de chamar o código do método ObjectScript diretamente (o que nunca foi permitido de forma oficial) gerará um erro <NOLINE> a partir do IRIS 2023.1. O código que faz isso deve ser ajustado para chamar o método pela classe/objeto.

Detalhes

Antes do IRIS 2023.1, quando você compilava uma classe User.Test.cls com um método como:

method Test() {
  Write "Test",!
}

O código ObjectScript gerado apareceria na rotina "User.Test.1" como:

zTest() public {
  Write "Test",!
}

Como essa é uma rotina INT regular, você invoca esse rótulo com "Do zTest^User.Test.1()". No entanto, isso contornava o dispatch correto pelo descritor de classe. Então, ele não verificava se você podia chamar esse método, se era particular, e não configurava o contexto de classe/objeto. Portanto, a lógica nesse método que depende do contexto de classe/objeto falharia ou obteria resultados imprevisíveis.

No IRIS 2023.1, vamos gerar:

Test() methodimpl {
 Write "Test",!
}

Esse rótulo só pode ser chamado pelo descritor de classe, e qualquer tentativa de chamada direta obterá agora um erro de tempo de execução <NOLINE>. Antes, para qualquer método que não fosse %, o nome do rótulo tinha o prefixo "z" para indicar que não deveria ser chamado diretamente. Agora, para os métodos de bloco de procedimento, não adicionamos mais o prefixo "z", já que é explícito que esses rótulos não podem ser chamados, e isso melhora a legibilidade do código gerado.

0
0 66
Artigo Danusa Calixto · Maio 2, 2023 3m read

Oi, pessoal!  

Neste artigo, quero revisar as extensões do VS Code que uso para trabalhar com a InterSystems e que facilitam muito meu trabalho. Tenho certeza de que este artigo será útil para quem está apenas começando a jornada de aprendizado das tecnologias da InterSystems. No entanto, também espero que este artigo seja útil para desenvolvedores com vários anos de experiências e abra novas possibilidades de uso do VS Code para desenvolvimento.

Recomendo que todo mundo que trabalhe com a InterSystems tenha essas extensões instaladas e, neste artigo, quero mostrar como usar algumas delas.

Você pode ler mais sobre a funcionalidade e o uso de cada extensão na seção "Extensions" do VS Code, onde também é possível baixar, atualizar e desinstalar extensões:

 Após a instalação, os ícones da extensão aparecem na lateral ou na parte inferior do editor de código.

Extensões obrigatórias

Acho que faz sentido começar nossa jornada com estas extensões básicas, necessárias para trabalhar com a InterSystems no VS Code.

  • A Extensão InterSystems Server Manager para o VS Code ajuda a especificar conexões do servidor.
  • A Extensão InterSystems ObjectScript para o VS Code ajuda a escrever e compilar arquivos de código.
  • A Extensão InterSystems Language Server Extension para o VS Code oferece uma implementação de servidor de idioma para o ObjectScript, permitindo coloração, preenchimento de código, linting e muito mais.
  • Juntas, essas extensões oferecem aos desenvolvedores uma maneira simplificada de criar, testar e implantar aplicativos de pilha completa criados na InterSystems.

    Extensões adicionais

    Além das extensões necessárias básicas, o VS Code oferece várias outras extensões. Você pode escrever código sem elas, mas o desenvolvimento fica mais eficiente ao usar qualquer outra pilha de tecnologia, incluindo as tecnologias da InterSystems. Vou descrever algumas que parecem ser essenciais.

  • A expansão Docker torna o gerenciamento de projetos dockerizados um pouco mais fácil. Você pode gerar automaticamente um Dockerfile para projetos, gerar imagens e gerenciar os contêineres em execução.  
  • SQLTools Driver para o InterSystems IRIS e SqlTools - são duas extensões muito úteis que trabalham em conjunto. Com elas, você pode criar e executar as consultas SQL de banco de dados no VS Code sem acessar o portal de gerenciamento e realizar consultas SQL para interagir com os índices de conteúdo lá.
  •  

    Atualmente, é difícil imaginar o desenvolvimento de um projeto sem o controle de versões. Geralmente, é o Git que faz isso, e o Visual Studio Code oferece suporte mínimo para ele. Se isso não for suficiente para você, confira as duas extensões a seguir: 

  • Git Graph - mostra branches e os status deles de maneira esquemática. Isso é útil quando você precisa entender rapidamente os status dos branches, por exemplo, quando eles são mesclados.
  • Git Lens - permite ver o histórico das alterações na linha destacada e a autoria.
  •  É indispensável para o trabalho em equipe!

  • EditorConfig - uma extensão para melhorar a aparência do código, exige a escrita no arquivo .editorconfig, em que você pode especificar qualquer configuração de formatação do código. É importante ressaltar que, por padrão, essa funcionalidade pode ser implementada pela extensão InterSystems Language Server para o VS Code. Para aplicar a formatação de código ObjectScript padrão no VS Code, você precisa usar a combinação de teclas: Windows - [Shift + Alt + F], Mac - [Shift + Option + F], Ubuntu - [Ctrl + Shift + I]. No entanto, ao usar o arquivo .editorconfig, você pode especificar a própria formatação de código para arquivos diferentes dentro do projeto.
  • Neste artigo, analisei apenas as extensões que já usei. Agradeceria se você reservasse um tempo para escrever nos comentários o que mais pode ser usado para facilitar o desenvolvimento. Então, este artigo será ainda mais útil!

    0
    0 280
    Artigo Danusa Calixto · Maio 2, 2023 26m read

    Resumo

    O que é Query

    Query é um método para encontrar dados que atendem às condições e apresentar os resultados como um conjunto de dados.

    Tipo de Query

    • SQL Query,Usando %SQLQuery e SQL SELECT.
    • Custom Query,Usando a classe %Query e lógica personalizada para gerar resultados.

    Observação: antes de falar sobre a solução de Query geral, vamos primeiro entender os fundamentos da Query para compreender os princípios da implementação. Se você já conhece o uso básico da Query, pule esta seção e vá direto para "Desafios".

    Fundamentos da Query

    Fundamentos do SQL Query

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

    Descrição:

    • Query - Declara a palavra-chave do método Query.

    • QueryPersonByName - Declara o método Query.

    • name As %String = "" - Declara a palavra-chave do método Query.

    • %SQLQuery - O tipo da Query é %SQLQuery.

      • %SQLQuery é uma subclasse de %Query e usa uma forma simples de Query que permite a gravação de declarações Select SQL diretamente no corpo do método.
    • COMPILEMODE - parâmetro de %SQLQuery que indica o método de compilação.

      • IMMEDIATE - faz a compilação imediata, ao verificar se a declaração SQL atual está correta.
      • DYNAMIC - faz a compilação dinâmica, quando a declaração SQL é compilada no tempo de execução.
    • CONTAINID - Para definir o número da coluna colocada como o ID retornado.

      • 1 - Retorna a coluna do ID.
      • 0 - Não retorna.
    • SELECTMODE - Indica o método de exibição.

      • RUNTIME - Nenhum
      • ODBC - Mostra os dados como ODBC.
      • DISPLAY - Mostra os dados como um display.
      • LOGICAL - Mostra os dados de maneira lógica.
    • ROWSPEC - Fornece o nome da coluna de dados, o tipo de dado e a descrição. Uma lista de nomes de variáveis e tipos de dados separados por aspas e vírgulas. Confira o formato a seguir.

      • id - Indica o nome da coluna de dados.

      • %Integer - Indica o tipo de dado.

      • ID - A descrição dos dados.

    • SqlProc - Indica que o método pode ser chamado como um procedimento armazenado.

    • SqlName - O nome do procedimento a ser chamado.

      • Método de chamada não declarado - call M.Query_QueryPersonByName()
      • Método de chamada não declarado - call M.QueryPersonByName()

    image

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

    Fundamentos da Query personalizada

    Ao usar uma Query personalizada, geralmente seguimos um modelo fixo. Os seguintes métodos de classe são definidos na mesma classe.

    • QueryName - Especifica a %Query no tipo de método Query.
    • QueryNameExecute— Esse método principalmente grava a lógica de negócio para buscar os dados e obter o conjunto de dados.
    • QueryNameFetch — Esse método itera o conjunto de dados.
    • QueryNameClose — Esse método exclui dados ou objetos temporários.

    **Observação: o exemplo a seguir mostra um modelo de Query personalizada que é um dos casos mais usados, e não um fixo. **


    Definição de QueryName

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

    Define o tipo de Query chamado QueryPersonByAge e especificado como %Query. E deixa o corpo da definição da consulta em branco.


    Definição de QueryNameExecute

    ClassMethod QueryPersonByAgeExecute(ByRef qHandle As %Binary, pAge As %String = "", count As %Integer = "10") As %Status
    {
        s pid = $i(^IRISTemp) // comment1
        s qHandle = $lb(0, pid, 0) // comment2
        s index = 1 // comment 3
    
        /* Business Logic comment4 */ 
        s id = ""
        for {
            s id = $o(^M.T.PersonD(id))
            q:(id = "")
            q:(id > count)
            s data = ^M.T.PersonD(id)
            s i = 1
            s name = $lg(data, $i(i))
            s age = $lg(data, $i(i))
            continue:(age < pAge)
            s no = $lg(data, $i(i))
            d output
        }   
    
        q $$$OK
    
    output
        s ^IRISTemp(pid, index) = $lb(id, age, name, no) // comment 6 
        s index = index + 1 // comment 7
    }
    

    O método QueryNameExecute() fornece toda a lógica de negócio necessária. O nome do método precisa ser QueryNameExecute(), em que QueryName é o nome da Query definida.

    其中:

    • qHandle - Usado para a comunicação com outros métodos que implementam essa consulta. qHandle pode ser de qualquer tipo. O padrão é %Binary.
    • pAge As %String = "", count As %Integer = "10" são os parâmetros de entrada para a Query, que podem ser usados como condições para a lógica de negócio.
    • Comment 1,s pid = $i(^IRISTemp) - obtém pid.
    • Comment 2,s qHandle = $lb(0, pid, 0) - O primeiro elemento 0 no array indica o início do loop, o segundo elemento pid é usado para obter os dados de ^IRISTemp, e o terceiro elemento 0 é usado para percorrer o nó inicial ^IRISTemp.
    • Código de lógica de negócio - é a principal lógica de implementação para buscar o conjunto de dados.
    • Comment 3 e comment 7,adiciona nós de índice a ^IRISTemp.
    • Comment 6,s ^IRISTemp(pid, index) = $lb(id, name, age, no) - atribuindo valores a ^IRISTemp para a travessia subsequente.
      • Aqui o formato de dados está na forma de %Library.List, para que o método Fetch não precise converter o tipo. Caso contrário, o método Fetch ainda precisaria converter os dados para o formato de lista interna.

    Definição de QueryNameFetch

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

    O método QueryNameFetch() precisa retornar uma única linha de dados em %Library. O nome do método precisa ser QueryNameFetch, em que QueryName é o nome da Query definida.

    Em que:

    • qHandle - Usado para a comunicação com outros métodos que implementam essa consulta. O valor deve ser o definido por Execute.
    • row - indica o valor da linha de dados que é retornada do tipo %Library.List ou a string vazia, se nenhum dado for retornado.
    • end - precisa ser 1 quando a última linha de dados é alcançada. Se 1 não estiver especificado, ele ficará em loop infinito.
    • PlaceAfter - a palavra-chave do método PlaceAfter controla a ordem desse método no código gerado. Isso significa que o método QueryPersonByAgeFetch é gerado após a geração do método QueryPersonByAgeExecute.
    • Comment 1, linhas 1~3, analisa os valores do array qHandle para obter end, pid, index.
    • Comment 2,s index = $o(^IRISTemp(pid, index)) inicia a travessia de acordo com o pid e index analisado.
    • Comment 3, a travessia de ^IRISTemp(pid, index), cada linha pertence ao valor da fila, se "index" estiver vazio, precisa ser atribuído a 1 "end".
    • Comment 4,s qHandle = $lb(end, pid, index) buscará o end, recopiará o index do qHandle para a próxima linha de dados se preparar.

    Observação: O método Fetch é executado várias vezes e itera o maior número possível de linhas de dados. Os métodos Execute e Close são executados uma vez.


    Definição de QueryNameClose

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

    O método QueryNameClose() é encerrado após a conclusão da busca de dados ao remover e limpar os dados ou objetos temporários. O nome do método precisa ser QueryNameClose(), em que QueryName é o nome da Query definida.

    • qHandle - Usado para a comunicação com outros métodos que implementam essa consulta.
    • Comment 1,para obter o pid salvo por qHandle.
    • Comment 2,para limpar o ^IRISTemp gerado temporariamente.

    Chame uma Query personalizada

    USER> d ##class(%ResultSet).RunQuery("M.Query", "QueryPersonByAge","20")
    
    ID:name:age:no:
    1:yaoxin:21:314629:
    2:yx:29:685381:
    4:Pape,Ted F.:27:241661:
    5:Russell,Howard T.:25:873214:
    6:Xenia,Ashley U.:30:420471:
    7:Rotterman,Martin O.:24:578867:
    
    • A consulta aqui é para todas as pessoas com mais de 20 anos de idade e com ID menor que 10.

    Desafios

    Os 2 exemplos básicos acima de Query são provavelmente as duas maneiras mais frequentemente usadas no momento.

    No entanto, os desenvolvedores que geralmente escrevem consultas ou relatórios enfrentam vários problemas, como os seguintes:

    1. Sempre que você escreve uma Query, definir o cabeçalho da coluna ROWSPEC é bastante complicado, você consegue especificar o cabeçalho da coluna ROWSPEC sozinho?
    2. Agora quantos métodos retornam valores JSON, como converter métodos JSON em Query rapidamente?
    3. É possível escrever uma Query genérica que só precisa escrever a lógica principal de Execute?
    4. É possível otimizar o modelo atual, por exemplo, substituindo ^IRISTemp por ^||IRISTemp?

    Há soluções para todas as perguntas acima. Continue lendo a próxima seção do artigo.

    Solução

    Se você quiser implementar uma Query geral, também precisa conhecer o método de retorno de chamadas QueryNameGetInfo.

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

    Conforme acima:

    • colinfo - Esse parâmetro é fundamental para definir a seção do cabeçalho da coluna ROWSPEC. Ele contém um elemento de lista para cada coluna declarada em ROWSPEC. A forma é name:exttype:caption.
      • name - nome do cabeçalho da coluna.
      • exttype - tipo de dados.
      • caption - descrição.
    • O tipo colinfo precisa ser %Library.List, e o tipo do cabeçalho de coluna definido também é %Library.List.

    Exemplo:

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

    Descrição: Ordem de execução da Query Execute -> GetInfo -> Fetch(n) -> Close.

    As soluções a seguir são descritas separadamente:

    • Gerar uma Query dinamicamente a partir de dados ou métodos Json
    • Geração dinâmica de Query por uma declaração Select Sql
    • Geração dinâmica de Query por Query
    • Compatibilidade com a Query legada e geração de colunas Query por meio de parâmetros
    • Definição de uma Query genérica, só com a implementação do método Execute

    Geração dinâmica de uma Query por dados ou métodos Json


    Definindo os métodos Json

    • O método Json pode ser definido de maneira arbitrária, este exemplo é apenas para fins de teste. O seguinte método: faça uma consulta na unidade de disco atual do computador para gerar resultados Json.
    ClassMethod QueryDrives(fullyQualified = 1, type = "D")
    {
        s array = []
        s rs = ##class(%ResultSet).%New()
        s rs.ClassName = "%File"
        s rs.QueryName = "DriveList"
        d rs.Execute(fullyQualified)
        while (rs.Next()) {
            s drive = rs.Get("Drive")
            s drive = $zcvt(drive, "U")
            s obj = {}
            s obj.type = "D"
            continue:(type '= "D")
            s obj.drive = drive
            d array.%Push(obj)
        }
        q array
    }
    

    Execute:

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

    Defina QueryName

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

    其中:

    • className - nome da classe.
    • methodName - nome do método Json a ser executado.
    • arg... - parâmetros do método a ser executado.

    Defina QueryNameExecute

    ClassMethod Json2QueryExecute(ByRef qHandle As %Binary, className As %String, methodName As %String, arg...) As %Status
    {
        s array = $classmethod(className, methodName, arg...) // comment1
        if ('$isobject(array)) { // comment2
            s array = [].%FromJSON(array)
        }
        q:('array.%IsA("%Library.DynamicArray")) $$$ERROR($$$GeneralError, "Not a Array Object") // comment3
        q:(array.%Size() = 0) $$$ERROR($$$GeneralError, "No data") // comment4
        s qHandle = array // comment5
        q $$$OK
    }
    
    • Comment 1,usando o mecanismo de reflexão para chamar o método desejado e obter o valor de retorno.
    • Comment 2,determina se a string retornada é convertida em objeto Json.
    • Comment 3,determina se a string retornada é convertida em objeto Json.
    • Comment 4,o comprimento do array Json 0 emite uma mensagem de erro.
    • Comment 5,obtém o objeto do array.

    Defina QueryNameGetInfo

    ClassMethod Json2QueryGetInfo(ByRef colinfo As %List, ByRef parminfo As %List, ByRef idinfo As %List, ByRef qHandle As %Binary, extoption As %Integer = 0, extinfo As %List) As %Status
    {
        s colinfo = $lb() // comment1
        s count = 1 
        s obj = qHandle.%GetIterator()
        if obj.%GetNext(.key, .value) { 
            s obj = value.%GetIterator() 
            while obj.%GetNext(.objKey, .objValue) { // comment2
                s $li(colinfo, count) = $lb(objKey) 
                s count = $i(count) 
            }
        }   
        s parminfo = "" // comment3
        s idinfo = "" // comment4
        s qHandle = qHandle.%GetIterator() // comment5
        q $$$OK
    }
    
    • Comment 1,inicializa o array colinfo, atribui obj ao objeto iterador qHandle.%GetIterator().
    • Comment 2,itera o objeto Json para obter Key e atribui um valor a colinfo pelo $li.
    • Comment 3,inicializa parminfo ou o erro é relatado.
    • Comment 4,inicializa idinfo ou o erro é relatado.
    • Comment 5,obtém o objeto iterador

    Defina QueryNameFetch

    ClassMethod Json2QueryFetch(ByRef qHandle As %Binary, ByRef row As %List, ByRef end As %Integer = 0) As %Status [ PlaceAfter = Json2QueryExecute ]
    {
        s iter = qHandle
        q:($g(iter) = "") $$$OK
        if iter.%GetNext(.key, .value) { // comment1
            s row = ""
            s obj = value.%GetIterator()
            while obj.%GetNext(.objKey, .objValue) { // comment2
                if ( $g(row) = "" ) {
                    s row = $lb(objValue)
                } else {
                    s row = row _ $lb(objValue)
                }
            }
            s end = 0
        } else {
            s row = "" 
            s end = 1 // comment3
        }
        q $$$OK
    }
    
    • Comment 1,obtém a linha de dados Json do iterador atual.
    • Comment 2,itera o objeto Json e concatena o valor com a linha para $lb.
    • Comment 3,se não houver conjunto de dados, define end como 1 para indicar o final da travessia.

    Defina QueryNameClose

    ClassMethod Json2QueryClose(ByRef qHandle As %Binary) As %Status [ PlaceAfter = Json2QueryFetch ]
    {
        s qHandle = "" // comment1
        q $$$OK
    }
    
    • Comment 1,deixa o objeto qHandle em branco.

    Observação: na verdade, M tem um mecanismo de reciclagem relacionado e o método Close pode ser usado sem a declaração dele.


    Chamando o método Json2Query

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

    Geração dinâmica de Query por uma declaração Select Sql


    Defina QueryName

    Query Sql2Query(sql As %String, mode As %String = 1) As %Query
    {
    }
    
    • sql - variável que representa a declaração SQL a ser escrita.
    • mode - mostra o tipo de formato de dados.
      • 0 - formato lógico
      • 1 - formato lógico
      • 2 - formato lógico

    Defina QueryNameExecute

    ClassMethod Sql2QueryExecute(ByRef qHandle As %Binary, sql As %String, mode As %String = 1) As %Status
    {
        s sqlStatement = ##class(%SQL.Statement).%New()
        s sqlStatement.%SelectMode = mode // comment1
        s sqlStatus = sqlStatement.%Prepare(.sql) // comment2
        q:$$$ISERR(sqlStatus) sqlStatus
        s sqlResult = sqlStatement.%Execute() 
        s stateType = sqlStatement.%Metadata.statementType
        q:('stateType = 1 ) $$$ERROR($$$GeneralError, "Not a select statement") // comment3
        s qHandle = {}
        s qHandle.sqlResult = sqlResult // comment4
        s qHandle.sqlStatement = sqlStatement 
        q $$$OK
    }
    
    • Comment 1,define o formato de exibição dos dados de SQL.
    • Comment 2,transmite a declaração de SQL para obter o objeto sqlStatement e sqlResult.
    • Comment 3,transmite a declaração SQL não selecionada, emite uma mensagem de erro.
    • Comment 4,o qHandle transmitido nos dois objetos são sqlResult e sqlStatement.
      • sqlResult é utilizado para percorrer os dados usados.
      • sqlStatement é usado para obter informações do cabeçalho da coluna.

    Defina QueryNameGetInfo

    ClassMethod Sql2QueryGetInfo(ByRef colinfo As %List, ByRef parminfo As %List, ByRef idinfo As %List, ByRef qHandle As %Binary, extoption As %Integer = 0, extinfo As %List) As %Status
    {
        s colinfo = $lb()
        s sqlStatement = qHandle.sqlStatement // comment1
        s count = 1
        s column = ""
        for {
            s column = $o(sqlStatement.%Metadata.columnIndex(column)) 
            q:(column = "")
            s data = sqlStatement.%Metadata.columnIndex(column)
            s $li(colinfo, count) = $lb($lg(data, 2)) // comment2
            s count = $i(count)
        }
        s parminfo = ""
        s idinfo = ""
        q $$$OK
    }
    
    • Comment 1,obtém o objeto sqlStatement por qHandle.
    • Comment 2,dá a lista de colinfo para a atribuição cíclica das informações de cabeçalho da coluna.

    Defina QueryNameFetch

    ClassMethod Sql2QueryFetch(ByRef qHandle As %Binary, ByRef row As %List, ByRef end As %Integer = 0) As %Status [ PlaceAfter = Sql2QueryExecute ]
    {
        s sqlStatement = qHandle.sqlStatement // comment1
        s sqlResult =  qHandle.sqlResult 
        s colCount = sqlResult.%ResultColumnCount // comment2
        if (sqlResult.%Next()) {
            for i = 1 : 1 : colCount{
                s val = sqlResult.%GetData(i)
                if ( $g(row) = "" ) { // comment3
                    s row = $lb(val)
                } else {
                    s row = row _ $lb(val)
                }
            }
            s end = 0 
        } else {
           s row = ""
           s end = 1
        }
        s qHandle.sqlResult = sqlResult // comment4
        q $$$OK
    }
    
    • Comment 1,obtém o objeto sqlStatement e sqlResult por qHandle.
    • Comment 2,obtém o número de colunas, equivale a obter uma linha de dados do número de itens.
    • Comment 3,itera os dados para atribuir um valor à linha.
    • Comment 4,objeto qHandle.sqlResult, atribui um valor ao objeto atual do loop.

    Defina QueryNameClose

    Podemos pular isso.

    Observação: na verdade, M tem um mecanismo de reciclagem relacionado, e o método Close não deve ser declarado.


    Chame o método Sql2Query

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

    Chame o método Sql2Query


    Defina QueryName

    Query Query2Query(className As %String, queryName As %String, arg...) As %Query
    {
    }
    
    • className - nome da classe.
    • queryName - nome do método Query a ser executado.
    • arg... - parâmetros do método Query a ser executado.

    Defina QueryNameExecute

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

    Defina QueryNameGetInfo

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

    Defina QueryNameFetch

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

    Chame Query2Query

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

    Compatibilidade com a Query tradicional e geração de colunas Query por parâmetro

    • Compatibilidade com a Query tradicional e geração de colunas Query por parâmetro
    • Compatibilidade com a definição de colunas pelo formato de parâmetro sem especificar os parâmetros ROWSPEC
    • Otimização para transformar ^IRISTemp em ^||IRISTemp

    Defina M.CommonQuery

    Class M.CommonQuery Extends %Query
    {
    
    ClassMethod Close(ByRef qHandle As %Binary) As %Status [ CodeMode = generator, PlaceAfter = Execute, ProcedureBlock = 1, ServerOnly = 1 ]
    {
        s %code($i(%code))= (" s pid = $li(qHandle, 2)")
        s %code($i(%code))= (" k ^||GlobalTemp(pid)")
        s %code($i(%code))= (" q $$$OK")
        q $$$OK
    }
    
    ClassMethod Fetch(ByRef qHandle As %Binary, ByRef row As %List, ByRef end As %Integer = 0) As %Status [ CodeMode = generator, PlaceAfter = Execute, ProcedureBlock = 1, ServerOnly = 1 ]
    {
        s %code($i(%code))= (" s end = $li(qHandle, 1)")
        s %code($i(%code))= (" s pid = $li(qHandle, 2)")
        s %code($i(%code))= (" s ind = $li(qHandle, 3)")
        s %code($i(%code))= (" s ind = $o(^||GlobalTemp(pid, ind))")
        s %code($i(%code))= (" if (ind = """") { ")
        s %code($i(%code))= ("  s end = 1")
        s %code($i(%code))= ("  s row = """"")
        s %code($i(%code))= (" } else { ")
        s %code($i(%code))= ("  s row = ^||GlobalTemp(pid, ind)")
        s %code($i(%code))= (" }")
        s %code($i(%code))= (" s qHandle = $lb(end, pid, ind)")
        s %code($i(%code))= (" q $$$OK")
        q $$$OK
    }
    
    ClassMethod GetInfo(ByRef colinfo As %List, ByRef parminfo As %List, ByRef idinfo As %List, ByRef qHandle As %Binary, extoption As %Integer = 0, ByRef extinfo As %List) As %Status [ CodeMode = generator, ServerOnly = 1 ]
    {
        s %code($i(%code))= (" s colinfo = $lb()")
        s %code($i(%code))= (" s column = $lg(qHandle, 4)")
        s %code($i(%code))= (" if ($lv(column)) {")
        s %code($i(%code))= ("  for i = 1 : 1 : $ll(column) {")
        s %code($i(%code))= ("      s $li(colinfo, i) = $lb(""Column"" _ i )")
        s %code($i(%code))= ("  }   ")
        s %code($i(%code))= (" } else {")
        s %code($i(%code))= ("  s len = $l(column, "","")")
        s %code($i(%code))= ("  for i = 1 : 1 : len {")
        s %code($i(%code))= ("      s $li(colinfo, i) = $lb($p(column, "","", i))")
        s %code($i(%code))= ("  }")
        s %code($i(%code))= (" }")
        s %code($i(%code))= (" s parminfo = """"")
        s %code($i(%code))= (" s idinfo = """"")
        s %code($i(%code))= (" q $$$OK")
        q $$$OK
    }
    
    }
    
    
    

    Defina QueryName

    Query CustomColumnQuery(column As %List) As M.CommonQuery
    {
    }
    
    • column - variável que indica a coluna do parâmetro a ser personalizado.
    • M.CommonQuery - tipo de Query personalizada, sem necessidade de escrever métodos GetInfo, Fetch e Close.

    Defina QueryNameExecute

    QueryNameExecute é compatível com três maneiras de definição dos cabeçalhos de coluna:

    1. O cabeçalho de coluna é transmitido pelo parâmetro column e implementado da seguinte maneira.
    ClassMethod CustomColumnQueryExecute(ByRef qHandle As %Binary, column As %List) As %Status
    {
        s pid = $i(^||GlobalTemp)
        s qHandle = $lb(0, pid, 0)
        s $li(qHandle, 4) = column // Mode 1 This location is required
        s ind = 1
    
        s id = ""
        for {
            s id = $o(^M.T.PersonD(id))
            q:(id = "")
            s data = ^M.T.PersonD(id)
            s i = 1
            s name = $lg(data, $i(i))
            s age = $lg(data, $i(i))
            s no = $lg(data, $i(i))
            d output
        }   
    
        q $$$OK
    
    output
        s data = $lb(id, name)
        s ^||GlobalTemp(pid, ind)=data  
        s ind = ind + 1
    }
    
    USER> d ##class(%ResultSet).RunQuery("M.Query","CustomColumnQuery","ID,Name")
    
    ID:Name:
    1:yaoxin:
    2:yx:
    3:Umansky,Josephine Q.:
    4:Pape,Ted F.:
    5:Russell,Howard T.:
    
    1. Sem a transmissão do parâmetro column, os cabeçalhos de coluna são gerados automaticamente com base no número de dados de lista, implementado da seguinte maneira.
    ClassMethod CustomColumnQueryExecute(ByRef qHandle As %Binary, column As %String = "") As %Status
    {
        s pid = $i(^||GlobalTemp)
        s qHandle = $lb(0, pid, 0)
        s ind = 1
        s id = ""
        for {
            s id = $o(^M.T.PersonD(id))
            q:(id = "")
            s data = ^M.T.PersonD(id)
            s i = 1
            s name = $lg(data, $i(i))
            s age = $lg(data, $i(i))
            s no = $lg(data, $i(i))
            s data = $lb(id, name, no)
            q:(id > 5)
            d output
        }   
        s $li(qHandle, 4) = data // Mode 2 This location is required
        q $$$OK
    
    output
        s ^||GlobalTemp(pid, ind)=data  
        s ind = ind + 1
    }
    
    USER>d ##class(%ResultSet).RunQuery("M.Query","CustomColumnQuery")
    
    Column1:Column2:Column3:
    1:yaoxin:314629:
    2:yx:685381:
    3:Umansky,Josephine Q.:419268:
    4:Pape,Ted F.:241661:
    5:Russell,Howard T.:873214:
    
    1. 3. Sem a transmissão do parâmetro column, personalize as informações do cabeçalho da coluna pelo método Execute, implementado da seguinte maneira.
    ClassMethod CustomColumnQueryExecute0(ByRef qHandle As %Binary, column As %String = "") As %Status
    {
        s pid = $i(^||GlobalTemp)
        s qHandle = $lb(0, pid, 0)
        s ind = 1
    
        s id = ""
        for {
            s id = $o(^M.T.PersonD(id))
            q:(id = "")
            s data = ^M.T.PersonD(id)
            s i = 1
            s name = $lg(data, $i(i))
            s age = $lg(data, $i(i))
            s no = $lg(data, $i(i))
            s data = $lb(id, name, no)
            q:(id > 5)
            d output
        }    
        s $li(qHandle, 4) = "id,name,age" // Option 3 This position is required
        q $$$OK 
    
    output
        s ^||GlobalTemp(pid, ind)=data  
        s ind = ind + 1
    }
    
    USER>d ##class(%ResultSet).RunQuery("M.Query","CustomColumnQuery")
    
    id:name:age:
    1:yaoxin:314629:
    2:yx:685381:
    3:Umansky,Josephine Q.:419268:
    4:Pape,Ted F.:241661:
    5:Russell,Howard T.:873214:
    

    Execute CustomColumnQuery

    USER>d ##class(%ResultSet).RunQuery("M.Query","CustomColumnQuery","ID,Name")
    
    ID:Name:
    1:yaoxin:
    2:yx:
    3:Umansky,Josephine Q.:
    4:Pape,Ted F.:
    5:Russell,Howard T.:
    6:Xenia,Ashley U.:
    

    Definição de uma Query genérica, só com a implementação do método Execute

    Para implementar uma Query geral, você precisa abstrair os métodos e subclasses para substituí-los. Então, primeiro defina a classe mãe.

    Defina M.CommonQuery

    Class M.BaseQuery Extends %RegisteredObject
    {
    
    /// d ##class(%ResultSet).RunQuery("M.BaseQuery","CustomQuery","id,name")
    Query CustomQuery(column As %List, arg...) As %Query
    {
    }
    
    ClassMethod CustomQueryExecute(ByRef qHandle As %Binary, column As %List, arg...) As %Status
    {
        s qHandle = $lb(0, 0) // comment1
        s $li(qHandle, 3) = column // comment2
        d ..QueryLogic(arg...) // comment3
        q $$$OK
    }
    
    ClassMethod CustomQueryGetInfo(ByRef colinfo As %List, ByRef parminfo As %List, ByRef idinfo As %List, ByRef qHandle As %Binary, extoption As %Integer = 0, ByRef extinfo As %List) As %Status
    {
        s colinfo = $lb()
        s column = $lg(qHandle ,3)
        s len = $l(column, ",") 
        for i = 1 : 1 : len {
            s $li(colinfo, i) = $lb($p(column, ",", i)) // comment5
        }
        s parminfo = ""
        s idinfo = ""
        q $$$OK
    }
    
    ClassMethod CustomQueryClose(ByRef qHandle As %Binary) As %Status [ PlaceAfter = CustomQueryExecute ]
    {
        k %zQueryList // comment7
        q $$$OK
    }
    
    ClassMethod CustomQueryFetch(ByRef qHandle As %Binary, ByRef row As %List, ByRef end As %Integer = 0) As %Status [ PlaceAfter = CustomQueryExecute ]
    {
        s end = $li(qHandle,1)
        s index = $li(qHandle,2)
        s index = $o(%zQueryList(index))
        if index = "" {  // comment6
            s end = 1
            s row = ""
        } else      { 
            s row = %zQueryList(index)
        }
        s qHandle = $lb(end, index)
        Quit $$$OK
    }
    
    ClassMethod QueryLogic(arg...) [ Abstract ]
    {
        // comment4
    }
    
    }
    
    
    • column - indica a variável para personalizar a coluna de parâmetro.
    • arg... - os parâmetros a serem transmitidos.
    • Comment 1,aqui são feitas algumas mudanças, qHandle só registra "end" e "index", porque, se for uma variável global ou uma global de processo particular, só é válida para o processo atual, então pid pode ser omitido.
    • Comment 2,a terceira posição de qHandle será transmitida no nome do cabeçalho da coluna.
    • Comment 3,chama o método de lógica de negócio a ser implementado, esse método é abstrato e precisa de subclasse para a implementação.
    • Comment 4,chama o método de lógica de negócio a ser implementado, esse método é abstrato e precisa de subclasse para a implementação.
    • Comment 5,obtém o cabeçalho da coluna definido dinamicamente.
    • Comment 6,itera as variáveis globais.
    • Comment 7,após a travessia, as variáveis globais serão limpas.

    Defina a subclasse M.PersonQuery para herdar de M.BaseQuery e implementar o método QueryLogic

    • Tudo o que precisamos aqui é atribuir um valor à variável global %zQueryList($i(count)). O modelo fixo foi abstraído para a classe mãe.
    ClassMethod QueryLogic(arg...)
    {
        s pName = arg(1)
        s id = ""
        for {
            s id = $o(^M.T.PersonD(id))
            q:(id = "")
            s data = ^M.T.PersonD(id)
            s i = 1
            s name = $lg(data, $i(i))
            continue:(pName '= "")&&(name '= pName)
            s age = $lg(data, $i(i))
            s no = $lg(data, $i(i))
            s %zQueryList($i(count)) = $lb(id, name, age)
        }
    }
    

    Chame o método CustomQuery

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

    Observação: as variáveis globais são usadas aqui como transmissão de dados, então, se os dados forem muito grandes, pode ocorrer vazamento de memória. Basta mudar para global de processo particular. Cabe ao leitor implementá-lo com base nessa lógica.

    Observação: assim, uma classe só pode declarar uma Query. Se você quiser declarar mais de uma Query para uma classe, considere mudar para a abordagem de compatibilidade com a Query tradicional.


    Gere o Json por Query

    ClassMethod Query2Json(className, queryName, arg...)
    {
        s array = []
        s rs = ##class(%ResultSet).%New()
        s rs.ClassName = className
        s rs.QueryName = queryName
        d rs.Execute(arg...)
    
        s array = []
        #; 属性值
        while (rs.Next()) {
            s valStr = ""
            s obj = {}
            for i = 1 : 1 : rs.GetColumnCount(){
                s columnName = rs.GetColumnName(i)
                s val = rs.Data(columnName)
                d obj.%Set(columnName, val)
            }
            d array.%Push(obj)
        }
    
        q array.%ToJSON()
    }
    
    
    USER>w ##class(Util.JsonUtils).Query2Json("%SYSTEM.License","Summary")
    [{"LicenseUnitUse":"当前使用的软件许可单元 ","Local":"1","Distributed":"1"},{"Li           censeUnitUse":"使用的最大软件许可单元数 ","Local":"15","Distributed":"15"},{"Lic            enseUnitUse":"授权的软件许可单元 ","Local":"300","Distributed":"300"},{"LicenseU         nitUse":"当前连接 ","Local":"3","Distributed":"3"},{"LicenseUnitUse":"最大连接数          ","Local":"17","Distributed":"17"}]
    

    Gere o csv por Query

    ClassMethod Query2Csv(className, queryName, filePath, arg...)
    {
        s file = ##class(%FileCharacterStream).%New()
        s file.Filename = filePath
    
        s array = []
        s rs = ##class(%ResultSet).%New()
        s rs.ClassName = className
        s rs.QueryName = queryName
        d rs.Execute(arg...)
    
        #; 列名
        s colStr = ""
        for i = 1 : 1 : rs.GetColumnCount(){
            s columnName = rs.GetColumnName(i)
            s colStr = $s(colStr = "" : columnName, 1 : colStr _ "," _ columnName)
        }
        d file.Write(colStr)
    
        #; 属性值
        while (rs.Next()) {
            s valStr = ""
            for i = 1 : 1 : rs.GetColumnCount(){
                s columnName = rs.GetColumnName(i)
                s val = rs.Data(columnName)
                s valStr = $s(valStr = "" : val, 1 : valStr _ "," _ val)    
            }
            d file.Write($c(10) _ valStr)
        }
    
        d file.%Save()
        q $$$OK
    }
    
    USER>w ##class(Util.FileUtils).Query2Csv("%SYSTEM.License","Summary","E:\m\CsvFile2.csv")
    1
    

    imagem

    imagem

    Resumo

    • Entender o parâmetro qHandle e o método GetInfo é fundamental para implementar uma Query genérica.
    • O uso de uma Query genérica pode melhorar a eficácia do desenvolvimento.
    • O uso de uma Query genérica pode solucionar o problema de adaptação dos dados.

    Acima estão alguns dos meus entendimentos sobre Query. Devido à minha compreensão limitada, comentários e outros contatos são bem-vindos.

    Se uma boa ideia tiver o uso banido no futuro só porque alguém pensou nela primeiro, a sociedade inteira precisará fazer muito mais desvios, algo que o espírito do software gratuito sempre expressou.                                                                                                                                - Richard Matthew Stallman

    0
    0 94
    Artigo Cristiano Silva · Abr. 27, 2023 6m read

    InterSystems FAQ rubric

    Neste artigo, apresentaremos como lidar com a situação: "Excluí acidentalmente uma global!"

    Arquivos de backup e journals são usados para recuperar globais específicas que foram excluídas acidentalmente. A restauração é executada especificando as condições e restaurando registros do journal usando o utilitário ^ZJRNFILT. Dessa forma, você pode aplicar um backup pontual do banco de dados e até incluindo a exclusão de uma global específica para registros do journal que contêm as exclusões. Para obter mais informações sobre o utilitário ^ZJRNFILT, consulte a documentação:

    0
    0 98
    Artigo Heloisa Paiva · Mar. 10, 2023 3m read

    Introdução

    Esse artigo tem a intenção de ser um simples tutorial sobre como criar conexões ODBC e trabalhar com elas, já que eu achei o assunto um pouco confuso quando estava começando, mas tive pessoas incríveis que pegaram minha mão e me guiaram para conseguir, e eu acredito que todos merecem esse tipo de ajuda também.

    Vou dividir cada pequena parte em seções, então sinta-se à vontade para pular para a que sentir necessidade, apesar de eu recomendar ler o texto na íntegra.

    0
    0 439
    Artigo Danusa Calixto · Out. 27, 2022 15m read

    Por que eu amo ObjectScript e por que eu acho que poderia amar Python ainda mais

    Eu estava olhando o tópico de mensagens sobre o assunto "Desempenho ao construir uma string separada por vírgulas" e comecei a escrever uma resposta. No entanto, me distraí, a página foi atualizada e perdi meu texto. Não podia gastar tempo reescrevendo minha resposta, então comecei a escrever este documento em vez disso.

    Comecei a escrever na linguagem MUMPS no início da minha carreira. Eu escrevia blocos de código bastante concisos e densos em que exercícios como o exemplo da string eram verdadeiros desafios. O desempenho dos servidores VAX ou Digital DEC eram aproveitados até a última gota. Planejávamos onde globais importantes ficariam em um disco. Quando o Caché foi lançado, ainda estávamos trabalhando com M/SQL. Durante um período, estive envolvido em diversas comparações de desempenho entre o Caché e o Oracle, Sybase e SQL Server. Criávamos um esquema de algumas tabelas, preenchidas com milhões de registros, e executávamos várias pesquisas no banco de dados resultante. Eu costumava escrever duas versões de declaração SQL. Uma era uma declaração SQL pura, e a outra era uma consulta personalizada que eu escrevia na definição de classe. A maior parte da lógica está no "Fetch", e eu elaborava esse método para maximizar os indíces definidos e usar ^CachéTemp para qualquer junção complexa de resultados provisórios. Às vezes, eu criava jobs de uma ou mais subconsultas que gerariam os globais temporários e resolvia as junções após a conclusão de todos os processos de jobs. O resultado poderia ser resumido da seguinte maneira:

    Inserir dados no banco de dados usando SQL ou Caché Objects sempre foi mais rápido do que qualquer outro DB. Usar COS puro e conjuntos globais diretos era uma ordem de grandeza mais rápida do que SQL, Objects e qualquer outro banco de dados. O banco de dados resultante teria aproximadamente metade do tamanho de um criado por qualquer banco de dados relacional.


    Ao comparar o código que escrevi no meu método "Fetch" com o código gerado pelo Caché SQL Engine, vi que usei menos variáveis, 25% menos linhas e o código ficou mais legível.

    O número de leituras de blocos de dados físicos seria praticamente o mesmo que o código gerado pelo M/SQL. No entanto, o número de leituras lógicas do pool de buffers globais seria 20% menor que o M/SQL.

    Recorri a todos os truques possíveis de desenvolvedores MUMPS. Usei comandos como "execute", "job" (criando threads para lidar com subconsultas em paralelo de forma eficaz), indireção e pós-condições. Recomendamos aos desenvolvedores que não usem esses recursos de linguagem para escrever código legível e capaz de ser mantido por outros desenvolvedores.

    Eu inicializaria variáveis desta forma:

    set (a,b,c,d)="",(x,y,z)=0,p1=+$h,p2=...,pN=99


    Espremi o máximo de expressões possível em uma linha de código. Acreditávamos que a leitura de cada linha de código no "buffer de execução" resultava em um custo. Portanto, o número de linhas de código executadas sempre teve um efeito direto e inverso no desempenho.

    Quando trabalho com código escrito por outro desenvolvedor e noto blocos de código que consistem em um comando definido por cada linha, fico um pouco agitado e sempre condenso essas 30 linhas em uma. Me apaixonei por Caché Objects. Vinte e cinco anos depois, esse caso durou mais que dois relacionamentos sérios e um casamento. Definições de classe, com nomes de propriedade precisos e bastante legíveis, indexação de bitmap em tudo, a menos que a indexação de busca possa fazer melhor. Quando possível, relacionamentos pais-filhos em vez de um-muitos. Uso uma chave primária personalizada em tabelas de código quando a indexação de bitmap não é necessária porque set record=$g(^global(code)) sempre será mais rápido que

    set record="",rowId=$o(^IndexGlobal("IndexName",code,"")) set:$l(rowId) record=^Global(rowId)

    Havia algumas formas de declarações SQL select com que o M/SQL não era compatível ou executava mal. Em geral, o Caché era 2 a 3 vezes mais rápido do que qualquer outro banco de dados.


    Ao longo dos anos, o mecanismo SQL melhorou significativamente. A indexação de bitmap e iFind foi lançada. Usamos a indexação iFind nos nomes e endereços de pacientes em um banco de dados de 15 milhões de pessoas. Todos os outros campos são indexados por bitmap. Quando recebemos uma Pesquisa de Paciente de FHIR com vários parâmetros, oferecemos suporte a todos os qualificadores e operadores de especificação FHIR. Construímos uma declaração SQL que começa com uma junção em todas as entidades do paciente de FHIR, que armazenamos em classes persistentes. Estou reivindicando o uso do repositório IRIS for Health para nossa próxima fase de desenvolvimento. O IRIS teve dois lançamentos e amadureceu desde a primeira vez que trabalhei com ele na versão 2019.1. A junção é seguida por qualquer cláusula iFind em Nomes e Endereços, se especificada nos critérios de pesquisa. As cláusulas AND/OR são adicionadas para os campos nos critérios de pesquisa que sabemos serem compatíveis com índices bitmap. As pesquisas determinísticas ou probabilísticas que realizamos são tão rápidas e precisas que ainda me fazem pular de animação (na minha idade!!!).


    Preciso confessar que nunca gostei do SQL quando fazia parte de um grupo cada vez menor de desenvolvedores que escreviam código MUMPS no final dos anos 80. Meus colegas foram rápidos em aderir ao Oracle ou SQL Server. Às vezes, era difícil não entrar em um estado de desespero quando ouvia opositores gritando: "O MUMPS está morto".

    Então, na conferência anual do MUMPS em Dublin, acordamos em determinada manhã com um bilhete enfiado debaixo das nossas portas anunciando que a InterSystems havia comprado a DTM. Em uma conferência realizada um ano depois em Birmingham, eu estava trabalhando para a InterSystems e estávamos apresentando formulários do Visual Basic usando o Caché de dll que adquirimos quando compramos Data Tree. A Micronetics estava no estande em frente ao nosso, e eles não tinham um dll. O sistema de som deles era mais alto, mas nós havíamos vencido. Levaria mais um ano para comprarmos DSM da Digital e, por fim, MSM da Micronetics. Não havia mais como recuar.  Lembro de mostrar o M/SQL a um cliente em Birmingham que escrevia um software de contabilidade. Um dos seus clientes era o Barings Bank, que havia acabado de perder 859.000.000 GBP devido ao trader desonesto Nick Leeson. Não pude deixar de configurar meu banco de dados de exemplo para poder executar uma consulta SQL que provavelmente não era mais complexa que "SELECT sum(Total) from Accounts WHERE .... and AccountNumber="666..". A conta tinha o mesmo número daquela usada por Nick Leeson para esconder as negociações que estava fazendo para recuperar sua situação, que piorava a cada toque do sino do pregão no mercado de ações de Singapura. Lembro de ficar rindo silenciosamente, em parte pela referência implícita ao colapso do Barings Bank, mas também porque a consulta foi realmente executada, forneceu a resposta correta e não levou mais de um minuto (nenhuma dessas coisas era uma certeza na época).

    Essa é a única memória que tenho de gostar do SQL. Eu lidava com vários públicos de DBA do Oracle e SQL Server e demonstrava o Caché, Caché e VB, Caché Objects e Caché SQL. O Caché Objects me encantava: tão elegante, óbvio, maleável e legível. A sintaxe de objetos (em qualquer linguagem) é muito mais natural para mim do que qualquer declaração SQL. Quando tivemos a oportunidade de pegar o esquema do aplicativo de um cliente em potencial, executá-lo através do importador SQL e traduzir o conjunto de procedimentos armazenados que o cliente em potencial incluiria no esquema em Caché Objects ou Caché Globals puro, me familiarizei muito com a leitura do plano de execução SQL e a consulta armazenada gerada. Entrei em longas conversas com Ariel Klausner sobre a consulta SQL que o cliente em potencial me deu e não estava funcionando, e essa seria a diferença entre: ver os DBAs do Oracle saindo da sala de reuniões de volta para a segurança do ajuste do índice e as 6 horas garantidas de inatividade dos sistemas todos os dias durante os backups, onde eles poderiam trazer seus aplicativos relacionais de volta à vida em prontidão para os próximos dias de negociações, ou a emoção de conquistar um cliente que havíamos buscado por meses e estava mais interessado na velocidade do Caché, Orientação de Objetos, gateways .Net ou Java e a elegância simples do CSP de corretagem. Acredito que "por que escrever aplicativos DENTRO de um ambiente de DB?" não é uma pergunta. Primeiro, crio um banco de dados para meu código e outro para meus globais e, ali mesmo, tenho um ponto de separação. Todos nós crescemos nos últimos 25+ anos pensando em Classes, Objetos, ObjectScript e Globais como todos agrupados. Argumento que, no tempo de execução, o código sendo executado no buffer é OBJ. Basicamente, o código OBJ é uma mistura de código C compilado, código de máquina puro otimizado para a plataforma em que está sendo executado e alguns resquícios da definição de classe necessária se você estiver usando $classname, $classmethod, $property e outros fatores. Grande parte do "mecanismo" do Caché ou IRIS é escrito em ObjectScript, provando que essa é uma linguagem perfeita para trabalho. É uma linguagem que pode ser explícita, abreviada e muito compacta. Ela contém todos os operadores e as construções de qualquer linguagem moderna (if, ifelse, else, try - catch, while, for [para ser justo, nossa implementação de FOR é maravilhosa: | for i="apples","pears","Nigel","Fruit" {} | for {} | for i=$$$StartGValue():$$$Increment():$$$EndValue() | ]). Se um dos primeiros criadores do MUMPS tivesse chamado $order de "$next", ele seria imediatamente reconhecível como Next(), conforme encontrado em todas as outras linguagens que iteram por uma array. $PIECE é um pouco peculiar, mas só porque todos os outros bancos de dados usam campos de tamanho fixo. O conceito das strings delimitadas usadas como construção do banco de dados é estranho para um DBA do SQL. Ao analisar o código de máquina compilado de qualquer uma das formas de banco de dados, as instruções se movem pela string, caractere por caractere, e contando o número de caracteres ou fazendo isso enquanto procuram por um delimitador de campo específico.

    $list conseguiu um desempenho um pouco melhor do que $piece, mas à custa de um ou dois bytes adicionais no início de cada campo. No entanto, ainda consumiu menos espaço do que os campos de comprimento fixo. O motivo pelo qual todo código de sistema é escrito em ObjectScript até hoje é porque a linguagem é muito eficiente e legível. Além disso, quando os principais desenvolvedores do Caché/IRIS, Scott Jones, Dave McCalldon e Mo Chung, precisavam de algo onde o ObjectScript fosse inadequado, eles escreviam em C e enterravam no Kernal.


    Segundo, se eu tiver uma definição de tabela e campos que exigem alguma forma de formatação ou validação em cima e além das restrições óbvias de tipo e comprimento, então quero escrever esse código e mantê-lo bastante próximo à própria definição do campo. Por que eu entraria em outra linguagem, outro ambiente, para escrever essa validação? Os bancos de dados relacionais usam procedimentos e gatilhos armazenados para lidar com essa validação usando a linguagem SQL para expressar a lógica de validação. Se encontrar um programador que prefira usar SQL para escrever lógica complexa em vez de Basic, C#, C++, ObjectScript ou Python, eu compro uma cerveja para você na próxima vez que eu passar por Viena :-)


    Na universidade, aprendi a programar em Fortran e Pascal. Pascal era uma linguagem utilizável e perfeitamente legível. Como um jovem de 19 anos que ficava facilmente empolgado, o fato de que o compilador Pascal pudesse ser escrito nessa linguagem me fascinou. Mais tarde, aprendi COBOL. WTF??? No entanto, tenho um amigo que é desenvolvedor da Sage Accounting e escreve nessa linguagem porque a Sage Accounting foi escrita em COBOL. Páginas e páginas da linguagem mais detalhada, ilegível e inutilizável que já vi. Na verdade, o COBOL ainda é muito usado.

    Você poderia pensar que Pascal ultrapassaria facilmente COBAL e até Basic. Mas isso não aconteceu. Por quê? É simples. Essa linguagem não era usada nos aplicativos bancários (o COBOL foi amplamente adotado em grandes aplicativos de processamento em lotes de mainframe, como Accounting). Brincávamos dizendo que os bancos não queriam comprar o modelo Caché porque não era sofisticado o suficiente. Não era porque o ObjectScript não conseguisse fazer o processamento transacional desses aplicativos bancários. Nós éramos comprovadamente mais rápidos do que qualquer tecnologia que eles estivessem usando. O problema era que eles haviam gastado muito dinheiro nos sistemas que tinham e no hardware necessário para executar esses Lotes durante a noite para os bancos abrirem às 9h do dia seguinte. As salas caras dos servidores com gás radônio e sistemas de filtragem removem até mesmo as menores partículas de poeira para que não caiam em um disco ou uma fita magnética, causando a perda de um dia inteiro de transações de contas.


    Pascal deveria ter vivido mais do que o Basic e talvez isso tivesse acontecido se a Microsoft não houvesse criado o Visual Basic e entrado em competição com Delphi e Borland. O IDE parecia mesmo com o VB, mas usava Pascal em vez de Basic. Tudo isso estava acontecendo enquanto a Microsoft lançava o C#, porque eles tinham que acomodar todos os programadores de C++ e certamente não conseguiriam conquistá-los com o Basic. Eles também estavam ameaçando lançar a própria versão do Java ou remover a compatibilidade com ele, porque o Java rodar em plataformas de hardware em que o Windows nunca seria capaz de rodar era algo que os incomodava. A Microsoft só recuou quando os avanços da tecnologia tornaram o conceito de máquinas virtuais ou containers uma opção de implantação realista. Então, Pascal e Delphi simplesmente desapareceram. Fiz uma pesquisa rápida no Google e há um interpretador de Pascal para Android, então ele ainda existe.

    Considerando que Pascal era uma linguagem, ao contrário do Basic, que era apenas uma linguagem de certa forma. No entanto, ele foi usado pela Microsoft para os scripts de aplicativos como o Excel e uma conexão de propriedade com o SQL Server. Isso permitiu vincular dois ambientes que eram intrinsecamente inadequados sem o incômodo de atender aos padrões ODBC e JDBC. Os padrões eram fortemente apoiados pelo Oracle, Sybase e praticamente todos que precisavam fornecer um gateway para as versões proprietárias do SQL. Assim, o Basic sobreviveu. Estou feliz por ter começado minha carreira de programador com o Pascal. Depois, tive uma grande surpresa ao escrever programas COBOL por um ano trabalhando para uma empresa de seguros. Cheguei às costas úmidas e cinzentas do Reino Unido e comecei meu primeiro emprego, que, por acaso, usava MUMPS. Toda a evolução do MUMPS para CachéObjectScript, Objects, Object Gateways, .Net e Java, Caché Basic e MultiValueBasic e agora Python. O Python fecha um ciclo e, em certo sentido, prova que o ObjectScript não é uma aberração em uma tecnologia de banco de dados não relacional rejeitada.


    Caché Globals são arrays esparsas multidimensionais tão convenientes para a própria natureza dos dados de saúde que não importa o esforço do Oracle e da Microsoft em consumir esse espaço no mercado. Ainda que tenham matado Pascal e Fortran, e até Basic, eles não conseguiram matar a InterSystems. Lembro de participar de um seminário do Oracle sobre "Oracle para a Saúde". A apresentadora falava sobre o Oracle na área da saúde que, ela nos garantiu, dominaria o mercado da saúde de uma vez por todas. Levantei minha mão e perguntei: "Não é isso o que vocês afirmam em todos os grandes lançamentos há anos? Vocês fracassaram antes. Por que fariam melhor desta vez?" Ela olhou para mim e perguntou, "Quem é você?". Respondi: "Sou da InterSystems. Dominamos o mercado da saúde há 35 anos. Conseguimos isso porque nossa tecnologia nasceu no Massachusetts General Hospital. E adivinhe. Eles ainda executam os sistemas centrais nas nossas tecnologias". Nessa hora, dois seguranças corpulentos me tiraram do auditório.


    Então, o Oracle com o pSQL tem o Java. A Microsoft tem o SQL Server, C# e tSQL e, quando você precisa interagir com o Java, fica sujeito ao JDBC. Da mesma forma, com Java, se você precisa conversar com tabelas do SQL Server, é obrigado a usar ODBC. Onde ficamos? Bem, chegamos à grande ideia de ter wrappers para .Net e Java. Ao usar o ObjectScript, eu instancio uma instância da Classe A. Na verdade, não importa se a Classe A é uma classe .Net, Java ou ObjectScript, porque eu instancio esses objetos usando exatamente a mesma sintaxe em todos os casos. Depois, invoco os métodos de classe ou instância para manipular os objetos. O interior desses métodos não é importante, porque a sintaxe para interagir com essas classes e os métodos é basicamente idêntica, independentemente do que elas contenham.

    Junto vem o Python, que compartilha vários recursos com o ObjectScript, pois é uma linguagem interpretada, e não compilada. É bastante legível e utilizável. Assim como o Caché ObjectScript encontrou um nicho nos dados não estruturados, o Python encontrou um nicho no mundo da modelagem matemática, ML, IA e muito, muito mais. Esse não é um mundo em que C# ou Java se sintam particularmente confortáveis. Aliás, nem o ObjectScript. Assim, a InterSystems foca em fornecer funcionalidades cada vez mais poderosas para manipular grandes quantidades de dados não estruturados, além de lançar iFind e iKnow, algumas técnicas de indexação muito inteligentes e algoritmos de correspondência de probabilidade. Então, você convida o Python para se aconchegar nas nossas arrays esparsas multidimensionais, trazendo milhões de bebês .py que fazem praticamente tudo o que você precisa. É a combinação perfeita. Por precaução, esqueci de mencionar que várias arquiteturas que dominam o mundo do desenvolvimento de páginas da Web são completamente baseadas em JS (Angular.js, REACT.js, Vue.js, Bootstrap — certo, não há JS, mas ele está em tudo menos no nome — e Node.js) e Arrays de JS. O JS não vai desaparecer tão cedo. No entanto, será interessante ver o que vai acontecer com a Golang, se é que você me entende. Vi entradas baseadas em arrays de JS nas últimas competições de código. Se existe uma tecnologia que entende arrays melhor do que qualquer outra, é a IRIS.


    Penso naqueles dias, sentado no meu escritório na empresa onde trabalhava no coração de Londres. Em determinado momento, a empresa estava cheia de programadores MUMPS, mas ela os transformou em programadores SQL relacionais, que depois se tornaram redundantes. Lembro da sensação de começar a questionar se minha fé no MUMPS ser simplesmente a melhor linguagem que já havia encontrado poderia estar errada. A linguagem e as empresas que construíram interpretações dessa linguagem morreriam. Isso me deixou muito triste porque, até então, havia aprendido cinco outras linguagens de programação (APL, Basic, Fortran, COBOL e Pascal) antes de descobrir o MUMPS, e o MUMPS era tão simples. Fácil de escrever, ler e implantar. Em outras palavras, era tão natural para mim quanto o inglês, e tinha um ritmo parecido com os hinos que cantávamos na escola metodista que frequentei:

    Onward, Christian soldiers!

    Marching as to war,

    With the cross of Jesus

    Going on before.

    Christ, the royal Master,

    Leads against the foe;

    Forward into battle,

    See his banners go!

    Mas ele não morreu. A música mudou um pouco:

     

    A bordo, Nigel Saaalllm

    Voando para a guerra

    Com seu cartão de crédito internacional

    Indo à frente.

    John, o Mestre, McCormick

    Lidera contra o inimigo (Microsoft)

    Avançando na batalha

    Veja seus duty frees partindo

    CacheObjectScript era ainda melhor que o MUMPS se isso era possível. CacheObjects parecia tão legal quando demonstrado para uma audiência pelas primeiras vezes, e CacheSQL deixou seus dias de M/SQL para trás e melhorou muito ao longo dos anos. Ainda assim, eu particularmente não gosto de escrever muito em SQL, mas encontrei um bom equilíbrio entre Objects, SQL e referências globais diretas conforme relaxei. Meu código era bastante orientado para as referências globais diretas, com um pouco de OO e o mínimo de SQL. Quando vi com os produtos que o código gerado era compacto, elegante, eficiente e legível, o equilíbrio mudou novamente. Agora, uso nomes globais diretos raramente, muito Objects e uma quantidade razoável de SQL.

    Para trabalhar com Python, minha mente precisará ver padrões diferentes do meu código ObjectScript. Há muitos "abc" e outras estruturas estranhas. No entanto, depois de escrever algumas páginas de código py e me afastar, como faço ao pintar a óleo, os padrões saltarão. Assim como vejo a música como sinestesia de cores, meus programas py codificados por cores fluindo pela página também começarão a parecer aquarela ou até mesmo uma pintura a óleo pesada. Ficarei encantado, e estará tudo bem.

    1
    0 174
    Artigo Danusa Calixto · Out. 18, 2022 7m read

     Olá, Comunidade!

    Este artigo fornece uma visão geral dos webservices JSON REST desenvolvidos pelo TrakCare.

    Esses webservices permitem aos usuários acessar os dados do TrakCare fora do software, principalmente por apps externos.

    Eles são desenvolvidos em REST com ObjectScript e permitem o acesso aos dados em quatro modos:

    • Listar: reúne uma lista de dados com base em um filtro (por exemplo, todos os episódios de um determinado paciente);
    • Abrir: obtém uma linha específica do ID interno ou de uma chave única (por exemplo, o número de um determinado episódio);
    • Salvar: insere uma nova entrada na tabela correta e retorna o ID interno;
    • Atualizar: atualiza uma entrada existente em uma tabela com base no ID.

    Acesso aos webservices

    O URL genérico para os webservices é:

    http://<adresse IP ou alias du serveur TrakCare>:57772/trakRestWS/tcrest/call

    As solicitações são enviadas pelo método POST, com um corpo JSON contendo os dados de autenticação do usuário, a identificação do webservice que será chamado (classe e método) e os parâmetros de método. Esses parâmetros são, na maioria das vezes:

    • para webservices Listar, o número do episódio;
    • para Abrir, o ID interno do elemento;
    • para Salvar, os campos obrigatórios para a inserção de um novo elemento, mais o contexto e ação ACN associados e os parâmetros não obrigatórios;
    • para Atualizar, o ID interno do elemento e os campos a serem atualizados.

    A estrutura JSON é a seguinte:

    {
        < class="hljs-string">"ClassName": < class="hljs-string">"<Full class name>",
        < class="hljs-string">"MethodName": < class="hljs-string">"<Method name, generally List, Open, Save, SaveWithACN or Update>",
        < class="hljs-string">"AppName": < class="hljs-string">"<Name of the application calling the webservice>",
        < class="hljs-string">"Request": {
            < class="hljs-string">"UserName": < class="hljs-string">"<TrakCare username>",
            < class="hljs-string">"Password": < class="hljs-string">"<Encoded password>",
            < class="hljs-string">"Encoder": < class="hljs-string">"Base64",
            < class="hljs-string">"Params": {
                <Parameters>
            }
        }
    }
    

    O parâmetro Encoder define a codificação da senha, o que impede o envio da senha do usuário sem criptografia. Porém, se o parâmetro Encoder não estiver definido, o campo Password conterá a string não codificada da senha do usuário.

    Filtros

    Os webservices Listar e Abrir geralmente retornam grandes volumes de dados, o que pode fazer com que os aplicativos clientes fiquem lentos. Assim, um sistema de filtragem de resultado do webservice é possível se outro parâmetro for adicionado ao JSON.

    A filtragem é possível obtendo apenas uma lista de campos solicitados com o parâmetro FilterInclude ou excluindo os campos não desejados com FilterExclude. O JSON anterior pode, portanto, ser um dos dois a seguir:

    {
        "ClassName": "&lt;Full class name>",
        "MethodName": "&lt;Method name, generally List, Open, Save, SaveWithACN or Update>",
        "AppName": "&lt;Name of the application calling the webservice>",
        "Request": {
            "UserName": "&lt;TrakCare username>",
            "Password": "&lt;Encoded password>",
            "Encoder": "Base64",
            "Params": {
                <Parameters>
            },
            "FilterInclude": "&lt;Fields to include separated by a comma>"
        }
    }
    

    OU

    {
        "ClassName": "&lt;Full class name>",
        "MethodName": "&lt;Method name, generally List, Open, Save, SaveWithACN or Update>",
        "AppName": "&lt;Name of the application calling the webservice>",
        "Request": {
            "UserName": "&lt;TrakCare username>",
            "Password": "&lt;Encoded password>",
            "Encoder": "Base64",
            "Params": {
                <Parameters>
            },
            "FilterExclude": "&lt;Fields to exclude separated by a comma>"
        }
    }
    

     

    Tabelas de código / Thesauri

    As tabelas de código, ou thesauri, armazenam alguns dos dados do TrakCare. São dicionários referenciados por outras tabelas do TrakCare por código ou descrição e, portanto, necessários para o funcionamento correto dos webservices. 

    Lista de tabelas de código

    A lista de tabelas de códigos está disponível para o usuário ao usar o seguinte JSON:

    {
        "ClassName": "Region.FRXX.WebServices.REST.CodeTables.GenericCodeTables",
        "MethodName": "List",
        "AppName": "&lt;Name of the app calling the webservice>",
        "Request": {
            "UserName": "&lt;TrakCare username>",
            "Password": "&lt;Password>",
            "Params": {
                "CT": "*"
            }
        }
    }
    

    O JSON resultante conterá a lista ou os thesauri disponíveis.

    Conteúdo da tabela de código

    Para acessar uma tabela de código, o parâmetro dela no JSON anterior precisa conter o nome da tabela de código que queremos acessar. Dois parâmetros adicionais, Filter e FilterActif, especificam a solicitação.

    Filtro

    O parâmetro Filter filtra no código e na descrição os elementos na tabela de código. É baseado em um sistema de correspondência de padrão semelhante ao do SQL, usando o caractere % como curinga.

    A filtragem pode ser feita de uma das seguintes maneiras:

    • Filtragem estrita com uma string exata: o resultado de "Bilateral" será somente os elementos que tiverem código ou descrição correspondente a "Bilateral", sem diferenciar maiúsculas de minúsculas.
    • Filtragem suave com o curinga %: o % define um grupo desconhecido de caracteres. Portanto,
      • o resultado de "%al" incluirá todos os elementos que acabam em "al" (ou "AL", "Al", "aL")
      • o resultado de "Bi%" incluirá todos os elementos que começam com "Bi" (ou "BI", "bi", "bI").
      • o resultado de "%la%" incluirá todos os elementos que tiverem código ou descrição com "al" em algum lugar (sem diferenciar maiúsculas de minúsculas).
        • nesse caso, o % pode ser combinado para a correspondência com elementos contendo várias strings, por exemplo, "%bi%al%"
      • o resultado de "%" incluirá todos os elementos. No entanto, tenha cuidado, algumas tabelas de código contêm várias linhas, o que pode causar falha ou lentidão nos aplicativos clientes.

    Se o parâmetro Filter estiver em branco ou não definido, o resultado do webservice será vazio.

    FilterActif

    Por padrão, o webservice só gera parâmetros padrão do thesaurus como resultado. Para filtrar em elementos ativos e inativos, o parâmetro FilterActif definido com o valor "ALL" precisa ser transmitido no JSON.

    Veja este exemplo completo para obter a lista de prestadores de cuidados (tabela de código CTCareProv):

    {
        "ClassName""Region.FRXX.WebServices.REST.CodeTables.GenericCodeTables",
        "MethodName""List",
        "AppName""Postman",
        "Request": {
            "UserName""userWS",
            "Password""****",
            "Params": {
                "CT""CTCareProv",
                "Filter""%",
                "FilterActif""ALL"
            }
        }
    }

     

    ACN

    O método Salvar é genérico e implementado em todos os webservices ao inserir uma nova entrada. No entanto, ele só insere a entrada na base de dados do TrakCare, e não no EPR.

    O método SaveWithACN implementado por todos os webservices Salvar salva os dados na base de dados e no EPR. Esse método exige dois parâmetros adicionais: ACNContext e ** ACNAction**. O número do episódio (Episode) também precisa ser definido, mesmo se não for necessário para inserções típicas.

    Todos os contextos podem ser obtidos na tabela de código MRCEncEntryType. Depois, a ação associada pode ser obtida ao chamar o webservice Salvar genérico e o método GetAction.

    Exemplos finais

    Vamos considerar o histórico médico familiar. Uma solicitação para listar todo o histórico médico familiar de um paciente será desta maneira:

    {
        "ClassName": "Region.FRXX.WebServices.REST.PAFamily.List",
        "MethodName": "List",
        "AppName": "SoapUI",
        "Request": {
            "UserName": "userWS",
            "Password": "******",
            "Params": {
                "IPP": "21002205"
            }
        }
    }
    

    Em seguida, para obter um desses registros médicos, usaremos:

    {
        "ClassName": "Region.FRXX.WebServices.REST.PAFamily.Open",
        "MethodName": "Open",
        "AppName": "SoapUI",
        "Request": {
            "UserName": "userWS",
            "Password": "demo",
            "Params": {
                "ID": "679063||2"
            }
        }
    }
    

    Para salvar um novo histórico médico no EPR, precisamos usar o seguinte JSON:

    {
        "ClassName": "Region.FRXX.WebServices.REST.PAFamily.Save",
        "MethodName": "SaveWithACN",
        "AppName": "SoapUI",
        "Request": {
            "UserName": "userWS",
            "Password": "demo",
            "Params": {
                "Episode": "196001820",
                "IPP": "21002205",
                "ACNContext": "Toutes les actions",
                "ACNAction": "TC.FMHIS",
                "MRCIDCodeOrDesc": "C254",
                "FAMRelationCodeOrDesc": "10",
                "FAMDesc": "Created with a webservice"
            }
        }
    }
    

    Por fim, para atualizar esse mesmo histórico, só enviaremos os campos modificados para o método Atualizar:

    {
        "ClassName": "Region.FRXX.WebServices.REST.PAFamily.Save",
        "MethodName": "Update",
        "AppName": "SoapUI",
        "Request": {
            "UserName": "userWS",
            "Password": "******",
            "Params": {
                "ID": "679063||1",
                "FAMDesc": "Updated with a webservice"
            }
        }
    }
    

     

    Conclusão

    Esses webservices JSON REST permitem que aplicativos de terceiros acessem e modifiquem dados do TrakCare.

    A próxima etapa seria adicionar uma camada ao Mecanismo de redirecionamento para deixar os usuários acessarem os dados com os padrões REST (separação de recursos por URL, uso de verbos HTTP, etc.)

    0
    0 82
    Artigo Danusa Calixto · Ago. 12, 2022 4m read

    A combinação da sintaxe de objetos com SQL é um dos recursos legais no Object Script. No entanto, em um caso, forneceu resultados estranhos. Portanto, decidi isolar esse caso e descrevê-lo aqui.

    Digamos que você precisa escrever um classmethod que atualiza uma única propriedade no disco. Geralmente, eu escreveria isso usando SQL, desta forma:

    ClassMethod ActivateSQL(customerId) as %Status
    {
       &sql(Update Test.Customer Set Active=1 Where ID=:customerId)
       If SQLCODE'=0 {
          Set exception = ##class(%Exception.SQL).CreateFromSQLCODE(SQLCODE, $Get(%msg))
          Quit exception.AsStatus()
       } Else {
          Quit $$$OK
       }
    }
     

    e chamaria esse classmethod sempre que necessário no meu aplicativo.

    No entanto, se o código do aplicativo tiver a instância aberta durante a chamada do classmethod e executar %Save em seguida, ele substituirá as atualizações realizadas no classmethod:

    Set objCust=##class(Test.Customer).%OpenId(id)
    Do objCust.ActivateSQL(id)
    Set objCust.Name = "something"
    Set sc = objCust.%Save()


    Ao mudar a ordem das linhas, o problema estaria resolvido, mas você precisa tomar cuidado com este tipo de combinação:

    Do ##class(Test.Customer).ActivateSQL(id)
    Set objCust=##class(Test.Customer).%OpenId(id)
    Set objCust.Name = "something"
    Set sc = objCust.%Save()


    Quando classmethod fosse escrito usando sintaxe de OO assim:

    ClassMethod ActivateOO(customerId) as %Status
    {
    Set objCust = ##class(Test.Customer).%OpenId(customerId)
    Set objCust.Active = 1
    Quit objCust.%Save()
    }

    não haveria problema, já que a instância aberta no código da chamada e a instância aberta em classmethod apontariam para a mesma instância na memória.
    (Além de uma penalidade no desempenho, já que abrir uma instância com várias propriedades para atualizar uma propriedade é mais demorado do que uma atualização do SQL)

    Portanto, para concluir: tenha cuidado ao abrir instâncias "muito longas" no seu código se também estiver usando SQL.

    Anexei uma classe de teste completa. Se quiser ver por si mesmo, chame Do ##class(Test.Customer).Test(0) para ver o código usando somente OO e .Test(1), usando SQL (observe que a atualização do SQL é substituída)
    Qualquer comentário é bem-vindo!
     

    Class Test.Customer Extends %Persistent
        {
        Property Name As%String;Property Active As%Boolean;ClassMethod ActivateSQL(customerId) As%Status
        {
            #Dim exception
        
            &sql(Update Test.Customer Set Active=1WhereID=:customerId)
            If SQLCODE'=0 {
                Set exception = ##class(%Exception.SQL).CreateFromSQLCODE(SQLCODE, $Get(%msg))
                Quit exception.AsStatus()
            }
        
            &sql(SelectName, Active Into :name, :active From Test.Customer WhereID = :customerId)
            Write !,"Result After SQL Update : ",!
            Write"Name   : ",name,!
            Write"Active : ",active,!!
            Quit
        }
        
        ClassMethod ActivateOO(customerId) As%Status
        {
            #Dim objCust as Test.Customer
            #Dim sc as%StatusSet objCust = ##class(Test.Customer).%OpenId(customerId)
            Set objCust.Active = 1Set sc = objCust.%Save()
            If sc'=$$$OKQuit sc
            &sql(SelectName, Active Into :name, :active From Test.Customer WhereID = :customerId)
            Write !,"Result After %Save : ",!
            Write"Name   : ",objCust.Name,!
            Write"Active : ",objCust.Active,!!  
            Quit
        }
        
        ClassMethod Test(mode = 0)
        {
            #Dim objCust as Test.Customer
            #Dim sc as%Status#Dim id as%Integer;Create an instance and keep the id in memorySet objCust = ##class(Test.Customer).%New()
            Set objCust.Name = "Danny"Set sc = objCust.%Save() If sc'=1Write"Could not save",!
            Set id = objCust.%Id()
            Kill objCust
        
            ;Open and display the created instanceSet objCust=##class(Test.Customer).%OpenId(id)
            Write"Name   : ",objCust.Name,!
            Write"Active : ",objCust.Active,!   
        
            ;Chame um classmethod que atualize o id com SQL ou OOIf mode=0 {
                Do objCust.ActivateOO(id)
            } else {
                Do objCust.ActivateSQL(id)
            }   
            ;Mude a instância (que ainda está na memória)Set objCust = ##class(Test.Customer).%OpenId(id)
            Set objCust.Name = objCust.Name_" - edited"Set sc = objCust.%Save() If sc'=1Write"Could not save",!
            Write"Name   : ",objCust.Name,!
            Write"Active : ",objCust.Active,!
            ;a atualização do SQL em classmethod é substituída pela instância que ainda estava na memória;Abra e demonstre a instância criadaKill objCust
            Set objCust = ##class(Test.Customer).%OpenId(id)
            Write"Name   : ",objCust.Name,!
            Write"Active : ",objCust.Active,!
        }
        }
    0
    0 121
    Anúncio Angelo Bruno Braga · Mar. 31, 2022

     A InterSystems está feliz em anunciar a versão 2.0.0 do Servidor de Idiomas para VS Code. O Servidor de Idiomas aprimora a extensão VS Code para ObjectScript disponibilizando uma melhor coloração para sintaxes, documentação incorporada, conclusão de código e mais. Informações detalhadas estão disponíveis no arquivo README do repositório no GitHub. A versão 2.0.0 adiciona suporte para várias novas arquiteturas da plataforma incluindo Macs M1! Ela também reduz o tamanho do pacote, melhora a coloração no SQL e corrige uma série de problemas detalhados no CHANGELOG. 

    0
    0 86
    Anúncio Angelo Bruno Braga · Mar. 23, 2022

    É um prazer anunciar o lançamento da versão 1.4.4 da extensão para VS Code, que contém as seguintes melhorias e correções. Os links irão levá-los para o problema  no GitHub que está sendo endereçado. Como sempre, se você já possuir a extensão instalada, o VS Code deverá atualizar automaticamente sua extensão. Se você é um novo usuário, utilize estas instruções para iniciar. 

    Log de Mudanças

    0
    1 119
    Pergunta Arian Botine · Mar. 19, 2022

    Antes de mais nada, o exemplo abaixo trata-se da manutenção de um código legado e em uma nova implementação não usaria a ^CacheTemp dessa forma.

    Em determinado momento na codificação, realizamos um $GET em uma variavel do tipo global na CacheTemp da seguinte forma:

    Set dataHora = $GET(^CacheTemp.ResAnteriores("DataColeta",pUsuario,pSessao,paciente,objOs.%Id(),pProcedimento),"")
    1
    0 167
    Artigo Rochael Ribeiro · Fev. 22, 2022 2m read

    Pessoal !

    Recentemente encontrei vários comandos ObjectScript de apenas uma linha na Comunidade de Desenvolvedores e achei que seria uma ótima ideia colecioná-los para não perdê-los!

    Decidi então juntar alguns poucos casos, colocá-los em um projeto OEX, e compartilhá-los com vocês!

    E aqui está como vocês podem utilizá-los:

    1. Criar uma configuração cliente SSL:

    set $namespace="%SYS", name="DefaultSSL" do:'##class(Security.SSLConfigs).Exists(name) ##class(Security.SSLConfigs).Create(name)

    Útil se você precisar ler conteúdo de uma URL.

    0
    0 104
    Anúncio Angelo Bruno Braga · Dez. 28, 2021

    Olá Desenvolvedores,

    Obrigado a todos pela participação no Advento do Código 2021 e por codificar em InterSystems ObjectScript! Estamos felizes em apresentar os ganhadores e distribuir os prêmios para todos ! 

    E nossos aplausos vão para esses desenvolvedores: 

       @Kevin An (w/ repo)

       @Yuval Golan (w/ repo)

       @Oliver Wilms (w/ repo)

    Também gostaríamos de recompensar outro desenvolvedor que conquistou o domínio sobre o Python Incorporado e realizou 25 desafios nele. Deem as boas vindas para:

       @Robert Cemper (w/ repo)

    0
    0 88
    Artigo Yuri Marx · Nov. 30, 2021 3m read

    O XData (https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=GOBJ_XDATA) é um recurso poderoso para definir informações de documentação e metadados para classes e métodos. A classe% CSP.REST usa XDATA para mapear chamadas REST(https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=GREST_csprest), portanto, neste artigo, você verá como usar XData em seus aplicativos como código, não apenas como documentação.

    0
    0 175
    Artigo Larissa Prussak · Out. 26, 2021 2m read

    As tecnologias da InterSystems são conhecidas por seus bancos de dados de alto desempenho, que suportam os sistemas e operações de muitas organizações. No entanto, um ingrediente chave para esse sucesso é a qualidade e a facilidade de manutenção de seu código.

    A qualidade do código pode afetar tudo, desde a velocidade e facilidade de corrigir bugs e fazer melhorias, até o desempenho geral de sua organização e sua capacidade de chegar à frente no mercado.

    Ao garantir que seu código seja sustentável, você pode reduzir aproximadamente 75% dos custos do ciclo de vida do sistema *. É por isso que, na George James Software, as soluções que construímos são sempre diretas e escritas em código de alta qualidade - porque sabemos que essa base sólida pode impactar positivamente o resto da sua organização.

    Com um sistema que pode ser mantido, você pode reduzir a manutenção geral, pois qualquer problema que ocorra é significativamente mais rápido de identificar e corrigir. Isso significa que você está livre para alocar tempo e orçamento para melhorias, permitindo que você obtenha o máximo valor de suas aplicações e, em última análise, dê um melhor suporte à sua organização.

    Fique atento às nossas próximas postagens sobre como é um sistema sustentável e as ferramentas que podem ajudá-lo a manter seu código sustentável, a fim de ajudá-lo a reduzir esses custos de manutenção.

    0
    0 78
    Pergunta Davidson Espindola · Set. 15, 2021

    Hello everyone
    I use cache script, I would like to know from you if there is any function or class in the cache where I can get the start and end date of a given month:
    Example: What is the first and last day of the month of February 2015.

    Grateful.
    Davidson

    3
    0 161
    Artigo Andre Larsen Barbosa · Ago. 18, 2021 2m read

    ObjectScript tem pelo menos três maneiras de lidar com erros (códigos de status, exceções, SQLCODE, etc.).A maior parte do código do sistema usa status, mas as exceções são mais fáceis de tratar por vários motivos.Trabalhando com código legado, você passa algum tempo traduzindo entre as diferentes técnicas.Eu uso muito esses trechos para referência.Esperançosamente, eles também são úteis para outras pessoas. 

    0
    0 173
    Artigo Larissa Prussak · Ago. 9, 2021 1m read

    Olá desenvolvedores!

    Só quero compartilhar uma prática recomendada antiga, mas sempre relevante, sobre a alteração de namespaces @Dmitry Maslennikov compartilhada comigo (de novo).

    Considere o método:

    classmethod DoSomethingInSYS() as %Status
    
    {
    
    set sc=$$$OK
    
    set ns=$namespace
    
    zn "%SYS"
    
    // try-catch in case there will be an error
    
    try {
    
    // do something, e.g. config change
    
    }
    
    catch {}
    
     zn ns    ; returning back to the namespace we came in the routine
    
    return sc
    
    }

    E com o novo $namespace, o método pode ser reescrito como:

    classmethod DoSomethingInSYS() as %Status
    
    {
    
    set sc=$$$OK
    
    new $namespace
    
    set $namespace="%SYS"
    
    // do something
    
    return sc
    
    }

    Então! A diferença é que não precisamos alterar o namespace manualmente, pois ele voltará automaticamente assim que retornarmos o método.

    e não precisamos do try-catch (pelo menos para esse propósito) também.

    0
    0 174
    Artigo Andre Larsen Barbosa · Ago. 9, 2021 11m read

    Neste artigo, vamos comparar as funções $Increment e $Sequence.

    Em primeiro lugar, uma nota para os leitores que nunca ouviram falar de $Increment. $Increment é uma função Caché ObjectScript que realiza uma operação atômica para incrementar seu argumento em 1 e retornar o valor resultante. Você só pode passar um nó de variável global ou local como um parâmetro para $Increment, não uma expressão arbitrária. $Increment é muito usado ao atribuir IDs sequenciais. Em tais casos, o parâmetro de $Increment é geralmente um nó global. $Increment garante que cada processo que o utiliza obtenha um ID exclusivo.

    `    for i=1:1:10000 {
             set Id = $Increment(^Person) ; new Id
             set surname = ##class(%PopulateUtils).LastName() ; random last name
             set name = ##class(%PopulateUtils).FirstName()  ; random first name
             set ^Person(Id) = $ListBuild(surname, name)
        }`
    

    O problema com $Increment é que se muitos processos estão adicionando linhas em paralelo, esses processos podem perder tempo esperando sua vez para alterar atomicamente o valor do nó global que contém o ID - ^ Person na amostra acima

    [$ Sequence] (http://docs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KEY=RCOS_fsequence) é uma nova função que foi projetada para lidar com esse problema. $ Sequence está disponível desde o Caché 2015.1. Assim como $ Increment, $ Sequence incrementa atomicamente o valor de seu parâmetro. Ao contrário de $ Increment, $ Sequence reservará alguns valores de contador subsequentes para o processo atual e, durante a próxima chamada no mesmo processo, simplesmente retornará o próximo valor do intervalo reservado. $ Sequence calcula automaticamente quantos valores reservar. Mais frequentemente, processar chamadas $ Sequence, mais valores $ Sequence reservas:

    USER>kill ^myseq
    
    
    USER>for i=1:1:15 {write "increment:",$Seq(^myseq)," allocated:",^myseq,! }
        increment:1 allocated:1
        increment:2 allocated:2
        increment:3 allocated:4
        increment:4 allocated:4
        increment:5 allocated:8
        increment:6 allocated:8
        increment:7 allocated:8
        increment:8 allocated:8
        increment:9 allocated:16
        increment:10 allocated:16
        increment:11 allocated:16
        increment:12 allocated:16
        increment:13 allocated:16
        increment:14 allocated:16
        increment:15 allocated:16
    

    Quando $Sequence (^ myseq) retornou 9, os próximos 8 valores (até 16) já estavam reservados para o processo atual. Se outro processo chamar $Sequence, ele obterá o valor 17, não 10.

    $Sequence é projetado para processos que incrementam simultaneamente algum nó global. Como os valores de reserva de $Sequence, os IDs podem ter lacunas se o processo não usar todos os valores que foram reservados. O principal uso de $Sequence é a geração de IDs sequenciais. Comparado com $Sequence, $Increment é uma função mais genérica.

    Vamos comparar a performance de $Increment e $Sequence:

     Class DC.IncSeq.Test 
        {
        
        ClassMethod filling()
        {
            lock +^P:"S"
            set job = $job
             for i=1:1:200000 {
                 set Id = $Increment(^Person)
                 set surname = ##class(%PopulateUtils).LastName()
                 set name = ##class(%PopulateUtils).FirstName()
                 set ^Person(Id) = $ListBuild(job, surname, name)
             }
             lock -^P:"S"
        }
        
        ClassMethod run()
        {
            kill ^Person
            set z1 = $zhorolog
            for i=1:1:10 {
                job ..filling()
             }
             lock ^P
             set z2 = $zhorolog - z1
             lock
             write "done:",z2,!
        }
        
        }
    

    O método run jobs em 10 processos, cada um inserindo 200.000 registros em ^ Person global. Para esperar até que os processos filhos terminem. o método run tenta obter um bloqueio exclusivo em ^ P. Quando os processos filhos concluem seu trabalho e liberam o bloqueio compartilhado em ^ P, a execução adquirirá um bloqueio exclusivo em ^ P e continuará a execução. Em seguida, registramos o tempo da variável de sistema $zhorolog e calculamos quanto tempo levou para inserir esses registros. Meu notebook multi-core com HDD lento levou 40 segundos (para ciência, eu o executei várias vezes antes, então esta foi a 5ª execução):

      USER>do ##class(DC.IncSeq.Test).run()
        done:39.198488
    

    É interessante detalhar esses 40 segundos. Ao executar [& # 94;% SYS.MONLBL] (http://docs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KEY=GCM_monlbl), podemos ver que um total de 100 segundos foram gastos obtendo ID. 100 segundos / 10 processos = cada processo gastou 10 segundos para adquirir um novo ID, 1,7 segundo para obter o nome e o sobrenome e 28,5 segundos para gravar dados nos dados globais.

    A primeira coluna no relatório% SYS.MONLBL abaixo é o número da linha, a segunda é quantas vezes essa linha foi executada e a terceira é quantos segundos levou para executar esta linha.

      ; ** Source for Method 'filling' **
        1            10    .001143    lock +^P:"S"
        2            10    .000055    set job = $JOB
        3            10    .000118     for i=1:1:200000 {
        4       1998499 100.356554         set Id = $Increment(^Person)
        5       1993866  10.409804         set surname = ##class(%PopulateUtils).LastName()
        6       1990461   6.347832         set name = ##class(%PopulateUtils).FirstName()
        7       1999762  285.54603         set ^Person(Id) = $ListBuild(job, surname, name)
        8       1999825   3.393706     }
        9            10    .000259     lock -^P:"S"
         ; ** End of source for Method 'filling' **
         ;
         ; ** Source for Method 'run' **
        1             1    .005503    kill ^Person
        2             1    .000002    set z1 = $zhorolog
        3             1    .000002    for i=1:1:10 {
        4            10    .201327        job ..filling()
        5             0          0     }
        6             1  43.472692     lock ^P
        7             1     .00003     set z2 = $zhorolog - z1
        8             1     .00001     lock
        9             1    .000053     write "done:",z2,!
         ; ** End of source for Method 'run' **
    

    O tempo total (43,47 segundos) é 4 segundos a mais do que durante a execução anterior devido ao perfil.

    Vamos substituir algo em nosso código de teste, no método fill. Vamos mudar $ Increment (^Person) para $Sequence (^Person) e executar o teste novamente:

    USER>do ##class(DC.IncSeq.Test).run()
        done:5.135189
    

    Este resultado é surpreendente. Ok, $Sequence diminuiu o tempo para obter a ID, mas para onde foram 28,5 segundos para armazenar dados no global? Vamos verificar ^% SYS.MONLBL:

     ; ** Source for Method 'filling' **
        1            10    .001181    lock +^P:"S"
        2            10    .000026    set job = $JOB
        3            10    .000087     for i=1:1:200000 {
        4       1802473   1.996279         set Id = $Sequence(^Person)
        5       1784910   4.429576         set surname = ##class(%PopulateUtils).LastName()
        6       1853508   3.829051         set name = ##class(%PopulateUtils).FirstName()
        7       1838752  32.281624         set ^Person(Id) = $ListBuild(job, surname, name)
        8       1951569     1.0243     }
        9            10    .000219     lock -^P:"S"
         ; ** End of source for Method 'filling' **
         ;
         ; ** Source for Method 'run' **
        1             1    .006514    kill ^Person
        2             1    .000002    set z1 = $zhorolog
        3             1    .000002    for i=1:1:10 {
        4            10    .385055        job ..filling()
        5             0          0     }
        6             1   6.558119     lock ^P
        7             1    .000011     set z2 = $zhorolog - z1
        8             1    .000008     lock
        9             1    .000025     write "done:",z2,!
         ; ** End of source for Method 'run' **
    

    Agora, cada processo gasta 0,2 segundos em vez de 10 segundos para aquisição de ID. O que não está claro é por que o armazenamento de dados leva apenas 3,23 segundos por processo? O motivo é que os nós globais são armazenados em blocos de dados e, geralmente, cada bloco tem um tamanho de 8.192 bytes. Antes de alterar o valor do nó global (como set ^Person (Id) =…), o processo bloqueia todo o bloco. Se vários processos estiverem tentando alterar dados dentro de um mesmo bloco ao mesmo tempo, apenas um processo terá permissão para alterar o bloco e os outros terão que aguardar sua conclusão.

    Vejamos o global criado usando $Increment para gerar novos IDs. Os registros sequenciais quase nunca teriam o mesmo ID do processo (lembre-se - armazenamos o ID do processo como o primeiro elemento da lista de dados):

     1:    ^Person(100000)    =    $lb("12950","Kelvin","Lydia")
        2:     ^Person(100001)    =    $lb("12943","Umansky","Agnes")
        3:     ^Person(100002)    =    $lb("12945","Frost","Natasha")
        4:     ^Person(100003)    =    $lb("12942","Loveluck","Terry")
        5:     ^Person(100004)    =    $lb("12951","Russell","Debra")
        6:     ^Person(100005)    =    $lb("12947","Wells","Chad")
        7:     ^Person(100006)    =    $lb("12946","Geoffrion","Susan")
        8:     ^Person(100007)    =    $lb("12945","Lennon","Roberta")
        9:     ^Person(100008)    =    $lb("12944","Beatty","Mark")
        10:     ^Person(100009)    =    $lb("12946","Kovalev","Nataliya")
        11:     ^Person(100010)    =    $lb("12947","Klingman","Olga")
        12:     ^Person(100011)    =    $lb("12942","Schultz","Alice")
        13:     ^Person(100012)    =    $lb("12949","Young","Filomena")
        14:     ^Person(100013)    =    $lb("12947","Klausner","James")
        15:     ^Person(100014)    =    $lb("12945","Ximines","Christine")
        16:     ^Person(100015)    =    $lb("12948","Quine","Mary")
        17:     ^Person(100016)    =    $lb("12948","Rogers","Sally")
        18:     ^Person(100017)    =    $lb("12950","Ueckert","Thelma")
        19:     ^Person(100018)    =    $lb("12944","Xander","Kim")
        20:     ^Person(100019)    =    $lb("12948","Ubertini","Juanita")
    

    Os processos simultâneos estavam tentando gravar dados no mesmo bloco e gastando mais tempo esperando do que realmente alterando os dados. Usando $ Sequence, os IDs são gerados em blocos, portanto, processos diferentes provavelmente usariam blocos diferentes:

       1:     ^Person(100000)    =    $lb("12963","Yezek","Amanda")
        // 351 records with process number 12963
        353:     ^Person(100352)    =    $lb("12963","Young","Lola")
        354:     ^Person(100353)    =    $lb("12967","Roentgen","Barb")
    

    Se este exemplo parece algo que você está fazendo em seus projetos, considere o uso de $Sequence em vez de $Increment. Claro, consulte a [documentação] (http://docs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KEY=RCOS_fsequence) antes de substituir cada ocorrência de $Increment por $Sequence.

    E, claro, não acredite nos testes fornecidos aqui - verifique você mesmo.

    A partir do Caché 2015.2, você pode configurar tabelas para usar $Sequence em vez de $Increment. Existe uma função de sistema [$system.Sequence.SetDDLUseSequence] (http://docs.intersystems.com/latest/csp/documatic/%25CSP.Documatic.cls?PAGE=CLASS&LIBRARY=%25SYS&CLASSNAME=%25SYSTEM.SQL#METHOD_SetDDLUseSequence ) para isso, e a mesma opção está disponível em Configurações de SQL no Portal de gerenciamento.

    Além disso, há um novo parâmetro de armazenamento na definição de classe - [IDFunction] (http://docs.intersystems.com/latest/csp/documatic/%25CSP.Documatic.cls?PAGE=CLASS&LIBRARY=%25SYS&CLASSNAME=%25Dictionary.StorageDefinition #PROPERTY_IdFunction), que é definido como “incremento” por padrão, o que significa que $ Increment é usado para geração de Id. Você pode alterá-lo para "sequência" (Inspetor> Armazenamento> Padrão> IDFunction).

    Bônus

    Outro teste rápido que realizei no meu notebook: é uma pequena configuração ECP com DB Server localizado no sistema operacional host e Application Server na VM convidada no mesmo notebook. Mapeei ^ Pessoa para banco de dados remoto. É um teste básico, então não quero fazer generalizações com base nele. Existem [coisas a serem consideradas] (http://docs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KEY=GDDM_develop#GDDM_develop_consider_increment) ao usar $ Increment e ECP. Dito isso, aqui estão os resultados:

    ### Com $Increment:
    
        USER>do ##class(DC.IncSeq.Test).run()
        done:163.781288
    
    ^%SYS.MONLBL:
        
         ; ** Source for Method 'filling' **
        1            10    .000503         --     lock +^P:"S"
        2            10    .000016    set job = $job
        3            10    .000044    for i=1:1:200000 {
        4       1843745 1546.57015        set Id = $Increment(^Person)
        5       1880231   6.818051        set surname = ##class(%PopulateUtils).LastName()
        6       1944594   3.520858        set name = ##class(%PopulateUtils).FirstName()
        7       1816896  16.576452        set ^Person(Id) = $ListBuild(job, surname, name)
        8       1933736    .895912    }
        9            10    .000279    lock -^P:"S"
         ; ** End of source for Method 'filling' **
         ;
         ; ** Source for Method 'run' **
        1             1    .000045    kill ^Person
        2             1    .000001    set z1 = $zhorolog
        3             1    .000007    for i=1:1:10 {
        4            10    .059868        job ..filling()
        5             0          0    }
        6             1 170.342459    lock ^P
        7             1    .000005    set z2 = $zhorolog - z1
        8             1    .000013    lock
        9             1    .000018    write "done:",z2,!
         ; ** End of source for Method 'run' **
        
    
    ### $Sequence:
    
        USER>do ##class(DC.IncSeq.Test).run()
        done:13.826716
    
    ^%SYS.MONLBL
    
         ; ** Source for Method 'filling' **
        1            10    .000434     lock +^P:"S"
        2            10    .000014    set job = $job
        3            10    .000033    for i=1:1:200000 {
        4       1838247  98.491738        set Id = $Sequence(^Person)
        5       1712000   3.979588        set surname = ##class(%PopulateUtils).LastName()
        6       1809643   3.522974        set name = ##class(%PopulateUtils).FirstName()
        7       1787612  16.157567        set ^Person(Id) = $ListBuild(job, surname, name)
        8       1862728    .825769    }
        9            10    .000255    lock -^P:"S"
         ; ** End of source for Method 'filling' **
         ;
         ; ** Source for Method 'run' **
        1             1    .000046    kill ^Person
        2             1    .000002    set z1 = $zhorolog
        3             1    .000004    for i=1:1:10 {
        4            10    .037271        job ..filling()
        5             0          0    }
        6             1  14.620781    lock ^P
        7             1    .000005    set z2 = $zhorolog - z1
        8             1    .000013    lock
        9             1    .000016    write "done:",z2,!
         ; ** End of source for Method 'run' **
    
    0
    0 225