OpenEdge Dynamic TempTable - progress-4gl

I am creating an application that accesses a database with many tables.
To make the coding easier and shorter, I am planning to make one procedure that either gets/sets the data dynamically or executes a procedure for specific data manipulations.
I've got something so far, but I'm kind of stuck now.
What I've done so far is made sure I can dynamically built a temp-table with the same schema as a database table I want to retrieve data from. I then query for a record and add it to the dynamic temp-table. Then this temp-table is passed as an output parameter.
What I want to do now is , when the user has changed the record, save that record dynamically. Therefore, I have to query the table dynamically and find the record the user wants to change. Actually the same as retrieving the record. But saving the changes made requires me to go through the input dynamic temp-table. How is this done?
The normal way of actions is like this:
- Get record by passing the table name and key. Then give the record to output parameter.
- Update record by getting dynamic temp-table as input parameter and then saving the changes from the correct record to the correct record. This second part is where I fail.
The code supplied here only does the first part, but the second part should be included into this code.
Code:
DEF VAR G-TableBuf AS HANDLE NO-UNDO.
DEF VAR G-TableBuf-Handle AS HANDLE NO-UNDO.
DEF VAR G-Query AS HANDLE NO-UNDO.
DEF VAR G-Table-FirNr AS INT NO-UNDO.
DEF VAR G-Qstring AS CHAR NO-UNDO.
DEF VAR G-Heeft-FirNr AS LOG NO-UNDO.
DEF VAR G-TempTable AS HANDLE NO-UNDO.
DEF VAR G-tt-Buffer AS HANDLE NO-UNDO.
DEF VAR G-MatchZone AS CHAR NO-UNDO.
DEF VAR G-prime-field AS CHAR NO-UNDO.
DEF VAR G-Zones-Buffer AS HANDLE NO-UNDO.
{lib/def_tt_ds_Errors.i}
DEF INPUT PARAMETER p_iFirnr AS INT NO-UNDO.
DEF INPUT PARAMETER p_iApplNr AS INT NO-UNDO.
DEF INPUT PARAMETER p_cUsrCd AS CHAR NO-UNDO.
DEF INPUT PARAMETER p_cAction AS CHAR NO-UNDO.
DEF INPUT PARAMETER p_cKeyCd AS CHAR NO-UNDO. /* Record key */
DEF INPUT PARAMETER p_cTable AS CHAR NO-UNDO. /* Table name */
DEF INPUT-OUTPUT PARAMETER TABLE-HANDLE hTT. /* INPUT-OUTPUT dynamic temp-table */
DEF OUTPUT PARAMETER DATASET FOR dsErrors.
RUN FindRecord.
RETURN.
PROCEDURE FindRecord :
/*------------------------------------------------------------------------------------------------*/
/*------------------------------------------------------------------------------------------------*/
DEF VAR i AS INT NO-UNDO.
CREATE TEMP-TABLE G-TempTable.
CREATE BUFFER G-TableBuf FOR TABLE p_cTable.
CREATE QUERY G-Query.
ASSIGN G-Table-FirNr = Get-Fir (p_cTable)
G-MatchZone = "kolom,Waarde"
G-heeft-firnr = FALSE
G-Zones-Buffer = BUFFER zones:HANDLE
G-TableBuf-Handle = G-TableBuf:HANDLE.
/* SCHEMA BUILDING CODE GOES HERE */
G-TempTable:TEMP-TABLE-PREPARE("tt" + p_cTable).
G-tt-Buffer = G-TempTable:DEFAULT-BUFFER-HANDLE.
G-tt-Buffer:EMPTY-TEMP-TABLE().
hTT = G-TempTable.
G-Qstring = "FOR EACH " + p_cTable.
G-Query:SET-BUFFERS(G-TableBuf).
G-Query:QUERY-PREPARE(G-Qstring).
G-Query:QUERY-OPEN().
G-Query:GET-NEXT().
REPEAT:
IF G-query:QUERY-OFF-END THEN
LEAVE.
G-tt-Buffer:BUFFER-CREATE.
G-tt-Buffer:BUFFER-COPY (G-TableBuf-Handle).
G-Query:GET-NEXT().
END.
END PROCEDURE.
Thanks in advance!

