Opening a STREAM in a Persistent Procedure Function - progress-4gl

I have a persistent procedure, in which I am trying to open, write and close a stream.
I have in the main area of the procedure
DEFINE STREAM sOutFile.
OPEN STREAM sOutFile TO VALUE( outFileName ).
MESSAGE SEEK( sOutFile ).
and subsequently a function within the persistent procedure
FUNCTION example RETURN LOGICAL:
MESSAGE SEEK( sOutFile ).
PUT STREAM sOutFile UNFORMATTED someData.
END.
When the persistent procedure is instantiated, the message displays "0" so the stream has been opened. However, when example is called, the message displays "?" and I get a message about attempting to write to a closed stream.
I've tried declaring the stream NEW SHARED but that didn't make any difference.
Am I doing something wrong, or is it impossible to define streams within persistent procedures?

It is early and my coffee hasn't kicked in yet but I think that you have to open the stream outside the body of the PP.
This works:
/* ppstream.p
*
*/
define stream logStream.
session:add-super-procedure( this-procedure ).
/* end of PP init */
function initLog returns logical ( input lgFile as character ):
output stream logStream to value( lgFile ) unbuffered.
return true.
end.
function logStuff returns logical ( input msg as character ):
put stream logStream unformatted msg skip.
return true.
end.
and then call it like so:
function initLog returns logical ( input lgFile as character ) in super.
function logStuff returns logical ( input msg as character ) in super.
run ./ppstream.p persistent.
initLog( "log.txt" ).
logStuff( "test" ).
(I used a session super-procedure to avoid having to define handles -- you would not necessarily need to do that.)

Related

Value within a function seems to not be able to detect the local variables and also fails

I want to be able to run something like the following:
f:{[dt] syms:`sym1;eval parse"select from tbl where date = dt, sym=syms"}
f[.z.D]
Given the following :
tbl:([] date:2022.01.01 2022.01.01; Id:1000000 2000000; sym:`sym1`sym2;price:10 20;qty:3 4)
f:{[dt] syms:`sym1; ?[tbl;((=;`date;`dt);(=;`sym;`syms));0b;()]}
f1:{[dt] syms:`sym1; (?) . (tbl;((=;`date;`dt);(=;`sym;`syms));0b;())}
f2:{[dt] syms:`sym1; value (?;tbl;((=;`date;`dt);(=;`sym;`syms));0b;())}
f[.z.D] // works
f1[.z.D] // Gives Error - dt not recognized/out of scope
f2[.z.D] // Gives Error - dt not recognized/out of scope
Value within a function seems to not be able to detect the local variables and surprisingly (?) . also fails. (maybe because this in itself is a function and dt is not defined here?)
Is there any work around for this?
For context, I have a function that takes a select string/functional select, parses it, does some checks and manipulations on the functional form and returns a modified functional form.
I want users to be able to call this function from their own functions and that parameters they have defined in their function can be in the outputted functional form and that functional form can be valued some how.
I don't want users to be forced to pass more variables into my function etc.
What you need to do here is remove the backtick for dt and syms
I would also recommend using a backtick when calling your table name.
Further, you should make sure syms is enlisted if it is only one symbol.
So your function should be:
f:{[dt] syms:(),`sym1; ?[`tbl;((=;`date;dt);(=;`sym;syms));0b;()]}
If you parse your select statement you can see the correct form for functional selects:
q)parse "select from tbl where date=2022.01.01,sym=`sym1"
?
`tbl
,((=;`date;2022.01.01);(=;`sym;,`sym1)) // comma in front of `sym1 means enlist
0b
()
The backtick is not needed as this is a variable, defined in your function, it would be the same as doing:
?[`tbl;((=;`date;2022.01.01);(=;`sym;enlist `sym1));0b;()]
This should allow you to use your function correctly:
q)f[2022.01.01]
date Id sym price qty
---------------------------------
2022.01.01 1000000 sym1 10 3
For more information, see the kx documentation

