- FIXes for Delphi 10.0 Seattle

- New! `TMVCActiveRecord.Count` method (e.g. `TMVCActiveRecord.Count(TCustomer)` returns the number of records for the entity mapped by the class `TCustomer`)
- Change! `TMVCACtiveRecord.GetByPK<T>` raises an exception if the record is not found
- New! `contains` clause has been added in the RQL compiler for Firebird and Interbase
- New Installation procedure! Just open the project group, build all and install the design-time package (which is `dmvcframeworkDT`)
This commit is contained in:
Daniele Teti 2019-01-18 18:11:27 +01:00
parent 3174504b90
commit 1b3dc4ae2c
13 changed files with 228 additions and 30 deletions

View File

@ -69,6 +69,18 @@ Congratulations to Daniele Teti and all the staff for the excellent work!" -- Ma
- New! Added SQLGenerator for PostgreSQL (in addition to MySQL, MariaDB, Firebird and Interbase)
- Better packages organization (check `packages` folder)
- New! `TMVCActiveRecord.Count` method (e.g. `TMVCActiveRecord.Count(TCustomer)` returns the number of records for the entity mapped by the class `TCustomer`)
- Change! `TMVCACtiveRecord.GetByPK<T>` raises an exception if the record is not found
- New! `contains` clause has been added in the RQL compiler for Firebird and Interbase
- New Installation procedure! Just open the project group, build all and install the design-time package (which is `dmvcframeworkDT`)
|Delphi Version|Project Group|
|---|---|
|Delphi 10.3 Rio| `packages\d103\dmvcframework_group.groupproj`|
|Delphi 10.2 Tokyo| `packages\d102\dmvcframework_group.groupproj`|
|Delphi 10.1 Berlin| `packages\d101\dmvcframework_group.groupproj`|
For older Delphi versions still there aren't complete packages available, but DMVCFramework is usable from XE7 without any issues. If you use a version previous of `Delphi 10.1 Berlin` and you want to contribute, please provide your group project using the distributed packages as example.
### DelphiMVCFramework 3.1.0-lithium
- New! Added `TMVCActiveRecord` framework (check sample `activerecord_showcase` and `activerecord_crud`)

View File

