2018-09-25 15:36:53 +02:00
|
|
|
// *************************************************************************** }
|
|
|
|
//
|
|
|
|
// Delphi MVC Framework
|
|
|
|
//
|
2019-01-08 12:48:27 +01:00
|
|
|
// Copyright (c) 2010-2019 Daniele Teti and the DMVCFramework Team
|
2018-09-25 15:36:53 +02:00
|
|
|
//
|
|
|
|
// https://github.com/danieleteti/delphimvcframework
|
|
|
|
//
|
|
|
|
// ***************************************************************************
|
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
|
|
|
|
//
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
// limitations under the License.
|
|
|
|
//
|
|
|
|
// ***************************************************************************
|
|
|
|
|
|
|
|
unit MVCFramework.ActiveRecord;
|
|
|
|
|
|
|
|
interface
|
|
|
|
|
|
|
|
uses
|
|
|
|
System.Generics.Defaults,
|
|
|
|
System.Generics.Collections,
|
|
|
|
System.RTTI,
|
|
|
|
FireDAC.DApt,
|
|
|
|
Data.DB,
|
2018-10-14 18:23:20 +02:00
|
|
|
FireDAC.Comp.Client,
|
2018-11-21 22:11:58 +01:00
|
|
|
FireDAC.Stan.Param,
|
2019-02-21 20:17:11 +01:00
|
|
|
System.SysUtils,
|
|
|
|
MVCFramework,
|
|
|
|
MVCFramework.Commons,
|
|
|
|
MVCFramework.RQL.Parser,
|
|
|
|
MVCFramework.Serializer.Intf;
|
2018-09-25 15:36:53 +02:00
|
|
|
|
|
|
|
type
|
|
|
|
EMVCActiveRecord = class(EMVCException)
|
|
|
|
public
|
|
|
|
constructor Create(const AMsg: string); reintroduce; { do not override!! }
|
|
|
|
end;
|
|
|
|
|
2019-01-18 18:11:27 +01:00
|
|
|
EMVCActiveRecordNotFound = class(EMVCActiveRecord)
|
|
|
|
|
|
|
|
end;
|
|
|
|
|
2018-09-25 15:36:53 +02:00
|
|
|
TMVCActiveRecordClass = class of TMVCActiveRecord;
|
2019-03-05 20:55:37 +01:00
|
|
|
TMVCActiveRecordFieldOption = (foPrimaryKey, foAutoGenerated, foTransient);
|
2018-10-14 18:23:20 +02:00
|
|
|
TMVCActiveRecordFieldOptions = set of TMVCActiveRecordFieldOption;
|
|
|
|
TMVCEntityAction = (eaCreate, eaRetrieve, eaUpdate, eaDelete);
|
|
|
|
TMVCEntityActions = set of TMVCEntityAction;
|
2018-12-09 23:03:06 +01:00
|
|
|
TMVCActiveRecordLoadOption = (loIgnoreNotExistentFields);
|
|
|
|
TMVCActiveRecordLoadOptions = set of TMVCActiveRecordLoadOption;
|
2018-10-14 18:23:20 +02:00
|
|
|
|
|
|
|
IMVCEntityProcessor = interface
|
|
|
|
['{E7CD11E6-9FF9-46D2-B7B0-DA5B38EAA14E}']
|
2019-03-05 20:55:37 +01:00
|
|
|
procedure GetEntities(const Context: TWebContext; const Renderer: TMVCRenderer; const entityname: string;
|
2018-10-14 18:23:20 +02:00
|
|
|
var Handled: Boolean);
|
2019-03-05 20:55:37 +01:00
|
|
|
procedure GetEntity(const Context: TWebContext; const Renderer: TMVCRenderer; const entityname: string;
|
|
|
|
const id: Integer; var Handled: Boolean);
|
|
|
|
procedure CreateEntity(const Context: TWebContext; const Renderer: TMVCRenderer; const entityname: string;
|
2018-10-14 18:23:20 +02:00
|
|
|
var Handled: Boolean);
|
2019-03-05 20:55:37 +01:00
|
|
|
procedure UpdateEntity(const Context: TWebContext; const Renderer: TMVCRenderer; const entityname: string;
|
|
|
|
const id: Integer; var Handled: Boolean);
|
|
|
|
procedure DeleteEntity(const Context: TWebContext; const Renderer: TMVCRenderer; const entityname: string;
|
|
|
|
const id: Integer; var Handled: Boolean);
|
2018-10-14 18:23:20 +02:00
|
|
|
end;
|
2018-09-25 15:36:53 +02:00
|
|
|
|
2018-10-14 18:23:20 +02:00
|
|
|
MVCActiveRecordCustomAttribute = class(TCustomAttribute)
|
2018-09-25 15:36:53 +02:00
|
|
|
|
|
|
|
end;
|
|
|
|
|
2018-10-23 16:18:34 +02:00
|
|
|
MVCTableAttribute = class(MVCActiveRecordCustomAttribute)
|
2019-01-08 12:48:27 +01:00
|
|
|
public
|
2018-09-25 15:36:53 +02:00
|
|
|
Name: string;
|
2018-10-14 18:23:20 +02:00
|
|
|
constructor Create(aName: string);
|
2018-09-25 15:36:53 +02:00
|
|
|
end;
|
|
|
|
|
2018-10-23 16:18:34 +02:00
|
|
|
MVCTableFieldAttribute = class(MVCActiveRecordCustomAttribute)
|
2018-09-25 15:36:53 +02:00
|
|
|
public
|
|
|
|
FieldName: string;
|
2019-02-15 12:21:11 +01:00
|
|
|
FieldOptions: TMVCActiveRecordFieldOptions;
|
2019-03-05 20:55:37 +01:00
|
|
|
constructor Create(const aFieldName: string; const aFieldOptions: TMVCActiveRecordFieldOptions); overload;
|
2019-02-15 12:21:11 +01:00
|
|
|
constructor Create(aFieldName: string); overload;
|
2018-09-25 15:36:53 +02:00
|
|
|
end;
|
|
|
|
|
2019-03-05 20:55:37 +01:00
|
|
|
MVCPrimaryKeyAttribute = MVCTableFieldAttribute deprecated '(ERROR) Use MVCTableFieldAttribute';
|
2018-09-25 15:36:53 +02:00
|
|
|
|
2018-10-23 16:18:34 +02:00
|
|
|
MVCEntityActionsAttribute = class(MVCActiveRecordCustomAttribute)
|
2018-10-14 18:23:20 +02:00
|
|
|
private
|
|
|
|
EntityAllowedActions: TMVCEntityActions;
|
|
|
|
public
|
|
|
|
constructor Create(const aEntityAllowedActions: TMVCEntityActions);
|
|
|
|
|
|
|
|
end;
|
|
|
|
|
2018-10-23 16:18:34 +02:00
|
|
|
TMVCActiveRecord = class;
|
|
|
|
|
2018-11-02 21:43:09 +01:00
|
|
|
TMVCSQLGenerator = class;
|
|
|
|
|
2018-10-23 16:18:34 +02:00
|
|
|
TMVCActiveRecordList = class(TObjectList<TMVCActiveRecord>)
|
|
|
|
public
|
|
|
|
constructor Create; virtual;
|
|
|
|
end;
|
|
|
|
|
2018-09-25 15:36:53 +02:00
|
|
|
TMVCActiveRecord = class
|
|
|
|
private
|
|
|
|
fConn: TFDConnection;
|
2018-11-02 21:43:09 +01:00
|
|
|
fSQLGenerator: TMVCSQLGenerator;
|
2018-09-25 15:36:53 +02:00
|
|
|
fPrimaryKeyFieldName: string;
|
2018-10-14 18:23:20 +02:00
|
|
|
fPrimaryKeyOptions: TMVCActiveRecordFieldOptions;
|
|
|
|
fEntityAllowedActions: TMVCEntityActions;
|
2018-11-02 21:43:09 +01:00
|
|
|
fRQL2SQL: TRQL2SQL;
|
2019-03-05 20:55:37 +01:00
|
|
|
procedure MapColumnToTValue(const aFieldName: string; const aField: TField; const aRTTIField: TRttiField);
|
2018-09-25 15:36:53 +02:00
|
|
|
procedure MapTValueToParam(const aValue: TValue; const aParam: TFDParam);
|
|
|
|
protected
|
|
|
|
fRTTIType: TRttiType;
|
|
|
|
fProps: TArray<TRttiField>;
|
|
|
|
fObjAttributes: TArray<TCustomAttribute>;
|
|
|
|
fPropsAttributes: TArray<TCustomAttribute>;
|
|
|
|
fTableName: string;
|
|
|
|
fMap: TDictionary<TRttiField, string>;
|
2019-02-21 20:17:11 +01:00
|
|
|
fMapNonTransientFields: TDictionary<TRttiField, string>;
|
2018-09-25 15:36:53 +02:00
|
|
|
fPrimaryKey: TRttiField;
|
2018-11-02 21:43:09 +01:00
|
|
|
fBackendDriver: string;
|
|
|
|
fMapping: TMVCFieldsMapping;
|
|
|
|
function GetBackEnd: string;
|
2018-10-23 16:18:34 +02:00
|
|
|
function SelfConnection: TFDConnection;
|
2018-09-25 15:36:53 +02:00
|
|
|
procedure InitTableInfo;
|
2019-03-05 20:55:37 +01:00
|
|
|
class function ExecQuery(const SQL: string; const Values: array of Variant): TDataSet; overload;
|
|
|
|
class function ExecQuery(const SQL: string; const Values: array of Variant; const Connection: TFDConnection)
|
2018-11-24 16:56:21 +01:00
|
|
|
: TDataSet; overload;
|
2018-10-14 18:23:20 +02:00
|
|
|
|
|
|
|
// class function ExecNonQuery(const SQL: string; const Values: array of Variant): int64; overload;
|
|
|
|
// class function ExecNonQuery(const SQL: string; const Values: array of Variant; const Connection: TFDConnection): int64; overload;
|
|
|
|
|
2019-03-05 20:55:37 +01:00
|
|
|
function ExecNonQuery(const SQL: string; RefreshAutoGenerated: Boolean = false): int64; overload;
|
2018-09-25 15:36:53 +02:00
|
|
|
// load events
|
|
|
|
/// <summary>
|
|
|
|
/// Called everywhere before persist object into database
|
|
|
|
/// </summary>
|
|
|
|
procedure OnValidation; virtual;
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Called just after load the object state from database
|
|
|
|
/// </summary>
|
|
|
|
procedure OnAfterLoad; virtual;
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Called before load the object state from database
|
|
|
|
/// </summary>
|
|
|
|
procedure OnBeforeLoad; virtual;
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Called before insert the object state to database
|
|
|
|
/// </summary>
|
|
|
|
procedure OnBeforeInsert; virtual;
|
2018-10-23 16:18:34 +02:00
|
|
|
|
2018-09-25 15:36:53 +02:00
|
|
|
/// <summary>
|
2018-10-23 16:18:34 +02:00
|
|
|
/// Called after insert the object state to database
|
2018-09-25 15:36:53 +02:00
|
|
|
/// </summary>
|
2018-10-23 16:18:34 +02:00
|
|
|
procedure OnAfterInsert; virtual;
|
2018-09-25 15:36:53 +02:00
|
|
|
|
2018-10-23 16:18:34 +02:00
|
|
|
/// <summary>
|
|
|
|
/// Called before update the object state to database
|
|
|
|
/// </summary>
|
2018-09-25 15:36:53 +02:00
|
|
|
procedure OnBeforeUpdate; virtual;
|
|
|
|
|
2018-10-23 16:18:34 +02:00
|
|
|
/// <summary>
|
|
|
|
/// Called after update the object state to database
|
|
|
|
/// </summary>
|
|
|
|
procedure OnAfterUpdate; virtual;
|
|
|
|
|
2018-09-25 15:36:53 +02:00
|
|
|
/// <summary>
|
|
|
|
/// Called before delete object from database
|
|
|
|
/// </summary>
|
|
|
|
procedure OnBeforeDelete; virtual;
|
|
|
|
|
2018-10-23 16:18:34 +02:00
|
|
|
/// <summary>
|
|
|
|
/// Called after delete object from database
|
|
|
|
/// </summary>
|
|
|
|
procedure OnAfterDelete; virtual;
|
|
|
|
|
2018-09-25 15:36:53 +02:00
|
|
|
/// <summary>
|
|
|
|
/// Called before insert or update the object to the database
|
|
|
|
/// </summary>
|
|
|
|
procedure OnBeforeInsertOrUpdate; virtual;
|
|
|
|
|
2019-03-18 14:08:34 +01:00
|
|
|
/// <summary>
|
|
|
|
/// Called before execute sql
|
|
|
|
/// </summary>
|
|
|
|
procedure OnBeforeExecuteSQL(var SQL:String); virtual;
|
|
|
|
|
2018-10-23 16:18:34 +02:00
|
|
|
/// <summary>
|
|
|
|
/// Called after insert or update the object to the database
|
|
|
|
/// </summary>
|
|
|
|
procedure OnAfterInsertOrUpdate; virtual;
|
|
|
|
|
2018-11-02 21:43:09 +01:00
|
|
|
function GenerateSelectSQL: string;
|
2018-11-24 16:56:21 +01:00
|
|
|
|
|
|
|
function SQLGenerator: TMVCSQLGenerator;
|
2018-09-25 15:36:53 +02:00
|
|
|
public
|
2018-11-24 16:56:21 +01:00
|
|
|
constructor Create(aLazyLoadConnection: Boolean); overload;
|
|
|
|
{ cannot be virtual! }
|
2018-09-28 13:01:46 +02:00
|
|
|
constructor Create; overload; virtual;
|
2018-09-25 15:36:53 +02:00
|
|
|
destructor Destroy; override;
|
2019-03-05 20:55:37 +01:00
|
|
|
function CheckAction(const aEntityAction: TMVCEntityAction; const aRaiseException: Boolean = True): Boolean;
|
2018-09-25 15:36:53 +02:00
|
|
|
procedure Insert;
|
2018-09-28 13:01:46 +02:00
|
|
|
function GetMapping: TMVCFieldsMapping;
|
2018-10-23 16:18:34 +02:00
|
|
|
function LoadByPK(id: int64): Boolean; virtual;
|
2018-09-25 15:36:53 +02:00
|
|
|
procedure Update;
|
|
|
|
procedure Delete;
|
|
|
|
function TableInfo: string;
|
2019-03-05 20:55:37 +01:00
|
|
|
procedure LoadByDataset(const aDataSet: TDataSet; const aOptions: TMVCActiveRecordLoadOptions = []);
|
2018-09-25 15:36:53 +02:00
|
|
|
procedure SetPK(const aValue: TValue);
|
|
|
|
function GetPK: TValue;
|
2019-03-05 20:55:37 +01:00
|
|
|
class function GetByPK<T: TMVCActiveRecord, constructor>(const aValue: int64): T; overload;
|
|
|
|
class function GetByPK(const aClass: TMVCActiveRecordClass; const aValue: int64): TMVCActiveRecord; overload;
|
|
|
|
class function GetScalar(const SQL: string; const Params: array of Variant): Variant;
|
|
|
|
class function Select<T: TMVCActiveRecord, constructor>(const SQL: string; const Params: array of Variant;
|
|
|
|
const Options: TMVCActiveRecordLoadOptions = []): TObjectList<T>; overload;
|
|
|
|
class function Select(const aClass: TMVCActiveRecordClass; const SQL: string; const Params: array of Variant)
|
2018-11-24 16:56:21 +01:00
|
|
|
: TMVCActiveRecordList; overload;
|
2019-03-05 20:55:37 +01:00
|
|
|
class function Select(const aClass: TMVCActiveRecordClass; const SQL: string; const Params: array of Variant;
|
2018-11-24 16:56:21 +01:00
|
|
|
const Connection: TFDConnection): TMVCActiveRecordList; overload;
|
2019-03-05 20:55:37 +01:00
|
|
|
class function SelectRQL(const aClass: TMVCActiveRecordClass; const RQL: string; const MaxRecordCount: Integer)
|
2018-11-24 16:56:21 +01:00
|
|
|
: TMVCActiveRecordList; overload;
|
2019-03-05 20:55:37 +01:00
|
|
|
class function DeleteRQL(const aClass: TMVCActiveRecordClass; const RQL: string): int64;
|
|
|
|
class function SelectRQL<T: constructor, TMVCActiveRecord>(const RQL: string; const MaxRecordCount: Integer)
|
2018-12-09 23:03:06 +01:00
|
|
|
: TObjectList<T>; overload;
|
2019-03-05 20:55:37 +01:00
|
|
|
function SelectRQL(const RQL: string; const MaxRecordCount: Integer): TMVCActiveRecordList; overload;
|
|
|
|
class function Where<T: TMVCActiveRecord, constructor>(const SQLWhere: string; const Params: array of Variant)
|
|
|
|
: TObjectList<T>; overload;
|
|
|
|
class function GetOneByWhere<T: TMVCActiveRecord, constructor>(const SQLWhere: string;
|
|
|
|
const Params: array of Variant; const RaiseExceptionIfNotFound: Boolean = True): T;
|
|
|
|
class function GetFirstByWhere<T: TMVCActiveRecord, constructor>(const SQLWhere: string;
|
|
|
|
const Params: array of Variant; const RaiseExceptionIfNotFound: Boolean = True): T;
|
|
|
|
class function Where(const aClass: TMVCActiveRecordClass; const SQLWhere: string; const Params: array of Variant)
|
2019-02-21 18:11:14 +01:00
|
|
|
: TMVCActiveRecordList; overload;
|
2019-03-05 20:55:37 +01:00
|
|
|
class function Where(const aClass: TMVCActiveRecordClass; const SQLWhere: string; const Params: array of Variant;
|
2018-11-02 21:43:09 +01:00
|
|
|
const Connection: TFDConnection): TMVCActiveRecordList; overload;
|
2019-03-05 20:55:37 +01:00
|
|
|
class function All<T: TMVCActiveRecord, constructor>: TObjectList<T>; overload;
|
|
|
|
class function All(const aClass: TMVCActiveRecordClass): TObjectList<TMVCActiveRecord>; overload;
|
|
|
|
class function DeleteAll(const aClass: TMVCActiveRecordClass): int64; overload;
|
2019-01-13 19:18:57 +01:00
|
|
|
function Count: int64; overload;
|
|
|
|
class function Count<T: TMVCActiveRecord>: int64; overload;
|
|
|
|
class function Count(const aClass: TMVCActiveRecordClass): int64; overload;
|
2019-03-05 20:55:37 +01:00
|
|
|
class function SelectDataSet(const SQL: string; const Params: array of Variant): TDataSet;
|
2018-10-23 16:18:34 +02:00
|
|
|
class function CurrentConnection: TFDConnection;
|
2018-09-25 15:36:53 +02:00
|
|
|
end;
|
|
|
|
|
|
|
|
IMVCEntitiesRegistry = interface
|
|
|
|
['{BB227BEB-A74A-4637-8897-B13BA938C07B}']
|
2019-03-05 20:55:37 +01:00
|
|
|
procedure AddEntity(const aURLSegment: string; const aActiveRecordClass: TMVCActiveRecordClass);
|
|
|
|
procedure AddEntityProcessor(const aURLSegment: string; const aEntityProcessor: IMVCEntityProcessor);
|
2018-11-24 16:56:21 +01:00
|
|
|
function FindEntityClassByURLSegment(const aURLSegment: string;
|
|
|
|
out aMVCActiveRecordClass: TMVCActiveRecordClass): Boolean;
|
2019-03-05 20:55:37 +01:00
|
|
|
function FindProcessorByURLSegment(const aURLSegment: string; out aMVCEntityProcessor: IMVCEntityProcessor)
|
|
|
|
: Boolean;
|
2018-09-25 15:36:53 +02:00
|
|
|
end;
|
|
|
|
|
|
|
|
TMVCEntitiesRegistry = class(TInterfacedObject, IMVCEntitiesRegistry)
|
|
|
|
private
|
|
|
|
fEntitiesDict: TDictionary<string, TMVCActiveRecordClass>;
|
2018-10-14 18:23:20 +02:00
|
|
|
fProcessorsDict: TDictionary<string, IMVCEntityProcessor>;
|
2018-09-25 15:36:53 +02:00
|
|
|
public
|
|
|
|
constructor Create; virtual;
|
|
|
|
destructor Destroy; override;
|
|
|
|
protected
|
2019-03-05 20:55:37 +01:00
|
|
|
procedure AddEntityProcessor(const aURLSegment: string; const aEntityProcessor: IMVCEntityProcessor);
|
|
|
|
procedure AddEntity(const aURLSegment: string; const aActiveRecordClass: TMVCActiveRecordClass);
|
2018-11-24 16:56:21 +01:00
|
|
|
function FindEntityClassByURLSegment(const aURLSegment: string;
|
|
|
|
out aMVCActiveRecordClass: TMVCActiveRecordClass): Boolean;
|
2019-03-05 20:55:37 +01:00
|
|
|
function FindProcessorByURLSegment(const aURLSegment: string; out aMVCEntityProcessor: IMVCEntityProcessor)
|
|
|
|
: Boolean;
|
2018-09-25 15:36:53 +02:00
|
|
|
end;
|
|
|
|
|
2018-09-28 13:01:46 +02:00
|
|
|
IMVCActiveRecordConnections = interface
|
2018-09-25 15:36:53 +02:00
|
|
|
['{7B87473C-1784-489F-A838-925E7DDD0DE2}']
|
2019-03-05 20:55:37 +01:00
|
|
|
procedure AddConnection(const aName: string; const aConnection: TFDConnection; const Owns: Boolean = false);
|
2018-10-14 18:23:20 +02:00
|
|
|
procedure RemoveConnection(const aName: string);
|
|
|
|
procedure SetCurrent(const aName: string);
|
2018-09-25 15:36:53 +02:00
|
|
|
function GetCurrent: TFDConnection;
|
2018-11-02 21:43:09 +01:00
|
|
|
function GetCurrentBackend: string;
|
2018-09-25 15:36:53 +02:00
|
|
|
end;
|
|
|
|
|
2019-03-05 20:55:37 +01:00
|
|
|
TMVCConnectionsRepository = class(TInterfacedObject, IMVCActiveRecordConnections)
|
2018-11-24 16:56:21 +01:00
|
|
|
private type
|
2018-11-09 18:11:59 +01:00
|
|
|
TConnHolder = class
|
2019-01-08 12:48:27 +01:00
|
|
|
public
|
2018-11-09 18:11:59 +01:00
|
|
|
Connection: TFDConnection;
|
|
|
|
OwnsConnection: Boolean;
|
|
|
|
destructor Destroy; override;
|
|
|
|
end;
|
|
|
|
|
|
|
|
var
|
2018-10-14 18:23:20 +02:00
|
|
|
fMREW: TMultiReadExclusiveWriteSynchronizer;
|
2018-11-09 18:11:59 +01:00
|
|
|
fConnectionsDict: TDictionary<string, TConnHolder>;
|
2018-10-14 18:23:20 +02:00
|
|
|
fCurrentConnectionsByThread: TDictionary<TThreadID, string>;
|
|
|
|
function GetKeyName(const aName: string): string;
|
2018-09-25 15:36:53 +02:00
|
|
|
public
|
|
|
|
constructor Create; virtual;
|
|
|
|
destructor Destroy; override;
|
2019-03-05 20:55:37 +01:00
|
|
|
procedure AddConnection(const aName: string; const aConnection: TFDConnection; const aOwns: Boolean = false);
|
2018-10-14 18:23:20 +02:00
|
|
|
procedure RemoveConnection(const aName: string);
|
|
|
|
procedure SetCurrent(const aName: string);
|
2018-09-25 15:36:53 +02:00
|
|
|
function GetCurrent: TFDConnection;
|
2018-10-14 18:23:20 +02:00
|
|
|
function GetByName(const aName: string): TFDConnection;
|
2018-11-02 21:43:09 +01:00
|
|
|
function GetCurrentBackend: string;
|
|
|
|
end;
|
|
|
|
|
|
|
|
TMVCSQLGenerator = class abstract
|
|
|
|
private
|
|
|
|
fMapping: TMVCFieldsMapping;
|
|
|
|
fCompiler: TRQLCompiler;
|
|
|
|
fRQL2SQL: TRQL2SQL;
|
|
|
|
protected
|
|
|
|
function GetRQLParser: TRQL2SQL;
|
|
|
|
function GetCompiler: TRQLCompiler;
|
|
|
|
function GetCompilerClass: TRQLCompilerClass; virtual; abstract;
|
|
|
|
function GetMapping: TMVCFieldsMapping;
|
2019-03-05 20:55:37 +01:00
|
|
|
function TableFieldsDelimited(const Map: TDictionary<TRttiField, string>; const PKFieldName: string;
|
|
|
|
const Delimiter: string): string;
|
2018-11-02 21:43:09 +01:00
|
|
|
public
|
|
|
|
constructor Create(Mapping: TMVCFieldsMapping); virtual;
|
|
|
|
destructor Destroy; override;
|
2019-03-05 20:55:37 +01:00
|
|
|
function CreateSQLWhereByRQL(const RQL: string; const Mapping: TMVCFieldsMapping;
|
2019-02-21 20:17:11 +01:00
|
|
|
const UseArtificialLimit: Boolean = True): string; virtual; abstract;
|
2019-03-05 20:55:37 +01:00
|
|
|
function CreateSelectSQL(const TableName: string; const Map: TDictionary<TRttiField, string>;
|
|
|
|
const PKFieldName: string; const PKOptions: TMVCActiveRecordFieldOptions): string; virtual; abstract;
|
|
|
|
function CreateSelectByPKSQL(const TableName: string; const Map: TDictionary<TRttiField, string>;
|
|
|
|
const PKFieldName: string; const PKOptions: TMVCActiveRecordFieldOptions; const PrimaryKeyValue: int64): string;
|
2018-12-09 23:03:06 +01:00
|
|
|
virtual; abstract;
|
2019-03-05 20:55:37 +01:00
|
|
|
function CreateInsertSQL(const TableName: string; const Map: TDictionary<TRttiField, string>;
|
|
|
|
const PKFieldName: string; const PKOptions: TMVCActiveRecordFieldOptions): string; virtual; abstract;
|
|
|
|
function CreateUpdateSQL(const TableName: string; const Map: TDictionary<TRttiField, string>;
|
|
|
|
const PKFieldName: string; const PKOptions: TMVCActiveRecordFieldOptions): string; virtual; abstract;
|
|
|
|
function CreateDeleteSQL(const TableName: string; const Map: TDictionary<TRttiField, string>;
|
|
|
|
const PKFieldName: string; const PKOptions: TMVCActiveRecordFieldOptions; const PrimaryKeyValue: int64): string;
|
2018-12-09 23:03:06 +01:00
|
|
|
virtual; abstract;
|
2019-03-05 20:55:37 +01:00
|
|
|
function CreateDeleteAllSQL(const TableName: string): string; virtual; abstract;
|
|
|
|
function CreateSelectCount(const TableName: String): String; virtual; abstract;
|
2018-11-02 21:43:09 +01:00
|
|
|
end;
|
|
|
|
|
|
|
|
TMVCSQLGeneratorClass = class of TMVCSQLGenerator;
|
|
|
|
|
|
|
|
TMVCSQLGeneratorRegistry = class sealed
|
|
|
|
private
|
2018-11-09 18:11:59 +01:00
|
|
|
class var cInstance: TMVCSQLGeneratorRegistry;
|
2018-11-02 21:43:09 +01:00
|
|
|
|
|
|
|
class var
|
2018-11-09 18:11:59 +01:00
|
|
|
cLock: TObject;
|
2018-11-02 21:43:09 +01:00
|
|
|
fSQLGenerators: TDictionary<string, TMVCSQLGeneratorClass>;
|
|
|
|
protected
|
|
|
|
constructor Create;
|
|
|
|
public
|
|
|
|
destructor Destroy; override;
|
|
|
|
class function Instance: TMVCSQLGeneratorRegistry;
|
|
|
|
class destructor Destroy;
|
|
|
|
class constructor Create;
|
2019-03-05 20:55:37 +01:00
|
|
|
procedure RegisterSQLGenerator(const aBackend: string; const aRQLBackendClass: TMVCSQLGeneratorClass);
|
2018-11-02 21:43:09 +01:00
|
|
|
procedure UnRegisterSQLGenerator(const aBackend: string);
|
|
|
|
function GetSQLGenerator(const aBackend: string): TMVCSQLGeneratorClass;
|
2018-09-25 15:36:53 +02:00
|
|
|
end;
|
|
|
|
|
2018-09-28 13:01:46 +02:00
|
|
|
function ActiveRecordConnectionsRegistry: IMVCActiveRecordConnections;
|
2018-09-25 15:36:53 +02:00
|
|
|
|
|
|
|
function ActiveRecordMappingRegistry: IMVCEntitiesRegistry;
|
|
|
|
|
2018-11-02 21:43:09 +01:00
|
|
|
function GetBackEndByConnection(aConnection: TFDConnection): string;
|
2018-10-14 18:23:20 +02:00
|
|
|
|
2018-09-25 15:36:53 +02:00
|
|
|
implementation
|
|
|
|
|
|
|
|
uses
|
2018-09-28 18:33:19 +02:00
|
|
|
System.TypInfo,
|
|
|
|
System.IOUtils,
|
|
|
|
System.Classes,
|
2018-09-25 15:36:53 +02:00
|
|
|
MVCFramework.DataSet.Utils,
|
|
|
|
MVCFramework.Logger,
|
2018-10-14 18:23:20 +02:00
|
|
|
FireDAC.Stan.Option,
|
|
|
|
Data.FmtBcd;
|
2018-09-25 15:36:53 +02:00
|
|
|
|
|
|
|
var
|
2018-10-14 18:23:20 +02:00
|
|
|
gCtx: TRttiContext;
|
2018-09-25 15:36:53 +02:00
|
|
|
gEntitiesRegistry: IMVCEntitiesRegistry;
|
2018-10-14 18:23:20 +02:00
|
|
|
gConnections: IMVCActiveRecordConnections;
|
2018-09-25 15:36:53 +02:00
|
|
|
gLock: TObject;
|
|
|
|
|
2018-11-02 21:43:09 +01:00
|
|
|
function GetBackEndByConnection(aConnection: TFDConnection): string;
|
|
|
|
begin
|
|
|
|
case aConnection.RDBMSKind of
|
|
|
|
0:
|
|
|
|
Exit('unknown');
|
|
|
|
1:
|
|
|
|
Exit('oracle');
|
|
|
|
2:
|
2018-11-09 18:11:59 +01:00
|
|
|
Exit('mssql');
|
2018-11-02 21:43:09 +01:00
|
|
|
3:
|
|
|
|
Exit('msaccess');
|
|
|
|
4:
|
|
|
|
Exit('mysql');
|
|
|
|
5:
|
|
|
|
Exit('db2');
|
|
|
|
6:
|
|
|
|
Exit('sqlanywhere');
|
|
|
|
7:
|
|
|
|
Exit('advantage');
|
|
|
|
8:
|
|
|
|
Exit('interbase');
|
|
|
|
9:
|
|
|
|
Exit('firebird');
|
|
|
|
10:
|
|
|
|
Exit('sqlite');
|
|
|
|
11:
|
|
|
|
Exit('postgresql');
|
|
|
|
12:
|
|
|
|
Exit('nexusdb');
|
|
|
|
13:
|
|
|
|
Exit('dataSnap');
|
|
|
|
14:
|
|
|
|
Exit('informix');
|
|
|
|
15:
|
|
|
|
Exit('teradata');
|
|
|
|
16:
|
|
|
|
Exit('mongodb');
|
|
|
|
17:
|
|
|
|
Exit('other');
|
|
|
|
else
|
|
|
|
raise EMVCActiveRecord.Create('Unknown RDBMS Kind');
|
|
|
|
end;
|
2018-10-14 18:23:20 +02:00
|
|
|
end;
|
|
|
|
|
2018-09-28 13:01:46 +02:00
|
|
|
function ActiveRecordConnectionsRegistry: IMVCActiveRecordConnections;
|
2018-09-25 15:36:53 +02:00
|
|
|
begin
|
2018-10-14 18:23:20 +02:00
|
|
|
if gConnections = nil then // double check here
|
2018-09-25 15:36:53 +02:00
|
|
|
begin
|
2018-10-14 18:23:20 +02:00
|
|
|
TMonitor.Enter(gLock);
|
|
|
|
try
|
|
|
|
if gConnections = nil then
|
|
|
|
begin
|
|
|
|
gConnections := TMVCConnectionsRepository.Create;
|
|
|
|
end;
|
|
|
|
finally
|
|
|
|
TMonitor.Exit(gLock);
|
|
|
|
end;
|
2018-09-25 15:36:53 +02:00
|
|
|
end;
|
|
|
|
Result := gConnections;
|
|
|
|
end;
|
|
|
|
|
|
|
|
{ TConnectionsRepository }
|
|
|
|
|
2019-03-05 20:55:37 +01:00
|
|
|
procedure TMVCConnectionsRepository.AddConnection(const aName: string; const aConnection: TFDConnection;
|
|
|
|
const aOwns: Boolean = false);
|
2018-09-25 15:36:53 +02:00
|
|
|
var
|
|
|
|
lName: string;
|
2018-10-14 18:23:20 +02:00
|
|
|
lConnKeyName: string;
|
2018-11-09 18:11:59 +01:00
|
|
|
lConnHolder: TConnHolder;
|
2018-09-25 15:36:53 +02:00
|
|
|
begin
|
2018-10-14 18:23:20 +02:00
|
|
|
lName := aName.ToLower;
|
|
|
|
lConnKeyName := GetKeyName(lName);
|
|
|
|
|
|
|
|
fMREW.BeginWrite;
|
|
|
|
try
|
2018-11-09 18:11:59 +01:00
|
|
|
lConnHolder := TConnHolder.Create;
|
|
|
|
lConnHolder.Connection := aConnection;
|
|
|
|
lConnHolder.OwnsConnection := aOwns;
|
2018-11-24 16:56:21 +01:00
|
|
|
fConnectionsDict.Add(lConnKeyName, lConnHolder);
|
|
|
|
// raise exception on duplicates
|
2019-03-05 20:55:37 +01:00
|
|
|
if (lName = 'default') and (not fCurrentConnectionsByThread.ContainsKey(TThread.CurrentThread.ThreadID)) then
|
2018-10-14 18:23:20 +02:00
|
|
|
begin
|
2019-03-05 20:55:37 +01:00
|
|
|
fCurrentConnectionsByThread.AddOrSetValue(TThread.CurrentThread.ThreadID, lName);
|
2018-10-14 18:23:20 +02:00
|
|
|
end;
|
|
|
|
finally
|
|
|
|
fMREW.EndWrite;
|
2018-09-25 15:36:53 +02:00
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
|
|
|
constructor TMVCConnectionsRepository.Create;
|
|
|
|
begin
|
|
|
|
inherited;
|
2018-10-14 18:23:20 +02:00
|
|
|
fMREW := TMultiReadExclusiveWriteSynchronizer.Create;
|
2018-11-09 18:11:59 +01:00
|
|
|
fConnectionsDict := TDictionary<string, TConnHolder>.Create;
|
2018-10-14 18:23:20 +02:00
|
|
|
fCurrentConnectionsByThread := TDictionary<TThreadID, string>.Create;
|
2018-09-25 15:36:53 +02:00
|
|
|
end;
|
|
|
|
|
|
|
|
destructor TMVCConnectionsRepository.Destroy;
|
|
|
|
begin
|
|
|
|
fConnectionsDict.Free;
|
2018-10-14 18:23:20 +02:00
|
|
|
fCurrentConnectionsByThread.Free;
|
|
|
|
fMREW.Free;
|
2018-09-25 15:36:53 +02:00
|
|
|
inherited;
|
|
|
|
end;
|
|
|
|
|
2019-03-05 20:55:37 +01:00
|
|
|
function TMVCConnectionsRepository.GetByName(const aName: string): TFDConnection;
|
2018-10-14 18:23:20 +02:00
|
|
|
var
|
|
|
|
lKeyName: string;
|
2018-11-09 18:11:59 +01:00
|
|
|
lConnHolder: TConnHolder;
|
2018-09-25 15:36:53 +02:00
|
|
|
begin
|
2018-10-14 18:23:20 +02:00
|
|
|
lKeyName := GetKeyName(aName.ToLower);
|
|
|
|
fMREW.BeginRead;
|
|
|
|
try
|
2018-11-09 18:11:59 +01:00
|
|
|
if not fConnectionsDict.TryGetValue(lKeyName, lConnHolder) then
|
2018-10-14 18:23:20 +02:00
|
|
|
raise Exception.CreateFmt('Unknown connection %s', [aName]);
|
2018-11-09 18:11:59 +01:00
|
|
|
Result := lConnHolder.Connection;
|
2018-10-14 18:23:20 +02:00
|
|
|
finally
|
|
|
|
fMREW.EndRead;
|
|
|
|
end;
|
2018-09-25 15:36:53 +02:00
|
|
|
end;
|
|
|
|
|
|
|
|
function TMVCConnectionsRepository.GetCurrent: TFDConnection;
|
2018-10-14 18:23:20 +02:00
|
|
|
var
|
|
|
|
lName: string;
|
2018-09-25 15:36:53 +02:00
|
|
|
begin
|
2018-10-14 18:23:20 +02:00
|
|
|
fMREW.BeginRead;
|
|
|
|
try
|
2019-03-05 20:55:37 +01:00
|
|
|
if fCurrentConnectionsByThread.TryGetValue(TThread.Current.ThreadID, lName) then
|
2018-10-14 18:23:20 +02:00
|
|
|
begin
|
|
|
|
Result := GetByName(lName);
|
|
|
|
end
|
|
|
|
else
|
|
|
|
begin
|
|
|
|
raise EMVCActiveRecord.Create('No current connection for thread');
|
|
|
|
end;
|
|
|
|
finally
|
|
|
|
fMREW.EndRead;
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
2018-11-02 21:43:09 +01:00
|
|
|
function TMVCConnectionsRepository.GetCurrentBackend: string;
|
|
|
|
begin
|
|
|
|
Result := GetBackEndByConnection(GetCurrent);
|
|
|
|
end;
|
|
|
|
|
2018-10-14 18:23:20 +02:00
|
|
|
function TMVCConnectionsRepository.GetKeyName(const aName: string): string;
|
|
|
|
begin
|
2018-11-09 18:11:59 +01:00
|
|
|
Result := Format('%10.10d::%s', [TThread.Current.ThreadID, aName]);
|
2018-09-25 15:36:53 +02:00
|
|
|
end;
|
|
|
|
|
2018-10-14 18:23:20 +02:00
|
|
|
procedure TMVCConnectionsRepository.RemoveConnection(const aName: string);
|
2018-09-25 15:36:53 +02:00
|
|
|
var
|
|
|
|
lName: string;
|
2018-10-14 18:23:20 +02:00
|
|
|
lKeyName: string;
|
2018-11-09 18:11:59 +01:00
|
|
|
lConnHolder: TConnHolder;
|
2018-09-25 15:36:53 +02:00
|
|
|
begin
|
2018-10-14 18:23:20 +02:00
|
|
|
lName := aName.ToLower;
|
|
|
|
lKeyName := GetKeyName(lName);
|
|
|
|
|
|
|
|
fMREW.BeginWrite;
|
2018-09-25 15:36:53 +02:00
|
|
|
try
|
2018-11-09 18:11:59 +01:00
|
|
|
if not fConnectionsDict.TryGetValue(lKeyName, lConnHolder) then
|
2018-10-14 18:23:20 +02:00
|
|
|
raise Exception.CreateFmt('Unknown connection %s', [aName]);
|
|
|
|
fConnectionsDict.Remove(lKeyName);
|
|
|
|
try
|
2018-11-09 18:11:59 +01:00
|
|
|
FreeAndNil(lConnHolder);
|
2018-10-14 18:23:20 +02:00
|
|
|
except
|
|
|
|
on E: Exception do
|
|
|
|
begin
|
|
|
|
LogE('ActiveRecord: ' + E.ClassName + ' > ' + E.Message);
|
|
|
|
raise;
|
|
|
|
end;
|
2018-09-25 15:36:53 +02:00
|
|
|
end;
|
2018-10-14 18:23:20 +02:00
|
|
|
finally
|
|
|
|
fMREW.EndWrite;
|
2018-09-25 15:36:53 +02:00
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
2018-10-14 18:23:20 +02:00
|
|
|
procedure TMVCConnectionsRepository.SetCurrent(const aName: string);
|
2018-09-25 15:36:53 +02:00
|
|
|
var
|
|
|
|
lName: string;
|
2018-10-14 18:23:20 +02:00
|
|
|
lKeyName: string;
|
2018-09-25 15:36:53 +02:00
|
|
|
begin
|
2018-10-14 18:23:20 +02:00
|
|
|
lName := aName.ToLower;
|
|
|
|
lKeyName := GetKeyName(lName);
|
|
|
|
|
|
|
|
fMREW.BeginWrite;
|
|
|
|
try
|
|
|
|
if not fConnectionsDict.ContainsKey(lKeyName) then
|
|
|
|
raise Exception.CreateFmt('Unknown connection %s', [aName]);
|
|
|
|
fCurrentConnectionsByThread.AddOrSetValue(TThread.Current.ThreadID, lName);
|
|
|
|
finally
|
|
|
|
fMREW.EndWrite;
|
|
|
|
end;
|
2018-09-25 15:36:53 +02:00
|
|
|
end;
|
|
|
|
|
|
|
|
function ActiveRecordMappingRegistry: IMVCEntitiesRegistry;
|
|
|
|
begin
|
|
|
|
if gEntitiesRegistry = nil then
|
|
|
|
begin
|
|
|
|
TMonitor.Enter(gLock);
|
|
|
|
try
|
|
|
|
if gEntitiesRegistry = nil then
|
|
|
|
begin
|
|
|
|
gEntitiesRegistry := TMVCEntitiesRegistry.Create;
|
|
|
|
end;
|
|
|
|
finally
|
|
|
|
TMonitor.Exit(gLock);
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
Result := gEntitiesRegistry;
|
|
|
|
end;
|
|
|
|
|
|
|
|
{ TableFieldAttribute }
|
|
|
|
|
2018-10-23 16:18:34 +02:00
|
|
|
constructor MVCTableFieldAttribute.Create(aFieldName: string);
|
2018-09-25 15:36:53 +02:00
|
|
|
begin
|
2019-02-15 12:21:11 +01:00
|
|
|
Create(aFieldName, []);
|
2018-09-25 15:36:53 +02:00
|
|
|
end;
|
|
|
|
|
|
|
|
{ TableAttribute }
|
|
|
|
|
2018-10-23 16:18:34 +02:00
|
|
|
constructor MVCTableAttribute.Create(aName: string);
|
2018-09-25 15:36:53 +02:00
|
|
|
begin
|
|
|
|
inherited Create;
|
2018-10-14 18:23:20 +02:00
|
|
|
name := aName;
|
2018-09-25 15:36:53 +02:00
|
|
|
end;
|
|
|
|
|
|
|
|
{ TActiveRecord }
|
|
|
|
|
|
|
|
destructor TMVCActiveRecord.Destroy;
|
|
|
|
begin
|
|
|
|
fMap.Free;
|
2019-02-21 20:17:11 +01:00
|
|
|
fMapNonTransientFields.Free;
|
2018-11-02 21:43:09 +01:00
|
|
|
fSQLGenerator.Free;
|
|
|
|
fRQL2SQL.Free;
|
|
|
|
fConn := nil; // do not free it!!
|
2018-09-25 15:36:53 +02:00
|
|
|
inherited;
|
|
|
|
end;
|
|
|
|
|
2019-03-05 20:55:37 +01:00
|
|
|
function TMVCActiveRecord.ExecNonQuery(const SQL: string; RefreshAutoGenerated: Boolean = false): int64;
|
2018-09-25 15:36:53 +02:00
|
|
|
var
|
|
|
|
lQry: TFDQuery;
|
|
|
|
lPar: TFDParam;
|
|
|
|
lPair: TPair<TRttiField, string>;
|
|
|
|
lValue: TValue;
|
2019-03-18 14:08:34 +01:00
|
|
|
lSQL : String;
|
2018-09-25 15:36:53 +02:00
|
|
|
begin
|
|
|
|
lQry := TFDQuery.Create(nil);
|
|
|
|
try
|
|
|
|
lQry.Connection := fConn;
|
2019-03-18 14:08:34 +01:00
|
|
|
lSQL := SQL;
|
|
|
|
OnBeforeExecuteSQL(lSQL);
|
|
|
|
lQry.SQL.Text := lSQL;
|
2018-10-14 18:23:20 +02:00
|
|
|
// lQry.Prepare;
|
2018-09-25 15:36:53 +02:00
|
|
|
for lPair in fMap do
|
|
|
|
begin
|
|
|
|
lPar := lQry.FindParam(lPair.value);
|
|
|
|
if lPar <> nil then
|
|
|
|
begin
|
|
|
|
lValue := lPair.Key.GetValue(Self);
|
|
|
|
MapTValueToParam(lValue, lPar);
|
|
|
|
end
|
2018-10-14 18:23:20 +02:00
|
|
|
end;
|
|
|
|
|
|
|
|
// check if it's the primary key
|
|
|
|
lPar := lQry.FindParam(fPrimaryKeyFieldName);
|
|
|
|
if lPar <> nil then
|
|
|
|
begin
|
|
|
|
if lPar.DataType = ftUnknown then
|
2018-09-25 15:36:53 +02:00
|
|
|
begin
|
2018-10-14 18:23:20 +02:00
|
|
|
{ TODO -oDanieleT -cGeneral : Let's find a smarter way to do this if the engine cannot recognize parameter's datatype }
|
|
|
|
lPar.DataType := ftLargeint;
|
2018-09-25 15:36:53 +02:00
|
|
|
end;
|
2018-10-14 18:23:20 +02:00
|
|
|
MapTValueToParam(fPrimaryKey.GetValue(Self), lPar);
|
2018-09-25 15:36:53 +02:00
|
|
|
end;
|
|
|
|
|
2019-03-05 20:55:37 +01:00
|
|
|
if RefreshAutoGenerated and (TMVCActiveRecordFieldOption.foAutoGenerated in fPrimaryKeyOptions) then
|
2018-09-25 15:36:53 +02:00
|
|
|
begin
|
|
|
|
lQry.Open;
|
2019-03-05 20:55:37 +01:00
|
|
|
fPrimaryKey.SetValue(Self, lQry.FieldByName(fPrimaryKeyFieldName).AsInteger);
|
2018-09-25 15:36:53 +02:00
|
|
|
OnAfterLoad;
|
|
|
|
end
|
|
|
|
else
|
|
|
|
begin
|
2019-03-18 14:08:34 +01:00
|
|
|
lQry.ExecSQL(lSQL);
|
2018-09-25 15:36:53 +02:00
|
|
|
end;
|
|
|
|
|
|
|
|
Result := lQry.RowsAffected;
|
2018-10-14 18:23:20 +02:00
|
|
|
finally
|
2018-09-25 15:36:53 +02:00
|
|
|
lQry.Free;
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
2018-10-14 18:23:20 +02:00
|
|
|
// class function TMVCActiveRecord.ExecNonQuery(const SQL: string;
|
|
|
|
// const Values: array of Variant; const Connection: TFDConnection): int64;
|
|
|
|
// var
|
|
|
|
// lQry: TFDQuery;
|
|
|
|
// begin
|
|
|
|
// lQry := TFDQuery.Create(nil);
|
|
|
|
// try
|
|
|
|
// lQry.FetchOptions.Unidirectional := True;
|
|
|
|
// if Connection = nil then
|
|
|
|
// begin
|
|
|
|
// lQry.Connection := ActiveRecordConnectionsRegistry.GetCurrent;
|
|
|
|
// end
|
|
|
|
// else
|
|
|
|
// begin
|
|
|
|
// lQry.Connection := Connection;
|
|
|
|
// end;
|
|
|
|
// lQry.SQL.Text := SQL;
|
|
|
|
// lQry.Prepare;
|
|
|
|
// lQry.ExecSQL(SQL, Values);
|
|
|
|
// Result := lQry.RowsAffected;
|
|
|
|
// except
|
|
|
|
// lQry.Free;
|
|
|
|
// raise;
|
|
|
|
// end;
|
|
|
|
// end;
|
|
|
|
//
|
|
|
|
// class function TMVCActiveRecord.ExecNonQuery(const SQL: string;
|
|
|
|
// const Values: array of Variant): int64;
|
|
|
|
// begin
|
|
|
|
// Result := ExecNonQuery(SQL, Values, nil);
|
|
|
|
// end;
|
|
|
|
|
2019-03-05 20:55:37 +01:00
|
|
|
class function TMVCActiveRecord.ExecQuery(const SQL: string; const Values: array of Variant;
|
|
|
|
const Connection: TFDConnection): TDataSet;
|
2018-09-25 15:36:53 +02:00
|
|
|
var
|
|
|
|
lQry: TFDQuery;
|
|
|
|
begin
|
|
|
|
lQry := TFDQuery.Create(nil);
|
|
|
|
try
|
2019-03-16 17:20:28 +01:00
|
|
|
lQry.FetchOptions.Unidirectional := False; //True;
|
2018-09-28 13:01:46 +02:00
|
|
|
if Connection = nil then
|
|
|
|
begin
|
|
|
|
lQry.Connection := ActiveRecordConnectionsRegistry.GetCurrent;
|
|
|
|
end
|
|
|
|
else
|
|
|
|
begin
|
|
|
|
lQry.Connection := Connection;
|
|
|
|
end;
|
2018-09-25 15:36:53 +02:00
|
|
|
lQry.SQL.Text := SQL;
|
2018-10-14 18:23:20 +02:00
|
|
|
// lQry.Prepare;
|
2018-09-25 15:36:53 +02:00
|
|
|
lQry.Open(SQL, Values);
|
|
|
|
Result := lQry;
|
|
|
|
except
|
|
|
|
lQry.Free;
|
|
|
|
raise;
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
2019-03-05 20:55:37 +01:00
|
|
|
class function TMVCActiveRecord.ExecQuery(const SQL: string; const Values: array of Variant): TDataSet;
|
2018-09-25 15:36:53 +02:00
|
|
|
begin
|
2018-09-28 13:01:46 +02:00
|
|
|
Result := ExecQuery(SQL, Values, nil);
|
2018-09-25 15:36:53 +02:00
|
|
|
end;
|
|
|
|
|
|
|
|
procedure TMVCActiveRecord.InitTableInfo;
|
|
|
|
var
|
2018-10-14 18:23:20 +02:00
|
|
|
lAttribute: TCustomAttribute;
|
|
|
|
lRTTIField: TRttiField;
|
2018-09-25 15:36:53 +02:00
|
|
|
begin
|
2019-03-05 20:55:37 +01:00
|
|
|
fEntityAllowedActions := [TMVCEntityAction.eaCreate, TMVCEntityAction.eaRetrieve, TMVCEntityAction.eaUpdate,
|
2018-11-02 21:43:09 +01:00
|
|
|
TMVCEntityAction.eaDelete];
|
2018-09-25 15:36:53 +02:00
|
|
|
fTableName := '';
|
2018-10-14 18:23:20 +02:00
|
|
|
fRTTIType := gCtx.GetType(Self.ClassInfo);
|
2018-09-25 15:36:53 +02:00
|
|
|
fObjAttributes := fRTTIType.GetAttributes;
|
2018-10-14 18:23:20 +02:00
|
|
|
for lAttribute in fObjAttributes do
|
|
|
|
begin
|
2018-10-23 16:18:34 +02:00
|
|
|
if lAttribute is MVCTableAttribute then
|
2018-10-14 18:23:20 +02:00
|
|
|
begin
|
2018-10-23 16:18:34 +02:00
|
|
|
fTableName := MVCTableAttribute(lAttribute).Name;
|
2018-10-14 18:23:20 +02:00
|
|
|
continue;
|
|
|
|
end;
|
2018-10-23 16:18:34 +02:00
|
|
|
if lAttribute is MVCEntityActionsAttribute then
|
2018-09-25 15:36:53 +02:00
|
|
|
begin
|
2019-03-05 20:55:37 +01:00
|
|
|
fEntityAllowedActions := MVCEntityActionsAttribute(lAttribute).EntityAllowedActions;
|
2018-09-25 15:36:53 +02:00
|
|
|
Break;
|
|
|
|
end;
|
2018-10-14 18:23:20 +02:00
|
|
|
end;
|
2018-09-25 15:36:53 +02:00
|
|
|
|
|
|
|
if fTableName = '' then
|
|
|
|
raise Exception.Create('Cannot find TableNameAttribute');
|
|
|
|
|
|
|
|
fProps := fRTTIType.GetFields;
|
2018-10-14 18:23:20 +02:00
|
|
|
for lRTTIField in fProps do
|
2018-09-25 15:36:53 +02:00
|
|
|
begin
|
2018-10-14 18:23:20 +02:00
|
|
|
fPropsAttributes := lRTTIField.GetAttributes;
|
2018-09-25 15:36:53 +02:00
|
|
|
if Length(fPropsAttributes) = 0 then
|
2018-10-14 18:23:20 +02:00
|
|
|
continue;
|
|
|
|
for lAttribute in fPropsAttributes do
|
2018-09-25 15:36:53 +02:00
|
|
|
begin
|
2018-10-23 16:18:34 +02:00
|
|
|
if lAttribute is MVCTableFieldAttribute then
|
2018-09-25 15:36:53 +02:00
|
|
|
begin
|
2019-03-05 20:55:37 +01:00
|
|
|
if foPrimaryKey in MVCTableFieldAttribute(lAttribute).FieldOptions then
|
|
|
|
begin
|
|
|
|
fPrimaryKey := lRTTIField;
|
|
|
|
fPrimaryKeyFieldName := MVCTableFieldAttribute(lAttribute).FieldName;
|
|
|
|
fPrimaryKeyOptions := MVCTableFieldAttribute(lAttribute).FieldOptions;
|
|
|
|
continue;
|
|
|
|
end;
|
|
|
|
|
|
|
|
fMap.Add(lRTTIField, { fTableName + '.' + } MVCTableFieldAttribute(lAttribute).FieldName);
|
|
|
|
if not(foTransient in MVCTableFieldAttribute(lAttribute).FieldOptions) then
|
|
|
|
fMapNonTransientFields.Add(lRTTIField, MVCTableFieldAttribute(lAttribute).FieldName);
|
2018-09-25 15:36:53 +02:00
|
|
|
end;
|
2019-03-05 20:55:37 +01:00
|
|
|
// else if lAttribute is MVCPrimaryKeyAttribute then
|
|
|
|
// begin
|
|
|
|
// fPrimaryKey := lRTTIField;
|
|
|
|
// fPrimaryKeyFieldName := MVCPrimaryKeyAttribute(lAttribute).FieldName;
|
|
|
|
// fPrimaryKeyOptions := MVCPrimaryKeyAttribute(lAttribute).FieldOptions;
|
|
|
|
// end;
|
2018-09-25 15:36:53 +02:00
|
|
|
end;
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
|
|
|
procedure TMVCActiveRecord.Insert;
|
|
|
|
var
|
|
|
|
SQL: string;
|
|
|
|
begin
|
2018-10-14 18:23:20 +02:00
|
|
|
CheckAction(TMVCEntityAction.eaCreate);
|
2018-09-25 15:36:53 +02:00
|
|
|
OnValidation;
|
|
|
|
OnBeforeInsert;
|
|
|
|
OnBeforeInsertOrUpdate;
|
2019-02-21 20:17:11 +01:00
|
|
|
if fMapNonTransientFields.Count = 0 then
|
|
|
|
begin
|
|
|
|
raise EMVCActiveRecord.CreateFmt
|
2019-03-05 20:55:37 +01:00
|
|
|
('Cannot insert an entity if all fields are transient. Class [%s] mapped on table [%s]', [ClassName, fTableName]);
|
2019-02-21 20:17:11 +01:00
|
|
|
end;
|
2019-03-05 20:55:37 +01:00
|
|
|
SQL := SQLGenerator.CreateInsertSQL(fTableName, fMapNonTransientFields, fPrimaryKeyFieldName, fPrimaryKeyOptions);
|
2018-09-25 15:36:53 +02:00
|
|
|
ExecNonQuery(SQL, True);
|
2018-10-23 16:18:34 +02:00
|
|
|
OnAfterInsert;
|
|
|
|
OnAfterInsertOrUpdate;
|
2018-09-25 15:36:53 +02:00
|
|
|
end;
|
|
|
|
|
2018-10-14 18:23:20 +02:00
|
|
|
constructor TMVCActiveRecord.Create(aLazyLoadConnection: Boolean);
|
2018-09-25 15:36:53 +02:00
|
|
|
begin
|
2018-09-28 13:01:46 +02:00
|
|
|
inherited Create;
|
|
|
|
fConn := nil;
|
2018-11-02 21:43:09 +01:00
|
|
|
SetLength(fMapping, 0);
|
|
|
|
{ TODO -oDanieleT -cGeneral : Consider lazyconnection }
|
|
|
|
// if not aLazyLoadConnection then
|
|
|
|
// begin
|
|
|
|
SelfConnection;
|
|
|
|
// end;
|
2018-09-25 15:36:53 +02:00
|
|
|
fMap := TDictionary<TRttiField, string>.Create;
|
2019-02-21 20:17:11 +01:00
|
|
|
fMapNonTransientFields := TDictionary<TRttiField, string>.Create;
|
2018-09-25 15:36:53 +02:00
|
|
|
InitTableInfo;
|
|
|
|
end;
|
|
|
|
|
2018-11-02 21:43:09 +01:00
|
|
|
function TMVCActiveRecord.GenerateSelectSQL: string;
|
2018-09-25 15:36:53 +02:00
|
|
|
begin
|
2019-03-05 20:55:37 +01:00
|
|
|
Result := SQLGenerator.CreateSelectSQL(fTableName, fMap, fPrimaryKeyFieldName, fPrimaryKeyOptions);
|
2018-11-02 21:43:09 +01:00
|
|
|
end;
|
|
|
|
|
|
|
|
function TMVCActiveRecord.GetBackEnd: string;
|
|
|
|
begin
|
|
|
|
if fBackendDriver.IsEmpty then
|
2018-09-25 15:36:53 +02:00
|
|
|
begin
|
2018-11-02 21:43:09 +01:00
|
|
|
fBackendDriver := GetBackEndByConnection(fConn);
|
2018-09-25 15:36:53 +02:00
|
|
|
end;
|
2018-11-02 21:43:09 +01:00
|
|
|
Result := fBackendDriver;
|
2018-09-25 15:36:53 +02:00
|
|
|
end;
|
|
|
|
|
2019-03-05 20:55:37 +01:00
|
|
|
class function TMVCActiveRecord.GetByPK(const aClass: TMVCActiveRecordClass; const aValue: int64): TMVCActiveRecord;
|
2018-09-25 15:36:53 +02:00
|
|
|
begin
|
|
|
|
Result := aClass.Create;
|
2019-01-18 18:11:27 +01:00
|
|
|
if not Result.LoadByPK(aValue) then
|
|
|
|
begin
|
|
|
|
Result.Free;
|
|
|
|
raise EMVCActiveRecordNotFound.Create('Record not found');
|
|
|
|
end;
|
2018-09-25 15:36:53 +02:00
|
|
|
end;
|
|
|
|
|
2018-11-24 16:56:21 +01:00
|
|
|
class function TMVCActiveRecord.GetByPK<T>(const aValue: int64): T;
|
2018-09-25 15:36:53 +02:00
|
|
|
var
|
|
|
|
lActiveRecord: TMVCActiveRecord;
|
|
|
|
begin
|
|
|
|
Result := T.Create;
|
|
|
|
lActiveRecord := TMVCActiveRecord(Result);
|
2019-01-18 18:11:27 +01:00
|
|
|
if not lActiveRecord.LoadByPK(aValue) then
|
|
|
|
begin
|
|
|
|
Result.Free;
|
|
|
|
raise EMVCActiveRecordNotFound.Create('Record not found');
|
|
|
|
end;
|
2018-09-25 15:36:53 +02:00
|
|
|
end;
|
|
|
|
|
2019-03-05 20:55:37 +01:00
|
|
|
class function TMVCActiveRecord.GetFirstByWhere<T>(const SQLWhere: string; const Params: array of Variant;
|
|
|
|
const RaiseExceptionIfNotFound: Boolean): T;
|
2018-10-23 16:18:34 +02:00
|
|
|
var
|
|
|
|
lList: TObjectList<T>;
|
|
|
|
begin
|
|
|
|
lList := Where<T>(SQLWhere, Params);
|
|
|
|
try
|
|
|
|
if lList.Count = 0 then
|
|
|
|
begin
|
|
|
|
if RaiseExceptionIfNotFound then
|
|
|
|
raise EMVCActiveRecord.Create('Got 0 rows');
|
|
|
|
Exit(nil);
|
|
|
|
end;
|
|
|
|
lList.OwnsObjects := false;
|
|
|
|
Result := lList.First;
|
|
|
|
finally
|
|
|
|
lList.Free;
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
2018-09-28 13:01:46 +02:00
|
|
|
function TMVCActiveRecord.GetMapping: TMVCFieldsMapping;
|
|
|
|
var
|
|
|
|
lPair: TPair<TRttiField, string>;
|
|
|
|
i: Integer;
|
|
|
|
begin
|
2018-11-02 21:43:09 +01:00
|
|
|
if Length(fMapping) = 0 then
|
2018-09-28 13:01:46 +02:00
|
|
|
begin
|
2018-11-02 21:43:09 +01:00
|
|
|
if not fPrimaryKeyFieldName.IsEmpty then
|
2019-02-05 18:08:21 +01:00
|
|
|
begin
|
|
|
|
SetLength(fMapping, fMap.Count + 1);
|
|
|
|
fMapping[0].InstanceFieldName := fPrimaryKey.Name.Substring(1).ToLower;
|
|
|
|
fMapping[0].DatabaseFieldName := fPrimaryKeyFieldName;
|
|
|
|
i := 1;
|
|
|
|
end
|
2018-11-02 21:43:09 +01:00
|
|
|
else
|
2019-02-05 18:08:21 +01:00
|
|
|
begin
|
2018-11-02 21:43:09 +01:00
|
|
|
SetLength(fMapping, fMap.Count);
|
2019-02-05 18:08:21 +01:00
|
|
|
i := 0;
|
|
|
|
end;
|
2018-09-28 13:01:46 +02:00
|
|
|
|
2018-11-02 21:43:09 +01:00
|
|
|
for lPair in fMap do
|
|
|
|
begin
|
|
|
|
fMapping[i].InstanceFieldName := lPair.Key.Name.Substring(1).ToLower;
|
|
|
|
fMapping[i].DatabaseFieldName := lPair.value;
|
|
|
|
inc(i);
|
|
|
|
end;
|
2018-09-28 13:01:46 +02:00
|
|
|
end;
|
2018-11-02 21:43:09 +01:00
|
|
|
Result := fMapping;
|
2018-09-28 13:01:46 +02:00
|
|
|
end;
|
|
|
|
|
2019-03-05 20:55:37 +01:00
|
|
|
class function TMVCActiveRecord.GetOneByWhere<T>(const SQLWhere: string; const Params: array of Variant;
|
|
|
|
const RaiseExceptionIfNotFound: Boolean): T;
|
2018-10-23 16:18:34 +02:00
|
|
|
begin
|
|
|
|
Result := GetFirstByWhere<T>(SQLWhere, Params, false);
|
|
|
|
if Result = nil then
|
|
|
|
begin
|
|
|
|
if RaiseExceptionIfNotFound then
|
|
|
|
raise EMVCActiveRecord.Create('Got 0 rows when exactly 1 was expected');
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
2018-09-25 15:36:53 +02:00
|
|
|
function TMVCActiveRecord.GetPK: TValue;
|
|
|
|
begin
|
|
|
|
if fPrimaryKeyFieldName.IsEmpty then
|
|
|
|
raise Exception.Create('No primary key defined');
|
|
|
|
Result := fPrimaryKey.GetValue(Self);
|
|
|
|
end;
|
|
|
|
|
2019-03-05 20:55:37 +01:00
|
|
|
class function TMVCActiveRecord.GetScalar(const SQL: string; const Params: array of Variant): Variant;
|
2019-01-08 12:48:27 +01:00
|
|
|
begin
|
|
|
|
Result := CurrentConnection.ExecSQLScalar(SQL, Params);
|
|
|
|
end;
|
|
|
|
|
2019-03-05 20:55:37 +01:00
|
|
|
function TMVCActiveRecord.CheckAction(const aEntityAction: TMVCEntityAction; const aRaiseException: Boolean): Boolean;
|
2018-10-14 18:23:20 +02:00
|
|
|
begin
|
|
|
|
Result := aEntityAction in fEntityAllowedActions;
|
|
|
|
if (not Result) and aRaiseException then
|
|
|
|
raise EMVCActiveRecord.CreateFmt('Action not allowed on "%s"', [ClassName]);
|
|
|
|
end;
|
|
|
|
|
2019-03-05 20:55:37 +01:00
|
|
|
class function TMVCActiveRecord.Count(const aClass: TMVCActiveRecordClass): int64;
|
2019-01-13 19:18:57 +01:00
|
|
|
var
|
|
|
|
lAR: TMVCActiveRecord;
|
|
|
|
begin
|
|
|
|
lAR := aClass.Create;
|
|
|
|
try
|
|
|
|
Result := lAR.Count;
|
|
|
|
finally
|
|
|
|
lAR.Free;
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
|
|
|
function TMVCActiveRecord.Count: int64;
|
|
|
|
begin
|
|
|
|
Result := GetScalar(Self.SQLGenerator.CreateSelectCount(fTableName), []);
|
|
|
|
end;
|
|
|
|
|
|
|
|
class function TMVCActiveRecord.Count<T>: int64;
|
|
|
|
begin
|
|
|
|
Result := Count(TMVCActiveRecordClass(T));
|
|
|
|
end;
|
|
|
|
|
2018-11-24 16:56:21 +01:00
|
|
|
class function TMVCActiveRecord.CurrentConnection: TFDConnection;
|
2018-10-23 16:18:34 +02:00
|
|
|
begin
|
|
|
|
Result := ActiveRecordConnectionsRegistry.GetCurrent;
|
|
|
|
end;
|
|
|
|
|
|
|
|
function TMVCActiveRecord.SelfConnection: TFDConnection;
|
2018-09-28 13:01:46 +02:00
|
|
|
begin
|
|
|
|
if fConn = nil then
|
|
|
|
begin
|
|
|
|
fConn := ActiveRecordConnectionsRegistry.GetCurrent;
|
|
|
|
end;
|
|
|
|
Result := fConn;
|
|
|
|
end;
|
|
|
|
|
|
|
|
constructor TMVCActiveRecord.Create;
|
|
|
|
begin
|
|
|
|
Create(false);
|
|
|
|
end;
|
|
|
|
|
2018-09-25 15:36:53 +02:00
|
|
|
procedure TMVCActiveRecord.Delete;
|
|
|
|
var
|
|
|
|
SQL: string;
|
|
|
|
begin
|
2018-10-23 16:18:34 +02:00
|
|
|
OnBeforeDelete;
|
2018-09-25 15:36:53 +02:00
|
|
|
if not Assigned(fPrimaryKey) then
|
2019-03-05 20:55:37 +01:00
|
|
|
raise Exception.CreateFmt('Cannot delete %s without a primary key', [ClassName]);
|
|
|
|
SQL := SQLGenerator.CreateDeleteSQL(fTableName, fMap, fPrimaryKeyFieldName, fPrimaryKeyOptions,
|
|
|
|
fPrimaryKey.GetValue(Self).AsInt64);
|
2018-09-25 15:36:53 +02:00
|
|
|
ExecNonQuery(SQL, false);
|
2018-10-23 16:18:34 +02:00
|
|
|
OnAfterDelete;
|
2018-09-25 15:36:53 +02:00
|
|
|
end;
|
|
|
|
|
2019-03-05 20:55:37 +01:00
|
|
|
class function TMVCActiveRecord.DeleteAll(const aClass: TMVCActiveRecordClass): int64;
|
2019-02-21 18:11:14 +01:00
|
|
|
var
|
|
|
|
lAR: TMVCActiveRecord;
|
|
|
|
begin
|
|
|
|
lAR := aClass.Create;
|
|
|
|
try
|
2019-03-05 20:55:37 +01:00
|
|
|
Result := lAR.ExecNonQuery(lAR.SQLGenerator.CreateDeleteAllSQL(lAR.fTableName));
|
2019-02-21 18:11:14 +01:00
|
|
|
finally
|
|
|
|
lAR.Free;
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
2019-03-05 20:55:37 +01:00
|
|
|
class function TMVCActiveRecord.DeleteRQL(const aClass: TMVCActiveRecordClass; const RQL: string): int64;
|
2019-02-21 18:11:14 +01:00
|
|
|
var
|
|
|
|
lAR: TMVCActiveRecord;
|
|
|
|
begin
|
|
|
|
lAR := aClass.Create(True);
|
|
|
|
try
|
2019-03-05 20:55:37 +01:00
|
|
|
Result := lAR.ExecNonQuery(lAR.SQLGenerator.CreateDeleteAllSQL(lAR.fTableName) +
|
|
|
|
lAR.SQLGenerator.CreateSQLWhereByRQL(RQL, lAR.GetMapping, false));
|
2019-02-21 18:11:14 +01:00
|
|
|
finally
|
|
|
|
lAR.Free;
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
2019-03-05 20:55:37 +01:00
|
|
|
procedure TMVCActiveRecord.MapColumnToTValue(const aFieldName: string; const aField: TField;
|
|
|
|
const aRTTIField: TRttiField);
|
2018-09-25 15:36:53 +02:00
|
|
|
var
|
2018-10-14 18:23:20 +02:00
|
|
|
lInternalStream: TStream;
|
2018-11-09 18:11:59 +01:00
|
|
|
lSStream: TStringStream;
|
2018-09-25 15:36:53 +02:00
|
|
|
begin
|
|
|
|
case aField.DataType of
|
|
|
|
ftString, ftWideString:
|
|
|
|
begin
|
2018-10-14 18:23:20 +02:00
|
|
|
aRTTIField.SetValue(Self, aField.AsString);
|
2018-09-25 15:36:53 +02:00
|
|
|
end;
|
2018-10-23 16:18:34 +02:00
|
|
|
ftLargeint, ftAutoInc:
|
2018-09-25 15:36:53 +02:00
|
|
|
begin
|
2018-10-14 18:23:20 +02:00
|
|
|
aRTTIField.SetValue(Self, aField.AsLargeInt);
|
2018-09-25 15:36:53 +02:00
|
|
|
end;
|
2018-09-28 18:33:19 +02:00
|
|
|
ftInteger, ftSmallint, ftShortint:
|
2018-09-25 15:36:53 +02:00
|
|
|
begin
|
2018-10-14 18:23:20 +02:00
|
|
|
aRTTIField.SetValue(Self, aField.AsInteger);
|
2018-09-25 15:36:53 +02:00
|
|
|
end;
|
|
|
|
ftLongWord, ftWord:
|
|
|
|
begin
|
2018-10-14 18:23:20 +02:00
|
|
|
aRTTIField.SetValue(Self, aField.AsLongWord);
|
2018-09-25 15:36:53 +02:00
|
|
|
end;
|
2018-12-17 00:39:29 +01:00
|
|
|
ftFMTBcd:
|
|
|
|
begin
|
|
|
|
aRTTIField.SetValue(Self, BCDtoCurrency(aField.AsBCD));
|
|
|
|
end;
|
2018-09-25 15:36:53 +02:00
|
|
|
ftDate:
|
|
|
|
begin
|
2018-10-14 18:23:20 +02:00
|
|
|
aRTTIField.SetValue(Self, Trunc(aField.AsDateTime));
|
2018-09-25 15:36:53 +02:00
|
|
|
end;
|
|
|
|
ftDateTime:
|
|
|
|
begin
|
2018-10-14 18:23:20 +02:00
|
|
|
aRTTIField.SetValue(Self, aField.AsDateTime);
|
2018-09-25 15:36:53 +02:00
|
|
|
end;
|
2018-11-09 18:11:59 +01:00
|
|
|
ftTimeStamp:
|
|
|
|
begin
|
|
|
|
aRTTIField.SetValue(Self, aField.AsDateTime);
|
|
|
|
end;
|
2018-09-25 15:36:53 +02:00
|
|
|
ftBoolean:
|
|
|
|
begin
|
2018-10-14 18:23:20 +02:00
|
|
|
aRTTIField.SetValue(Self, aField.AsBoolean);
|
2018-09-25 15:36:53 +02:00
|
|
|
end;
|
|
|
|
ftMemo, ftWideMemo:
|
|
|
|
begin
|
2019-03-05 20:55:37 +01:00
|
|
|
if aRTTIField.FieldType.TypeKind in [tkString, tkUString, tkWideString] then
|
2018-11-09 18:11:59 +01:00
|
|
|
begin
|
|
|
|
// In case you want to map a "TEXT" blob into a Delphi String
|
|
|
|
lSStream := TStringStream.Create('', TEncoding.Unicode);
|
|
|
|
try
|
|
|
|
TBlobField(aField).SaveToStream(lSStream);
|
|
|
|
aRTTIField.SetValue(Self, lSStream.DataString);
|
|
|
|
finally
|
|
|
|
lSStream.Free;
|
|
|
|
end;
|
|
|
|
end
|
|
|
|
else
|
|
|
|
begin
|
|
|
|
// In case you want to map a bynary blob into a Delphi Stream
|
|
|
|
lInternalStream := aRTTIField.GetValue(Self).AsObject as TStream;
|
|
|
|
if lInternalStream = nil then
|
|
|
|
begin
|
2019-03-05 20:55:37 +01:00
|
|
|
raise EMVCActiveRecord.CreateFmt('Property target for %s field is nil', [aFieldName]);
|
2018-11-09 18:11:59 +01:00
|
|
|
end;
|
|
|
|
lInternalStream.Position := 0;
|
|
|
|
TBlobField(aField).SaveToStream(lInternalStream);
|
|
|
|
lInternalStream.Position := 0;
|
|
|
|
end;
|
2018-10-14 18:23:20 +02:00
|
|
|
end;
|
|
|
|
ftBCD:
|
|
|
|
begin
|
|
|
|
aRTTIField.SetValue(Self, BCDtoCurrency(aField.AsBCD));
|
2018-09-25 15:36:53 +02:00
|
|
|
end;
|
2019-03-14 12:14:12 +01:00
|
|
|
ftFloat:
|
|
|
|
begin
|
|
|
|
aRTTIField.SetValue(Self, aField.AsFloat);
|
|
|
|
end;
|
2018-09-25 15:36:53 +02:00
|
|
|
ftBlob:
|
|
|
|
begin
|
2018-10-14 18:23:20 +02:00
|
|
|
lInternalStream := aRTTIField.GetValue(Self).AsObject as TStream;
|
|
|
|
if lInternalStream = nil then
|
|
|
|
begin
|
2019-03-05 20:55:37 +01:00
|
|
|
raise EMVCActiveRecord.CreateFmt('Property target for %s field is nil', [aFieldName]);
|
2018-09-25 15:36:53 +02:00
|
|
|
end;
|
2018-10-14 18:23:20 +02:00
|
|
|
lInternalStream.Position := 0;
|
|
|
|
TBlobField(aField).SaveToStream(lInternalStream);
|
|
|
|
lInternalStream.Position := 0;
|
2018-09-25 15:36:53 +02:00
|
|
|
end;
|
2019-03-05 20:55:37 +01:00
|
|
|
ftGuid:
|
|
|
|
begin
|
|
|
|
aRTTIField.SetValue(Self, TValue.From<TGUID>(aField.AsGuid));
|
|
|
|
end;
|
2018-09-25 15:36:53 +02:00
|
|
|
else
|
2019-03-05 20:55:37 +01:00
|
|
|
raise EMVCActiveRecord.CreateFmt('Unsupported FieldType (%d) for field %s', [Ord(aField.DataType), aFieldName]);
|
2018-09-25 15:36:53 +02:00
|
|
|
end;
|
2018-10-14 18:23:20 +02:00
|
|
|
|
2018-09-25 15:36:53 +02:00
|
|
|
end;
|
|
|
|
|
2019-03-05 20:55:37 +01:00
|
|
|
procedure TMVCActiveRecord.MapTValueToParam(const aValue: TValue; const aParam: TFDParam);
|
2018-09-25 15:36:53 +02:00
|
|
|
var
|
|
|
|
lStream: TStream;
|
2019-03-13 12:38:04 +01:00
|
|
|
lName: String;
|
2018-09-25 15:36:53 +02:00
|
|
|
begin
|
2019-03-13 12:38:04 +01:00
|
|
|
{$IFDEF NEXTGEN}
|
|
|
|
lName := aValue.TypeInfo.NameFld.ToString;
|
|
|
|
{$ELSE}
|
2019-03-18 14:08:34 +01:00
|
|
|
lName := String(aValue.TypeInfo.Name);
|
2019-03-13 12:38:04 +01:00
|
|
|
{$ENDIF}
|
2018-10-14 18:23:20 +02:00
|
|
|
case aValue.TypeInfo.Kind of
|
|
|
|
// tkUnknown:
|
|
|
|
// begin
|
|
|
|
// { aParam.DataType could be pkUndefined for some RDBMS (es. MySQL), so we rely on Variant }
|
|
|
|
// if (aValue.TypeInfo.Kind = tkClass) then
|
|
|
|
// begin
|
|
|
|
// if not aValue.IsInstanceOf(TStream) then
|
|
|
|
// raise EMVCActiveRecord.CreateFmt('Unsupported type for param %s', [aParam.Name]);
|
|
|
|
// lStream := aValue.AsType<TStream>(false);
|
|
|
|
// if Assigned(lStream) then
|
|
|
|
// begin
|
|
|
|
// lStream.Position := 0;
|
|
|
|
// aParam.LoadFromStream(lStream, ftBlob);
|
|
|
|
// end
|
|
|
|
// else
|
|
|
|
// begin
|
|
|
|
// aParam.Clear;
|
|
|
|
// end;
|
|
|
|
//
|
|
|
|
// end
|
|
|
|
// else
|
|
|
|
// begin
|
|
|
|
// aParam.value := aValue.AsVariant;
|
|
|
|
// end;
|
|
|
|
// end;
|
|
|
|
tkString, tkUString:
|
2018-09-25 15:36:53 +02:00
|
|
|
begin
|
|
|
|
aParam.AsString := aValue.AsString;
|
|
|
|
end;
|
2018-10-14 18:23:20 +02:00
|
|
|
tkWideString:
|
2018-09-25 15:36:53 +02:00
|
|
|
begin
|
|
|
|
aParam.AsWideString := aValue.AsString;
|
|
|
|
end;
|
2018-10-14 18:23:20 +02:00
|
|
|
tkInt64:
|
2018-09-25 15:36:53 +02:00
|
|
|
begin
|
|
|
|
aParam.AsLargeInt := aValue.AsInt64;
|
|
|
|
end;
|
2018-10-14 18:23:20 +02:00
|
|
|
tkInteger:
|
2018-09-25 15:36:53 +02:00
|
|
|
begin
|
|
|
|
aParam.AsInteger := aValue.AsInteger;
|
|
|
|
end;
|
2018-10-14 18:23:20 +02:00
|
|
|
tkEnumeration:
|
2018-09-25 15:36:53 +02:00
|
|
|
begin
|
2018-10-14 18:23:20 +02:00
|
|
|
if aValue.TypeInfo = TypeInfo(System.Boolean) then
|
|
|
|
begin
|
|
|
|
aParam.AsBoolean := aValue.AsBoolean;
|
|
|
|
end
|
|
|
|
else
|
|
|
|
begin
|
|
|
|
aParam.AsInteger := Ord(aValue.AsInteger);
|
|
|
|
end;
|
2018-09-25 15:36:53 +02:00
|
|
|
end;
|
2018-10-14 18:23:20 +02:00
|
|
|
tkFloat:
|
2018-09-25 15:36:53 +02:00
|
|
|
begin
|
2019-03-13 12:38:04 +01:00
|
|
|
if lName = 'TDate' then
|
2018-10-14 18:23:20 +02:00
|
|
|
begin
|
|
|
|
aParam.AsDate := Trunc(aValue.AsExtended);
|
|
|
|
end
|
2019-03-13 12:38:04 +01:00
|
|
|
else if lName = 'TDateTime' then
|
2018-10-14 18:23:20 +02:00
|
|
|
begin
|
|
|
|
aParam.AsDateTime := aValue.AsExtended;
|
2018-11-02 21:43:09 +01:00
|
|
|
end
|
2019-03-13 12:38:04 +01:00
|
|
|
else if lName = 'Currency' then
|
2018-11-02 21:43:09 +01:00
|
|
|
begin
|
|
|
|
aParam.AsCurrency := aValue.AsCurrency;
|
|
|
|
end
|
|
|
|
else
|
|
|
|
begin
|
|
|
|
aParam.AsFloat := aValue.AsExtended;
|
2018-10-14 18:23:20 +02:00
|
|
|
end;
|
2018-09-25 15:36:53 +02:00
|
|
|
end;
|
2018-10-14 18:23:20 +02:00
|
|
|
tkClass:
|
2018-09-25 15:36:53 +02:00
|
|
|
begin
|
2018-10-14 18:23:20 +02:00
|
|
|
if not aValue.IsInstanceOf(TStream) then
|
2019-03-05 20:55:37 +01:00
|
|
|
raise EMVCActiveRecord.CreateFmt('Unsupported reference type for param %s: %s',
|
2018-11-24 16:56:21 +01:00
|
|
|
[aParam.Name, aValue.AsObject.ClassName]);
|
2018-09-25 15:36:53 +02:00
|
|
|
lStream := aValue.AsType<TStream>(false);
|
|
|
|
if Assigned(lStream) then
|
|
|
|
begin
|
|
|
|
lStream.Position := 0;
|
|
|
|
aParam.LoadFromStream(lStream, ftBlob);
|
|
|
|
end
|
|
|
|
else
|
|
|
|
begin
|
|
|
|
aParam.Clear;
|
|
|
|
end;
|
|
|
|
end;
|
2019-03-05 20:55:37 +01:00
|
|
|
tkRecord:
|
|
|
|
begin
|
|
|
|
if aValue.IsType(TypeInfo(TGUID)) then
|
|
|
|
begin
|
|
|
|
aParam.AsGuid := aValue.AsType<TGUID>;
|
|
|
|
end
|
|
|
|
else
|
|
|
|
begin
|
|
|
|
raise Exception.CreateFmt('Unsupported Record TypeKind (%d) for param %s',
|
|
|
|
[Ord(aValue.TypeInfo.Kind), aParam.Name]);
|
|
|
|
end;
|
|
|
|
end;
|
2018-10-14 18:23:20 +02:00
|
|
|
// ftBoolean:
|
|
|
|
// begin
|
|
|
|
// aParam.AsBoolean := aValue.AsBoolean;
|
|
|
|
// end;
|
|
|
|
// ftMemo:
|
|
|
|
// begin
|
|
|
|
// aParam.AsMemo := AnsiString(aValue.AsString);
|
|
|
|
// end;
|
|
|
|
// ftWideMemo:
|
|
|
|
// begin
|
|
|
|
// aParam.AsWideMemo := aValue.AsString;
|
|
|
|
// end;
|
|
|
|
// ftBCD:
|
|
|
|
// begin
|
|
|
|
// aParam.AsBCD := aValue.AsCurrency;
|
|
|
|
// end;
|
|
|
|
// ftBlob:
|
|
|
|
// begin
|
|
|
|
// lStream := aValue.AsType<TStream>(false);
|
|
|
|
// if Assigned(lStream) then
|
|
|
|
// begin
|
|
|
|
// lStream.Position := 0;
|
|
|
|
// aParam.LoadFromStream(lStream, ftBlob);
|
|
|
|
// end
|
|
|
|
// else
|
|
|
|
// begin
|
|
|
|
// aParam.Clear;
|
|
|
|
// end;
|
|
|
|
// end;
|
2018-09-25 15:36:53 +02:00
|
|
|
else
|
2019-03-05 20:55:37 +01:00
|
|
|
raise Exception.CreateFmt('Unsupported TypeKind (%d) for param %s', [Ord(aValue.TypeInfo.Kind), aParam.Name]);
|
2018-09-25 15:36:53 +02:00
|
|
|
end;
|
2018-10-14 18:23:20 +02:00
|
|
|
|
|
|
|
// case aParam.DataType of
|
|
|
|
// ftUnknown:
|
|
|
|
// begin
|
|
|
|
// { aParam.DataType could be pkUndefined for some RDBMS (es. MySQL), so we rely on Variant }
|
|
|
|
// if (aValue.TypeInfo.Kind = tkClass) then
|
|
|
|
// begin
|
|
|
|
// if not aValue.IsInstanceOf(TStream) then
|
|
|
|
// raise EMVCActiveRecord.CreateFmt('Unsupported type for param %s', [aParam.Name]);
|
|
|
|
// lStream := aValue.AsType<TStream>(false);
|
|
|
|
// if Assigned(lStream) then
|
|
|
|
// begin
|
|
|
|
// lStream.Position := 0;
|
|
|
|
// aParam.LoadFromStream(lStream, ftBlob);
|
|
|
|
// end
|
|
|
|
// else
|
|
|
|
// begin
|
|
|
|
// aParam.Clear;
|
|
|
|
// end;
|
|
|
|
//
|
|
|
|
// end
|
|
|
|
// else
|
|
|
|
// begin
|
|
|
|
// aParam.value := aValue.AsVariant;
|
|
|
|
// end;
|
|
|
|
// end;
|
|
|
|
// ftString:
|
|
|
|
// begin
|
|
|
|
// aParam.AsString := aValue.AsString;
|
|
|
|
// end;
|
|
|
|
// ftWideString:
|
|
|
|
// begin
|
|
|
|
// aParam.AsWideString := aValue.AsString;
|
|
|
|
// end;
|
|
|
|
// ftLargeint:
|
|
|
|
// begin
|
|
|
|
// aParam.AsLargeInt := aValue.AsInt64;
|
|
|
|
// end;
|
|
|
|
// ftSmallint:
|
|
|
|
// begin
|
|
|
|
// aParam.AsSmallInt := aValue.AsInteger;
|
|
|
|
// end;
|
|
|
|
// ftInteger:
|
|
|
|
// begin
|
|
|
|
// aParam.AsInteger := aValue.AsInteger;
|
|
|
|
// end;
|
|
|
|
// ftLongWord:
|
|
|
|
// begin
|
|
|
|
// aParam.AsLongWord := aValue.AsInteger;
|
|
|
|
// end;
|
|
|
|
// ftWord:
|
|
|
|
// begin
|
|
|
|
// aParam.AsWord := aValue.AsInteger;
|
|
|
|
// end;
|
|
|
|
// ftDate:
|
|
|
|
// begin
|
|
|
|
// aParam.AsDate := Trunc(aValue.AsExtended);
|
|
|
|
// end;
|
|
|
|
// ftDateTime:
|
|
|
|
// begin
|
|
|
|
// aParam.AsDateTime := aValue.AsExtended;
|
|
|
|
// end;
|
|
|
|
// ftBoolean:
|
|
|
|
// begin
|
|
|
|
// aParam.AsBoolean := aValue.AsBoolean;
|
|
|
|
// end;
|
|
|
|
// ftMemo:
|
|
|
|
// begin
|
|
|
|
// aParam.AsMemo := AnsiString(aValue.AsString);
|
|
|
|
// end;
|
|
|
|
// ftWideMemo:
|
|
|
|
// begin
|
|
|
|
// aParam.AsWideMemo := aValue.AsString;
|
|
|
|
// end;
|
|
|
|
// ftBCD:
|
|
|
|
// begin
|
|
|
|
// aParam.AsBCD := aValue.AsCurrency;
|
|
|
|
// end;
|
|
|
|
// ftBlob:
|
|
|
|
// begin
|
|
|
|
// lStream := aValue.AsType<TStream>(false);
|
|
|
|
// if Assigned(lStream) then
|
|
|
|
// begin
|
|
|
|
// lStream.Position := 0;
|
|
|
|
// aParam.LoadFromStream(lStream, ftBlob);
|
|
|
|
// end
|
|
|
|
// else
|
|
|
|
// begin
|
|
|
|
// aParam.Clear;
|
|
|
|
// end;
|
|
|
|
// end;
|
|
|
|
// else
|
|
|
|
// raise Exception.CreateFmt('Unsupported FieldType (%d) for param %s', [Ord(aParam.DataType), aParam.Name]);
|
|
|
|
// end;
|
2018-09-25 15:36:53 +02:00
|
|
|
end;
|
|
|
|
|
2019-03-05 20:55:37 +01:00
|
|
|
procedure TMVCActiveRecord.LoadByDataset(const aDataSet: TDataSet; const aOptions: TMVCActiveRecordLoadOptions);
|
2018-09-25 15:36:53 +02:00
|
|
|
var
|
|
|
|
lItem: TPair<TRttiField, string>;
|
2018-12-09 23:03:06 +01:00
|
|
|
lField: TField;
|
2018-09-25 15:36:53 +02:00
|
|
|
begin
|
2018-10-14 18:23:20 +02:00
|
|
|
CheckAction(TMVCEntityAction.eaRetrieve);
|
2018-09-25 15:36:53 +02:00
|
|
|
OnBeforeLoad;
|
|
|
|
for lItem in fMap do
|
|
|
|
begin
|
2018-12-09 23:03:06 +01:00
|
|
|
lField := aDataSet.FindField(lItem.value);
|
|
|
|
if lField = nil then
|
|
|
|
begin
|
|
|
|
if TMVCActiveRecordLoadOption.loIgnoreNotExistentFields in aOptions then
|
|
|
|
continue
|
|
|
|
else
|
2018-12-17 00:39:29 +01:00
|
|
|
raise EMVCActiveRecord.CreateFmt
|
2019-03-05 20:55:37 +01:00
|
|
|
('Field [%s] not found in dataset. [HINT] If you dont need it, use loIgnoreNotExistentFields', [lItem.value]);
|
2018-12-09 23:03:06 +01:00
|
|
|
end;
|
|
|
|
MapColumnToTValue(lItem.value, lField, lItem.Key);
|
2018-09-25 15:36:53 +02:00
|
|
|
end;
|
|
|
|
if not fPrimaryKeyFieldName.IsEmpty then
|
|
|
|
begin
|
2019-03-05 20:55:37 +01:00
|
|
|
MapColumnToTValue(fPrimaryKeyFieldName, aDataSet.FieldByName(fPrimaryKeyFieldName), fPrimaryKey);
|
2018-09-25 15:36:53 +02:00
|
|
|
end;
|
|
|
|
OnAfterLoad;
|
|
|
|
end;
|
|
|
|
|
2018-10-14 18:23:20 +02:00
|
|
|
function TMVCActiveRecord.LoadByPK(id: int64): Boolean;
|
2018-09-25 15:36:53 +02:00
|
|
|
var
|
|
|
|
SQL: string;
|
|
|
|
lDataSet: TDataSet;
|
|
|
|
begin
|
2018-10-14 18:23:20 +02:00
|
|
|
CheckAction(TMVCEntityAction.eaRetrieve);
|
2019-03-05 20:55:37 +01:00
|
|
|
SQL := SQLGenerator.CreateSelectByPKSQL(fTableName, fMap, fPrimaryKeyFieldName, fPrimaryKeyOptions, id);
|
2018-11-02 21:43:09 +01:00
|
|
|
lDataSet := ExecQuery(SQL, [id], fConn);
|
2018-09-25 15:36:53 +02:00
|
|
|
try
|
|
|
|
Result := not lDataSet.Eof;
|
|
|
|
if Result then
|
|
|
|
begin
|
|
|
|
LoadByDataset(lDataSet);
|
|
|
|
end;
|
|
|
|
finally
|
|
|
|
lDataSet.Free;
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
2018-10-23 16:18:34 +02:00
|
|
|
procedure TMVCActiveRecord.OnAfterDelete;
|
|
|
|
begin
|
|
|
|
// do nothing
|
|
|
|
end;
|
|
|
|
|
|
|
|
procedure TMVCActiveRecord.OnAfterInsert;
|
|
|
|
begin
|
|
|
|
// do nothing
|
|
|
|
end;
|
|
|
|
|
|
|
|
procedure TMVCActiveRecord.OnAfterInsertOrUpdate;
|
|
|
|
begin
|
|
|
|
// do nothing
|
|
|
|
end;
|
|
|
|
|
2018-09-25 15:36:53 +02:00
|
|
|
procedure TMVCActiveRecord.OnAfterLoad;
|
|
|
|
begin
|
|
|
|
// do nothing
|
|
|
|
end;
|
|
|
|
|
2018-10-23 16:18:34 +02:00
|
|
|
procedure TMVCActiveRecord.OnAfterUpdate;
|
|
|
|
begin
|
|
|
|
// do nothing
|
|
|
|
end;
|
|
|
|
|
2018-09-25 15:36:53 +02:00
|
|
|
procedure TMVCActiveRecord.OnBeforeDelete;
|
|
|
|
begin
|
|
|
|
// do nothing
|
|
|
|
end;
|
|
|
|
|
2019-03-18 14:08:34 +01:00
|
|
|
procedure TMVCActiveRecord.OnBeforeExecuteSQL(var SQL:String);
|
|
|
|
begin
|
|
|
|
// do nothing
|
|
|
|
end;
|
|
|
|
|
2018-09-25 15:36:53 +02:00
|
|
|
procedure TMVCActiveRecord.OnBeforeInsert;
|
|
|
|
begin
|
|
|
|
// do nothing
|
|
|
|
end;
|
|
|
|
|
|
|
|
procedure TMVCActiveRecord.OnBeforeInsertOrUpdate;
|
|
|
|
begin
|
|
|
|
// do nothing
|
|
|
|
end;
|
|
|
|
|
|
|
|
procedure TMVCActiveRecord.OnBeforeLoad;
|
|
|
|
begin
|
|
|
|
// do nothing
|
|
|
|
end;
|
|
|
|
|
|
|
|
procedure TMVCActiveRecord.OnBeforeUpdate;
|
|
|
|
begin
|
|
|
|
// do nothing
|
|
|
|
end;
|
|
|
|
|
|
|
|
procedure TMVCActiveRecord.OnValidation;
|
|
|
|
begin
|
|
|
|
// do nothing
|
|
|
|
end;
|
|
|
|
|
2019-03-05 20:55:37 +01:00
|
|
|
class function TMVCActiveRecord.Select(const aClass: TMVCActiveRecordClass; const SQL: string;
|
|
|
|
const Params: array of Variant): TMVCActiveRecordList;
|
2018-09-28 13:01:46 +02:00
|
|
|
begin
|
|
|
|
Result := Select(aClass, SQL, Params, nil);
|
|
|
|
end;
|
|
|
|
|
2019-03-05 20:55:37 +01:00
|
|
|
class function TMVCActiveRecord.Select(const aClass: TMVCActiveRecordClass; const SQL: string;
|
|
|
|
const Params: array of Variant; const Connection: TFDConnection): TMVCActiveRecordList;
|
2018-09-25 15:36:53 +02:00
|
|
|
var
|
|
|
|
lDataSet: TDataSet;
|
|
|
|
lAR: TMVCActiveRecord;
|
|
|
|
begin
|
2018-10-23 16:18:34 +02:00
|
|
|
Result := TMVCActiveRecordList.Create;
|
2018-09-25 15:36:53 +02:00
|
|
|
try
|
2018-09-28 13:01:46 +02:00
|
|
|
lDataSet := ExecQuery(SQL, Params, Connection);
|
2018-09-25 15:36:53 +02:00
|
|
|
try
|
|
|
|
while not lDataSet.Eof do
|
|
|
|
begin
|
|
|
|
lAR := aClass.Create;
|
|
|
|
Result.Add(lAR);
|
|
|
|
lAR.LoadByDataset(lDataSet);
|
|
|
|
lDataSet.Next;
|
|
|
|
end;
|
|
|
|
// lDataSet.First;
|
|
|
|
// TFile.WriteAllText('output.json', lDataSet.AsJSONArray);
|
|
|
|
finally
|
|
|
|
lDataSet.Free;
|
|
|
|
end;
|
|
|
|
except
|
|
|
|
Result.Free;
|
|
|
|
raise;
|
|
|
|
end;
|
2018-09-28 13:01:46 +02:00
|
|
|
|
2018-09-25 15:36:53 +02:00
|
|
|
end;
|
|
|
|
|
2019-03-05 20:55:37 +01:00
|
|
|
class function TMVCActiveRecord.Select<T>(const SQL: string; const Params: array of Variant;
|
|
|
|
const Options: TMVCActiveRecordLoadOptions): TObjectList<T>;
|
2018-09-25 15:36:53 +02:00
|
|
|
var
|
|
|
|
lDataSet: TDataSet;
|
|
|
|
lAR: TMVCActiveRecord;
|
|
|
|
begin
|
|
|
|
Result := TObjectList<T>.Create(True);
|
|
|
|
try
|
|
|
|
lDataSet := ExecQuery(SQL, Params);
|
|
|
|
try
|
|
|
|
while not lDataSet.Eof do
|
|
|
|
begin
|
|
|
|
lAR := T.Create;
|
|
|
|
Result.Add(lAR);
|
2018-12-09 23:03:06 +01:00
|
|
|
lAR.LoadByDataset(lDataSet, Options);
|
2018-09-25 15:36:53 +02:00
|
|
|
lDataSet.Next;
|
|
|
|
end;
|
|
|
|
finally
|
|
|
|
lDataSet.Free;
|
|
|
|
end;
|
|
|
|
except
|
|
|
|
Result.Free;
|
|
|
|
raise;
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
2019-03-05 20:55:37 +01:00
|
|
|
class function TMVCActiveRecord.SelectDataSet(const SQL: string; const Params: array of Variant): TDataSet;
|
2018-09-25 15:36:53 +02:00
|
|
|
begin
|
|
|
|
Result := TMVCActiveRecord.ExecQuery(SQL, Params);
|
|
|
|
end;
|
|
|
|
|
2019-03-05 20:55:37 +01:00
|
|
|
function TMVCActiveRecord.SelectRQL(const RQL: string; const MaxRecordCount: Integer): TMVCActiveRecordList;
|
2018-09-27 12:26:50 +02:00
|
|
|
var
|
|
|
|
lSQL: string;
|
|
|
|
begin
|
2019-02-21 18:11:14 +01:00
|
|
|
lSQL := SQLGenerator.CreateSQLWhereByRQL(RQL, GetMapping);
|
2018-11-02 21:43:09 +01:00
|
|
|
LogD(Format('RQL [%s] => SQL [%s]', [RQL, lSQL]));
|
|
|
|
Result := Where(TMVCActiveRecordClass(Self.ClassType), lSQL, []);
|
|
|
|
end;
|
|
|
|
|
2019-03-05 20:55:37 +01:00
|
|
|
class function TMVCActiveRecord.SelectRQL<T>(const RQL: string; const MaxRecordCount: Integer): TObjectList<T>;
|
2018-11-09 18:11:59 +01:00
|
|
|
var
|
|
|
|
lAR: TMVCActiveRecord;
|
|
|
|
lSQL: string;
|
|
|
|
begin
|
|
|
|
lAR := T.Create;
|
|
|
|
try
|
2019-02-21 18:11:14 +01:00
|
|
|
lSQL := lAR.SQLGenerator.CreateSQLWhereByRQL(RQL, lAR.GetMapping).Trim;
|
2019-01-13 19:18:57 +01:00
|
|
|
// LogD(Format('RQL [%s] => SQL [%s]', [RQL, lSQL]));
|
2018-11-09 18:11:59 +01:00
|
|
|
if lSQL.StartsWith('where', True) then
|
|
|
|
lSQL := lSQL.Remove(0, 5).Trim;
|
|
|
|
Result := Where<T>(lSQL, []);
|
|
|
|
finally
|
|
|
|
lAR.Free;
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
2019-03-05 20:55:37 +01:00
|
|
|
class function TMVCActiveRecord.SelectRQL(const aClass: TMVCActiveRecordClass; const RQL: string;
|
|
|
|
const MaxRecordCount: Integer): TMVCActiveRecordList;
|
2018-11-02 21:43:09 +01:00
|
|
|
var
|
|
|
|
lAR: TMVCActiveRecord;
|
|
|
|
begin
|
|
|
|
lAR := aClass.Create(True);
|
2018-09-27 12:26:50 +02:00
|
|
|
try
|
2018-11-02 21:43:09 +01:00
|
|
|
Result := lAR.SelectRQL(RQL, MaxRecordCount);
|
2018-09-27 12:26:50 +02:00
|
|
|
finally
|
2018-11-02 21:43:09 +01:00
|
|
|
lAR.Free;
|
2018-09-27 12:26:50 +02:00
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
2018-09-25 15:36:53 +02:00
|
|
|
procedure TMVCActiveRecord.SetPK(const aValue: TValue);
|
|
|
|
begin
|
|
|
|
if fPrimaryKeyFieldName.IsEmpty then
|
|
|
|
raise Exception.Create('No primary key defined');
|
|
|
|
fPrimaryKey.SetValue(Self, aValue);
|
|
|
|
end;
|
|
|
|
|
2018-11-24 16:56:21 +01:00
|
|
|
function TMVCActiveRecord.SQLGenerator: TMVCSQLGenerator;
|
|
|
|
begin
|
|
|
|
if not Assigned(fSQLGenerator) then
|
|
|
|
begin
|
|
|
|
fConn.Connected := True;
|
2019-03-05 20:55:37 +01:00
|
|
|
fSQLGenerator := TMVCSQLGeneratorRegistry.Instance.GetSQLGenerator(GetBackEnd).Create(GetMapping);
|
2018-11-24 16:56:21 +01:00
|
|
|
end;
|
|
|
|
Result := fSQLGenerator;
|
|
|
|
end;
|
|
|
|
|
2018-09-25 15:36:53 +02:00
|
|
|
function TMVCActiveRecord.TableInfo: string;
|
|
|
|
var
|
|
|
|
keyvalue: TPair<TRttiField, string>;
|
|
|
|
begin
|
|
|
|
Result := 'Table Name: ' + fTableName;
|
|
|
|
for keyvalue in fMap do
|
2019-03-05 20:55:37 +01:00
|
|
|
Result := Result + sLineBreak + #9 + keyvalue.Key.Name + ' = ' + keyvalue.value;
|
2018-09-25 15:36:53 +02:00
|
|
|
end;
|
|
|
|
|
|
|
|
procedure TMVCActiveRecord.Update;
|
|
|
|
var
|
|
|
|
SQL: string;
|
|
|
|
begin
|
2018-10-14 18:23:20 +02:00
|
|
|
CheckAction(TMVCEntityAction.eaUpdate);
|
2018-09-25 15:36:53 +02:00
|
|
|
OnValidation;
|
|
|
|
OnBeforeUpdate;
|
|
|
|
OnBeforeInsertOrUpdate;
|
2019-02-21 20:17:11 +01:00
|
|
|
if fMapNonTransientFields.Count = 0 then
|
|
|
|
begin
|
|
|
|
raise EMVCActiveRecord.CreateFmt
|
2019-03-05 20:55:37 +01:00
|
|
|
('Cannot update an entity if all fields are transient. Class [%s] mapped on table [%s]', [ClassName, fTableName]);
|
2019-02-21 20:17:11 +01:00
|
|
|
end;
|
2019-03-05 20:55:37 +01:00
|
|
|
SQL := SQLGenerator.CreateUpdateSQL(fTableName, fMapNonTransientFields, fPrimaryKeyFieldName, fPrimaryKeyOptions);
|
2018-09-25 15:36:53 +02:00
|
|
|
ExecNonQuery(SQL, false);
|
2018-10-23 16:18:34 +02:00
|
|
|
OnAfterUpdate;
|
|
|
|
OnAfterInsertOrUpdate;
|
2018-09-25 15:36:53 +02:00
|
|
|
end;
|
|
|
|
|
2019-03-05 20:55:37 +01:00
|
|
|
class function TMVCActiveRecord.All(const aClass: TMVCActiveRecordClass): TObjectList<TMVCActiveRecord>;
|
2018-09-25 15:36:53 +02:00
|
|
|
var
|
|
|
|
lAR: TMVCActiveRecord;
|
|
|
|
begin
|
|
|
|
lAR := aClass.Create;
|
|
|
|
try
|
2018-11-02 21:43:09 +01:00
|
|
|
Result := Select(aClass, lAR.GenerateSelectSQL, []);
|
2018-09-25 15:36:53 +02:00
|
|
|
finally
|
|
|
|
lAR.Free;
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
2018-11-24 16:56:21 +01:00
|
|
|
class function TMVCActiveRecord.All<T>: TObjectList<T>;
|
2018-09-25 15:36:53 +02:00
|
|
|
var
|
|
|
|
lAR: TMVCActiveRecord;
|
|
|
|
begin
|
|
|
|
lAR := T.Create;
|
|
|
|
try
|
2018-11-02 21:43:09 +01:00
|
|
|
Result := Select<T>(lAR.GenerateSelectSQL, []);
|
2018-09-25 15:36:53 +02:00
|
|
|
finally
|
|
|
|
lAR.Free;
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
2019-03-05 20:55:37 +01:00
|
|
|
class function TMVCActiveRecord.Where(const aClass: TMVCActiveRecordClass; const SQLWhere: string;
|
|
|
|
const Params: array of Variant): TMVCActiveRecordList;
|
2018-09-28 13:01:46 +02:00
|
|
|
begin
|
2018-10-23 16:18:34 +02:00
|
|
|
Result := Where(aClass, SQLWhere, Params, nil);
|
2018-09-28 13:01:46 +02:00
|
|
|
end;
|
|
|
|
|
2019-03-05 20:55:37 +01:00
|
|
|
class function TMVCActiveRecord.Where(const aClass: TMVCActiveRecordClass; const SQLWhere: string;
|
|
|
|
const Params: array of Variant; const Connection: TFDConnection): TMVCActiveRecordList;
|
2018-09-27 12:26:50 +02:00
|
|
|
var
|
|
|
|
lAR: TMVCActiveRecord;
|
|
|
|
begin
|
|
|
|
lAR := aClass.Create;
|
|
|
|
try
|
2019-03-05 20:55:37 +01:00
|
|
|
Result := Select(aClass, lAR.GenerateSelectSQL + SQLWhere, Params, Connection);
|
2018-09-27 12:26:50 +02:00
|
|
|
finally
|
|
|
|
lAR.Free;
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
2019-03-05 20:55:37 +01:00
|
|
|
class function TMVCActiveRecord.Where<T>(const SQLWhere: string; const Params: array of Variant): TObjectList<T>;
|
2018-09-25 15:36:53 +02:00
|
|
|
var
|
|
|
|
lAR: TMVCActiveRecord;
|
|
|
|
begin
|
|
|
|
lAR := T.Create;
|
|
|
|
try
|
2019-03-05 20:55:37 +01:00
|
|
|
if SQLWhere.Trim.IsEmpty() or SQLWhere.Trim.StartsWith('/*limit*/') or SQLWhere.Trim.StartsWith('/*sort*/') then
|
2018-09-27 12:26:50 +02:00
|
|
|
begin
|
2018-12-17 00:39:29 +01:00
|
|
|
Result := Select<T>(lAR.GenerateSelectSQL + SQLWhere, Params);
|
2018-09-27 12:26:50 +02:00
|
|
|
end
|
|
|
|
else
|
|
|
|
begin
|
2018-12-17 00:39:29 +01:00
|
|
|
Result := Select<T>(lAR.GenerateSelectSQL + ' WHERE ' + SQLWhere, Params);
|
2018-09-27 12:26:50 +02:00
|
|
|
end;
|
2018-09-25 15:36:53 +02:00
|
|
|
finally
|
|
|
|
lAR.Free;
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
{ PrimaryKeyAttribute }
|
|
|
|
|
2019-03-05 20:55:37 +01:00
|
|
|
// constructor MVCPrimaryKeyAttribute.Create(const aFieldName: string);
|
|
|
|
// begin
|
|
|
|
// Create(aFieldName, []);
|
|
|
|
// end;
|
|
|
|
//
|
|
|
|
// constructor MVCPrimaryKeyAttribute.Create(const aFieldName: string; const aFieldOptions: TMVCActiveRecordFieldOptions);
|
|
|
|
// begin
|
|
|
|
// inherited Create;
|
|
|
|
// FieldName := aFieldName;
|
|
|
|
// FieldOptions := aFieldOptions;
|
|
|
|
// end;
|
2018-09-25 15:36:53 +02:00
|
|
|
|
|
|
|
{ TMVCEntitiesRegistry }
|
|
|
|
|
2019-03-05 20:55:37 +01:00
|
|
|
procedure TMVCEntitiesRegistry.AddEntity(const aURLSegment: string; const aActiveRecordClass: TMVCActiveRecordClass);
|
2018-09-25 15:36:53 +02:00
|
|
|
begin
|
|
|
|
fEntitiesDict.AddOrSetValue(aURLSegment.ToLower, aActiveRecordClass);
|
|
|
|
end;
|
|
|
|
|
2018-10-14 18:23:20 +02:00
|
|
|
procedure TMVCEntitiesRegistry.AddEntityProcessor(const aURLSegment: string;
|
|
|
|
const aEntityProcessor: IMVCEntityProcessor);
|
|
|
|
begin
|
|
|
|
fProcessorsDict.Add(aURLSegment, aEntityProcessor);
|
|
|
|
end;
|
|
|
|
|
2018-09-25 15:36:53 +02:00
|
|
|
constructor TMVCEntitiesRegistry.Create;
|
|
|
|
begin
|
|
|
|
inherited;
|
|
|
|
fEntitiesDict := TDictionary<string, TMVCActiveRecordClass>.Create;
|
2018-10-14 18:23:20 +02:00
|
|
|
fProcessorsDict := TDictionary<string, IMVCEntityProcessor>.Create;
|
2018-09-25 15:36:53 +02:00
|
|
|
end;
|
|
|
|
|
|
|
|
destructor TMVCEntitiesRegistry.Destroy;
|
|
|
|
begin
|
|
|
|
fEntitiesDict.Free;
|
2018-10-14 18:23:20 +02:00
|
|
|
fProcessorsDict.Free;
|
2018-09-25 15:36:53 +02:00
|
|
|
inherited;
|
|
|
|
end;
|
|
|
|
|
2019-03-05 20:55:37 +01:00
|
|
|
function TMVCEntitiesRegistry.FindEntityClassByURLSegment(const aURLSegment: string;
|
|
|
|
out aMVCActiveRecordClass: TMVCActiveRecordClass): Boolean;
|
2018-09-25 15:36:53 +02:00
|
|
|
begin
|
2019-03-05 20:55:37 +01:00
|
|
|
Result := fEntitiesDict.TryGetValue(aURLSegment.ToLower, aMVCActiveRecordClass);
|
2018-10-14 18:23:20 +02:00
|
|
|
end;
|
|
|
|
|
2019-03-05 20:55:37 +01:00
|
|
|
function TMVCEntitiesRegistry.FindProcessorByURLSegment(const aURLSegment: string;
|
|
|
|
out aMVCEntityProcessor: IMVCEntityProcessor): Boolean;
|
2018-10-14 18:23:20 +02:00
|
|
|
begin
|
2019-03-05 20:55:37 +01:00
|
|
|
Result := fProcessorsDict.TryGetValue(aURLSegment.ToLower, aMVCEntityProcessor);
|
2018-09-25 15:36:53 +02:00
|
|
|
end;
|
|
|
|
|
|
|
|
{ EMVCActiveRecord }
|
|
|
|
|
|
|
|
constructor EMVCActiveRecord.Create(const AMsg: string);
|
|
|
|
begin
|
|
|
|
inherited Create(http_status.BadRequest, AMsg);
|
|
|
|
end;
|
|
|
|
|
2018-10-14 18:23:20 +02:00
|
|
|
{ EntityActionsAttribute }
|
|
|
|
|
2019-03-05 20:55:37 +01:00
|
|
|
constructor MVCEntityActionsAttribute.Create(const aEntityAllowedActions: TMVCEntityActions);
|
2018-10-14 18:23:20 +02:00
|
|
|
begin
|
|
|
|
inherited Create;
|
|
|
|
EntityAllowedActions := aEntityAllowedActions;
|
|
|
|
end;
|
|
|
|
|
2018-10-23 16:18:34 +02:00
|
|
|
{ TMVCActiveRecordList }
|
|
|
|
|
|
|
|
constructor TMVCActiveRecordList.Create;
|
|
|
|
begin
|
|
|
|
inherited Create(True);
|
|
|
|
end;
|
|
|
|
|
2018-11-02 21:43:09 +01:00
|
|
|
{ TMVCSQLGeneratorRegistry }
|
|
|
|
|
|
|
|
constructor TMVCSQLGeneratorRegistry.Create;
|
|
|
|
begin
|
|
|
|
inherited;
|
|
|
|
fSQLGenerators := TDictionary<string, TMVCSQLGeneratorClass>.Create;
|
|
|
|
end;
|
|
|
|
|
|
|
|
class constructor TMVCSQLGeneratorRegistry.Create;
|
|
|
|
begin
|
2018-11-09 18:11:59 +01:00
|
|
|
cLock := TObject.Create;
|
2018-11-02 21:43:09 +01:00
|
|
|
end;
|
|
|
|
|
|
|
|
class destructor TMVCSQLGeneratorRegistry.Destroy;
|
|
|
|
begin
|
2018-11-09 18:11:59 +01:00
|
|
|
cLock.Free;
|
|
|
|
cInstance.Free;
|
2018-11-02 21:43:09 +01:00
|
|
|
end;
|
|
|
|
|
|
|
|
destructor TMVCSQLGeneratorRegistry.Destroy;
|
|
|
|
begin
|
|
|
|
fSQLGenerators.Free;
|
|
|
|
inherited;
|
|
|
|
end;
|
|
|
|
|
2019-03-05 20:55:37 +01:00
|
|
|
function TMVCSQLGeneratorRegistry.GetSQLGenerator(const aBackend: string): TMVCSQLGeneratorClass;
|
2018-11-02 21:43:09 +01:00
|
|
|
begin
|
|
|
|
if not fSQLGenerators.TryGetValue(aBackend, Result) then
|
|
|
|
begin
|
2019-03-05 20:55:37 +01:00
|
|
|
raise ERQLCompilerNotFound.CreateFmt('SQLGenerator not found for "%s"', [aBackend]);
|
2018-11-02 21:43:09 +01:00
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
2018-11-24 16:56:21 +01:00
|
|
|
class function TMVCSQLGeneratorRegistry.Instance: TMVCSQLGeneratorRegistry;
|
2018-11-02 21:43:09 +01:00
|
|
|
begin
|
2018-11-09 18:11:59 +01:00
|
|
|
if not Assigned(cInstance) then
|
2018-11-02 21:43:09 +01:00
|
|
|
begin
|
2018-11-09 18:11:59 +01:00
|
|
|
TMonitor.Enter(cLock);
|
2018-11-02 21:43:09 +01:00
|
|
|
try
|
2018-11-09 18:11:59 +01:00
|
|
|
if not Assigned(cInstance) then
|
2018-11-02 21:43:09 +01:00
|
|
|
begin
|
2018-11-09 18:11:59 +01:00
|
|
|
cInstance := TMVCSQLGeneratorRegistry.Create;
|
2018-11-02 21:43:09 +01:00
|
|
|
end;
|
|
|
|
finally
|
2018-11-09 18:11:59 +01:00
|
|
|
TMonitor.Exit(cLock);
|
2018-11-02 21:43:09 +01:00
|
|
|
end;
|
|
|
|
end;
|
2018-11-09 18:11:59 +01:00
|
|
|
Result := cInstance;
|
2018-11-02 21:43:09 +01:00
|
|
|
end;
|
|
|
|
|
|
|
|
procedure TMVCSQLGeneratorRegistry.RegisterSQLGenerator(const aBackend: string;
|
|
|
|
const aRQLBackendClass: TMVCSQLGeneratorClass);
|
|
|
|
begin
|
2018-11-24 16:56:21 +01:00
|
|
|
fSQLGenerators.AddOrSetValue(aBackend, aRQLBackendClass);
|
2018-11-02 21:43:09 +01:00
|
|
|
end;
|
|
|
|
|
2019-03-05 20:55:37 +01:00
|
|
|
procedure TMVCSQLGeneratorRegistry.UnRegisterSQLGenerator(const aBackend: string);
|
2018-11-02 21:43:09 +01:00
|
|
|
begin
|
|
|
|
fSQLGenerators.Remove(aBackend);
|
|
|
|
end;
|
|
|
|
|
|
|
|
{ TMVCSQLGenerator }
|
|
|
|
|
|
|
|
constructor TMVCSQLGenerator.Create(Mapping: TMVCFieldsMapping);
|
|
|
|
begin
|
|
|
|
inherited Create;
|
|
|
|
fMapping := Mapping;
|
|
|
|
end;
|
|
|
|
|
|
|
|
function TMVCSQLGenerator.GetMapping: TMVCFieldsMapping;
|
|
|
|
begin
|
|
|
|
Result := fMapping;
|
|
|
|
end;
|
|
|
|
|
|
|
|
destructor TMVCSQLGenerator.Destroy;
|
|
|
|
begin
|
|
|
|
fCompiler.Free;
|
|
|
|
fRQL2SQL.Free;
|
|
|
|
inherited;
|
|
|
|
end;
|
|
|
|
|
|
|
|
function TMVCSQLGenerator.GetCompiler: TRQLCompiler;
|
|
|
|
begin
|
|
|
|
if fCompiler = nil then
|
|
|
|
begin
|
|
|
|
fCompiler := GetCompilerClass.Create(fMapping);
|
|
|
|
end;
|
|
|
|
Result := fCompiler;
|
|
|
|
end;
|
|
|
|
|
|
|
|
function TMVCSQLGenerator.GetRQLParser: TRQL2SQL;
|
|
|
|
begin
|
|
|
|
if fRQL2SQL = nil then
|
|
|
|
begin
|
2019-02-25 12:48:36 +01:00
|
|
|
fRQL2SQL := TRQL2SQL.Create;//(20);
|
2018-11-02 21:43:09 +01:00
|
|
|
end;
|
|
|
|
Result := fRQL2SQL;
|
|
|
|
end;
|
|
|
|
|
2019-03-05 20:55:37 +01:00
|
|
|
function TMVCSQLGenerator.TableFieldsDelimited(const Map: TDictionary<TRttiField, string>; const PKFieldName: string;
|
2018-11-02 21:43:09 +01:00
|
|
|
const Delimiter: string): string;
|
|
|
|
var
|
|
|
|
lPair: TPair<TRttiField, string>;
|
|
|
|
begin
|
|
|
|
for lPair in Map do
|
|
|
|
begin
|
|
|
|
Result := Result + lPair.value + Delimiter;
|
|
|
|
end;
|
|
|
|
Result := Copy(Result, 1, Length(Result) - Length(Delimiter));
|
|
|
|
if not PKFieldName.IsEmpty then
|
|
|
|
begin
|
|
|
|
Result := PKFieldName + ',' + Result;
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
2018-11-09 18:11:59 +01:00
|
|
|
{ TMVCConnectionsRepository.TConnHolder }
|
|
|
|
|
|
|
|
destructor TMVCConnectionsRepository.TConnHolder.Destroy;
|
|
|
|
begin
|
|
|
|
if OwnsConnection then
|
2019-03-18 14:08:34 +01:00
|
|
|
Begin
|
|
|
|
if Connection.connected then
|
|
|
|
Connection.connected := False;
|
2018-11-09 18:11:59 +01:00
|
|
|
FreeAndNil(Connection);
|
2019-03-18 14:08:34 +01:00
|
|
|
End;
|
2018-11-09 18:11:59 +01:00
|
|
|
inherited;
|
|
|
|
end;
|
|
|
|
|
2019-03-05 20:55:37 +01:00
|
|
|
constructor MVCTableFieldAttribute.Create(const aFieldName: string; const aFieldOptions: TMVCActiveRecordFieldOptions);
|
2019-02-15 12:21:11 +01:00
|
|
|
begin
|
|
|
|
inherited Create;
|
|
|
|
FieldName := aFieldName;
|
|
|
|
FieldOptions := aFieldOptions;
|
|
|
|
end;
|
|
|
|
|
2018-09-25 15:36:53 +02:00
|
|
|
initialization
|
|
|
|
|
|
|
|
gLock := TObject.Create;
|
2018-10-14 18:23:20 +02:00
|
|
|
gCtx := TRttiContext.Create;
|
|
|
|
gCtx.FindType('');
|
2018-09-25 15:36:53 +02:00
|
|
|
|
|
|
|
finalization
|
|
|
|
|
2018-10-14 18:23:20 +02:00
|
|
|
gCtx.Free;
|
2018-09-25 15:36:53 +02:00
|
|
|
gLock.Free;
|
|
|
|
|
|
|
|
end.
|