xtool/contrib/mORMot/SQLite3/mORMotVCL.pas

495 lines
18 KiB
ObjectPascal

/// DB VCL dataset using TSQLTable/TSQLTableJSON data access
// - this unit is a part of the freeware Synopse framework,
// licensed under a MPL/GPL/LGPL tri-license; version 1.18
unit mORMotVCL;
{
This file is part of Synopse mORmot framework.
Synopse mORMot framework. Copyright (C) 2022 Arnaud Bouchez
Synopse Informatique - https://synopse.info
*** BEGIN LICENSE BLOCK *****
Version: MPL 1.1/GPL 2.0/LGPL 2.1
The contents of this file are subject to the Mozilla Public License Version
1.1 (the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.mozilla.org/MPL
Software distributed under the License is distributed on an "AS IS" basis,
WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
for the specific language governing rights and limitations under the License.
The Original Code is Synopse mORMot framework.
The Initial Developer of the Original Code is Arnaud Bouchez.
Portions created by the Initial Developer are Copyright (C) 2022
the Initial Developer. All Rights Reserved.
Contributor(s):
- Alfred Glaenzer (alf)
Alternatively, the contents of this file may be used under the terms of
either the GNU General Public License Version 2 or later (the "GPL"), or
the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
in which case the provisions of the GPL or the LGPL are applicable instead
of those above. If you wish to allow use of your version of this file only
under the terms of either the GPL or the LGPL, and not to allow others to
use your version of this file under the terms of the MPL, indicate your
decision by deleting the provisions above and replace them with the notice
and other provisions required by the GPL or the LGPL. If you do not delete
the provisions above, a recipient may use your version of this file under
the terms of any one of the MPL, the GPL or the LGPL.
***** END LICENSE BLOCK *****
Version 1.17
- first public release, corresponding to Synopse mORMot Framework 1.17
Version 1.18
- renamed SQLite3VCL.pas to mORMotVCL.pas
- fixed ticket [9de8be5d9e] with some types like TEnumeration or TTimeLog
- fixed process with Unicode content
- introduced new aForceWideString optional parameter for ticket [2970335e40]
}
{$I Synopse.inc} // define HASINLINE CPU32 CPU64 OWNNORMTOUPPER
interface
uses
{$ifdef ISDELPHIXE2}System.SysUtils,{$else}SysUtils,{$endif}
Classes,
{$ifndef FPC}
Contnrs,
{$endif}
DB,
SynVirtualDataSet,
SynCommons, mORMot;
type
/// read-only virtual TDataSet able to access a TSQLTable
TSynSQLTableDataSet = class(TSynVirtualDataSet)
protected
fTable: TSQLTable;
{$ifndef UNICODE}
fForceWideString: boolean;
{$endif}
fTableShouldBeFreed: boolean;
fTemp64: Int64;
fTempBlob: TSQLRawBlob;
procedure InternalInitFieldDefs; override;
function GetRecordCount: Integer; override;
function GetRowFieldData(Field: TField; RowIndex: integer;
out ResultLen: Integer; OnlyCheckNull: boolean): Pointer; override;
function SearchForField(const aLookupFieldName: RawUTF8; const aLookupValue: variant;
aOptions: TLocateOptions): integer; override;
public
/// initialize the virtual TDataSet from a TSQLTable
// - WARNING: the supplied TSQLTable instance shall remain available
// all the time the returned TSynSQLTableDataSet instance is used, unless
// the TableShouldBeFreed property is set to true or CreateOwnedTable()
// constructor is used instead
// - with non-Unicode version of Delphi, you can set ForceWideString to
// force the use of WideString fields instead of AnsiString, if needed
// - the TDataSet will be opened once created
constructor Create(Owner: TComponent; Table: TSQLTable
{$ifndef UNICODE}; ForceWideString: boolean=false{$endif}); reintroduce;
/// initialize the virtual TDataSet owning a TSQLTable
// - this constructor will set TableShouldBeFreed to TRUE
// - with non-Unicode version of Delphi, you can set ForceWideString to
// force the use of WideString fields instead of AnsiString, if needed
// - the TDataSet will be opened once created
constructor CreateOwnedTable(Owner: TComponent; Table: TSQLTable
{$ifndef UNICODE}; ForceWideString: boolean=false{$endif}); reintroduce;
/// initialize the virtual TDataSet from a supplied JSON result
// - this constructor will parse the supplied JSON content and create
// an internal TSQLTableJSON instance to process the data, guessing the
// column types from the JSON content
// - with non-Unicode version of Delphi, you can set ForceWideString to
// force the use of WideString fields instead of AnsiString, if needed
// - the TDataSet will be opened once created
constructor CreateFromJSON(Owner: TComponent; const JSON: RawUTF8
{$ifndef UNICODE}; ForceWideString: boolean=false{$endif}); reintroduce; overload;
/// initialize the virtual TDataSet from a supplied JSON result
// - you can set the expected column types matching the results column layout
// - this constructor will parse the supplied JSON content and create
// an internal TSQLTableJSON instance to process the data
// - with non-Unicode version of Delphi, you can set ForceWideString to
// force the use of WideString fields instead of AnsiString, if needed
// - the TDataSet will be opened once created
constructor CreateFromJSON(Owner: TComponent; const JSON: RawUTF8;
const ColumnTypes: array of TSQLFieldType
{$ifndef UNICODE}; ForceWideString: boolean=false{$endif}); reintroduce; overload;
/// initialize the virtual TDataSet from a supplied JSON ORM result
// - you can set the TSQLRecord classes to retrieve the expected column types
// - this constructor will parse the supplied JSON content and create
// an internal TSQLTableJSON instance to process the data
// - with non-Unicode version of Delphi, you can set ForceWideString to
// force the use of WideString fields instead of AnsiString, if needed
// - the TDataSet will be opened once created
constructor CreateFromJSON(Owner: TComponent; const JSON: RawUTF8;
const Tables: array of TSQLRecordClass
{$ifndef UNICODE}; ForceWideString: boolean=false{$endif}); reintroduce; overload;
/// finalize the class instance
destructor Destroy; override;
/// if the supplied TSQLTable instance should be released with this class
// - Create() will left to FALSE (meaning that the TSQLTable instance shall
// remain available all the time the TSynSQLTableDataSet instance is used)
// - CreateOwnedTable() will set to TRUE if you want the TSQLTable to be
// freed when this TSynSQLTableDataSet instance will be released
// - you can also set it after Create(), on purpose
property TableShouldBeFreed: boolean read fTableShouldBeFreed write fTableShouldBeFreed;
/// access to the internal TSQLTable[JSON] data
// - you can use e.g. the SortFields() methods
// - you may change the table content on the fly, if the column remains the same
property Table: TSQLTable read fTable write fTable;
end;
/// store low-level DB.pas field information
// - as used by GetDBFieldDef and GetDBFieldValue
TDBFieldDef = record
FieldName: string;
DBType: TFieldType;
DBSize: Integer;
SQLType: TSQLFieldType;
SQLIndex: integer;
FieldType: PSQLTableFieldType;
end;
/// get low-level DB.pas field information
// - ready to be added to a TDataset as:
// ! aDataSet.FieldDefs.Add(FieldName,DBType,DBSize);
procedure GetDBFieldDef(aTable: TSQLTable; aField: integer;
out DBFieldDef: TDBFieldDef{$ifndef UNICODE}; aForceWideString: boolean=false{$endif});
/// fill a DB.pas field content
// - used e.g. by mORMotMidasVCL.ToClientDataSet
procedure GetDBFieldValue(aTable: TSQLTable; aRow: integer; aField: TField;
aDataSet: TDataSet; const DBFieldDef: TDBFieldDef);
/// convert a JSON result into a VCL DataSet, guessing the field types from the JSON
// - this function is just a wrapper around TSynSQLTableDataSet.CreateFromJSON()
// - with non-Unicode version of Delphi, you can set aForceWideString to
// force the use of WideString fields instead of AnsiString, if needed
// - with Unicode version of Delphi (2009+), string/UnicodeString will be used
function JSONToDataSet(aOwner: TComponent; const aJSON: RawUTF8
{$ifndef UNICODE}; aForceWideString: boolean=false{$endif}): TSynSQLTableDataSet; overload;
{$ifdef HASINLINE}inline;{$endif}
/// convert a JSON ORM result into a VCL DataSet, following TSQLRecord field types
// - this function is just a wrapper around TSynSQLTableDataSet.CreateFromJSON()
// - with non-Unicode version of Delphi, you can set aForceWideString to
// force the use of WideString fields instead of AnsiString, if needed
// - with Unicode version of Delphi (2009+), string/UnicodeString will be used
function JSONTableToDataSet(aOwner: TComponent; const aJSON: RawUTF8;
const Tables: array of TSQLRecordClass
{$ifndef UNICODE}; aForceWideString: boolean=false{$endif}): TSynSQLTableDataSet;
/// convert a JSON result into a VCL DataSet, with a given set of column types
// - this function is just a wrapper around TSynSQLTableDataSet.CreateFromJSON()
// - with non-Unicode version of Delphi, you can set aForceWideString to
// force the use of WideString fields instead of AnsiString, if needed
// - with Unicode version of Delphi (2009+), string/UnicodeString will be used
function JSONToDataSet(aOwner: TComponent; const aJSON: RawUTF8;
const ColumnTypes: array of TSQLFieldType
{$ifndef UNICODE}; aForceWideString: boolean=false{$endif}): TSynSQLTableDataSet; overload;
implementation
function JSONToDataSet(aOwner: TComponent; const aJSON: RawUTF8
{$ifndef UNICODE}; aForceWideString: boolean{$endif}): TSynSQLTableDataSet;
begin
result := TSynSQLTableDataSet.CreateFromJSON(
aOwner,aJSON{$ifndef UNICODE},aForceWideString{$endif});
end;
function JSONToDataSet(aOwner: TComponent; const aJSON: RawUTF8;
const ColumnTypes: array of TSQLFieldType
{$ifndef UNICODE}; aForceWideString: boolean{$endif}): TSynSQLTableDataSet;
begin
result := TSynSQLTableDataSet.CreateFromJSON(
aOwner,aJSON,ColumnTypes{$ifndef UNICODE},aForceWideString{$endif});
end;
function JSONTableToDataSet(aOwner: TComponent; const aJSON: RawUTF8;
const Tables: array of TSQLRecordClass
{$ifndef UNICODE}; aForceWideString: boolean{$endif}): TSynSQLTableDataSet;
begin
result := TSynSQLTableDataSet.CreateFromJSON(
aOwner,aJSON,Tables{$ifndef UNICODE},aForceWideString{$endif});
end;
{ TSynSQLTableDataSet }
constructor TSynSQLTableDataSet.Create(Owner: TComponent; Table: TSQLTable
{$ifndef UNICODE}; ForceWideString: boolean{$endif});
begin
inherited Create(Owner);
{$ifndef UNICODE}
fForceWideString := ForceWideString;
{$endif}
if Table<>nil then
fTable := Table;
Open;
end;
constructor TSynSQLTableDataSet.CreateOwnedTable(Owner: TComponent; Table: TSQLTable
{$ifndef UNICODE}; ForceWideString: boolean{$endif});
begin
Create(Owner,Table{$ifndef UNICODE},ForceWideString{$endif});
if Table<>nil then
fTableShouldBeFreed := true;
end;
constructor TSynSQLTableDataSet.CreateFromJSON(Owner: TComponent; const JSON: RawUTF8
{$ifndef UNICODE}; ForceWideString: boolean{$endif});
var T: TSQLTable;
begin
T := TSQLTableJSON.Create('',JSON);
try
CreateOwnedTable(Owner,T{$ifndef UNICODE},ForceWideString{$endif});
T := nil;
finally
T.Free; // release temporary instance in case of TSynSQLTableDataSet error
end;
end;
constructor TSynSQLTableDataSet.CreateFromJSON(Owner: TComponent; const JSON: RawUTF8;
const ColumnTypes: array of TSQLFieldType
{$ifndef UNICODE}; ForceWideString: boolean{$endif});
var T: TSQLTable;
begin
T := TSQLTableJSON.CreateWithColumnTypes(ColumnTypes,'',JSON);
try
CreateOwnedTable(Owner,T{$ifndef UNICODE},ForceWideString{$endif});
T := nil;
finally
T.Free; // release temporary instance in case of TSynSQLTableDataSet error
end;
end;
constructor TSynSQLTableDataSet.CreateFromJSON(Owner: TComponent; const JSON: RawUTF8;
const Tables: array of TSQLRecordClass
{$ifndef UNICODE}; ForceWideString: boolean{$endif});
var T: TSQLTable;
begin
T := TSQLTableJSON.CreateFromTables(Tables,'',JSON);
try
CreateOwnedTable(Owner,T{$ifndef UNICODE},ForceWideString{$endif});
T := nil;
finally
T.Free; // release temporary instance in case of TSynSQLTableDataSet error
end;
end;
destructor TSynSQLTableDataSet.Destroy;
begin
inherited;
if fTableShouldBeFreed then
FreeAndNil(fTable);
end;
function TSynSQLTableDataSet.GetRecordCount: Integer;
begin
if fTable<>nil then
result := fTable.RowCount else
result := 0;
end;
function TSynSQLTableDataSet.GetRowFieldData(Field: TField; RowIndex: integer;
out ResultLen: Integer; OnlyCheckNull: boolean): Pointer;
var info: PSQLTableFieldType;
F: integer;
P: PUTF8Char;
label Txt;
begin
result := nil;
F := Field.Index;
inc(RowIndex); // first TSQLTable row are field names
P := fTable.Get(RowIndex,F);
if P=nil then // null field or out-of-range RowIndex/F -> result := nil
exit;
result := @fTemp64; // let result point to Int64, Double or TDatetime
if OnlyCheckNull then
exit;
case fTable.FieldType(F,info) of
sftBoolean, sftInteger, sftID, sftTID:
SetInt64(P,fTemp64);
sftFloat, sftCurrency:
unaligned(PDouble(@fTemp64)^) := GetExtended(P);
sftEnumerate, sftSet:
if info^.ContentTypeInfo=nil then
SetInt64(P,fTemp64) else
goto Txt;
sftDateTime, sftDateTimeMS:
unaligned(PDouble(@fTemp64)^) := Iso8601ToDateTimePUTF8Char(P,0);
sftTimeLog, sftModTime, sftCreateTime:
unaligned(PDouble(@fTemp64)^) := TimeLogToDateTime(GetInt64(P));
sftUnixTime:
unaligned(PDouble(@fTemp64)^) := UnixTimeToDateTime(GetInt64(P));
sftUnixMSTime:
unaligned(PDouble(@fTemp64)^) := UnixMSTimeToDateTime(GetInt64(P));
sftBlob: begin
fTempBlob := BlobToTSQLRawBlob(P);
result := pointer(fTempBlob);
resultLen := length(fTempBlob);
end;
else begin // e.g. sftUTF8Text
Txt:result := P;
resultLen := StrLen(P);
end;
end;
end;
procedure GetDBFieldValue(aTable: TSQLTable; aRow: integer; aField: TField;
aDataSet: TDataSet; const DBFieldDef: TDBFieldDef);
var blob: TSQLRawBlob;
sstream,dstream: TStream;
P: PUTF8Char;
begin
if (aField<>nil) and (aRow>0) then
with DBFieldDef do begin
P := aTable.Get(aRow,SQLIndex);
if P=nil then
aField.Clear else
case SQLType of
sftBoolean:
aField.AsBoolean := GetInt64(P)<>0;
sftInteger, sftID, sftTID, sftSessionUserID:
if aField.DataType=ftLargeInt then // handle Int64 values directly
TLargeintField(aField).Value := GetInt64(P) else
aField.AsInteger := GetInteger(P);
sftFloat, sftCurrency:
aField.AsFloat := GetExtended(P);
sftEnumerate, sftSet:
if FieldType^.ContentTypeInfo=nil then
aField.AsInteger := GetInteger(P) else
aField.AsString := aTable.GetString(aRow,SQLIndex);
sftDateTime, sftDateTimeMS:
aField.AsDateTime := Iso8601ToDateTimePUTF8Char(P,0);
sftUnixTime:
aField.AsDateTime := UnixTimeToDateTime(GetInt64(P));
sftUnixMSTime:
aField.AsDateTime := UnixMSTimeToDateTime(GetInt64(P));
sftTimeLog, sftModTime, sftCreateTime:
aField.AsDateTime := TimeLogToDateTime(GetInt64(P));
sftBlob: begin
blob := BlobToTSQLRawBlob(P);
if (blob='') or (aDataSet=nil) then
aField.Clear else begin
sstream := TRawByteStringStream.Create(blob);
try
dstream := aDataSet.CreateBlobStream(aField,bmWrite);
try
dstream.CopyFrom(sstream,0);
finally
dstream.Free;
end;
finally
sstream.Free;
end;
end;
end;
sftUTF8Text:
if aField.DataType=ftWideString then
TWideStringField(aField).Value := aTable.GetSynUnicode(aRow,SQLIndex) else
aField.AsString := aTable.GetString(aRow,SQLIndex);
else
aField.AsVariant := aTable.GetVariant(aRow,SQLIndex);
end;
end;
end;
procedure GetDBFieldDef(aTable: TSQLTable; aField: integer;
out DBFieldDef: TDBFieldDef{$ifndef UNICODE}; aForceWideString: boolean{$endif});
begin
with DBFieldDef do begin
DBSize := 0;
SQLIndex := aField;
FieldName := aTable.GetString(0,aField);
if FieldName='' then begin
DBType := DB.ftUnknown;
SQLType := sftUnknown;
end else begin
SQLType := aTable.FieldType(aField,FieldType);
case SQLType of
sftBoolean:
DBType := ftBoolean;
sftInteger, sftID, sftTID:
DBType := ftLargeint; // LargeInt=Int64
sftFloat, sftCurrency:
DBType := ftFloat;
sftEnumerate, sftSet:
if FieldType^.ContentTypeInfo=nil then
DBType := ftInteger else begin
DBSize := 64;
DBType := ftDefaultVCLString;
end;
sftRecord: begin
DBSize := 64;
DBType := ftDefaultVCLString;
end;
sftDateTime, sftDateTimeMS, sftUnixTime, sftUnixMSTime,
sftTimeLog, sftModTime, sftCreateTime:
DBType := ftDateTime;
sftBlob: begin
DBSize := (aTable.FieldLengthMax(aField,true)*3) shr 2;
DBType := DB.ftBlob;
end;
sftUTF8Text: begin
DBSize := aTable.FieldLengthMax(aField,true);
{$ifndef UNICODE} // for Delphi 2009+ TWideStringField = UnicodeString!
if aForceWideString then
DBType := ftWideString else
{$endif}
DBType := ftDefaultVCLString;
end;
else begin
DBType := ftDefaultVCLString;
DBSize := aTable.FieldLengthMax(aField,true);
end;
end;
end;
end;
end;
procedure TSynSQLTableDataSet.InternalInitFieldDefs;
var F: Integer;
Def: TDBFieldDef;
begin
FieldDefs.Clear;
for F := 0 to fTable.FieldCount-1 do begin
GetDBFieldDef(fTable,F,Def{$ifndef UNICODE},fForceWideString{$endif});
FieldDefs.Add(Def.FieldName,Def.DBType,Def.DBSize);
end;
end;
function TSynSQLTableDataSet.SearchForField(const aLookupFieldName: RawUTF8;
const aLookupValue: variant; aOptions: TLocateOptions): integer;
var f: integer;
val: RawUTF8;
begin
f := Table.FieldIndex(aLookupFieldName);
if f<0 then
result := 0 else begin
VariantToUTF8(aLookupValue,val);
if loPartialKey in aOptions then
result := Table.SearchFieldIdemPChar(val,f) else
result := Table.SearchFieldEquals(val,f,1,not (loCaseInsensitive in aOptions));
end;
end;
end.