Delphi REST - How do I know when the data is all retrieved? - rest

In Delphi Tokyo, I have a series of REST components (the ones shipped with Delphi: RESTClient, RESTRequest, RESTResponse, RESTAdapater) tied together to retrieve REST data.
The REST call, as defined on the server, has pagination set to some value.
As such, within my Delphi app I have to repeatedly update the RESTRequest.
ResourceSuffix to add '?page=' and then a page number.
Since various REST Services may have different pagination, or will have different result row counts.
How do I know when I have retrieved all the data?
Surely there is something more elegant that keep trying until rows retrieved = 0/some error.

I found a solution that works for me... My data is coming from Oracle RDBMS, via ORDS and APEX. The REST content (Specifically RAW data) has a URL in it for the next pagination set. For the FIRST set of data, this URL reference is at the end of the REST data stream. For each subsequent data set, the URL reference is at the beginning of the raw data stream, so you have to check both locations. Here is the code I am using...
function IsMoreRESTDataAvailable(Content: String) : Boolean;
var
Test600 : String;
begin
// This routine takes the RESTResponse.Content, aka the raw REST data, and checks to see if there is a NEXT: string
// in either the end of the data (only available for the FIRST DATA set
// or the beginning of the data
result := False;
Test600 := RightStr(Content, 600);
If AnsiPos('"next":{"$ref":"https://<YOUR_SERVER_HERE>', Test600) <> 0 then
begin
result:= True;
Exit;
end;
// If we didn't find it at the end of the REST content, then check at the beginning
Test600 := LeftStr(Content, 600);
If AnsiPos('"next":{"$ref":"https://<YOUR_SERVER_HERE>', Test600) <> 0 then
begin
result:= True;
Exit;
end;
end;

Related

Access object on another form with the form as a variable

I'm writing a program in Delphi which includes creating the same dynamic object on multiple forms (never simultaneously), and then a procedure in another unit writes certain text to it.
How the object (TMemo) is created:
memHulp := TMemo.Create(frmHome);
with memHulp do
begin
Parent := frmHome;
Top := 208;
Left := 88;
Height := 98;
Width := 209;
ReadOnly := True;
end;
The properties aren't that important, it's just to show the creation of the object and how it is referred to.
Now, I need to read certain text into the memo from a text file, which there is no problem with, but the problem comes when there are different forms involved that all use that same self-defined procedure.
It's easy to say frmHome.memHulp.Lines.Add() in this particular case, but when I need it to display the text on the memo named exactly the same in all cases, but on a different form, I'm having some trouble.
The frmHome part needs to be a variable. So I tried this:
var
Form: TForm;
begin
Form := Application.FindComponent('frmHome') as TForm;
end;
That doesn't warn me or give an error, but as soon as I try to say Form.memHulp.Lines.Add(), it does not work, and I understand that it probably doesn't have any properties for Form, but how do I make it look at the correct place? I need to be able to tell the program to look on whichever form name I pass as a parameter into the FindComponent() part.
If this is completely not possible, please suggest other solutions to achieve the same.
Form.memHulp doesn't work because Form is a plain vanilla TForm pointer, and TForm doesn't have a memHulp member. You could use Form.FindComponent('memHulp') instead, since you are assigning the TForm object as the Memo's Owner, but that would require you to assign a Name to the Memo, eg:
memHulp := TMemo.Create(frmHome);
with memHulp do
begin
Parent := frmHome;
Name := 'memHulp';
...
end;
Alternatively, since you say you are creating only 1 Memo object at a time, you could simply make memHulp be a global variable in some unit's interface section, and then you would have direct access to it without having to hunt for it.

Setting up a golang API that queries a database every hour to refresh its data

