Display a (Synopse) SQLite3 table-column in a Delphi7 TListView - database

I would like to take the following unit (DrivesData) and display the drive column in a TListView. I've never worked with the (Synopse) SQLite3 code before so I'm hoping someone could give me a little push in the right direction.
Just add the DrivesData unit to the uses clause then run and it will create the "drives.sqlite" database file with a list of drives 'A' to 'Z'.
unit DrivesData;
interface
uses
SynCommons, SQLite3Commons;
type
TDrives = class(TSQLRecord)
private
{ Private declarations }
FDrive: RawUTF8;
protected
{ Protected declarations }
FDrivesModel: TSQLModel;
FDrivesDatabase: TSQLRest;
public
{ Public declarations }
constructor Create(); override;
destructor Destroy(); override;
published
{ Published declarations }
property Drive: RawUTF8 read FDrive write FDrive;
end;
var
DriveRecord: TDrives;
implementation
uses
SQLite3;
function CreateDrivesModel(): TSQLModel;
begin
Result := TSQLModel.Create([TDrives]);
end;
{ TDrives }
constructor TDrives.Create();
var
X: Char;
begin
inherited Create();
FDrivesModel := CreateDrivesModel();
FDrivesDatabase := TSQLRestServerDB.Create(FDrivesModel, 'drives.sqlite');
TSQLRestServerDB(FDrivesDatabase).DB.Execute(
'CREATE TABLE IF NOT EXISTS drives ' +
'(id INTEGER PRIMARY KEY, drive TEXT NOT NULL UNIQUE COLLATE NOCASE);');
for X := 'A' to 'Z' do
begin
TSQLRestServerDB(FDrivesDatabase).DB.Execute(
'INSERT OR IGNORE INTO drives (drive) VALUES ("' + X + ':")');
end;
end;
destructor TDrives.Destroy();
begin
if Assigned(FDrivesDatabase) then
FDrivesDatabase.Free();
if Assigned(FDrivesModel) then
FDrivesModel.Free();
inherited Destroy();
end;
initialization
DriveRecord := TDrives.Create();
finalization
if Assigned(DriveRecord) then
DriveRecord.Free();
end.