@ -49,6 +49,10 @@ type
constructor Create(const AMsg: string); reintroduce; { do not override!! }
end;
EMVCActiveRecordNotFound = class(EMVCActiveRecord)
end;
TMVCActiveRecordClass = class of TMVCActiveRecord;
TMVCActiveRecordFieldOption = (foAutoGenerated);
TMVCActiveRecordFieldOptions = set of TMVCActiveRecordFieldOption;
@ -833,7 +837,11 @@ end;
class function TMVCActiveRecord.GetByPK(const aClass: TMVCActiveRecordClass; const aValue: int64): TMVCActiveRecord;
begin
Result := aClass.Create;
Result.LoadByPK(aValue);
if not Result.LoadByPK(aValue) then
begin
Result.Free;
raise EMVCActiveRecordNotFound.Create('Record not found');
end;
end;
class function TMVCActiveRecord.GetByPK<T>(const aValue: int64): T;
@ -842,7 +850,11 @@ var
begin
Result := T.Create;
lActiveRecord := TMVCActiveRecord(Result);
lActiveRecord.LoadByPK(aValue);
if not lActiveRecord.LoadByPK(aValue) then
begin
Result.Free;
raise EMVCActiveRecordNotFound.Create('Record not found');
end;
end;
class function TMVCActiveRecord.GetFirstByWhere<T>(const SQLWhere: string; const Params: array of Variant;

View File

@ -43,8 +43,6 @@ uses
MVCFramework.Serializer.Commons,
MVCFramework.Serializer.jsondataobjects;
{$I DMVCFramework.inc}
const
JSONRPC_VERSION = '2.0';
JSONRPC_HEADER = 'jsonrpc';
@ -369,11 +367,11 @@ begin
end;
pdtDate:
begin
JSONArr.Add(DateToISODate(TDate(Value.AsExtended)));
JSONArr.Add(DateToISODate(FloatToDateTime(Value.AsExtended)));
end;
pdtTime:
begin
JSONArr.Add(TimeToISOTime(TTime(Value.AsExtended)));
JSONArr.Add(TimeToISOTime(FloatToDateTime(Value.AsExtended)));
end;
pdtString:
begin

View File

@ -180,7 +180,7 @@ begin
if AuthHeader.StartsWith('bearer', True) then
begin
AuthToken := AuthHeader.Remove(0, 'bearer'.Length).Trim;
AuthToken := Trim(TNetEncoding.URL.URLDecode(AuthToken));
AuthToken := Trim(TNetEncoding.URL.Decode(AuthToken));
end;
// check the jwt

View File

@ -113,6 +113,10 @@ begin
begin
Result := Format('(%s != %s)', [lDBFieldName, lValue]);
end;
tkContains:
begin
Result := Format('(%s containing ''%s'')', [lDBFieldName, lValue.DeQuotedString.ToLower ])
end;
end;
end;

View File

@ -122,7 +122,7 @@ end;
function TRQLMySQLCompiler.RQLLimitToSQL(const aRQLLimit: TRQLLimit): string;
begin
Result := Format(' LIMIT %d, %d', [aRQLLimit.Start, aRQLLimit.Count]);
Result := Format(' /*limit*/ LIMIT %d, %d', [aRQLLimit.Start, aRQLLimit.Count]);
end;
function TRQLMySQLCompiler.RQLLogicOperatorToSQL(const aRQLFIlter: TRQLLogicOperator): string;
@ -162,7 +162,7 @@ function TRQLMySQLCompiler.RQLSortToSQL(const aRQLSort: TRQLSort): string;
var
I: Integer;
begin
Result := ' ORDER BY';
Result := ' /*sort*/ ORDER BY';
for I := 0 to aRQLSort.Fields.Count - 1 do
begin
if I > 0 then

View File

@ -122,7 +122,7 @@ end;
function TRQLPostgreSQLCompiler.RQLLimitToSQL(const aRQLLimit: TRQLLimit): string;
begin
Result := Format(' LIMIT %d OFFSET %d', [aRQLLimit.Count, aRQLLimit.Start]);
Result := Format(' /*limit*/ LIMIT %d OFFSET %d', [aRQLLimit.Count, aRQLLimit.Start]);
end;
function TRQLPostgreSQLCompiler.RQLLogicOperatorToSQL(const aRQLFIlter: TRQLLogicOperator): string;
@ -162,7 +162,7 @@ function TRQLPostgreSQLCompiler.RQLSortToSQL(const aRQLSort: TRQLSort): string;
var
I: Integer;
begin
Result := ' ORDER BY';
Result := ' /*sort*/ ORDER BY';
for I := 0 to aRQLSort.Fields.Count - 1 do
begin
if I > 0 then

View File

@ -766,10 +766,19 @@ var
lChar: Char;
begin
Result := True;
lFieldValue := '';
lChar := C(0);
if IsDigit(lChar) then
if CharInSet(lChar, ['+','-']) then
begin
lFieldValue := lChar;
Skip(1);
lChar := C(0);
end;
if IsDigit(lChar) then
begin
lFieldValue := lFieldValue + lChar;
while True do
begin
Skip(1);

View File

@ -1,3 +1,4 @@
eq(name,-1)
contains(nome,"João")
sort(+last_name);limit(0,1)
limit(1,5)

View File

@ -1,7 +1,7 @@
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<ProjectGuid>{FD44EB2E-3630-4DA7-A18A-D8F572433F4E}</ProjectGuid>
<ProjectVersion>18.4</ProjectVersion>
<ProjectVersion>18.5</ProjectVersion>
<FrameworkType>VCL</FrameworkType>
<MainSource>RQL2SQL.dpr</MainSource>
<Base>True</Base>
@ -84,9 +84,9 @@
<PropertyGroup Condition="'$(Cfg_1_Win32)'!=''">
<DCC_RemoteDebug>false</DCC_RemoteDebug>
<AppEnableRuntimeThemes>true</AppEnableRuntimeThemes>
<AppEnableHighDPI>true</AppEnableHighDPI>
<VerInfo_IncludeVerInfo>true</VerInfo_IncludeVerInfo>
<VerInfo_Locale>1033</VerInfo_Locale>
<AppDPIAwarenessMode>PerMonitor</AppDPIAwarenessMode>
</PropertyGroup>
<PropertyGroup Condition="'$(Cfg_2)'!=''">
<DCC_LocalDebugSymbols>false</DCC_LocalDebugSymbols>
@ -96,7 +96,7 @@
</PropertyGroup>
<PropertyGroup Condition="'$(Cfg_2_Win32)'!=''">
<AppEnableRuntimeThemes>true</AppEnableRuntimeThemes>
<AppEnableHighDPI>true</AppEnableHighDPI>
<AppDPIAwarenessMode>PerMonitor</AppDPIAwarenessMode>
</PropertyGroup>
<ItemGroup>
<DelphiCompile Include="$(MainSource)">
@ -154,7 +154,6 @@
<Operation>1</Operation>
</Platform>
<Platform Name="Win32">
<RemoteDir>Contents\MacOS</RemoteDir>
<Operation>0</Operation>
</Platform>
</DeployClass>
@ -164,6 +163,12 @@
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="AndroidFileProvider">
<Platform Name="Android">
<RemoteDir>res\xml</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="AndroidGDBServer">
<Platform Name="Android">
<RemoteDir>library\lib\armeabi-v7a</RemoteDir>
@ -200,6 +205,12 @@
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="AndroidSplashStylesV21">
<Platform Name="Android">
<RemoteDir>res\values-v21</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_DefaultAppIcon">
<Platform Name="Android">
<RemoteDir>res\drawable</RemoteDir>
@ -278,6 +289,11 @@
<Operation>1</Operation>
<Extensions>.framework</Extensions>
</Platform>
<Platform Name="OSX64">
<RemoteDir>Contents\MacOS</RemoteDir>
<Operation>1</Operation>
<Extensions>.framework</Extensions>
</Platform>
<Platform Name="Win32">
<Operation>0</Operation>
</Platform>
@ -300,6 +316,11 @@
<Operation>1</Operation>
<Extensions>.dylib</Extensions>
</Platform>
<Platform Name="OSX64">
<RemoteDir>Contents\MacOS</RemoteDir>
<Operation>1</Operation>
<Extensions>.dylib</Extensions>
</Platform>
<Platform Name="Win32">
<Operation>0</Operation>
<Extensions>.dll;.bpl</Extensions>
@ -323,6 +344,11 @@
<Operation>1</Operation>
<Extensions>.dylib</Extensions>
</Platform>
<Platform Name="OSX64">
<RemoteDir>Contents\MacOS</RemoteDir>
<Operation>1</Operation>
<Extensions>.dylib</Extensions>
</Platform>
<Platform Name="Win32">
<Operation>0</Operation>
<Extensions>.bpl</Extensions>
@ -345,6 +371,10 @@
<RemoteDir>Contents\Resources\StartUp\</RemoteDir>
<Operation>0</Operation>
</Platform>
<Platform Name="OSX64">
<RemoteDir>Contents\Resources\StartUp\</RemoteDir>
<Operation>0</Operation>
</Platform>
<Platform Name="Win32">
<Operation>0</Operation>
</Platform>
@ -481,23 +511,41 @@
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="ProjectOSXDebug">
<Platform Name="OSX64">
<RemoteDir>..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="ProjectOSXEntitlements">
<Platform Name="OSX32">
<RemoteDir>..\</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="OSX64">
<RemoteDir>..\</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="ProjectOSXInfoPList">
<Platform Name="OSX32">
<RemoteDir>Contents</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="OSX64">
<RemoteDir>Contents</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="ProjectOSXResource">
<Platform Name="OSX32">
<RemoteDir>Contents\Resources</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="OSX64">
<RemoteDir>Contents\Resources</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Required="true" Name="ProjectOutput">
<Platform Name="Android">
@ -520,6 +568,10 @@
<RemoteDir>Contents\MacOS</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="OSX64">
<RemoteDir>Contents\MacOS</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Win32">
<Operation>0</Operation>
</Platform>
@ -559,6 +611,7 @@
<ProjectRoot Platform="Win32" Name="$(PROJECTNAME)"/>
<ProjectRoot Platform="OSX32" Name="$(PROJECTNAME).app"/>
<ProjectRoot Platform="Android" Name="$(PROJECTNAME)"/>
<ProjectRoot Platform="OSX64" Name="$(PROJECTNAME).app"/>
<ProjectRoot Platform="iOSSimulator" Name="$(PROJECTNAME).app"/>
</Deployment>
<Platforms>
@ -573,11 +626,3 @@
<Import Project="$(APPDATA)\Embarcadero\$(BDSAPPDATABASEDIR)\$(PRODUCTVERSION)\UserTools.proj" Condition="Exists('$(APPDATA)\Embarcadero\$(BDSAPPDATABASEDIR)\$(PRODUCTVERSION)\UserTools.proj')"/>
<Import Project="$(MSBuildProjectName).deployproj" Condition="Exists('$(MSBuildProjectName).deployproj')"/>
</Project>
<!-- EurekaLog First Line
[Exception Log]
EurekaLog Version=7007
Activate=0
DeleteMapAfterCompile=0
Encrypt Password=""
EurekaLog Last Line -->

View File

@ -29,7 +29,7 @@ interface
uses
DUnitX.TestFramework,
MVCFramework.RESTClient,
MVCFramework.JSONRPC.Client;
MVCFramework.JSONRPC.Client, System.DateUtils;
const
@ -187,6 +187,7 @@ type
TJSONRPCServerTest = class(TBaseServerTest)
protected
FExecutor: IMVCJSONRPCExecutor;
FExecutor2: IMVCJSONRPCExecutor;
public
[SetUp]
procedure SetUp;
@ -202,6 +203,8 @@ type
procedure TestRequest_S_I_ret_S;
[Test]
procedure TestRequestWithParams_I_I_ret_A;
[Test]
procedure TestRequestWithParams_DT_T_ret_DT;
end;
implementation
@ -1285,6 +1288,7 @@ end;
procedure TJSONRPCServerTest.SetUp;
begin
FExecutor := TMVCJSONRPCExecutor.Create('http://localhost:9999/jsonrpc', false);
FExecutor2 := TMVCJSONRPCExecutor.Create('http://localhost:9999/jsonrpcclass', false);
end;
procedure TJSONRPCServerTest.TestRequestToNotFoundMethod;
@ -1301,6 +1305,34 @@ begin
Assert.isTrue(lResp.Error.ErrMessage.StartsWith('Method "nonexist" not found.'));
end;
procedure TJSONRPCServerTest.TestRequestWithParams_DT_T_ret_DT;
var
lReq: IJSONRPCRequest;
lRPCResp: IJSONRPCResponse;
lArr: TJDOJSONArray;
I: Integer;
x: Integer;
LRes: TDateTime;
lYear: Word;
lMonth: Word;
lDay: Word;
lHour: Word;
lMinute: Word;
lSecond: Word;
lMillisecond: Word;
begin
lReq := TJSONRPCRequest.Create;
lReq.Method := 'addtimetodatetime';
lReq.Params.Add(EncodeDate(2000, 10, 1) + EncodeTime(12, 0, 0, 0), TJSONRPCParamDataType.pdtDateTime);
lReq.Params.Add(EncodeTime(1, 0, 0, 0), TJSONRPCParamDataType.pdtTime);
lReq.RequestID := 1234;
lRPCResp := FExecutor2.ExecuteRequest(lReq);
LRes := lRPCResp.Result.AsType<TDateTime>();
DecodeDateTime(LRes, lYear, lMonth, lDay, lHour, lMinute, lSecond, lMillisecond);
Assert.AreEqual(2000, lYear);
end;
procedure TJSONRPCServerTest.TestRequestWithoutParams;
var
lReq: IJSONRPCNotification;
@ -1308,6 +1340,7 @@ begin
lReq := TJSONRPCNotification.Create;
lReq.Method := 'mynotify';
FExecutor.ExecuteNotification(lReq);
FExecutor2.ExecuteNotification(lReq);
Assert.Pass();
end;
@ -1324,6 +1357,10 @@ begin
lResp := FExecutor.ExecuteRequest(lReq);
Assert.areEqual(10, lResp.Result.AsInteger);
Assert.areEqual(1234, lResp.RequestID.AsInteger);
lResp := FExecutor2.ExecuteRequest(lReq);
Assert.areEqual(10, lResp.Result.AsInteger);
Assert.areEqual(1234, lResp.RequestID.AsInteger);
end;
procedure TJSONRPCServerTest.TestRequestWithParams_I_I_ret_A;
@ -1339,6 +1376,7 @@ begin
lReq.Params.Add(1);
lReq.Params.Add(5);
lReq.RequestID := 1234;
lRPCResp := FExecutor.ExecuteRequest(lReq);
lArr := TJDOJSONArray(lRPCResp.Result.AsObject);
x := 1;
@ -1347,6 +1385,16 @@ begin
Assert.areEqual(x, lArr[I].IntValue);
Inc(x);
end;
lRPCResp := FExecutor2.ExecuteRequest(lReq);
lArr := TJDOJSONArray(lRPCResp.Result.AsObject);
x := 1;
for I := 0 to lArr.Count - 1 do
begin
Assert.areEqual(x, lArr[I].IntValue);
Inc(x);
end;
end;
procedure TJSONRPCServerTest.TestRequestWithParams_I_I_I_ret_O;
@ -1361,9 +1409,14 @@ begin
lReq.Params.Add(4);
lReq.Params.Add(5);
lReq.RequestID := 1234;
lRPCResp := FExecutor.ExecuteRequest(lReq);
lS := (lRPCResp.Result.AsObject as TJDOJsonObject).ToJSON();
Assert.areEqual(12, TJDOJsonObject(lRPCResp.Result.AsObject).I['res']);
lRPCResp := FExecutor2.ExecuteRequest(lReq);
lS := (lRPCResp.Result.AsObject as TJDOJsonObject).ToJSON();
Assert.areEqual(12, TJDOJsonObject(lRPCResp.Result.AsObject).I['res']);
end;
procedure TJSONRPCServerTest.TestRequest_S_I_ret_S;
@ -1378,6 +1431,9 @@ begin
lReq.RequestID := 1234;
lRPCResp := FExecutor.ExecuteRequest(lReq);
Assert.areEqual('DanieleDanieleDanieleDaniele', lRPCResp.Result.AsString);
lRPCResp := FExecutor2.ExecuteRequest(lReq);
Assert.areEqual('DanieleDanieleDanieleDaniele', lRPCResp.Result.AsString);
end;
initialization

View File

@ -15,6 +15,16 @@ type
function MultiplyString(aString: string; Multiplier: Int64): string;
end;
TTestJSONRPCClass = class(TObject)
public
function Subtract(aValue1, aValue2: Int64): Integer;
procedure MyNotify;
function Add(aValue1, aValue2, aValue3: Int64): TJsonObject;
function GetListFromTo(aFrom, aTo: Int64): TJsonArray;
function MultiplyString(aString: string; Multiplier: Int64): string;
function AddTimeToDateTime(aDateTime: TDateTime; aTime: TTime): TDateTime;
end;
implementation
uses
@ -60,4 +70,50 @@ begin
Result := aValue1 - aValue2;
end;
{ TTestJSONRPCClass }
function TTestJSONRPCClass.Add(aValue1, aValue2, aValue3: Int64): TJsonObject;
begin
Result := TJsonObject.Create;
Result.I['res'] := aValue1 + aValue2 + aValue3;
end;
function TTestJSONRPCClass.AddTimeToDateTime(aDateTime: TDateTime;
aTime: TTime): TDateTime;
begin
Result := aDateTime + aTime;
end;
function TTestJSONRPCClass.GetListFromTo(aFrom, aTo: Int64): TJsonArray;
var
I: Int64;
begin
Result := TJsonArray.Create;
for I := aFrom to aTo do
Result.Add(I);
end;
function TTestJSONRPCClass.MultiplyString(aString: string;
Multiplier: Int64): string;
var
I: Integer;
begin
Result := aString;
for I := 2 to Multiplier do
begin
Result := Result + aString;
end;
end;
procedure TTestJSONRPCClass.MyNotify;
begin
// this is a notify with no parameters and no result code
Self.ClassName;
end;
function TTestJSONRPCClass.Subtract(aValue1, aValue2: Int64): Integer;
begin
Result := aValue1 - aValue2;
end;
end.

View File

@ -67,12 +67,17 @@ begin
.AddController(TTestServerControllerActionFilters)
.AddController(TTestPrivateServerControllerCustomAuth)
.AddController(TTestJSONRPCController, '/jsonrpc')
.AddController(TTestFaultController) //this will raise an exception
.PublishObject(
function: TObject
begin
Result := TTestJSONRPCClass.Create
end, '/jsonrpcclass')
.AddController(TTestFaultController) // this will raise an exception
.AddController(TTestFault2Controller,
function: TMVCController
begin
Result := TTestFault2Controller.Create; //this will raise an exception
end)
function: TMVCController
begin
Result := TTestFault2Controller.Create; // this will raise an exception
end)
.AddMiddleware(TMVCSpeedMiddleware.Create)
.AddMiddleware(TMVCBasicAuthenticationMiddleware.Create(TBasicAuthHandler.Create))
.AddMiddleware(TMVCCustomAuthenticationMiddleware.Create(TCustomAuthHandler.Create, '/system/users/logged'))