You need to study the ProDataset functionality, it'll make loading a TT and saving it's changed records back to the DB a lot easier.
To create a dynamic TT like a db table (at a high level), get the table's buffer handle, then go through it using b-handle:buffer-field(field-number):name to get a list of fields, and then use add-fields-from to create fields in the TT based on the db table.
Once you've got that, use the Prodataset "FILL" functionality to load data into it, and then pass the PDS back to the calling program for it to use.
when you're ready to save the data out, use the PDS functionity (save-row-changes) to save the changed records back to the database.
The process of loading and saving TT records is largely documented in the Prodatset set of docs. I highly recommend the 11.3 docs, as they're much improved over the prior version PDS docs.
This KB will also give you some ideas on how to build a dynamic TT like a db table. http://knowledgebase.progress.com/articles/Article/000045189?q=how+to+create+dynamic+temp-table&l=en_US&c=Product_Group%3AOpenEdge&type=Article__kav&fs=Search&pn=1

I used 2 different parameters for the dynamic temp-table, one for input and one for output. The rest I figured out myself. Thanks for your help guys!

Related

4GL ABL Openedge loop through handle?

here is my current code
def var hbTT as handle.
for each Cust:
hbTT:buffer-create().
assign
hbTT::Name = Cust.Name
hbTT::address = Cust.Address.
end.
now what I want to do is to loop through hbtt. How can I do that?
I tried
for each hbTT:
/* Do something */
end.
the error I get is
unknown or ambiguous table hbTT. (725)
thank you
You won't be able to do a loop that way, as for each requires a static name.
Instead, try this:
DEFINE VARIABLE hQuery AS HANDLE NO-UNDO.
create query hQuery.
hQuery:set-buffers(hbtt).
hquery:query-prepare('for each tt'). /* <-- Where tt is the original buffer name */
hquery:query-open().
hquery:get-first().
do while not hquery:query-off-end:
disp hbtt::name hbtt::address .
hquery:get-next().
end.

How do you get the output value of a C(CDELC) function defined in an external library as char**

