For non-blob fields, TRemoteStatement.FieldsToStream (RemoteDB.Server.Wrappers.pas) writes Fields[col].DataSize bytes, i.e. the declared buffer size, not the actual value length. If a SQL result column is reported by FireDAC as a string field with a large declared size, every row carries that size in zero padding. We would like to know whether RemoteDB can avoid sending the padding.
Observed
- Query: SELECT id, number, coalesce((SELECT lang_data FROM ...), (SELECT '')) AS f2 ... LIMIT 2500 (lang_data is varchar(255); the coalesce with an untyped literal gives postgreSQL type text, shown by our SQL tool as varchar(10485760))
- On the server FireDAC reports f2 as WideString, Size=8190, DataSize=16382
- Response body for 2,500 rows: 41,337,500 bytes = 16,535 bytes per row, almost all zero padding
- On the wire (deflate, chunked) the same response is only about 259,000 bytes (server side count of bytes written into the HTTP response), so the data is highly compressible padding
- The same query with CAST(... AS varchar(255)) reports Size=255, DataSize=512: response body 1,662,500 bytes (665 bytes per row)
Consequences on the client (measured)
- Client-side memory and CPU cost grow with the padded size, not the payload. For pages of 5,000 to 10,000 rows (82 to 165 MB decompressed) TRemoteDBProvider.Open spent 17 to 27 seconds in TTableData.Write in a debug build (FastMM FullDebugMode), which we believe is caused by the TMemoryStream growing in 8 KB steps; in a release build the same copy loop took 47 to 150 ms.
- Responses above MemoryCacheSize (default 100 MB) go to a temp file; with the padded data we saw about 76 MB written and read back per workflow, with the cast it dropped to a few KB.
Where the code assumes full-size values
- Server: TRemoteStatement.FieldsToStream - non-blob branch: Size := D.Fields[col].DataSize, followed by S.Write(blob[0], Size)
- Client: TRemoteDBProvider.GetData - System.Move(B[0], Buffer[0], Size) with Size = Fields[I].DataSize of the client dataset, i.e. it assumes Length(B) = DataSize
- Client: TBaseXDataset.FillRecordData allocates SetLength(Bytes, DataSize) and sets the last byte (ftString) or last two bytes (ftWideString) to zero
Workaround we use now:
We cast the affected SQL expression back to the declared column length (varchar(255)). This helps for this case but needs the column length to be known in the generator.
Is there an existing or planned option to send ftString / ftWideString values in their actual length (up to and including the terminator) and to pad on the client?