+ Fixed a bug in RQL' MaxRecordCount

+ Implemented Table Partitioning (WIP)
This commit is contained in:
Daniele Teti 2021-11-18 00:49:12 +01:00
parent 49518a311b
commit 5550819fca
16 changed files with 696 additions and 149 deletions

View File

@ -569,23 +569,40 @@ The current beta release is named 3.2.2-nitrogen. If you want to stay on the-edg
end;
```
### Bug Fixes in 3.2.2-nitrogen
- Fix https://github.com/danieleteti/delphimvcframework/issues/484 (thanks to [João Antônio Duarte](https://github.com/joaoduarte19))
- Fix https://github.com/danieleteti/delphimvcframework/issues/472 (thanks to [João Antônio Duarte](https://github.com/joaoduarte19))
- Fix https://github.com/danieleteti/delphimvcframework/issues/470 (thanks to [João Antônio Duarte](https://github.com/joaoduarte19))
- Fix https://github.com/danieleteti/delphimvcframework/issues/453 (thanks to [João Antônio Duarte](https://github.com/joaoduarte19))
- Fix https://github.com/danieleteti/delphimvcframework/issues/455 (thanks to [João Antônio Duarte](https://github.com/joaoduarte19))
- Fix https://github.com/danieleteti/delphimvcframework/issues/461
- Fix https://github.com/danieleteti/delphimvcframework/issues/489 (thanks to [drcrck](https://github.com/drcrck) for his initial analisys)
- Fix https://github.com/danieleteti/delphimvcframework/issues/493 (thanks to [DelphiMan68](https://github.com/DelphiMan68) for his initial analisys)
- Fix https://github.com/danieleteti/delphimvcframework/issues/451
- Fix for nil objects in lists during serialization
- Uniformed behavior in `Update` and `Delete` method in `TMVCActiveRecord`. Now these methods raise an exception if the record doesn't exists anymore in the table (update or delete statements return `AffectedRows` = 0). The behavior can be altered using the new parameter in the call, which by default is `true`. WARNING! This change could raise some incompatibilities with the previous version, however this is the correct behavior. Consider the previous one a "incorrect behavior to fix".
- Fix a very subtle bug in `MaxRecordCount` parameter for RQL based methods in `TMVCActiveRecord`
- Uniformed behavior in `Update` and `Delete` method in `TMVCActiveRecord`. Now these methods raise an exception if the record doesn't exists anymore in the table (update or delete statements return `AffectedRows` = 0). The behavior can be altered using the new parameter in the call, which by default is `true`.
> WARNING! This change could raise some incompatibilities with the previous version, however this is the correct behavior. Consider the previous one a "incorrect behavior to fix".
- Fix https://github.com/danieleteti/delphimvcframework/issues/489
- Fix https://github.com/danieleteti/delphimvcframework/issues/518 (Thanks to [Microcom-Bjarne](https://github.com/Microcom-Bjarne))
- Fix https://github.com/danieleteti/delphimvcframework/issues/526 (Thanks to [David Moorhouse](https://github.com/fastbike))
- Fix *fileupload* sample
## Older Releases

View File

@ -133,11 +133,47 @@ type
property Note: string read fNote write fNote;
end;
[MVCNameCase(ncLowerCase)]
[MVCTable('customers')]
TPartitionedCustomer = class(TCustomEntity)
private
[MVCTableField('id', [foPrimaryKey, foAutoGenerated])]
fID: NullableInt64;
[MVCTableField('code')]
fCode: NullableString;
[MVCTableField('description')]
fCompanyName: NullableString;
[MVCTableField('city')]
fCity: string;
[MVCTableField('note')]
fNote: string;
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 Note: string read fNote write fNote;
end;
[MVCNameCase(ncLowerCase)]
[MVCTable('customers', 'ge(Rating,4)')]
TGoodCustomer = class(TCustomer)
end;
[MVCTable('customers')]
[MVCPartition('rating=(integer)1')]
TCustomerWithRate1 = class(TPartitionedCustomer)
end;
[MVCTable('customers')]
[MVCPartition('rating=(integer)2')]
TCustomerWithRate2 = class(TPartitionedCustomer)
end;
[MVCNameCase(ncLowerCase)]
[MVCTable('customers', 'le(Rating,3)')]
@ -570,4 +606,15 @@ begin
'').Replace('}', '').Substring(0, 20)));
end;
{ TPartitionedCustomer }
function TPartitionedCustomer.ToString: String;
begin
Result := '';
if PKIsNull then
Result := '<null>';
Result := Format('[ID: %6s][CODE: %6s][CompanyName: %18s][City: %16s][Note: %s]',[
Result, fCode.ValueOrDefault, fCompanyName.ValueOrDefault, fCity, fNote]);
end;
end.

View File

@ -2,8 +2,8 @@ object MainForm: TMainForm
Left = 0
Top = 0
Caption = 'TMVCActiveRecord - ShowCase'
ClientHeight = 758
ClientWidth = 1054
ClientHeight = 569
ClientWidth = 1104
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
@ -14,8 +14,8 @@ object MainForm: TMainForm
OnShow = FormShow
PixelsPerInch = 96
DesignSize = (
1054
758)
1104
569)
TextHeight = 13
object btnCRUD: TButton
Left = 8
@ -36,10 +36,10 @@ object MainForm: TMainForm
OnClick = btnSelectClick
end
object Memo1: TMemo
Left = 135
Left = 280
Top = 8
Width = 911
Height = 742
Width = 816
Height = 553
Anchors = [akLeft, akTop, akRight, akBottom]
Ctl3D = True
DoubleBuffered = True
@ -56,7 +56,8 @@ object MainForm: TMainForm
TabOrder = 2
WantReturns = False
WordWrap = False
ExplicitWidth = 492
ExplicitWidth = 766
ExplicitHeight = 754
end
object btnRelations: TButton
Left = 8
@ -86,10 +87,10 @@ object MainForm: TMainForm
OnClick = btnValidationClick
end
object btnMultiThreading: TButton
Left = 8
Top = 403
Left = 144
Top = 8
Width = 121
Height = 34
Height = 33
Caption = 'Multi Threading'
TabOrder = 6
OnClick = btnMultiThreadingClick
@ -113,10 +114,10 @@ object MainForm: TMainForm
OnClick = btnTransientFieldsClick
end
object btnNullTest: TButton
Left = 8
Top = 443
Left = 144
Top = 47
Width = 121
Height = 34
Height = 33
Caption = 'Nullables'
TabOrder = 9
OnClick = btnNullTestClick
@ -150,17 +151,17 @@ object MainForm: TMainForm
OnClick = btnWithSpacesClick
end
object btnCountWithRQL: TButton
Left = 8
Top = 483
Left = 144
Top = 86
Width = 121
Height = 34
Height = 33
Caption = 'Count with RQL'
TabOrder = 13
OnClick = btnCountWithRQLClick
end
object btnReadAndWriteOnly: TButton
Left = 8
Top = 523
Left = 144
Top = 125
Width = 121
Height = 33
Caption = 'R/O, R/W'
@ -168,8 +169,8 @@ object MainForm: TMainForm
OnClick = btnReadAndWriteOnlyClick
end
object btnClientGeneratedPK: TButton
Left = 8
Top = 562
Left = 144
Top = 164
Width = 121
Height = 33
Caption = 'Client Generated PKs'
@ -177,8 +178,8 @@ object MainForm: TMainForm
OnClick = btnClientGeneratedPKClick
end
object btnAttributes: TButton
Left = 8
Top = 599
Left = 144
Top = 203
Width = 121
Height = 33
Caption = 'Attributes'
@ -186,34 +187,43 @@ object MainForm: TMainForm
OnClick = btnAttributesClick
end
object btnJSON_XML_Types: TButton
Left = 8
Top = 638
Left = 144
Top = 242
Width = 121
Height = 33
Height = 35
Caption = 'JSON && XML'
TabOrder = 17
OnClick = btnJSON_XML_TypesClick
end
object btnMerge: TButton
Left = 8
Top = 677
Left = 144
Top = 283
Width = 121
Height = 33
Height = 34
Caption = 'Merge'
TabOrder = 18
OnClick = btnMergeClick
end
object btnTableFilter: TButton
Left = 8
Top = 716
Left = 144
Top = 323
Width = 121
Height = 33
Height = 34
Caption = 'Table Filter'
TabOrder = 19
OnClick = btnTableFilterClick
end
object btnPartitioning: TButton
Left = 144
Top = 363
Width = 121
Height = 33
Caption = 'Table Partitioning'
TabOrder = 20
OnClick = btnPartitioningClick
end
object FDConnection1: TFDConnection
Left = 192
Top = 56
Left = 56
Top = 408
end
end

View File

@ -23,8 +23,11 @@ uses
FireDAC.Stan.Async,
FireDAC.Phys,
FireDAC.VCLUI.Wait,
Data.DB, FireDAC.Comp.Client, MVCFramework.Nullables,
MVCFramework.ActiveRecord, System.Generics.Collections;
Data.DB,
FireDAC.Comp.Client,
MVCFramework.Nullables,
MVCFramework.ActiveRecord,
System.Generics.Collections;
type
TMainForm = class(TForm)
@ -49,6 +52,7 @@ type
btnJSON_XML_Types: TButton;
btnMerge: TButton;
btnTableFilter: TButton;
btnPartitioning: TButton;
procedure btnCRUDClick(Sender: TObject);
procedure btnInheritanceClick(Sender: TObject);
procedure btnMultiThreadingClick(Sender: TObject);
@ -71,6 +75,7 @@ type
procedure btnJSON_XML_TypesClick(Sender: TObject);
procedure btnMergeClick(Sender: TObject);
procedure btnTableFilterClick(Sender: TObject);
procedure btnPartitioningClick(Sender: TObject);
private
procedure Log(const Value: string);
procedure LoadCustomers;
@ -92,7 +97,9 @@ uses
MVCFramework.DataSet.Utils,
MVCFramework.RQL.Parser,
System.Math,
FDConnectionConfigU, EngineChoiceFormU, System.Rtti;
FDConnectionConfigU,
EngineChoiceFormU,
System.Rtti;
const
Cities: array [0 .. 4] of string = ('Rome', 'New York', 'London', 'Melbourne', 'Berlin');
@ -763,6 +770,44 @@ begin
end;
procedure TMainForm.btnPartitioningClick(Sender: TObject);
begin
TMVCActiveRecord.DeleteAll(TCustomerWithRate1);
Assert(TMVCActiveRecord.Count(TCustomerWithRate1) = 0);
TMVCActiveRecord.DeleteAll(TCustomerWithRate2);
Assert(TMVCActiveRecord.Count(TCustomerWithRate2) = 0);
var lCust1 := TCustomerWithRate1.Create;
try
lCust1.City := 'Rome';
lCust1.Code := '123';
lCust1.Store;
finally
lCust1.Free;
end;
var lCust2 := TCustomerWithRate2.Create;
try
lCust2.City := 'Rome';
lCust2.Code := '456';
lCust2.Store;
Assert(TMVCActiveRecord.GetByPK<TCustomerWithRate1>(lCust2.ID, False) = nil);
finally
lCust2.Free;
end;
var lList := TMVCActiveRecord.SelectRQL<TCustomerWithRate1>('eq(city,"Rome")',-1);
try
Assert(lList.Count = 1);
Assert(lList[0].Code = '123');
finally
lList.Free;
end;
Assert(TMVCActiveRecord.Count(TCustomerWithRate1) = 1);
Assert(TMVCActiveRecord.Count(TCustomerWithRate1, 'eq(code,"xxx")') = 0);
Assert(TMVCActiveRecord.Count(TCustomerWithRate2) = 1);
Assert(TMVCActiveRecord.Count(TCustomerWithRate2, 'eq(code,"xxx")') = 0);
end;
procedure TMainForm.btnReadAndWriteOnlyClick(Sender: TObject);
var
lArtWO, lArtWO2: TArticleWithWriteOnlyFields;

View File

@ -1,7 +1,7 @@
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<ProjectGuid>{28115991-DA41-4172-924A-7FCD5AC95F77}</ProjectGuid>
<ProjectVersion>18.8</ProjectVersion>
<ProjectVersion>19.3</ProjectVersion>
<FrameworkType>None</FrameworkType>
<MainSource>FilesUploadDemo.dpr</MainSource>
<Base>True</Base>
@ -23,13 +23,8 @@
<CfgParent>Base</CfgParent>
<Base>true</Base>
</PropertyGroup>
<PropertyGroup Condition="('$(Platform)'=='OSX32' and '$(Base)'=='true') or '$(Base_OSX32)'!=''">
<Base_OSX32>true</Base_OSX32>
<CfgParent>Base</CfgParent>
<Base>true</Base>
</PropertyGroup>
<PropertyGroup Condition="('$(Platform)'=='OSX64' and '$(Base)'=='true') or '$(Base_OSX64)'!=''">
<Base_OSX64>true</Base_OSX64>
<PropertyGroup Condition="('$(Platform)'=='OSXARM64' and '$(Base)'=='true') or '$(Base_OSXARM64)'!=''">
<Base_OSXARM64>true</Base_OSXARM64>
<CfgParent>Base</CfgParent>
<Base>true</Base>
</PropertyGroup>
@ -104,6 +99,7 @@
<Android_NotificationIcon48>$(BDS)\bin\Artwork\Android\FM_NotificationIcon_48x48.png</Android_NotificationIcon48>
<Android_NotificationIcon72>$(BDS)\bin\Artwork\Android\FM_NotificationIcon_72x72.png</Android_NotificationIcon72>
<Android_NotificationIcon96>$(BDS)\bin\Artwork\Android\FM_NotificationIcon_96x96.png</Android_NotificationIcon96>
<Android_LauncherIcon192>$(BDS)\bin\Artwork\Android\FM_LauncherIcon_192x192.png</Android_LauncherIcon192>
</PropertyGroup>
<PropertyGroup Condition="'$(Base_Android64)'!=''">
<VerInfo_Keys>package=com.embarcadero.$(MSBuildProjectName);label=$(MSBuildProjectName);versionCode=1;versionName=1.0.0;persistent=False;restoreAnyVersion=False;installLocation=auto;largeHeap=False;theme=TitleBar;hardwareAccelerated=true;apiKey=</VerInfo_Keys>
@ -136,17 +132,14 @@
<Android_NotificationIcon48>$(BDS)\bin\Artwork\Android\FM_NotificationIcon_48x48.png</Android_NotificationIcon48>
<Android_NotificationIcon72>$(BDS)\bin\Artwork\Android\FM_NotificationIcon_72x72.png</Android_NotificationIcon72>
<Android_NotificationIcon96>$(BDS)\bin\Artwork\Android\FM_NotificationIcon_96x96.png</Android_NotificationIcon96>
<Android_LauncherIcon192>$(BDS)\bin\Artwork\Android\FM_LauncherIcon_192x192.png</Android_LauncherIcon192>
</PropertyGroup>
<PropertyGroup Condition="'$(Base_OSX32)'!=''">
<BT_BuildType>Debug</BT_BuildType>
<VerInfo_Keys>CFBundleName=$(MSBuildProjectName);CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleVersion=1.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);NSHighResolutionCapable=true;LSApplicationCategoryType=public.app-category.utilities;NSLocationAlwaysUsageDescription=The reason for accessing the location information of the user;NSLocationWhenInUseUsageDescription=The reason for accessing the location information of the user;NSContactsUsageDescription=The reason for accessing the contacts</VerInfo_Keys>
</PropertyGroup>
<PropertyGroup Condition="'$(Base_OSX64)'!=''">
<VerInfo_Keys>CFBundleName=$(MSBuildProjectName);CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleVersion=1.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);NSHighResolutionCapable=true;LSApplicationCategoryType=public.app-category.utilities;NSLocationAlwaysUsageDescription=The reason for accessing the location information of the user;NSLocationWhenInUseUsageDescription=The reason for accessing the location information of the user;NSContactsUsageDescription=The reason for accessing the contacts</VerInfo_Keys>
<BT_BuildType>Debug</BT_BuildType>
<Base_OSX32>true</Base_OSX32>
<PropertyGroup Condition="'$(Base_OSXARM64)'!=''">
<Base_OSX64>true</Base_OSX64>
<CfgParent>Base</CfgParent>
<Base>true</Base>
<BT_BuildType>Debug</BT_BuildType>
<Base_OSX32>true</Base_OSX32>
</PropertyGroup>
<PropertyGroup Condition="'$(Base_Win32)'!=''">
<VerInfo_Locale>1033</VerInfo_Locale>
@ -185,10 +178,6 @@
<DCCReference Include="FileUploadControllerU.pas"/>
<DCCReference Include="..\serversideviewcustom\MVCFramework.View.Renderers.TemplatePro.pas"/>
<DCCReference Include="..\serversideviewcustom\lib\TemplateProU.pas"/>
<BuildConfiguration Include="Release">
<Key>Cfg_2</Key>
<CfgParent>Base</CfgParent>
</BuildConfiguration>
<BuildConfiguration Include="Base">
<Key>Base</Key>
</BuildConfiguration>
@ -196,6 +185,10 @@
<Key>Cfg_1</Key>
<CfgParent>Base</CfgParent>
</BuildConfiguration>
<BuildConfiguration Include="Release">
<Key>Cfg_2</Key>
<CfgParent>Base</CfgParent>
</BuildConfiguration>
</ItemGroup>
<ProjectExtensions>
<Borland.Personality>Delphi.Personality.12</Borland.Personality>
@ -252,42 +245,42 @@
</Excluded_Packages>
</Delphi.Personality>
<Deployment Version="3">
<DeployFile LocalName="$(BDS)\Redist\osx32\libcgunwind.1.0.dylib" Class="DependencyModule">
<Platform Name="OSX32">
<Overwrite>true</Overwrite>
</Platform>
</DeployFile>
<DeployFile LocalName="$(BDS)\Redist\osx64\libcgsqlite3.dylib" Class="DependencyModule">
<Platform Name="OSX64">
<Overwrite>true</Overwrite>
</Platform>
</DeployFile>
<DeployFile LocalName="$(BDS)\Redist\iossimulator\libPCRE.dylib" Class="DependencyModule">
<Platform Name="iOSSimulator">
<Overwrite>true</Overwrite>
</Platform>
</DeployFile>
<DeployFile LocalName="$(BDS)\Redist\iossimulator\libcgunwind.1.0.dylib" Class="DependencyModule">
<Platform Name="iOSSimulator">
<Overwrite>true</Overwrite>
</Platform>
</DeployFile>
<DeployFile LocalName="$(BDS)\Redist\iossimulator\libpcre.dylib" Class="DependencyModule">
<Platform Name="iOSSimulator">
<Overwrite>true</Overwrite>
</Platform>
</DeployFile>
<DeployFile LocalName="$(BDS)\Redist\iossimulator\libcgunwind.1.0.dylib" Class="DependencyModule">
<Platform Name="iOSSimulator">
<Overwrite>true</Overwrite>
</Platform>
</DeployFile>
<DeployFile LocalName="$(BDS)\Redist\osx32\libcgsqlite3.dylib" Class="DependencyModule">
<Platform Name="OSX32">
<Overwrite>true</Overwrite>
</Platform>
</DeployFile>
<DeployFile LocalName="$(BDS)\Redist\osx32\libcgunwind.1.0.dylib" Class="DependencyModule">
<Platform Name="OSX32">
<Overwrite>true</Overwrite>
</Platform>
</DeployFile>
<DeployFile LocalName="Win32\Debug\FilesUploadDemo.exe" Configuration="Debug" Class="ProjectOutput">
<Platform Name="Win32">
<RemoteName>FilesUploadDemo.exe</RemoteName>
<Overwrite>true</Overwrite>
</Platform>
</DeployFile>
<DeployFile LocalName="$(BDS)\Redist\osx64\libcgsqlite3.dylib" Class="DependencyModule">
<Platform Name="OSX64">
<Overwrite>true</Overwrite>
</Platform>
</DeployFile>
<DeployClass Name="AdditionalDebugSymbols">
<Platform Name="OSX32">
<Operation>1</Operation>
@ -296,6 +289,16 @@
<Operation>0</Operation>
</Platform>
</DeployClass>
<DeployClass Name="AndroidClasses">
<Platform Name="Android">
<RemoteDir>classes</RemoteDir>
<Operation>64</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>classes</RemoteDir>
<Operation>64</Operation>
</Platform>
</DeployClass>
<DeployClass Name="AndroidClassesDexFile">
<Platform Name="Android">
<RemoteDir>classes</RemoteDir>
@ -424,6 +427,16 @@
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_LauncherIcon192">
<Platform Name="Android">
<RemoteDir>res\drawable-xxxhdpi</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-xxxhdpi</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_LauncherIcon36">
<Platform Name="Android">
<RemoteDir>res\drawable-ldpi</RemoteDir>
@ -584,6 +597,10 @@
<Operation>1</Operation>
<Extensions>.framework</Extensions>
</Platform>
<Platform Name="OSXARM64">
<Operation>1</Operation>
<Extensions>.framework</Extensions>
</Platform>
<Platform Name="Win32">
<Operation>0</Operation>
</Platform>
@ -597,6 +614,10 @@
<Operation>1</Operation>
<Extensions>.dylib</Extensions>
</Platform>
<Platform Name="OSXARM64">
<Operation>1</Operation>
<Extensions>.dylib</Extensions>
</Platform>
<Platform Name="Win32">
<Operation>0</Operation>
<Extensions>.dll;.bpl</Extensions>
@ -623,6 +644,10 @@
<Operation>1</Operation>
<Extensions>.dylib</Extensions>
</Platform>
<Platform Name="OSXARM64">
<Operation>1</Operation>
<Extensions>.dylib</Extensions>
</Platform>
<Platform Name="Win32">
<Operation>0</Operation>
<Extensions>.bpl</Extensions>
@ -650,10 +675,39 @@
<Platform Name="OSX64">
<Operation>0</Operation>
</Platform>
<Platform Name="OSXARM64">
<Operation>0</Operation>
</Platform>
<Platform Name="Win32">
<Operation>0</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iOS_AppStore1024">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPad_AppIcon152">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimulator">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPad_AppIcon167">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimulator">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPad_Launch1024">
<Platform Name="iOSDevice32">
<Operation>1</Operation>
@ -786,6 +840,16 @@
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPad_Launch2x">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimulator">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPad_Launch768">
<Platform Name="iOSDevice32">
<Operation>1</Operation>
@ -808,6 +872,66 @@
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPad_LaunchDark2x">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimulator">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPad_Notification40">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimulator">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPad_Setting58">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimulator">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPad_SpotLight80">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimulator">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPhone_AppIcon120">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimulator">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPhone_AppIcon180">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimulator">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPhone_Launch1125">
<Platform Name="iOSDevice32">
<Operation>1</Operation>
@ -907,6 +1031,16 @@
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPhone_Launch2x">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimulator">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPhone_Launch320">
<Platform Name="iOSDevice32">
<Operation>1</Operation>
@ -918,6 +1052,16 @@
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPhone_Launch3x">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimulator">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPhone_Launch640">
<Platform Name="iOSDevice32">
<Operation>1</Operation>
@ -962,6 +1106,86 @@
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPhone_LaunchDark2x">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimulator">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPhone_LaunchDark3x">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimulator">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPhone_Notification40">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimulator">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPhone_Notification60">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimulator">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPhone_Setting58">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimulator">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPhone_Setting87">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimulator">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPhone_Spotlight120">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimulator">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPhone_Spotlight80">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimulator">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="ProjectAndroidManifest">
<Platform Name="Android">
<Operation>1</Operation>
@ -983,6 +1207,7 @@
<DeployClass Name="ProjectiOSDeviceResourceRules"/>
<DeployClass Name="ProjectiOSEntitlements"/>
<DeployClass Name="ProjectiOSInfoPList"/>
<DeployClass Name="ProjectiOSLaunchScreen"/>
<DeployClass Name="ProjectiOSResource">
<Platform Name="iOSDevice32">
<Operation>1</Operation>
@ -1006,6 +1231,10 @@
<RemoteDir>Contents\Resources</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="OSXARM64">
<RemoteDir>Contents\Resources</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Required="true" Name="ProjectOutput">
<Platform Name="Android">
@ -1034,6 +1263,9 @@
<Platform Name="OSX64">
<Operation>1</Operation>
</Platform>
<Platform Name="OSXARM64">
<Operation>1</Operation>
</Platform>
<Platform Name="Win32">
<Operation>0</Operation>
</Platform>
@ -1072,23 +1304,23 @@
<Operation>1</Operation>
</Platform>
</DeployClass>
<ProjectRoot Platform="iOSDevice64" Name="$(PROJECTNAME).app"/>
<ProjectRoot Platform="Win64" Name="$(PROJECTNAME)"/>
<ProjectRoot Platform="iOSDevice32" Name="$(PROJECTNAME).app"/>
<ProjectRoot Platform="Linux64" Name="$(PROJECTNAME)"/>
<ProjectRoot Platform="Win32" Name="$(PROJECTNAME)"/>
<ProjectRoot Platform="OSX32" Name="$(PROJECTNAME)"/>
<ProjectRoot Platform="Android" Name="$(PROJECTNAME)"/>
<ProjectRoot Platform="OSX64" Name="$(PROJECTNAME)"/>
<ProjectRoot Platform="iOSSimulator" Name="$(PROJECTNAME).app"/>
<ProjectRoot Platform="Win32" Name="$(PROJECTNAME)"/>
<ProjectRoot Platform="Android64" Name="$(PROJECTNAME)"/>
<ProjectRoot Platform="Android" Name="$(PROJECTNAME)"/>
<ProjectRoot Platform="Win64" Name="$(PROJECTNAME)"/>
<ProjectRoot Platform="OSXARM64" Name="$(PROJECTNAME)"/>
<ProjectRoot Platform="iOSDevice32" Name="$(PROJECTNAME).app"/>
<ProjectRoot Platform="iOSDevice64" Name="$(PROJECTNAME).app"/>
<ProjectRoot Platform="Linux64" Name="$(PROJECTNAME)"/>
<ProjectRoot Platform="iOSSimulator" Name="$(PROJECTNAME).app"/>
</Deployment>
<Platforms>
<Platform value="Android">False</Platform>
<Platform value="Android64">False</Platform>
<Platform value="Linux64">True</Platform>
<Platform value="OSX32">False</Platform>
<Platform value="OSX64">False</Platform>
<Platform value="OSXARM64">False</Platform>
<Platform value="Win32">True</Platform>
<Platform value="Win64">False</Platform>
</Platforms>

View File

@ -70,6 +70,11 @@ type
TMVCActiveRecordLoadOption = (loIgnoreNotExistentFields);
TMVCActiveRecordLoadOptions = set of TMVCActiveRecordLoadOption;
TPartionFieldNames = TArray<String>;
TPartionFieldValues = TArray<String>;
TPartionFieldTypes = TArray<TFieldType>;
IMVCEntityProcessor = interface
['{E7CD11E6-9FF9-46D2-B7B0-DA5B38EAA14E}']
procedure GetEntities(const Context: TWebContext; const Renderer: TMVCRenderer;
@ -123,6 +128,13 @@ type
constructor Create(aName: string; aRQLFilter: String); overload;
end;
MVCPartitionAttribute = class(MVCActiveRecordCustomAttribute)
public
PartitionClause: String;
constructor Create(const PartitionClause: String);
end;
MVCTableFieldAttribute = class(MVCActiveRecordCustomAttribute)
public
FieldName: string;
@ -145,6 +157,16 @@ type
TMVCSQLGenerator = class;
TPartitionInfo = class
FieldNames: TPartionFieldNames;
FieldValues: TPartionFieldValues;
FieldTypes: TPartionFieldTypes;
RQLFilter: String;
SQLFilter: String;
constructor Create;
procedure InitializeFilterStrings(const SQLGenerator: TMVCSQLGenerator);
end;
TMVCActiveRecordList = class(TObjectList<TMVCActiveRecord>)
public
constructor Create; virtual;
@ -179,6 +201,7 @@ type
fPrimaryKey: TRTTIField;
fBackendDriver: string;
fMapping: TMVCFieldsMapping;
fPartitionInfo: TPartitionInfo;
function GetBackEnd: string;
function GetConnection: TFDConnection;
procedure InitTableInfo;
@ -490,16 +513,19 @@ type
fCompiler: TRQLCompiler;
fRQL2SQL: TRQL2SQL;
protected
fPartitionInfo: TPartitionInfo;
function GetDefaultSQLFilter(const IncludeWhereClause: Boolean; const IncludeAndClauseBeforeFilter: Boolean = False): String; //inline;
function MergeDefaultRQLFilter(const RQL: String): String; //inline;
function GetRQLParser(const MaxRecordCount: Int32): TRQL2SQL;
function MergeRQL(const RQL1, RQL2: String): String;
function MergeSQLFilter(const SQL1, SQL2: String): String;
function GetRQLParser: TRQL2SQL;
function GetCompiler: TRQLCompiler;
function GetCompilerClass: TRQLCompilerClass; virtual; abstract;
function GetMapping: TMVCFieldsMapping;
function TableFieldsDelimited(const Map: TFieldsMap; const PKFieldName: string;
const Delimiter: string): string;
public
constructor Create(Mapping: TMVCFieldsMapping; const DefaultRQLFilter: string); virtual;
constructor Create(Mapping: TMVCFieldsMapping; const DefaultRQLFilter: string; const PartitionInfo: TPartitionInfo); virtual;
destructor Destroy; override;
// capabilities
function HasSequences: Boolean; virtual;
@ -507,7 +533,7 @@ type
// end-capabilities
function CreateSQLWhereByRQL(const RQL: string; const Mapping: TMVCFieldsMapping;
const UseArtificialLimit: Boolean = True; const UseFilterOnly: Boolean = false;
const MaxRecordCount: UInt32 = TMVCConstants.MAX_RECORD_COUNT): string; virtual; abstract;
const MaxRecordCount: Int32 = TMVCConstants.MAX_RECORD_COUNT): string; virtual; abstract;
function CreateSelectSQL(const TableName: string; const Map: TFieldsMap;
const PKFieldName: string;
const PKOptions: TMVCActiveRecordFieldOptions): string; virtual; abstract;
@ -618,6 +644,44 @@ var
gConnections: IMVCActiveRecordConnections;
gLock: TObject;
function ParsePartitionClause(const PartitionClause: String): TPartitionInfo;
var
lPieces, lItems: TArray<String>;
lPiece: String;
begin
{
Needs to parse [MVCPartition('rating=(integer)4;classname=(string)persona')]
}
Result := TPartitionInfo.Create;
try
lPieces := PartitionClause.Split([';']);
for lPiece in lPieces do
begin
lItems := lPiece.Split(['=','(',')'], TStringSplitOptions.ExcludeEmpty);
if Length(lItems)<>3 then
begin
raise EMVCActiveRecord.Create('Invalid partitioning clause: ' + lPiece + '. [HINT] Paritioning must be in the form: "[fieldname1=(integer|string)value1]"');
end;
Insert(lItems[0], Result.FieldNames,0);
if lItems[1]='integer' then
Insert(ftInteger, Result.FieldTypes,0)
else if lItems[1]='string' then
begin
Insert(ftString, Result.FieldTypes,0)
end
else
begin
raise EMVCActiveRecord.Create('Unknown data type in partitioning: ' + lItems[1] + '. [HINT] data type can be "integer" or "string"');
end;
Insert(lItems[2], Result.FieldValues,0);
end;
except
Result.Free;
raise;
end;
end;
function GetBackEndByConnection(aConnection: TFDConnection): string;
begin
case Ord(aConnection.RDBMSKind) of
@ -920,6 +984,7 @@ begin
fMap.Free;
fSQLGenerator.Free;
fRQL2SQL.Free;
fPartitionInfo.Free;
fConn := nil; // do not free it!!
inherited;
end;
@ -938,6 +1003,7 @@ var
lValue: TValue;
lSQL: string;
lHandled: Boolean;
I: Integer;
begin
lQry := TFDQuery.Create(nil);
try
@ -951,6 +1017,22 @@ begin
MapObjectToParams(lQry.Params, lHandled);
if not lHandled then
begin
{partitioning}
for I := 0 to Length(fPartitionInfo.FieldNames)-1 do
begin
lPar := lQry.FindParam(SQLGenerator.GetParamNameForSQL(fPartitionInfo.FieldNames[I]));
if lPar <> nil then
begin
if fPartitionInfo.FieldTypes[I] = ftInteger then
lValue := StrToInt(fPartitionInfo.FieldValues[I])
else
lValue := fPartitionInfo.FieldValues[I];
//lPar.DataTypeName := fPartitionInfo.FieldValues[I];
MapTValueToParam(lValue, lPar);
end
end;
{end-partitioning}
for lPair in fMap do
begin
lPar := lQry.FindParam(SQLGenerator.GetParamNameForSQL(lPair.Value.FieldName));
@ -1041,6 +1123,7 @@ var
lFieldInfo: TFieldInfo;
lPrimaryFieldTypeAsStr: string;
begin
fPartitionInfo := nil;
fEntityAllowedActions := [TMVCEntityAction.eaCreate, TMVCEntityAction.eaRetrieve,
TMVCEntityAction.eaUpdate,
TMVCEntityAction.eaDelete];
@ -1059,8 +1142,19 @@ begin
begin
fEntityAllowedActions := MVCEntityActionsAttribute(lAttribute).EntityAllowedActions;
end;
if lAttribute is MVCPartitionAttribute then
begin
fPartitionInfo := ParsePartitionClause(MVCPartitionAttribute(lAttribute).PartitionClause);
Continue;
end;
end;
if fPartitionInfo = nil then
begin
fPartitionInfo := TPartitionInfo.Create;
end;
if fTableName = '' then
begin
if [eaCreate, eaUpdate, eaDelete] * fEntityAllowedActions <> [] then
@ -1133,6 +1227,7 @@ begin
end;
end;
fMap.EndUpdates;
fPartitionInfo.InitializeFilterStrings(SQLGenerator);
Assert(fMap.WritableFieldsCount + fMap.ReadableFieldsCount > 0,
'No fields defined [HINT] Use MVCTableField in private fields');
end;
@ -2273,9 +2368,8 @@ var
begin
lAR := T.Create;
try
lSQL := lAR.SQLGenerator.CreateSQLWhereByRQL(RQL, lAR.GetMapping, MaxRecordCount > -1, false,
MaxRecordCount).Trim;
// LogD(Format('RQL [%s] => SQL [%s]', [RQL, lSQL]));
lSQL := lAR.SQLGenerator.CreateSQLWhereByRQL(RQL, lAR.GetMapping,
MaxRecordCount > -1, False, MaxRecordCount).Trim;
lSQL := TMVCSQLGenerator.RemoveInitialWhereKeyword(lSQL);
Result := Where<T>(lSQL, []);
finally
@ -2574,7 +2668,7 @@ begin
begin
GetConnection.Connected := True;
fSQLGenerator := TMVCSQLGeneratorRegistry.Instance.GetSQLGenerator(GetBackEnd)
.Create(GetMapping, fDefaultRQLFilter);
.Create(GetMapping, fDefaultRQLFilter, fPartitionInfo);
end;
Result := fSQLGenerator;
end;
@ -2942,15 +3036,16 @@ end;
{ TMVCSQLGenerator }
constructor TMVCSQLGenerator.Create(Mapping: TMVCFieldsMapping; const DefaultRQLFilter: string);
constructor TMVCSQLGenerator.Create(Mapping: TMVCFieldsMapping; const DefaultRQLFilter: string; const PartitionInfo: TPartitionInfo);
begin
inherited Create;
fMapping := Mapping;
fDefaultRQLFilter := DefaultRQLFilter;
fPartitionInfo := PartitionInfo;
GetCompiler;
if not fDefaultRQLFilter.IsEmpty then
begin
GetRQLParser(-1).Execute(fDefaultRQLFilter,fDefaultSQLFilter, GetCompiler, False, True);
GetRQLParser.Execute(fDefaultRQLFilter,fDefaultSQLFilter, GetCompiler, False, True);
fDefaultSQLFilter := TMVCSQLGenerator.RemoveInitialWhereKeyword(fDefaultSQLFilter);
end;
end;
@ -2983,19 +3078,17 @@ end;
function TMVCSQLGenerator.GetDefaultSQLFilter(const IncludeWhereClause: Boolean; const IncludeAndClauseBeforeFilter: Boolean): String;
begin
Result := '';
if not fDefaultSQLFilter.IsEmpty then
Result := MergeSQLFilter(fPartitionInfo.SQLFilter, fDefaultSQLFilter);
if not Result.IsEmpty then
begin
if IncludeWhereClause then
begin
Result := ' WHERE ' + fDefaultSQLFilter
Result := ' WHERE ' + Result;
end
else
begin
if IncludeAndClauseBeforeFilter then
Result := ' and ' + fDefaultSQLFilter
else
Result := fDefaultSQLFilter;
Result := ' and ' + Result;
end;
end;
end;
@ -3005,11 +3098,11 @@ begin
Result := fCompiler.GetFieldNameForSQL(FieldName);
end;
function TMVCSQLGenerator.GetRQLParser(const MaxRecordCount: Int32): TRQL2SQL;
function TMVCSQLGenerator.GetRQLParser: TRQL2SQL;
begin
if fRQL2SQL = nil then
begin
fRQL2SQL := TRQL2SQL.Create(MaxRecordCount);
fRQL2SQL := TRQL2SQL.Create;
end;
Result := fRQL2SQL;
end;
@ -3063,6 +3156,41 @@ begin
begin
Result := RQL;
end;
Result := MergeRQL(Result, fPartitionInfo.RQLFilter);
end;
function TMVCSQLGenerator.MergeRQL(const RQL1, RQL2: String): String;
begin
if RQL1+RQL2 = '' then
begin
Exit('');
end;
if RQL1.IsEmpty and (not RQL2.IsEmpty) then
begin
Exit(RQL2);
end;
if RQL2.IsEmpty and (not RQL1.IsEmpty) then
begin
Exit(RQL1);
end;
Result := 'and(' + RQL1 + ',' + RQL2 + ')';
end;
function TMVCSQLGenerator.MergeSQLFilter(const SQL1, SQL2: String): String;
begin
if SQL1 + SQL2 = '' then
begin
Exit('');
end;
if SQL1.IsEmpty and (not SQL2.IsEmpty) then
begin
Exit(SQL2);
end;
if SQL2.IsEmpty and (not SQL1.IsEmpty) then
begin
Exit(SQL1);
end;
Result := '((' + SQL1 + ') and (' + SQL2 + '))';
end;
class function TMVCSQLGenerator.RemoveInitialWhereKeyword(
@ -3412,6 +3540,47 @@ begin
RQLFilter := aRQLFilter;
end;
{ MVCPartitionAttribute }
constructor MVCPartitionAttribute.Create(const PartitionClause: String);
begin
inherited Create;
Self.PartitionClause := PartitionClause;
end;
{ TPartitionInfo }
constructor TPartitionInfo.Create;
begin
inherited;
SetLength(FieldNames, 0);
SetLength(FieldValues, 0);
SetLength(FieldTypes, 0);
end;
procedure TPartitionInfo.InitializeFilterStrings(
const SQLGenerator: TMVCSQLGenerator);
var
lFieldCount, I: Integer;
begin
RQLFilter := '';
lFieldCount := Length(FieldNames);
if lFieldCount > 0 then
begin
for I := 0 to lFieldCount - 1 do
begin
RQLFilter := RQLFilter + 'eq(' + FieldNames[i] + ',' + FieldValues[i] + '),';
end;
RQLFilter := RQLFilter.Remove(RQLFilter.Length - 1,1);
if lFieldCount > 1 then
begin
RQLFilter := 'and(' + RQLFilter + ')';
end;
end;
SQLGenerator.GetRQLParser.Execute(RQLFilter, SQLFilter,SQLGenerator.GetCompiler,False, True);
SQLFilter := TMVCSQLGenerator.RemoveInitialWhereKeyword(SQLFilter);
end;
initialization
gLock := TObject.Create;

View File

@ -123,7 +123,7 @@ type
DEFAULT_MAX_REQUEST_SIZE = OneMiB * 5; // 5 MiB
HATEOAS_PROP_NAME = 'links';
X_HTTP_Method_Override = 'X-HTTP-Method-Override';
MAX_RECORD_COUNT = 20;
MAX_RECORD_COUNT = 100;
end;
HATEOAS = record

View File

@ -86,7 +86,7 @@ begin
else
lValue := aRQLFIlter.OpRight;
lDBFieldName := GetDatabaseFieldName(aRQLFIlter.OpLeft);
lDBFieldName := GetDatabaseFieldName(aRQLFIlter.OpLeft, True);
case aRQLFIlter.Token of
tkEq:

View File

@ -91,7 +91,7 @@ type
private
fMapping: TMVCFieldsMapping;
protected
function GetDatabaseFieldName(const RQLPropertyName: string): string;
function GetDatabaseFieldName(const RQLPropertyName: string; const UsePropertyNameIfAttributeDoesntExists: Boolean = False): string;
function QuoteStringArray(const aStringArray: TArray<string>): TArray<string>;
public
constructor Create(const Mapping: TMVCFieldsMapping); virtual;
@ -179,16 +179,15 @@ type
fInputLength: Integer;
fCurr: Char;
fCurrToken: TRQLToken;
fMaxRecordCount: Int64;
protected
/// /// RQL Sections
function ParseFilters: Boolean;
function ParseSort: Boolean;
function ParseLimit: Boolean;
function ParseLimit(const MaxRecordCount: Integer): Boolean;
/// ///RQL functions
procedure ParseBinOperator(const aToken: TRQLToken; const aAST: TObjectList<TRQLCustom>);
procedure ParseLogicOperator(const aToken: TRQLToken; const aAST: TObjectList<TRQLCustom>);
procedure ParseSortLimit(const Required: Boolean);
procedure ParseSortLimit(const Required: Boolean; const MaxRecordCount: Integer);
/// //Parser utils
function MatchFieldName(out lFieldName: string): Boolean;
function MatchFieldStringValue(out lFieldValue: string): Boolean;
@ -208,14 +207,15 @@ type
procedure EatWhiteSpaces;
procedure CheckEOF(const Token: TRQLToken);
public
constructor Create(const MaxRecordCount: Integer = -1);
constructor Create; virtual;
destructor Destroy; override;
procedure Execute(
const RQL: string;
out SQL: string;
const RQLCompiler: TRQLCompiler;
const SetArtificialLimit: Boolean = true;
const UseFilterOnly: Boolean = False);
const UseFilterOnly: Boolean = False;
const MaxRecordCount: Int32 = TMVCConstants.MAX_RECORD_COUNT);
end;
TRQLCompilerRegistry = class sealed
@ -268,11 +268,10 @@ begin
Error('Unexpected end of expression');
end;
constructor TRQL2SQL.Create(const MaxRecordCount: Integer);
constructor TRQL2SQL.Create;
begin
inherited Create;
fAST := TRQLAbstractSyntaxTree.Create;
fMaxRecordCount := MaxRecordCount;
end;
destructor TRQL2SQL.Destroy;
@ -321,7 +320,8 @@ procedure TRQL2SQL.Execute(
out SQL: string;
const RQLCompiler: TRQLCompiler;
const SetArtificialLimit: Boolean;
const UseFilterOnly: Boolean);
const UseFilterOnly: Boolean;
const MaxRecordCount: Int32);
var
lLimit: TRQLLimit;
lRQLItem: TRQLCustom;
@ -345,25 +345,25 @@ begin
fAST.Insert(0, TRQLWhere.Create);
if GetToken = tkSemicolon then
begin
ParseSortLimit(true);
ParseSortLimit(true, MaxRecordCount);
end;
end
else
begin
ParseSortLimit(False);
ParseSortLimit(False, MaxRecordCount);
end;
EatWhiteSpaces;
if GetToken <> tkEOF then
Error('Expected EOF');
// add artificial limit
if SetArtificialLimit and (fMaxRecordCount > -1) and (not fAST.TreeContainsToken(tkLimit, lRQLItem)) then
if SetArtificialLimit and (MaxRecordCount > -1) and (not fAST.TreeContainsToken(tkLimit, lRQLItem)) then
begin
lLimit := TRQLLimit.Create;
fAST.Add(lLimit);
lLimit.Token := tkLimit;
lLimit.Start := 0;
lLimit.Count := fMaxRecordCount;
lLimit.Count := MaxRecordCount;
end;
@ -686,7 +686,7 @@ begin
end;
end;
function TRQL2SQL.ParseLimit: Boolean;
function TRQL2SQL.ParseLimit(const MaxRecordCount: Integer): Boolean;
var
lStart: string;
lCount: string;
@ -717,9 +717,9 @@ begin
fAST.Add(lRQLLimit);
lRQLLimit.Token := tkLimit;
lRQLLimit.Start := StrToInt64(lStart); // XE7 compat
if fMaxRecordCount > -1 then
if MaxRecordCount > -1 then
begin
lRQLLimit.Count := Min(StrToInt64(lCount), fMaxRecordCount);
lRQLLimit.Count := Min(StrToInt64(lCount), MaxRecordCount);
end
else
begin
@ -810,7 +810,7 @@ begin
Error('Expected ")"');
end;
procedure TRQL2SQL.ParseSortLimit(const Required: Boolean);
procedure TRQL2SQL.ParseSortLimit(const Required: Boolean; const MaxRecordCount: Integer);
var
lFoundSort: Boolean;
lFoundLimit: Boolean;
@ -823,7 +823,7 @@ begin
begin
BackToLastPos;
end;
lFoundLimit := ParseLimit;
lFoundLimit := ParseLimit(MaxRecordCount);
if Required and (not(lFoundSort or lFoundLimit)) then
Error('Expected "sort" and/or "limit"');
end;
@ -1151,7 +1151,7 @@ begin
end;
function TRQLCompiler.GetDatabaseFieldName(
const RQLPropertyName: string): string;
const RQLPropertyName: string; const UsePropertyNameIfAttributeDoesntExists: Boolean): string;
var
lField: TMVCFieldMap;
lRQLProperty: string;
@ -1171,8 +1171,11 @@ begin
Exit(GetFieldNameForSQL(lField.DatabaseFieldName));
end;
{ TODO -oDanieleT -cGeneral : Here we should consider also MVCNameAs attribute to find the name }
raise ERQLException.CreateFmt('Property %s does not exist or is transient and cannot be used in RQL',
[RQLPropertyName]);
if UsePropertyNameIfAttributeDoesntExists then
Exit(GetFieldNameForSQL(RQLPropertyName))
else
raise ERQLException.CreateFmt('Property %s does not exist or is transient and cannot be used in RQL',
[RQLPropertyName]);
end;
function TRQLCompiler.GetFieldNameForSQL(const FieldName: string): string;

View File

@ -73,7 +73,7 @@ type
const RQL: string; const Mapping: TMVCFieldsMapping;
const UseArtificialLimit: Boolean = True;
const UseFilterOnly: Boolean = False;
const MaxRecordCount: UInt32 = TMVCConstants.MAX_RECORD_COUNT): string; override;
const MaxRecordCount: Int32 = TMVCConstants.MAX_RECORD_COUNT): string; override;
function CreateSelectCount(
const TableName: string): string; override;
end;
@ -161,13 +161,14 @@ function TMVCSQLGeneratorFirebird.CreateSQLWhereByRQL(
const RQL: string; const Mapping: TMVCFieldsMapping;
const UseArtificialLimit: Boolean = True;
const UseFilterOnly: Boolean = False;
const MaxRecordCount: UInt32 = TMVCConstants.MAX_RECORD_COUNT): string;
const MaxRecordCount: Int32 = TMVCConstants.MAX_RECORD_COUNT): string;
var
lFirebirdCompiler: TRQLFirebirdCompiler;
begin
lFirebirdCompiler := TRQLFirebirdCompiler.Create(Mapping);
try
GetRQLParser(MaxRecordCount).Execute(RQL, Result, lFirebirdCompiler, UseArtificialLimit, UseFilterOnly);
GetRQLParser.Execute(RQL, Result, lFirebirdCompiler,
UseArtificialLimit, UseFilterOnly, MaxRecordCount);
finally
lFirebirdCompiler.Free;
end;

View File

@ -68,7 +68,7 @@ type
const RQL: string; const Mapping: TMVCFieldsMapping;
const UseArtificialLimit: Boolean = True;
const UseFilterOnly: Boolean = False;
const MaxRecordCount: UInt32 = TMVCConstants.MAX_RECORD_COUNT): string; override;
const MaxRecordCount: Int32 = TMVCConstants.MAX_RECORD_COUNT): string; override;
function CreateSelectCount(
const TableName: string): string; override;
function CreateDeleteAllSQL(
@ -156,13 +156,13 @@ function TMVCSQLGeneratorMSSQL.CreateSQLWhereByRQL(
const RQL: string; const Mapping: TMVCFieldsMapping;
const UseArtificialLimit: Boolean = True;
const UseFilterOnly: Boolean = False;
const MaxRecordCount: UInt32 = TMVCConstants.MAX_RECORD_COUNT): string;
const MaxRecordCount: Int32 = TMVCConstants.MAX_RECORD_COUNT): string;
var
lMSSQLCompiler: TRQLMSSQLCompiler;
begin
lMSSQLCompiler := TRQLMSSQLCompiler.Create(Mapping);
try
GetRQLParser(MaxRecordCount).Execute(RQL, Result, lMSSQLCompiler, UseArtificialLimit, UseFilterOnly);
GetRQLParser.Execute(RQL, Result, lMSSQLCompiler, UseArtificialLimit, UseFilterOnly, MaxRecordCount);
finally
lMSSQLCompiler.Free;
end;

View File

@ -70,7 +70,7 @@ type
const RQL: string; const Mapping: TMVCFieldsMapping;
const UseArtificialLimit: Boolean = True;
const UseFilterOnly: Boolean = False;
const MaxRecordCount: UInt32 = TMVCConstants.MAX_RECORD_COUNT): string; override;
const MaxRecordCount: Int32 = TMVCConstants.MAX_RECORD_COUNT): string; override;
function CreateSelectCount(
const TableName: string): string; override;
end;
@ -157,13 +157,13 @@ function TMVCSQLGeneratorMySQL.CreateSQLWhereByRQL(
const RQL: string; const Mapping: TMVCFieldsMapping;
const UseArtificialLimit: Boolean = True;
const UseFilterOnly: Boolean = False;
const MaxRecordCount: UInt32 = TMVCConstants.MAX_RECORD_COUNT): string;
const MaxRecordCount: Int32 = TMVCConstants.MAX_RECORD_COUNT): string;
var
lMySQLCompiler: TRQLMySQLCompiler;
begin
lMySQLCompiler := TRQLMySQLCompiler.Create(Mapping);
try
GetRQLParser(MaxRecordCount).Execute(RQL, Result, lMySQLCompiler, UseArtificialLimit, UseFilterOnly);
GetRQLParser.Execute(RQL, Result, lMySQLCompiler, UseArtificialLimit, UseFilterOnly, MaxRecordCount);
finally
lMySQLCompiler.Free;
end;

View File

@ -71,7 +71,7 @@ type
const Mapping: TMVCFieldsMapping;
const UseArtificialLimit: Boolean;
const UseFilterOnly: Boolean;
const MaxRecordCount: UInt32 = TMVCConstants.MAX_RECORD_COUNT): string; override;
const MaxRecordCount: Int32 = TMVCConstants.MAX_RECORD_COUNT): string; override;
function CreateSelectCount(
const TableName: string): string; override;
function GetSequenceValueSQL(const PKFieldName: string;
@ -98,6 +98,7 @@ 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));
@ -109,6 +110,13 @@ begin
lSB.Append(GetFieldNameForSQL(PKFieldName) + ',');
end;
{partition}
for lFieldName in fPartitionInfo.FieldNames do
begin
lSB.Append(GetFieldNameForSQL(lFieldName) + ',');
end;
{end-partition}
for lKeyValue in Map do
begin
// if not(foTransient in lKeyValue.Value.FieldOptions) then
@ -123,6 +131,14 @@ begin
begin
lSB.Append(':' + GetParamNameForSQL(PKFieldName) + ',');
end;
{partition}
for lFieldName in fPartitionInfo.FieldNames do
begin
lSB.Append(':' + GetParamNameForSQL(lFieldName) + ',');
end;
{end-partition}
for lKeyValue in Map do
begin
if lKeyValue.Value.Writeable then
@ -177,13 +193,14 @@ function TMVCSQLGeneratorPostgreSQL.CreateSQLWhereByRQL(
const Mapping: TMVCFieldsMapping;
const UseArtificialLimit: Boolean;
const UseFilterOnly: Boolean;
const MaxRecordCount: UInt32): string;
const MaxRecordCount: Int32): string;
var
lPostgreSQLCompiler: TRQLPostgreSQLCompiler;
begin
lPostgreSQLCompiler := TRQLPostgreSQLCompiler.Create(Mapping);
try
GetRQLParser(MaxRecordCount).Execute(MergeDefaultRQLFilter(RQL), Result, lPostgreSQLCompiler, UseArtificialLimit, UseFilterOnly);
GetRQLParser.Execute(MergeDefaultRQLFilter(RQL), Result, lPostgreSQLCompiler,
UseArtificialLimit, UseFilterOnly, MaxRecordCount);
finally
lPostgreSQLCompiler.Free;
end;
@ -193,6 +210,7 @@ function TMVCSQLGeneratorPostgreSQL.CreateUpdateSQL(const TableName: string; con
const PKFieldName: string; const PKOptions: TMVCActiveRecordFieldOptions): string;
var
lPair: TPair<TRttiField, TFieldInfo>;
I: Integer;
begin
Result := 'UPDATE ' + GetTableNameForSQL(TableName) + ' SET ';
for lPair in Map do
@ -203,6 +221,13 @@ begin
GetParamNameForSQL(lPair.Value.FieldName) + ',';
end;
end;
{partition}
for I := 0 to Length(fPartitionInfo.FieldNames) - 1 do
begin
Result := Result + GetFieldNameForSQL(fPartitionInfo.FieldNames[I]) + ' = :' +
GetParamNameForSQL(fPartitionInfo.FieldNames[I]) + ',';
end;
{end-partitioning}
Result[Length(Result)] := ' ';
if not PKFieldName.IsEmpty then
begin

View File

@ -51,7 +51,7 @@ type
const PKFieldName: string; const PKOptions: TMVCActiveRecordFieldOptions): string; override;
function CreateSQLWhereByRQL(const RQL: string; const Mapping: TMVCFieldsMapping;
const UseArtificialLimit: Boolean = True; const UseFilterOnly: Boolean = False;
const MaxRecordCount: UInt32 = TMVCConstants.MAX_RECORD_COUNT): string; override;
const MaxRecordCount: Int32 = TMVCConstants.MAX_RECORD_COUNT): string; override;
function CreateSelectCount(const TableName: string): string; override;
function HasSequences: Boolean; override;
end;
@ -135,14 +135,14 @@ end;
function TMVCSQLGeneratorSQLite.CreateSQLWhereByRQL(const RQL: string;
const Mapping: TMVCFieldsMapping; const UseArtificialLimit: Boolean = True;
const UseFilterOnly: Boolean = False;
const MaxRecordCount: UInt32 = TMVCConstants.MAX_RECORD_COUNT): string;
const MaxRecordCount: Int32 = TMVCConstants.MAX_RECORD_COUNT): string;
var
lSQLiteCompiler: TRQLSQLiteCompiler;
begin
lSQLiteCompiler := TRQLSQLiteCompiler.Create(Mapping);
try
GetRQLParser(MaxRecordCount).Execute(RQL, Result, lSQLiteCompiler, UseArtificialLimit,
UseFilterOnly);
GetRQLParser.Execute(RQL, Result, lSQLiteCompiler,
UseArtificialLimit, UseFilterOnly, MaxRecordCount);
finally
lSQLiteCompiler.Free;
end;

View File

@ -4,7 +4,7 @@
<ProjectVersion>19.3</ProjectVersion>
<FrameworkType>VCL</FrameworkType>
<Base>True</Base>
<Config Condition="'$(Config)'==''">Debug</Config>
<Config Condition="'$(Config)'==''">TESTINSIGHT</Config>
<Platform Condition="'$(Platform)'==''">Win32</Platform>
<TargetedPlatforms>1</TargetedPlatforms>
<AppType>Console</AppType>
@ -120,7 +120,6 @@
<DCC_UsePackage>FireDACSqliteDriver;DBXSqliteDriver;FireDACPgDriver;fmx;IndySystem;TeeDB;vclib;DBXInterBaseDriver;DataSnapClient;DataSnapCommon;DataSnapServer;DataSnapProviderClient;DBXSybaseASEDriver;DbxCommonDriver;vclimg;dbxcds;DatasnapConnectorsFreePascal;MetropolisUILiveTile;vcldb;vcldsnap;fmxFireDAC;DBXDb2Driver;DBXOracleDriver;CustomIPTransport;vclribbon;dsnap;IndyIPServer;fmxase;vcl;IndyCore;IndyIPCommon;CloudService;DBXMSSQLDriver;FmxTeeUI;FireDACIBDriver;FireDACDBXDriver;inetdbxpress;webdsnap;FireDACDb2Driver;adortl;FireDACASADriver;bindcompfmx;FireDACODBCDriver;rtl;dbrtl;DbxClientDriver;FireDACCommon;bindcomp;inetdb;Tee;DBXOdbcDriver;vclFireDAC;xmlrtl;ibxpress;IndyProtocols;DBXMySQLDriver;FireDACCommonDriver;vclactnband;bindengine;soaprtl;bindcompdbx;FMXTee;TeeUI;bindcompvcl;vclie;FireDACADSDriver;vcltouch;VclSmp;FireDACMSSQLDriver;FireDAC;VCLRESTComponents;Intraweb;DBXInformixDriver;DataSnapConnectors;FireDACDataSnapDriver;dsnapcon;DBXFirebirdDriver;inet;fmxobj;FireDACMySQLDriver;vclx;DBXSybaseASADriver;FireDACOracleDriver;fmxdae;RESTComponents;FireDACMSAccDriver;DataSnapIndy10ServerTransport;dbexpress;IndyIPClient;$(DCC_UsePackage)</DCC_UsePackage>
</PropertyGroup>
<PropertyGroup Condition="'$(Cfg_1)'!=''">
<DCC_Define>DEBUG;$(DCC_Define)</DCC_Define>
<DCC_DebugDCUs>true</DCC_DebugDCUs>
<DCC_Optimize>false</DCC_Optimize>
<DCC_GenerateStackFrames>true</DCC_GenerateStackFrames>
@ -157,7 +156,6 @@
</PropertyGroup>
<PropertyGroup Condition="'$(Cfg_2)'!=''">
<DCC_ExeOutput>.\bin</DCC_ExeOutput>
<DCC_Define>TESTINSIGHT;$(DCC_Define)</DCC_Define>
</PropertyGroup>
<PropertyGroup Condition="'$(Cfg_2_Win32)'!=''">
<VerInfo_Locale>1033</VerInfo_Locale>

View File

@ -7,7 +7,7 @@
<TargetedPlatforms>129</TargetedPlatforms>
<AppType>Console</AppType>
<FrameworkType>None</FrameworkType>
<ProjectVersion>19.2</ProjectVersion>
<ProjectVersion>19.3</ProjectVersion>
<Platform Condition="'$(Platform)'==''">Win32</Platform>
</PropertyGroup>
<PropertyGroup Condition="'$(Config)'=='Base' or '$(Base)'!=''">
@ -159,6 +159,12 @@
<Overwrite>true</Overwrite>
</Platform>
</DeployFile>
<DeployFile LocalName="bin\customers.json" Configuration="CI" Class="File">
<Platform Name="Linux64">
<RemoteName>customers.json</RemoteName>
<Overwrite>true</Overwrite>
</Platform>
</DeployFile>
<DeployFile LocalName="$(BDS)\Redist\iossimulator\libpcre.dylib" Class="DependencyModule">
<Platform Name="iOSSimulator">
<Overwrite>true</Overwrite>
@ -180,12 +186,6 @@
<Overwrite>true</Overwrite>
</Platform>
</DeployFile>
<DeployFile LocalName="bin\customers.json" Configuration="CI" Class="File">
<Platform Name="Linux64">
<RemoteName>customers.json</RemoteName>
<Overwrite>true</Overwrite>
</Platform>
</DeployFile>
<DeployFile LocalName="TestServer" Configuration="CI" Class="ProjectOutput">
<Platform Name="Linux64">
<RemoteName>TestServer</RemoteName>