New feature: ObjectVersioning for TMVCActiveRecord - see foVersion

- TMVCActiveRecord.TableName is now readonly
This commit is contained in:
Daniele Teti 2023-11-02 17:36:19 +01:00
parent 7c7443d0b2
commit a2f190df44
17 changed files with 346 additions and 198 deletions

View File

@ -681,6 +681,35 @@ type
end;
[MVCNameCase(ncLowerCase)]
[MVCTable('customers_with_version')]
TCustomerWithVersion = class(TCustomEntity)
private
[MVCTableField('id', [foPrimaryKey, foAutoGenerated])]
fID: NullableInt64;
[MVCTableField('code')]
fCode: NullableString;
[MVCTableField('description')]
fCompanyName: NullableString;
[MVCTableField('city')]
fCity: string;
[MVCTableField('rating')]
fRating: NullableInt32;
[MVCTableField('note')]
fNote: string;
[MVCTableField('objversion', [foVersion])]
fObjVersion: Integer;
public
function ToString: String; override;
property ID: NullableInt64 read fID write fID;
property Code: NullableString read fCode write fCode;
property CompanyName: NullableString read fCompanyName write fCompanyName;
property City: string read fCity write fCity;
property Rating: NullableInt32 read fRating write fRating;
property Note: string read fNote write fNote;
end;
implementation
@ -896,4 +925,17 @@ begin
inherited;
end;
{ TCustomerWithVersion }
function TCustomerWithVersion.ToString: String;
begin
Result := '';
if PKIsNull then
Result := '<null>'
else
Result := fID.ValueOrDefault.ToString;
Result := Format('[ID: %6s][CODE: %6s][CompanyName: %18s][City: %16s][Rating: %3d][Note: %s][Version: %d]',[
Result, fCode.ValueOrDefault, fCompanyName.ValueOrDefault, fCity, fRating.ValueOrDefault, fNote, fObjVersion]);
end;
end.

View File

@ -2,8 +2,8 @@ object MainForm: TMainForm
Left = 0
Top = 0
Caption = 'TMVCActiveRecord - ShowCase'
ClientHeight = 593
ClientWidth = 1104
ClientHeight = 626
ClientWidth = 1094
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
@ -13,8 +13,8 @@ object MainForm: TMainForm
OnDestroy = FormDestroy
OnShow = FormShow
DesignSize = (
1104
593)
1094
626)
TextHeight = 13
object btnCRUD: TButton
Left = 8
@ -37,8 +37,8 @@ object MainForm: TMainForm
object Memo1: TMemo
Left = 280
Top = 8
Width = 816
Height = 577
Width = 806
Height = 610
Anchors = [akLeft, akTop, akRight, akBottom]
Ctl3D = True
DoubleBuffered = True
@ -55,8 +55,8 @@ object MainForm: TMainForm
TabOrder = 2
WantReturns = False
WordWrap = False
ExplicitWidth = 812
ExplicitHeight = 576
ExplicitWidth = 802
ExplicitHeight = 609
end
object btnRelations: TButton
Left = 8
@ -294,6 +294,15 @@ object MainForm: TMainForm
TabOrder = 28
OnClick = btnIntegersAsBoolClick
end
object btnObjectVersion: TButton
Left = 8
Top = 562
Width = 121
Height = 34
Caption = 'Object Version'
TabOrder = 29
OnClick = btnObjectVersionClick
end
object FDConnection1: TFDConnection
Left = 312
Top = 40

View File

@ -61,6 +61,7 @@ type
btnNamedQuery: TButton;
btnVirtualEntities: TButton;
btnIntegersAsBool: TButton;
btnObjectVersion: TButton;
procedure btnCRUDClick(Sender: TObject);
procedure btnInheritanceClick(Sender: TObject);
procedure btnMultiThreadingClick(Sender: TObject);
@ -92,6 +93,7 @@ type
procedure btnNamedQueryClick(Sender: TObject);
procedure btnVirtualEntitiesClick(Sender: TObject);
procedure btnIntegersAsBoolClick(Sender: TObject);
procedure btnObjectVersionClick(Sender: TObject);
private
procedure Log(const Value: string);
procedure LoadCustomers(const HowManyCustomers: Integer = 50);
@ -1951,6 +1953,33 @@ begin
end;
end;
procedure TMainForm.btnObjectVersionClick(Sender: TObject);
begin
var lID: NullableInt64;
var lCust := TCustomerWithVersion.Create();
try
Log('Entity ' + TCustomerWithVersion.ClassName + ' is mapped to table ' + lCust.TableName);
lCust.CompanyName := 'Google Inc.';
lCust.City := 'Montain View, CA';
lCust.Note := 'Μῆνιν ἄειδε θεὰ Πηληϊάδεω Ἀχιλῆος οὐλομένην 😁';
lCust.Insert;
lID := lCust.ID;
Log('Just inserted CustomerWithVersion ' + lID.ValueOrDefault.ToString);
lCust.Store;
finally
lCust.Free;
end;
lCust := TMVCActiveRecord.GetByPK<TCustomerWithVersion>(lID);
try
lCust.CompanyName := 'Alphabet Inc.';
lCust.Store;
finally
lCust.Free;
end;
end;
procedure TMainForm.btnOOPClick(Sender: TObject);
begin
Log('** OOP with ActiveRecord (person, employee, manager)');

Binary file not shown.

Binary file not shown.

View File

@ -101,5 +101,16 @@ create table integers_as_booleans (
done_int smallint not null
);
CREATE TABLE customers_with_version (
id bigint generated by default as identity NOT NULL,
code varchar(20),
description varchar(200),
city varchar(200),
note varchar(1000),
rating integer,
objversion integer
);
ALTER TABLE orders ADD CONSTRAINT orders_customers_fk FOREIGN KEY (id_customer) REFERENCES customers(id) ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE order_details ADD CONSTRAINT order_details_orders_fk FOREIGN KEY (id_order) REFERENCES orders(id) ON DELETE CASCADE ON UPDATE CASCADE;

