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
.png)
Demonstração
Para esse exemplo, foi definido o seguinte armazenamento NoSQL:
Para teste, o procedimento armazenado pode ser executado em um terminal:
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:
///Ref: Defining and Using Class Queries
///https://docs.intersystems.com/iris20232/csp/docbook/DocBook.UI.Page.cls?KEY=GOBJ_queries
Classalwo.PyProcTest [ Abstract ]
{
///<example>
///do $SYSTEM.SQL.Shell()
///call alwo.PyProcTest_GetNotes('D')
///</example>
QueryGetNotes(tabNameAs%String) As%Query(ROWSPEC = "Tab:%String,NoteId:%Integer,NoteText:%String") [ SqlName = PyProcTest_GetNotes, SqlProc ]
{
}
///ObjectScript due to ByRef signature
ClassMethodGetNotesExecute(ByRefqHandleAs%Binary, tabNameAs%String = "") As%Status
{
setqHandle=##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
ClassMethodGetNotesFetch(ByRefqHandleAs%Binary, ByRefRowAs%List, ByRefAtEndAs%Integer = 0) As%Status [ PlaceAfter = GetNotesExecute ]
{
setrefRow=##class(alwo.PyNote.GetNotes.Row).%New()
setstatus=..GetNotesFetchPy(.qHandle,.refRow)
ifqHandle.atEnd {
setAtEnd=1
} else {
// repack output row to $List format
setRow=$ListBuild(refRow.Tab,+refRow.NoteId,refRow.NoteText)
}
Quitstatus
}
///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>
ClassMethodGetNotesFetchPy(qHandleAsalwo.PyNote.GetNotes.qHandle, pRowAsalwo.PyNote.GetNotes.Row) As%String [ Language = python ]
{
importiris
importtraceback
ret=iris.cls('%SYSTEM.Status').OK()
try:
# basedontheexistanceofdefinednodestheniterate
gname="^alwo.IndexBook"
gIterator=iris.gref(gname)
# IterateonKey1"Tab name"whenKey2"NoteId"waspreviouslysettoempty
if (None==qHandle.currentPage) or (""==qHandle.currentPage):
qHandle.currentTab=gIterator.order([qHandle.currentTab])
# changeoftabcontext
if (None==qHandle.currentTab) or (qHandle.currentTab==""): # norecords
qHandle.atEnd=True
returnret
# defaultopenfirsttabifhasvalues
ifqHandle.selectedTab==NoneorqHandle.selectedTab=="":
qHandle.selectedTab=qHandle.currentTab
pRow.Tab=qHandle.currentTab
#x=10/0 # uncommenttodemonstrateZeroDivisionErrorhandling
# IterateonKey2"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])
# checksifcurrentrecordwasthelastone
next=gIterator.order([qHandle.currentTab,qHandle.currentPage])
if (None==next) or (""==next):
qHandle.currentPage=None # causesiterateonKey1onnextmethodinvocation
exceptException:
pErrorMessage='alwo.PyProcTest::GetNotesFetchPy:'+(traceback.format_exc())
returniris.cls('%SYSTEM.Status').Error(2603,pErrorMessage)
returnret
}
///ObjectScript due to ByRef signature
ClassMethodGetNotesClose(ByRefqHandleAs%Binary) As%Status
{
setqHandle=""
Quit$$$OK
}
}
Classe helper qHandle. É inicializada em Execute e atualizada durante a recuperação de registro.
Classalwo.PyNote.GetNotes.qHandleExtends%RegisteredObject
{
PropertycurrentTabAs%String;
PropertycurrentPageAs%String;
PropertyselectedTabAs%String;
PropertyatEndAs%Integer [ InitialExpression = 0 ];
}
Classe helper para preencher linhas do Python com nomes legíveis:
Classalwo.PyNote.GetNotes.RowExtends%RegisteredObject
{
PropertyTabAs%String;
PropertyNoteIdAs%String;
PropertyNoteTextAs%String;
}
Espero que esse exemplo seja útil para explorar algumas novas ideias e possibilidades com o Embedded.