I want to be able to use the following function defined in a shared object file (lib.so) :
int encrypt_data (char* buffer_in, int size_in, char** buffer_out, int* size_out)
Basically, i want to be able to pass the content of a file to this function (buffer_in) and write the output content (buffer_out) into another file. Here is what i've tried so far :
PROCEDURE encrypt_data EXTERNAL "lib.so" CDECL :
DEFINE INPUT PARAMETER buffer_in AS MEMPTR.
DEFINE INPUT PARAMETER size_in AS LONG.
DEFINE OUTPUT PARAMETER buffer_out AS MEMPTR.
DEFINE OUTPUT PARAMETER size_out AS LONG.
DEFINE RETURN PARAMETER returnvalue AS LONG.
END PROCEDURE.
PROCEDURE pi_encryptHash:
DEFINE INPUT PARAMETER ipc_fullPathToEncrypt AS CHARACTER NO-UNDO.
DEFINE VARIABLE lm_bufferIn AS MEMPTR NO-UNDO.
DEFINE VARIABLE li_sizeIn AS INT NO-UNDO.
DEFINE VARIABLE lm_bufferFakeOut AS MEMPTR NO-UNDO.
DEFINE VARIABLE lm_bufferOut AS MEMPTR NO-UNDO.
DEFINE VARIABLE li_sizeOut AS INT NO-UNDO.
DEFINE VARIABLE li_returnedCode AS INTEGER NO-UNDO.
/* make sure MEMPTR is sized correctly */
FILE-INFO:FILE-NAME = ipc_fullPathToEncrypt.
SET-SIZE(lm_bufferIn) = FILE-INFO:FILE-SIZE.
li_sizeIn = GET-SIZE(lm_bufferIn).
/* the actual read */
INPUT FROM VALUE(ipc_fullPathToEncrypt) BINARY NO-MAP NO-CONVERT.
IMPORT lm_bufferIn.
INPUT CLOSE.
/* Call the encrypt proc a first time to get the output lenght */
SET-SIZE(lm_bufferFakeOut) = 2.
RUN encrypt_data(INPUT lm_bufferIn, INPUT li_sizeIn, OUTPUT lm_bufferFakeOut, OUTPUT li_sizeOut, OUTPUT li_returnedCode).
/* Call it a second time with a buffer large enough to get the output value */
SET-SIZE(lm_bufferOut) = li_sizeOut.
RUN encrypt_data(INPUT lm_bufferIn, INPUT li_sizeIn, OUTPUT lm_bufferOut, OUTPUT li_sizeOut, OUTPUT li_returnedCode).
/* Write MEMPTR to file */
OUTPUT TO VALUE(ipc_fullPathToEncrypt + ".sha") BINARY NO-MAP NO-CONVERT.
EXPORT lm_bufferOut.
OUTPUT CLOSE.
SET-SIZE(lm_bufferIn) = 0.
SET-SIZE(lm_bufferOut) = 0.
DISPLAY li_returnedCode.
RETURN "".
END PROCEDURE.
I think my problem is that i can't read the data of char** buffer_out, from what i understand it is a pointer to a pointer so what i'm really exporting here EXPORT lm_bufferOut. is the pointer address of the data i would like to export? But how can i export the data instead?
Maybe not an answer to your original question but be aware that there are built functions in Progress for encryption. For instance you can do like this (in this example the encrypted value is base64-encoded so the result is "viewable"):
DEFINE VARIABLE cClearText AS CHARACTER NO-UNDO.
DEFINE VARIABLE rBinaryKey AS RAW NO-UNDO.
DEFINE VARIABLE rEncryptedValue AS RAW NO-UNDO.
DEFINE VARIABLE cEncryptedText AS CHARACTER NO-UNDO.
DEFINE VARIABLE cDecryptedText AS CHARACTER NO-UNDO.
ASSIGN
cClearText = "This is the clear text string to be encrypted.".
MESSAGE "Original message: " cCleartext
VIEW-AS ALERT-BOX INFO BUTTONS OK.
ASSIGN
SECURITY-POLICY:SYMMETRIC-ENCRYPTION-ALGORITHM = "AES_OFB_256"
rBinaryKey = GENERATE-RANDOM-KEY
SECURITY-POLICY:SYMMETRIC-ENCRYPTION-KEY = rBinaryKey
SECURITY-POLICY:SYMMETRIC-ENCRYPTION-IV = ?
rEncryptedValue = Encrypt (cClearText)
cEncryptedText = BASE64-ENCODE(rEncryptedValue)
.
MESSAGE "Encrypted Message:" cEncryptedText
VIEW-AS ALERT-BOX INFO BUTTONS OK.
ASSIGN
SECURITY-POLICY:SYMMETRIC-ENCRYPTION-KEY = rBinaryKey
cDecryptedText = GET-STRING(DECRYPT (rEncryptedValue),1).
MESSAGE "Decrypted Message: " cDecryptedText
VIEW-AS ALERT-BOX INFO BUTTONS OK.
This example is based on this knowledge base entry.
You can do:
MESSAGE SECURITY-POLICY:SYMMETRIC-SUPPORT VIEW-AS ALERT-BOX.
to see supported symmetric encryption algorithms supported.
Here's an SO question regarding different versions of AES algorithms:
How to choose an AES encryption mode (CBC ECB CTR OCB CFB)?
A solution has been found, below is the program used :
PROCEDURE pi_encryptDecrypt:
DEFINE INPUT PARAMETER ipc_inputFile AS CHARACTER NO-UNDO.
DEFINE INPUT PARAMETER ipc_outputFile AS CHARACTER NO-UNDO.
DEFINE INPUT PARAMETER ipi_codeOp AS INTEGER NO-UNDO.
DEFINE VARIABLE lm_bufferIn AS MEMPTR NO-UNDO.
DEFINE VARIABLE li_sizeIn AS INT NO-UNDO.
DEFINE VARIABLE lm_bufferFakeOut AS MEMPTR NO-UNDO.
DEFINE VARIABLE lm_bufferOut AS MEMPTR NO-UNDO.
DEFINE VARIABLE li_sizeOut AS INT NO-UNDO.
DEFINE VARIABLE li_returnedCode AS INTEGER NO-UNDO.
define variable mtarget as memptr.
/* make sure MEMPTR is sized correctly */
FILE-INFO:FILE-NAME = ipc_inputFile.
SET-SIZE(lm_bufferIn) = FILE-INFO:FILE-SIZE.
li_sizeIn = GET-SIZE(lm_bufferIn).
/* the actual read */
copy-lob from file ipc_inputFile to lm_bufferIn.
/* set a buffer large enough to get the output value */
SET-SIZE(lm_bufferOut) = 8.
if ipi_codeOp = 1 then
RUN encrypt_data(INPUT lm_bufferIn, INPUT li_sizeIn, OUTPUT lm_bufferOut, OUTPUT li_sizeOut, OUTPUT li_returnedCode).
else
RUN decrypt_data(INPUT lm_bufferIn, INPUT li_sizeIn, OUTPUT lm_bufferOut, OUTPUT li_sizeOut, OUTPUT li_returnedCode).
set-size(mtarget) = li_sizeOut.
SET-POINTER-VALUE(mtarget) = GET-INT64(lm_bufferOut, 1).
/* Write MEMPTR to file */
os-delete value(ipc_outputFile).
COPY-LOB FROM mtarget for /* FOR est SUPER IMPORTANT !! - ne pas enlever */ li_sizeOut TO FILE (ipc_outputFile).
SET-SIZE(lm_bufferIn) = 0.
SET-SIZE(lm_bufferOut) = 0.
set-size(mtarget) = 0.
/*DISPLAY li_returnedCode.
DISPLAY li_sizeOut.*/
RETURN "".
END PROCEDURE.
As i stated in my question, the problem was indeed that char** buffer_out returns a pointer to a pointer to the data in the memory.
The trick was to use a second MEMPTR (mtarget) that will point to the actual data, size it to be large enough with SET-SIZE and then sets it to the value of a particular memory location (using SET-POINTER-VALUE), which we get from GET-INT64(lm_bufferOut, 1).