View File

@ -117,6 +117,22 @@ CREATE TABLE "customers with spaces" (
CONSTRAINT cust_with_space_pk PRIMARY KEY ("id with spaces")
);
create table integers_as_booleans (
id bigint generated by default as identity primary key,
done_bool boolean not null,
done_int smallint not null
);
CREATE TABLE customers_with_version (
id bigint generated by default as identity NOT NULL,
code varchar(20),
description varchar(200),
city varchar(200),
note varchar(1000),
rating integer,
objversion integer
);
ALTER TABLE orders ADD CONSTRAINT orders_customers_fk FOREIGN KEY (id_customer) REFERENCES customers(id) ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE order_details ADD CONSTRAINT order_details_orders_fk FOREIGN KEY (id_order) REFERENCES orders(id) ON DELETE CASCADE ON UPDATE CASCADE;

View File

@ -115,5 +115,15 @@ create table integers_as_booleans (
done_int smallint not null
);
CREATE TABLE customers_with_version (
id bigint not null auto_increment primary key,
code varchar(20),
description varchar(200),
city varchar(200),
note varchar(1000),
rating integer,
objversion integer
);
ALTER TABLE orders ADD CONSTRAINT orders_customers_fk FOREIGN KEY (id_customer) REFERENCES customers(id) ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE order_details ADD CONSTRAINT order_details_orders_fk FOREIGN KEY (id_order) REFERENCES orders(id) ON DELETE CASCADE ON UPDATE CASCADE;

View File