I'm relatively new to golang and could use some high-level tips on how to structure this. I'm trying to a build a REST API:
The user would provide a small JSON payload via POST method
The API compares the user’s input data against a reference dataset stored as a slice of structs and calculates a value
This value is returned to the user
Every hour a database is queried to and replaces the slice of structs dataset with another slice of structs dataset. Basically this refreshes the reference data
I'd like this refreshing job to be async so it doesn't slow down the user experience
I'm using the golang's Echo framework (https://echo.labstack.com/). Here is my attempt in golang-like pseudocode.
How would you structure this API to refresh the data hourly async?
To clarify, the part Im stuck on is the “query the DB every hour async in the background” bit. Im unsure how to do that in thos scenario.
func main() {
e := echo.New()
e.POST("/", func(something) {
// This func queries the DB and saves reference dataset result as a slice of structs
dataset := refreshDB()
// Does some calculations on input JSON data and reference dataset
result := doCalcs(inputJSON, dataset)
// Prep response in neat JSON
responseForUser := prepOutput(result)
return responseForUser
})
}
For async code in Go you can use a goroutine, to execute the code periodically you can use a ticker.
package main
import (
"fmt"
"time"
"sync"
)
var rwm sync.RWMutex
var sliceOfStructs []struct{/* ... */}
func main() {
go func() {
tick := time.NewTicker(time.Hour)
for range tick.C {
rwm.Lock()
sliceOfStructs = []struct{}{ /* refresh with new data */ }
rwm.Unlock()
}
}()
// start server
}
If sliceOfStructs needs to be accessible across multiple packages then you'll need to export it and move it to a non-main package, i.e. one that can be imported. And do the same for rwm.
Make sure that any code that reads sliceOfStructs invokes rwm.RLock before reading and rwm.RUnlock when done.
If you have more than one goroutine that needs to write sliceOfStructs then you should change rwm from sync.RWMutex to sync.Mutex.

ORDS 18.4 Why am I getting an empty :body_text (CLOB)?

Tell me, please, why does the empty value come?
To send a request, I use SoapUI 5.5.
But :body is not null.
Do I need to do something in the settings of ORDS?
DECLARE
--b_body BLOB := :body;
c_body CLOB := :body_text;
BEGIN
if :body_text is null then
htp.print('EMPTY');
end if;
END;
As it says in documentation https://docs.oracle.com/en/database/oracle/oracle-rest-data-services/18.3/aelig/implicit-parameters.html#GUID-76A23568-EA67-4375-A4AA-880E1D160D27, for each implicit parameter :body and :body_text "if it is dereferenced more than once, then the second and subsequent dereferences will appear to be empty."
So, change your code like this:
DECLARE
--b_body BLOB := :body;
c_body CLOB := :body_text;
BEGIN
if c_body is null then
htp.print('EMPTY');
end if;
END;
If I remember correct it's not a good idea to use both binds in 1 code block...
If ORDS checks that you're using :body, :body_text is not populated (I think because of the overall performance of converting a blob to clob).
So just use :body_text and you should be fine!
This symptom may result from creating RESTful Services via older versions of the APEX SQL Workshop interface. APEX 5.1 certainly exhibits this behaviour, possibly others. If you are unable to upgrade APEX, use SQL Developer to create your ORDS modules.

How to send event to multiple forms in Delphi

In my application I have multiple forms that can be visible at the same time and they all show disk space (files, hard disk size etc.) in the same size units. So all of them show disk space in Bytes, KB, MB, GB or TB. I also have a seperate settings form in which the user can change the display size, he wants in the other forms. Once the user clicks OK in the settings form, I want all the other (open) forms to immediately change their size settings.
Every form has a protected procedure SetViewSettings, which takes care of the job. They are all descendants of an ancestor form which defines SetViewSettings as virtual and abstract. The actual displayed forms override the SetViewSettings method of the ancestor. So far no problems.
Because I don't want to call every individual form (FormX.SetViewSetttings, FormY.SetViewSettings, etc.), I am using the following solution:
procedure TApplicationForms.SetUnits;
var
I: Integer;
begin
for I := 0 to Screen.FormCount - 1 do
if Screen.Forms[I] is TfrAncestorInfo then
with Screen.Forms[I] as TfrAncestorInfo do
acSetUnits.Execute;
end;
This procedure is called from the SettingsForm as the user clicks OK.
TFrAncestorInfo is a descendant of TForm, declaring the SetViewSettings method as virtual and abstract. acSetUnits is an Action, declared in TfrAncestorInfo, which only calls SetViewSettings. This all works fine, but the risk lies in creating a new descendant form of TFrAncestorInfo, whilest forgetting to override the SetViewSettings method, in which case you will run into an 'Abstract Error' exception.
Are there any alternatives to calling the SetViewSettings method in the forms, without listing (calling) all the descendant forms individually? I know of messages and events, but I don't know how to use these in a multiple forms situation. In general: how can I directly send a message to or generate an event for all TFrAncestorInfo descendant forms, without listing them individually?
One option (there are many other possible options) would be to send a custom message to every Form. No need for worry about virtual/abstract overriding, type checking, etc. Only the Forms that implement a message handler will react to the message, the rest will simply ignore it.
const
WM_SETTINGS_UPDATED = WM_APP + 1;
procedure TApplicationForms.SetUnits;
var
I: Integer;
begin
for I := 0 to Screen.FormCount - 1 do
Screen.Forms[I].Perform(WM_SETTINGS_UPDATED, 0, 0);
end;
type
TSomeForm = class(TBaseForm)
private
procedure WMSettingsUpdated(var Message: TMessage); message WM_SETTINGS_UPDATED;
protected
procedure SetViewSettings;
end;
procedure TSomeForm.WMSettingsUpdated(var Message: TMessage);
begin
SetViewSettings;
end;
procedure TSomeForm.SetViewSettings;
begin
//...
end;

Can the Sequence of RecordSets in a Multiple RecordSet ADO.Net resultset be determined, controlled?

I am using code similar to this Support / KB article to return multiple recordsets to my C# program.
But I don't want C# code to be dependant on the physical sequence of the recordsets returned, in order to do it's job.
So my question is, "Is there a way to determine which set of records from a multiplerecordset resultset am I currently processing?"
I know I could probably decipher this indirectly by looking for a unique column name or something per resultset, but I think/hope there is a better way.
P.S. I am using Visual Studio 2008 Pro & SQL Server 2008 Express Edition.
No, because the SqlDataReader is forward only. As far as I know, the best you can do is open the reader with KeyInfo and inspect the schema data table created with the reader's GetSchemaTable method (or just inspect the fields, which is easier, but less reliable).
I spent a couple of days on this. I ended up just living with the physical order dependency. I heavily commented both the code method and the stored procedure with !!!IMPORTANT!!!, and included an #If...#End If to output the result sets when needed to validate the stored procedure output.
The following code snippet may help you.
Helpful Code
Dim fContainsNextResult As Boolean
Dim oReader As DbDataReader = Nothing
oReader = Me.SelectCommand.ExecuteReader(CommandBehavior.CloseConnection Or CommandBehavior.KeyInfo)
#If DEBUG_ignore Then
'load method of data table internally advances to the next result set
'therefore, must check to see if reader is closed instead of calling next result
Do
Dim oTable As New DataTable("Table")
oTable.Load(oReader)
oTable.WriteXml("C:\" + Environment.TickCount.ToString + ".xml")
oTable.Dispose()
Loop While oReader.IsClosed = False
'must re-open the connection
Me.SelectCommand.Connection.Open()
'reload data reader
oReader = Me.SelectCommand.ExecuteReader(CommandBehavior.CloseConnection Or CommandBehavior.KeyInfo)
#End If
Do
Dim oSchemaTable As DataTable = oReader.GetSchemaTable
'!!!IMPORTANT!!! PopulateTable expects the result sets in a specific order
' Therefore, if you suddenly start getting exceptions that only a novice would make
' the stored procedure has been changed!
PopulateTable(oReader, oDatabaseTable, _includeHiddenFields)
fContainsNextResult = oReader.NextResult
Loop While fContainsNextResult
Because you're explicitly stating in which order to execute the SQL statements the results will appear in that same order. In any case if you want to programmatically determine which recordset you're processing you still have to identify some columns in the result.