How to execute procedure from List with parameters in Progress 4gl?

I have some list like this
DEFINE VARIABLE procedurelist AS CHARACTER EXTENT 5
INITIAL [ "1.p", "2.p", "3.p", "4.p", "5.p"].
but this all procedures with input-output parameters and i want to execute this procedure, How can i do this? I have no idea how to do this.
The base of your solution is the RUN VALUE statement.
The manual states.
VALUE( extern-expression ) An expression that returns the name of the (local or remote) external procedure you want to run....
This basically means that you can input a string with the value of a program (or procedure) into your RUN statement.
If all input-output parameters are exactly the same you can do like this:
DEFINE VARIABLE procedurelist AS CHARACTER EXTENT 5 INITIAL [ "1.p", "2.p", "3.p", "4.p", "5.p"].
DEFINE VARIABLE iExtent AS INTEGER NO-UNDO.
DEFINE VARIABLE cVariable AS CHARACTER NO-UNDO.
DO iExtent = 1 TO EXTENT(procedurelist):
RUN VALUE(procedurelist[iExtent]) (INPUT-OUTPUT cVariable).
END.
If the parameters are different it gets trickier (but not impossible). The CREATE CALL and the Call Object can help you there. In this case you would need some kind of way to keep track of the different parameters as well.
Here's a basic example taken directly from the online help:
DEFINE VARIABLE hCall AS HANDLE NO-UNDO.
CREATE CALL hCall.
/* Invoke hello.p non-persistently */
hCall:CALL-NAME = "hello.p".
/* Sets CALL-TYPE to the default */
hCall:CALL-TYPE = PROCEDURE-CALL-TYPE.
hCall:NUM-PARAMETERS = 1.
hCall:SET-PARAMETER(1, "CHARACTER", "INPUT", "HELLO WORLD").
hCall:INVOKE.
/* Clean up */
DELETE OBJECT hCall.

Bracketed key value - limit OR foreach ... in OR?