@ -131,6 +131,19 @@ CREATE TABLE public.customers (
ALTER TABLE public.customers OWNER TO postgres;
CREATE TABLE public.customers_with_version (
id bigint generated by default as identity NOT NULL,
code character varying(20),
description character varying(200),
city character varying(200),
note text,
rating integer,
objversion integer
);
ALTER TABLE public.customers_with_version OWNER TO postgres;
--
-- TOC entry 205 (class 1259 OID 58864)
-- Name: customers with spaces; Type: TABLE; Schema: public; Owner: postgres

View File

@ -63,7 +63,9 @@ type
TMVCActiveRecordFieldOption = (foPrimaryKey, { it's the primary key of the mapped table }
foAutoGenerated, { not written, read - similar to readonly }
foReadOnly, { not written, read }
foWriteOnly); { written, not read }
foWriteOnly, { written, not read }
foVersion {used for versioning, only one field with foVersion is allowed in class}
);
TMVCActiveRecordFieldOptions = set of TMVCActiveRecordFieldOption;
TMVCEntityAction = (eaCreate, eaRetrieve, eaUpdate, eaDelete);
TMVCEntityActions = set of TMVCEntityAction;
@ -95,11 +97,10 @@ type
TFieldInfo = class
public
// TableName: string;
FieldName: string;
FieldOptions: TMVCActiveRecordFieldOptions;
DataTypeName: string;
Writeable, Readable: Boolean;
Writeable, Readable, IsVersion: Boolean;
procedure EndUpdates;
end;
@ -213,16 +214,19 @@ type
end;
TMVCTableMap = class
protected
public
fPartitionInfoInternal: TPartitionInfo;
fEntityAllowedActions: TMVCEntityActions;
fTableName: string;
fTableName: String;
fVersionFieldName: String;
fIsVersioned: Boolean;
fPartitionClause: String;
fRTTIType: TRttiInstanceType;
fObjAttributes: TArray<TCustomAttribute>;
fDefaultRQLFilter: string;
fMap: TFieldsMap;
fPrimaryKey: TRTTIField;
fVersionRTTIField: TRttiField;
fMapping: TMVCFieldsMapping;
fPropsAttributes: TArray<TCustomAttribute>;
fProps: TArray<TRTTIField>;
@ -243,15 +247,15 @@ type
fConn: TFDConnection;
fSQLGenerator: TMVCSQLGenerator;
fRQL2SQL: TRQL2SQL;
fCustomTableName: String;
procedure MapTValueToParam(aValue: TValue; const aParam: TFDParam);
function MapNullableTValueToParam(aValue: TValue; const aParam: TFDParam): Boolean;
function GetPrimaryKeyIsAutogenerated: Boolean;
procedure SetPrimaryKeyIsAutogenerated(const Value: Boolean);
procedure SetTableName(const Value: string);
function GetAttributes(const AttrName: string): TValue;
procedure SetAttributes(const AttrName: string; const Value: TValue);
function GetTableName: string;
procedure AdvanceVersioning(const TableMap: TMVCTableMap; const ARInstance: TMVCActiveRecord);
procedure SetInitialObjVersion(const TableMap: TMVCTableMap; const ARInstance: TMVCActiveRecord);
protected
fBackendDriver: string;
fTableMap: TMVCTableMap;
@ -413,8 +417,7 @@ type
[MVCDoNotSerialize]
property TableName: string
read GetTableName
write SetTableName;
read GetTableName;
[MVCDoNotSerialize]
property PrimaryKeyIsAutogenerated: Boolean
@ -791,18 +794,15 @@ type
const MaxRecordCount: Int32 = TMVCConstants.MAX_RECORD_COUNT): string;
function CreateSelectSQL(const TableName: string; const Map: TFieldsMap; const PKFieldName: string;
const PKOptions: TMVCActiveRecordFieldOptions): string; virtual;
function CreateInsertSQL(const TableName: string; const Map: TFieldsMap; const PKFieldName: string;
const PKOptions: TMVCActiveRecordFieldOptions): string; virtual; abstract;
function CreateInsertSQL(const TableMap: TMVCTableMap; const ARInstance: TMVCActiveRecord): string; virtual; abstract;
// virtual methods with default implementation
function CreateSelectByPKSQL(const TableName: string; const Map: TFieldsMap; const PKFieldName: string;
const PKOptions: TMVCActiveRecordFieldOptions): string; virtual;
function CreateDeleteSQL(const TableName: string; const Map: TFieldsMap; const PKFieldName: string;
const PKOptions: TMVCActiveRecordFieldOptions): string; virtual;
function CreateDeleteSQL(const TableMap: TMVCTableMap; const ARInstance: TMVCActiveRecord): string; virtual;
function CreateDeleteAllSQL(const TableName: string): string; virtual;
function CreateSelectCount(const TableName: string): string; virtual;
function CreateUpdateSQL(const TableName: string; const Map: TFieldsMap; const PKFieldName: string;
const PKOptions: TMVCActiveRecordFieldOptions): string; virtual;
function CreateUpdateSQL(const TableMap: TMVCTableMap; const ARInstance: TMVCActiveRecord): string; virtual;
function GetSequenceValueSQL(const PKFieldName: string; const SequenceName: string; const Step: Integer = 1)
: string; virtual;
@ -895,6 +895,10 @@ function ActiveRecordTableMapRegistry: IMVCActiveRecordTableMap;
function ActiveRecordMappingRegistry: IMVCEntitiesRegistry;
function GetBackEndByConnection(aConnection: TFDConnection): string;
const
OBJECT_VERSION_STARTING_VALUE = '1';
OBJECT_VERSION_STARTING_VALUE_AS_INT: Int64 = 1;
implementation
uses
@ -1564,6 +1568,17 @@ begin
lFieldInfo.FieldName := MVCTableFieldAttribute(lAttribute).FieldName;
lFieldInfo.FieldOptions := MVCTableFieldAttribute(lAttribute).FieldOptions;
lFieldInfo.DataTypeName := MVCTableFieldAttribute(lAttribute).DataTypeName;
if foVersion in lFieldInfo.FieldOptions then
begin
if not lTableMap.fVersionFieldName.IsEmpty then
begin
raise EMVCActiveRecord.Create('Only one version field is allowed for table - Currently at least fields [' + lTableMap.fVersionFieldName + '] and [' + lFieldInfo.FieldName + '] are marked as foVersion');
end;
lTableMap.fVersionRTTIField := lRTTIField;
lTableMap.fVersionFieldName := lFieldInfo.FieldName;
lTableMap.fIsVersioned := True;
end;
end;
end;
end;
@ -1620,9 +1635,13 @@ begin
end;
end;
SQL := SQLGenerator.CreateInsertSQL(TableName, fTableMap.fMap,
fTableMap.fPrimaryKeyFieldName, fTableMap.fPrimaryKeyOptions);
SQL := SQLGenerator.CreateInsertSQL(fTableMap, Self);
ExecNonQuery(SQL, True);
if fTableMap.fIsVersioned then
begin
{ in case of INSERT version is defined by constants }
SetInitialObjVersion(fTableMap, Self);
end;
OnAfterInsert;
OnAfterInsertOrUpdate;
end;
@ -2030,10 +2049,7 @@ end;
function TMVCActiveRecord.GetTableName: string;
begin
if fCustomTableName.IsEmpty then
Result := fTableMap.fTableName
else
Result := fCustomTableName;
Result := fTableMap.fTableName
end;
function TMVCActiveRecord.CheckAction(const aEntityAction: TMVCEntityAction; const aRaiseException: Boolean): Boolean;
@ -2184,13 +2200,20 @@ begin
OnBeforeDelete;
if not Assigned(fTableMap.fPrimaryKey) then
raise Exception.CreateFmt('Cannot delete %s without a primary key', [ClassName]);
SQL := SQLGenerator.CreateDeleteSQL(TableName, fTableMap.fMap,
fTableMap.fPrimaryKeyFieldName, fTableMap.fPrimaryKeyOptions);
SQL := SQLGenerator.CreateDeleteSQL(fTableMap, Self);
lAffectedRows := ExecNonQuery(SQL, false);
if (lAffectedRows = 0) and RaiseExceptionIfNotFound then
begin
raise EMVCActiveRecordNotFound.CreateFmt('No record deleted for key [Entity: %s][PK: %s]',
[ClassName, fTableMap.fPrimaryKeyFieldName]);
if fTableMap.fIsVersioned then
begin
raise EMVCActiveRecordNotFound.CreateFmt('No record deleted for key [Entity: %s][PK: %s][Version: %d]',
[ClassName, fTableMap.fPrimaryKeyFieldName, fTableMap.fVersionRTTIField.GetValue(Self).AsInt64]);
end
else
begin
raise EMVCActiveRecordNotFound.CreateFmt('No record deleted for key [Entity: %s][PK: %s]',
[ClassName, fTableMap.fPrimaryKeyFieldName]);
end;
end;
OnAfterDelete;
end;
@ -3340,16 +3363,10 @@ begin
end;
end;
procedure TMVCActiveRecord.SetTableName(const Value: string);
procedure TMVCActiveRecord.SetInitialObjVersion(const TableMap: TMVCTableMap;
const ARInstance: TMVCActiveRecord);
begin
if Value = fTableMap.fTableName then
begin
fCustomTableName := '';
end
else
begin
fCustomTableName := Value;
end;
TableMap.fVersionRTTIField.SetValue(ARInstance, OBJECT_VERSION_STARTING_VALUE_AS_INT);
end;
function TMVCActiveRecord.SQLGenerator: TMVCSQLGenerator;
@ -3460,13 +3477,24 @@ begin
raise EMVCActiveRecord.CreateFmt
('Cannot update an entity if no fields are writeable. Class [%s] mapped on table [%s]', [ClassName, TableName]);
end;
SQL := SQLGenerator.CreateUpdateSQL(TableName, fTableMap.fMap,
fTableMap.fPrimaryKeyFieldName, fTableMap.fPrimaryKeyOptions);
SQL := SQLGenerator.CreateUpdateSQL(fTableMap, Self);
lAffectedRows := ExecNonQuery(SQL, false);
if (lAffectedRows = 0) and RaiseExceptionIfNotFound then
begin
raise EMVCActiveRecordNotFound.CreateFmt('No record updated for key [Entity: %s][PK: %s]',
[ClassName, fTableMap.fPrimaryKeyFieldName]);
if fTableMap.fIsVersioned then
begin
raise EMVCActiveRecordNotFound.CreateFmt('No record updated for key [Entity: %s][PK: %s][Version: %d]',
[ClassName, fTableMap.fPrimaryKeyFieldName, fTableMap.fVersionRTTIField.GetValue(Self).AsInt64]);
end
else
begin
raise EMVCActiveRecordNotFound.CreateFmt('No record updated for key [Entity: %s][PK: %s]',
[ClassName, fTableMap.fPrimaryKeyFieldName]);
end;
end;
if fTableMap.fIsVersioned then
begin
AdvanceVersioning(fTableMap, Self);
end;
OnAfterUpdate;
OnAfterInsertOrUpdate;
@ -3511,6 +3539,15 @@ begin
end;
end;
procedure TMVCActiveRecord.AdvanceVersioning(const TableMap: TMVCTableMap; const ARInstance: TMVCActiveRecord);
var
lCurrVersion: Int64;
begin
lCurrVersion := TableMap.fVersionRTTIField.GetValue(ARInstance).AsInt64;
Inc(lCurrVersion);
TableMap.fVersionRTTIField.SetValue(ARInstance, lCurrVersion);
end;
procedure TMVCActiveRecord.Assign(ActiveRecord: TMVCActiveRecord);
begin
//do nothing
@ -3790,11 +3827,14 @@ begin
Result := 'DELETE FROM ' + GetTableNameForSQL(TableName);
end;
function TMVCSQLGenerator.CreateDeleteSQL(const TableName: string; const Map: TFieldsMap; const PKFieldName: string;
const PKOptions: TMVCActiveRecordFieldOptions): string;
function TMVCSQLGenerator.CreateDeleteSQL(const TableMap: TMVCTableMap; const ARInstance: TMVCActiveRecord): string;
begin
Result := CreateDeleteAllSQL(TableName) + ' WHERE ' + GetFieldNameForSQL(PKFieldName) + '=:' +
GetParamNameForSQL(PKFieldName);
Result := CreateDeleteAllSQL(TableMap.fTableName) + ' WHERE ' + GetFieldNameForSQL(TableMap.fPrimaryKeyFieldName) + '=:' +
GetParamNameForSQL(TableMap.fPrimaryKeyFieldName);
if TableMap.fIsVersioned then
begin
Result := Result + ' and ' + GetFieldNameForSQL(TableMap.fVersionFieldName) + ' = ' + IntToStr(TableMap.fVersionRTTIField.GetValue(ARInstance).AsInt64);
end;
end;
function TMVCSQLGenerator.CreateSelectByPKSQL(const TableName: string; const Map: TFieldsMap; const PKFieldName: string;
@ -3829,16 +3869,19 @@ begin
MaxRecordCount);
end;
function TMVCSQLGenerator.CreateUpdateSQL(const TableName: string; const Map: TFieldsMap; const PKFieldName: string;
const PKOptions: TMVCActiveRecordFieldOptions): string;
function TMVCSQLGenerator.CreateUpdateSQL(const TableMap: TMVCTableMap; const ARInstance: TMVCActiveRecord): string;
var
lPair: TPair<TRTTIField, TFieldInfo>;
// I: Integer;
begin
Result := 'UPDATE ' + GetTableNameForSQL(TableName) + ' SET ';
for lPair in Map do
Result := 'UPDATE ' + GetTableNameForSQL(TableMap.fTableName) + ' SET ';
for lPair in TableMap.fMap do
begin
if lPair.Value.Writeable then
if lPair.Value.IsVersion then
begin
Result := Result + GetFieldNameForSQL(lPair.Value.FieldName) + ' = :' +
GetParamNameForSQL(lPair.Value.FieldName) + ' + 1,';
end else if lPair.Value.Writeable then
begin
Result := Result + GetFieldNameForSQL(lPair.Value.FieldName) + ' = :' +
GetParamNameForSQL(lPair.Value.FieldName) + ',';
@ -3852,9 +3895,14 @@ begin
// end;
{ end-partitioning }
Result[Length(Result)] := ' ';
if not PKFieldName.IsEmpty then
if not TableMap.fPrimaryKeyFieldName.IsEmpty then
begin
Result := Result + ' where ' + GetFieldNameForSQL(PKFieldName) + '= :' + GetParamNameForSQL(PKFieldName);
Result := Result + ' where ' +
GetFieldNameForSQL(TableMap.fPrimaryKeyFieldName) + '= :' + GetParamNameForSQL(TableMap.fPrimaryKeyFieldName);
if TableMap.fIsVersioned then
begin
Result := Result + ' and ' + GetFieldNameForSQL(TableMap.fVersionFieldName) + ' = ' + TableMap.fVersionRTTIField.GetValue(ARInstance).AsInt64.ToString
end;
end
else
begin
@ -4175,6 +4223,7 @@ begin
Writeable := ((FieldOptions * [foReadOnly, foAutoGenerated]) = []);
Readable := (FieldOptions * [foWriteOnly]) = [];
end;
IsVersion := foVersion in FieldOptions;
end;
{ TMVCUnitOfWork<T> }
@ -4550,6 +4599,8 @@ constructor TMVCTableMap.Create;
begin
inherited;
fMap := TFieldsMap.Create;
fIsVersioned := False;
fVersionFieldName := '';
end;
destructor TMVCTableMap.Destroy;

