2018-09-25 15:36:53 +02:00
// *************************************************************************** }
//
// Delphi MVC Framework
//
2023-01-17 08:52:26 +01:00
// Copyright (c) 2010-2023 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;
2019-08-05 12:55:44 +02:00
{$I dmvcframework.inc}
2018-09-25 15:36:53 +02:00
interface
uses
2023-08-30 11:58:50 +02:00
System. SysUtils,
2018-09-25 15:36:53 +02:00
System. Generics. Defaults,
System. Generics. Collections,
System. RTTI,
FireDAC. DApt,
Data. DB,
2018-10-14 18:23:20 +02:00
FireDAC. Comp . Client,
2019-09-30 00:05:46 +02:00
FireDAC. Stan. Def,
FireDAC. Stan. Pool,
FireDAC. Stan. Async,
2018-11-21 22:11:58 +01:00
FireDAC. Stan. Param,
2019-02-21 20:17:11 +01:00
MVCFramework,
MVCFramework. Commons,
MVCFramework. RQL. Parser,
2021-11-18 17:52:06 +01:00
MVCFramework. Cache,
2020-03-25 11:35:25 +01:00
MVCFramework. Serializer. Intf,
2022-11-17 19:33:27 +01:00
MVCFramework. Serializer. Commons, System. SyncObjs, System. TypInfo;
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)
2020-01-04 12:53:53 +01:00
public
procedure AfterConstruction; override ;
2019-01-18 18:11:27 +01:00
end ;
2018-09-25 15:36:53 +02:00
TMVCActiveRecordClass = class of TMVCActiveRecord;
2021-04-29 22:52:28 +02:00
TMVCActiveRecord = class ;
2020-08-28 18:04:29 +02:00
TMVCActiveRecordFieldOption = ( foPrimaryKey, { it's the primary key of the mapped table }
2020-08-11 00:54:42 +02:00
foAutoGenerated, { not written, read - similar to readonly }
foReadOnly, { not written, read }
foWriteOnly) ; { written, not read }
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
2021-11-18 17:52:06 +01:00
TPartitionFieldNames = class( TList< String > )
end ;
2022-08-02 17:07:14 +02:00
2021-11-18 17:52:06 +01:00
TPartitionFieldValues = class( TList< String > )
end ;
2022-08-02 17:07:14 +02:00
2021-11-18 17:52:06 +01:00
TPartitionFieldTypes = class( TList< TFieldType> )
end ;
2021-11-18 00:49:12 +01:00
2018-10-14 18:23:20 +02:00
IMVCEntityProcessor = interface
[ '{E7CD11E6-9FF9-46D2-B7B0-DA5B38EAA14E}' ]
2022-08-02 17:07:14 +02:00
procedure GetEntities( const Context: TWebContext; const Renderer: TMVCRenderer; const entityname: string ;
2018-10-14 18:23:20 +02:00
var Handled: Boolean ) ;
2022-08-02 17:07:14 +02:00
procedure GetEntity( const Context: TWebContext; const Renderer: TMVCRenderer; const entityname: string ;
2019-03-05 20:55:37 +01:00
const id: Integer ; var Handled: Boolean ) ;
2022-08-02 17:07:14 +02:00
procedure CreateEntity( const Context: TWebContext; const Renderer: TMVCRenderer; const entityname: string ;
2018-10-14 18:23:20 +02:00
var Handled: Boolean ) ;
2022-08-02 17:07:14 +02:00
procedure UpdateEntity( const Context: TWebContext; const Renderer: TMVCRenderer; const entityname: string ;
2019-03-05 20:55:37 +01:00
const id: Integer ; var Handled: Boolean ) ;
2022-08-02 17:07:14 +02:00
procedure DeleteEntity( const Context: TWebContext; const Renderer: TMVCRenderer; const entityname: string ;
2019-03-05 20:55:37 +01:00
const id: Integer ; var Handled: Boolean ) ;
2018-10-14 18:23:20 +02:00
end ;
2018-09-25 15:36:53 +02:00
2020-03-25 11:35:25 +01:00
TFieldInfo = class
public
2020-08-11 00:54:42 +02:00
// TableName: string;
2020-03-25 11:35:25 +01:00
FieldName: string ;
FieldOptions: TMVCActiveRecordFieldOptions;
DataTypeName: string ;
Writeable, Readable: Boolean ;
procedure EndUpdates;
end ;
2023-08-09 10:55:34 +02:00
TSQLQueryWithName = record
2023-08-09 01:23:24 +02:00
Name : String ;
2023-08-09 10:55:34 +02:00
SQLText: String ;
BackEnd: String ; //TMVCActiveRecordBackEnd
2023-08-09 01:23:24 +02:00
end ;
2023-08-09 10:55:34 +02:00
TRQLQueryWithName = record
Name : String ;
RQLText: String ;
end ;
2020-03-25 11:35:25 +01:00
TFieldsMap = class( TObjectDictionary< TRTTIField, TFieldInfo> )
private
2020-08-11 00:54:42 +02:00
fWritableFieldsCount: Integer ;
fReadableFieldsCount: Integer ;
2020-03-25 11:35:25 +01:00
public
constructor Create;
procedure EndUpdates;
2020-08-11 00:54:42 +02:00
property WritableFieldsCount: Integer read fWritableFieldsCount;
2020-09-30 11:16:10 +02:00
property ReadableFieldsCount: Integer read fReadableFieldsCount;
2020-03-25 11:35:25 +01:00
function GetInfoByFieldName( const FieldName: string ) : TFieldInfo;
end ;
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 ;
2021-11-17 15:36:20 +01:00
RQLFilter: string ;
constructor Create( aName: string ) ; overload ;
constructor Create( aName: string ; aRQLFilter: String ) ; overload ;
2018-09-25 15:36:53 +02:00
end ;
2021-11-18 00:49:12 +01:00
MVCPartitionAttribute = class( MVCActiveRecordCustomAttribute)
public
PartitionClause: String ;
constructor Create( const PartitionClause: String ) ;
end ;
2023-08-09 01:23:24 +02:00
MVCNamedSQLQueryAttribute = class( MVCActiveRecordCustomAttribute)
public
Name : string ;
SQLQuery: String ;
2023-08-09 10:55:34 +02:00
Backend: String ; //TMVCActiveRecordBackEnd
constructor Create( aName: string ; aSQLSelect: String ) ; overload ;
constructor Create( aName: string ; aSQLSelect: String ; aBackEnd: String ) ; overload ;
end ;
MVCNamedRQLQueryAttribute = class( MVCActiveRecordCustomAttribute)
public
Name : string ;
RQLQuery: String ;
constructor Create( aName: string ; aRQL: String ) ;
2023-08-09 01:23:24 +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;
2020-03-12 20:37:48 +01:00
SequenceName, DataTypeName: string ;
2020-01-08 15:30:10 +01:00
constructor Create( const aFieldName: string ; const aFieldOptions: TMVCActiveRecordFieldOptions;
2020-03-23 18:51:57 +01:00
const aSequenceName: string = '' ; const aDataTypeName: string = '' ) ; overload ;
constructor Create( aFieldName: string ; const aDataTypeName: 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-11-02 21:43:09 +01:00
TMVCSQLGenerator = class ;
2021-11-18 00:49:12 +01:00
TPartitionInfo = class
2021-11-18 17:52:06 +01:00
private
class
var PartitionInfoCache: TMVCThreadedObjectCache< TPartitionInfo> ;
private
fRQLFilter: String ;
fSQLFilter: String ;
fFieldValues: TPartitionFieldValues;
fFieldTypes: TPartitionFieldTypes;
fFieldNames: TPartitionFieldNames;
public
property FieldNames: TPartitionFieldNames read fFieldNames;
property FieldValues: TPartitionFieldValues read fFieldValues;
property FieldTypes: TPartitionFieldTypes read fFieldTypes;
property RQLFilter: String read fRQLFilter;
property SQLFilter: String read fSQLFilter;
2021-11-18 00:49:12 +01:00
constructor Create;
2021-11-18 17:52:06 +01:00
destructor Destroy; override ;
class constructor Create;
class destructor Destroy;
procedure InitializeFilterStrings( const RQLCompiler: TRQLCompiler) ;
class function BuildPartitionClause( const PartitionClause: String ; const RQLCompilerClass: TRQLCompilerClass) : TPartitionInfo;
2021-11-18 00:49:12 +01:00
end ;
2018-10-23 16:18:34 +02:00
TMVCActiveRecordList = class( TObjectList< TMVCActiveRecord> )
public
constructor Create; virtual ;
end ;
2022-11-17 19:33:27 +01:00
TMVCTableMap = class
protected
fPartitionInfoInternal: TPartitionInfo;
fEntityAllowedActions: TMVCEntityActions;
fTableName: string ;
fPartitionClause: String ;
fRTTIType: TRttiInstanceType;
fObjAttributes: TArray< TCustomAttribute> ;
fDefaultRQLFilter: string ;
fMap: TFieldsMap;
fPrimaryKey: TRTTIField;
fMapping: TMVCFieldsMapping;
fPropsAttributes: TArray< TCustomAttribute> ;
fProps: TArray< TRTTIField> ;
fPrimaryKeyFieldName: string ;
fPrimaryKeyOptions: TMVCActiveRecordFieldOptions;
fPrimaryKeySequenceName: string ;
fPrimaryKeyFieldType: TFieldType;
2023-08-09 10:55:34 +02:00
fNamedSQLQueries: TArray< TSQLQueryWithName> ;
fNamedRQLQueries: TArray< TRQLQueryWithName> ;
2022-11-17 19:33:27 +01:00
public
constructor Create;
destructor Destroy; override ;
end ;
2018-09-25 15:36:53 +02:00
TMVCActiveRecord = class
private
2020-05-04 09:42:16 +02:00
fChildren: TObjectList< TObject> ;
2018-09-25 15:36:53 +02:00
fConn: TFDConnection;
2018-11-02 21:43:09 +01:00
fSQLGenerator: TMVCSQLGenerator;
fRQL2SQL: TRQL2SQL;
2022-11-17 19:33:27 +01:00
fCustomTableName: String ;
2020-01-06 16:49:18 +01:00
procedure MapTValueToParam( aValue: TValue; const aParam: TFDParam) ;
2020-08-28 18:04:29 +02:00
function MapNullableTValueToParam( aValue: TValue; const aParam: TFDParam) : Boolean ;
2020-01-06 16:49:18 +01:00
function GetPrimaryKeyIsAutogenerated: Boolean ;
procedure SetPrimaryKeyIsAutogenerated( const Value: Boolean ) ;
2020-08-11 00:54:42 +02:00
procedure SetTableName( const Value: string ) ;
2020-08-28 18:04:29 +02:00
function GetAttributes( const AttrName: string ) : TValue;
procedure SetAttributes( const AttrName: string ; const Value: TValue) ;
2022-11-17 19:33:27 +01:00
function GetTableName: string ;
2018-09-25 15:36:53 +02:00
protected
2018-11-02 21:43:09 +01:00
fBackendDriver: string ;
2022-11-17 19:33:27 +01:00
fTableMap: TMVCTableMap;
2021-11-18 17:52:06 +01:00
function GetPartitionInfo: TPartitionInfo;
2020-02-03 10:51:40 +01:00
function GetConnection: TFDConnection;
2018-09-25 15:36:53 +02:00
procedure InitTableInfo;
2022-06-19 18:57:47 +02:00
class function ExecQuery(
const SQL: string ;
const Values: array of Variant ;
2023-01-02 22:09:42 +01:00
const Unidirectional: Boolean ;
const DirectExecute: Boolean ) : TDataSet; overload ;
2022-06-19 18:57:47 +02:00
class function ExecQuery(
const SQL: string ;
const Values: array of Variant ;
2023-01-02 22:09:42 +01:00
const Connection: TFDConnection;
const Unidirectional: Boolean ;
const DirectExecute: Boolean )
2018-11-24 16:56:21 +01:00
: TDataSet; overload ;
2023-01-02 22:09:42 +01:00
class function ExecQuery(
const SQL: string ;
const Values: array of Variant ;
const ValueTypes: array of TFieldType;
const Unidirectional: Boolean ;
const DirectExecute: Boolean )
2020-08-28 18:04:29 +02:00
: TDataSet; overload ;
2022-06-19 18:57:47 +02:00
class function ExecQuery(
const SQL: string ;
const Values: array of Variant ;
2021-05-03 19:29:01 +02:00
const ValueTypes: array of TFieldType;
2022-06-19 18:57:47 +02:00
const Connection: TFDConnection;
2023-01-02 22:09:42 +01:00
const Unidirectional: Boolean ;
const DirectExecute: Boolean ) : TDataSet; overload ;
2020-03-23 18:51:57 +01:00
procedure FillPrimaryKey( const SequenceName: string ) ;
2023-08-09 00:46:31 +02:00
function ExecNonQuery( const SQL: string ; RefreshAutoGenerated: Boolean = false ) : Int64 ;
2021-05-03 19:29:01 +02:00
overload ;
2022-05-13 17:23:00 +02:00
class function GetByPK( aActiveRecord: TMVCActiveRecord; const aValue: string ; const aFieldType: TFieldType;
const RaiseExceptionIfNotFound: Boolean ) : TMVCActiveRecord; overload ;
2023-01-29 17:29:24 +01:00
2018-09-25 15:36:53 +02:00
// load events
/// <summary>
/// Called everywhere before persist object into database
/// </summary>
2020-03-30 13:30:45 +02:00
procedure OnValidation( const EntityAction: TMVCEntityAction) ; virtual ;
2018-09-25 15:36:53 +02:00
/// <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>
2020-03-23 18:51:57 +01:00
procedure OnBeforeExecuteSQL( var SQL: string ) ; virtual ;
2019-03-18 14:08:34 +01:00
2018-10-23 16:18:34 +02:00
/// <summary>
/// Called after insert or update the object to the database
/// </summary>
procedure OnAfterInsertOrUpdate; virtual ;
2019-11-27 19:04:06 +01:00
procedure MapObjectToParams( const Params: TFDParams; var Handled: Boolean ) ; virtual ;
2020-01-04 12:53:53 +01:00
procedure MapDatasetToObject( const DataSet: TDataSet; const Options: TMVCActiveRecordLoadOptions;
var Handled: Boolean ) ; virtual ;
2019-11-27 19:04:06 +01:00
2018-11-02 21:43:09 +01:00
function GenerateSelectSQL: string ;
2018-11-24 16:56:21 +01:00
function SQLGenerator: TMVCSQLGenerator;
2023-08-09 00:46:31 +02:00
function InternalCount( const RQL: string ) : Int64 ;
2021-05-03 19:29:01 +02:00
function InternalSelectRQL( const RQL: string ; const MaxRecordCount: Integer )
2023-08-08 14:31:23 +02:00
: TMVCActiveRecordList; overload ;
function InternalSelectRQL( const RQL: string ; const MaxRecordCount: Integer ;
const OutList: TMVCActiveRecordList) : UInt32 ; overload ;
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 ;
2020-02-03 10:51:40 +01:00
procedure EnsureConnection;
2022-08-28 13:06:16 +02:00
procedure Assign( ActiveRecord: TMVCActiveRecord) ; virtual ;
2020-03-30 13:30:45 +02:00
procedure InvalidateConnection( const ReacquireAfterInvalidate: Boolean = false ) ;
2023-08-09 10:55:34 +02:00
function GetBackEnd: string ;
2020-02-03 13:19:55 +01:00
/// <summary>
2020-04-08 18:04:45 +02:00
/// Executes an Insert (pk is null) or an Update (pk is not null)
2020-02-03 13:19:55 +01:00
/// </summary>
procedure Store;
2023-01-29 17:29:24 +01:00
/// <summary>
/// Reload the current instance from database if the primary key is not empty.
/// </summary>
procedure Refresh; virtual ;
2021-05-03 19:29:01 +02: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;
2023-08-09 00:46:31 +02:00
function LoadByPK( const id: Int64 ) : Boolean ; overload ; virtual ;
2020-03-31 16:23:22 +02:00
function LoadByPK( const id: string ) : Boolean ; overload ; virtual ;
2022-05-13 17:23:00 +02:00
function LoadByPK( const id: TGuid) : Boolean ; overload ; virtual ;
function LoadByPK( const id: string ; const aFieldType: TFieldType) : Boolean ; overload ; virtual ;
2021-06-27 15:14:37 +02:00
procedure Update( const RaiseExceptionIfNotFound: Boolean = True ) ;
procedure Delete( const RaiseExceptionIfNotFound: Boolean = True ) ;
2018-09-25 15:36:53 +02:00
function TableInfo: string ;
2021-05-03 19:29:01 +02:00
procedure LoadByDataset( const aDataSet: TDataSet;
const aOptions: TMVCActiveRecordLoadOptions = [ ] ) ;
2018-09-25 15:36:53 +02:00
procedure SetPK( const aValue: TValue) ;
2020-08-28 18:04:29 +02:00
procedure SetPropertyValue( const aProp: TRttiProperty; const aValue: TValue) ;
2018-09-25 15:36:53 +02:00
function GetPK: TValue;
2020-02-03 13:19:55 +01:00
function TryGetPKValue( var Value: TValue; out IsNullableType: Boolean ) : Boolean ;
2021-04-25 22:40:06 +02:00
function PKIsNullable( out PKValue: TValue) : Boolean ;
function PKIsNull: Boolean ;
2020-05-04 09:42:16 +02:00
procedure AddChildren( const ChildObject: TObject) ;
procedure RemoveChildren( const ChildObject: TObject) ;
2021-05-03 19:29:01 +02:00
function GetPrimaryKeyFieldType: TFieldType;
2023-08-09 00:46:31 +02:00
2023-08-09 10:55:34 +02:00
function FindSQLQueryByName( const QueryName: String ; out NamedSQLQuery: TSQLQueryWithName) : Boolean ;
function FindRQLQueryByName( const QueryName: String ; out NamedRQLQuery: TRQLQueryWithName) : Boolean ;
2023-08-09 01:23:24 +02:00
2023-08-09 00:46:31 +02:00
property Attributes[ const AttrName: string ] : TValue
read GetAttributes
write SetAttributes;
2020-07-18 20:14:58 +02:00
[ MVCDoNotSerialize]
2023-08-09 00:46:31 +02:00
property TableName: string
read GetTableName
write SetTableName;
2020-02-03 10:51:40 +01:00
[ MVCDoNotSerialize]
2023-08-09 00:46:31 +02:00
property PrimaryKeyIsAutogenerated: Boolean
read GetPrimaryKeyIsAutogenerated
2021-05-03 19:29:01 +02:00
write SetPrimaryKeyIsAutogenerated;
2023-08-09 00:46:31 +02:00
2019-03-05 20:55:37 +01:00
class function GetScalar( const SQL: string ; const Params: array of Variant ) : Variant ;
2018-10-23 16:18:34 +02:00
class function CurrentConnection: TFDConnection;
2018-09-25 15:36:53 +02:00
end ;
2021-07-16 23:55:33 +02:00
IMVCUnitOfWork< T: TMVCActiveRecord> = interface
[ '{68B55DD3-57F6-4CC0-A4DE-BFDE7C3AA287}' ]
procedure RegisterDelete( const Value: T) ; overload ;
procedure RegisterDelete( const Enumerable: TEnumerable< T> ) ; overload ;
procedure RegisterUpdate( const Value: T) ;
procedure RegisterInsert( const Value: T) ;
procedure UnregisterDelete( const Value: T) ;
procedure UnregisterUpdate( const Value: T) ;
procedure UnregisterInsert( const Value: T) ;
end ;
TMVCItemApplyAction< T: TMVCActiveRecord> = reference to procedure( const Obj: T;
const EntityAction: TMVCEntityAction; var Handled: Boolean ) ;
2022-08-28 13:06:16 +02:00
TMergeModeItem = ( mmInsert, mmUpdate, mmDelete) ;
TMergeMode = set of TMergeModeItem;
2021-07-16 23:55:33 +02:00
IMVCMultiExecutor< T: TMVCActiveRecord> = interface
[ '{C815246B-19CA-4F6C-AA67-8E491F809340}' ]
procedure Apply( const ItemApplyAction: TMVCItemApplyAction< T> = nil ) ;
end ;
2019-08-02 12:32:23 +02:00
TMVCActiveRecordHelper = class helper for TMVCActiveRecord
2023-08-09 00:46:31 +02:00
{ GetByPK }
class function GetByPK( const aClass: TMVCActiveRecordClass; const aValue: Int64 ;
const RaiseExceptionIfNotFound: Boolean = True ) : TMVCActiveRecord; overload ;
class function GetByPK( const aClass: TMVCActiveRecordClass; const aValue: string ;
const RaiseExceptionIfNotFound: Boolean = True ) : TMVCActiveRecord; overload ;
class function GetByPK( const aClass: TMVCActiveRecordClass; const aValue: TGuid;
const RaiseExceptionIfNotFound: Boolean = True ) : TMVCActiveRecord; overload ;
2022-07-15 20:42:29 +02:00
class function GetByPK< T: TMVCActiveRecord, constructor > ( const aValue: string ; const aFieldType: TFieldType;
const RaiseExceptionIfNotFound: Boolean ) : T; overload ;
2023-08-09 00:46:31 +02:00
class function GetByPK< T: TMVCActiveRecord, constructor > ( const aValue: Int64 ;
2020-01-04 12:53:53 +01:00
const RaiseExceptionIfNotFound: Boolean = True ) : T; overload ;
2020-03-31 16:23:22 +02:00
class function GetByPK< T: TMVCActiveRecord, constructor > ( const aValue: string ;
const RaiseExceptionIfNotFound: Boolean = True ) : T; overload ;
2022-05-13 17:23:00 +02:00
class function GetByPK< T: TMVCActiveRecord, constructor > ( const aValue: TGuid;
const RaiseExceptionIfNotFound: Boolean = True ) : T; overload ;
2023-08-09 00:46:31 +02:00
{ Select }
2023-08-08 14:31:23 +02:00
/// <summary>
/// Returns a TObjectList<TMVCActiveRecord> from a SQL using variant params
/// </summary>
2021-05-03 19:29:01 +02:00
class function Select< T: TMVCActiveRecord, constructor > ( const SQL: string ;
const Params: array of Variant ;
2019-08-02 12:32:23 +02:00
const Options: TMVCActiveRecordLoadOptions = [ ] ) : TObjectList< T> ; overload ;
2023-08-08 14:31:23 +02:00
/// <summary>
/// Returns a TObjectList<TMVCActiveRecord> from a SQL using typed params
/// </summary>
2021-05-03 19:29:01 +02:00
class function Select< T: TMVCActiveRecord, constructor > ( const SQL: string ;
const Params: array of Variant ;
const ParamTypes: array of TFieldType; const Options: TMVCActiveRecordLoadOptions = [ ] )
: TObjectList< T> ; overload ;
2023-08-08 14:31:23 +02:00
/// <summary>
/// Fills a TObjectList<TMVCActiveRecord> from a SQL using typed params.
/// Returns number of the records in the list (not only the selected records, but the current .Count of the list)
/// </summary>
class function Select< T: TMVCActiveRecord, constructor > ( const SQL: string ; const Params: array of Variant ;
const ParamTypes: array of TFieldType; const Options: TMVCActiveRecordLoadOptions;
const OutList: TObjectList< T> ) : UInt32 ; overload ;
2023-08-09 00:46:31 +02:00
class function Select( const aClass: TMVCActiveRecordClass; const SQL: string ;
const Params: array of Variant )
: TMVCActiveRecordList; overload ;
class function Select( const aClass: TMVCActiveRecordClass; const SQL: string ;
const Params: array of Variant ;
const Connection: TFDConnection) : TMVCActiveRecordList; overload ;
class function Select( const aClass: TMVCActiveRecordClass; const SQL: string ;
const Params: array of Variant ;
const Connection: TFDConnection; const OutList: TMVCActiveRecordList) : UInt32 ; overload ;
{ SelectOne }
2021-05-03 19:29:01 +02:00
class function SelectOne< T: TMVCActiveRecord, constructor > ( const SQL: string ;
const Params: array of Variant ;
2020-08-28 18:04:29 +02:00
const ParamTypes: array of TFieldType; const Options: TMVCActiveRecordLoadOptions = [ ] ;
2020-03-30 13:30:45 +02:00
const RaiseExceptionIfNotFound: Boolean = True ) : T; overload ;
2021-05-03 19:29:01 +02:00
class function SelectOne< T: TMVCActiveRecord, constructor > ( const SQL: string ;
const Params: array of Variant ;
2020-03-30 13:30:45 +02:00
const RaiseExceptionIfNotFound: Boolean = True ) : T; overload ;
2023-08-09 00:46:31 +02:00
{ SelectRQL }
function SelectRQL( const RQL: string ; const MaxRecordCount: Integer )
: TMVCActiveRecordList; overload ;
2021-05-03 19:29:01 +02:00
class function SelectRQL< T: constructor , TMVCActiveRecord> ( const RQL: string ;
const MaxRecordCount: Integer )
2019-08-02 12:32:23 +02:00
: TObjectList< T> ; overload ;
2023-08-08 14:31:23 +02:00
class function SelectRQL< T: constructor , TMVCActiveRecord> ( const RQL: string ;
const MaxRecordCount: Integer ; const OutList: TObjectList< T> ) : UInt32 ; overload ;
2020-08-28 18:04:29 +02:00
class function SelectOneByRQL< T: constructor , TMVCActiveRecord> ( const RQL: string ;
2021-11-19 00:34:37 +01:00
const RaiseExceptionIfNotFound: Boolean = True ) : T; overload ;
2023-08-09 00:46:31 +02:00
class function SelectRQL( const aClass: TMVCActiveRecordClass; const RQL: string ;
const MaxRecordCount: Integer )
: TMVCActiveRecordList; overload ;
class function SelectRQL( const aClass: TMVCActiveRecordClass; const RQL: string ;
const MaxRecordCount: Integer ; const OutList: TMVCActiveRecordList) : UInt32 ; overload ;
{ Misc }
2019-08-02 12:32:23 +02:00
class function All< T: TMVCActiveRecord, constructor > : TObjectList< T> ; overload ;
2023-08-09 00:46:31 +02:00
class function DeleteRQL< T: TMVCActiveRecord> ( const RQL: string = '' ) : Int64 ; overload ;
class function Count< T: TMVCActiveRecord> ( const RQL: string = '' ) : Int64 ; overload ;
{ Where }
2021-05-03 19:29:01 +02:00
class function Where< T: TMVCActiveRecord, constructor > ( const SQLWhere: string ;
const Params: array of Variant )
2020-08-28 18:04:29 +02:00
: TObjectList< T> ; overload ;
2021-11-19 00:34:37 +01:00
/// <summary>
/// Executes a SQL select using the SQLWhere parameter as where clause. This method is partitioning safe.
/// Returns TObjectList<EntityType>.
/// </summary>
2021-05-03 19:29:01 +02:00
class function Where< T: TMVCActiveRecord, constructor > ( const SQLWhere: string ;
const Params: array of Variant ;
2020-03-23 18:51:57 +01:00
const ParamTypes: array of TFieldType) : TObjectList< T> ; overload ;
2023-08-08 14:31:23 +02:00
class function Where< T: TMVCActiveRecord, constructor > ( const SQLWhere: string ;
const Params: array of Variant ;
const ParamTypes: array of TFieldType;
const OutList: TObjectList< T> ) : UInt32 ; overload ;
2023-08-09 00:46:31 +02:00
class function Where(
const aClass: TMVCActiveRecordClass;
const SQLWhere: string ;
const Params: array of Variant )
: TMVCActiveRecordList; overload ;
class function Where(
const aClass: TMVCActiveRecordClass;
const SQLWhere: string ;
const Params: array of Variant ;
const Connection: TFDConnection) : TMVCActiveRecordList; overload ;
class function Where(
const aClass: TMVCActiveRecordClass;
const SQLWhere: string ;
const Params: array of Variant ;
const Connection: TFDConnection;
const OutList: TMVCActiveRecordList) : UInt32 ; overload ;
{ GetXXXByWhere }
2019-08-02 12:32:23 +02:00
class function GetOneByWhere< T: TMVCActiveRecord, constructor > ( const SQLWhere: string ;
2020-08-28 18:04:29 +02:00
const Params: array of Variant ; const RaiseExceptionIfNotFound: Boolean = True ) : T; overload ;
class function GetOneByWhere< T: TMVCActiveRecord, constructor > ( const SQLWhere: string ;
const Params: array of Variant ; const ParamTypes: array of TFieldType;
2020-03-23 18:51:57 +01:00
const RaiseExceptionIfNotFound: Boolean = True ) : T; overload ;
2020-08-28 18:04:29 +02:00
class function GetFirstByWhere< T: TMVCActiveRecord, constructor > ( const SQLWhere: string ;
const Params: array of Variant ; const RaiseExceptionIfNotFound: Boolean = True ) : T; overload ;
class function GetFirstByWhere< T: TMVCActiveRecord, constructor > ( const SQLWhere: string ;
const Params: array of Variant ; const ParamTypes: array of TFieldType;
2020-03-23 18:51:57 +01:00
const RaiseExceptionIfNotFound: Boolean = True ) : T; overload ;
2023-08-09 00:46:31 +02:00
{ Merge }
2021-04-29 22:52:28 +02:00
class function Merge< T: TMVCActiveRecord> ( CurrentList,
2022-08-28 13:06:16 +02:00
NewList: TObjectList< T> ; const MergeMode: TMergeMode = [ mmInsert, mmUpdate, mmDelete] ) : IMVCMultiExecutor< T> ;
2023-08-09 00:46:31 +02:00
{ Misc }
class function All( const aClass: TMVCActiveRecordClass) : TObjectList< TMVCActiveRecord> ;
overload ;
2023-08-30 11:58:50 +02:00
class function All( const aQualifiedClassName: String ) : TObjectList< TMVCActiveRecord> ;
overload ;
2023-08-09 00:46:31 +02:00
class function DeleteAll( const aClass: TMVCActiveRecordClass) : Int64 ; overload ;
class function DeleteRQL( const aClass: TMVCActiveRecordClass; const RQL: string ) : Int64 ; overload ;
function Count( const RQL: string = '' ) : Int64 ; overload ;
class function Count( const aClass: TMVCActiveRecordClass; const RQL: string = '' )
: int64 ; overload ;
{ SelectDataSet }
class function SelectDataSet( const SQL: string ; const Params: array of Variant ;
const Unidirectional: Boolean = False ;
const DirectExecute: Boolean = False ) : TDataSet; overload ;
class function SelectDataSet( const SQL: string ; const Params: array of Variant ;
const ParamTypes: array of TFieldType;
const Unidirectional: Boolean = False ;
const DirectExecute: Boolean = False ) : TDataSet; overload ;
2023-08-09 01:23:24 +02:00
{ NamedQuery}
class function SelectByNamedQuery< T: TMVCActiveRecord, constructor > (
const QueryName: String ;
const Params: array of Variant ;
const ParamTypes: array of TFieldType;
const Options: TMVCActiveRecordLoadOptions = [ ] ) : TObjectList< T> ;
2023-08-09 10:55:34 +02:00
class function SelectRQLByNamedQuery< T: constructor , TMVCActiveRecord> (
const QueryName: String ;
const Params: array of const ;
const MaxRecordCount: Integer ) : TObjectList< T> ;
class function DeleteRQLByNamedQuery< T: TMVCActiveRecord, constructor > (
const QueryName: String ;
const Params: array of const ) : Int64 ;
class function CountRQLByNamedQuery< T: TMVCActiveRecord, constructor > (
const QueryName: string ;
const Params: array of const ) : Int64 ;
2023-08-30 11:58:50 +02:00
{ RTTI }
class function CreateMVCActiveRecord< T: TMVCActiveRecord> ( AQualifiedClassName: string ; const AParams: TArray< TValue> = nil ) : T;
2019-08-02 12:32:23 +02:00
end ;
2018-09-25 15:36:53 +02:00
IMVCEntitiesRegistry = interface
[ '{BB227BEB-A74A-4637-8897-B13BA938C07B}' ]
2019-03-05 20:55:37 +01:00
procedure AddEntity( const aURLSegment: string ; const aActiveRecordClass: TMVCActiveRecordClass) ;
2021-05-03 19:29:01 +02:00
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 ;
2021-05-03 19:29:01 +02:00
function FindProcessorByURLSegment( const aURLSegment: string ;
out aMVCEntityProcessor: IMVCEntityProcessor)
2019-03-05 20:55:37 +01:00
: Boolean ;
2021-04-07 23:58:02 +02:00
function GetEntities: TArray< String > ;
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
2022-08-02 17:07:14 +02:00
procedure AddEntityProcessor( const aURLSegment: string ; const aEntityProcessor: IMVCEntityProcessor) ;
2019-03-05 20:55:37 +01:00
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 ;
2022-08-02 17:07:14 +02:00
function FindProcessorByURLSegment( const aURLSegment: string ; out aMVCEntityProcessor: IMVCEntityProcessor)
2019-03-05 20:55:37 +01:00
: Boolean ;
2021-04-07 23:58:02 +02:00
function GetEntities: TArray< String > ;
2018-09-25 15:36:53 +02:00
end ;
2022-11-17 19:33:27 +01:00
IMVCActiveRecordTableMap = interface
[ '{517A863F-8BAD-4F66-A520-205149228360}' ]
procedure AddTableMap( const AR: TMVCActiveRecord; const TableMap: TMVCTableMap) ;
function GetTableMap( const TypeInfo: TMVCActiveRecord) : TMVCTableMap;
function TryGetValue( const AR: TMVCActiveRecord; out TableMap: TMVCTableMap) : Boolean ;
procedure ExecWithExclusiveLock( Proc: TProc< IMVCActiveRecordTableMap> ) ;
procedure FlushCache;
end ;
2018-09-28 13:01:46 +02:00
IMVCActiveRecordConnections = interface
2018-09-25 15:36:53 +02:00
[ '{7B87473C-1784-489F-A838-925E7DDD0DE2}' ]
2022-11-24 11:07:34 +01:00
procedure AddConnection( const aName: string ; const aConnection: TFDConnection; const Owns: Boolean = false ) ; overload ;
2021-10-20 11:29:49 +02:00
procedure AddDefaultConnection( const aConnection: TFDConnection; const Owns: Boolean = false ) ; overload ;
procedure AddDefaultConnection( const aConnectionDefName: String ) ; overload ;
2022-11-24 11:07:34 +01:00
procedure AddConnection( const aName, aConnectionDefName: String ) ; overload ;
2021-08-13 18:05:48 +02:00
procedure RemoveConnection( const aName: string ; const RaiseExceptionIfNotAvailable: Boolean = True ) ;
procedure RemoveDefaultConnection( const RaiseExceptionIfNotAvailable: Boolean = True ) ;
2018-10-14 18:23:20 +02:00
procedure SetCurrent( const aName: string ) ;
2020-10-17 23:19:05 +02:00
function GetCurrent( const RaiseExceptionIfNotAvailable: Boolean = True ) : TFDConnection;
2023-03-24 17:16:03 +01:00
function GetCurrentConnectionName( const RaiseExceptionIfNotAvailable: Boolean = False ) : String ;
2018-11-02 21:43:09 +01:00
function GetCurrentBackend: string ;
2020-03-30 13:30:45 +02:00
procedure SetDefault;
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 ;
2022-11-24 11:07:34 +01:00
procedure AddConnection( const aName: string ; const aConnection: TFDConnection; const aOwns: Boolean = false ) ; overload ;
procedure AddConnection( const aName, aConnectionDefName: String ) ; overload ;
2021-10-20 11:29:49 +02:00
procedure AddDefaultConnection( const aConnection: TFDConnection; const aOwns: Boolean = false ) ; overload ;
procedure AddDefaultConnection( const aConnectionDefName: String ) ; overload ;
2021-08-13 18:05:48 +02:00
procedure RemoveConnection( const aName: string ; const RaiseExceptionIfNotAvailable: Boolean = True ) ;
procedure RemoveDefaultConnection( const RaiseExceptionIfNotAvailable: Boolean = True ) ;
2018-10-14 18:23:20 +02:00
procedure SetCurrent( const aName: string ) ;
2020-10-17 23:19:05 +02:00
function GetCurrent( const RaiseExceptionIfNotAvailable: Boolean = True ) : TFDConnection;
2023-03-24 17:16:03 +01:00
function GetCurrentConnectionName( const RaiseExceptionIfNotAvailable: Boolean = False ) : String ;
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 ;
2020-03-30 13:30:45 +02:00
procedure SetDefault;
2018-11-02 21:43:09 +01:00
end ;
2022-11-17 19:33:27 +01:00
TMVCTableMapRepository = class( TInterfacedObject, IMVCActiveRecordTableMap)
private
fMREW: TMultiReadExclusiveWriteSynchronizer;
fTableMapDict: TObjectDictionary< String , TMVCTableMap> ;
function GetCacheKey( const AR: TMVCActiveRecord) : String ; inline ;
protected
procedure AddTableMap( const AR: TMVCActiveRecord; const TableMap: TMVCTableMap) ;
function GetTableMap( const TypeInfo: TMVCActiveRecord) : TMVCTableMap;
function TryGetValue( const AR: TMVCActiveRecord; out TableMap: TMVCTableMap) : Boolean ;
procedure ExecWithExclusiveLock( Proc: TProc< IMVCActiveRecordTableMap> ) ;
procedure FlushCache;
public
constructor Create; virtual ;
destructor Destroy; override ;
end ;
2018-11-02 21:43:09 +01:00
TMVCSQLGenerator = class abstract
private
fMapping: TMVCFieldsMapping;
2021-11-17 15:36:20 +01:00
fDefaultSQLFilter: String ;
fDefaultRQLFilter: String ;
2018-11-02 21:43:09 +01:00
fCompiler: TRQLCompiler;
fRQL2SQL: TRQL2SQL;
protected
2021-11-18 00:49:12 +01:00
fPartitionInfo: TPartitionInfo;
2022-08-02 17:07:14 +02:00
function GetDefaultSQLFilter( const IncludeWhereClause: Boolean ; const IncludeAndClauseBeforeFilter: Boolean = false )
: String ; // inline;
function MergeDefaultRQLFilter( const RQL: String ) : String ; // inline;
2023-07-07 20:29:09 +02:00
function MergeSQLFilter( const PartitionSQL, FilteringSQL: String ) : String ;
2021-11-18 00:49:12 +01:00
function GetRQLParser: TRQL2SQL;
2018-11-02 21:43:09 +01:00
function GetCompiler: TRQLCompiler;
function GetCompilerClass: TRQLCompilerClass; virtual ; abstract ;
function GetMapping: TMVCFieldsMapping;
2022-08-02 17:07:14 +02:00
function TableFieldsDelimited( const Map: TFieldsMap; const PKFieldName: string ; const Delimiter: string ) : string ;
2018-11-02 21:43:09 +01:00
public
2022-08-02 17:07:14 +02:00
constructor Create( Mapping: TMVCFieldsMapping; const DefaultRQLFilter: string ;
const PartitionInfo: TPartitionInfo) ; virtual ;
2018-11-02 21:43:09 +01:00
destructor Destroy; override ;
2020-01-08 15:30:10 +01:00
// capabilities
function HasSequences: Boolean ; virtual ;
function HasReturning: Boolean ; virtual ;
2022-06-23 14:34:01 +02:00
function HasNativeUUID: Boolean ; virtual ;
2020-01-08 15:30:10 +01:00
// end-capabilities
2021-11-21 19:27:06 +01:00
// abstract SQL generator methods
2019-03-05 20:55:37 +01:00
function CreateSQLWhereByRQL( const RQL: string ; const Mapping: TMVCFieldsMapping;
2020-09-30 11:16:10 +02:00
const UseArtificialLimit: Boolean = True ; const UseFilterOnly: Boolean = false ;
2021-11-21 19:27:06 +01:00
const MaxRecordCount: Int32 = TMVCConstants. MAX_RECORD_COUNT) : string ;
2022-08-02 17:07:14 +02:00
function CreateSelectSQL( const TableName: string ; const Map: TFieldsMap; const PKFieldName: string ;
2021-11-21 19:27:06 +01:00
const PKOptions: TMVCActiveRecordFieldOptions) : string ; virtual ;
2022-08-02 17:07:14 +02:00
function CreateInsertSQL( const TableName: string ; const Map: TFieldsMap; const PKFieldName: string ;
2020-08-28 18:04:29 +02:00
const PKOptions: TMVCActiveRecordFieldOptions) : string ; virtual ; abstract ;
2021-11-21 19:27:06 +01:00
// virtual methods with default implementation
2022-08-02 17:07:14 +02:00
function CreateSelectByPKSQL( const TableName: string ; const Map: TFieldsMap; const PKFieldName: string ;
2021-11-21 19:27:06 +01:00
const PKOptions: TMVCActiveRecordFieldOptions) : string ; virtual ;
2022-08-02 17:07:14 +02:00
function CreateDeleteSQL( const TableName: string ; const Map: TFieldsMap; const PKFieldName: string ;
2021-11-21 19:27:06 +01:00
const PKOptions: TMVCActiveRecordFieldOptions) : string ; virtual ;
function CreateDeleteAllSQL( const TableName: string ) : string ; virtual ;
function CreateSelectCount( const TableName: string ) : string ; virtual ;
2022-08-02 17:07:14 +02:00
function CreateUpdateSQL( const TableName: string ; const Map: TFieldsMap; const PKFieldName: string ;
2021-11-21 19:27:06 +01:00
const PKOptions: TMVCActiveRecordFieldOptions) : string ; virtual ;
2022-08-02 17:07:14 +02:00
function GetSequenceValueSQL( const PKFieldName: string ; const SequenceName: string ; const Step: Integer = 1 )
: string ; virtual ;
2020-04-08 18:04:45 +02:00
// Overwritten by descendant if the SQL syntaxt requires more than the simple table name
// or if the table name contains spaces.
function GetTableNameForSQL( const TableName: string ) : string ; virtual ;
// Overwritten by descendant if the SQL syntaxt requires more than the simple field name
// or if the field name contains spaces.
function GetFieldNameForSQL( const FieldName: string ) : string ; virtual ;
function GetParamNameForSQL( const FieldName: string ) : string ; virtual ;
2022-08-02 17:07:14 +02:00
// helper methods
2021-11-17 15:36:20 +01:00
class function RemoveInitialWhereKeyword( const SQLFilter: String ) : String ;
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;
2022-08-02 17:07:14 +02: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 ;
2022-08-02 17:07:14 +02:00
TMVCUnitOfWork< T: TMVCActiveRecord> = class( TInterfacedObject, IMVCUnitOfWork< T> , IMVCMultiExecutor< T> )
2021-04-26 23:01:31 +02:00
private
2021-04-27 22:57:15 +02:00
fListToDelete: TObjectList< T> ;
fListToUpdate: TObjectList< T> ;
fListToInsert: TObjectList< T> ;
2021-04-26 23:01:31 +02:00
protected
2021-05-03 19:29:01 +02:00
// multiexecutor
2021-04-29 22:52:28 +02:00
procedure Apply( const ItemApplyAction: TMVCItemApplyAction< T> = nil ) ;
2021-05-03 19:29:01 +02:00
// unitofwork
2021-04-27 22:57:15 +02:00
procedure RegisterDelete( const Value: T) ; overload ;
procedure RegisterDelete( const Enumerable: TEnumerable< T> ) ; overload ;
procedure RegisterUpdate( const Value: T) ;
procedure RegisterInsert( const Value: T) ;
procedure UnregisterDelete( const Value: T) ;
procedure UnregisterUpdate( const Value: T) ;
procedure UnregisterInsert( const Value: T) ;
2021-05-03 19:29:01 +02:00
// events
procedure DoItemApplyAction( const Obj: TMVCActiveRecord; const EntityAction: TMVCEntityAction;
const ItemApplyAction: TMVCItemApplyAction< T> ; var Handled: Boolean ) ;
2021-04-29 22:52:28 +02:00
2022-08-02 17:07:14 +02:00
class function KeyExistsInt( const NewList: TObjectList< T> ; const KeyValue: Integer ; out Index : Integer ) : Boolean ;
class function KeyExistsInt64( const NewList: TObjectList< T> ; const KeyValue: int64 ; out Index : Integer ) : Boolean ;
class function KeyExistsString( const NewList: TObjectList< T> ; const KeyValue: String ; out Index : Integer ) : Boolean ;
2021-04-26 23:01:31 +02:00
public
constructor Create; virtual ;
destructor Destroy; override ;
end ;
2022-10-29 00:34:49 +02:00
TMVCActiveRecordBackEnd = record
public
const
Unknown = 'unknown' ;
Oracle = 'oracle' ;
MSSql = 'mssql' ;
MSAccess = 'msaccess' ;
MySQL = 'mysql' ;
DB2 = 'db2' ;
SQLAnywhere = 'sqlanywhere' ;
Advantage = 'advantage' ;
Interbase = 'interbase' ;
FirebirdSQL = 'firebird' ;
SQLite = 'sqlite' ;
PostgreSQL = 'postgresql' ;
NexusDB = 'nexusdb' ;
DataSnap = 'dataSnap' ;
Informix = 'informix' ;
Teradata = 'teradata' ;
MongoDB = 'mongodb' ;
Other = 'other' ;
end ;
2018-09-28 13:01:46 +02:00
function ActiveRecordConnectionsRegistry: IMVCActiveRecordConnections;
2022-11-17 19:33:27 +01:00
function ActiveRecordTableMapRegistry: IMVCActiveRecordTableMap;
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. IOUtils,
System. Classes,
2018-09-25 15:36:53 +02:00
MVCFramework. DataSet. Utils,
MVCFramework. Logger,
2020-01-04 12:53:53 +01:00
MVCFramework. Nullables,
2020-08-28 18:04:29 +02:00
MVCFramework. RTTI. Utils,
2018-10-14 18:23:20 +02:00
FireDAC. Stan. Option,
2020-01-08 15:30:10 +01:00
Data. FmtBcd, System. Variants;
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;
2022-11-17 19:33:27 +01:00
gConnectionsLock: TObject;
gTableMap: IMVCActiveRecordTableMap;
gTableMapLock: TObject;
2018-09-25 15:36:53 +02:00
2018-11-02 21:43:09 +01:00
function GetBackEndByConnection( aConnection: TFDConnection) : string ;
begin
2019-08-02 12:32:23 +02:00
case Ord( aConnection. RDBMSKind) of
2018-11-02 21:43:09 +01:00
0 :
2022-10-29 00:34:49 +02:00
Exit( TMVCActiveRecordBackEnd. Unknown) ;
2018-11-02 21:43:09 +01:00
1 :
2022-10-29 00:34:49 +02:00
Exit( TMVCActiveRecordBackEnd. Oracle) ;
2018-11-02 21:43:09 +01:00
2 :
2022-10-29 00:34:49 +02:00
Exit( TMVCActiveRecordBackEnd. MSSql) ;
2018-11-02 21:43:09 +01:00
3 :
2022-10-29 00:34:49 +02:00
Exit( TMVCActiveRecordBackEnd. MSAccess) ;
2018-11-02 21:43:09 +01:00
4 :
2022-10-29 00:34:49 +02:00
Exit( TMVCActiveRecordBackEnd. MySQL) ;
2018-11-02 21:43:09 +01:00
5 :
2022-10-29 00:34:49 +02:00
Exit( TMVCActiveRecordBackEnd. DB2) ;
2018-11-02 21:43:09 +01:00
6 :
2022-10-29 00:34:49 +02:00
Exit( TMVCActiveRecordBackEnd. SQLAnywhere) ;
2018-11-02 21:43:09 +01:00
7 :
2022-10-29 00:34:49 +02:00
Exit( TMVCActiveRecordBackEnd. Advantage) ;
2018-11-02 21:43:09 +01:00
8 :
2022-10-29 00:34:49 +02:00
Exit( TMVCActiveRecordBackEnd. Interbase) ;
2018-11-02 21:43:09 +01:00
9 :
2022-10-29 00:34:49 +02:00
Exit( TMVCActiveRecordBackEnd. FirebirdSQL) ;
2018-11-02 21:43:09 +01:00
1 0 :
2022-10-29 00:34:49 +02:00
Exit( TMVCActiveRecordBackEnd. SQLite) ;
2018-11-02 21:43:09 +01:00
1 1 :
2022-10-29 00:34:49 +02:00
Exit( TMVCActiveRecordBackEnd. PostgreSQL) ;
2018-11-02 21:43:09 +01:00
1 2 :
2022-10-29 00:34:49 +02:00
Exit( TMVCActiveRecordBackEnd. NexusDB) ;
2018-11-02 21:43:09 +01:00
1 3 :
2022-10-29 00:34:49 +02:00
Exit( TMVCActiveRecordBackEnd. DataSnap) ;
2018-11-02 21:43:09 +01:00
1 4 :
2022-10-29 00:34:49 +02:00
Exit( TMVCActiveRecordBackEnd. Informix) ;
2018-11-02 21:43:09 +01:00
1 5 :
2022-10-29 00:34:49 +02:00
Exit( TMVCActiveRecordBackEnd. Teradata) ;
2018-11-02 21:43:09 +01:00
1 6 :
2022-10-29 00:34:49 +02:00
Exit( TMVCActiveRecordBackEnd. MongoDB) ;
2018-11-02 21:43:09 +01:00
1 7 :
2022-10-29 00:34:49 +02:00
Exit( TMVCActiveRecordBackEnd. Other) ;
2018-11-02 21:43:09 +01:00
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 ;
2022-11-17 19:33:27 +01:00
function ActiveRecordTableMapRegistry: IMVCActiveRecordTableMap;
begin
if gTableMap = nil then // double check here
begin
TMonitor. Enter( gTableMapLock) ;
try
if gTableMap = nil then
begin
gTableMap : = TMVCTableMapRepository. Create;
end ;
finally
TMonitor. Exit( gTableMapLock) ;
end ;
end ;
Result : = gTableMap;
end ;
2020-01-08 15:30:10 +01:00
function IntToNullableInt( const Value: Integer ) : NullableInt32;
begin
Result . SetValue( Value) ;
end ;
2018-09-25 15:36:53 +02:00
{ TConnectionsRepository }
2022-08-02 17:07:14 +02:00
procedure TMVCConnectionsRepository. AddConnection( const aName: string ; const aConnection: TFDConnection;
2019-03-05 20:55:37 +01:00
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) ;
2020-08-11 00:54:42 +02:00
{ If the transaction is not started, initialize TxIsolation as ReadCommitted }
if aConnection. Transaction = nil then
begin
{ needed for Delphi 10.4 Sydney+ }
aConnection. TxOptions. Isolation : = TFDTxIsolation. xiReadCommitted;
end ;
2018-10-14 18:23:20 +02:00
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
2022-08-02 17:07:14 +02: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 ;
2022-08-02 17:07:14 +02:00
procedure TMVCConnectionsRepository. AddDefaultConnection( const aConnection: TFDConnection; const aOwns: Boolean ) ;
2019-05-09 20:53:52 +02:00
begin
AddConnection( 'default' , aConnection, aOwns) ;
end ;
2022-11-24 11:07:34 +01:00
procedure TMVCConnectionsRepository. AddConnection( const aName,
aConnectionDefName: String ) ;
2021-10-20 11:29:49 +02:00
var
lConn: TFDConnection;
begin
lConn : = TFDConnection. Create( nil ) ;
try
lConn. ConnectionDefName : = aConnectionDefName;
2022-11-24 11:07:34 +01:00
AddConnection( aName, lConn, True ) ;
2021-10-20 11:29:49 +02:00
except
on E: Exception do
begin
lConn. Free;
raise ;
end ;
end ;
end ;
2022-11-24 11:07:34 +01:00
procedure TMVCConnectionsRepository. AddDefaultConnection( const aConnectionDefName: String ) ;
begin
AddConnection( 'default' , aConnectionDefName) ;
end ;
2018-09-25 15:36:53 +02:00
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
2020-02-21 20:14:15 +01:00
{$IF not Defined(TokyoOrBetter)}
2019-08-02 12:32:23 +02:00
Result : = nil ;
2019-08-05 12:55:44 +02:00
{$ENDIF}
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 ;
2023-03-24 17:16:03 +01:00
function TMVCConnectionsRepository. GetCurrentConnectionName(
const RaiseExceptionIfNotAvailable: Boolean ) : String ;
var
lName: string ;
begin
{$IF not Defined(TokyoOrBetter)}
Result : = '' ;
{$ENDIF}
fMREW. BeginRead;
try
if fCurrentConnectionsByThread. TryGetValue( TThread. CurrentThread. ThreadID, lName) then
begin
Result : = lName;
end
else
begin
if RaiseExceptionIfNotAvailable then
raise EMVCActiveRecord. Create( 'No current connection for thread' )
else
Result : = '' ;
end ;
finally
fMREW. EndRead;
end ;
end ;
2022-08-02 17:07:14 +02:00
function TMVCConnectionsRepository. GetCurrent( const RaiseExceptionIfNotAvailable: Boolean ) : TFDConnection;
2018-10-14 18:23:20 +02:00
var
lName: string ;
2018-09-25 15:36:53 +02:00
begin
2020-02-21 20:14:15 +01:00
{$IF not Defined(TokyoOrBetter)}
2019-08-02 12:32:23 +02:00
Result : = nil ;
2019-08-05 12:55:44 +02:00
{$ENDIF}
2018-10-14 18:23:20 +02:00
fMREW. BeginRead;
try
2019-08-02 12:32:23 +02:00
if fCurrentConnectionsByThread. TryGetValue( TThread. CurrentThread. ThreadID, lName) then
2018-10-14 18:23:20 +02:00
begin
Result : = GetByName( lName) ;
end
else
begin
2020-10-17 23:19:05 +02:00
if RaiseExceptionIfNotAvailable then
raise EMVCActiveRecord. Create( 'No current connection for thread' )
else
Result : = nil ;
2018-10-14 18:23:20 +02:00
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
2019-08-02 12:32:23 +02:00
Result : = Format( '%10.10d::%s' , [ TThread. CurrentThread. ThreadID, aName] ) ;
2018-09-25 15:36:53 +02:00
end ;
2022-08-02 17:07:14 +02:00
procedure TMVCConnectionsRepository. RemoveConnection( const aName: string ;
const RaiseExceptionIfNotAvailable: Boolean = True ) ;
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
2021-08-13 18:05:48 +02:00
begin
if RaiseExceptionIfNotAvailable then
begin
raise Exception. CreateFmt( 'Unknown connection %s' , [ aName] )
end
else
begin
Exit;
end ;
end ;
2018-10-14 18:23:20 +02:00
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 ;
2022-11-21 15:20:18 +01:00
fCurrentConnectionsByThread. Remove( TThread. CurrentThread. ThreadID) ;
2018-10-14 18:23:20 +02:00
finally
fMREW. EndWrite;
2018-09-25 15:36:53 +02:00
end ;
end ;
2021-08-13 18:05:48 +02:00
procedure TMVCConnectionsRepository. RemoveDefaultConnection( const RaiseExceptionIfNotAvailable: Boolean = True ) ;
2019-05-09 20:53:52 +02:00
begin
2021-08-13 18:05:48 +02:00
RemoveConnection( 'default' , RaiseExceptionIfNotAvailable) ;
2019-05-09 20:53:52 +02:00
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] ) ;
2019-08-02 12:32:23 +02:00
fCurrentConnectionsByThread. AddOrSetValue( TThread. CurrentThread. ThreadID, lName) ;
2018-10-14 18:23:20 +02:00
finally
fMREW. EndWrite;
end ;
2018-09-25 15:36:53 +02:00
end ;
2020-03-30 13:30:45 +02:00
procedure TMVCConnectionsRepository. SetDefault;
begin
SetCurrent( 'default' ) ;
end ;
2018-09-25 15:36:53 +02:00
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 }
2020-03-23 18:51:57 +01:00
constructor MVCTableFieldAttribute. Create( aFieldName: string ; const aDataTypeName: string = '' ) ;
2018-09-25 15:36:53 +02:00
begin
2020-03-12 20:37:48 +01:00
Create( aFieldName, [ ] , '' , aDataTypeName) ;
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
2021-11-17 15:36:20 +01:00
Create( aName, '' ) ;
2018-09-25 15:36:53 +02:00
end ;
{ TActiveRecord }
destructor TMVCActiveRecord. Destroy;
begin
2020-05-04 09:42:16 +02:00
fChildren. 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 ;
2020-02-03 10:51:40 +01:00
procedure TMVCActiveRecord. EnsureConnection;
begin
GetConnection;
end ;
2022-08-02 17:07:14 +02:00
function TMVCActiveRecord. ExecNonQuery( const SQL: string ; RefreshAutoGenerated: Boolean = false ) : int64 ;
2018-09-25 15:36:53 +02:00
var
lQry: TFDQuery;
lPar: TFDParam;
2020-03-25 11:35:25 +01:00
lPair: TPair< TRTTIField, TFieldInfo> ;
2018-09-25 15:36:53 +02:00
lValue: TValue;
2020-03-23 18:51:57 +01:00
lSQL: string ;
2019-11-27 19:04:06 +01:00
lHandled: Boolean ;
2021-11-18 00:49:12 +01:00
I: Integer ;
2018-09-25 15:36:53 +02:00
begin
2022-08-02 17:07:14 +02:00
{ TODO -oDanieleT -cGeneral : Why not a TFDCommand? }
2018-09-25 15:36:53 +02:00
lQry : = TFDQuery. Create( nil ) ;
try
2020-02-03 10:51:40 +01:00
lQry. Connection : = GetConnection;
2019-03-18 14:08:34 +01:00
lSQL : = SQL;
OnBeforeExecuteSQL( lSQL) ;
lQry. SQL. Text : = lSQL;
2019-11-27 19:04:06 +01:00
2020-01-04 12:53:53 +01:00
lHandled : = false ;
// lQry.Prepare;
2019-11-27 19:04:06 +01:00
MapObjectToParams( lQry. Params, lHandled) ;
if not lHandled then
2018-09-25 15:36:53 +02:00
begin
2022-08-02 17:07:14 +02:00
{ partitioning }
2021-11-18 17:52:06 +01:00
for I : = 0 to GetPartitionInfo. FieldNames. Count - 1 do
2021-11-18 00:49:12 +01:00
begin
2021-11-18 17:52:06 +01:00
lPar : = lQry. FindParam( SQLGenerator. GetParamNameForSQL( GetPartitionInfo. FieldNames[ I] ) ) ;
2021-11-18 00:49:12 +01:00
if lPar < > nil then
begin
2021-11-18 17:52:06 +01:00
if GetPartitionInfo. FieldTypes[ I] = ftInteger then
lValue : = StrToInt( GetPartitionInfo. FieldValues[ I] )
2021-11-18 00:49:12 +01:00
else
2021-11-18 17:52:06 +01:00
lValue : = GetPartitionInfo. FieldValues[ I] ;
2022-08-02 17:07:14 +02:00
// lPar.DataTypeName := fPartitionInfo.FieldValues[I];
2021-11-18 00:49:12 +01:00
MapTValueToParam( lValue, lPar) ;
end
end ;
2022-08-02 17:07:14 +02:00
{ end-partitioning }
2021-11-18 00:49:12 +01:00
2022-11-17 19:33:27 +01:00
for lPair in fTableMap. fMap do
2018-09-25 15:36:53 +02:00
begin
2020-04-08 18:04:45 +02:00
lPar : = lQry. FindParam( SQLGenerator. GetParamNameForSQL( lPair. Value. FieldName) ) ;
2019-11-27 19:04:06 +01:00
if lPar < > nil then
begin
lValue : = lPair. Key. GetValue( Self) ;
2022-11-17 19:33:27 +01:00
lPar. DataTypeName : = fTableMap. fMap. GetInfoByFieldName( lPair. Value. FieldName) . DataTypeName;
2019-11-27 19:04:06 +01:00
MapTValueToParam( lValue, lPar) ;
end
end ;
2018-10-14 18:23:20 +02:00
2022-08-02 17:07:14 +02:00
// Check if it's the primary key
2022-11-17 19:33:27 +01:00
lPar : = lQry. FindParam( SQLGenerator. GetParamNameForSQL( fTableMap. fPrimaryKeyFieldName) ) ;
2019-11-27 19:04:06 +01:00
if lPar < > nil then
2018-09-25 15:36:53 +02:00
begin
2019-11-27 19:04:06 +01:00
if lPar. DataType = ftUnknown then
begin
{ TODO -oDanieleT -cGeneral : Let's find a smarter way to do this if the engine cannot recognize parameter's datatype }
2020-03-31 16:23:22 +02:00
lPar. DataType : = GetPrimaryKeyFieldType;
2019-11-27 19:04:06 +01:00
end ;
2022-11-17 19:33:27 +01:00
MapTValueToParam( fTableMap. fPrimaryKey. GetValue( Self) , lPar) ;
2018-09-25 15:36:53 +02:00
end ;
end ;
2022-11-17 19:33:27 +01:00
if RefreshAutoGenerated and ( TMVCActiveRecordFieldOption. foAutoGenerated in fTableMap. fPrimaryKeyOptions) and
fTableMap. fPrimaryKeySequenceName. IsEmpty then
2018-09-25 15:36:53 +02:00
begin
2022-11-17 19:33:27 +01:00
lValue : = fTableMap. fPrimaryKey. GetValue( Self) ;
2020-01-08 15:30:10 +01:00
lQry. Open;
2020-02-03 10:51:40 +01:00
2020-01-08 15:30:10 +01:00
if ( lValue. Kind = tkRecord) then
2020-01-04 12:53:53 +01:00
begin
2022-11-17 19:33:27 +01:00
MapDataSetFieldToNullableRTTIField( lValue, lQry. Fields[ 0 ] , fTableMap. fPrimaryKey, Self) ;
2020-01-04 12:53:53 +01:00
end
else
begin
2022-11-17 19:33:27 +01:00
lValue : = lQry. FieldByName( fTableMap. fPrimaryKeyFieldName) . AsInteger;
fTableMap. fPrimaryKey. SetValue( Self, lValue) ;
2020-01-04 12:53:53 +01:00
end ;
2018-09-25 15:36:53 +02:00
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 ;
2019-03-05 20:55:37 +01:00
class function TMVCActiveRecord. ExecQuery( const SQL: string ; const Values: array of Variant ;
2023-01-02 22:09:42 +01:00
const Connection: TFDConnection; const Unidirectional: Boolean ;
const DirectExecute: Boolean ) : TDataSet;
2018-09-25 15:36:53 +02:00
begin
2023-01-02 22:09:42 +01:00
Result : = ExecQuery( SQL, Values, [ ] , Connection, Unidirectional, DirectExecute) ;
2018-09-25 15:36:53 +02:00
end ;
2020-03-23 18:51:57 +01:00
procedure TMVCActiveRecord. FillPrimaryKey( const SequenceName: string ) ;
2020-01-08 15:30:10 +01:00
var
lDS: TDataSet;
lSQL: string ;
begin
if not SequenceName. IsEmpty then
begin
2022-11-17 19:33:27 +01:00
lSQL : = SQLGenerator. GetSequenceValueSQL( fTableMap. fPrimaryKeyFieldName, SequenceName) ;
2020-01-08 15:30:10 +01:00
if lSQL. IsEmpty then
begin
Exit;
end ;
2023-01-02 22:09:42 +01:00
lDS : = ExecQuery( lSQL, [ ] , True , False ) ;
2020-01-08 15:30:10 +01:00
try
2022-11-17 19:33:27 +01:00
MapDataSetFieldToRTTIField( lDS. Fields[ 0 ] , fTableMap. fPrimaryKey, Self) ;
2020-01-08 15:30:10 +01:00
finally
lDS. Free;
end ;
end ;
end ;
2023-08-09 10:55:34 +02:00
function TMVCActiveRecord. FindRQLQueryByName( const QueryName: String ;
out NamedRQLQuery: TRQLQueryWithName) : Boolean ;
var
I: Integer ;
begin
for I : = Low( fTableMap. fNamedRQLQueries) to High( fTableMap. fNamedRQLQueries) do
begin
if SameText( QueryName, fTableMap. fNamedRQLQueries[ I] . Name ) then
begin
NamedRQLQuery : = fTableMap. fNamedRQLQueries[ I] ;
Exit( True ) ;
end ;
end ;
Result : = False ;
end ;
2023-08-09 01:23:24 +02:00
function TMVCActiveRecord. FindSQLQueryByName( const QueryName: String ;
2023-08-09 10:55:34 +02:00
out NamedSQLQuery: TSQLQueryWithName) : Boolean ;
2023-08-09 01:23:24 +02:00
var
I: Integer ;
2023-08-09 10:55:34 +02:00
lBackEnd: String ;
2023-08-09 01:23:24 +02:00
begin
for I : = Low( fTableMap. fNamedSQLQueries) to High( fTableMap. fNamedSQLQueries) do
begin
if SameText( QueryName, fTableMap. fNamedSQLQueries[ I] . Name ) then
begin
2023-08-09 10:55:34 +02:00
lBackEnd : = fTableMap. fNamedSQLQueries[ I] . BackEnd;
if lBackEnd. IsEmpty or ( lBackEnd = GetBackEnd) then
begin
NamedSQLQuery : = fTableMap. fNamedSQLQueries[ I] ;
Exit( True ) ;
end ;
2023-08-09 01:23:24 +02:00
end ;
end ;
Result : = False ;
end ;
2022-08-02 17:07:14 +02:00
class function TMVCActiveRecord. ExecQuery( const SQL: string ; const Values: array of Variant ;
2023-01-02 22:09:42 +01:00
const Unidirectional: Boolean ; const DirectExecute: Boolean ) : TDataSet;
2018-09-25 15:36:53 +02:00
begin
2023-01-02 22:09:42 +01:00
Result : = ExecQuery( SQL, Values, nil , Unidirectional, DirectExecute) ;
2018-09-25 15:36:53 +02:00
end ;
procedure TMVCActiveRecord. InitTableInfo;
var
2018-10-14 18:23:20 +02:00
lAttribute: TCustomAttribute;
2020-03-25 11:35:25 +01:00
lRTTIField: TRTTIField;
lFieldInfo: TFieldInfo;
2020-03-31 16:23:22 +02:00
lPrimaryFieldTypeAsStr: string ;
2022-11-17 19:33:27 +01:00
lTableMap: TMVCTableMap;
2023-06-15 23:42:07 +02:00
lPKCount: Integer ;
2023-08-09 01:23:24 +02:00
lNamedSQLQueryCount: Integer ;
2023-08-09 10:55:34 +02:00
lNamedRQLQueryCount: Integer ;
2018-09-25 15:36:53 +02:00
begin
2022-11-17 19:33:27 +01:00
if ActiveRecordTableMapRegistry. TryGetValue( Self, fTableMap) then
begin
Exit;
end ;
2022-11-17 23:53:32 +01:00
TMonitor. Enter( gTableMapLock) ;
try
if ActiveRecordTableMapRegistry. TryGetValue( Self, fTableMap) then //double check here
2018-09-25 15:36:53 +02:00
begin
2022-11-17 23:53:32 +01:00
Exit;
2018-09-25 15:36:53 +02:00
end ;
2022-11-17 23:53:32 +01:00
lTableMap : = TMVCTableMap. Create;
SetLength( lTableMap. fMapping, 0 ) ;
lTableMap. fPartitionInfoInternal : = nil ;
lTableMap. fEntityAllowedActions : = [ TMVCEntityAction. eaCreate, TMVCEntityAction. eaRetrieve, TMVCEntityAction. eaUpdate,
TMVCEntityAction. eaDelete] ;
lTableMap. fTableName : = '' ;
lTableMap. fPartitionClause : = '' ;
lTableMap. fRTTIType : = gCtx. GetType( Self. ClassInfo) as TRttiInstanceType;
lTableMap. fObjAttributes : = lTableMap. fRTTIType. GetAttributes;
2023-06-15 23:42:07 +02:00
lPKCount : = 0 ;
2023-08-09 01:23:24 +02:00
lNamedSQLQueryCount : = Length( lTableMap. fNamedSQLQueries) ;
2023-08-09 10:55:34 +02:00
lNamedRQLQueryCount : = Length( lTableMap. fNamedRQLQueries) ;
2022-11-17 23:53:32 +01:00
for lAttribute in lTableMap. fObjAttributes do
2021-11-18 00:49:12 +01:00
begin
2022-11-17 23:53:32 +01:00
if lAttribute is MVCTableAttribute then
begin
lTableMap. fTableName : = MVCTableAttribute( lAttribute) . Name ;
lTableMap. fDefaultRQLFilter : = MVCTableAttribute( lAttribute) . RQLFilter;
Continue;
end ;
if lAttribute is MVCEntityActionsAttribute then
begin
lTableMap. fEntityAllowedActions : = MVCEntityActionsAttribute( lAttribute) . EntityAllowedActions;
2023-06-15 23:42:07 +02:00
Continue;
2022-11-17 23:53:32 +01:00
end ;
if lAttribute is MVCPartitionAttribute then
begin
lTableMap. fPartitionClause : = MVCPartitionAttribute( lAttribute) . PartitionClause;
Continue;
end ;
2023-08-09 01:23:24 +02:00
if lAttribute is MVCNamedSQLQueryAttribute then
begin
Inc( lNamedSQLQueryCount) ;
SetLength( lTableMap. fNamedSQLQueries, lNamedSQLQueryCount) ;
lTableMap. fNamedSQLQueries[ lNamedSQLQueryCount - 1 ] . Name : = MVCNamedSQLQueryAttribute( lAttribute) . Name ;
2023-08-09 10:55:34 +02:00
lTableMap. fNamedSQLQueries[ lNamedSQLQueryCount - 1 ] . SQLText : = MVCNamedSQLQueryAttribute( lAttribute) . SQLQuery;
lTableMap. fNamedSQLQueries[ lNamedSQLQueryCount - 1 ] . BackEnd : = MVCNamedSQLQueryAttribute( lAttribute) . Backend;
Continue;
end ;
if lAttribute is MVCNamedRQLQueryAttribute then
begin
Inc( lNamedRQLQueryCount) ;
SetLength( lTableMap. fNamedRQLQueries, lNamedRQLQueryCount) ;
lTableMap. fNamedRQLQueries[ lNamedRQLQueryCount - 1 ] . Name : = MVCNamedRQLQueryAttribute( lAttribute) . Name ;
lTableMap. fNamedRQLQueries[ lNamedRQLQueryCount - 1 ] . RQLText : = MVCNamedRQLQueryAttribute( lAttribute) . RQLQuery;
2023-08-09 01:23:24 +02:00
Continue;
end ;
2021-11-18 00:49:12 +01:00
end ;
2022-11-17 23:53:32 +01:00
if lTableMap. fTableName = '' then
2020-03-30 13:30:45 +02:00
begin
2022-11-17 23:53:32 +01:00
if [ eaCreate, eaUpdate, eaDelete] * lTableMap. fEntityAllowedActions < > [ ] then
begin
2023-08-08 18:54:44 +02:00
raise Exception. Create( 'Cannot find TableNameAttribute on class ' + ClassName + ' - [HINT] Is this class decorated with MVCTable and its fields with MVCTableField?' ) ;
2022-11-17 23:53:32 +01:00
end ;
2020-03-30 13:30:45 +02:00
end ;
2018-09-25 15:36:53 +02:00
2022-11-17 23:53:32 +01:00
lTableMap. fProps : = lTableMap. fRTTIType. GetFields;
for lRTTIField in lTableMap. fProps do
2018-09-25 15:36:53 +02:00
begin
2022-11-17 23:53:32 +01:00
lTableMap. fPropsAttributes : = lRTTIField. GetAttributes;
if Length( lTableMap. fPropsAttributes) = 0 then
Continue;
for lAttribute in lTableMap. fPropsAttributes do
2018-09-25 15:36:53 +02:00
begin
2022-11-17 23:53:32 +01:00
if lAttribute is MVCTableFieldAttribute then
2019-03-05 20:55:37 +01:00
begin
2022-11-17 23:53:32 +01:00
if foPrimaryKey in MVCTableFieldAttribute( lAttribute) . FieldOptions then
2022-05-13 17:15:32 +02:00
begin
2022-11-17 23:53:32 +01:00
lTableMap. fPrimaryKey : = lRTTIField;
lPrimaryFieldTypeAsStr : = lTableMap. fPrimaryKey. FieldType. ToString. ToLowerInvariant;
if lPrimaryFieldTypeAsStr. EndsWith( 'int64' ) then
begin
lTableMap. fPrimaryKeyFieldType : = ftLargeInt;
end
else if lPrimaryFieldTypeAsStr. EndsWith( 'integer' )
or lPrimaryFieldTypeAsStr. EndsWith( 'int16' )
or lPrimaryFieldTypeAsStr. EndsWith( 'int32' ) then
begin
lTableMap. fPrimaryKeyFieldType : = ftInteger;
end
else if lPrimaryFieldTypeAsStr. EndsWith( 'string' ) then
begin
lTableMap. fPrimaryKeyFieldType : = ftString;
end
else if lPrimaryFieldTypeAsStr. EndsWith( 'guid' ) then
begin
lTableMap. fPrimaryKeyFieldType : = ftGuid;
end
else
begin
raise EMVCActiveRecord. Create
( 'Allowed primary key types are: (Nullable)Integer, (Nullable)Int16, (Nullable)Int32, (Nullable)Int64, (Nullable)String, GUID - found: ' +
lPrimaryFieldTypeAsStr) ;
end ;
lTableMap. fPrimaryKeyFieldName : = MVCTableFieldAttribute( lAttribute) . FieldName;
lTableMap. fPrimaryKeyOptions : = MVCTableFieldAttribute( lAttribute) . FieldOptions;
lTableMap. fPrimaryKeySequenceName : = MVCTableFieldAttribute( lAttribute) . SequenceName;
2023-06-15 23:42:07 +02:00
Inc( lPKCount) ;
2022-11-17 23:53:32 +01:00
Continue;
2020-03-31 16:23:22 +02:00
end ;
2019-03-05 20:55:37 +01:00
2022-11-17 23:53:32 +01:00
lFieldInfo : = TFieldInfo. Create;
lTableMap. fMap. Add( lRTTIField, lFieldInfo) ;
lFieldInfo. FieldName : = MVCTableFieldAttribute( lAttribute) . FieldName;
lFieldInfo. FieldOptions : = MVCTableFieldAttribute( lAttribute) . FieldOptions;
lFieldInfo. DataTypeName : = MVCTableFieldAttribute( lAttribute) . DataTypeName;
end ;
2018-09-25 15:36:53 +02:00
end ;
end ;
2022-11-17 23:53:32 +01:00
lTableMap. fMap. EndUpdates;
2023-06-16 00:59:51 +02:00
if ( lPKCount + lTableMap. fMap. WritableFieldsCount + lTableMap. fMap. ReadableFieldsCount) = 0 then
2023-06-15 23:42:07 +02:00
raise EMVCActiveRecord. Create(
2023-08-08 18:54:44 +02:00
'No fields nor PKs defined in class ' + ClassName + '. [HINT] Use MVCTableField in private fields' ) ;
2022-11-17 23:53:32 +01:00
lTableMap. fPartitionInfoInternal : = nil ;
ActiveRecordTableMapRegistry. AddTableMap( Self, lTableMap) ;
fTableMap : = lTableMap;
finally
TMonitor. Exit( gTableMapLock) ;
2018-09-25 15:36:53 +02:00
end ;
end ;
procedure TMVCActiveRecord. Insert;
var
SQL: string ;
begin
2018-10-14 18:23:20 +02:00
CheckAction( TMVCEntityAction. eaCreate) ;
2020-03-30 13:30:45 +02:00
OnValidation( TMVCEntityAction. eaCreate) ;
2018-09-25 15:36:53 +02:00
OnBeforeInsert;
OnBeforeInsertOrUpdate;
2022-11-17 19:33:27 +01:00
if fTableMap. fMap. WritableFieldsCount = 0 then
2019-02-21 20:17:11 +01:00
begin
raise EMVCActiveRecord. CreateFmt
2022-11-15 09:27:08 +01:00
( 'Cannot insert an entity if no fields are writable. Class [%s] mapped on table [%s]' ,
2022-11-17 19:33:27 +01:00
[ ClassName, TableName] ) ;
2019-02-21 20:17:11 +01:00
end ;
2022-11-17 19:33:27 +01:00
if ( foAutoGenerated in fTableMap. fPrimaryKeyOptions) then
2020-01-08 15:30:10 +01:00
begin
if not SQLGenerator. HasReturning then
begin
if not SQLGenerator. HasSequences then
begin
raise EMVCActiveRecord. Create
( 'Cannot use AutoGenerated primary keys if the engine doesn' 't support returning clause nor sequences' ) ;
end
else
begin
2022-11-17 19:33:27 +01:00
if fTableMap. fPrimaryKeySequenceName. IsEmpty then
2020-01-08 15:30:10 +01:00
begin
2022-08-02 17:07:14 +02:00
raise EMVCActiveRecord. Create( 'SequenceName is empty for entity ' + ClassName + ' but ' + GetBackEnd +
2020-01-08 15:30:10 +01:00
' requires it' ) ;
end ;
2022-11-17 19:33:27 +01:00
if foReadOnly in fTableMap. fPrimaryKeyOptions then
2022-01-26 23:00:32 +01:00
begin
2022-08-02 17:07:14 +02:00
raise EMVCActiveRecord. Create( 'Cannot define a read-only primary key when a sequence is used for the class ' +
ClassName) ;
2022-01-26 23:00:32 +01:00
end ;
2022-11-17 19:33:27 +01:00
FillPrimaryKey( fTableMap. fPrimaryKeySequenceName) ;
2020-01-08 15:30:10 +01:00
end ;
end ;
end ;
2022-11-17 19:33:27 +01:00
SQL : = SQLGenerator. CreateInsertSQL( TableName, fTableMap. fMap,
fTableMap. fPrimaryKeyFieldName, fTableMap. 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 ;
2020-03-23 18:51:57 +01:00
function TMVCActiveRecord. InternalCount( const RQL: string ) : int64 ;
2019-08-02 12:32:23 +02:00
var
lSQL: string ;
begin
2022-11-17 19:33:27 +01:00
lSQL : = Self. SQLGenerator. CreateSelectCount( TableName) ;
2021-11-17 15:36:20 +01:00
lSQL : = lSQL + fSQLGenerator. CreateSQLWhereByRQL( RQL, GetMapping, false , True ) ;
2019-08-02 12:32:23 +02:00
Result : = GetScalar( lSQL, [ ] ) ;
end ;
2023-08-08 14:31:23 +02:00
function TMVCActiveRecord. InternalSelectRQL( const RQL: string ;
const MaxRecordCount: Integer ; const OutList: TMVCActiveRecordList) : UInt32 ;
var
lSQL: string ;
begin
lSQL : = SQLGenerator. CreateSQLWhereByRQL( RQL, GetMapping, True , false , MaxRecordCount) ;
LogD( Format( 'RQL [%s] => SQL [%s]' , [ RQL, lSQL] ) ) ;
Result : = Where( TMVCActiveRecordClass( Self. ClassType) , lSQL, [ ] , nil , OutList) ;
end ;
2022-08-02 17:07:14 +02:00
function TMVCActiveRecord. InternalSelectRQL( const RQL: string ; const MaxRecordCount: Integer ) : TMVCActiveRecordList;
2019-08-02 12:32:23 +02:00
var
lSQL: string ;
begin
2020-12-18 14:41:01 +01:00
lSQL : = SQLGenerator. CreateSQLWhereByRQL( RQL, GetMapping, True , false , MaxRecordCount) ;
2019-08-02 12:32:23 +02:00
LogD( Format( 'RQL [%s] => SQL [%s]' , [ RQL, lSQL] ) ) ;
Result : = Where( TMVCActiveRecordClass( Self. ClassType) , lSQL, [ ] ) ;
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 ;
2020-02-03 10:51:40 +01:00
if not aLazyLoadConnection then
begin
GetConnection;
end ;
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
2022-11-17 19:33:27 +01:00
Result : = SQLGenerator. CreateSelectSQL( TableName, fTableMap. fMap,
fTableMap. fPrimaryKeyFieldName, fTableMap. fPrimaryKeyOptions) ;
2018-11-02 21:43:09 +01:00
end ;
2020-08-28 18:04:29 +02:00
function TMVCActiveRecord. GetAttributes( const AttrName: string ) : TValue;
var
lProperty: TRttiProperty;
begin
if not TRttiUtils. ExistsProperty( Self, AttrName, lProperty) then
begin
raise EMVCActiveRecord. CreateFmt( 'Attribute [%s] not found' , [ AttrName] ) ;
end ;
Result : = lProperty. GetValue( Self) ;
end ;
2018-11-02 21:43:09 +01:00
function TMVCActiveRecord. GetBackEnd: string ;
begin
if fBackendDriver. IsEmpty then
2018-09-25 15:36:53 +02:00
begin
2020-02-03 10:51:40 +01:00
fBackendDriver : = GetBackEndByConnection( GetConnection) ;
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 ;
2022-08-02 17:07:14 +02:00
class function TMVCActiveRecord. GetByPK( aActiveRecord: TMVCActiveRecord; const aValue: string ;
const aFieldType: TFieldType; const RaiseExceptionIfNotFound: Boolean ) : TMVCActiveRecord;
2022-06-23 14:34:01 +02:00
var
lFound: Boolean ;
2020-03-31 16:23:22 +02:00
begin
2022-05-13 17:23:00 +02:00
Result : = aActiveRecord;
try
2022-06-23 14:34:01 +02:00
if Result . SQLGenerator. HasNativeUUID then
begin
lFound : = Result . LoadByPK( aValue, aFieldType)
end
else
begin
lFound : = Result . LoadByPK( aValue) ;
end ;
if not lFound then
2020-03-31 16:23:22 +02:00
begin
2022-05-13 17:23:00 +02:00
if RaiseExceptionIfNotFound then
2022-06-16 14:05:01 +02:00
raise EMVCActiveRecordNotFound. Create( 'No data found' )
2022-05-13 17:23:00 +02:00
else
FreeAndNil( Result ) ;
2020-03-31 16:23:22 +02:00
end ;
2022-05-13 17:23:00 +02:00
except
FreeAndNil( Result ) ;
raise ;
2020-03-31 16:23:22 +02:00
end ;
end ;
2023-08-09 00:46:31 +02:00
class function TMVCActiveRecordHelper. GetByPK( const aClass: TMVCActiveRecordClass; const aValue: string ;
2022-05-13 17:23:00 +02:00
const RaiseExceptionIfNotFound: Boolean ) : TMVCActiveRecord;
begin
Result : = GetByPK( aClass. Create, aValue, ftString, RaiseExceptionIfNotFound) ;
end ;
2023-08-09 00:46:31 +02:00
class function TMVCActiveRecordHelper. GetByPK( const aClass: TMVCActiveRecordClass; const aValue: int64 ;
2020-01-04 12:53:53 +01:00
const RaiseExceptionIfNotFound: Boolean ) : TMVCActiveRecord;
2018-09-25 15:36:53 +02:00
begin
2022-05-13 17:23:00 +02:00
Result : = GetByPK( aClass. Create, aValue. ToString, ftInteger, RaiseExceptionIfNotFound) ;
end ;
2023-08-09 00:46:31 +02:00
class function TMVCActiveRecordHelper. GetByPK( const aClass: TMVCActiveRecordClass; const aValue: TGuid;
2022-05-13 17:23:00 +02:00
const RaiseExceptionIfNotFound: Boolean ) : TMVCActiveRecord;
begin
Result : = GetByPK( aClass. Create, aValue. ToString, ftGuid, RaiseExceptionIfNotFound) ;
2018-09-25 15:36:53 +02:00
end ;
2020-01-04 12:53:53 +01:00
class function TMVCActiveRecordHelper. GetByPK< T> ( const aValue: int64 ;
const RaiseExceptionIfNotFound: Boolean = True ) : T;
2018-09-25 15:36:53 +02:00
begin
2022-05-13 17:23:00 +02:00
Result : = GetByPK< T> ( aValue. ToString, ftInteger, RaiseExceptionIfNotFound) ;
2018-09-25 15:36:53 +02:00
end ;
2022-08-02 17:07:14 +02:00
class function TMVCActiveRecordHelper. GetByPK< T> ( const aValue: string ; const RaiseExceptionIfNotFound: Boolean ) : T;
2020-03-31 16:23:22 +02:00
begin
2022-05-13 17:23:00 +02:00
Result : = GetByPK< T> ( aValue, ftString, RaiseExceptionIfNotFound) ;
end ;
2022-08-02 17:07:14 +02:00
class function TMVCActiveRecordHelper. GetByPK< T> ( const aValue: TGuid; const RaiseExceptionIfNotFound: Boolean ) : T;
2022-05-13 17:23:00 +02:00
begin
Result : = GetByPK< T> ( aValue. ToString, ftGuid, RaiseExceptionIfNotFound) ;
2020-03-31 16:23:22 +02:00
end ;
2022-08-02 17:07:14 +02:00
class function TMVCActiveRecordHelper. GetByPK< T> ( const aValue: string ; const aFieldType: TFieldType;
const RaiseExceptionIfNotFound: Boolean ) : T;
2022-07-15 20:42:29 +02:00
begin
Result : = T( GetByPK( T. Create, aValue, aFieldType, RaiseExceptionIfNotFound) ) ;
end ;
2022-08-02 17:07:14 +02:00
class function TMVCActiveRecordHelper. GetFirstByWhere< T> ( const SQLWhere: string ; const Params: array of Variant ;
2020-08-28 18:04:29 +02:00
const ParamTypes: array of TFieldType; const RaiseExceptionIfNotFound: Boolean ) : T;
2018-10-23 16:18:34 +02:00
var
lList: TObjectList< T> ;
begin
2020-03-23 18:51:57 +01:00
lList : = Where< T> ( SQLWhere, Params, ParamTypes) ;
2018-10-23 16:18:34 +02:00
try
if lList. Count = 0 then
begin
if RaiseExceptionIfNotFound then
2020-03-23 18:51:57 +01:00
raise EMVCActiveRecordNotFound. Create( 'Got 0 rows when at least 1 was expected' ) ;
2018-10-23 16:18:34 +02:00
Exit( nil ) ;
end ;
2020-02-05 23:46:38 +01:00
Result : = lList. Extract( lList. First) ;
2018-10-23 16:18:34 +02:00
finally
lList. Free;
end ;
end ;
2022-08-02 17:07:14 +02:00
class function TMVCActiveRecordHelper. GetFirstByWhere< T> ( const SQLWhere: string ; const Params: array of Variant ;
2020-03-23 18:51:57 +01:00
const RaiseExceptionIfNotFound: Boolean ) : T;
begin
Result : = GetFirstByWhere< T> ( SQLWhere, Params, [ ] , RaiseExceptionIfNotFound) ;
end ;
2022-08-02 17:07:14 +02:00
class function TMVCActiveRecordHelper. GetOneByWhere< T> ( const SQLWhere: string ; const Params: array of Variant ;
2020-08-28 18:04:29 +02:00
const ParamTypes: array of TFieldType; const RaiseExceptionIfNotFound: Boolean ) : T;
2020-03-23 18:51:57 +01:00
begin
Result : = GetFirstByWhere< T> ( SQLWhere, Params, ParamTypes, false ) ;
if Result = nil then
begin
if RaiseExceptionIfNotFound then
raise EMVCActiveRecordNotFound. Create( 'Got 0 rows when exactly 1 was expected' ) ;
end ;
end ;
2018-09-28 13:01:46 +02:00
function TMVCActiveRecord. GetMapping: TMVCFieldsMapping;
var
2020-03-25 11:35:25 +01:00
lPair: TPair< TRTTIField, TFieldInfo> ;
2022-08-02 17:07:14 +02:00
I: Integer ;
2022-08-02 23:57:09 +02:00
lPropFromField: TRttiProperty;
lParentType: TRttiType;
lTmp: String ;
2018-09-28 13:01:46 +02:00
begin
2020-04-08 18:04:45 +02:00
{ TODO -oDanieleT -cGeneral : Let share the mapping for instances of the same type }
2021-04-08 00:33:27 +02:00
{ TODO -oDanieleT -cGeneral : Add NameAs in the TFieldInfo because the user needs to use the property name he see }
2022-11-17 19:33:27 +01:00
if Length( fTableMap. fMapping) = 0 then
2018-09-28 13:01:46 +02:00
begin
2022-11-17 19:33:27 +01:00
if not fTableMap. fPrimaryKeyFieldName. IsEmpty then
2019-02-05 18:08:21 +01:00
begin
2022-11-17 19:33:27 +01:00
lParentType : = fTableMap. fPrimaryKey. Parent;
SetLength( fTableMap. fMapping, fTableMap. fMap. Count + 1 ) ;
fTableMap. fMapping[ 0 ] . InstanceFieldName : = fTableMap. fPrimaryKey. Name . Substring( 1 ) . ToLower;
fTableMap. fMapping[ 0 ] . DatabaseFieldName : = fTableMap. fPrimaryKeyFieldName;
lPropFromField : = lParentType. GetProperty( fTableMap. fPrimaryKey. Name . Substring( 1 ) ) ;
2022-08-02 23:57:09 +02:00
if Assigned( lPropFromField) then
begin
lTmp : = TMVCSerializerHelper. GetKeyName( lPropFromField, lParentType) ;
2022-11-17 19:33:27 +01:00
if not SameText( lTmp, fTableMap. fMapping[ 0 ] . InstanceFieldName) then
2022-08-02 23:57:09 +02:00
begin
2022-11-17 19:33:27 +01:00
fTableMap. fMapping[ 0 ] . Alias : = lTmp;
2022-08-02 23:57:09 +02:00
end ;
end ;
2022-08-02 17:07:14 +02:00
I : = 1 ;
2019-02-05 18:08:21 +01:00
end
2018-11-02 21:43:09 +01:00
else
2019-02-05 18:08:21 +01:00
begin
2022-11-17 19:33:27 +01:00
SetLength( fTableMap. fMapping, fTableMap. fMap. Count) ;
2022-08-02 17:07:14 +02:00
I : = 0 ;
2019-02-05 18:08:21 +01:00
end ;
2018-09-28 13:01:46 +02:00
2022-11-17 19:33:27 +01:00
for lPair in fTableMap. fMap do
2018-11-02 21:43:09 +01:00
begin
2022-08-02 23:57:09 +02:00
lParentType : = lPair. Key. Parent;
2022-11-17 19:33:27 +01:00
fTableMap. fMapping[ I] . InstanceFieldName : = lPair. Key. Name . Substring( 1 ) . ToLower;
fTableMap. fMapping[ I] . DatabaseFieldName : = lPair. Value. FieldName;
2022-08-02 23:57:09 +02:00
lPropFromField : = lParentType. GetProperty( lPair. Key. Name . Substring( 1 ) ) ;
if Assigned( lPropFromField) then
begin
lTmp : = TMVCSerializerHelper. GetKeyName( lPropFromField, lParentType) ;
2022-11-17 19:33:27 +01:00
if not SameText( lTmp, fTableMap. fMapping[ I] . InstanceFieldName) then
2022-08-02 23:57:09 +02:00
begin
2022-11-17 19:33:27 +01:00
fTableMap. fMapping[ I] . Alias : = lTmp;
2022-08-02 23:57:09 +02:00
end ;
end ;
2022-08-02 17:07:14 +02:00
Inc( I) ;
2018-11-02 21:43:09 +01:00
end ;
2018-09-28 13:01:46 +02:00
end ;
2022-11-17 19:33:27 +01:00
Result : = fTableMap. fMapping;
2018-09-28 13:01:46 +02:00
end ;
2022-08-02 17:07:14 +02:00
class function TMVCActiveRecordHelper. GetOneByWhere< T> ( const SQLWhere: string ; const Params: array of Variant ;
2019-03-05 20:55:37 +01:00
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
2020-03-23 18:51:57 +01:00
raise EMVCActiveRecordNotFound. Create( 'Got 0 rows when exactly 1 was expected' ) ;
2018-10-23 16:18:34 +02:00
end ;
end ;
2022-08-02 17:07:14 +02:00
class function TMVCActiveRecordHelper. SelectOneByRQL< T> ( const RQL: string ; const RaiseExceptionIfNotFound: Boolean ) : T;
2020-06-22 15:24:20 +02:00
var
lAR: TMVCActiveRecord;
lSQL: string ;
begin
lAR : = T. Create;
try
lSQL : = lAR. SQLGenerator. CreateSQLWhereByRQL( RQL, lAR. GetMapping) . Trim;
2021-11-17 15:36:20 +01:00
lSQL : = TMVCSQLGenerator. RemoveInitialWhereKeyword( lSQL) ;
2020-06-22 15:24:20 +02:00
Result : = GetFirstByWhere< T> ( lSQL, [ ] , RaiseExceptionIfNotFound) ;
if Result = nil then
begin
if RaiseExceptionIfNotFound then
raise EMVCActiveRecordNotFound. Create( 'Got 0 rows when exactly 1 was expected' ) ;
end ;
finally
lAR. Free;
end ;
end ;
2023-08-08 14:31:23 +02:00
class function TMVCActiveRecordHelper. SelectRQL< T> ( const RQL: string ;
const MaxRecordCount: Integer ; const OutList: TObjectList< T> ) : UInt32 ;
var
lAR: TMVCActiveRecord;
lSQL: string ;
begin
lAR : = T. Create;
try
lSQL : = lAR. SQLGenerator. CreateSQLWhereByRQL( RQL, lAR. GetMapping, MaxRecordCount > - 1 , false , MaxRecordCount) . Trim;
lSQL : = TMVCSQLGenerator. RemoveInitialWhereKeyword( lSQL) ;
Result : = Where< T> ( lSQL, [ ] , [ ] , OutList) ;
finally
lAR. Free;
end ;
end ;
2023-08-09 10:55:34 +02:00
class function TMVCActiveRecordHelper. SelectRQLByNamedQuery< T> (
const QueryName: string ;
const Params: array of const ;
const MaxRecordCount: Integer ) : TObjectList< T> ;
var
lT: T;
lRQLQuery: TRQLQueryWithName;
begin
lT : = T. Create;
try
if not lT. FindRQLQueryByName( QueryName, lRQLQuery) then
begin
raise EMVCActiveRecord. CreateFmt( 'NamedRQLQuery not found: %s' , [ QueryName] ) ;
end ;
Result : = SelectRQL< T> ( Format( lRQLQuery. RQLText, Params) , MaxRecordCount) ;
finally
lT. Free;
end ;
end ;
2023-08-08 14:31:23 +02:00
class function TMVCActiveRecordHelper. Where< T> ( const SQLWhere: string ;
const Params: array of Variant ; const ParamTypes: array of TFieldType;
const OutList: TObjectList< T> ) : UInt32 ;
var
lAR: TMVCActiveRecord;
lFilter: string ;
begin
lAR : = T. Create;
try
lFilter : = lAR. SQLGenerator. GetDefaultSQLFilter( True ) ;
if SQLWhere. Trim. IsEmpty( ) or SQLWhere. Trim. StartsWith( '/*limit*/' ) or SQLWhere. Trim. StartsWith( '/*sort*/' ) then
begin
Result : = Select< T> ( lAR. GenerateSelectSQL + lFilter + SQLWhere, Params, ParamTypes, [ ] , OutList) ;
end
else
begin
if lFilter. IsEmpty then
begin
Result : = Select< T> ( lAR. GenerateSelectSQL + ' WHERE ' + SQLWhere, Params, ParamTypes, [ ] , OutList) ;
end
else
begin
Result : = Select< T> ( lAR. GenerateSelectSQL + lFilter + ' AND ' + SQLWhere, Params, ParamTypes, [ ] , OutList) ;
end ;
end ;
finally
lAR. Free;
end ;
end ;
2021-11-18 17:52:06 +01:00
function TMVCActiveRecord. GetPartitionInfo: TPartitionInfo;
var
lRQLCompilerClass: TRQLCompilerClass;
begin
2022-11-17 19:33:27 +01:00
if fTableMap. fPartitionInfoInternal = nil then
2021-11-18 17:52:06 +01:00
begin
lRQLCompilerClass : = TRQLCompilerRegistry. Instance. GetCompiler( GetBackEnd) ;
2022-11-17 19:33:27 +01:00
fTableMap. fPartitionInfoInternal : = TPartitionInfo. BuildPartitionClause( fTableMap. fPartitionClause, lRQLCompilerClass) ;
2021-11-18 17:52:06 +01:00
end ;
2022-11-17 19:33:27 +01:00
Result : = fTableMap. fPartitionInfoInternal;
2021-11-18 17:52:06 +01:00
end ;
2018-09-25 15:36:53 +02:00
function TMVCActiveRecord. GetPK: TValue;
2020-02-03 13:19:55 +01:00
var
lIsNullableType: Boolean ;
2018-09-25 15:36:53 +02:00
begin
2020-02-03 13:19:55 +01:00
if not TryGetPKValue( Result , lIsNullableType) then
2020-01-04 12:53:53 +01:00
begin
2020-02-03 13:19:55 +01:00
if not lIsNullableType then
begin
raise EMVCActiveRecord. Create( 'Primary key not available' ) ;
end ;
2020-01-04 12:53:53 +01:00
end ;
end ;
2021-04-25 22:40:06 +02:00
function TMVCActiveRecord. PKIsNull: Boolean ;
var
lValue: TValue;
lIsNullableType: Boolean ;
begin
if not PKIsNullable( lValue) then
begin
raise EMVCActiveRecord. Create( 'PK is not nullable' ) ;
end ;
Result : = not TryGetPKValue( lValue, lIsNullableType) ;
end ;
function TMVCActiveRecord. PKIsNullable( out PKValue: TValue) : Boolean ;
var
lValue: TValue;
begin
PKValue : = TryGetPKValue( lValue, Result ) ;
end ;
2020-03-31 16:23:22 +02:00
function TMVCActiveRecord. GetPrimaryKeyFieldType: TFieldType;
begin
2022-11-17 19:33:27 +01:00
Result : = fTableMap. fPrimaryKeyFieldType;
2020-03-31 16:23:22 +02:00
end ;
2020-01-04 12:53:53 +01:00
function TMVCActiveRecord. GetPrimaryKeyIsAutogenerated: Boolean ;
begin
2022-11-17 19:33:27 +01:00
Result : = foAutoGenerated in fTableMap. fPrimaryKeyOptions;
2018-09-25 15:36:53 +02:00
end ;
2022-08-02 17:07:14 +02: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 ;
2022-11-17 19:33:27 +01:00
function TMVCActiveRecord. GetTableName: string ;
begin
if fCustomTableName. IsEmpty then
Result : = fTableMap. fTableName
else
Result : = fCustomTableName;
end ;
2022-08-02 17:07:14 +02:00
function TMVCActiveRecord. CheckAction( const aEntityAction: TMVCEntityAction; const aRaiseException: Boolean ) : Boolean ;
2018-10-14 18:23:20 +02:00
begin
2022-11-17 19:33:27 +01:00
Result : = aEntityAction in fTableMap. fEntityAllowedActions;
2018-10-14 18:23:20 +02:00
if ( not Result ) and aRaiseException then
2020-09-30 11:16:10 +02:00
raise EMVCActiveRecord. CreateFmt
2022-11-10 18:30:11 +01:00
( 'Action [%s] not allowed on entity [%s]. [HINT] If this isn' 't the expected behavior, add the entity action in MVCEntityActions attribute.' ,
2023-06-15 23:42:07 +02:00
[ GetEnumName( TypeInfo( TMVCEntityAction) , Ord( aEntityAction) ) , ClassName] ) at ReturnAddress;
2018-10-14 18:23:20 +02:00
end ;
2023-08-09 00:46:31 +02:00
class function TMVCActiveRecordHelper. Count( const aClass: TMVCActiveRecordClass; const RQL: string ) : int64 ;
2019-01-13 19:18:57 +01:00
var
lAR: TMVCActiveRecord;
begin
lAR : = aClass. Create;
try
2020-08-30 08:52:21 +02:00
// Up to 10.1 Berlin, here the compiler try to call the Count<T> introduced by the class helper
2020-01-04 12:53:53 +01:00
// Instead of the Count() which exists in "TMVCActiveRecord"
2019-08-02 12:32:23 +02:00
Result : = lAR. InternalCount( RQL) ;
2019-01-13 19:18:57 +01:00
finally
lAR. Free;
end ;
end ;
2023-08-09 00:46:31 +02:00
function TMVCActiveRecordHelper. Count( const RQL: string = '' ) : int64 ;
2019-05-09 20:53:52 +02:00
begin
2019-08-02 12:32:23 +02:00
Result : = InternalCount( RQL) ;
2019-05-09 20:53:52 +02:00
end ;
2020-03-23 18:51:57 +01:00
class function TMVCActiveRecordHelper. Count< T> ( const RQL: string = '' ) : int64 ;
2019-05-09 20:53:52 +02:00
begin
Result : = TMVCActiveRecord. Count( TMVCActiveRecordClass( T) , RQL) ;
2019-01-13 19:18:57 +01:00
end ;
2023-08-09 10:55:34 +02:00
class function TMVCActiveRecordHelper. CountRQLByNamedQuery< T> (
const QueryName: string ;
const Params: array of const ) : Int64 ;
var
lRQLQuery: TRQLQueryWithName;
lT: T;
begin
lT : = T. Create;
try
if not lT. FindRQLQueryByName( QueryName, lRQLQuery) then
begin
raise EMVCActiveRecord. CreateFmt( 'NamedRQLQuery not found: %s' , [ QueryName] ) ;
end ;
Result : = Count< T> ( Format( lRQLQuery. RQLText, Params) ) ;
finally
lT. Free;
end ;
end ;
2023-08-30 11:58:50 +02:00
class function TMVCActiveRecordHelper. CreateMVCActiveRecord< T> (
AQualifiedClassName: string ; const AParams: TArray< TValue> ) : T;
var
lTmp: TObject;
begin
lTmp : = TRttiUtils. CreateObject( AQualifiedClassName, AParams) ;
try
Result : = lTmp as T;
except
on E: EInvalidCast do
begin
lTmp. Free;
raise EMVCActiveRecord. Create( AQualifiedClassName + ' is not a TMVCActiveRecord descendant' ) ;
end ;
end ;
end ;
2023-06-15 23:42:07 +02:00
class function TMVCActiveRecordHelper. DeleteRQL< T> ( const RQL: string ) : int64 ;
begin
Result : = TMVCActiveRecord. DeleteRQL( TMVCActiveRecordClass( T) , RQL) ;
end ;
2023-08-09 10:55:34 +02:00
class function TMVCActiveRecordHelper. DeleteRQLByNamedQuery< T> (
const QueryName: String ;
const Params: array of const ) : Int64 ;
var
lRQLQuery: TRQLQueryWithName;
lT: T;
begin
lT : = T. Create;
try
if not lT. FindRQLQueryByName( QueryName, lRQLQuery) then
begin
raise EMVCActiveRecord. CreateFmt( 'NamedRQLQuery not found: %s' , [ QueryName] ) ;
end ;
Result : = DeleteRQL< T> ( Format( lRQLQuery. RQLText, Params) ) ;
finally
lT. Free;
end ;
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 ;
2020-02-03 10:51:40 +01:00
function TMVCActiveRecord. GetConnection: 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
2020-02-03 10:51:40 +01:00
Create( True ) ;
2018-09-28 13:01:46 +02:00
end ;
2021-06-27 15:14:37 +02:00
procedure TMVCActiveRecord. Delete( const RaiseExceptionIfNotFound: Boolean ) ;
2018-09-25 15:36:53 +02:00
var
SQL: string ;
2022-08-02 17:07:14 +02:00
lAffectedRows: int64 ;
2018-09-25 15:36:53 +02:00
begin
2020-03-30 13:30:45 +02:00
CheckAction( TMVCEntityAction. eaDelete) ;
OnValidation( TMVCEntityAction. eaDelete) ;
2018-10-23 16:18:34 +02:00
OnBeforeDelete;
2022-11-17 19:33:27 +01:00
if not Assigned( fTableMap. fPrimaryKey) then
2019-03-05 20:55:37 +01:00
raise Exception. CreateFmt( 'Cannot delete %s without a primary key' , [ ClassName] ) ;
2022-11-17 19:33:27 +01:00
SQL : = SQLGenerator. CreateDeleteSQL( TableName, fTableMap. fMap,
fTableMap. fPrimaryKeyFieldName, fTableMap. fPrimaryKeyOptions) ;
2021-06-27 15:14:37 +02:00
lAffectedRows : = ExecNonQuery( SQL, false ) ;
if ( lAffectedRows = 0 ) and RaiseExceptionIfNotFound then
begin
raise EMVCActiveRecordNotFound. CreateFmt( 'No record deleted for key [Entity: %s][PK: %s]' ,
2022-11-17 19:33:27 +01:00
[ ClassName, fTableMap. fPrimaryKeyFieldName] ) ;
2021-06-27 15:14:37 +02:00
end ;
2018-10-23 16:18:34 +02:00
OnAfterDelete;
2018-09-25 15:36:53 +02:00
end ;
2023-08-09 00:46:31 +02:00
class function TMVCActiveRecordHelper. DeleteAll( const aClass: TMVCActiveRecordClass) : int64 ;
2019-02-21 18:11:14 +01:00
var
lAR: TMVCActiveRecord;
begin
lAR : = aClass. Create;
try
2022-11-17 19:33:27 +01:00
Result : = lAR. ExecNonQuery( lAR. SQLGenerator. CreateDeleteAllSQL( lAR. fTableMap. fTableName) +
2022-08-02 17:07:14 +02:00
lAR. SQLGenerator. GetDefaultSQLFilter( True ) ) ;
2019-02-21 18:11:14 +01:00
finally
lAR. Free;
end ;
end ;
2023-08-09 00:46:31 +02:00
class function TMVCActiveRecordHelper. 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
2022-11-17 19:33:27 +01:00
Result : = lAR. ExecNonQuery( lAR. SQLGenerator. CreateDeleteAllSQL( lAR. fTableMap. fTableName) +
2019-03-05 20:55:37 +01:00
lAR. SQLGenerator. CreateSQLWhereByRQL( RQL, lAR. GetMapping, false ) ) ;
2019-02-21 18:11:14 +01:00
finally
lAR. Free;
end ;
end ;
2022-08-02 17:07:14 +02:00
procedure TMVCActiveRecord. MapDatasetToObject( const DataSet: TDataSet; const Options: TMVCActiveRecordLoadOptions;
2019-11-27 19:04:06 +01:00
var Handled: Boolean ) ;
begin
2020-01-04 12:53:53 +01:00
// do nothing
2019-11-27 19:04:06 +01:00
end ;
2020-08-28 18:04:29 +02:00
procedure TMVCActiveRecord. MapObjectToParams( const Params: TFDParams; var Handled: Boolean ) ;
2019-11-27 19:04:06 +01:00
begin
2020-01-04 12:53:53 +01:00
// do nothing
2019-11-27 19:04:06 +01:00
end ;
2020-01-04 12:53:53 +01:00
function TMVCActiveRecord. MapNullableTValueToParam( aValue: TValue; const aParam: TFDParam) : Boolean ;
2022-08-01 19:11:42 +02:00
var
2022-08-02 17:07:14 +02:00
lNullableType: TNullableType;
2020-01-04 12:53:53 +01:00
begin
Assert( aValue. Kind = tkRecord) ;
2022-08-02 17:07:14 +02:00
Result : = True ;
lNullableType : = GetNullableType( aValue. TypeInfo) ;
case lNullableType of
ntInvalidNullableType:
begin
Exit( False ) ;
end ;
ntNullableString:
begin
if not aValue. AsType< NullableString> ( ) . HasValue then
begin
aParam. DataType : = ftString;
aParam. Clear;
Exit( True ) ;
end
else
begin
aValue : = aValue. AsType< NullableString> ( ) . Value;
end ;
end ;
ntNullableCurrency:
begin
if not aValue. AsType< NullableCurrency> ( ) . HasValue then
begin
aParam. DataType : = TFieldType. ftCurrency;
aParam. Clear;
Exit( True ) ;
end
else
begin
aValue : = aValue. AsType< NullableCurrency> ( ) . Value;
end ;
end ;
ntNullableBoolean:
begin
if not aValue. AsType< NullableBoolean> ( ) . HasValue then
begin
aParam. DataType : = ftBoolean;
aParam. Clear;
Exit( True ) ;
end
else
begin
aValue : = TValue. From< Boolean > ( aValue. AsType< NullableBoolean> ( ) . Value) ;
end ;
end ;
ntNullableTDate:
begin
if not aValue. AsType< NullableTDate> ( ) . HasValue then
begin
aParam. DataType : = ftDate;
aParam. Clear;
Exit( True ) ;
end
else
begin
aValue : = TValue. From< TDate> ( aValue. AsType< NullableTDate> ( ) . Value) ;
end ;
end ;
ntNullableTTime:
begin
if not aValue. AsType< NullableTTime> ( ) . HasValue then
begin
aParam. DataType : = ftTime;
aParam. Clear;
Exit( True ) ;
end
else
begin
aValue : = TValue. From< TTime> ( aValue. AsType< NullableTTime> ( ) . Value) ;
end ;
end ;
ntNullableTDateTime:
begin
if not aValue. AsType< NullableTDateTime> ( ) . HasValue then
begin
aParam. DataType : = ftDateTime;
aParam. Clear;
Exit( True ) ;
end
else
begin
aValue : = TValue. From< TDateTime> ( aValue. AsType< NullableTDateTime> ( ) . Value) ;
end ;
end ;
ntNullableSingle:
begin
if not aValue. AsType< NullableSingle> ( ) . HasValue then
begin
aParam. DataType : = TFieldType. ftSingle;
aParam. Clear;
Exit( True ) ;
end
else
begin
aValue : = aValue. AsType< NullableSingle> ( ) . Value;
end ;
end ;
ntNullableDouble:
begin
if not aValue. AsType< NullableDouble> ( ) . HasValue then
begin
aParam. DataType : = TFieldType. ftFloat;
aParam. Clear;
Exit( True ) ;
end
else
begin
aValue : = aValue. AsType< NullableDouble> ( ) . Value;
end ;
end ;
ntNullableExtended:
begin
if not aValue. AsType< NullableExtended> ( ) . HasValue then
begin
aParam. DataType : = TFieldType. ftExtended;
aParam. Clear;
Exit( True ) ;
end
else
begin
aValue : = aValue. AsType< NullableExtended> ( ) . Value;
end ;
end ;
ntNullableInt16:
begin
if not aValue. AsType< NullableInt16> ( ) . HasValue then
begin
aParam. DataType : = ftInteger;
aParam. Clear;
Exit( True ) ;
end
else
begin
aValue : = aValue. AsType< NullableInt16> ( ) . Value;
end ;
end ;
ntNullableUInt16:
begin
if not aValue. AsType< NullableUInt16> ( ) . HasValue then
begin
aParam. DataType : = ftInteger;
aParam. Clear;
Exit( True ) ;
end
else
begin
aValue : = aValue. AsType< NullableUInt16> ( ) . Value;
end ;
end ;
ntNullableInt32:
begin
if not aValue. AsType< NullableInt32> ( ) . HasValue then
begin
aParam. DataType : = ftInteger;
aParam. Clear;
Exit( True ) ;
end
else
begin
aValue : = aValue. AsType< NullableInt32> ( ) . Value;
end ;
end ;
ntNullableUInt32:
begin
if not aValue. AsType< NullableUInt32> ( ) . HasValue then
begin
aParam. DataType : = ftInteger;
aParam. Clear;
Exit( True ) ;
end
else
begin
aValue : = aValue. AsType< NullableUInt32> ( ) . Value;
end ;
end ;
ntNullableInt64:
begin
if not aValue. AsType< NullableInt64> ( ) . HasValue then
begin
aParam. DataType : = ftLargeInt;
aParam. Clear;
Exit( True ) ;
end
else
begin
aValue : = aValue. AsType< NullableInt64> ( ) . Value;
end ;
end ;
ntNullableUInt64:
begin
if not aValue. AsType< NullableUInt64> ( ) . HasValue then
begin
aParam. DataType : = ftLargeInt;
aParam. Clear;
Exit( True ) ;
end
else
begin
aValue : = aValue. AsType< NullableUInt64> ( ) . Value;
end ;
end ;
ntNullableTGUID:
begin
if not aValue. AsType< NullableTGUID> ( ) . HasValue then
begin
2023-03-28 17:08:58 +02:00
aParam. DataType : = TFieldType. ftGuid;
2022-08-02 17:07:14 +02:00
aParam. Clear;
Exit( True ) ;
end
else
begin
aValue : = TValue. From< TGuid> ( aValue. AsType< NullableTGUID> ( ) . Value) ;
end ;
end ;
end ; // case
2022-06-19 18:57:47 +02:00
2022-08-02 17:07:14 +02:00
// the nullable value contains a value, so let's call
// the "non nullable" version of this procedure
MapTValueToParam( aValue, aParam) ;
2020-01-04 12:53:53 +01:00
end ;
procedure TMVCActiveRecord. MapTValueToParam( aValue: TValue; const aParam: TFDParam) ;
2021-08-02 14:53:11 +02:00
const
MAX_STRING_PARAM_LENGTH = 1 0 0 0 ; { Arbitrary value }
2018-09-25 15:36:53 +02:00
var
lStream: TStream;
2020-03-23 18:51:57 +01:00
lName: string ;
2018-09-25 15:36:53 +02:00
begin
2019-05-09 20:53:52 +02:00
{$IFDEF NEXTGEN}
lName : = aValue. TypeInfo. NameFld. ToString;
{$ELSE}
2020-03-23 18:51:57 +01:00
lName : = string( aValue. TypeInfo. Name ) ;
2019-05-09 20:53:52 +02:00
{$ENDIF}
2022-06-19 18:57:47 +02:00
if ( lName. StartsWith( 'Nullable' , True ) and ( aValue. TypeInfo. Kind = tkRecord) ) then
2020-01-04 12:53:53 +01:00
begin
if MapNullableTValueToParam( aValue, aParam) then
begin
Exit;
end ;
end ;
2018-10-14 18:23:20 +02:00
case aValue. TypeInfo. Kind of
2020-12-18 14:41:01 +01:00
tkUString:
2018-09-25 15:36:53 +02:00
begin
2019-12-17 14:52:11 +01:00
case aParam. DataType of
2021-04-05 19:35:46 +02:00
ftUnknown, ftWideString:
2020-12-18 14:41:01 +01:00
begin
2021-08-02 14:53:11 +02:00
if aValue. AsString. Length > MAX_STRING_PARAM_LENGTH then
begin
aParam. AsWideMemo : = aValue. AsString;
end
else
begin
aParam. AsWideString : = aValue. AsString;
end ;
2020-12-18 14:41:01 +01:00
end ;
2021-04-05 19:35:46 +02:00
ftString:
2020-12-18 14:41:01 +01:00
begin
2021-08-02 14:53:11 +02:00
if aValue. AsString. Length > MAX_STRING_PARAM_LENGTH then
begin
aParam. AsMemo : = AnsiString( aValue. AsString) ;
end
else
begin
aParam. AsString : = aValue. AsString;
end ;
2020-12-18 14:41:01 +01:00
end ;
ftWideMemo:
begin
aParam. AsWideMemo : = aValue. AsString;
end ;
ftMemo:
begin
aParam. AsMemo : = AnsiString( aValue. AsString) ;
end ;
else
begin
2022-08-02 17:07:14 +02:00
raise EMVCActiveRecord. CreateFmt( 'Invalid parameter type for (tkUString) [%s]' , [ lName] ) ;
2020-12-18 14:41:01 +01:00
end ;
end ;
end ;
tkString:
begin
case aParam. DataType of
2021-04-05 19:35:46 +02:00
ftUnknown, ftWideString:
2020-01-04 12:53:53 +01:00
begin
2021-08-02 14:53:11 +02:00
if aValue. AsString. Length > MAX_STRING_PARAM_LENGTH then
begin
aParam. AsWideMemo : = aValue. AsString;
end
else
begin
aParam. AsWideString : = aValue. AsString;
end ;
2020-01-04 12:53:53 +01:00
end ;
2021-04-05 19:35:46 +02:00
ftString:
2020-12-18 14:41:01 +01:00
begin
2021-08-02 14:53:11 +02:00
if aValue. AsString. Length > MAX_STRING_PARAM_LENGTH then
begin
aParam. AsMemo : = AnsiString( aValue. AsString) ;
end
else
begin
aParam. AsString : = aValue. AsString;
end ;
2020-12-18 14:41:01 +01:00
end ;
2020-01-04 12:53:53 +01:00
ftWideMemo:
begin
aParam. AsWideMemo : = aValue. AsString;
end ;
ftMemo:
begin
aParam. AsMemo : = AnsiString( aValue. AsString) ;
end ;
2019-12-17 14:52:11 +01:00
else
begin
2020-12-18 14:41:01 +01:00
raise EMVCActiveRecord. CreateFmt( 'Invalid parameter type for (tkString) [%s]' , [ lName] ) ;
2019-12-17 14:52:11 +01:00
end ;
end ;
2018-09-25 15:36:53 +02:00
end ;
2019-08-02 12:32:23 +02:00
{$IF Defined(SeattleOrBetter)}
2018-10-14 18:23:20 +02:00
tkWideString:
2018-09-25 15:36:53 +02:00
begin
2021-08-02 14:53:11 +02:00
if aValue. AsString. Length > MAX_STRING_PARAM_LENGTH then
begin
aParam. AsWideMemo : = aValue. AsString;
end
else
begin
aParam. AsWideString : = aValue. AsString;
end
2018-09-25 15:36:53 +02:00
end ;
2019-08-02 12:32:23 +02:00
{$ENDIF}
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
2022-10-21 23:08:20 +02:00
aParam. AsInteger : = aValue. AsOrdinal;
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
tkFloat:
2018-09-25 15:36:53 +02:00
begin
2019-05-09 20:53:52 +02:00
if lName = 'TDate' then
2018-10-14 18:23:20 +02:00
begin
aParam. AsDate : = Trunc( aValue. AsExtended) ;
end
2020-08-28 18:04:29 +02:00
else if lName = 'TDateTime' then
2020-01-04 12:53:53 +01:00
begin
aParam. AsDateTime : = aValue. AsExtended;
end
2020-08-28 18:04:29 +02:00
else if lName = 'TTime' then
2020-01-04 12:53:53 +01:00
begin
aParam. AsTime : = aValue. AsExtended;
end
2020-08-28 18:04:29 +02:00
else if lName = 'Currency' then
2020-01-04 12:53:53 +01:00
begin
aParam. AsCurrency : = aValue. AsCurrency;
end
else
begin
aParam. AsFloat : = aValue. AsExtended;
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
2020-01-06 16:49:18 +01:00
if ( aValue. AsObject < > nil ) and ( 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] ) ;
2020-12-18 14:41:01 +01:00
{ .$IF Defined(SeattleOrBetter) }
// lStream := aValue.AsType<TStream>();
{ .$ELSE }
2020-01-04 12:53:53 +01:00
lStream : = aValue. AsType< TStream> ( ) ;
2020-12-18 14:41:01 +01:00
{ .$ENDIF }
2018-09-25 15:36:53 +02:00
if Assigned( lStream) then
begin
lStream. Position : = 0 ;
aParam. LoadFromStream( lStream, ftBlob) ;
end
else
begin
2020-01-06 16:49:18 +01:00
aParam. DataType : = TFieldType. ftBlob;
2018-09-25 15:36:53 +02:00
aParam. Clear;
end ;
end ;
2019-03-05 20:55:37 +01:00
tkRecord:
begin
2022-08-02 17:07:14 +02:00
if aValue. TypeInfo = TypeInfo( TGuid) then
2019-03-05 20:55:37 +01:00
begin
2022-06-23 14:34:01 +02:00
if SQLGenerator. HasNativeUUID then
begin
2022-08-02 17:07:14 +02:00
aParam. AsGuid : = aValue. AsType< TGuid>
2022-06-23 14:34:01 +02:00
end
else
begin
2022-08-02 17:07:14 +02:00
aParam. AsString : = GUIDToString( aValue. AsType< TGuid> ) ;
2022-06-23 14:34:01 +02:00
end ;
2019-03-05 20:55:37 +01:00
end
2022-08-02 17:07:14 +02:00
else if aValue. TypeInfo = TypeInfo( NullableTGUID) then
2022-06-16 14:05:01 +02:00
begin
if aValue. AsType< NullableTGUID> . HasValue then
aParam. AsGuid : = aValue. AsType< NullableTGUID> . Value
else
aParam. Clear( ) ;
end
2019-03-05 20:55:37 +01:00
else
begin
raise Exception. CreateFmt( 'Unsupported Record TypeKind (%d) for param %s' ,
[ Ord( aValue. TypeInfo. Kind) , aParam. Name ] ) ;
end ;
end ;
2018-09-25 15:36:53 +02:00
else
2022-08-02 17:07:14 +02: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 ;
end ;
2022-08-02 17:07:14 +02:00
procedure TMVCActiveRecord. LoadByDataset( const aDataSet: TDataSet; const aOptions: TMVCActiveRecordLoadOptions) ;
2018-09-25 15:36:53 +02:00
var
2020-03-25 11:35:25 +01:00
lItem: TPair< TRTTIField, TFieldInfo> ;
2018-12-09 23:03:06 +01:00
lField: TField;
2019-11-27 19:04:06 +01:00
lHandled: Boolean ;
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;
2019-11-27 19:04:06 +01:00
2020-01-04 12:53:53 +01:00
lHandled : = false ;
2019-11-27 19:04:06 +01:00
MapDatasetToObject( aDataSet, aOptions, lHandled) ;
if not lHandled then
2018-09-25 15:36:53 +02:00
begin
2022-11-17 19:33:27 +01:00
for lItem in fTableMap. fMap do
2019-11-05 16:57:22 +01:00
begin
2020-03-25 11:35:25 +01:00
if not lItem. Value. Readable then
2019-11-27 19:04:06 +01:00
begin
2022-08-02 17:07:14 +02:00
Continue;
2019-11-27 19:04:06 +01:00
end ;
2020-03-25 11:35:25 +01:00
lField : = aDataSet. FindField( lItem. Value. FieldName) ;
2019-11-27 19:04:06 +01:00
if lField = nil then
begin
if TMVCActiveRecordLoadOption. loIgnoreNotExistentFields in aOptions then
2022-08-02 17:07:14 +02:00
Continue
2019-11-27 19:04:06 +01:00
else
raise EMVCActiveRecord. CreateFmt
2020-01-04 12:53:53 +01:00
( 'Field [%s] not found in dataset. [HINT] If you dont need it, use loIgnoreNotExistentFields' ,
2020-03-25 11:35:25 +01:00
[ lItem. Value. FieldName] ) ;
2019-11-27 19:04:06 +01:00
end ;
2020-02-05 23:46:38 +01:00
MapDataSetFieldToRTTIField( lField, lItem. Key, Self) ;
2019-11-05 16:57:22 +01:00
end ;
2022-11-17 19:33:27 +01:00
if not fTableMap. fPrimaryKeyFieldName. IsEmpty then
2018-12-09 23:03:06 +01:00
begin
2022-11-17 19:33:27 +01:00
MapDataSetFieldToRTTIField( aDataSet. FieldByName( fTableMap. fPrimaryKeyFieldName) , fTableMap. fPrimaryKey, Self) ;
2018-12-09 23:03:06 +01:00
end ;
2018-09-25 15:36:53 +02:00
end ;
OnAfterLoad;
end ;
2022-05-13 17:23:00 +02:00
function TMVCActiveRecord. LoadByPK( const id: string ; const aFieldType: TFieldType) : 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) ;
2022-11-17 19:33:27 +01:00
SQL : = SQLGenerator. CreateSelectByPKSQL( TableName, fTableMap. fMap,
fTableMap. fPrimaryKeyFieldName, fTableMap. fPrimaryKeyOptions) ;
2023-01-02 22:09:42 +01:00
lDataSet : = ExecQuery( SQL, [ id] , [ aFieldType] , GetConnection, True , False ) ;
2020-03-31 16:23:22 +02:00
try
Result : = not lDataSet. Eof;
if Result then
begin
LoadByDataset( lDataSet) ;
end ;
finally
lDataSet. Free;
end ;
end ;
2022-05-13 17:23:00 +02:00
function TMVCActiveRecord. LoadByPK( const id: string ) : Boolean ;
begin
Result : = LoadByPK( id, ftString) ;
end ;
2020-03-31 16:23:22 +02:00
function TMVCActiveRecord. LoadByPK( const id: int64 ) : Boolean ;
begin
2022-05-13 17:23:00 +02:00
Result : = LoadByPK( id. ToString, ftInteger) ;
end ;
function TMVCActiveRecord. LoadByPK( const id: TGuid) : Boolean ;
begin
Result : = LoadByPK( id. ToString, ftGuid) ;
2018-09-25 15:36:53 +02:00
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 ;
2020-03-23 18:51:57 +01:00
procedure TMVCActiveRecord. OnBeforeExecuteSQL( var SQL: string ) ;
2019-03-18 14:08:34 +01:00
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 ;
2020-03-30 13:30:45 +02:00
procedure TMVCActiveRecord. OnValidation( const EntityAction: TMVCEntityAction) ;
2018-09-25 15:36:53 +02:00
begin
// do nothing
end ;
2023-01-29 17:29:24 +01:00
procedure TMVCActiveRecord. Refresh;
begin
if not GetPK. IsEmpty then
begin
case GetPrimaryKeyFieldType of
ftLargeInt: begin
LoadByPK( GetPK. AsInt64) ;
end ;
ftInteger: begin
LoadByPK( GetPK. AsInteger) ;
end ;
ftString: begin
LoadByPK( GetPK. AsString) ;
end ;
ftGuid: begin
LoadByPK( GetPK. AsType< TGUID> ) ;
end ;
else
raise EMVCActiveRecord. Create( 'Unknown primary key type' ) ;
end ;
end ;
end ;
2020-05-04 09:42:16 +02:00
procedure TMVCActiveRecord. RemoveChildren( const ChildObject: TObject) ;
begin
if fChildren < > nil then
begin
fChildren. Extract( ChildObject) ;
end ;
end ;
2020-03-30 13:30:45 +02:00
procedure TMVCActiveRecord. InvalidateConnection( const ReacquireAfterInvalidate: Boolean = false ) ;
begin
FreeAndNil( fConn) ;
if ReacquireAfterInvalidate then
begin
EnsureConnection;
end ;
end ;
2023-08-09 00:46:31 +02:00
class function TMVCActiveRecordHelper. Select( const aClass: TMVCActiveRecordClass; const SQL: string ;
2019-03-05 20:55:37 +01:00
const Params: array of Variant ) : TMVCActiveRecordList;
2018-09-28 13:01:46 +02:00
begin
Result : = Select( aClass, SQL, Params, nil ) ;
end ;
2023-08-09 00:46:31 +02:00
class function TMVCActiveRecordHelper. Select( const aClass: TMVCActiveRecordClass; const SQL: string ;
2019-03-05 20:55:37 +01:00
const Params: array of Variant ; const Connection: TFDConnection) : TMVCActiveRecordList;
2018-09-25 15:36:53 +02:00
begin
2018-10-23 16:18:34 +02:00
Result : = TMVCActiveRecordList. Create;
2018-09-25 15:36:53 +02:00
try
2023-08-08 14:31:23 +02:00
Select( aClass, SQL, Params, Connection, Result ) ;
2018-09-25 15:36:53 +02:00
except
Result . Free;
raise ;
end ;
end ;
2023-08-09 00:46:31 +02:00
class function TMVCActiveRecordHelper. SelectDataSet( const SQL: string ; const Params: array of Variant ;
2023-01-02 22:09:42 +01:00
const ParamTypes: array of TFieldType; const Unidirectional: Boolean ; const DirectExecute: Boolean ) : TDataSet;
2020-03-23 18:51:57 +01:00
begin
2023-01-02 22:09:42 +01:00
Result : = TMVCActiveRecord. ExecQuery( SQL, Params, ParamTypes, Unidirectional, DirectExecute) ;
2020-03-23 18:51:57 +01:00
end ;
2019-08-02 12:32:23 +02:00
class function TMVCActiveRecordHelper. Select< T> ( const SQL: string ; const Params: array of Variant ;
2019-03-05 20:55:37 +01:00
const Options: TMVCActiveRecordLoadOptions) : TObjectList< T> ;
2020-03-23 18:51:57 +01:00
begin
Result : = Select< T> ( SQL, Params, [ ] , Options) ;
end ;
2023-08-09 00:46:31 +02:00
class function TMVCActiveRecordHelper. SelectDataSet( const SQL: string ; const Params: array of Variant ;
2023-01-02 22:09:42 +01:00
const Unidirectional: Boolean ; const DirectExecute: Boolean ) : TDataSet;
2020-03-23 18:51:57 +01:00
begin
2023-01-02 22:09:42 +01:00
Result : = TMVCActiveRecord. ExecQuery( SQL, Params, Unidirectional, DirectExecute) ;
2020-03-23 18:51:57 +01:00
end ;
2023-08-09 00:46:31 +02:00
function TMVCActiveRecordHelper. SelectRQL( const RQL: string ; const MaxRecordCount: Integer ) : TMVCActiveRecordList;
2020-03-23 18:51:57 +01:00
begin
Result : = InternalSelectRQL( RQL, MaxRecordCount) ;
end ;
2023-08-09 00:46:31 +02:00
class function TMVCActiveRecordHelper. SelectRQL( const aClass: TMVCActiveRecordClass;
2023-08-08 14:31:23 +02:00
const RQL: string ; const MaxRecordCount: Integer ;
const OutList: TMVCActiveRecordList) : UInt32 ;
var
lAR: TMVCActiveRecord;
begin
lAR : = aClass. Create( True ) ;
try
Result : = lAR. InternalSelectRQL( RQL, MaxRecordCount, OutList) ;
finally
lAR. Free;
end ;
end ;
class function TMVCActiveRecordHelper. Select< T> ( const SQL: string ; const Params: array of Variant ;
const ParamTypes: array of TFieldType; const Options: TMVCActiveRecordLoadOptions; const OutList: TObjectList< T> ) : UInt32 ;
var
lDataSet: TDataSet;
lAR: TMVCActiveRecord;
begin
lDataSet : = ExecQuery( SQL, Params, ParamTypes, True , False ) ;
try
while not lDataSet. Eof do
begin
lAR : = T. Create;
OutList. Add( lAR) ;
lAR. LoadByDataset( lDataSet, Options) ;
lDataSet. Next;
end ;
Result : = OutList. Count;
finally
lDataSet. Free;
end ;
end ;
2023-08-09 01:23:24 +02:00
class function TMVCActiveRecordHelper. SelectByNamedQuery< T> (
const QueryName: String ; const Params: array of Variant ;
const ParamTypes: array of TFieldType;
const Options: TMVCActiveRecordLoadOptions) : TObjectList< T> ;
var
lT: T;
2023-08-09 10:55:34 +02:00
lSQLQuery: TSQLQueryWithName;
2023-08-09 01:23:24 +02:00
begin
lT : = T. Create;
try
if not lT. FindSQLQueryByName( QueryName, lSQLQuery) then
begin
raise EMVCActiveRecord. CreateFmt( 'NamedSQLQuery not found: %s' , [ QueryName] ) ;
end ;
2023-08-09 10:55:34 +02:00
Result : = Select< T> ( lSQLQuery. SQLText, Params, ParamTypes, Options) ;
2023-08-09 01:23:24 +02:00
finally
lT. Free;
end ;
end ;
2020-08-28 18:04:29 +02:00
class function TMVCActiveRecordHelper. Select< T> ( const SQL: string ; const Params: array of Variant ;
2022-08-02 17:07:14 +02:00
const ParamTypes: array of TFieldType; const Options: TMVCActiveRecordLoadOptions) : TObjectList< T> ;
2018-09-25 15:36:53 +02:00
var
lDataSet: TDataSet;
lAR: TMVCActiveRecord;
2019-11-27 19:04:06 +01:00
lHandled: Boolean ;
2018-09-25 15:36:53 +02:00
begin
Result : = TObjectList< T> . Create( True ) ;
try
2023-08-08 14:31:23 +02:00
Select< T> ( SQL, Params, ParamTypes, Options, Result ) ;
2018-09-25 15:36:53 +02:00
except
Result . Free;
raise ;
end ;
end ;
2020-08-28 18:04:29 +02:00
class function TMVCActiveRecordHelper. SelectOne< T> ( const SQL: string ; const Params: array of Variant ;
const RaiseExceptionIfNotFound: Boolean ) : T;
2020-03-30 13:30:45 +02:00
begin
Result : = SelectOne< T> ( SQL, Params, [ ] , [ ] , RaiseExceptionIfNotFound) ;
end ;
2020-08-28 18:04:29 +02:00
class function TMVCActiveRecordHelper. SelectOne< T> ( const SQL: string ; const Params: array of Variant ;
const ParamTypes: array of TFieldType; const Options: TMVCActiveRecordLoadOptions;
2020-03-30 13:30:45 +02:00
const RaiseExceptionIfNotFound: Boolean ) : T;
var
lDataSet: TDataSet;
lAR: TMVCActiveRecord;
lHandled: Boolean ;
lList: TObjectList< T> ;
begin
Result : = nil ;
lList : = Select< T> ( SQL, Params, ParamTypes, Options) ;
try
if ( lList. Count = 0 ) then
begin
if RaiseExceptionIfNotFound then
raise EMVCActiveRecordNotFound. Create( 'Got 0 rows when exactly 1 was expected' )
else
Exit( nil ) ;
end ;
if lList. Count > 1 then
begin
2022-08-02 17:07:14 +02:00
raise EMVCActiveRecordNotFound. CreateFmt( 'Got %d rows when exactly 1 was expected' , [ lList. Count] ) ;
2020-03-30 13:30:45 +02:00
end ;
2020-04-02 19:26:04 +02:00
Result : = lList. Extract( lList. First) ;
2020-03-30 13:30:45 +02:00
finally
lList. Free;
end ;
end ;
2022-08-02 17:07:14 +02:00
class function TMVCActiveRecordHelper. 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
2022-08-02 17:07:14 +02:00
lSQL : = lAR. SQLGenerator. CreateSQLWhereByRQL( RQL, lAR. GetMapping, MaxRecordCount > - 1 , false , MaxRecordCount) . Trim;
2021-11-17 15:36:20 +01:00
lSQL : = TMVCSQLGenerator. RemoveInitialWhereKeyword( lSQL) ;
2018-11-09 18:11:59 +01:00
Result : = Where< T> ( lSQL, [ ] ) ;
finally
lAR. Free;
end ;
end ;
2022-08-02 17:07:14 +02:00
class function TMVCActiveRecordHelper. Where< T> ( const SQLWhere: string ; const Params: array of Variant ;
2020-03-23 18:51:57 +01:00
const ParamTypes: array of TFieldType) : TObjectList< T> ;
begin
2023-08-08 14:31:23 +02:00
Result : = TObjectList< T> . Create( True ) ;
2020-03-23 18:51:57 +01:00
try
2023-08-08 14:31:23 +02:00
Where< T> ( SQLWhere, Params, ParamTypes, Result ) ;
except
Result . Free;
raise ;
2020-03-23 18:51:57 +01:00
end ;
end ;
2023-08-09 00:46:31 +02:00
class function TMVCActiveRecordHelper. SelectRQL( const aClass: TMVCActiveRecordClass; const RQL: string ;
2019-03-05 20:55:37 +01:00
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
2019-08-02 12:32:23 +02:00
Result : = lAR. InternalSelectRQL( 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 ;
2020-08-28 18:04:29 +02:00
procedure TMVCActiveRecord. SetAttributes( const AttrName: string ; const Value: TValue) ;
var
lProperty: TRttiProperty;
begin
if not TRttiUtils. ExistsProperty( Self, AttrName, lProperty) then
begin
raise EMVCActiveRecord. CreateFmt( 'Attribute [%s] not found' , [ AttrName] ) ;
end ;
SetPropertyValue( lProperty, Value) ;
end ;
procedure TMVCActiveRecord. SetPropertyValue( const aProp: TRttiProperty; const aValue: TValue) ;
var
lCurrValue: TValue;
lNullableString: NullableString;
lNullableUInt32: NullableUInt32;
lNullableUInt64: NullableUInt64;
lNullableInt64: NullableInt64;
lNullableBoolean: NullableBoolean;
lNullableTDateTime: NullableTDateTime;
lNullableTDate: NullableTDate;
lNullableTTime: NullableTTime;
begin
if aProp. GetValue( Self) . Kind = tkRecord then
begin
lCurrValue : = aProp. GetValue( Self) ;
if lCurrValue. IsType< NullableInt32> then
begin
if aValue. IsType< NullableInt32> ( ) then
begin
lCurrValue : = aValue;
end
else
begin
lCurrValue : = TValue. From< NullableInt32> ( IntToNullableInt( aValue. AsInteger) ) ;
end
end
else if lCurrValue. IsType< NullableInt64> then
begin
if aValue. IsType< NullableInt64> ( ) then
begin
lCurrValue : = aValue;
end
else
begin
lNullableInt64 : = aValue. AsInt64;
lCurrValue : = TValue. From< NullableInt64> ( lNullableInt64) ;
end ;
end
else if lCurrValue. IsType< NullableString> then
begin
if aValue. IsType< NullableString> ( ) then
begin
lCurrValue : = aValue;
end
else
begin
lNullableString : = aValue. AsString;
lCurrValue : = TValue. From< NullableString> ( lNullableString) ;
end ;
end
else if lCurrValue. IsType< NullableUInt32> then
begin
if aValue. IsType< NullableUInt32> ( ) then
begin
lCurrValue : = aValue;
end
else
begin
lNullableUInt32 : = aValue. AsInteger;
lCurrValue. From< NullableUInt32> ( lNullableUInt32) ;
end ;
end
else if lCurrValue. IsType< NullableUInt64> then
begin
if aValue. IsType< NullableUInt64> ( ) then
begin
lCurrValue : = aValue;
end
else
begin
lNullableUInt64 : = aValue. AsUInt64;
lCurrValue. From< NullableUInt64> ( lNullableUInt64) ;
end ;
end
else if lCurrValue. IsType< NullableBoolean> then
begin
if aValue. IsType< NullableBoolean> ( ) then
begin
lCurrValue : = aValue;
end
else
begin
lNullableBoolean : = aValue. AsBoolean;
lCurrValue. From< NullableBoolean> ( lNullableBoolean) ;
end ;
end
else if lCurrValue. IsType< NullableTDateTime> then
begin
if aValue. IsType< NullableTDateTime> ( ) then
begin
lCurrValue : = aValue;
end
else
begin
2021-05-03 19:29:01 +02:00
{$IF Defined(TOKYOORBETTER)}
2020-08-28 18:04:29 +02:00
lNullableTDateTime : = TDateTime( aValue. AsExtended) ;
2021-05-03 19:29:01 +02:00
{$ELSE}
2021-01-05 17:01:08 +01:00
lNullableTDateTime : = aValue. AsExtended;
2021-05-03 19:29:01 +02:00
{$ENDIF}
2020-08-28 18:04:29 +02:00
lCurrValue. From< NullableTDateTime> ( lNullableTDateTime) ;
end ;
end
else if lCurrValue. IsType< NullableTDate> then
begin
if aValue. IsType< NullableTDate> ( ) then
begin
lCurrValue : = aValue;
end
else
begin
2021-05-03 19:29:01 +02:00
{$IF Defined(TOKYOORBETTER)}
2020-08-28 18:04:29 +02:00
lNullableTDate : = TDate( aValue. AsExtended) ;
2021-05-03 19:29:01 +02:00
{$ELSE}
2021-01-05 17:01:08 +01:00
lNullableTDate : = aValue. AsExtended;
2021-05-03 19:29:01 +02:00
{$ENDIF}
2020-08-28 18:04:29 +02:00
lCurrValue. From< NullableTDate> ( lNullableTDate) ;
end ;
end
else if lCurrValue. IsType< NullableTTime> then
begin
if aValue. IsType< NullableTTime> ( ) then
begin
lCurrValue : = aValue;
end
else
begin
2021-05-03 19:29:01 +02:00
{$IF Defined(TOKYOORBETTER)}
2020-08-28 18:04:29 +02:00
lNullableTTime : = TTime( aValue. AsExtended) ;
2021-05-03 19:29:01 +02:00
{$ELSE}
2021-01-05 17:01:08 +01:00
lNullableTTime : = aValue. AsExtended;
2021-05-03 19:29:01 +02:00
{$ENDIF}
2020-08-28 18:04:29 +02:00
lCurrValue. From< NullableTTime> ( lNullableTTime) ;
end ;
end
else
begin
raise EMVCActiveRecord. Create( 'Invalid data type for dynamic property access' ) ;
end ;
aProp. SetValue( Self, lCurrValue) ;
end
else
begin
aProp. SetValue( Self, aValue)
end ;
end ;
2018-09-25 15:36:53 +02:00
procedure TMVCActiveRecord. SetPK( const aValue: TValue) ;
2020-01-08 15:30:10 +01:00
var
lPKValue: TValue;
2018-09-25 15:36:53 +02:00
begin
2022-11-17 19:33:27 +01:00
if fTableMap. fPrimaryKeyFieldName. IsEmpty then
2020-01-08 15:30:10 +01:00
begin
2018-09-25 15:36:53 +02:00
raise Exception. Create( 'No primary key defined' ) ;
2020-01-08 15:30:10 +01:00
end ;
2020-08-28 18:04:29 +02:00
2022-11-17 19:33:27 +01:00
if fTableMap. fPrimaryKey. GetValue( Self) . Kind = tkRecord then
2020-01-08 15:30:10 +01:00
begin
2022-11-17 19:33:27 +01:00
lPKValue : = fTableMap. fPrimaryKey. GetValue( Self) ;
2022-08-02 17:07:14 +02:00
if lPKValue. IsType< NullableInt32> { and aValue.IsType<NullableInt32>() } then
2020-01-08 15:30:10 +01:00
begin
2022-08-01 19:11:42 +02:00
if aValue. IsType< Int32 > then
2020-01-08 15:30:10 +01:00
begin
lPKValue : = TValue. From< NullableInt32> ( IntToNullableInt( aValue. AsInteger) ) ;
2022-08-01 19:11:42 +02:00
end
else
begin
raise EMVCActiveRecord. Create( 'Invalid type for primary key' ) ;
2020-01-08 15:30:10 +01:00
end ;
end
2020-08-13 17:40:02 +02:00
else if lPKValue. IsType< NullableInt64> and aValue. IsType< NullableInt64> ( ) then
2020-01-08 15:30:10 +01:00
begin
if aValue. AsType< NullableInt64> ( ) . HasValue then
begin
lPKValue : = aValue;
end
else
begin
lPKValue. AsType< NullableInt64> ( ) . Clear;
end ;
end
2020-08-13 17:40:02 +02:00
else if lPKValue. IsType< NullableString> and aValue. IsType< NullableString> ( ) then
begin
if aValue. AsType< NullableString> ( ) . HasValue then
begin
lPKValue : = aValue;
end
else
begin
lPKValue. AsType< NullableString> ( ) . Clear;
end ;
end
else if lPKValue. IsType< NullableUInt32> and aValue. IsType< NullableUInt32> ( ) then
2020-01-08 15:30:10 +01:00
begin
if aValue. AsType< NullableUInt32> ( ) . HasValue then
begin
lPKValue : = aValue;
end
else
begin
lPKValue. AsType< NullableUInt32> ( ) . Clear;
end ;
end
2020-08-13 17:40:02 +02:00
else if lPKValue. IsType< NullableUInt64> and aValue. IsType< NullableUInt64> ( ) then
2020-01-08 15:30:10 +01:00
begin
if aValue. AsType< NullableUInt64> ( ) . HasValue then
begin
lPKValue : = aValue;
end
else
begin
lPKValue. AsType< NullableUInt64> ( ) . Clear;
end ;
end
else
2020-08-13 17:40:02 +02:00
begin
2020-08-28 18:04:29 +02:00
raise EMVCActiveRecord. Create
( 'Invalid type for primary key [HINT] Double check if TypeInfo(PK) is equal to TypeInfo(Value)' ) ;
2020-08-13 17:40:02 +02:00
end ;
2022-11-17 19:33:27 +01:00
fTableMap. fPrimaryKey. SetValue( Self, lPKValue) ;
2020-01-08 15:30:10 +01:00
end
else
begin
2022-11-17 19:33:27 +01:00
fTableMap. fPrimaryKey. SetValue( Self, aValue)
2020-01-08 15:30:10 +01:00
end ;
2018-09-25 15:36:53 +02:00
end ;
2020-01-04 12:53:53 +01:00
procedure TMVCActiveRecord. SetPrimaryKeyIsAutogenerated( const Value: Boolean ) ;
begin
if Value then
begin
2022-11-17 19:33:27 +01:00
Include( fTableMap. fPrimaryKeyOptions, foAutoGenerated) ;
2020-01-04 12:53:53 +01:00
end
else
begin
2022-11-17 19:33:27 +01:00
Exclude( fTableMap. fPrimaryKeyOptions, foAutoGenerated) ;
2020-01-04 12:53:53 +01:00
end ;
end ;
2020-08-11 00:54:42 +02:00
procedure TMVCActiveRecord. SetTableName( const Value: string ) ;
2020-07-02 23:19:36 +02:00
begin
2022-11-17 19:33:27 +01:00
if Value = fTableMap. fTableName then
begin
fCustomTableName : = '' ;
end
else
begin
fCustomTableName : = Value;
end ;
2020-07-02 23:19:36 +02:00
end ;
2018-11-24 16:56:21 +01:00
function TMVCActiveRecord. SQLGenerator: TMVCSQLGenerator;
2022-08-13 15:01:15 +02:00
var
lSQLGeneratorClass: TMVCSQLGeneratorClass;
2018-11-24 16:56:21 +01:00
begin
if not Assigned( fSQLGenerator) then
begin
2020-02-03 10:51:40 +01:00
GetConnection. Connected : = True ;
2022-08-13 15:01:15 +02:00
lSQLGeneratorClass : = TMVCSQLGeneratorRegistry. Instance. GetSQLGenerator( GetBackEnd) ;
2022-11-17 19:33:27 +01:00
fSQLGenerator : = lSQLGeneratorClass. Create( GetMapping, fTableMap. fDefaultRQLFilter, GetPartitionInfo) ;
2018-11-24 16:56:21 +01:00
end ;
Result : = fSQLGenerator;
end ;
2020-02-03 13:19:55 +01:00
procedure TMVCActiveRecord. Store;
var
lValue: TValue;
lRes: Boolean ;
lIsNullableType: Boolean ;
begin
lRes : = TryGetPKValue( lValue, lIsNullableType) ;
if not lIsNullableType then
begin
2022-08-02 17:07:14 +02:00
raise EMVCActiveRecord. Create( 'Store can be used only with nullable PKs [HINT] Use NullableInt64 as PK' ) ;
2020-02-03 13:19:55 +01:00
end ;
if lRes then
begin
Update;
end
else
begin
Insert;
end ;
end ;
2018-09-25 15:36:53 +02:00
function TMVCActiveRecord. TableInfo: string ;
var
2021-05-03 19:29:01 +02:00
KeyValue: TPair< TRTTIField, TFieldInfo> ;
2018-09-25 15:36:53 +02:00
begin
2022-11-17 19:33:27 +01:00
Result : = 'Table Name: ' + TableName;
for KeyValue in fTableMap. fMap do
2021-05-03 19:29:01 +02:00
Result : = Result + sLineBreak + #9 + KeyValue. Key. Name + ' = ' + KeyValue. Value. FieldName;
2018-09-25 15:36:53 +02:00
end ;
2020-02-03 13:19:55 +01:00
function TMVCActiveRecord. TryGetPKValue( var Value: TValue; out IsNullableType: Boolean ) : Boolean ;
begin
IsNullableType : = false ;
2022-11-17 19:33:27 +01:00
if fTableMap. fPrimaryKeyFieldName. IsEmpty then
2020-02-03 13:19:55 +01:00
raise Exception. Create( 'No primary key defined' ) ;
2022-11-17 19:33:27 +01:00
Value : = fTableMap. fPrimaryKey. GetValue( Self) ;
2020-02-03 13:19:55 +01:00
if Value. Kind = tkRecord then
begin
if Value. IsType< NullableInt32> ( ) then
begin
2023-01-29 17:29:24 +01:00
Result : = Value. AsType< NullableInt32> ( ) . TryHasValue( Value) ;
2020-02-03 13:19:55 +01:00
end
else if Value. IsType< NullableInt64> ( ) then
begin
2023-01-29 17:29:24 +01:00
Result : = Value. AsType< NullableInt64> ( ) . TryHasValue( Value)
2020-02-03 13:19:55 +01:00
end
else if Value. IsType< NullableUInt32> ( ) then
begin
2023-01-29 17:29:24 +01:00
Result : = Value. AsType< NullableUInt32> ( ) . TryHasValue( Value)
2020-02-03 13:19:55 +01:00
end
else if Value. IsType< NullableUInt64> ( ) then
begin
2023-01-29 17:29:24 +01:00
Result : = Value. AsType< NullableUInt64> ( ) . TryHasValue( Value)
2020-02-03 13:19:55 +01:00
end
else if Value. IsType< NullableInt16> ( ) then
begin
2023-01-29 17:29:24 +01:00
Result : = Value. AsType< NullableInt16> ( ) . TryHasValue( Value)
2020-02-03 13:19:55 +01:00
end
else if Value. IsType< NullableUInt16> ( ) then
begin
2023-01-29 17:29:24 +01:00
Result : = Value. AsType< NullableUInt16> ( ) . TryHasValue( Value)
2020-02-03 13:19:55 +01:00
end
2020-03-31 16:23:22 +02:00
else if Value. IsType< NullableString> ( ) then
begin
2023-01-29 17:29:24 +01:00
Result : = Value. AsType< NullableString> ( ) . TryHasValue( Value)
2020-03-31 16:23:22 +02:00
end
2022-06-23 14:34:01 +02:00
else if Value. IsType< NullableTGUID> ( ) then
begin
2023-01-29 17:29:24 +01:00
Result : = Value. AsType< NullableTGUID> ( ) . TryHasValue( Value)
2022-06-23 14:34:01 +02:00
end
2020-02-03 13:19:55 +01:00
else
2020-03-23 18:51:57 +01:00
raise EMVCActiveRecord. Create
2020-03-31 16:23:22 +02:00
( 'Invalid primary key type [HINT: Use Int64, String, NullableInt64 or NullableString, so that Store method is available too.]' ) ;
2020-02-03 13:19:55 +01:00
IsNullableType : = True ;
end
else
begin
Result : = not Value. IsEmpty;
end ;
end ;
2021-06-27 15:14:37 +02:00
procedure TMVCActiveRecord. Update( const RaiseExceptionIfNotFound: Boolean = True ) ;
2018-09-25 15:36:53 +02:00
var
SQL: string ;
2022-08-02 17:07:14 +02:00
lAffectedRows: int64 ;
2018-09-25 15:36:53 +02:00
begin
2018-10-14 18:23:20 +02:00
CheckAction( TMVCEntityAction. eaUpdate) ;
2020-03-30 13:30:45 +02:00
OnValidation( TMVCEntityAction. eaUpdate) ;
2018-09-25 15:36:53 +02:00
OnBeforeUpdate;
OnBeforeInsertOrUpdate;
2022-11-17 19:33:27 +01:00
if fTableMap. fMap. WritableFieldsCount = 0 then
2019-02-21 20:17:11 +01:00
begin
raise EMVCActiveRecord. CreateFmt
2022-11-17 19:33:27 +01:00
( 'Cannot update an entity if no fields are writeable. Class [%s] mapped on table [%s]' , [ ClassName, TableName] ) ;
2019-02-21 20:17:11 +01:00
end ;
2022-11-17 19:33:27 +01:00
SQL : = SQLGenerator. CreateUpdateSQL( TableName, fTableMap. fMap,
fTableMap. fPrimaryKeyFieldName, fTableMap. fPrimaryKeyOptions) ;
2021-06-27 15:14:37 +02:00
lAffectedRows : = ExecNonQuery( SQL, false ) ;
if ( lAffectedRows = 0 ) and RaiseExceptionIfNotFound then
2020-12-11 18:53:37 +01:00
begin
2020-12-18 14:41:01 +01:00
raise EMVCActiveRecordNotFound. CreateFmt( 'No record updated for key [Entity: %s][PK: %s]' ,
2022-11-17 19:33:27 +01:00
[ ClassName, fTableMap. fPrimaryKeyFieldName] ) ;
2020-12-11 18:53:37 +01:00
end ;
2018-10-23 16:18:34 +02:00
OnAfterUpdate;
OnAfterInsertOrUpdate;
2018-09-25 15:36:53 +02:00
end ;
2023-08-09 00:46:31 +02:00
class function TMVCActiveRecordHelper. Where( const aClass: TMVCActiveRecordClass;
2023-08-08 14:31:23 +02:00
const SQLWhere: string ; const Params: array of Variant ;
const Connection: TFDConnection; const OutList: TMVCActiveRecordList) : UInt32 ;
var
lAR: TMVCActiveRecord;
begin
lAR : = aClass. Create;
try
Result : = Select( aClass, lAR. GenerateSelectSQL + SQLWhere, Params, Connection, OutList) ;
finally
lAR. Free;
end ;
end ;
2020-05-04 09:42:16 +02:00
procedure TMVCActiveRecord. AddChildren( const ChildObject: TObject) ;
begin
if fChildren = nil then
begin
fChildren : = TObjectList< TObject> . Create( True ) ;
end ;
2020-08-11 00:54:42 +02:00
if not( fChildren. Contains( ChildObject) ) and ( not( ChildObject = Self) ) then
2020-05-04 09:42:16 +02:00
begin
fChildren. Add( ChildObject) ;
end ;
end ;
2023-08-09 00:46:31 +02:00
class function TMVCActiveRecordHelper. All( const aClass: TMVCActiveRecordClass) : TObjectList< TMVCActiveRecord> ;
2018-09-25 15:36:53 +02:00
var
lAR: TMVCActiveRecord;
begin
lAR : = aClass. Create;
try
2022-11-10 18:30:11 +01:00
Result : = Select( aClass,
lAR. GenerateSelectSQL + lAR. SQLGenerator. GetDefaultSQLFilter( True ) , [ ] ) ;
2018-09-25 15:36:53 +02:00
finally
lAR. Free;
end ;
end ;
2022-08-28 13:06:16 +02:00
procedure TMVCActiveRecord. Assign( ActiveRecord: TMVCActiveRecord) ;
begin
//do nothing
end ;
2023-08-30 11:58:50 +02:00
class function TMVCActiveRecordHelper. All( const aQualifiedClassName: String ) : TObjectList< TMVCActiveRecord> ;
var
lAR: TMVCActiveRecord;
begin
lAR : = TMVCActiveRecord. CreateMVCActiveRecord< TMVCActiveRecord> ( aQualifiedClassName, [ ] ) ;
try
Result : = Select( TMVCActiveRecordClass( lAR. ClassType) ,
lAR. GenerateSelectSQL + lAR. SQLGenerator. GetDefaultSQLFilter( True ) , [ ] ) ;
finally
lAr. Free;
end ;
end ;
2020-08-28 18:04:29 +02:00
class function TMVCActiveRecordHelper. All< T> : TObjectList< T> ;
2018-09-25 15:36:53 +02:00
var
lAR: TMVCActiveRecord;
begin
lAR : = T. Create;
try
2022-11-10 18:30:11 +01:00
Result : = Select< T> (
lAR. GenerateSelectSQL + lAR. SQLGenerator. GetDefaultSQLFilter( True ) , [ ] ) ;
2018-09-25 15:36:53 +02:00
finally
lAR. Free;
end ;
end ;
2023-08-09 00:46:31 +02:00
class function TMVCActiveRecordHelper. Where( const aClass: TMVCActiveRecordClass; const SQLWhere: string ;
2019-03-05 20:55:37 +01:00
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 ;
2023-08-09 00:46:31 +02:00
class function TMVCActiveRecordHelper. Where( const aClass: TMVCActiveRecordClass; const SQLWhere: string ;
2019-03-05 20:55:37 +01:00
const Params: array of Variant ; const Connection: TFDConnection) : TMVCActiveRecordList;
2018-09-27 12:26:50 +02:00
begin
2023-08-08 14:31:23 +02:00
Result : = TMVCActiveRecordList. Create;
2018-09-27 12:26:50 +02:00
try
2023-08-08 14:31:23 +02:00
Where( aClass, SQLWhere, Params, Connection, Result ) ;
except
Result . Free;
raise ;
2018-09-27 12:26:50 +02:00
end ;
end ;
2022-08-02 17:07:14 +02:00
class function TMVCActiveRecordHelper. Where< T> ( const SQLWhere: string ; const Params: array of Variant ) : TObjectList< T> ;
2018-09-25 15:36:53 +02:00
begin
2020-03-23 18:51:57 +01:00
Result : = Where< T> ( SQLWhere, Params, [ ] ) ;
2018-09-25 15:36:53 +02:00
end ;
2021-04-29 22:52:28 +02:00
2022-08-28 13:06:16 +02:00
class function TMVCActiveRecordHelper. Merge< T> ( CurrentList, NewList: TObjectList< T> ; const MergeMode: TMergeMode) : IMVCMultiExecutor< T> ;
2021-04-29 22:52:28 +02:00
var
2022-08-02 17:07:14 +02:00
I: Integer ;
2021-04-29 22:52:28 +02:00
lFoundAtIndex: Integer ;
lCurrPKValue: Integer ;
lPKValue: TValue;
lUnitOfWork: IMVCUnitOfWork< T> ;
2021-05-03 19:29:01 +02:00
lPKType: TFieldType;
lNeedsToBeUpdated: Boolean ;
2021-04-29 22:52:28 +02:00
begin
lUnitOfWork : = TMVCUnitOfWork< T> . Create;
2022-08-28 13:06:16 +02:00
if mmDelete in MergeMode then
begin
lUnitOfWork. RegisterDelete( CurrentList) ;
end ;
2021-09-23 22:52:28 +02:00
if NewList. Count > 0 then
2021-05-03 19:29:01 +02:00
begin
2021-09-23 22:52:28 +02:00
lPKType : = NewList[ 0 ] . GetPrimaryKeyFieldType;
2022-08-02 17:07:14 +02:00
for I : = 0 to NewList. Count - 1 do
2021-04-29 22:52:28 +02:00
begin
2022-08-02 17:07:14 +02:00
if NewList[ I] . PKIsNull then
2021-09-23 22:52:28 +02:00
begin
2022-08-28 13:06:16 +02:00
if mmInsert in MergeMode then
begin
lUnitOfWork. RegisterInsert( NewList[ I] ) ;
end ;
2022-08-02 17:07:14 +02:00
Continue;
2021-09-23 22:52:28 +02:00
end ;
2021-05-03 19:29:01 +02:00
2021-09-23 22:52:28 +02:00
case lPKType of
ftString:
begin
2022-08-02 17:07:14 +02:00
lNeedsToBeUpdated : = TMVCUnitOfWork< T> . KeyExistsString( CurrentList, NewList[ I] . GetPK. AsString,
lFoundAtIndex) ;
2021-09-23 22:52:28 +02:00
end ;
ftInteger:
begin
2022-08-02 17:07:14 +02:00
lNeedsToBeUpdated : = TMVCUnitOfWork< T> . KeyExistsInt( CurrentList, NewList[ I] . GetPK. AsInteger, lFoundAtIndex) ;
2021-09-23 22:52:28 +02:00
end ;
ftLargeInt:
begin
2022-08-02 17:07:14 +02:00
lNeedsToBeUpdated : = TMVCUnitOfWork< T> . KeyExistsInt64( CurrentList, NewList[ I] . GetPK. AsInt64, lFoundAtIndex) ;
2021-09-23 22:52:28 +02:00
end ;
else
2022-08-28 13:06:16 +02:00
raise EMVCActiveRecord. Create( 'Invalid primary key type for merge' ) ;
2021-09-23 22:52:28 +02:00
end ;
2021-05-03 19:29:01 +02:00
2021-09-23 22:52:28 +02:00
if lNeedsToBeUpdated then
2022-08-28 13:06:16 +02:00
begin
if mmUpdate in MergeMode then
begin
lUnitOfWork. RegisterUpdate( NewList[ I] )
end ;
end
2021-09-23 22:52:28 +02:00
else
2022-08-28 13:06:16 +02:00
begin
if mmInsert in MergeMode then
begin
lUnitOfWork. RegisterInsert( NewList[ I] ) ;
end ;
end ;
2021-09-23 22:52:28 +02:00
end ;
2021-04-29 22:52:28 +02:00
end ;
Result : = lUnitOfWork as IMVCMultiExecutor< T> ;
end ;
2018-09-25 15:36:53 +02:00
{ TMVCEntitiesRegistry }
2022-08-02 17:07:14 +02: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 ;
2021-04-07 23:58:02 +02:00
function TMVCEntitiesRegistry. GetEntities: TArray< String > ;
begin
Result : = fEntitiesDict. Keys. ToArray;
end ;
2018-09-25 15:36:53 +02:00
{ 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 ;
2020-08-28 18:04:29 +02: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 }
2022-08-02 17:07:14 +02:00
constructor TMVCSQLGenerator. Create( Mapping: TMVCFieldsMapping; const DefaultRQLFilter: string ;
const PartitionInfo: TPartitionInfo) ;
2018-11-02 21:43:09 +01:00
begin
inherited Create;
fMapping : = Mapping;
2021-11-17 15:36:20 +01:00
fDefaultRQLFilter : = DefaultRQLFilter;
2021-11-18 00:49:12 +01:00
fPartitionInfo : = PartitionInfo;
2020-04-08 18:04:45 +02:00
GetCompiler;
2021-11-17 15:36:20 +01:00
if not fDefaultRQLFilter. IsEmpty then
begin
2022-08-02 17:07:14 +02:00
GetRQLParser. Execute( fDefaultRQLFilter, fDefaultSQLFilter, GetCompiler, false , True ) ;
2021-11-17 15:36:20 +01:00
fDefaultSQLFilter : = TMVCSQLGenerator. RemoveInitialWhereKeyword( fDefaultSQLFilter) ;
end ;
2018-11-02 21:43:09 +01:00
end ;
function TMVCSQLGenerator. GetMapping: TMVCFieldsMapping;
begin
Result : = fMapping;
end ;
2020-04-08 18:04:45 +02:00
function TMVCSQLGenerator. GetParamNameForSQL( const FieldName: string ) : string ;
begin
Result : = fCompiler. GetParamNameForSQL( FieldName) ;
end ;
2021-11-21 19:27:06 +01:00
function TMVCSQLGenerator. CreateDeleteAllSQL( const TableName: string ) : string ;
begin
Result : = 'DELETE FROM ' + GetTableNameForSQL( TableName) ;
end ;
2022-08-02 17:07:14 +02:00
function TMVCSQLGenerator. CreateDeleteSQL( const TableName: string ; const Map: TFieldsMap; const PKFieldName: string ;
2021-11-21 19:27:06 +01:00
const PKOptions: TMVCActiveRecordFieldOptions) : string ;
begin
Result : = CreateDeleteAllSQL( TableName) + ' WHERE ' + GetFieldNameForSQL( PKFieldName) + '=:' +
GetParamNameForSQL( PKFieldName) ;
end ;
2022-08-02 17:07:14 +02:00
function TMVCSQLGenerator. CreateSelectByPKSQL( const TableName: string ; const Map: TFieldsMap; const PKFieldName: string ;
2021-11-21 19:27:06 +01:00
const PKOptions: TMVCActiveRecordFieldOptions) : string ;
begin
if PKFieldName. IsEmpty then
begin
2022-08-02 17:07:14 +02:00
raise EMVCActiveRecord. Create
( 'No primary key provided. [HINT] Define a primary key field adding foPrimaryKey in field options.' ) ;
2021-11-21 19:27:06 +01:00
end ;
2022-08-02 17:07:14 +02:00
Result : = CreateSelectSQL( TableName, Map, PKFieldName, PKOptions) + ' WHERE ' + GetFieldNameForSQL( PKFieldName) +
'= :' + GetParamNameForSQL( PKFieldName) + GetDefaultSQLFilter( false , True ) ;
2021-11-21 19:27:06 +01:00
end ;
function TMVCSQLGenerator. CreateSelectCount( const TableName: string ) : string ;
begin
2022-08-02 17:07:14 +02:00
{ do not add SQLFilter here! }
2021-11-21 19:27:06 +01:00
Result : = 'SELECT count(*) FROM ' + GetTableNameForSQL( TableName) ;
end ;
2022-08-02 17:07:14 +02:00
function TMVCSQLGenerator. CreateSelectSQL( const TableName: string ; const Map: TFieldsMap; const PKFieldName: string ;
2021-11-21 19:27:06 +01:00
const PKOptions: TMVCActiveRecordFieldOptions) : string ;
begin
Result : = 'SELECT ' + TableFieldsDelimited( Map, PKFieldName, ',' ) + ' FROM ' + GetTableNameForSQL( TableName) ;
end ;
2022-08-02 17:07:14 +02:00
function TMVCSQLGenerator. CreateSQLWhereByRQL( const RQL: string ; const Mapping: TMVCFieldsMapping;
const UseArtificialLimit, UseFilterOnly: Boolean ; const MaxRecordCount: Int32 ) : string ;
2021-11-21 19:27:06 +01:00
begin
2022-08-02 17:07:14 +02:00
GetRQLParser. Execute( MergeDefaultRQLFilter( RQL) , Result , GetCompiler, UseArtificialLimit, UseFilterOnly,
MaxRecordCount) ;
2021-11-21 19:27:06 +01:00
end ;
2022-08-02 17:07:14 +02:00
function TMVCSQLGenerator. CreateUpdateSQL( const TableName: string ; const Map: TFieldsMap; const PKFieldName: string ;
2021-11-21 19:27:06 +01:00
const PKOptions: TMVCActiveRecordFieldOptions) : string ;
var
2022-08-02 17:07:14 +02:00
lPair: TPair< TRTTIField, TFieldInfo> ;
2023-07-07 20:29:09 +02:00
// I: Integer;
2021-11-21 19:27:06 +01:00
begin
Result : = 'UPDATE ' + GetTableNameForSQL( TableName) + ' SET ' ;
for lPair in Map do
begin
if lPair. Value. Writeable then
begin
Result : = Result + GetFieldNameForSQL( lPair. Value. FieldName) + ' = :' +
GetParamNameForSQL( lPair. Value. FieldName) + ',' ;
end ;
end ;
2022-08-02 17:07:14 +02:00
{ partition }
2023-07-07 20:29:09 +02:00
// for I := 0 to fPartitionInfo.FieldNames.Count - 1 do
// begin
// Result := Result + GetFieldNameForSQL(fPartitionInfo.FieldNames[I]) + ' = :' +
// GetParamNameForSQL(fPartitionInfo.FieldNames[I]) + ',';
// end;
2022-08-02 17:07:14 +02:00
{ end-partitioning }
2021-11-21 19:27:06 +01:00
Result [ Length( Result ) ] : = ' ' ;
if not PKFieldName. IsEmpty then
begin
Result : = Result + ' where ' + GetFieldNameForSQL( PKFieldName) + '= :' + GetParamNameForSQL( PKFieldName) ;
2022-11-10 18:30:11 +01:00
end
else
begin
raise EMVCActiveRecord. Create( 'Cannot perform an update without an entity primary key' ) ;
2021-11-21 19:27:06 +01:00
end ;
end ;
2018-11-02 21:43:09 +01:00
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 ;
2022-08-02 17:07:14 +02:00
function TMVCSQLGenerator. GetDefaultSQLFilter( const IncludeWhereClause: Boolean ;
const IncludeAndClauseBeforeFilter: Boolean ) : String ;
2021-11-17 15:36:20 +01:00
begin
2021-11-18 00:49:12 +01:00
Result : = MergeSQLFilter( fPartitionInfo. SQLFilter, fDefaultSQLFilter) ;
if not Result . IsEmpty then
2021-11-17 15:36:20 +01:00
begin
if IncludeWhereClause then
begin
2021-11-18 00:49:12 +01:00
Result : = ' WHERE ' + Result ;
2021-11-17 15:36:20 +01:00
end
else
begin
if IncludeAndClauseBeforeFilter then
2021-11-18 00:49:12 +01:00
Result : = ' and ' + Result ;
2021-11-17 15:36:20 +01:00
end ;
end ;
end ;
2020-04-08 18:04:45 +02:00
function TMVCSQLGenerator. GetFieldNameForSQL( const FieldName: string ) : string ;
begin
Result : = fCompiler. GetFieldNameForSQL( FieldName) ;
end ;
2021-11-18 00:49:12 +01:00
function TMVCSQLGenerator. GetRQLParser: TRQL2SQL;
2018-11-02 21:43:09 +01:00
begin
if fRQL2SQL = nil then
begin
2021-11-18 00:49:12 +01:00
fRQL2SQL : = TRQL2SQL. Create;
2018-11-02 21:43:09 +01:00
end ;
Result : = fRQL2SQL;
end ;
2020-08-28 18:04:29 +02:00
function TMVCSQLGenerator. GetSequenceValueSQL( const PKFieldName: string ; const SequenceName: string ;
const Step: Integer = 1 ) : string ;
2020-01-08 15:30:10 +01:00
begin
Result : = '' ;
end ;
2020-04-08 18:04:45 +02:00
function TMVCSQLGenerator. GetTableNameForSQL( const TableName: string ) : string ;
begin
Result : = fCompiler. GetTableNameForSQL( TableName) ;
end ;
2022-06-23 14:34:01 +02:00
function TMVCSQLGenerator. HasNativeUUID: Boolean ;
begin
2022-08-02 17:07:14 +02:00
Result : = false ;
2022-06-23 14:34:01 +02:00
end ;
2020-01-08 15:30:10 +01:00
function TMVCSQLGenerator. HasReturning: Boolean ;
begin
Result : = True ;
end ;
function TMVCSQLGenerator. HasSequences: Boolean ;
begin
Result : = True ;
end ;
2021-11-17 15:36:20 +01:00
function TMVCSQLGenerator. MergeDefaultRQLFilter( const RQL: String ) : String ;
2021-11-18 17:52:06 +01:00
var
lRQLFilterPart, lRQLSortingAndLimitPart: String ;
lSemicolonPos: Integer ;
2021-11-17 15:36:20 +01:00
begin
2021-11-18 17:52:06 +01:00
lRQLFilterPart : = RQL;
lRQLSortingAndLimitPart : = '' ;
lSemicolonPos : = RQL. IndexOf( ';' ) ;
if lSemicolonPos > - 1 then
begin
2022-08-02 17:07:14 +02:00
lRQLFilterPart : = RQL. Substring( 0 , lSemicolonPos) ;
2021-11-18 17:52:06 +01:00
lRQLSortingAndLimitPart : = RQL. Substring( lSemicolonPos + 1 , 1 0 0 0 ) ;
end ;
2022-08-02 17:07:14 +02:00
{ this is not the best solution, but it works... }
2021-11-18 17:52:06 +01:00
if lRQLFilterPart. Contains( 'sort' ) or lRQLFilterPart. Contains( 'limit' ) then
begin
lRQLSortingAndLimitPart : = lRQLFilterPart;
lRQLFilterPart : = '' ;
end ;
if ( not fDefaultRQLFilter. IsEmpty) or ( not fPartitionInfo. RQLFilter. IsEmpty) then
2021-11-17 15:36:20 +01:00
begin
2021-11-18 17:52:06 +01:00
Result : = 'and(' ;
if not fDefaultRQLFilter. IsEmpty then
2021-11-17 15:36:20 +01:00
begin
2021-11-18 17:52:06 +01:00
Result : = Result + fDefaultRQLFilter;
end ;
if not fPartitionInfo. RQLFilter. IsEmpty then
2021-11-17 15:36:20 +01:00
begin
2021-11-18 17:52:06 +01:00
Result : = Result + ',' + fPartitionInfo. RQLFilter;
end ;
if not lRQLFilterPart. IsEmpty then
begin
Result : = Result + ',' + lRQLFilterPart;
2021-11-17 15:36:20 +01:00
end ;
2021-11-18 17:52:06 +01:00
Result : = Result + ')' ;
2021-11-17 15:36:20 +01:00
end
else
begin
2021-11-18 17:52:06 +01:00
Exit( RQL) ;
2021-11-17 15:36:20 +01:00
end ;
2021-11-18 00:49:12 +01:00
2021-11-18 17:52:06 +01:00
if not lRQLSortingAndLimitPart. IsEmpty then
2021-11-18 00:49:12 +01:00
begin
2021-11-18 17:52:06 +01:00
Result : = Result + ';' + lRQLSortingAndLimitPart;
2021-11-18 00:49:12 +01:00
end ;
end ;
2023-07-07 20:29:09 +02:00
function TMVCSQLGenerator. MergeSQLFilter( const PartitionSQL, FilteringSQL: String ) : String ;
2021-11-18 00:49:12 +01:00
begin
2023-07-07 20:29:09 +02:00
Result : = '' ;
if PartitionSQL + FilteringSQL = '' then
2021-11-18 00:49:12 +01:00
begin
2023-07-07 20:29:09 +02:00
Exit;
2021-11-18 00:49:12 +01:00
end ;
2023-07-07 20:29:09 +02:00
//if PartitionSQL.IsEmpty and (not FilteringSQL.IsEmpty) then
if not FilteringSQL. IsEmpty then
2021-11-18 00:49:12 +01:00
begin
2023-07-07 20:29:09 +02:00
Exit( FilteringSQL) ; //ignore partitioning while reading if filtering is present
2021-11-18 00:49:12 +01:00
end ;
2023-07-07 20:29:09 +02:00
if FilteringSQL. IsEmpty and ( not PartitionSQL. IsEmpty) then
2021-11-18 00:49:12 +01:00
begin
2023-07-07 20:29:09 +02:00
Exit( PartitionSQL) ;
2021-11-18 00:49:12 +01:00
end ;
2023-07-07 20:29:09 +02:00
// Result := '((' + PartitionSQL + ') and (' + FilteringSQL + '))';
2021-11-17 15:36:20 +01:00
end ;
2022-08-02 17:07:14 +02:00
class function TMVCSQLGenerator. RemoveInitialWhereKeyword( const SQLFilter: String ) : String ;
2021-11-17 15:36:20 +01:00
begin
Result : = SQLFilter. TrimLeft;
2022-08-02 17:07:14 +02:00
if Result . StartsWith( 'where' , True ) then
2021-11-17 15:36:20 +01:00
begin
Result : = Result . Remove( 0 , 5 ) ;
end ;
end ;
2020-03-25 11:35:25 +01:00
function TMVCSQLGenerator. TableFieldsDelimited( const Map: TFieldsMap; const PKFieldName: string ;
2018-11-02 21:43:09 +01:00
const Delimiter: string ) : string ;
var
2020-03-25 11:35:25 +01:00
lPair: TPair< TRTTIField, TFieldInfo> ;
2018-11-02 21:43:09 +01:00
begin
for lPair in Map do
begin
2020-03-25 11:35:25 +01:00
// if not lPair.Value.FieldName.IsEmpty then
if lPair. Value. Readable then
2019-11-05 16:57:22 +01:00
begin
2020-04-08 18:04:45 +02:00
Result : = Result + GetFieldNameForSQL( lPair. Value. FieldName) + Delimiter;
2019-11-05 16:57:22 +01:00
end ;
2018-11-02 21:43:09 +01:00
end ;
Result : = Copy( Result , 1 , Length( Result ) - Length( Delimiter) ) ;
if not PKFieldName. IsEmpty then
begin
2020-08-11 00:54:42 +02:00
if not Result . IsEmpty then
begin
Result : = GetFieldNameForSQL( PKFieldName) + ', ' + Result
end
else
begin
Result : = GetFieldNameForSQL( PKFieldName)
end ;
2018-11-02 21:43:09 +01:00
end ;
end ;
2018-11-09 18:11:59 +01:00
{ TMVCConnectionsRepository.TConnHolder }
destructor TMVCConnectionsRepository. TConnHolder. Destroy;
begin
if OwnsConnection then
2020-03-23 18:51:57 +01:00
begin
2019-05-09 20:53:52 +02:00
if Connection. Connected then
Connection. Connected : = false ;
2018-11-09 18:11:59 +01:00
FreeAndNil( Connection) ;
2020-03-23 18:51:57 +01:00
end ;
2018-11-09 18:11:59 +01:00
inherited ;
end ;
2022-08-02 17:07:14 +02:00
constructor MVCTableFieldAttribute. Create( const aFieldName: string ; const aFieldOptions: TMVCActiveRecordFieldOptions;
2020-03-23 18:51:57 +01:00
const aSequenceName: string ; const aDataTypeName: string ) ;
2019-02-15 12:21:11 +01:00
begin
inherited Create;
FieldName : = aFieldName;
FieldOptions : = aFieldOptions;
2020-01-08 15:30:10 +01:00
SequenceName : = aSequenceName;
2020-03-12 20:37:48 +01:00
DataTypeName : = aDataTypeName;
2019-02-15 12:21:11 +01:00
end ;
2020-01-04 12:53:53 +01:00
{ EMVCActiveRecordNotFound }
procedure EMVCActiveRecordNotFound. AfterConstruction;
begin
inherited ;
2023-09-05 12:29:38 +02:00
FHTTPStatusCode : = http_status. NotFound;
2020-01-04 12:53:53 +01:00
end ;
2023-01-02 22:09:42 +01:00
class function TMVCActiveRecord. ExecQuery(
const SQL: string ;
const Values: array of Variant ;
const ValueTypes: array of TFieldType;
const Connection: TFDConnection;
const Unidirectional: Boolean ;
const DirectExecute: Boolean ) : TDataSet;
2020-03-23 18:51:57 +01:00
var
lQry: TFDQuery;
begin
lQry : = TFDQuery. Create( nil ) ;
try
2023-01-02 22:09:42 +01:00
lQry. FetchOptions. Unidirectional : = Unidirectional;
2022-06-19 18:57:47 +02:00
lQry. UpdateOptions. ReadOnly : = True ;
2023-07-12 15:01:58 +02:00
lQry. ResourceOptions. DirectExecute : = DirectExecute; //2023-07-12
2023-01-02 22:09:42 +01:00
if Unidirectional then
begin
lQry. FetchOptions. CursorKind : = ckForwardOnly;
end ;
2020-03-23 18:51:57 +01:00
if Connection = nil then
begin
lQry. Connection : = ActiveRecordConnectionsRegistry. GetCurrent;
end
else
begin
lQry. Connection : = Connection;
end ;
if Length( ValueTypes) = 0 then
begin
lQry. Open( SQL, Values) ;
end
else
begin
lQry. Open( SQL, Values, ValueTypes) ;
end ;
Result : = lQry;
except
lQry. Free;
raise ;
end ;
end ;
2020-08-28 18:04:29 +02:00
class function TMVCActiveRecord. ExecQuery( const SQL: string ; const Values: array of Variant ;
2023-01-02 22:09:42 +01:00
const ValueTypes: array of TFieldType; const Unidirectional: Boolean ; const DirectExecute: Boolean ) : TDataSet;
2020-03-23 18:51:57 +01:00
begin
2023-01-02 22:09:42 +01:00
Result : = ExecQuery( SQL, Values, ValueTypes, nil , Unidirectional, DirectExecute) ;
2020-03-23 18:51:57 +01:00
end ;
2020-03-25 11:35:25 +01:00
{ TFieldsMap }
constructor TFieldsMap. Create;
begin
inherited Create( [ doOwnsValues] ) ;
2020-08-11 00:54:42 +02:00
fWritableFieldsCount : = - 1 ;
fReadableFieldsCount : = - 1 ;
2020-03-25 11:35:25 +01:00
end ;
procedure TFieldsMap. EndUpdates;
var
lPair: TPair< TRTTIField, TFieldInfo> ;
begin
2020-08-11 00:54:42 +02:00
fWritableFieldsCount : = 0 ;
fReadableFieldsCount : = 0 ;
2020-03-25 11:35:25 +01:00
for lPair in Self do
begin
lPair. Value. EndUpdates;
2020-08-11 00:54:42 +02:00
if lPair. Value. Writeable then
begin
Inc( fWritableFieldsCount) ;
end ;
if lPair. Value. Readable then
2020-03-25 11:35:25 +01:00
begin
2020-08-11 00:54:42 +02:00
Inc( fReadableFieldsCount) ;
2020-03-25 11:35:25 +01:00
end ;
end ;
end ;
function TFieldsMap. GetInfoByFieldName( const FieldName: string ) : TFieldInfo;
var
lPair: TPair< TRTTIField, TFieldInfo> ;
begin
for lPair in Self do
begin
if SameText( FieldName, lPair. Value. FieldName) then
begin
Result : = Items[ lPair. Key] ;
Exit;
end ;
end ;
2022-06-19 18:57:47 +02:00
raise EMVCActiveRecord. CreateFmt( 'FieldName [%s] not found in table' , [ FieldName] ) ;
2020-03-25 11:35:25 +01:00
end ;
{ TFieldInfo }
procedure TFieldInfo. EndUpdates;
begin
2020-08-11 00:54:42 +02:00
if FieldName. IsEmpty then
begin
Writeable : = false ;
Readable : = false ;
end
else
begin
2022-11-15 09:27:08 +01:00
Writeable : = ( ( FieldOptions * [ foReadOnly, foAutoGenerated] ) = [ ] ) ;
Readable : = ( FieldOptions * [ foWriteOnly] ) = [ ] ;
2020-08-11 00:54:42 +02:00
end ;
2020-03-25 11:35:25 +01:00
end ;
2021-04-26 23:01:31 +02:00
{ TMVCUnitOfWork<T> }
2021-04-29 22:52:28 +02:00
procedure TMVCUnitOfWork< T> . Apply( const ItemApplyAction: TMVCItemApplyAction< T> ) ;
2021-04-26 23:01:31 +02:00
var
2022-08-02 17:07:14 +02:00
I: Integer ;
2021-04-29 22:52:28 +02:00
lHandled: Boolean ;
2021-04-26 23:01:31 +02:00
begin
2022-08-02 17:07:14 +02:00
for I : = 0 to fListToInsert. Count - 1 do
2021-04-26 23:01:31 +02:00
begin
2021-05-03 19:29:01 +02:00
lHandled : = false ;
2022-08-02 17:07:14 +02:00
DoItemApplyAction( fListToInsert[ I] , eaCreate, ItemApplyAction, lHandled) ;
2021-04-29 22:52:28 +02:00
if not lHandled then
begin
2022-08-02 17:07:14 +02:00
fListToInsert[ I] . Insert;
2021-04-29 22:52:28 +02:00
end ;
2021-04-26 23:01:31 +02:00
end ;
2022-08-02 17:07:14 +02:00
for I : = 0 to fListToUpdate. Count - 1 do
2021-04-26 23:01:31 +02:00
begin
2021-05-03 19:29:01 +02:00
lHandled : = false ;
2022-08-02 17:07:14 +02:00
DoItemApplyAction( fListToUpdate[ I] , eaUpdate, ItemApplyAction, lHandled) ;
2021-04-29 22:52:28 +02:00
if not lHandled then
begin
2022-08-02 17:07:14 +02:00
fListToUpdate[ I] . Update( True ) ;
2021-04-29 22:52:28 +02:00
end ;
2021-04-26 23:01:31 +02:00
end ;
2022-08-02 17:07:14 +02:00
for I : = 0 to fListToDelete. Count - 1 do
2021-04-26 23:01:31 +02:00
begin
2021-05-03 19:29:01 +02:00
lHandled : = false ;
2022-08-02 17:07:14 +02:00
DoItemApplyAction( fListToDelete[ I] , eaDelete, ItemApplyAction, lHandled) ;
2021-04-29 22:52:28 +02:00
if not lHandled then
begin
2022-08-02 17:07:14 +02:00
fListToDelete[ I] . Delete( True ) ;
2021-04-29 22:52:28 +02:00
end ;
2021-04-26 23:01:31 +02:00
end ;
end ;
constructor TMVCUnitOfWork< T> . Create;
begin
2022-08-28 13:06:16 +02:00
inherited Create;
2021-05-03 19:29:01 +02:00
fListToDelete : = TObjectList< T> . Create( false ) ;
fListToUpdate : = TObjectList< T> . Create( false ) ;
fListToInsert : = TObjectList< T> . Create( false ) ;
2021-04-26 23:01:31 +02:00
end ;
destructor TMVCUnitOfWork< T> . Destroy;
begin
fListToDelete. Free;
fListToUpdate. Free;
fListToInsert. Free;
inherited ;
end ;
2022-08-02 17:07:14 +02:00
procedure TMVCUnitOfWork< T> . DoItemApplyAction( const Obj: TMVCActiveRecord; const EntityAction: TMVCEntityAction;
const ItemApplyAction: TMVCItemApplyAction< T> ; var Handled: Boolean ) ;
2021-04-26 23:01:31 +02:00
begin
2021-04-29 22:52:28 +02:00
if Assigned( ItemApplyAction) then
begin
ItemApplyAction( Obj, EntityAction, Handled) ;
end ;
2021-04-26 23:01:31 +02:00
end ;
2022-08-02 17:07:14 +02:00
class function TMVCUnitOfWork< T> . KeyExistsInt( const NewList: TObjectList< T> ; const KeyValue: Integer ;
out Index : Integer ) : Boolean ;
2021-04-27 22:57:15 +02:00
var
2022-08-02 17:07:14 +02:00
I: Integer ;
2021-04-27 22:57:15 +02:00
begin
2021-05-03 19:29:01 +02:00
Result : = false ;
2022-08-02 17:07:14 +02:00
for I : = 0 to NewList. Count - 1 do
2021-05-03 19:29:01 +02:00
begin
2022-08-02 17:07:14 +02:00
if NewList[ I] . GetPK. AsInteger = KeyValue then
2021-05-03 19:29:01 +02:00
begin
2022-08-02 17:07:14 +02:00
Index : = I;
2021-05-03 19:29:01 +02:00
Exit( True ) ;
end ;
end ;
end ;
2022-08-02 17:07:14 +02:00
class function TMVCUnitOfWork< T> . KeyExistsInt64( const NewList: TObjectList< T> ; const KeyValue: int64 ;
out Index : Integer ) : Boolean ;
2021-05-03 19:29:01 +02:00
var
2022-08-02 17:07:14 +02:00
I: Integer ;
2021-05-03 19:29:01 +02:00
begin
Result : = false ;
2022-08-02 17:07:14 +02:00
for I : = 0 to NewList. Count - 1 do
2021-05-03 19:29:01 +02:00
begin
2022-08-02 17:07:14 +02:00
if ( not NewList[ I] . PKIsNull) and ( NewList[ I] . GetPK. AsInt64 = KeyValue) then
2021-05-03 19:29:01 +02:00
begin
2022-08-02 17:07:14 +02:00
Index : = I;
2021-05-03 19:29:01 +02:00
Exit( True ) ;
end ;
end ;
end ;
2022-08-02 17:07:14 +02:00
class function TMVCUnitOfWork< T> . KeyExistsString( const NewList: TObjectList< T> ; const KeyValue: String ;
out Index : Integer ) : Boolean ;
2021-05-03 19:29:01 +02:00
var
2022-08-02 17:07:14 +02:00
I: Integer ;
2021-05-03 19:29:01 +02:00
begin
Result : = false ;
2022-08-02 17:07:14 +02:00
for I : = 0 to NewList. Count - 1 do
2021-04-27 22:57:15 +02:00
begin
2022-08-02 17:07:14 +02:00
if NewList[ I] . GetPK. AsString = KeyValue then
2021-04-27 22:57:15 +02:00
begin
2022-08-02 17:07:14 +02:00
Index : = I;
2021-04-27 22:57:15 +02:00
Exit( True ) ;
end ;
end ;
end ;
procedure TMVCUnitOfWork< T> . RegisterDelete( const Value: T) ;
begin
2021-04-29 22:52:28 +02:00
fListToDelete. Add( Value) ;
2021-04-27 22:57:15 +02:00
end ;
procedure TMVCUnitOfWork< T> . RegisterDelete( const Enumerable: TEnumerable< T> ) ;
begin
2021-04-29 22:52:28 +02:00
fListToDelete. AddRange( Enumerable) ;
2021-04-27 22:57:15 +02:00
end ;
procedure TMVCUnitOfWork< T> . RegisterInsert( const Value: T) ;
begin
2021-04-29 22:52:28 +02:00
fListToInsert. Add( Value) ;
2021-04-27 22:57:15 +02:00
end ;
procedure TMVCUnitOfWork< T> . RegisterUpdate( const Value: T) ;
var
lCurrPKValue: Integer ;
lFoundAtIndex: Integer ;
begin
2021-04-29 22:52:28 +02:00
fListToUpdate. Add( Value) ;
2021-04-27 22:57:15 +02:00
lCurrPKValue : = Value. GetPK. AsInteger;
2021-05-03 19:29:01 +02:00
if KeyExistsInt( fListToDelete, lCurrPKValue, lFoundAtIndex) then
2021-04-27 22:57:15 +02:00
begin
fListToDelete. Delete( lFoundAtIndex) ;
end ;
end ;
procedure TMVCUnitOfWork< T> . UnregisterDelete( const Value: T) ;
begin
2021-04-29 22:52:28 +02:00
fListToDelete. Remove( Value) ;
2021-04-27 22:57:15 +02:00
end ;
procedure TMVCUnitOfWork< T> . UnregisterInsert( const Value: T) ;
begin
2021-04-29 22:52:28 +02:00
fListToInsert. Remove( Value) ;
2021-04-27 22:57:15 +02:00
end ;
procedure TMVCUnitOfWork< T> . UnregisterUpdate( const Value: T) ;
begin
2021-04-29 22:52:28 +02:00
fListToUpdate. Remove( Value) ;
2021-04-27 22:57:15 +02:00
end ;
2021-11-17 15:36:20 +01:00
constructor MVCTableAttribute. Create( aName, aRQLFilter: String ) ;
begin
inherited Create;
Name : = aName;
RQLFilter : = aRQLFilter;
end ;
2021-11-18 00:49:12 +01:00
{ MVCPartitionAttribute }
constructor MVCPartitionAttribute. Create( const PartitionClause: String ) ;
begin
inherited Create;
Self. PartitionClause : = PartitionClause;
end ;
{ TPartitionInfo }
constructor TPartitionInfo. Create;
begin
inherited ;
2021-11-18 17:52:06 +01:00
fFieldNames : = TPartitionFieldNames. Create;
fFieldValues : = TPartitionFieldValues. Create;
fFieldTypes : = TPartitionFieldTypes. Create;
end ;
destructor TPartitionInfo. Destroy;
begin
fFieldNames. Free;
fFieldValues. Free;
fFieldTypes. Free;
inherited ;
end ;
class destructor TPartitionInfo. Destroy;
begin
PartitionInfoCache. Free;
2021-11-18 00:49:12 +01:00
end ;
2022-08-02 17:07:14 +02:00
procedure TPartitionInfo. InitializeFilterStrings( const RQLCompiler: TRQLCompiler) ;
2021-11-18 00:49:12 +01:00
var
lFieldCount, I: Integer ;
2021-11-23 18:03:48 +01:00
lRQL2SQL: TRQL2SQL;
2021-11-18 00:49:12 +01:00
begin
2021-11-18 17:52:06 +01:00
fRQLFilter : = '' ;
lFieldCount : = FieldNames. Count;
2021-11-18 00:49:12 +01:00
if lFieldCount > 0 then
begin
for I : = 0 to lFieldCount - 1 do
begin
2021-11-19 00:34:37 +01:00
case FieldTypes[ I] of
ftString:
2022-08-02 17:07:14 +02:00
begin
fRQLFilter : = fRQLFilter + 'eq(' + FieldNames[ I] + ',' + FieldValues[ I] . QuotedString( '"' ) + '),' ;
end ;
2021-11-19 00:34:37 +01:00
ftInteger:
2022-08-02 17:07:14 +02:00
begin
fRQLFilter : = fRQLFilter + 'eq(' + FieldNames[ I] + ',' + FieldValues[ I] + '),' ;
end ;
else
raise ERQLException. CreateFmt( 'DataType for field [%s] not supported in partition clause' , [ fFieldNames[ I] ] ) ;
2021-11-19 00:34:37 +01:00
end ;
2021-11-18 00:49:12 +01:00
end ;
2022-08-02 17:07:14 +02:00
fRQLFilter : = fRQLFilter. Remove( fRQLFilter. Length - 1 , 1 ) ;
2021-11-18 00:49:12 +01:00
if lFieldCount > 1 then
begin
2021-11-18 17:52:06 +01:00
fRQLFilter : = 'and(' + fRQLFilter + ')' ;
2021-11-18 00:49:12 +01:00
end ;
end ;
2021-11-23 18:03:48 +01:00
lRQL2SQL : = TRQL2SQL. Create;
2021-11-18 17:52:06 +01:00
try
2022-08-02 17:07:14 +02:00
lRQL2SQL. Execute( fRQLFilter, fSQLFilter, RQLCompiler, false , True )
2021-11-18 17:52:06 +01:00
finally
2021-11-23 18:03:48 +01:00
lRQL2SQL. Free;
2021-11-18 17:52:06 +01:00
end ;
fSQLFilter : = TMVCSQLGenerator. RemoveInitialWhereKeyword( fSQLFilter) ;
end ;
2022-08-02 17:07:14 +02:00
class function TPartitionInfo. BuildPartitionClause( const PartitionClause: String ;
const RQLCompilerClass: TRQLCompilerClass) : TPartitionInfo;
2021-11-18 17:52:06 +01:00
var
lPieces, lItems: TArray< String > ;
lPiece: String ;
lRQLCompiler: TRQLCompiler;
begin
{
2022-08-02 17:07:14 +02:00
Needs to parse [ MVCPartition( 'rating=(integer)4;classname=(string)persona' ) ]
2021-11-18 17:52:06 +01:00
}
if not PartitionInfoCache. TryGetValue( PartitionClause + '|' + RQLCompilerClass. ClassName, Result ) then
begin
lRQLCompiler : = RQLCompilerClass. Create( nil ) ;
try
Result : = TPartitionInfo. Create;
try
lPieces : = PartitionClause. Split( [ ';' ] ) ;
for lPiece in lPieces do
begin
2022-08-02 17:07:14 +02:00
lItems : = lPiece. Split( [ '=' , '(' , ')' ] , TStringSplitOptions. ExcludeEmpty) ;
if Length( lItems) < > 3 then
2021-11-18 17:52:06 +01:00
begin
2022-08-02 17:07:14 +02:00
raise EMVCActiveRecord. Create( 'Invalid partitioning clause: ' + lPiece +
2022-08-13 15:01:15 +02:00
'. [HINT] Partioning must be in the form: "[fieldname1=(integer|string)value1]"' ) ;
2021-11-18 17:52:06 +01:00
end ;
Result . FieldNames. Add( lItems[ 0 ] ) ;
2022-08-02 17:07:14 +02:00
if lItems[ 1 ] = 'integer' then
2021-11-18 17:52:06 +01:00
Result . FieldTypes. Add( ftInteger)
2022-08-02 17:07:14 +02:00
else if lItems[ 1 ] = 'string' then
2021-11-18 17:52:06 +01:00
begin
Result . FieldTypes. Add( ftString)
end
else
begin
2022-08-02 17:07:14 +02:00
raise EMVCActiveRecord. Create( 'Unknown data type in partitioning: ' + lItems[ 1 ] +
'. [HINT] data type can be "integer" or "string"' ) ;
2021-11-18 17:52:06 +01:00
end ;
Result . FieldValues. Add( lItems[ 2 ] ) ;
end ;
except
Result . Free;
raise ;
end ;
Result . InitializeFilterStrings( lRQLCompiler) ;
PartitionInfoCache. Add( PartitionClause + '|' + RQLCompilerClass. ClassName, Result ) ;
finally
lRQLCompiler. Free;
end ;
end ;
end ;
class constructor TPartitionInfo. Create;
begin
PartitionInfoCache : = TMVCThreadedObjectCache< TPartitionInfo> . Create;
2021-11-18 00:49:12 +01:00
end ;
2022-11-17 19:33:27 +01:00
{ TMVCTableMapRepository }
procedure TMVCTableMapRepository. AddTableMap( const AR: TMVCActiveRecord; const TableMap: TMVCTableMap) ;
begin
fMREW. BeginWrite;
try
fTableMapDict. Add( GetCacheKey( AR) , TableMap) ;
finally
fMREW. EndWrite;
end ;
end ;
constructor TMVCTableMapRepository. Create;
begin
inherited ;
fMREW : = TMultiReadExclusiveWriteSynchronizer. Create;
fTableMapDict : = TObjectDictionary< String , TMVCTableMap> . Create( [ doOwnsValues] ) ;
end ;
destructor TMVCTableMapRepository. Destroy;
begin
fTableMapDict. Free;
fMREW. Free;
inherited ;
end ;
procedure TMVCTableMapRepository. ExecWithExclusiveLock( Proc: TProc< IMVCActiveRecordTableMap> ) ;
begin
fMREW. BeginWrite;
try
Proc( Self) ;
finally
fMREW. EndWrite;
end ;
end ;
procedure TMVCTableMapRepository. FlushCache;
begin
ExecWithExclusiveLock(
procedure( Map: IMVCActiveRecordTableMap)
begin
TMVCTableMapRepository( Map) . fTableMapDict. Clear;
end ) ;
end ;
function TMVCTableMapRepository. GetCacheKey( const AR: TMVCActiveRecord) : String ;
begin
Result : = AR. QualifiedClassName;
end ;
function TMVCTableMapRepository. GetTableMap(
const TypeInfo: TMVCActiveRecord) : TMVCTableMap;
begin
{$IF not Defined(TokyoOrBetter)}
Result : = nil ;
{$ENDIF}
fMREW. BeginRead;
try
if not fTableMapDict. TryGetValue( TypeInfo. QualifiedClassName, Result ) then
begin
Result : = nil ;
end ;
finally
fMREW. EndRead;
end ;
end ;
function TMVCTableMapRepository. TryGetValue( const AR: TMVCActiveRecord;
out TableMap: TMVCTableMap) : Boolean ;
begin
{$IF not Defined(TokyoOrBetter)}
Result : = nil ;
{$ENDIF}
fMREW. BeginRead;
try
Result : = fTableMapDict. TryGetValue( GetCacheKey( AR) , TableMap) ;
finally
fMREW. EndRead;
end ;
end ;
{ TMVCTableMap }
constructor TMVCTableMap. Create;
begin
inherited ;
fMap : = TFieldsMap. Create;
end ;
destructor TMVCTableMap. Destroy;
begin
fMap. Free;
inherited ;
end ;
2023-08-09 00:46:31 +02:00
class function TMVCActiveRecordHelper. Select( const aClass: TMVCActiveRecordClass;
2023-08-08 14:31:23 +02:00
const SQL: string ; const Params: array of Variant ;
const Connection: TFDConnection; const OutList: TMVCActiveRecordList) : UInt32 ;
var
lDataSet: TDataSet;
lAR: TMVCActiveRecord;
begin
lDataSet : = ExecQuery( SQL, Params, Connection, True , False ) ;
try
while not lDataSet. Eof do
begin
lAR : = aClass. Create;
OutList. Add( lAR) ;
lAR. LoadByDataset( lDataSet) ;
lDataSet. Next;
end ;
Result : = OutList. Count;
finally
lDataSet. Free;
end ;
end ;
2023-08-09 01:23:24 +02:00
{ MVCNamedSQLQueryAttribute }
constructor MVCNamedSQLQueryAttribute. Create( aName, aSQLSelect: String ) ;
2023-08-09 10:55:34 +02:00
begin
Create( aName, aSQLSelect, '' ) ;
end ;
constructor MVCNamedSQLQueryAttribute. Create( aName, aSQLSelect,
aBackEnd: String ) ;
2023-08-09 01:23:24 +02:00
begin
inherited Create;
Name : = aName;
SQLQuery : = aSQLSelect;
2023-08-09 10:55:34 +02:00
BackEnd : = aBackEnd;
end ;
{ MVCNamedRQLQueryAttribute }
constructor MVCNamedRQLQueryAttribute. Create( aName, aRQL: String ) ;
begin
inherited Create;
Name : = aName;
RQLQuery : = aRQL;
2023-08-09 01:23:24 +02:00
end ;
2018-09-25 15:36:53 +02:00
initialization
2022-11-17 19:33:27 +01:00
gConnectionsLock : = TObject. Create;
gTableMapLock : = 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;
2022-11-17 19:33:27 +01:00
gConnectionsLock. Free;
gTableMapLock. Free;
2018-09-25 15:36:53 +02:00
end .