15 applications de réelle qualité 😀
Merci à tous les participants pour leur implication et leurs idées.
- Se connecter pour publier des commentaires
15 applications de réelle qualité 😀
Merci à tous les participants pour leur implication et leurs idées.
Bonjour @Julia Pertin
peux-tu me donner plus de détails sur l'erreur rencontrée ?
Vérifies-bien si tu recrées une production from scratch, en ajoutant service-process-operation, de conserver les noms les BS-BP-BO identiques aux noms des 3 classes EP.service.replication, EP.process.replication et EP.operation.replication, et qu'ils sont bien reliés entre eux.
L'intérêt du Ens.StreamContainer est de dépasser la limite des 3,6Mo du type %String.
Exemple d'une trace avec un flux json de 5,79 Mo (stream size: 6081494) pour 100000 enregistrements :
.png)
.png)
.png)
Le code de la Business Opération :
Class EP.operation.replication Extends Ens.BusinessOperation
{
Property Adapter As Ens.OutboundAdapter;Parameter ADAPTER = "Ens.OutboundAdapter";Parameter INVOCATION = "Queue";
Method sync(pRequest As Ens.StreamContainer, Output pResponse As Ens.StringContainer) As%Status
{
set pResponse = ##class(Ens.StringContainer).%New()
set json = [].%FromJSON(pRequest.Stream)
set array = json.%GetIterator()
while array.%GetNext(.key,.value) {
$$$TRACE("Data:"_value.%ToJSON())
set a = ##class(data.client2).%New()
set sc = a.%JSONImport(value)
if 'sc {
$$$LOGERROR("ERROR while importing:"_$system.Status.GetErrorText(sc))
} else {
set sc = a.%Save()
}
}
set pResponse.StringValue = key _" records inserted"return sc
}
XData MessageMap
{
<MapItems>
<MapItem MessageType="Ens.StreamContainer">
<Method>sync</Method>
</MapItem>
</MapItems>
}
}
Le code du Business Process
Class EP.process.replication Extends Ens.BusinessProcess [ ClassType = persistent, ProcedureBlock ]
{
Property Server As%String [ InitialExpression = "host.docker.internal" ];Property Port As%Integer [ InitialExpression = 12773 ];Property classname As%String [ InitialExpression = "data.client" ];Parameter SETTINGS = "Server:target,Port:target,classname:target";
Method OnRequest(pRequest As Ens.Request, Output pResponse As Ens.StringContainer) As%Status
{
set sc = $$$OKset tHttpRequest = ##class(%Net.HttpRequest).%New()
set pContainer = ##class(Ens.StreamContainer).%New()
set stream = ##class(%Stream.GlobalCharacter).%New()
set tHttpRequest.Server = ..Serverset tHttpRequest.Port = ..Portset location = "/common/list/" _ ..classname _ "/json"set tHttpRequest.ContentType = "application/json"set tHttpRequest.Timeout = 900set tSC = tHttpRequest.Get(location)
if$ISOBJECT(tHttpRequest.HttpResponse) {
Set jsonObject = [].%FromJSON(tHttpRequest.HttpResponse.Data)
Do jsonObject.%ToJSON(.stream)
set pContainer.Stream = stream
$$$TRACE("stream size:"_stream.Size)
$$$TRACE("stream data:"_stream.Read(1000))
set sc = ..SendRequestSync("EP.operation.replication",pContainer,.pResponse)
}
return sc
}
Storage Default
{
<Data name="replicationDefaultData">
<Subscript>"replication"</Subscript>
<Value name="1">
<Value>Server</Value>
</Value>
<Value name="2">
<Value>Port</Value>
</Value>
<Value name="3">
<Value>classname</Value>
</Value>
</Data>
<DefaultData>replicationDefaultData</DefaultData>
<Type>%Storage.Persistent</Type>
}
}
Le code du Business Service :
Class EP.service.replication Extends Ens.BusinessService
{
Property Adapter As Ens.InboundAdapter;Parameter ADAPTER = "Ens.InboundAdapter";Property TargetConfigNames As%String(MAXLEN = 1000);Property sync As%Boolean [ InitialExpression = 1 ];Parameter SETTINGS = "sync:Advanced,TargetConfigNames:Basic:selector?multiSelect=1&context={Ens.ContextSearch/ProductionItems?targets=1&productionName=@productionId}";
Method OnProcessInput(pInput As%RegisteredObject, Output pResponse As Ens.StringResponse) As%Status
{
set tSC = $$$OKfor iTarget=1:1:$L(..TargetConfigNames, ",") {
set tOneTarget=$ZStrip($P(..TargetConfigNames,",",iTarget),"<>W") Continue:""=tOneTarget
$$$TRACE("The target '"_tOneTarget_"' will be called "_$SELECT(..sync=1:"synchronously",..sync=0:"synchronously"))
if..sync {
set tSC1=..SendRequestSync(tOneTarget,pInput,.pResponse) Set:$$$ISERR(tSC1) tSC=$$$ADDSC(tSC,tSC1)
} else {
set tSC1=..SendRequestAsync(tOneTarget,pInput) Set:$$$ISERR(tSC1) tSC=$$$ADDSC(tSC,tSC1)
}
}
return tSC
}
/// Return an array of connections for drawing lines on the config diagramClassMethod OnGetConnections(Output pArray As%String, pItem As Ens.Config.Item)
{
Do##super(.pArray,pItem)
If pItem.GetModifiedSetting("TargetConfigNames",.tValue) {
For i=1:1:$L(tValue,",") { Set tOne=$ZStrip($P(tValue,",",i),"<>W") Continue:""=tOne Set pArray(tOne)="" }
}
}
}
Pour illustrer avec un exemple concret, j'ai développé un flux qui devrait t'aider à réaliser ce que tu veux :
.png)
Trace avec appel asynchrone :.png)
Trace avec appel synchrone :
.png)
Avec la réponse qui remonte bien jusqu'au Business Service :
.png)
Bonjour @Julia Pertin,
si j'ai bien compris ton besoin, pour régler ton problème qui consiste à éviter de récupérer plusieurs fois le même flux json, ton Business Service doit servir uniquement à appeler un Business Process, qui lui va se charger de récupérer le flux json pour le passer à une Business Operation.
Dans le cas où ton Business Service appelle le Business Process de manière synchrone, il attendra bien la réponse avant de s'exécuter à nouveau.
Dans le cas contraire, c'est à dire si le Business Process est appelé de manière asynchrone, le Business Service fera un nouvel appel à chaque atteinte du temps prévu par le paramètre "INTERVALLE ENTRE APPELS".
Réponse de @Dmitry Maslennikov
Pour quelques versions déjà, Caché et IRIS prennent déjà en charge OAuth2 dès le départ. Regardez la documentation.
Cela dépend de votre architecture, vous pouvez utiliser cette méthode ou utiliser n'importe quel fournisseur d'identité externe comme keycloack.
Et je pense qu'IAM peut également vous aider.
Réponse de @Rob Tweed
Nous avons deux produits Open Source qui prendront en charge les JWT pour vous de la manière correspondant à ce que vous demandez (c'est-à-dire les services REST avec IRIS) :
- QEWD, si vous souhaitez tout implémenter en back-end dans Node.js / JavaScript
- mgweb-server si vous souhaitez utiliser la logique ObjectScript pour votre logique back-end
Pour QEWD et IRIS, voir :
https://github.com/robtweed/qewd-starter-kit-iris-networked
En particulier pour les services REST, voir :
https://github.com/robtweed/qewd-starter-kit-iris-networked/blob/master/...
et plus précisément cette section:
https://github.com/robtweed/qewd-starter-kit-iris-networked/blob/master/...
Pour le serveur mgweb, voir :
https://github.com/robtweed/mgweb-server
spécifiquement utilisé avec IRIS :
https://github.com/robtweed/mgweb-server/blob/master/IRIS.md
et dans ce document, cette section sur les JWT :
https://github.com/robtweed/mgweb-server/blob/master/IRIS.md#using-json-...
Rob
Hâte de voir quels sont les gagnants des prix InterSystems 😀
Je t'en prie, mon cher @Robert Cemper ; merci à toi de continuer de publier et d'aider la communauté à un rythme aussi soutenu.
@Luis Angel Pérez Ramos propose une autre solution, hors Embedded Python :
Eh bien, si votre instance IRIS ne prend pas en charge Embedded Python, vous pouvez procéder comme suit (si vous utilisez un serveur Linux) :
$ZF(-100,"","magick","\usr\image.pdf","\usr\image.jpg")Dans ce cas, @Ben Spead propose d'installer Python sur le serveur avec la bibliothèque que l'on souhaitez utiliser et d'appeler le script Python via $zf(-100). Plus tard, lorsque d'une mise à niveau de l'environnement, il sera possible de passer directement à l’utilisation de Embedded Python.
Malheureusement, l'environnement de @Gilberto Alves n'est pas prêt à utiliser Embedded Python.
@Ben Spead a donné une première réponse en proposant d'utiliser Embedded Python :
Cela ressemble à un exemple classique d'exploitation de Python intégré dans IRIS pour utiliser le très grand nombre de bibliothèques Python qui font à peu près n'importe quoi :) Voici un article que j'ai trouvé lors d'une recherche rapide sur Google sur la façon de procéder en Python :
https://www.geeksforgeeks.org/convert-pdf-to-image-using-python/
Vous pouvez simplement exploiter les capacités Python d'InterSystems IRIS pour utiliser la façon dont ce problème a été résolu en Python !
Oui, le GRANT on SCHEMA ou GRANT on * simplifie clairement la chose 😀
Cela fait partie du standard SQL mais on n'y pense pas d'emblée.
Cela fonctionne aussi pour tous les objets d'un namespace :
GRANT SELECT ON * TOROLE|USER
@Alexander Koblov vient de me donner la solution.
La commande GRANT ... ON SCHEMA ... répond parfaitement au besoin.
GRANT SELECT ON SCHEMA data TO ROLE|USER
Et ce qui est cool, c'est que dès que votre utilisateur a reçu un GRANT sur un schéma, lorsque vous modifiez ensuite l'utilisateur dans le portail de gestion de la sécurité, vous pouvez voir dans l'onglet Tables SQL toutes les tables apparaître au fur et à mesure de leur création.
.png)
Bonjour @Franck Hanotin
en utilisant un paramètre, tu peux utiliser le même code sur plusieurs globales :
Class dc.axiell
{
Query data(globalName As %String) As %Query(ROWSPEC = "key1:%String,key2:%String,key3:%String,key4:%String,key5:%String,key6:%String,datavalue:%String") [ SqlProc ]
{
}
ClassMethod dataExecute(ByRef qHandle As %Binary, globalName As %String) As %Status
{
Set qHandle("node") = globalName
Quit $$$OK
}
ClassMethod dataFetch(ByRef qHandle As %Binary, ByRef Row As %List, ByRef AtEnd As %Boolean) As %Status [ PlaceAfter = dataExecute ]
{
Set sc = $$$OK
Set qHandle("node") = $Query(@qHandle("node"), 1, data)
If qHandle("node") = "" Set Row = "", AtEnd = $$$YES Quit $$$OK
; feeds the key x fields based on the subscripts of the global
For i=1:1:$QLength(qHandle("node")) Set $List(Row, i) = $QSubscript(qHandle("node"), i)
If i < 6 { ; if we do not have 6 subscripts, we feed the rest with an empty string
For j = i+1:1:6 Set $List(Row, j) = ""
}
Set $List(Row, 7) = data, AtEnd = $$$NO
Quit sc
}
ClassMethod dataClose(ByRef qHandle As %Binary) As %Status [ PlaceAfter = dataExecute ]
{
Kill qHandle Quit $$$OK
}
/// just for some test data
ClassMethod set() As %Status
{
set sc = $$$OK
kill ^AFO
s ^AFO("Site","Ville")="66722,3743"
s ^AFO("Site","Ville","111BB","OBT")=",MMM,XXX,"
s ^AFO("Site","Ville","111OW","OBT")=",XXX,MMM,"
s ^AFO("Site","Ville","AANVRBIBS","zzz") = "1^^1"
s ^AFO("Site","Ville","AANVRBIBS","zzz","*","dut") = "*afhalen waar gevonden"
s ^AFO("Site","Ville","AANVRBIBS","zzz","*","eng") = "*Pickup where found"
s ^AFO("Site","Ville","AANVRBIBS","zzz","*","fre") = "*Lieu où trouvé"
kill ^AAA
s ^AAA(1,2)="66722,3743"
s ^AAA(1,2,"3","4") =",MMM,XXX,"
s ^AAA(1,2,"4","5") =",XXX,MMM,"
s ^AAA(1,2,3,4) = "1^^1"
s ^AAA(1,2,3,4,"*","dut") = "*afhalen waar gevonden"
s ^AAA(1,2,3,4,"*","eng") = "*Pickup where found"
s ^AAA(1,2,3,4,"*","fre") = "*Lieu où trouvé"
kill ^BBB
s ^BBB("en") ="Hello"
s ^BBB("en","sub") ="World"
s ^BBB(1,2,3) ="BBB"
s ^BBB(1,2,3,4) = "BBB^^BBB"
kill ^CCC
s ^CCC("fr") ="Bonjour"
s ^CCC("fr","sub") ="la Communauté"
s ^CCC(1,2,3,"QUATRE","CINQ","SIX") ="6"
s ^CCC(1,2,3,4,"5","6") ="6"
s ^CCC("UN","DEUX","TROIS") ="3"
return sc
}
}
IRISAPP>:sql
SQL Command Line Shell
----------------------------------------------------
The command prefix is currently set to: <<nothing>>.
Enter <command>, 'q' to quit, '?' for help.
[SQL]IRISAPP>>select * from dc.axiell_data('^AFO')
34. select * from dc.axiell_data('^AFO')
key1 key2 key3 key4 key5 key6 datavalue
Site Ville 66722,3743
Site Ville 111BB OBT ,MMM,XXX,
Site Ville 111OW OBT ,XXX,MMM,
Site Ville AANVRBIBS zzz 1^^1
Site Ville AANVRBIBS zzz * dut *afhalen waar gevonden
Site Ville AANVRBIBS zzz * eng *Pickup where found
Site Ville AANVRBIBS zzz * fre *Lieu où trouvé
7 Rows(s) Affected
statement prepare time(s)/globals/cmds/disk: 0.0003s/4/139/0ms
execute time(s)/globals/cmds/disk: 0.0023s/11/3,294/0ms
query class: %sqlcq.IRISAPP.cls59
---------------------------------------------------------------------------
[SQL]IRISAPP>>select * from dc.axiell_data('^AAA')
35. select * from dc.axiell_data('^AAA')
key1 key2 key3 key4 key5 key6 datavalue
1 2 66722,3743
1 2 3 4 1^^1
1 2 3 4 * dut *afhalen waar gevonden
1 2 3 4 * eng *Pickup where found
1 2 3 4 * fre *Lieu où trouvé
1 2 4 5 ,XXX,MMM,
6 Rows(s) Affected
statement prepare time(s)/globals/cmds/disk: 0.0002s/4/139/0ms
execute time(s)/globals/cmds/disk: 0.0014s/10/3,099/0ms
query class: %sqlcq.IRISAPP.cls59
---------------------------------------------------------------------------
[SQL]IRISAPP>>select * from dc.axiell_data('^BBB')
36. select * from dc.axiell_data('^BBB')
key1 key2 key3 key4 key5 key6 datavalue
1 2 3 BBB
1 2 3 4 BBB^^BBB
en Hello
en sub World
4 Rows(s) Affected
statement prepare time(s)/globals/cmds/disk: 0.0003s/4/139/0ms
execute time(s)/globals/cmds/disk: 0.0045s/8/2,702/0ms
query class: %sqlcq.IRISAPP.cls59
---------------------------------------------------------------------------
[SQL]IRISAPP>>select * from dc.axiell_data('^CCC')
37. select * from dc.axiell_data('^CCC')
key1 key2 key3 key4 key5 key6 datavalue
1 2 3 4 5 6 6
1 2 3 QUATRE CINQ SIX 6
UN DEUX TROIS 3
fr Bonjour
fr sub la Communauté
5 Rows(s) Affected
statement prepare time(s)/globals/cmds/disk: 0.0002s/4/139/0ms
execute time(s)/globals/cmds/disk: 0.0018s/9/2,899/0ms
query class: %sqlcq.IRISAPP.cls59
---------------------------------------------------------------------------
[SQL]IRISAPP>>q
IRISAPP>
Bonjour Franck,
une autre façon consiste à définir un mapping utilisant le SQLStorage (comme nous avions commence à l'explorer ensemble).
Même si Lorenzo t'a déjà donné la solution avec $Query, à toute fin utile pour d'autres cas, je t'invite à consulter les articles de @Brendan Bannon
Bonjour @yurimarx Marx,
et aujourd'hui, vous pouvez ajouter à cette liste :
Et cette diapositive date de plus de 2 ans...
Bonjour @Yuri Marx,
en le tout premier sujet, je mettrai en tête de liste :
0. meilleures performances
Cela peut paraître évident, mais IRIS est **vraiment** plus rapide que Caché.
.png)
Le moyen le plus simple de toujours rester dans le siècle en cours est :
$ZDATEH("26/05/23",4,,6)Il suffit d'utiliser yearopt = 6 pour obtenir toutes les dates qui n'ont que 2 chiffres dans le siècle courant.
w $zdt($ZDATEH("26/05/23",4,,6),3)
2023-05-26
w $zdt($ZDATEH("26/05/1923",4,,6),3)
1923-05-26Pour activer l'ensemble des événements d'AUDIT système, il suffit d'exécuter la requête SQL suivante depuis l'espace de noms %SYS :
update security.events set enabled=1 where flags = 1
exemple :
set tRes = ##class(%SQL.Statement).%ExecDirect(,"update security.events set enabled=1 where flags = 1")
if tRes.%SQLCODE=0 {
set ^["USER"]TRACE("%SYS Security.Events")=tRes.%ROWCOUNT_" successfully enabled"
} else {
set ^["USER"]TRACE("%SYS Security.Events")=tRes.%Message_" SQLCODE:"_tRes.%SQLCODE
}
NB : pour le stockage orienté colonne indiqué au niveau de la table via l'instruction WITH STORAGETYPE = COLUMNAR , il est à noter qu'IRIS se laisse la liberté de choisir pour vous le stockage le plus communément optimal (en ligne ou en colonne), en fonction des types de données.
Exemple :
l'instruction suivante :
CREATE TABLE a.addressV1 (
city varchar(50),
zip varchar(15),
country varchar(15)
)
WITH STORAGETYPE = COLUMNAR
Ne créera aucun stockage orienté colonne, lié au risque de données trop disparates, du fait du nombre de caractères autorisés dans chaque colonne (15 ou 50) :
Class a.addressV1 Extends %Persistent [ ClassType = persistent, DdlAllowed, Final, Owner = {_SYSTEM}, ProcedureBlock, SqlRowIdPrivate, SqlTableName = addressV1 ]
{
Property city As %Library.String(COLLATION = "EXACT", MAXLEN = 50, STORAGEDEFAULT = "ROW") [ SqlColumnNumber = 2 ];
Property zip As %Library.String(COLLATION = "EXACT", MAXLEN = 15, STORAGEDEFAULT = "ROW") [ SqlColumnNumber = 3 ];
Property country As %Library.String(COLLATION = "EXACT", MAXLEN = 15, STORAGEDEFAULT = "ROW") [ SqlColumnNumber = 4 ];
Parameter STORAGEDEFAULT = "columnar";
Parameter USEEXTENTSET = 1;
alors que l'exemple donné dans l'article, retient bien une colonne (et une seule) en stockage orienté colonne, puisqu'ayant seulement 5 caractères autorisés pour la colonne zip.
CREATE TABLE a.addressV2 (
city varchar(50),
zip varchar(5),
country varchar(15)
)
WITH STORAGETYPE = COLUMNAR
Class a.addressV2 Extends %Persistent [ ClassType = persistent, DdlAllowed, Final, Owner = {_SYSTEM}, ProcedureBlock, SqlRowIdPrivate, SqlTableName = addressV2 ]
{
Property city As %Library.String(COLLATION = "EXACT", MAXLEN = 50, STORAGEDEFAULT = "ROW") [ SqlColumnNumber = 2 ];
Property zip As %Library.String(COLLATION = "EXACT", MAXLEN = 5) [ SqlColumnNumber = 3 ];
Property country As %Library.String(COLLATION = "EXACT", MAXLEN = 15, STORAGEDEFAULT = "ROW") [ SqlColumnNumber = 4 ];
Parameter STORAGEDEFAULT = "columnar";
Parameter USEEXTENTSET = 1;
Merci @Iryna Mykhailova pour ce brillant article !
Très clair et intelligemment présenté.
Avec embedded Python, vous pouvez avoir un code assez simple en utilisant pandas :
/// Convert an Excel file to a CSV file
ClassMethod XLStoCSV(source As %String = "/data/sample.xlsx") As %Status [ Language = python ]
{
import pandas as pd
read_file = pd.read_excel(source)
read_file.to_csv(source+'.csv', index = None, header=True)
}
La syntax correcte à utiliser pour exclure les schémas système est :
\%*
.png)