+ completed tests for default filtering and partitioning (just for PostgreSQL) (WIP for other RDBMSes)

This commit is contained in:
Daniele Teti 2021-11-19 00:34:37 +01:00
parent c231e6540e
commit 3360dea516
3 changed files with 377 additions and 8 deletions

View File

@ -426,12 +426,16 @@ type
const MaxRecordCount: Integer)
: TObjectList<T>; overload;
class function SelectOneByRQL<T: constructor, TMVCActiveRecord>(const RQL: string;
const RaiseExceptionIfNotFound: Boolean): T; overload;
const RaiseExceptionIfNotFound: Boolean = True): T; overload;
class function All<T: TMVCActiveRecord, constructor>: TObjectList<T>; overload;
class function Count<T: TMVCActiveRecord>(const RQL: string = ''): int64; overload;
class function Where<T: TMVCActiveRecord, constructor>(const SQLWhere: string;
const Params: array of Variant)
: TObjectList<T>; overload;
/// <summary>
/// Executes a SQL select using the SQLWhere parameter as where clause. This method is partitioning safe.
/// Returns TObjectList<EntityType>.
/// </summary>
class function Where<T: TMVCActiveRecord, constructor>(const SQLWhere: string;
const Params: array of Variant;
const ParamTypes: array of TFieldType): TObjectList<T>; overload;
@ -2372,17 +2376,25 @@ class function TMVCActiveRecordHelper.Where<T>(const SQLWhere: string;
const ParamTypes: array of TFieldType): TObjectList<T>;
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 + SQLWhere, Params, ParamTypes);
Result := Select<T>(lAR.GenerateSelectSQL +
lFilter + SQLWhere, Params, ParamTypes)
end
else
begin
Result := Select<T>(lAR.GenerateSelectSQL + ' WHERE ' + SQLWhere, Params, ParamTypes);
if lFilter.IsEmpty then
Result := Select<T>(lAR.GenerateSelectSQL + ' WHERE ' + SQLWhere, Params, ParamTypes)
else
begin
Result := Select<T>(lAR.GenerateSelectSQL + lFilter + ' AND ' + SQLWhere, Params, ParamTypes);
end;
end;
finally
lAR.Free;
@ -3621,7 +3633,18 @@ begin
begin
for I := 0 to lFieldCount - 1 do
begin
fRQLFilter := fRQLFilter + 'eq(' + FieldNames[i] + ',' + FieldValues[i] + '),';
case FieldTypes[I] of
ftString:
begin
fRQLFilter := fRQLFilter + 'eq(' + FieldNames[i] + ',' + FieldValues[i].QuotedString('"') + '),';
end;
ftInteger:
begin
fRQLFilter := fRQLFilter + 'eq(' + FieldNames[i] + ',' + FieldValues[i] + '),';
end;
else
raise ERQLException.CreateFmt('DataType for field [%s] not supported in partition clause', [fFieldNames[I]]);
end;
end;
fRQLFilter := fRQLFilter.Remove(fRQLFilter.Length - 1,1);
if lFieldCount > 1 then

View File