View File

@ -42,10 +42,8 @@ type
function GetCompilerClass: TRQLCompilerClass; override;
public
function CreateInsertSQL(
const TableName: string;
const Map: TFieldsMap;
const PKFieldName: string;
const PKOptions: TMVCActiveRecordFieldOptions): string; override;
const TableMap: TMVCTableMap;
const ARInstance: TMVCActiveRecord): string; override;
function GetSequenceValueSQL(const PKFieldName: string;
const SequenceName: string;
const Step: Integer = 1): string; override;
@ -57,22 +55,22 @@ uses
System.SysUtils,
MVCFramework.RQL.AST2FirebirdSQL;
function TMVCSQLGeneratorFirebird.CreateInsertSQL(const TableName: string; const Map: TFieldsMap;
const PKFieldName: string; const PKOptions: TMVCActiveRecordFieldOptions): string;
function TMVCSQLGeneratorFirebird.CreateInsertSQL(const TableMap: TMVCTableMap;
const ARInstance: TMVCActiveRecord): string;
var
lKeyValue: TPair<TRttiField, TFieldInfo>;
lSB: TStringBuilder;
lPKInInsert: Boolean;
lFieldName: String;
begin
lPKInInsert := (not PKFieldName.IsEmpty) and (not(TMVCActiveRecordFieldOption.foAutoGenerated in PKOptions));
lPKInInsert := lPKInInsert and (not(TMVCActiveRecordFieldOption.foReadOnly in PKOptions));
lPKInInsert := (not TableMap.fPrimaryKeyFieldName.IsEmpty) and (not(TMVCActiveRecordFieldOption.foAutoGenerated in TableMap.fPrimaryKeyOptions));
lPKInInsert := lPKInInsert and (not(TMVCActiveRecordFieldOption.foReadOnly in TableMap.fPrimaryKeyOptions));
lSB := TStringBuilder.Create;
try
lSB.Append('INSERT INTO ' + GetTableNameForSQL(TableName) + ' (');
lSB.Append('INSERT INTO ' + GetTableNameForSQL(TableMap.fTableName) + ' (');
if lPKInInsert then
begin
lSB.Append(GetFieldNameForSQL(PKFieldName) + ',');
lSB.Append(GetFieldNameForSQL(TableMap.fPrimaryKeyFieldName) + ',');
end;
{partition}
@ -82,7 +80,7 @@ begin
end;
{end-partition}
for lKeyValue in Map do
for lKeyValue in TableMap.fMap do
begin
// if not(foTransient in lKeyValue.Value.FieldOptions) then
if lKeyValue.Value.Writeable then
@ -95,7 +93,7 @@ begin
lSB.Append(') values (');
if lPKInInsert then
begin
lSB.Append(':' + GetParamNameForSQL(PKFieldName) + ',');
lSB.Append(':' + GetParamNameForSQL(TableMap.fPrimaryKeyFieldName) + ',');
end;
{partition}
@ -105,9 +103,12 @@ begin
end;
{end-partition}
for lKeyValue in Map do
for lKeyValue in TableMap.fMap do
begin
if lKeyValue.Value.Writeable then
if lKeyValue.Value.IsVersion then
begin
lSB.Append(OBJECT_VERSION_STARTING_VALUE + ',');
end else if lKeyValue.Value.Writeable then
begin
lSB.Append(':' + GetParamNameForSQL(lKeyValue.Value.FieldName) + ',');
end;
@ -116,9 +117,9 @@ begin
lSB.Remove(lSB.Length - 1, 1);
lSB.Append(')');
if TMVCActiveRecordFieldOption.foAutoGenerated in PKOptions then
if TMVCActiveRecordFieldOption.foAutoGenerated in TableMap.fPrimaryKeyOptions then
begin
lSB.Append(' RETURNING ' + GetFieldNameForSQL(PKFieldName));
lSB.Append(' RETURNING ' + GetFieldNameForSQL(TableMap.fPrimaryKeyFieldName));
end;
Result := lSB.ToString;
finally