Continuing my quest to convert .NET to Progress, I faced another challenge yesterday.
Our company bought time ago a .NET DLL to manage Excel document without the need to install Microsoft Excel. There is several functions that return a series of cells depending of the need.
The returned value is a class that implement IEnumerator interface in .NET.
The problem is that I cannot find a way to iterate trough the cells without getting the error:
System.ArgumentException: Row or column index is invalid or out of required range
Is there a way to in Progress to validate if X is inside of the extent range?
OR
Is there a way to iterate trough the array without knowing the upper limit of the array?
Thank you!
Sebastien
--- temporary solution ---
/* declaration */
DEFINE VARIABLE oCell AS CLASS GemBox.Spreadsheet.ExcelCell NO-UNDO.
DEFINE VARIABLE oRange AS CLASS GemBox.Spreadsheet.CellRange NO-UNDO.
DEFINE VARIABLE i AS INTEGER NO-UNDO.
/* load excel file */
...
/* retrieve a series of cells */
ASSIGN oRange = oWorksheet:Cells:GetSubrangeAbsolute(1,1, 2,2).
/* first cell */
ASSIGN i = 0.
ASSIGN oCell = ?.
ASSIGN oCell = oRange:Item[i] NO-ERROR.
/* validate cell is in the range */
DO WHILE NOT oCell EQ ?:
MESSAGE oCell:Value VIEW-AS ALERT-BOX.
/* next cell */
ASSIGN i = i + 1.
ASSIGN oCell = ?.
ASSIGN oCell = oRange:Item[i] NO-ERROR.
END.
I don't have access nor I can test this solution, but if it implements correctly the interface some solution like this one should work:
/* declaration */
DEFINE VARIABLE oCell AS CLASS GemBox.Spreadsheet.ExcelCell NO-UNDO.
DEFINE VARIABLE oRange AS CLASS GemBox.Spreadsheet.CellRange NO-UNDO.
DEFINE VARIABLE oEnumerator AS CLASS System.Collections.IEnumerator NO-UNDO.
DEFINE VARIABLE i AS INTEGER NO-UNDO.
/* load excel file */
...
/* retrieve a series of cells */
ASSIGN oRange = oWorksheet:Cells:GetSubrangeAbsolute(1,1, 2,2).
oEnumerator = oRange:getEnumerator().
DO WHILE oEnumerator:MoveNext():
oCell = CAST(oEnumerator:current,"GemBox.Spreadsheet.ExcelCell").
END.
If it doesn't work exactly like this, at least it should point you in the correct direction to use it.
From the web page I'd infer that the # of cols =
oRange:LastColumnIndex - oRange:FirstColumnIndex
and the # of rows is
oRange:LastRowIndex - oRange:FirstRowIndex
I'd think using
oCell = oRange:Item[Int32, Int32]
to get the item at the row, col position would work better instead of using a single element array element.

Progress 4GL: Buffer handle attribute for all fields

I am fairly new to Progress and even newer to handles so apologies if I have missed anything obvious, I've looked online but am yet to find what I'm looking for.
I am running a dynamic query similar to the below, in this example after then query is run, the "age" field of the corrresponding record is displayed on screen, I understand how this is done from the buffer-field attribute-method, but my question is how do I display the entire record, is there an equivalent attribute method, or have I misunderstood something crucial?. Thank you for your time. :
def var tbl as character no-undo.
def var fld as character no-undo.
def var qh as handle no-undo.
def var bh as handle no-undo.
def var fh as handle no-undo.
assign tbl = "customer".
assign fld = "age".
create buffer bh for table tbl.
create query qh.
qh:set-buffers( bh ).
qh:query-prepare( "for each " + tbl + " where name = 'tom'" ).
qh:query-open.
do transaction:
qh:get-first( no-lock ).
fh = bh:buffer-field( fld ).
display fh:buffer-value.
end.
delete object bh.
delete object qh
There's no "easy" way to display the entire record in one statement the way you can with a static "DISPLAY table-name" statement. You can get the count of fields (buffer-handle:NUM-FIELDS) and then step through the individual fields and display their values using
DO i = 1 to bh:NUM-FIELDS:
DISPLAY bh:BUFFER-FIELD(i):BUFFER-VALUE WITH DOWN.
DOWN.
END.
create query qh.
qh:set-buffers( bh ).
qh:query-prepare( "for each " + tbl + " where name = 'tom'" ).
qh:query-open.
qh:GET-FIRST().
DO while qh:QUERY-OFF-END = False:
DO i = 1 TO bh:NUM-FIELDS:
display bh:BUFFER-FIELD (i):NAME STRING(bh:BUFFER-FIELD(i):BUFFER-VALUE) with down.
down.
END.
qh:GET-NEXT ().
END.