Artigo
· Nov. 28, 2023 8min de leitura

Procedimento armazenado (stored procedure) em Python embutido

Visão geral

A documentação online contém o tópico Defining and Using Class Queries (Definir e usar consultas de classe) para referência-

A personalização direta de procedimentos armazenados com ObjectScript tem sido útil para acessar o armazenamento NoSQL e as mensagens externas pela integração, para apresentar a saída em um formato tabular.

Por exemplo: um aplicativo que já usa 90% da interação SQL de um front-end também pode estender esse acesso aos outros 10% da funcionalidade de plataforma necessária, pelo mesmo acesso SQL.

A finalidade deste artigo é explorar como alcançar o mesmo efeito usando os métodos do Embedded Python.

Figura 1: procedimento armazenado como um SQL gateway para outra funcionalidade de plataforma

Demonstração

Para esse exemplo, foi definido o seguinte armazenamento NoSQL:

^alwo.IndexBook("A",1)="abc"
^alwo.IndexBook("A",2)="def"
^alwo.IndexBook("B")=""
^alwo.IndexBook("C")=""
^alwo.IndexBook("D",1)="gef"
^alwo.IndexBook("E",1)="ijk"
^alwo.IndexBook("E",2)="lmn"

Para teste, o procedimento armazenado pode ser executado em um terminal:

GBI>Do $SYSTEM.SQL.Shell()
SQL Command Line Shell
----------------------------------------------------
 
The command prefix is currently set to: <<nothing>>.
Enter <command>, 'q' to quit, '?' for help.

[SQL]GBI>>call alwo.PyProcTest_GetNotes('A')

Dumping result #1
Tab     NoteId  NoteText
A       1       abc
A       2       def
B       0
C       0
D       0
E       0
 
6 Rows(s) Affected
statement prepare time(s)/globals/cmds/disk: 0.0003s/4/100/0ms
          execute time(s)/globals/cmds/disk: 0.0009s/12/1,096/0ms
                                query class: %sqlcq.GBI.cls27, %Library.ProcedureContext
---------------------------------------------------------------------------

Como foi fornecida a aba ( primeira chave ) "A", os dados dos sub-nós são expandidos e retornados como registros.

Os outros nós que não foram selecionados ou não contêm dados são retornados como registros "0".

Conceitos de código

Quando uma consulta ( GetNotes ) é implementada em uma classe, o conteúdo da consulta pode ser obtido apenas com SQL.

Após a compilação, são gerados três class methods:

  • GetNotesExecute
  • GetNotesFetch
  • GetNotesClose

Há vários cenários em que os dados não são SQL:

  • Globais NoSQL
  • Interação parametrizada com um sistema externo para recuperar e consolidar dados

Ao implementar esses três métodos diretamente, é possível controlar o acesso e dar respostas tabulares para uma ampla variedade de recursos de plataforma.

Estas são algumas das variações de interação:

Armazenar em cache todos os dados de resposta antecipadamente

1. O método GetNotesExecute acessaria os recursos para criar uma reposta em um global temporário.

Isso seria útil para uma visão consistente sobre os dados, o que pode envolver o bloqueio do acesso a atualizações por um breve período.

2. O método GetNotesFetch seria chamado repetidamente, retornando registros dos dados temporários

3. The GetNotesClose limparia e excluiria os dados temporários

Dados de respostas dinâmicas

1. O método GetNotesExecute é chamado. Isso não faz muito coisa além de iniciar um contexto qHandle disponível para o método Fetch

2. O método GetNotesFetch é chamado. Sempre que um novo registro é recuperado dinamicamente

3. O método GetNotesClose exige pouca ou nenhuma limpeza

Essa é a abordagem usada no código de exemplo.

Oportunidades de paginação etc.

Dependendo do cenário, o preenchimento dinâmico de lotes de registros de retorno pode ser usado para reduzir a necessidade de executar uma "consulta completa" quando só é necessária uma área da fatia de registros de retorno.

O código

O método execute tem uma expressão $C(0). Isso serve apenas para corresponder a uma string Null, que é diferente de uma string vazia.

Uma string nula pode ser passada quando um procedimento armazenado é invocado com um argumento de string vazio.

O método GetNotesFetch atua como um wrapper objectscript para GetNotesFetchPy, onde ocorre o trabalho de verdade. A lógica é a expectativa de o framework de chamada usar os argumentos ByRef e o wrapper unir isso.

O código é um exemplo de como navegar e recuperar dados NoSQL por código Python.

A implementação em Python usa um bloco try-except para interromper problemas de ambiente de execução do código Python e propagar esses detalhes das informações de erros da maneira normal de volta ao aplicativo do cliente. Isso pode ser ativado ao descomentar a linha que começa com "#x=10/0".

Por exemplo, o erro interrompido retornado ao cliente:

[SQLCODE: <-400>:<Fatal error occurred>]
[%msg: <Python general error 'alwo.PyProcTest::GetNotesFetchPy:Traceback (most recent call last):
                   File "PyProcTest", line 21, in GetNotesFetchPy
                                                                 ZeroDivisionError: division by zero
                    '>]

 


/// Ref: Defining and Using Class Queries
Class alwo.PyProcTest [ Abstract ]
{

 

/// <example>
/// do $SYSTEM.SQL.Shell()
/// call alwo.PyProcTest_GetNotes('D')
/// </example>
Query GetNotes(tabName As %String) As %Query(ROWSPEC = "Tab:%String,NoteId:%Integer,NoteText:%String") [ SqlName = PyProcTest_GetNotes, SqlProc ]
{
}

 

/// ObjectScript due to ByRef signature
ClassMethod GetNotesExecute(ByRef qHandle As %Binary, tabName As %String = "") As %Status
{
  set qHandle=##class(alwo.PyNote.GetNotes.qHandle).%New()
  // Note that an empty string passed from SQL statement may appear as the null character $C(0) instead of empty string ""
  set:tabName'=$C(0) qHandle.selectedTab=tabName // may be empty string
  Quit $$$OK
}

 

/// ObjectScript due to ByRef signature
ClassMethod GetNotesFetch(ByRef qHandle As %Binary, ByRef Row As %List, ByRef AtEnd As %Integer = 0) As %Status [ PlaceAfter = GetNotesExecute ]
{
  set refRow=##class(alwo.PyNote.GetNotes.Row).%New()
 
  set status=..GetNotesFetchPy(.qHandle,.refRow)
  if qHandle.atEnd {
    set AtEnd=1
  } else {
    // repack output row to $List format
    set Row=$ListBuild(refRow.Tab,+refRow.NoteId,refRow.NoteText)
  }
  Quit status
}

 

/// Access to tabular view of global 2 keys deep with data at level 2 nodes
/// <example>
/// zwrite ^alwo.IndexBook
///
/// ^alwo.IndexBook("A",1)="abc"
/// ^alwo.IndexBook("A",2)="def"
/// ^alwo.IndexBook("B")=""
/// ^alwo.IndexBook("C")=""
/// ^alwo.IndexBook("D",1)="gef"
/// ^alwo.IndexBook("E",1)="ijk"
/// ^alwo.IndexBook("E",2)="lmn"
/// <example>
///
/// Required output
/// <example>
/// | Tab | NoteId | NoteText
/// --------------------------
/// | A   | 1      | abc
/// | A   | 2      | def
/// | B   | 0      |
/// | C   | 0      |
/// | D   | 1      | gef
/// | E   | 1      | ijk
/// | E   | 2      | lmn
/// --------------------------
/// </example>
ClassMethod GetNotesFetchPy(qHandle As alwo.PyNote.GetNotes.qHandle, pRow As alwo.PyNote.GetNotes.Row) As %String [ Language = python ]
{
import iris
import traceback
ret=iris.cls('%SYSTEM.Status').OK()
try:
  # based on the existance of defined nodes then iterate
  gname="^alwo.IndexBook"
  gIterator=iris.gref(gname)
  # Iterate on Key1 "Tab name" when Key2 "NoteId" was previously set to empty
  if (None==qHandle.currentPage) or (""==qHandle.currentPage):
    qHandle.currentTab=gIterator.order([qHandle.currentTab])
    # change of tab context
    if (None==qHandle.currentTab) or (qHandle.currentTab==""):  # no records
      qHandle.atEnd=True
      return ret
    # default open first tab if has values
    if qHandle.selectedTab==None or qHandle.selectedTab=="":
      qHandle.selectedTab=qHandle.currentTab
  pRow.Tab=qHandle.currentTab
  #x=10/0 # uncomment to demonstrate ZeroDivisionError handling
  # Iterate on Key2 "NoteId"
  if (qHandle.selectedTab==qHandle.currentTab):
    qHandle.currentPage=gIterator.order([qHandle.currentTab, qHandle.currentPage])
    if (qHandle.currentPage!=None) and (qHandle.currentPage!=""):
      pRow.NoteId=qHandle.currentPage
      pRow.NoteText=gIterator.get([qHandle.currentTab, qHandle.currentPage])
      # checks if current record was the last one
      next=gIterator.order([qHandle.currentTab, qHandle.currentPage])
      if (None==next) or (""==next):
        qHandle.currentPage=None  # causes iterate on Key1 on next method invocation
except Exception:
 pErrorMessage='alwo.PyProcTest::GetNotesFetchPy:'+(traceback.format_exc())
 return iris.cls('%SYSTEM.Status').Error(2603,pErrorMessage)
return ret
}

 

/// ObjectScript due to ByRef signature
ClassMethod GetNotesClose(ByRef qHandle As %Binary) As %Status
{
  set qHandle=""
  Quit $$$OK
}

 

}

Classe helper qHandle. É inicializada em Execute e atualizada durante a recuperação de registro.

Class alwo.PyNote.GetNotes.qHandle Extends %RegisteredObject
{

 

Property currentTab As %String;

 

Property currentPage As %String;

 

Property selectedTab As %String;

 

Property atEnd As %Integer [ InitialExpression = 0 ];

 

}

Classe helper para preencher linhas do Python com nomes legíveis:

Class alwo.PyNote.GetNotes.Row Extends %RegisteredObject
{

 

Property Tab As %String;

 

Property NoteId As %String;

 

Property NoteText As %String;

 

}

 

Espero que esse exemplo seja útil para explorar algumas novas ideias e possibilidades com o Embedded.

Discussão (0)1
Entre ou crie uma conta para continuar