View File

@ -39,10 +39,9 @@ uses
type
TMVCSQLGeneratorInterbase = class(TMVCSQLGeneratorFirebird)
public
function CreateInsertSQL(const TableName: string;
const Map: TFieldsMap;
const PKFieldName: string;
const PKOptions: TMVCActiveRecordFieldOptions): string; override;
function CreateInsertSQL(
const TableMap: TMVCTableMap;
const ARInstance: TMVCActiveRecord): string; override;
function HasReturning: Boolean; override;
end;
@ -53,24 +52,23 @@ uses
{ TMVCSQLGeneratorInterbase }
function TMVCSQLGeneratorInterbase.CreateInsertSQL(const TableName: string;
const Map: TFieldsMap;
const PKFieldName: string;
const PKOptions: TMVCActiveRecordFieldOptions): string;
function TMVCSQLGeneratorInterbase.CreateInsertSQL(
const TableMap: TMVCTableMap;
const ARInstance: TMVCActiveRecord): string;
var
lKeyValue: TPair<TRttiField, TFieldInfo>;
lSB: TStringBuilder;
lPKInInsert: Boolean;
lFieldName: String;
begin
lPKInInsert := (not PKFieldName.IsEmpty); // and (not(TMVCActiveRecordFieldOption.foAutoGenerated in PKOptions));
lPKInInsert := lPKInInsert and (not(TMVCActiveRecordFieldOption.foReadOnly in PKOptions));
lPKInInsert := (not TableMap.fPrimaryKeyFieldName.IsEmpty);
lPKInInsert := lPKInInsert and (not(TMVCActiveRecordFieldOption.foReadOnly in TableMap.fPrimaryKeyOptions));
lSB := TStringBuilder.Create;
try
lSB.Append('INSERT INTO ' + GetTableNameForSQL(TableName) + '(');
lSB.Append('INSERT INTO ' + GetTableNameForSQL(TableMap.fTableName) + '(');
if lPKInInsert then
begin
lSB.Append(GetFieldNameForSQL(PKFieldName) + ',');
lSB.Append(GetFieldNameForSQL(TableMap.fPrimaryKeyFieldName) + ',');
end;
{partition}
@ -80,7 +78,7 @@ begin
end;
{end-partition}
for lKeyValue in Map do
for lKeyValue in TableMap.fMap do
begin
if lKeyValue.Value.Writeable then
begin
@ -92,7 +90,7 @@ begin
lSB.Append(') values (');
if lPKInInsert then
begin
lSB.Append(':' + GetParamNameForSQL(PKFieldName) + ',');
lSB.Append(':' + GetParamNameForSQL(TableMap.fPrimaryKeyFieldName) + ',');
end;
{partition}
@ -102,9 +100,12 @@ begin
end;
{end-partition}
for lKeyValue in Map do
for lKeyValue in TableMap.fMap do
begin
if lKeyValue.Value.Writeable then
if lKeyValue.Value.IsVersion then
begin
lSB.Append(OBJECT_VERSION_STARTING_VALUE + ',');
end else if lKeyValue.Value.Writeable then
begin
lSB.Append(':' + GetParamNameForSQL(lKeyValue.Value.FieldName) + ',');
end;