Are Ada function arguments evaluated if the body is empty?

According to this statement :
Trace.Debug("My String" & Integer'Image(x) & "is evaluated" & "or not" & "if my logger is disabled ?" & Boolean'Image(YesOrNo) );
And this implementation of Trace.Debug:
procedure Debug (Message : in String) is
begin
if Logger.Enabled then -- This boolean is defined during runtime by reading a value in a file
Put_Line(Message);
else
null; -- Do nothing
end if;
end Debug;
I have a software which can manage several levels of logs, and I would like to know what is the behavior in case of Logger.Enabled equals False.
I have a lot of logs calls, with sometimes complex strings to evaluate and I'm on a real time system, so I don't want to lost time to evaluate a string which will not printed.
I would like to know if compiler optimize the code in order to not evaluate the string in parameter of Trace.Debug while Logger.enabled is False, knowing that this boolean is set at the begging of runtime by reading a value in a file.
I am using gnat 7.3.2.
You can ensure that evaluation doesn't happen by providing a callback:
procedure Debug (Message : access function return String) is
begin
if Logger.Enabled then
Put_Line(Message.all);
end if;
end Debug;
Then to call it, do
declare
function Msg return String is
("My String" & Integer'Image(x) & "is evaluated" & "or not" & "if my logger is disabled ?" & Boolean'Image(YesOrNo));
begin
Debug (Msg'Access);
end;
In your original code, the only way that the compiler could skip the evaluation is when it inlines the Debug procedure and re-arranges the code so that the Message object is only assigned inside the if-block. You cannot force this; even pragma Inline is only a hint for the compiler.

Passing empty DataSet to Appserver

I'm trying to create a single proxy query to our appserver, which uses the following parameters:
editTest.p
DEFINE TEMP-TABLE TT_Test NO-UNDO
BEFORE-TABLE TT_TestBefore
FIELD fieldOne LIKE MyDBTable.FieldOne
FIELD fieldTwo LIKE MyDBTable.FieldTwo
FIELD fieldThree LIKE MyDBTable.FieldThree
.
DEFINE DATASET dsTest FOR TT_Test.
/* Parameters */
DEF INPUT-OUTPUT PARAM DATASET FOR dsTest.
The idea is that the program would call this procedure in 2 different ways:
with passed dataset parameter: read passed dataset and update db according to it's changes
without passed dataset parameter/unknown/unset: fill TT_Test and return dataset to client for editing
Is there any way to create a proxy like this? Easy solution would be to separate the get and insert,modify,delete to 2 own proxy files, so the client would always first get the dataset and then pass it for for the second one. However, I'd like to implement this functionality into this one file.
The key is to use the datasets, so the changes made to the data can be updated almost automatically.
Instead of using the dataset itself as the parameter, use a dataset handle. You can then make it null for your 2nd condition. Adding on to your example, procedure testProc will display a message "yes" when the dataset is passed in via the handle, and "no" when null is passed in.
DEFINE TEMP-TABLE TT_Test NO-UNDO
BEFORE-TABLE TT_TestBefore
FIELD fieldOne LIKE MyDBTable.FieldOne
FIELD fieldTwo LIKE MyDBTable.FieldTwo
FIELD fieldThree LIKE MyDBTable.FieldThree
.
DEFINE DATASET dsTest FOR TT_Test.
PROCEDURE testProc:
DEFINE INPUT-OUTPUT PARAMETER DATASET-HANDLE phDataSet.
MESSAGE VALID-HANDLE(phDataSet) VIEW-AS ALERT-BOX.
END.
DEFINE VARIABLE hTest AS HANDLE NO-UNDO.
/* Pass in a dataset. */
hTest = DATASET dsTest:HANDLE.
RUN testProc (INPUT-OUTPUT DATASET-HANDLE hTest).
/* Pass in null. */
hTest = ?.
RUN testProc (INPUT-OUTPUT DATASET-HANDLE hTest).

Creating ABL Client to Consume a REST Web Service

I'm not finding any documentation on connecting to a REST web service from progress. Can someone please point me in the direction of documentation or provide a connection example.
Progress: 11.3
Starting with 11.5.1 there's built in support for consuming REST webservices in a controlled way. But since you're not on that version I will leave that out for now.
Unless upgrading is an option you can do several things:
Call a OS program for HTTP like curl or wget
How to do this exactly will depend of utility of choice, os, version etc. Also be advised that os updates might change the behavior.
Also you should look into error redirecting etc before doing this. Calling out to the OS will create a second process so it might impact system resources. Use only if you take those things in account.
On the other side it's easy and fast. Curl is also highly flexible and will help with whatever headers or other things you might want to use.
You could also look into calling the curl library directly instead. Be advised that you will heavily rely on mempointers and other c-like structures in that case!
PROCEDURE curl:
DEFINE INPUT PARAMETER pcUrl AS CHARACTER NO-UNDO.
DEFINE OUTPUT PARAMETER pcResponse AS LONGCHAR NO-UNDO.
DEFINE VARIABLE cCommand AS CHARACTER NO-UNDO.
/* -s is for silent use - show no errors or feedback on the screen */
cCommand = "curl -s " + pcUrl + " > /tmp/curl.txt".
OS-COMMAND NO-CONSOLE VALUE(cCommand).
COPY-LOB FROM FILE "/tmp/curl.txt" TO pcResponse .
END.
DEFINE VARIABLE lc AS LONGCHAR NO-UNDO.
run curl("http://www.google.com/", OUTPUT lc).
MESSAGE STRING(SUBSTRING(lc,1,100)) VIEW-AS ALERT-BOX.
Use .net classes
I have no knowledge in .net so I can't help you with code for this. You will have to research how to call webservices in .Net to start with and then "translate" into ABL. This will only work on Windows - Progress have no support for Mono or other ways of doing .net in other OSes.
Roll your own using sockets
Shamelessly stolen example from: Progress Knowledgebase.
DEFINE VARIABLE vcHost AS CHARACTER INITIAL "localhost" NO-UNDO.
DEFINE VARIABLE vcPort AS CHARACTER INITIAL "8080" NO-UNDO.
DEFINE VARIABLE vhSocket AS HANDLE NO-UNDO.
CREATE SOCKET vhSocket.
vhSocket:CONNECT('-H ' + vcHost + ' -S ' + vcPort) NO-ERROR.
IF vhSocket:CONNECTED() = FALSE THEN
DO:
MESSAGE "Connection failure" VIEW-AS ALERT-BOX.
MESSAGE ERROR-STATUS:GET-MESSAGE(1) VIEW-AS ALERT-BOX.
RETURN.
END.
ELSE
MESSAGE "Connect"
VIEW-AS ALERT-BOX.
vhSocket:SET-READ-RESPONSE-PROCEDURE('getResponse').
/* supposes there is an webspeed app called yourapp.w that receives param1, param2, param3 */
RUN PostRequest (
INPUT '/scripts/cgiip.exe/WService=wsbroker1/yourApp.w',
INPUT 'param1=value&param2=value&param3=value'
).
WAIT-FOR READ-RESPONSE OF vhSocket.
vhSocket:DISCONNECT() NO-ERROR.
DELETE OBJECT vhSocket.
QUIT.
PROCEDURE getResponse:
DEFINE VARIABLE vcWebResp AS CHARACTER NO-UNDO.
DEFINE VARIABLE lSucess AS LOGICAL NO-UNDO.
DEFINE VARIABLE mResponse AS MEMPTR NO-UNDO.
IF vhSocket:CONNECTED() = FALSE THEN do:
MESSAGE 'Not Connected' VIEW-AS ALERT-BOX.
RETURN.
END.
lSucess = TRUE.
DO WHILE vhSocket:GET-BYTES-AVAILABLE() > 0:
SET-SIZE(mResponse) = vhSocket:GET-BYTES-AVAILABLE() + 1.
SET-BYTE-ORDER(mResponse) = BIG-ENDIAN.
vhSocket:READ(mResponse,1,1,vhSocket:GET-BYTES-AVAILABLE()).
vcWebResp = vcWebResp + GET-STRING(mResponse,1).
END.
/*
*PUT HERE THE CODE TO MANIPULATE THE ANSWER
*/
END.
PROCEDURE PostRequest:
DEFINE VARIABLE vcRequest AS CHARACTER.
DEFINE VARIABLE mRequest AS MEMPTR.
DEFINE INPUT PARAMETER postUrl AS CHAR.
/* URL that will send the data. It must be all the path after the server. IE:/scripts/cgiip.exe/WService=wsbroker1/myApp.htm */
DEFINE INPUT PARAMETER postData AS CHAR.
/* Parameters to be sent in the format paramName=value&paramName=value&paramName=value */
vcRequest =
'POST ' +
postUrl +
' HTTP/1.0~r~n' +
'Content-Type: application/x-www-form-urlencoded~r~n' +
'Content-Length:' + string(LENGTH(postData)) +
'~r~n' + '~r~n' +
postData + '~r~n'.
MESSAGE vcREquest
VIEW-AS ALERT-BOX.
SET-SIZE(mRequest) = 0.
SET-SIZE(mRequest) = LENGTH(vcRequest) + 1.
SET-BYTE-ORDER(mRequest) = BIG-ENDIAN.
PUT-STRING(mRequest,1) = vcRequest .
vhSocket:WRITE(mRequest, 1, LENGTH(vcRequest)).
END PROCEDURE.

PL/pgSQL , How to make a function using Raise notices and export the messages from the console to a text file from the Code

I have to make a update function that have multiple conditions like this
BEGIN
OPEN cur3 FOR execute('select id_organigramme from ( select distinct id_personne,id_organigramme,idfax from requpdate where
id_personne= ' || VariableIDpersonne || ' and idfax is null) a where
a.id_organigramme not in (select distinct id_organigramme from
requpdate where id_personne= ' ||VariableIDpersonne || ' and idfax is
not null and a.id_personne=requpdate.id_personne ) ');
LOOP
FETCH cur3 INTO VariableIDorganigrammeFax;
if not found then
--Message here !!!
--Raise notice 'hello word!'
exit;
end if;
I have to show up messages if any condition exists I found out that I can do this with Raise Notice/info ... statement, but I have to make auto export of those messages into a text file when the function finishes.
Is this possible? Otherwise what can I use to make it.
I use PGAdminIII as a client.
What your logging options are depends entirely on your client configuration. But rather than using RAISE NOTICE I would suggest you use the NOTIFY \ LISTEN framework. Basically, in your function you issue a notice to a channel of your choosing (can be any string) and in your client you listen to that same channel, logging the messages as they come in. How exactly the listening and logging works depends on your client.
The code you show can also you use some improvements.
First of all, your query is an incredibly convoluted version of:
SELECT DISTINCT id_organigramme
FROM requpdate
WHERE id_personne = VariableIDpersonne
AND idfax IS NULL;
Secondly, you do not need a dynamic query, you can get by with variable substitution. Assuming id_personne is not a string, it is as simple as stated above, otherwise use quote_literal(VariableIDpersonne).
Lastly, unless there are parts of your function not shown that require a cursor, you can simply do:
FOR VariableIDorganigrammeFax IN [query above]
LOOP
... -- do your processing here
END LOOP;
IF NOT FOUND THEN -- the loop above did not iterate because no records were returned
SELECT pg_notify('logMyFunction', format('%s: No records found', VariableIDpersonne));
END IF;
The pg_notify() function is a wrapper around the NOTIFY command that makes it possible to pass variable strings.
Before you call the function, you should issue the command LISTEN logMyFunction so that your session will receive the notifications from the channel.