Nice try!
But I'm afraid you are missing some points of the framework:
for instance you're mixing record level and MVC application level: a TSQLRecord maps a DB table and you should not declare MVC TSQLModel and TSQLRest inside this class;
and you're missing the ORM approach, you don't need to write all those SQL code (the CREATE TABLE and the INSERT): the framework will write it for you, with no error, and the exact expected column type (with collations)!
Instead of using a TSQLRestServerDB directly by itself, you should better use a TSQLRestClientDB (which will instantiate its privately owned TSQLRestServerDB), even if you are still working locally. So you'll get a lot more features with no performance penalty.
You are using a Char type in your code. Our framework is UTF-8 oriented, so you should use AnsiChar instead, or use StringToUtf8() function to ensure correctness (at least with Unicode version of Delphi).
I'll recommend that you take a look at the sample code source code and the provided documentation (especially the SAD document, in the general presentation in the first pages, including the SynFile main demo).
To retrieve some data, then display it in the VCL (e.g. in a TListBox), take a look at the TSQLTableJSON class. There are some code sample in the SAD document (take a look at the keyword index, at the beginning of the document, if you're a bit lost).
Perhaps StackOverflow is not the best place to ask such specific questions. You have our forum available at http://synopse.info to post any questions regarding this framework. You can post your code here.
Thanks for your interest!

Related

How to call a function with void return using Firedac FDConnection Component in Delphi XE5?

I recently started using the [ExecSQLScalar]1 and [ExecSQL]2 methods of the FDConnection component in Delphi XE5. It's very handy not to need to build a Dataset object, like FDQuery just for simple queries or executions.
However I had a curious problem when executing a function with void return that has internal validations where it can generate exceptions. I'm using a Postgres database.
CREATE FUNCTION can_be_exception()
RETURNS void AS
$$
BEGIN
RAISE EXCEPTION E'fail';
END;
$$
LANGUAGE plpgsql STABLE;
In delphi, I call the ExecSQLScalar function ...
FDConnection1.ExecSQLScalar('select 1');
FDConnection1.ExecSQLScalar('select can_be_exception()');
On first run, I get the following error:
Project TFDConnectionDEMO.exe raised exception class
EPgNativeException with message '[FireDAC][Phys][PG][libpq] ERROR:
fail'.
On the second run, I get a Violation Access error:
Project TFDConnectionDEMO.exe raised exception class $C0000005 with
message 'access violation at 0x00000000: read of address 0x00000000'.
Apparently the error occurs in the line below in unit FireDAC.Comp.Client
function TFDCustomConnection.ExecSQLScalar(const ASQL: String;
const AParams: array of Variant; const ATypes: array of TFieldType): Variant;
var
oCmd: IFDPhysCommand;
begin
oCmd := BaseCreateSQL;
try
if BasePrepareSQL(oCmd, ASQL, AParams, ATypes) or (FExecSQLTab = nil) then begin
FDFree(FExecSQLTab);
...
ignoring the previous error and trying again, another error is displayed...
Project TZConnectionDEMO.exe raised exception class EFDException with
message '[FireDAC][DatS]-24. Row is not nested'.
Searching, I found no response to this error. I figured my mistake would be to call the bank raise_exception function using the ExecSQLScalar function of the FDConnection component. So I tried using FDConnection.ExecSQL and as I imagined, you can not use this if there is a SELECT clause in the parameter.
Is there a better way to call function with void return using FDConnection.ExecSQL? would a BUG be in the component? or would not it be correct to make that kind of call?
Using ExecSQLScalar is fine in this case. This is certainly a bug (which was already fixed, at least in Delphi 10.2.3). As you've correctly pointed out, the problem is in releasing a table storage object instance held by the FExecSQLTab field by using FDFree procedure.
I don't have Delphi XE5 source code but maybe you can see something like this inside (comments about what happened are added by me):
if BasePrepareSQL(oCmd, ASQL, AParams, ATypes) or (FExecSQLTab = nil) then
begin
FDFree(FExecSQLTab); { ← directly calls destructor if object is not nil }
FExecSQLTab := oCmd.Define; { ← no assignment if command execution raises exception }
end;
Problem was that when a SQL command execution raised exception during storage table definition stage (oCmd.Define), reference to previously destroyed storage table object instance (by FDFree) remained stored in the FExecSQLTab field (as a dangling pointer).
Then when a different command was executed that way, FDFree procedure was called just for that dangling pointer. Hence the access violation.
Way to correct this is replacing line e.g. by:
FDFree(FExecSQLTab);
by:
FDFreeAndNil(FExecSQLTab);
which was done in some later Delphi release.

Ada : operator is not directly visible

I'm using GNAT GPS studio IDE in order to train a bit in Ada. I'm having an issue with package visibility.
First I specify a package in a file called "DScale.ads" containing a type:
package DScale is
type DMajor is (D, E, F_Sharp, G, A, B, C_Sharp);
end DScale;
Then I specify in a different file ("Noteworthy.ads") a package that defines a procedure that will use the DMajor type of the DScale package:
with Ada.Text_IO;
with DScale;
package NoteWorthy is
procedure Note;
end NoteWorthy;
Finally in "Noteworthy.adb" I provide the package body for the package "Noteworthy":
with Ada.Text_IO; use Ada.Text_IO;
package body Noteworthy is
procedure Note is
package ScaleIO is new Enumeration_IO(DScale.DMajor);
thisNote : DScale.DMajor := DScale.D;
begin
ScaleIO.Get(thisNote);
if thisNote = DScale.DMajor'First then
Put_Line("First note of scale.");
end if;
end Note;
begin
null;
end NoteWorthy;
If I leave the code as-is, I will get an "operator not directly visible" error for the "if thisNote = DScale.DMajor'First then" statement in the body of the "Noteworthy" package.
Is there a way to bypass this error without using a "use" or "use type" clause?
Thank you.
There are (at least) two answers to your question.
1:
if DScale."=" (thisNote, DScale.DMajor'First) then
2:
function "=" (Left, Right : DScale.DMajor) return Boolean renames DScale.DMajor;
...
if thisNote = DScale.DMajor'First then
But why would you use one of those options instead of:
use type DScale.DMajor;
...
if thisNote = DScale.DMajor'First then
Ada types are much more than just descriptions of the values, they are entities that bring into existence a whole slew of operations, embodied as both operators and attributes. So if you want direct visibility to a type's concrete "=" operator, you have to make it visible. For that you need either "use" or "use type".
Why bypass a language feature? Simply use it wisely.
A couple of points that are related to the structure of your code.
The package spec for Noteworthy doesn't need to "with DScale" as there is nothing in the spec that refers to any feature in that package (maybe you plan to in the future).
The use of enumeration_io is good if you want to simply want to do I/O to files to be read by a computer, but I would never use it for human interaction (apart from quick hacks). The input values it accepts is dictated by Ada's grammar, and typically not what user's expect.

How do I store say a file path that can be changed at runtime in Delphi

My program allows the user to change the location of the main set of files. Currently I have a text file with a fixed location that contains the folder location of the other files. However, this seems to almost defeat the purpose. Is there a better way of storing this file path?
Delphi contains the TRegistry class, which makes it very simple to save and retrieve your settings information from the Windows Registry.
uses
Registry;
const
RegKey = 'Software\Your Company\Your App\';
procedure TForm1.SaveSettingsClick(Sender: TObject);
var
Reg: TRegistry;
DataDir: string;
begin
// Use the result of SelectDirectory() or whatever means you use
// to get the desired location from the user here. ExtractFilePath()
// is only used as an example - it just gets the location of the
// application itself and then appends a subdirectory name to it.
DataDir:= ExtractFilePath(Application.ExeName) + 'Data\';
Reg := TRegistry.Create;
try
if Reg.OpenKey(RegKey, True) then
Reg.WriteString('DataDir', DataDir);
finally
Reg.Free;
end;
end;
procedure TForm1.GetSettingsClick(Sender: TObject);
var
Reg: TRegistry;
begin
Reg := TRegistry.Create;
try
if Reg.OpenKey(RegKey, False) then
Label1.Caption := Reg.ReadString('DataDir');
finally
Reg.Free;
end;
end;
As #SirRufo mentions in the comments, Delphi also has the TRegistryIniFile, which serves as a wrapper around the registry functions, allowing you to use it without any knowledge of the registry structure. (It's use is similar to that of TIniFile/TMemIniFile, which I'm describing next.)
If you prefer not to use the Registry, you can also use INI (text files) pretty easily. Delphi has support for standard (WinAPI based) INI files in TIniFile, as well as it's own (and IMO better implemented) TMemIniFile. Both are fairly compatible, so I'll demonstrate using just the TMemIniFile here.
(Locations used for reading/writing are for simplicity. In reality, you should use the appropriate subfolder of the %APPDATA% directory, obtained by the appropriate call to SHGetKnownFolderPath with the FOLDERID_RoamingAppData or FOLDERID_LocalAppData constant.)
uses
IniFiles;
// Writing
var
Ini: TMemIniFile;
RootDir: string;
begin
RootDir := ExtractFilePath(Application.ExeName);
Ini := TMemIniFile.Create(TheFile);
try
Ini.WriteString('Settings', 'DataDir', RootDir + 'Data\');
Ini.UpdateFile;
finally
Ini.Free;
end;
end;
// Reading
var
Ini: TMemIniFile;
RootDir: string;
begin
RootDir := ExtractFilePath(Application.ExeName);
Ini := TMemIniFile.Create(TheFile); // The file is your ini file name with path
try
DataDir := Ini.ReadString('Settings', 'DataDir', RootDir);
if DataDir = '' then
DataDir := RootDir; // No user specified location. Use app's dir
finally
Ini.Free;
end;
end;
I very much prefer inifiles. Your users can easily read and edit them if needed, you can keep all information neatly together, and you can easily copy or install configurations to other machines. Inifiles are so simple that you can easily parse them on platforms other than windows, and with any language.
Nowadays many people start using sqlite as some sort of filesystem replacement. They store settings, data files, history, logging, translations, etc all in an sqlite database. That happens on all platforms, even on mobile and embedded devices.
It takes a bit more to set it up, but after that it's pretty flexible.

Delphi mock and databases

I was reading Nick Hodges' blog http://nickhodges.com/post/Delphi-Mocks-The-Basics.aspx which is very interesting.
However it doesn't say how I can test classes with statements referring to databases in it for instance TQuery.Next or TQuery.ExecSQL or TQuery.FieldByName... etc
Did anybody face the same issue ? If so how did you solve it ?
What we have done at work is to have repositories that return a TDataSet. This way, you create a mock/fake of the repository (depending if you want to test how many tests methods get called, etc). Example:
TMyDatabaseRepository = class(TInterfacedObject, IMyDatabaseRepository)
// .. blah ..
function GetAllTheFun : TDataSet;
// .. blah ..
end;
TMyProcessThatAccessesTheDatabase = class
// .. blah ..
public
constructor Create(const AMyDatabaseRepository : IMyDatabaseRepository);
// .. blah ..
end;
So when it comes to testing, we passing in a mock IMyDatabaseRepository. GetAllTheFun actually returns a TKbmMemTable with data sample data set up.
While I haven't used Delphi Mocks, I do remember that you can override methods on the 'fly' (or something like that), so you might not need to go through the trouble of creating a separate class that implements IMyDatabaseRepository
Edit:
That is a fair large subject to try and cover here but there a few number of web sites (blogs) that cover this type of thing:
http://delphisorcery.blogspot.com.au/2011/09/pimp-your-unit-tests-using-mock-objects.html
http://www.finalbuilder.com/Resources/Blogs/PostId/417/introducing-delphi-mocks.aspx
http://blog.synopse.info/post/2012/10/14/Stubs-and-Mocks-for-Delphi-with-mORMot
http://blog.synopse.info/post/2012/10/14/Interfaces-in-practice%3A-dependency-injection%2C-stubs-and-mocks
http://blog.synopse.info/post/2012/10/14/Advanced-mocks-and-stubs

MSXML: How do ask an IXMLDOMNode what version MSXML it comes from?

How can i ask what version of MSXML an IXMLDOMDocument2 is? Given an IXMLDOMDocument2 i need to create another document of the same version.
If you give an IXMLDOMDocument from different versions of MSXML, you will get an exception from msxml:
It is an error to mix objects from different versions of MSXML.
Microsoft internally can ask an interface what version of MSXML it came from, i need access to the same thing.
Consider the following hypothetical function written in pseudo-code:
String XMLTransform(IXMLDOMNode xml, String xsl)
{
//Create a document to hold the xml
IXMLDOMDocument2 xslDocument = new CoDOMDocument();
//load the xsl string into the xsl dom document
xslDocument.loadXML(xsl);
//transform the xml
return xml.transformNode(xslDocument);
}
Problem is that if IXMLDOMNode comes from, say MSXML6. The created DOMDocument is from version 3 (because of Microsoft's version dependance in MSXML). This will cause the
xml.transformNode()
to throw a COM exception:
It is an error to mix objects from different versions of MSXML.
Since Microsoft is able to ask an interface what version of MSXML it came from, i should be able to do the same thing, but how?
Alternativly, given an IXMLDOMNode, how can i construct an XMLDOMDocument object of the same version...
i figured out an answer (that works for MSXML version 6.0 at least).
The interface type:
DOMDocument60
descends from IXMLDOMDocument30, while most people use IXMLDOMDocument or IXMLDOMDocument2.
So if the passed interface does not at least support IXMLDOMDocument3, then i know the object is not at least version 6:
procedure DoStuff(doc: IXMLDOMdocument2);
begin
if not (doc is IXMLDOMDocument3) then
raise Exception.Create('badness');
...
end;
Or alternativly:
procedure DoStuff(doc: IXMLDocument2);
begin
if not (doc is DOMDocument6) then
begin
DoStuffLegacyImplementation(doc);
Exit;
end;
//Real implementation
...
end;

Resources