View File

@ -41,10 +41,8 @@ type
function GetCompilerClass: TRQLCompilerClass; override;
public
function CreateInsertSQL(
const TableName: string;
const Map: TFieldsMap;
const PKFieldName: string;
const PKOptions: TMVCActiveRecordFieldOptions): string; override;
const TableMap: TMVCTableMap;
const ARInstance: TMVCActiveRecord): string; override;
end;
implementation
@ -53,22 +51,23 @@ uses
System.SysUtils,
MVCFramework.RQL.AST2MSSQL;
function TMVCSQLGeneratorMSSQL.CreateInsertSQL(const TableName: string; const Map: TFieldsMap;
const PKFieldName: string; const PKOptions: TMVCActiveRecordFieldOptions): string;
function TMVCSQLGeneratorMSSQL.CreateInsertSQL(
const TableMap: TMVCTableMap;
const ARInstance: TMVCActiveRecord): string;
var
lKeyValue: TPair<TRttiField, TFieldInfo>;
lSB: TStringBuilder;
lPKInInsert: Boolean;
lFieldName: String;
begin
lPKInInsert := (not PKFieldName.IsEmpty) and (not(TMVCActiveRecordFieldOption.foAutoGenerated in PKOptions));
lPKInInsert := lPKInInsert and (not(TMVCActiveRecordFieldOption.foReadOnly in PKOptions));
lPKInInsert := (not TableMap.fPrimaryKeyFieldName.IsEmpty) and (not(TMVCActiveRecordFieldOption.foAutoGenerated in TableMap.fPrimaryKeyOptions));
lPKInInsert := lPKInInsert and (not(TMVCActiveRecordFieldOption.foReadOnly in TableMap.fPrimaryKeyOptions));
lSB := TStringBuilder.Create;
try
lSB.Append('INSERT INTO ' + TableName + '(');
lSB.Append('INSERT INTO ' + TableMap.fTableName + '(');
if lPKInInsert then
begin
lSB.Append(PKFieldName + ',');
lSB.Append(TableMap.fPrimaryKeyFieldName + ',');
end;
{partition}
@ -78,7 +77,7 @@ begin
end;
{end-partition}
for lKeyValue in Map do
for lKeyValue in TableMap.fMap do
begin
if lKeyValue.Value.Writeable then
begin
@ -89,7 +88,7 @@ begin
lSB.Append(') values (');
if lPKInInsert then
begin
lSB.Append(':' + PKFieldName + ',');
lSB.Append(':' + TableMap.fPrimaryKeyFieldName + ',');
end;
{partition}
@ -99,9 +98,12 @@ begin
end;
{end-partition}
for lKeyValue in Map do
for lKeyValue in TableMap.fMap do
begin
if lKeyValue.Value.Writeable then
if lKeyValue.Value.IsVersion then
begin
lSB.Append(OBJECT_VERSION_STARTING_VALUE + ',');
end else if lKeyValue.Value.Writeable then
begin
lSB.Append(':' + lKeyValue.Value.FieldName + ',');
end;
@ -109,9 +111,9 @@ begin
lSB.Remove(lSB.Length - 1, 1);
lSB.Append(')');
if TMVCActiveRecordFieldOption.foAutoGenerated in PKOptions then
if TMVCActiveRecordFieldOption.foAutoGenerated in TableMap.fPrimaryKeyOptions then
begin
lSB.Append(';SELECT SCOPE_IDENTITY() as ' + PKFieldName);
lSB.Append(';SELECT SCOPE_IDENTITY() as ' + TableMap.fPrimaryKeyFieldName);
end;
Result := lSB.ToString;
finally

View File