@ -42,7 +42,8 @@ type
procedure LoadData(const JustAFew: Boolean = False); virtual;
procedure AfterDataLoad; virtual; abstract;
procedure InternalSetupFixture; virtual;
function CreateACustomer(Name: String; Rating: Integer): Integer;
function CreateACustomer(CompanyName: String; Rating: Integer): Integer; overload;
function CreateACustomer(CompanyName: String; City: String; Rating: Integer): Integer; overload;
public
[SetupFixture]
procedure SetupFixturePG;
@ -88,6 +89,7 @@ type
procedure TestMergeWhenChangedRecords;
[Test]
procedure TestMergeWhenMixedRecords;
{default filtering}
[Test]
procedure TestDefaultFilteringSelectByRQL;
[Test]
@ -102,6 +104,25 @@ type
procedure TestDefaultFilteringDelete;
[Test]
procedure TestDefaultFilteringGetByPK;
{partitioning}
[Test]
procedure TestPartitioningCRUD;
[Test]
procedure TestPartitioningSelectByWhere;
[Test]
procedure TestPartitioningSelectByRQL;
[Test]
procedure TestPartitioningSelectOneByRQL;
[Test]
procedure TestPartitioningCount;
[Test]
procedure TestPartitioningCountByRQL;
[Test]
procedure TestPartitioningDeleteByRQL;
[Test]
procedure TestPartitioningDelete;
[Test]
procedure TestPartitioningGetByPK;
end;
[TestFixture]
@ -1152,6 +1173,269 @@ begin
end;
end;
procedure TTestActiveRecordBase.TestPartitioningCount;
begin
TMVCActiveRecord.DeleteAll(TCustomer);
CreateACustomer('Daniele', 'Rome', 1);
CreateACustomer('Jack', 'Rome', 2);
CreateACustomer('John', 'New York', 3);
CreateACustomer('Scott', 'Milan', 4);
CreateACustomer('Bruce', 'Tokyo', 5);
Assert.AreEqual(Int64(2), TMVCActiveRecord.Count<TRomeBasedCustomer>);
Assert.AreEqual(Int64(1), TMVCActiveRecord.Count<TNewYorkBasedCustomer>);
end;
procedure TTestActiveRecordBase.TestPartitioningCountByRQL;
begin
TMVCActiveRecord.DeleteAll(TCustomer);
CreateACustomer('Daniele', 'Rome', 1);
CreateACustomer('Jack', 'Rome', 2);
CreateACustomer('John', 'New York', 3);
CreateACustomer('Scott', 'Milan', 4);
CreateACustomer('Bruce', 'Tokyo', 5);
Assert.AreEqual(Int64(1), TMVCActiveRecord.Count<TRomeBasedCustomer>('ge(rating,2)'));
Assert.AreEqual(Int64(0), TMVCActiveRecord.Count<TNewYorkBasedCustomer>('gt(rating,4)'));
Assert.AreEqual(Int64(0), TMVCActiveRecord.Count<TNewYorkBasedCustomer>('contains(CompanyName,"a")'));
Assert.AreEqual(Int64(1), TMVCActiveRecord.Count<TNewYorkBasedCustomer>('contains(CompanyName,"h")'));
end;
procedure TTestActiveRecordBase.TestPartitioningCRUD;
var
lRMCustomer: TRomeBasedCustomer;
lNYCustomer: TNewYorkBasedCustomer;
lIDRome, lIDNewYork: Integer;
begin
Assert.AreEqual(Int64(0), TMVCActiveRecord.Count<TRomeBasedCustomer>());
lRMCustomer := TRomeBasedCustomer.Create;
try
lRMCustomer.CompanyName := 'bit Time Professionals';
lRMCustomer.Note := 'note1';
lRMCustomer.Insert;
lIDRome := lRMCustomer.ID;
finally
lRMCustomer.Free;
end;
lNYCustomer := TNewYorkBasedCustomer.Create;
try
lNYCustomer.CompanyName := 'bit Time Professionals NY';
lRMCustomer.Note := 'note2';
lNYCustomer.Insert;
lIDNewYork := lNYCustomer.ID;
finally
lNYCustomer.Free;
end;
lRMCustomer := TMVCActiveRecord.GetByPK<TRomeBasedCustomer>(lIDRome);
try
Assert.IsFalse(lRMCustomer.Code.HasValue);
lRMCustomer.Code := '1234';
lRMCustomer.Note := lRMCustomer.Note + 'noteupdated';
lRMCustomer.Update;
finally
lRMCustomer.Free;
end;
lRMCustomer := TMVCActiveRecord.GetByPK<TRomeBasedCustomer>(lIDRome);
try
Assert.AreEqual('1234', lRMCustomer.Code.Value);
Assert.AreEqual('note1noteupdated', lRMCustomer.Note);
Assert.AreEqual('bit Time Professionals', lRMCustomer.CompanyName.Value);
Assert.AreEqual(1, lRMCustomer.ID.Value);
finally
lRMCustomer.Free;
end;
lRMCustomer := TMVCActiveRecord.GetByPK<TRomeBasedCustomer>(lIDRome);
try
lRMCustomer.Delete;
finally
lRMCustomer.Free;
end;
lRMCustomer := TMVCActiveRecord.GetByPK<TRomeBasedCustomer>(lIDRome, false);
Assert.IsNull(lRMCustomer);
lRMCustomer := TMVCActiveRecord.GetOneByWhere<TRomeBasedCustomer>('id = ?', [lIDRome], [ftInteger], false);
Assert.IsNull(lRMCustomer);
end;
procedure TTestActiveRecordBase.TestPartitioningDelete;
begin
TMVCActiveRecord.DeleteAll(TCustomer);
var lID1 := CreateACustomer('Daniele', 'Rome', 1);
var lID2 := CreateACustomer('Jack', 'Rome', 2);
var lID3 := CreateACustomer('Bruce', 'Tokyo', 3);
var lID4 := CreateACustomer('John', 'New York', 4);
var lID5 := CreateACustomer('Scott', 'New York', 5);
var lGoodNewYorkCustomer := TMVCActiveRecord.GetByPK<TNewYorkBasedGoodCustomer>(lID5);
try
lGoodNewYorkCustomer.Delete;
Assert.Pass;
finally
lGoodNewYorkCustomer.Free;
end;
Assert.AreEqual(Int64(1), TMVCActiveRecord.Count(TNewYorkBasedCustomer));
TMVCActiveRecord.DeleteAll(TNewYorkBasedGoodCustomer);
Assert.AreEqual(Int64(0), TMVCActiveRecord.Count(TNewYorkBasedGoodCustomer));
Assert.AreEqual(Int64(1), TMVCActiveRecord.Count(TNewYorkBasedCustomer));
end;
procedure TTestActiveRecordBase.TestPartitioningDeleteByRQL;
begin
TMVCActiveRecord.DeleteAll(TCustomer);
var lID1 := CreateACustomer('Daniele', 'Rome', 1);
var lID2 := CreateACustomer('Jack', 'Rome', 2);
var lID3 := CreateACustomer('Bruce', 'Tokyo', 3);
var lID4 := CreateACustomer('John', 'New York', 4);
var lID5 := CreateACustomer('Scott', 'New York', 5);
Assert.AreEqual(Int64(2), TMVCActiveRecord.Count(TNewYorkBasedCustomer));
TMVCActiveRecord.DeleteRQL(TNewYorkBasedCustomer, 'eq(CompanyName,"John")');
Assert.AreEqual(Int64(1), TMVCActiveRecord.Count(TNewYorkBasedCustomer));
TMVCActiveRecord.DeleteRQL(TNewYorkBasedCustomer, 'eq(CompanyName,"John")');
Assert.AreEqual(Int64(1), TMVCActiveRecord.Count(TNewYorkBasedCustomer));
Assert.AreEqual(Int64(1), TMVCActiveRecord.Count(TNewYorkBasedGoodCustomer));
end;
procedure TTestActiveRecordBase.TestPartitioningGetByPK;
begin
TMVCActiveRecord.DeleteAll(TCustomer);
var lID1 := CreateACustomer('Daniele', 'Rome', 1);
var lID2 := CreateACustomer('Jack', 'Rome', 2);
var lID3 := CreateACustomer('Bruce', 'Tokyo', 3);
var lID4 := CreateACustomer('John', 'New York', 4);
var lID5 := CreateACustomer('Scott', 'New York', 5);
var lRomeCustomer := TMVCActiveRecord.GetByPK<TRomeBasedCustomer>(lID1);
try
Assert.IsNotNull(lRomeCustomer);
finally
lRomeCustomer.Free;
end;
var lNYCustomer := TMVCActiveRecord.GetByPK<TNewYorkBasedCustomer>(lID1, False);
try
Assert.IsNull(lNYCustomer);
finally
lNYCustomer.Free;
end;
var lNYGoodCustomer := TMVCActiveRecord.GetByPK<TNewYorkBasedGoodCustomer>(lID5, False);
try
Assert.IsNotNull(lNYGoodCustomer);
finally
lNYGoodCustomer.Free;
end;
lNYGoodCustomer := TMVCActiveRecord.GetByPK<TNewYorkBasedGoodCustomer>(lID1, False);
try
Assert.IsNull(lNYGoodCustomer);
finally
lNYGoodCustomer.Free;
end;
end;
procedure TTestActiveRecordBase.TestPartitioningSelectByRQL;
begin
TMVCActiveRecord.DeleteAll(TCustomer);
CreateACustomer('Rome Company 1', 'Rome', 5);
CreateACustomer('Rome Company 2', 'Rome', 2);
CreateACustomer('New York 1', 'New York', 1);
CreateACustomer('Toyko 1', 'Tokyo', 4);
var lRomeCustomers := TMVCActiveRecord.SelectRQL<TRomeBasedCustomer>('',10);
try
Assert.AreEqual(2, lRomeCustomers.Count);
finally
lRomeCustomers.Free;
end;
lRomeCustomers := TMVCActiveRecord.SelectRQL<TRomeBasedCustomer>('sort(+CompanyName)',10);
try
Assert.AreEqual('Rome Company 1', lRomeCustomers[0].CompanyName.Value);
Assert.AreEqual('Rome Company 2', lRomeCustomers[1].CompanyName.Value);
finally
lRomeCustomers.Free;
end;
lRomeCustomers := TMVCActiveRecord.SelectRQL<TRomeBasedCustomer>('eq(Rating,5);sort(+CompanyName)',10);
try
Assert.AreEqual(1, lRomeCustomers.Count);
Assert.AreEqual('Rome Company 1', lRomeCustomers[0].CompanyName.Value);
finally
lRomeCustomers.Free;
end;
lRomeCustomers := TMVCActiveRecord.SelectRQL<TRomeBasedCustomer>('lt(Rating,2);sort(+CompanyName)',10);
try
Assert.AreEqual(0, lRomeCustomers.Count);
finally
lRomeCustomers.Free;
end;
end;
procedure TTestActiveRecordBase.TestPartitioningSelectByWhere;
var
lRMCustomer: TRomeBasedCustomer;
lNYCustomer: TNewYorkBasedCustomer;
lIDRome, lIDNewYork: Integer;
begin
Assert.AreEqual(Int64(0), TMVCActiveRecord.Count<TRomeBasedCustomer>());
CreateACustomer('Daniele','Rome',1);
CreateACustomer('Jack','New York',1);
var lRomeBasedCustomers := TMVCActiveRecord.Where<TRomeBasedCustomer>('city = ?', ['New York'], [ftString]);
try
Assert.AreEqual(0, lRomeBasedCustomers.Count);
finally
lRomeBasedCustomers.Free;
end;
lRomeBasedCustomers := TMVCActiveRecord.Where<TRomeBasedCustomer>('description = ?', ['Daniele'], [ftString]);
try
Assert.AreEqual(1, lRomeBasedCustomers.Count);
finally
lRomeBasedCustomers.Free;
end;
end;
procedure TTestActiveRecordBase.TestPartitioningSelectOneByRQL;
begin
TMVCActiveRecord.DeleteAll(TCustomer);
CreateACustomer('Rome Company 1', 'Rome', 5);
CreateACustomer('Rome Company 2', 'Rome', 2);
CreateACustomer('New York 1', 'New York', 5);
CreateACustomer('Toyko 1', 'Tokyo', 4);
var lRomeCustomer := TMVCActiveRecord.SelectOneByRQL<TRomeBasedCustomer>('contains(CompanyName,"1")');
try
Assert.IsNotNull(lRomeCustomer);
finally
lRomeCustomer.Free;
end;
lRomeCustomer := TMVCActiveRecord.SelectOneByRQL<TRomeBasedCustomer>('eq(Rating,5);sort(+CompanyName)');
try
Assert.AreEqual('Rome Company 1', lRomeCustomer.CompanyName.Value);
finally
lRomeCustomer.Free;
end;
TMVCActiveRecord.DeleteAll(TRomeBasedCustomer);
lRomeCustomer := TMVCActiveRecord.SelectOneByRQL<TRomeBasedCustomer>('eq(Rating,5);sort(+CompanyName)', False);
try
Assert.IsNull(lRomeCustomer);
finally
lRomeCustomer.Free;
end;
end;
procedure TTestActiveRecordBase.TestRQL;
var
lCustomers: TObjectList<TCustomer>;
@ -1406,15 +1690,21 @@ begin
end;
end;
function TTestActiveRecordBase.CreateACustomer(Name: String;
function TTestActiveRecordBase.CreateACustomer(CompanyName: String;
Rating: Integer): Integer;
begin
Result := CreateACustomer(CompanyName, CompanyName + 'City', Rating);
end;
function TTestActiveRecordBase.CreateACustomer(CompanyName, City: String;
Rating: Integer): Integer;
var
lCustomer: TCustomer;
begin
lCustomer := TCustomer.Create;
try
lCustomer.CompanyName := Name;
lCustomer.City := Name + ' city';
lCustomer.CompanyName := CompanyName;
lCustomer.City := City;
lCustomer.Rating := Rating;
lCustomer.Insert;
Result := lCustomer.ID;

View File

@ -137,6 +137,62 @@ type
TBadCustomer = class(TCustomer)
end;
[MVCTable('customers')]
TPartitionedCustomer = class(TMVCActiveRecord)
private
[MVCTableField('id', [foPrimaryKey, foAutoGenerated])]
fID: NullableInt32;
[MVCTableField('code')]
fCode: NullableString;
[MVCTableField('description')]
fCompanyName: NullableString;
[MVCTableField('note')]
fNote: string;
[MVCTableField('creation_time')]
fCreationTime: NullableTTime;
[MVCTableField('creation_date')]
fCreationDate: NullableTDate;
public
property ID: NullableInt32 read fID write fID;
property Code: NullableString read fCode write fCode;
property CompanyName: NullableString read fCompanyName write fCompanyName;
property CreationTime: NullableTTime read fCreationTime write fCreationTime;
property CreationDate: NullableTDate read fCreationDate write fCreationDate;
property Note: string read fNote write fNote;
end;
[MVCNameCase(ncLowerCase)]
[MVCTable('customers')]
[MVCPartition('rating=(integer)5')]
TCustomerR5 = class(TPartitionedCustomer)
end;
[MVCNameCase(ncLowerCase)]
[MVCTable('customers')]
[MVCPartition('rating=(integer)4')]
TCustomerR4 = class(TPartitionedCustomer)
end;
[MVCNameCase(ncLowerCase)]
[MVCTable('customers')]
[MVCPartition('city=(string)Rome')]
TRomeBasedCustomer = class(TPartitionedCustomer)
end;
[MVCNameCase(ncLowerCase)]
[MVCTable('customers')]
[MVCPartition('city=(string)New York')]
TNewYorkBasedCustomer = class(TPartitionedCustomer)
end;
[MVCNameCase(ncLowerCase)]
[MVCTable('customers')]
[MVCPartition('city=(string)New York;rating=(integer)5')]
TNewYorkBasedGoodCustomer = class(TPartitionedCustomer)
end;
[MVCTable('customers_with_code')]
TCustomerWithCode = class(TMVCActiveRecord)
private