diff --git a/sources/MVCFramework.ActiveRecord.pas b/sources/MVCFramework.ActiveRecord.pas index 1306d990..f21a6c10 100644 --- a/sources/MVCFramework.ActiveRecord.pas +++ b/sources/MVCFramework.ActiveRecord.pas @@ -426,12 +426,16 @@ type const MaxRecordCount: Integer) : TObjectList; overload; class function SelectOneByRQL(const RQL: string; - const RaiseExceptionIfNotFound: Boolean): T; overload; + const RaiseExceptionIfNotFound: Boolean = True): T; overload; class function All: TObjectList; overload; class function Count(const RQL: string = ''): int64; overload; class function Where(const SQLWhere: string; const Params: array of Variant) : TObjectList; overload; + /// + /// Executes a SQL select using the SQLWhere parameter as where clause. This method is partitioning safe. + /// Returns TObjectList. + /// class function Where(const SQLWhere: string; const Params: array of Variant; const ParamTypes: array of TFieldType): TObjectList; overload; @@ -2372,17 +2376,25 @@ class function TMVCActiveRecordHelper.Where(const SQLWhere: string; const ParamTypes: array of TFieldType): TObjectList; 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(lAR.GenerateSelectSQL + SQLWhere, Params, ParamTypes); + Result := Select(lAR.GenerateSelectSQL + + lFilter + SQLWhere, Params, ParamTypes) end else begin - Result := Select(lAR.GenerateSelectSQL + ' WHERE ' + SQLWhere, Params, ParamTypes); + if lFilter.IsEmpty then + Result := Select(lAR.GenerateSelectSQL + ' WHERE ' + SQLWhere, Params, ParamTypes) + else + begin + Result := Select(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 diff --git a/unittests/general/Several/ActiveRecordTestsU.pas b/unittests/general/Several/ActiveRecordTestsU.pas index efe3813b..9bcc9355 100644 --- a/unittests/general/Several/ActiveRecordTestsU.pas +++ b/unittests/general/Several/ActiveRecordTestsU.pas @@ -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); + Assert.AreEqual(Int64(1), TMVCActiveRecord.Count); +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('ge(rating,2)')); + Assert.AreEqual(Int64(0), TMVCActiveRecord.Count('gt(rating,4)')); + Assert.AreEqual(Int64(0), TMVCActiveRecord.Count('contains(CompanyName,"a")')); + Assert.AreEqual(Int64(1), TMVCActiveRecord.Count('contains(CompanyName,"h")')); +end; + +procedure TTestActiveRecordBase.TestPartitioningCRUD; +var + lRMCustomer: TRomeBasedCustomer; + lNYCustomer: TNewYorkBasedCustomer; + lIDRome, lIDNewYork: Integer; +begin + Assert.AreEqual(Int64(0), TMVCActiveRecord.Count()); + 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(lIDRome); + try + Assert.IsFalse(lRMCustomer.Code.HasValue); + lRMCustomer.Code := '1234'; + lRMCustomer.Note := lRMCustomer.Note + 'noteupdated'; + lRMCustomer.Update; + finally + lRMCustomer.Free; + end; + + lRMCustomer := TMVCActiveRecord.GetByPK(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(lIDRome); + try + lRMCustomer.Delete; + finally + lRMCustomer.Free; + end; + + lRMCustomer := TMVCActiveRecord.GetByPK(lIDRome, false); + Assert.IsNull(lRMCustomer); + + lRMCustomer := TMVCActiveRecord.GetOneByWhere('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(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(lID1); + try + Assert.IsNotNull(lRomeCustomer); + finally + lRomeCustomer.Free; + end; + + var lNYCustomer := TMVCActiveRecord.GetByPK(lID1, False); + try + Assert.IsNull(lNYCustomer); + finally + lNYCustomer.Free; + end; + + var lNYGoodCustomer := TMVCActiveRecord.GetByPK(lID5, False); + try + Assert.IsNotNull(lNYGoodCustomer); + finally + lNYGoodCustomer.Free; + end; + + lNYGoodCustomer := TMVCActiveRecord.GetByPK(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('',10); + try + Assert.AreEqual(2, lRomeCustomers.Count); + finally + lRomeCustomers.Free; + end; + + lRomeCustomers := TMVCActiveRecord.SelectRQL('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('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('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()); + CreateACustomer('Daniele','Rome',1); + CreateACustomer('Jack','New York',1); + var lRomeBasedCustomers := TMVCActiveRecord.Where('city = ?', ['New York'], [ftString]); + try + Assert.AreEqual(0, lRomeBasedCustomers.Count); + finally + lRomeBasedCustomers.Free; + end; + + lRomeBasedCustomers := TMVCActiveRecord.Where('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('contains(CompanyName,"1")'); + try + Assert.IsNotNull(lRomeCustomer); + finally + lRomeCustomer.Free; + end; + + lRomeCustomer := TMVCActiveRecord.SelectOneByRQL('eq(Rating,5);sort(+CompanyName)'); + try + Assert.AreEqual('Rome Company 1', lRomeCustomer.CompanyName.Value); + finally + lRomeCustomer.Free; + end; + + TMVCActiveRecord.DeleteAll(TRomeBasedCustomer); + + lRomeCustomer := TMVCActiveRecord.SelectOneByRQL('eq(Rating,5);sort(+CompanyName)', False); + try + Assert.IsNull(lRomeCustomer); + finally + lRomeCustomer.Free; + end; +end; + procedure TTestActiveRecordBase.TestRQL; var lCustomers: TObjectList; @@ -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; diff --git a/unittests/general/Several/BOs.pas b/unittests/general/Several/BOs.pas index 969017c9..ccc3f0c3 100644 --- a/unittests/general/Several/BOs.pas +++ b/unittests/general/Several/BOs.pas @@ -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