@ -41,10 +41,9 @@ type
function GetCompilerClass: TRQLCompilerClass; override;
public
function CreateInsertSQL(
const TableName: string;
const Map: TFieldsMap;
const PKFieldName: string;
const PKOptions: TMVCActiveRecordFieldOptions): string; override;
const TableMap: TMVCTableMap;
const ARInstance: TMVCActiveRecord
): string; override;
end;
implementation
@ -53,22 +52,24 @@ uses
System.SysUtils,
MVCFramework.RQL.AST2MySQL;
function TMVCSQLGeneratorMySQL.CreateInsertSQL(const TableName: string; const Map: TFieldsMap;
const PKFieldName: string; const PKOptions: TMVCActiveRecordFieldOptions): string;
function TMVCSQLGeneratorMySQL.CreateInsertSQL(
const TableMap: TMVCTableMap;
const ARInstance: TMVCActiveRecord
): string;
var
lKeyValue: TPair<TRttiField, TFieldInfo>;
lSB: TStringBuilder;
lPKInInsert: Boolean;
lFieldName: String;
begin
lPKInInsert := (not PKFieldName.IsEmpty) and (not(TMVCActiveRecordFieldOption.foAutoGenerated in PKOptions));
lPKInInsert := lPKInInsert and (not(TMVCActiveRecordFieldOption.foReadOnly in PKOptions));
lPKInInsert := (not TableMap.fPrimaryKeyFieldName.IsEmpty) and (not(TMVCActiveRecordFieldOption.foAutoGenerated in TableMap.fPrimaryKeyOptions));
lPKInInsert := lPKInInsert and (not(TMVCActiveRecordFieldOption.foReadOnly in TableMap.fPrimaryKeyOptions));
lSB := TStringBuilder.Create;
try
lSB.Append('INSERT INTO ' + TableName + '(');
lSB.Append('INSERT INTO ' + TableMap.fTableName + '(');
if lPKInInsert then
begin
lSB.Append(PKFieldName + ',');
lSB.Append(TableMap.fPrimaryKeyFieldName + ',');
end;
{partition}
@ -78,7 +79,7 @@ begin
end;
{end-partition}
for lKeyValue in Map do
for lKeyValue in TableMap.fMap do
begin
if lKeyValue.Value.Writeable then
begin
@ -90,7 +91,7 @@ begin
lSB.Append(') values (');
if lPKInInsert then
begin
lSB.Append(':' + PKFieldName + ',');
lSB.Append(':' + TableMap.fPrimaryKeyFieldName + ',');
end;
{partition}
@ -100,19 +101,22 @@ begin
end;
{end-partition}
for lKeyValue in Map do
for lKeyValue in TableMap.fMap do
begin
if lKeyValue.Value.Writeable then
if lKeyValue.Value.IsVersion then
begin
lSB.Append(':' + lKeyValue.Value.FieldName + ',');
lSB.Append(OBJECT_VERSION_STARTING_VALUE + ',');
end else if lKeyValue.Value.Writeable then
begin
lSB.Append(':' + GetParamNameForSQL(lKeyValue.Value.FieldName) + ',');
end;
end;
lSB.Remove(lSB.Length - 1, 1);
lSB.Append(')');
if TMVCActiveRecordFieldOption.foAutoGenerated in PKOptions then
if TMVCActiveRecordFieldOption.foAutoGenerated in TableMap.fPrimaryKeyOptions then
begin
lSB.Append(';SELECT LAST_INSERT_ID() as ' + PKFieldName);
lSB.Append(';SELECT LAST_INSERT_ID() as ' + TableMap.fPrimaryKeyFieldName);
end;
Result := lSB.ToString;
finally

View File

@ -40,10 +40,7 @@ type
public
function HasNativeUUID: Boolean; override;
function CreateInsertSQL(
const TableName: string;
const Map: TFieldsMap;
const PKFieldName: string;
const PKOptions: TMVCActiveRecordFieldOptions): string; override;
const TableMap: TMVCTableMap; const ARInstance: TMVCActiveRecord): string; override;
function GetSequenceValueSQL(const PKFieldName: string;
const SequenceName: string;
const Step: Integer = 1): string; override;
@ -62,22 +59,21 @@ uses
System.SysUtils,
MVCFramework.RQL.AST2PostgreSQL;
function TMVCSQLGeneratorPostgreSQL.CreateInsertSQL(const TableName: string; const Map: TFieldsMap;
const PKFieldName: string; const PKOptions: TMVCActiveRecordFieldOptions): string;
function TMVCSQLGeneratorPostgreSQL.CreateInsertSQL(const TableMap: TMVCTableMap; const ARInstance: TMVCActiveRecord): string;
var
lKeyValue: TPair<TRttiField, TFieldInfo>;
lSB: TStringBuilder;
lPKInInsert: Boolean;
lFieldName: String;
begin
lPKInInsert := (not PKFieldName.IsEmpty) and (not(TMVCActiveRecordFieldOption.foAutoGenerated in PKOptions));
lPKInInsert := lPKInInsert and (not(TMVCActiveRecordFieldOption.foReadOnly in PKOptions));
lPKInInsert := (not TableMap.fPrimaryKeyFieldName.IsEmpty) and (not(TMVCActiveRecordFieldOption.foAutoGenerated in TableMap.fPrimaryKeyOptions));
lPKInInsert := lPKInInsert and (not(TMVCActiveRecordFieldOption.foReadOnly in TableMap.fPrimaryKeyOptions));
lSB := TStringBuilder.Create;
try
lSB.Append('INSERT INTO ' + GetTableNameForSQL(TableName) + ' (');
lSB.Append('INSERT INTO ' + GetTableNameForSQL(TableMap.fTableName) + ' (');
if lPKInInsert then
begin
lSB.Append(GetFieldNameForSQL(PKFieldName) + ',');
lSB.Append(GetFieldNameForSQL(TableMap.fPrimaryKeyFieldName) + ',');
end;
{partition}
@ -87,7 +83,7 @@ begin
end;
{end-partition}
for lKeyValue in Map do
for lKeyValue in TableMap.fMap do
begin
// if not(foTransient in lKeyValue.Value.FieldOptions) then
if lKeyValue.Value.Writeable then
@ -99,7 +95,7 @@ begin
lSB.Append(') values (');
if lPKInInsert then
begin
lSB.Append(':' + GetParamNameForSQL(PKFieldName) + ',');
lSB.Append(':' + GetParamNameForSQL(TableMap.fPrimaryKeyFieldName) + ',');
end;
{partition}
@ -109,9 +105,12 @@ begin
end;
{end-partition}
for lKeyValue in Map do
for lKeyValue in TableMap.fMap do
begin
if lKeyValue.Value.Writeable then
if lKeyValue.Value.IsVersion then
begin
lSB.Append(OBJECT_VERSION_STARTING_VALUE + ',');
end else if lKeyValue.Value.Writeable then
begin
lSB.Append(':' + GetParamNameForSQL(lKeyValue.Value.FieldName) + ',');
end;
@ -119,9 +118,9 @@ begin
lSB.Remove(lSB.Length - 1, 1);
lSB.Append(')');
if TMVCActiveRecordFieldOption.foAutoGenerated in PKOptions then
if TMVCActiveRecordFieldOption.foAutoGenerated in TableMap.fPrimaryKeyOptions then
begin
lSB.Append(' RETURNING ' + GetFieldNameForSQL(PKFieldName));
lSB.Append(' RETURNING ' + GetFieldNameForSQL(TableMap.fPrimaryKeyFieldName));
end;
Result := lSB.ToString;
finally

View File

@ -38,8 +38,9 @@ type
protected
function GetCompilerClass: TRQLCompilerClass; override;
public
function CreateInsertSQL(const TableName: string; const Map: TFieldsMap;
const PKFieldName: string; const PKOptions: TMVCActiveRecordFieldOptions): string; override;
function CreateInsertSQL(
const TableMap: TMVCTableMap;
const ARInstance: TMVCActiveRecord): string; override;
function HasSequences: Boolean; override;
end;
@ -49,23 +50,24 @@ uses
System.SysUtils,
MVCFramework.RQL.AST2SQLite;
function TMVCSQLGeneratorSQLite.CreateInsertSQL(const TableName: string; const Map: TFieldsMap;
const PKFieldName: string; const PKOptions: TMVCActiveRecordFieldOptions): string;
function TMVCSQLGeneratorSQLite.CreateInsertSQL(
const TableMap: TMVCTableMap;
const ARInstance: TMVCActiveRecord): string;
var
lKeyValue: TPair<TRttiField, TFieldInfo>;
lSB: TStringBuilder;
lPKInInsert: Boolean;
lFieldName: String;
begin
lPKInInsert := (not PKFieldName.IsEmpty) and
(not(TMVCActiveRecordFieldOption.foAutoGenerated in PKOptions));
lPKInInsert := lPKInInsert and (not(TMVCActiveRecordFieldOption.foReadOnly in PKOptions));
lPKInInsert := (not TableMap.fPrimaryKeyFieldName.IsEmpty) and
(not(TMVCActiveRecordFieldOption.foAutoGenerated in TableMap.fPrimaryKeyOptions));
lPKInInsert := lPKInInsert and (not(TMVCActiveRecordFieldOption.foReadOnly in TableMap.fPrimaryKeyOptions));
lSB := TStringBuilder.Create;
try
lSB.Append('INSERT INTO ' + GetTableNameForSQL(TableName) + ' (');
lSB.Append('INSERT INTO ' + GetTableNameForSQL(TableMap.fTableName) + ' (');
if lPKInInsert then
begin
lSB.Append(GetFieldNameForSQL(PKFieldName) + ',');
lSB.Append(GetFieldNameForSQL(TableMap.fPrimaryKeyFieldName) + ',');
end;
{partition}
@ -75,7 +77,7 @@ begin
end;
{end-partition}
for lKeyValue in Map do
for lKeyValue in TableMap.fMap do
begin
if lKeyValue.Value.Writeable then
begin
@ -86,7 +88,7 @@ begin
lSB.Append(') values (');
if lPKInInsert then
begin
lSB.Append(':' + GetParamNameForSQL(PKFieldName) + ',');
lSB.Append(':' + GetParamNameForSQL(TableMap.fPrimaryKeyFieldName) + ',');
end;
{partition}
@ -96,9 +98,12 @@ begin
end;
{end-partition}
for lKeyValue in Map do
for lKeyValue in TableMap.fMap do
begin
if lKeyValue.Value.Writeable then
if lKeyValue.Value.IsVersion then
begin
lSB.Append(OBJECT_VERSION_STARTING_VALUE + ',');
end else if lKeyValue.Value.Writeable then
begin
lSB.Append(':' + GetParamNameForSQL(lKeyValue.Value.FieldName) + ',');
end;
@ -106,9 +111,9 @@ begin
lSB.Remove(lSB.Length - 1, 1);
lSB.Append(')');
if TMVCActiveRecordFieldOption.foAutoGenerated in PKOptions then
if TMVCActiveRecordFieldOption.foAutoGenerated in TableMap.fPrimaryKeyOptions then
begin
lSB.Append(' ; SELECT last_insert_rowid() as ' + GetFieldNameForSQL(PKFieldName) + ';');
lSB.Append(' ; SELECT last_insert_rowid() as ' + GetFieldNameForSQL(TableMap.fPrimaryKeyFieldName) + ';');
end;
Result := lSB.ToString;
finally

View File

@ -64,8 +64,6 @@ type
[Test]
procedure TestCRUDWithGUID;
[Test]
procedure TestCRUDWithTableChange;
[Test]
procedure TestCRUDStringPK;
[Test]
procedure TestSelectWithExceptions;
@ -477,49 +475,6 @@ begin
Assert.IsNull(lCustomer);
end;
procedure TTestActiveRecordBase.TestCRUDWithTableChange;
var
lCustomer: TCustomer;
lID: Integer;
begin
Assert.AreEqual(Int64(0), TMVCActiveRecord.Count<TCustomer>());
AfterDataLoad;
lCustomer := TCustomer.Create;
try
lCustomer.CompanyName := 'bit Time Professionals';
lCustomer.City := 'Rome, IT';
lCustomer.Note := 'note1';
lCustomer.CreationTime := Time;
lCustomer.CreationDate := Date;
lCustomer.ID := -1; { don't be fooled by the default! }
lCustomer.Insert;
lID := lCustomer.ID;
Assert.AreEqual(1, lID);
finally
lCustomer.Free;
end;
// the same changing tablename
lCustomer := TCustomer.Create;
try
Assert.AreEqual('customers', lCustomer.TableName);
lCustomer.TableName := 'customers2';
lCustomer.CompanyName := 'bit Time Professionals';
lCustomer.City := 'Rome, IT';
lCustomer.Note := 'note1';
lCustomer.CreationTime := Time;
lCustomer.CreationDate := Date;
lCustomer.ID := -1; { don't be fooled by the default! }
lCustomer.Insert;
lID := lCustomer.ID;
Assert.AreEqual(1, lID);
Assert.IsTrue(lCustomer.LoadByPK(lID));
finally
lCustomer.Free;
end;
end;
procedure TTestActiveRecordBase.TestDefaultFilteringCount;
begin
TMVCActiveRecord.DeleteAll(TCustomer);